2023-05-10 07:24:05 +00:00
package hscontrol
2020-06-21 10:32:08 +00:00
import (
2021-12-23 02:43:53 +00:00
"bytes"
2020-06-21 10:32:08 +00:00
"encoding/json"
2023-05-11 07:09:18 +00:00
"errors"
2023-09-28 19:33:53 +00:00
"fmt"
2021-12-23 02:43:53 +00:00
"html/template"
2024-08-07 15:40:41 +00:00
"io"
2020-06-21 10:32:08 +00:00
"net/http"
2023-06-06 15:14:56 +00:00
"strconv"
2020-06-21 10:32:08 +00:00
"time"
2022-06-20 10:30:41 +00:00
"github.com/gorilla/mux"
2021-11-13 08:39:04 +00:00
"github.com/rs/zerolog/log"
2023-06-06 15:14:56 +00:00
"tailscale.com/tailcfg"
2022-09-23 08:39:42 +00:00
"tailscale.com/types/key"
2020-06-21 10:32:08 +00:00
)
2021-11-18 08:49:55 +00:00
const (
2023-06-06 15:14:56 +00: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 07:36:17 +00:00
// TODO(juan): remove this once https://github.com/juanfont/headscale/issues/727 is fixed.
2023-05-11 07:09:18 +00: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 19:33:53 +00: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 17:31:51 +00:00
2024-08-07 15:40:41 +00:00
// 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" )
body , err := io . ReadAll ( req . Body )
if err != nil {
log . Error ( ) .
Str ( "handler" , "/verify" ) .
Err ( err ) .
Msg ( "Cannot read request body" )
http . Error ( writer , "Internal error" , http . StatusInternalServerError )
return
}
var derpAdmitClientRequest tailcfg . DERPAdmitClientRequest
if err := json . Unmarshal ( body , & derpAdmitClientRequest ) ; err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Cannot parse derpAdmitClientRequest" )
http . Error ( writer , "Internal error" , http . StatusInternalServerError )
return
}
nodes , err := h . db . ListNodes ( )
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Cannot list nodes" )
http . Error ( writer , "Internal error" , http . StatusInternalServerError )
}
for _ , node := range nodes {
log . Debug ( ) . Str ( "node" , node . NodeKey . String ( ) ) . Msg ( "Node" )
}
allow := false
// Check if the node is in the list of nodes
for _ , node := range nodes {
if node . NodeKey == derpAdmitClientRequest . NodePublic {
allow = true
break
}
}
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 15:14:56 +00: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 19:33:53 +00: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 15:14:56 +00:00
}
2023-09-28 19:33:53 +00:00
2023-06-06 15:14:56 +00:00
log . Debug ( ) .
Str ( "handler" , "/key" ) .
2023-11-23 07:31:33 +00:00
Int ( "cap_ver" , int ( capVer ) ) .
2023-09-28 19:33:53 +00:00
Msg ( "New noise client" )
// TS2021 (Tailscale v2 protocol) requires to have a different key
if capVer >= NoiseCapabilityVersion {
resp := tailcfg . OverTLSPublicKeyResponse {
2023-11-23 07:31:33 +00:00
PublicKey : h . noisePrivateKey . Public ( ) ,
2023-09-28 19:33:53 +00: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 15:14:56 +00:00
}
}
2022-07-06 11:39:10 +00: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 16:37:59 +00:00
if err := h . db . PingDB ( req . Context ( ) ) ; err != nil {
2022-07-06 11:39:10 +00:00
respond ( err )
return
}
respond ( nil )
}
2021-12-23 02:43:53 +00:00
type registerWebAPITemplateConfig struct {
Key string
}
2021-02-27 23:58:09 +00:00
2021-12-23 02:43:53 +00:00
var registerWebAPITemplate = template . Must (
2022-03-08 08:34:46 +00:00
template . New ( "registerweb" ) . Parse ( `
< html >
< head >
< title > Registration - Headscale < / title >
2024-06-15 07:40:49 +00:00
< meta name = viewport content = "width=device-width, initial-scale=1" >
< style >
body {
font - family : sans ;
}
code {
display : block ;
padding : 20 px ;
border : 1 px solid # bbb ;
background - color : # eee ;
}
< / style >
2022-03-08 08:34:46 +00:00
< / head >
2021-02-27 23:58:09 +00:00
< body >
2022-03-08 08:34:46 +00:00
< h1 > headscale < / h1 >
< h2 > Machine registration < / h2 >
< p >
Run the command below in the headscale server to add this machine to your network :
< / p >
2024-06-15 07:40:49 +00:00
< code > headscale nodes register -- user USERNAME -- key { { . Key } } < / code >
2021-02-27 23:58:09 +00:00
< / body >
2022-03-08 08:34:46 +00:00
< / html >
` ) )
2021-12-23 02:43:53 +00:00
// RegisterWebAPI shows a simple message in the browser to point to the CLI
2022-08-11 10:11:02 +00:00
// Listens in /register/:nkey.
2022-08-11 10:16:50 +00:00
//
// This is not part of the Tailscale control API, as we could send whatever URL
// in the RegisterResponse.AuthURL field.
2022-06-17 14:48:04 +00:00
func ( h * Headscale ) RegisterWebAPI (
2022-06-26 09:55:37 +00:00
writer http . ResponseWriter ,
req * http . Request ,
2022-06-17 14:48:04 +00:00
) {
2022-08-11 10:11:02 +00:00
vars := mux . Vars ( req )
2023-11-19 21:37:04 +00:00
machineKeyStr := vars [ "mkey" ]
2022-09-23 09:51:38 +00:00
2022-09-23 08:39:42 +00: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 21:37:04 +00:00
var machineKey key . MachinePublic
err := machineKey . UnmarshalText (
[ ] byte ( machineKeyStr ) ,
2022-09-23 08:39:42 +00:00
)
2023-11-19 21:37:04 +00:00
if err != nil {
2022-09-23 08:39:42 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Failed to parse incoming nodekey" )
2022-06-26 09:55:37 +00:00
writer . Header ( ) . Set ( "Content-Type" , "text/plain; charset=utf-8" )
writer . WriteHeader ( http . StatusBadRequest )
2022-06-26 10:21:35 +00:00
_ , err := writer . Write ( [ ] byte ( "Wrong params" ) )
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Failed to write response" )
}
2021-12-23 02:43:53 +00:00
return
}
var content bytes . Buffer
if err := registerWebAPITemplate . Execute ( & content , registerWebAPITemplateConfig {
2023-11-19 21:37:04 +00:00
Key : machineKey . String ( ) ,
2021-12-23 02:43:53 +00:00
} ) ; err != nil {
log . Error ( ) .
Str ( "func" , "RegisterWebAPI" ) .
Err ( err ) .
Msg ( "Could not render register web API template" )
2022-06-26 09:55:37 +00:00
writer . Header ( ) . Set ( "Content-Type" , "text/plain; charset=utf-8" )
writer . WriteHeader ( http . StatusInternalServerError )
2022-06-26 10:21:35 +00:00
_ , err = writer . Write ( [ ] byte ( "Could not render register web API template" ) )
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Failed to write response" )
}
2022-06-26 09:55:37 +00:00
return
2021-12-23 02:43:53 +00:00
}
2021-05-24 19:59:03 +00:00
2022-06-26 09:55:37 +00:00
writer . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
writer . WriteHeader ( http . StatusOK )
2022-09-23 08:39:42 +00:00
_ , err = writer . Write ( content . Bytes ( ) )
2022-06-26 10:21:35 +00:00
if err != nil {
log . Error ( ) .
Caller ( ) .
Err ( err ) .
Msg ( "Failed to write response" )
}
2021-02-27 23:58:09 +00:00
}