| 
									
										
										
										
											2023-05-10 09:24:05 +02:00
										 |  |  | package hscontrol | 
					
						
							| 
									
										
										
										
											2020-06-21 12:32:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-12-22 19:43:53 -07:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2020-06-21 12:32:08 +02:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2023-05-11 09:09:18 +02:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2023-09-28 12:33:53 -07:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-12-22 19:43:53 -07:00
										 |  |  | 	"html/template" | 
					
						
							| 
									
										
										
										
											2020-06-21 12:32:08 +02:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2023-06-06 17:14:56 +02:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2020-06-21 12:32:08 +02:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 12:30:41 +02:00
										 |  |  | 	"github.com/gorilla/mux" | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-22 19:43:53 -07:00
										 |  |  | type registerWebAPITemplateConfig struct { | 
					
						
							|  |  |  | 	Key string | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-02-28 00:58:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-22 19:43:53 -07:00
										 |  |  | var registerWebAPITemplate = template.Must( | 
					
						
							| 
									
										
										
										
											2022-03-08 18:34:46 +10:00
										 |  |  | 	template.New("registerweb").Parse(` | 
					
						
							|  |  |  | <html> | 
					
						
							|  |  |  | 	<head> | 
					
						
							|  |  |  | 		<title>Registration - Headscale</title> | 
					
						
							|  |  |  | 	</head> | 
					
						
							| 
									
										
										
										
											2021-02-28 00:58:09 +01:00
										 |  |  | 	<body> | 
					
						
							| 
									
										
										
										
											2022-03-08 18:34:46 +10: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> | 
					
						
							| 
									
										
										
										
											2023-01-20 01:37:37 +01:00
										 |  |  | 		<pre><code>headscale nodes register --user USERNAME --key {{.Key}}</code></pre> | 
					
						
							| 
									
										
										
										
											2021-02-28 00:58:09 +01:00
										 |  |  | 	</body> | 
					
						
							| 
									
										
										
										
											2022-03-08 18:34:46 +10:00
										 |  |  | </html> | 
					
						
							|  |  |  | `)) | 
					
						
							| 
									
										
										
										
											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. | 
					
						
							| 
									
										
										
										
											2022-06-17 16:48:04 +02:00
										 |  |  | func (h *Headscale) RegisterWebAPI( | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							| 
									
										
										
										
											2022-09-23 10:39:42 +02:00
										 |  |  | 		log.Warn().Err(err).Msg("Failed to parse incoming nodekey") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var content bytes.Buffer | 
					
						
							|  |  |  | 	if err := registerWebAPITemplate.Execute(&content, registerWebAPITemplateConfig{ | 
					
						
							| 
									
										
										
										
											2023-11-19 22:37:04 +01:00
										 |  |  | 		Key: machineKey.String(), | 
					
						
							| 
									
										
										
										
											2021-12-22 19:43:53 -07:00
										 |  |  | 	}); err != nil { | 
					
						
							|  |  |  | 		log.Error(). | 
					
						
							|  |  |  | 			Str("func", "RegisterWebAPI"). | 
					
						
							|  |  |  | 			Err(err). | 
					
						
							|  |  |  | 			Msg("Could not render register web API template") | 
					
						
							| 
									
										
										
										
											2022-06-26 11:55:37 +02:00
										 |  |  | 		writer.Header().Set("Content-Type", "text/plain; charset=utf-8") | 
					
						
							|  |  |  | 		writer.WriteHeader(http.StatusInternalServerError) | 
					
						
							| 
									
										
										
										
											2022-06-26 12:21:35 +02: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 11:55:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2021-12-22 19:43:53 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-24 21:59:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-26 11:55:37 +02:00
										 |  |  | 	writer.Header().Set("Content-Type", "text/html; charset=utf-8") | 
					
						
							|  |  |  | 	writer.WriteHeader(http.StatusOK) | 
					
						
							| 
									
										
										
										
											2022-09-23 10:39:42 +02:00
										 |  |  | 	_, err = writer.Write(content.Bytes()) | 
					
						
							| 
									
										
										
										
											2022-06-26 12:21:35 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Error(). | 
					
						
							|  |  |  | 			Caller(). | 
					
						
							|  |  |  | 			Err(err). | 
					
						
							|  |  |  | 			Msg("Failed to write response") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-28 00:58:09 +01:00
										 |  |  | } |