2023-05-10 09:24:05 +02:00
package hscontrol
2020-06-21 12:32:08 +02:00
import (
"encoding/json"
2023-05-11 09:09:18 +02:00
"errors"
2023-09-28 12:33:53 -07:00
"fmt"
2024-11-22 20:23:05 +08:00
"io"
2020-06-21 12:32:08 +02:00
"net/http"
2023-06-06 17:14:56 +02:00
"strconv"
Redo OIDC configuration (#2020)
expand user, add claims to user
This commit expands the user table with additional fields that
can be retrieved from OIDC providers (and other places) and
uses this data in various tailscale response objects if it is
available.
This is the beginning of implementing
https://docs.google.com/document/d/1X85PMxIaVWDF6T_UPji3OeeUqVBcGj_uHRM5CI-AwlY/edit
trying to make OIDC more coherant and maintainable in addition
to giving the user a better experience and integration with a
provider.
remove usernames in magic dns, normalisation of emails
this commit removes the option to have usernames as part of MagicDNS
domains and headscale will now align with Tailscale, where there is a
root domain, and the machine name.
In addition, the various normalisation functions for dns names has been
made lighter not caring about username and special character that wont
occur.
Email are no longer normalised as part of the policy processing.
untagle oidc and regcache, use typed cache
This commits stops reusing the registration cache for oidc
purposes and switches the cache to be types and not use any
allowing the removal of a bunch of casting.
try to make reauth/register branches clearer in oidc
Currently there was a function that did a bunch of stuff,
finding the machine key, trying to find the node, reauthing
the node, returning some status, and it was called validate
which was very confusing.
This commit tries to split this into what to do if the node
exists, if it needs to register etc.
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 14:50:17 +02:00
"strings"
2020-06-21 12:32:08 +02:00
"time"
2024-10-04 04:39:24 -07:00
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
"github.com/chasefleming/elem-go/styles"
2022-06-20 12:30:41 +02:00
"github.com/gorilla/mux"
2024-10-04 04:39:24 -07:00
"github.com/juanfont/headscale/hscontrol/templates"
2021-11-13 08:39:04 +00:00
"github.com/rs/zerolog/log"
2023-06-06 17:14:56 +02:00
"tailscale.com/tailcfg"
2022-09-23 10:39:42 +02:00
"tailscale.com/types/key"
2020-06-21 12:32:08 +02:00
)
2021-11-18 08:49:55 +00:00
const (
2023-06-06 17:14:56 +02:00
// The CapabilityVersion is used by Tailscale clients to indicate
// their codebase version. Tailscale clients can communicate over TS2021
// from CapabilityVersion 28, but we only have good support for it
// since https://github.com/tailscale/tailscale/pull/4323 (Noise in any HTTPS port).
//
// Related to this change, there is https://github.com/tailscale/tailscale/pull/5379,
// where CapabilityVersion 39 is introduced to indicate #4323 was merged.
//
// See also https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go
NoiseCapabilityVersion = 39
2022-08-12 09:36:17 +02:00
// TODO(juan): remove this once https://github.com/juanfont/headscale/issues/727 is fixed.
2023-05-11 09:09:18 +02:00
registrationHoldoff = time . Second * 5
reservedResponseHeaderSize = 4
)
var ErrRegisterMethodCLIDoesNotSupportExpire = errors . New (
"machines registered with CLI does not support expire" ,
2021-11-18 08:49:55 +00:00
)
2023-09-28 12:33:53 -07:00
var ErrNoCapabilityVersion = errors . New ( "no capability version set" )
func parseCabailityVersion ( req * http . Request ) ( tailcfg . CapabilityVersion , error ) {
clientCapabilityStr := req . URL . Query ( ) . Get ( "v" )
if clientCapabilityStr == "" {
return 0 , ErrNoCapabilityVersion
}
clientCapabilityVersion , err := strconv . Atoi ( clientCapabilityStr )
if err != nil {
return 0 , fmt . Errorf ( "failed to parse capability version: %w" , err )
}
return tailcfg . CapabilityVersion ( clientCapabilityVersion ) , nil
}
2021-11-14 18:31:51 +01:00
2024-11-22 20:23:05 +08:00
func ( h * Headscale ) handleVerifyRequest (
req * http . Request ,
) ( bool , error ) {
body , err := io . ReadAll ( req . Body )
if err != nil {
return false , fmt . Errorf ( "cannot read request body: %w" , err )
}
var derpAdmitClientRequest tailcfg . DERPAdmitClientRequest
if err := json . Unmarshal ( body , & derpAdmitClientRequest ) ; err != nil {
return false , fmt . Errorf ( "cannot parse derpAdmitClientRequest: %w" , err )
}
nodes , err := h . db . ListNodes ( )
if err != nil {
return false , fmt . Errorf ( "cannot list nodes: %w" , err )
}
return nodes . ContainsNodeKey ( derpAdmitClientRequest . NodePublic ) , nil
}
// see https://github.com/tailscale/tailscale/blob/964282d34f06ecc06ce644769c66b0b31d118340/derp/derp_server.go#L1159, Derp use verifyClientsURL to verify whether a client is allowed to connect to the DERP server.
func ( h * Headscale ) VerifyHandler (
writer http . ResponseWriter ,
req * http . Request ,
) {
if req . Method != http . MethodPost {
http . Error ( writer , "Wrong method" , http . StatusMethodNotAllowed )
return
}
log . Debug ( ) .
Str ( "handler" , "/verify" ) .
Msg ( "verify client" )
allow , err := h . handleVerifyRequest ( req )
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Failed to verify client" )
http . Error ( writer , "Internal error" , http . StatusInternalServerError )
}
resp := tailcfg . DERPAdmitClientResponse {
Allow : allow ,
}
writer . Header ( ) . Set ( "Content-Type" , "application/json" )
writer . WriteHeader ( http . StatusOK )
err = json . NewEncoder ( writer ) . Encode ( resp )
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Failed to write response" )
}
}
2023-06-06 17:14:56 +02:00
// KeyHandler provides the Headscale pub key
// Listens in /key.
func ( h * Headscale ) KeyHandler (
writer http . ResponseWriter ,
req * http . Request ,
) {
// New Tailscale clients send a 'v' parameter to indicate the CurrentCapabilityVersion
2023-09-28 12:33:53 -07:00
capVer , err := parseCabailityVersion ( req )
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "could not get capability version" )
writer . Header ( ) . Set ( "Content-Type" , "text/plain; charset=utf-8" )
writer . WriteHeader ( http . StatusInternalServerError )
return
2023-06-06 17:14:56 +02:00
}
2023-09-28 12:33:53 -07:00
2023-06-06 17:14:56 +02:00
log . Debug ( ) .
Str ( "handler" , "/key" ) .
2023-11-23 08:31:33 +01:00
Int ( "cap_ver" , int ( capVer ) ) .
2023-09-28 12:33:53 -07:00
Msg ( "New noise client" )
// TS2021 (Tailscale v2 protocol) requires to have a different key
if capVer >= NoiseCapabilityVersion {
resp := tailcfg . OverTLSPublicKeyResponse {
2023-11-23 08:31:33 +01:00
PublicKey : h . noisePrivateKey . Public ( ) ,
2023-09-28 12:33:53 -07:00
}
writer . Header ( ) . Set ( "Content-Type" , "application/json" )
writer . WriteHeader ( http . StatusOK )
err = json . NewEncoder ( writer ) . Encode ( resp )
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Failed to write response" )
}
return
2023-06-06 17:14:56 +02:00
}
}
2022-07-06 13:39:10 +02:00
func ( h * Headscale ) HealthHandler (
writer http . ResponseWriter ,
req * http . Request ,
) {
respond := func ( err error ) {
writer . Header ( ) . Set ( "Content-Type" , "application/health+json; charset=utf-8" )
res := struct {
Status string ` json:"status" `
} {
Status : "pass" ,
}
if err != nil {
writer . WriteHeader ( http . StatusInternalServerError )
log . Error ( ) . Caller ( ) . Err ( err ) . Msg ( "health check failed" )
res . Status = "fail"
}
buf , err := json . Marshal ( res )
if err != nil {
log . Error ( ) . Caller ( ) . Err ( err ) . Msg ( "marshal failed" )
}
_ , err = writer . Write ( buf )
if err != nil {
log . Error ( ) . Caller ( ) . Err ( err ) . Msg ( "write failed" )
}
}
2023-05-21 19:37:59 +03:00
if err := h . db . PingDB ( req . Context ( ) ) ; err != nil {
2022-07-06 13:39:10 +02:00
respond ( err )
return
}
respond ( nil )
}
2024-10-04 04:39:24 -07:00
var codeStyleRegisterWebAPI = styles . Props {
styles . Display : "block" ,
styles . Padding : "20px" ,
styles . Border : "1px solid #bbb" ,
styles . BackgroundColor : "#eee" ,
2021-12-22 19:43:53 -07:00
}
2021-02-28 00:58:09 +01:00
2024-10-04 04:39:24 -07:00
func registerWebHTML ( key string ) * elem . Element {
return elem . Html ( nil ,
elem . Head (
nil ,
elem . Title ( nil , elem . Text ( "Registration - Headscale" ) ) ,
elem . Meta ( attrs . Props {
attrs . Name : "viewport" ,
attrs . Content : "width=device-width, initial-scale=1" ,
} ) ,
) ,
elem . Body ( attrs . Props {
attrs . Style : styles . Props {
styles . FontFamily : "sans" ,
} . ToInline ( ) ,
} ,
elem . H1 ( nil , elem . Text ( "headscale" ) ) ,
elem . H2 ( nil , elem . Text ( "Machine registration" ) ) ,
elem . P ( nil , elem . Text ( "Run the command below in the headscale server to add this machine to your network:" ) ) ,
elem . Code ( attrs . Props { attrs . Style : codeStyleRegisterWebAPI . ToInline ( ) } ,
elem . Text ( fmt . Sprintf ( "headscale nodes register --user USERNAME --key %s" , key ) ) ,
) ,
) ,
)
}
2021-12-22 19:43:53 -07:00
Redo OIDC configuration (#2020)
expand user, add claims to user
This commit expands the user table with additional fields that
can be retrieved from OIDC providers (and other places) and
uses this data in various tailscale response objects if it is
available.
This is the beginning of implementing
https://docs.google.com/document/d/1X85PMxIaVWDF6T_UPji3OeeUqVBcGj_uHRM5CI-AwlY/edit
trying to make OIDC more coherant and maintainable in addition
to giving the user a better experience and integration with a
provider.
remove usernames in magic dns, normalisation of emails
this commit removes the option to have usernames as part of MagicDNS
domains and headscale will now align with Tailscale, where there is a
root domain, and the machine name.
In addition, the various normalisation functions for dns names has been
made lighter not caring about username and special character that wont
occur.
Email are no longer normalised as part of the policy processing.
untagle oidc and regcache, use typed cache
This commits stops reusing the registration cache for oidc
purposes and switches the cache to be types and not use any
allowing the removal of a bunch of casting.
try to make reauth/register branches clearer in oidc
Currently there was a function that did a bunch of stuff,
finding the machine key, trying to find the node, reauthing
the node, returning some status, and it was called validate
which was very confusing.
This commit tries to split this into what to do if the node
exists, if it needs to register etc.
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 14:50:17 +02:00
type AuthProviderWeb struct {
serverURL string
}
func NewAuthProviderWeb ( serverURL string ) * AuthProviderWeb {
return & AuthProviderWeb {
serverURL : serverURL ,
}
}
func ( a * AuthProviderWeb ) AuthURL ( mKey key . MachinePublic ) string {
return fmt . Sprintf (
"%s/register/%s" ,
strings . TrimSuffix ( a . serverURL , "/" ) ,
mKey . String ( ) )
}
2021-12-22 19:43:53 -07:00
// RegisterWebAPI shows a simple message in the browser to point to the CLI
2022-08-11 12:11:02 +02:00
// Listens in /register/:nkey.
2022-08-11 12:16:50 +02:00
//
// This is not part of the Tailscale control API, as we could send whatever URL
// in the RegisterResponse.AuthURL field.
Redo OIDC configuration (#2020)
expand user, add claims to user
This commit expands the user table with additional fields that
can be retrieved from OIDC providers (and other places) and
uses this data in various tailscale response objects if it is
available.
This is the beginning of implementing
https://docs.google.com/document/d/1X85PMxIaVWDF6T_UPji3OeeUqVBcGj_uHRM5CI-AwlY/edit
trying to make OIDC more coherant and maintainable in addition
to giving the user a better experience and integration with a
provider.
remove usernames in magic dns, normalisation of emails
this commit removes the option to have usernames as part of MagicDNS
domains and headscale will now align with Tailscale, where there is a
root domain, and the machine name.
In addition, the various normalisation functions for dns names has been
made lighter not caring about username and special character that wont
occur.
Email are no longer normalised as part of the policy processing.
untagle oidc and regcache, use typed cache
This commits stops reusing the registration cache for oidc
purposes and switches the cache to be types and not use any
allowing the removal of a bunch of casting.
try to make reauth/register branches clearer in oidc
Currently there was a function that did a bunch of stuff,
finding the machine key, trying to find the node, reauthing
the node, returning some status, and it was called validate
which was very confusing.
This commit tries to split this into what to do if the node
exists, if it needs to register etc.
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 14:50:17 +02:00
func ( a * AuthProviderWeb ) RegisterHandler (
2022-06-26 11:55:37 +02:00
writer http . ResponseWriter ,
req * http . Request ,
2022-06-17 16:48:04 +02:00
) {
2022-08-11 12:11:02 +02:00
vars := mux . Vars ( req )
2023-11-19 22:37:04 +01:00
machineKeyStr := vars [ "mkey" ]
2022-09-23 11:51:38 +02:00
2022-09-23 10:39:42 +02:00
// We need to make sure we dont open for XSS style injections, if the parameter that
// is passed as a key is not parsable/validated as a NodePublic key, then fail to render
// the template and log an error.
2023-11-19 22:37:04 +01:00
var machineKey key . MachinePublic
err := machineKey . UnmarshalText (
[ ] byte ( machineKeyStr ) ,
2022-09-23 10:39:42 +02:00
)
2023-11-19 22:37:04 +01:00
if err != nil {
Redo OIDC configuration (#2020)
expand user, add claims to user
This commit expands the user table with additional fields that
can be retrieved from OIDC providers (and other places) and
uses this data in various tailscale response objects if it is
available.
This is the beginning of implementing
https://docs.google.com/document/d/1X85PMxIaVWDF6T_UPji3OeeUqVBcGj_uHRM5CI-AwlY/edit
trying to make OIDC more coherant and maintainable in addition
to giving the user a better experience and integration with a
provider.
remove usernames in magic dns, normalisation of emails
this commit removes the option to have usernames as part of MagicDNS
domains and headscale will now align with Tailscale, where there is a
root domain, and the machine name.
In addition, the various normalisation functions for dns names has been
made lighter not caring about username and special character that wont
occur.
Email are no longer normalised as part of the policy processing.
untagle oidc and regcache, use typed cache
This commits stops reusing the registration cache for oidc
purposes and switches the cache to be types and not use any
allowing the removal of a bunch of casting.
try to make reauth/register branches clearer in oidc
Currently there was a function that did a bunch of stuff,
finding the machine key, trying to find the node, reauthing
the node, returning some status, and it was called validate
which was very confusing.
This commit tries to split this into what to do if the node
exists, if it needs to register etc.
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 14:50:17 +02:00
log . Warn ( ) . Err ( err ) . Msg ( "Failed to parse incoming machinekey" )
2022-09-23 10:39:42 +02:00
2022-06-26 11:55:37 +02:00
writer . Header ( ) . Set ( "Content-Type" , "text/plain; charset=utf-8" )
writer . WriteHeader ( http . StatusBadRequest )
2022-06-26 12:21:35 +02:00
_ , err := writer . Write ( [ ] byte ( "Wrong params" ) )
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Failed to write response" )
}
2021-12-22 19:43:53 -07:00
return
}
2024-10-04 04:39:24 -07:00
writer . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
writer . WriteHeader ( http . StatusOK )
if _ , err := writer . Write ( [ ] byte ( registerWebHTML ( machineKey . String ( ) ) . Render ( ) ) ) ; err != nil {
if _ , err := writer . Write ( [ ] byte ( templates . RegisterWeb ( machineKey . String ( ) ) . Render ( ) ) ) ; err != nil {
2022-06-26 12:21:35 +02:00
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Failed to write response" )
}
}
2021-02-28 00:58:09 +01:00
}