mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-31 16:11:03 +01:00 
			
		
		
		
	This commit renames a bunch of files to try to make it a bit less confusing; protocol_ is now auth as they contained registration, auth and login/out flow protocol_.*_poll is now poll.go api.go and other generic handlers are now handlers.go Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
		
			
				
	
	
		
			245 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package hscontrol
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"html/template"
 | |
| 	"net/http"
 | |
| 	"strconv"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/gorilla/mux"
 | |
| 	"github.com/juanfont/headscale/hscontrol/util"
 | |
| 	"github.com/rs/zerolog/log"
 | |
| 	"tailscale.com/tailcfg"
 | |
| 	"tailscale.com/types/key"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// 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
 | |
| 
 | |
| 	// TODO(juan): remove this once https://github.com/juanfont/headscale/issues/727 is fixed.
 | |
| 	registrationHoldoff        = time.Second * 5
 | |
| 	reservedResponseHeaderSize = 4
 | |
| )
 | |
| 
 | |
| var ErrRegisterMethodCLIDoesNotSupportExpire = errors.New(
 | |
| 	"machines registered with CLI does not support expire",
 | |
| )
 | |
| 
 | |
| // 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
 | |
| 	clientCapabilityStr := req.URL.Query().Get("v")
 | |
| 	if clientCapabilityStr != "" {
 | |
| 		log.Debug().
 | |
| 			Str("handler", "/key").
 | |
| 			Str("v", clientCapabilityStr).
 | |
| 			Msg("New noise client")
 | |
| 		clientCapabilityVersion, err := strconv.Atoi(clientCapabilityStr)
 | |
| 		if err != nil {
 | |
| 			writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | |
| 			writer.WriteHeader(http.StatusBadRequest)
 | |
| 			_, err := writer.Write([]byte("Wrong params"))
 | |
| 			if err != nil {
 | |
| 				log.Error().
 | |
| 					Caller().
 | |
| 					Err(err).
 | |
| 					Msg("Failed to write response")
 | |
| 			}
 | |
| 
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		// TS2021 (Tailscale v2 protocol) requires to have a different key
 | |
| 		if clientCapabilityVersion >= NoiseCapabilityVersion {
 | |
| 			resp := tailcfg.OverTLSPublicKeyResponse{
 | |
| 				LegacyPublicKey: h.privateKey2019.Public(),
 | |
| 				PublicKey:       h.noisePrivateKey.Public(),
 | |
| 			}
 | |
| 			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
 | |
| 		}
 | |
| 	}
 | |
| 	log.Debug().
 | |
| 		Str("handler", "/key").
 | |
| 		Msg("New legacy client")
 | |
| 
 | |
| 	// Old clients don't send a 'v' parameter, so we send the legacy public key
 | |
| 	writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | |
| 	writer.WriteHeader(http.StatusOK)
 | |
| 	_, err := writer.Write([]byte(util.MachinePublicKeyStripPrefix(h.privateKey2019.Public())))
 | |
| 	if err != nil {
 | |
| 		log.Error().
 | |
| 			Caller().
 | |
| 			Err(err).
 | |
| 			Msg("Failed to write response")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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")
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err := h.db.PingDB(req.Context()); err != nil {
 | |
| 		respond(err)
 | |
| 
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	respond(nil)
 | |
| }
 | |
| 
 | |
| type registerWebAPITemplateConfig struct {
 | |
| 	Key string
 | |
| }
 | |
| 
 | |
| var registerWebAPITemplate = template.Must(
 | |
| 	template.New("registerweb").Parse(`
 | |
| <html>
 | |
| 	<head>
 | |
| 		<title>Registration - Headscale</title>
 | |
| 	</head>
 | |
| 	<body>
 | |
| 		<h1>headscale</h1>
 | |
| 		<h2>Machine registration</h2>
 | |
| 		<p>
 | |
| 			Run the command below in the headscale server to add this machine to your network:
 | |
| 		</p>
 | |
| 		<pre><code>headscale nodes register --user USERNAME --key {{.Key}}</code></pre>
 | |
| 	</body>
 | |
| </html>
 | |
| `))
 | |
| 
 | |
| // RegisterWebAPI shows a simple message in the browser to point to the CLI
 | |
| // Listens in /register/:nkey.
 | |
| //
 | |
| // This is not part of the Tailscale control API, as we could send whatever URL
 | |
| // in the RegisterResponse.AuthURL field.
 | |
| func (h *Headscale) RegisterWebAPI(
 | |
| 	writer http.ResponseWriter,
 | |
| 	req *http.Request,
 | |
| ) {
 | |
| 	vars := mux.Vars(req)
 | |
| 	nodeKeyStr, ok := vars["nkey"]
 | |
| 
 | |
| 	if !util.NodePublicKeyRegex.Match([]byte(nodeKeyStr)) {
 | |
| 		log.Warn().Str("node_key", nodeKeyStr).Msg("Invalid node key passed to registration url")
 | |
| 
 | |
| 		writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | |
| 		writer.WriteHeader(http.StatusUnauthorized)
 | |
| 		_, err := writer.Write([]byte("Unauthorized"))
 | |
| 		if err != nil {
 | |
| 			log.Error().
 | |
| 				Caller().
 | |
| 				Err(err).
 | |
| 				Msg("Failed to write response")
 | |
| 		}
 | |
| 
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// 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.
 | |
| 	var nodeKey key.NodePublic
 | |
| 	err := nodeKey.UnmarshalText(
 | |
| 		[]byte(util.NodePublicKeyEnsurePrefix(nodeKeyStr)),
 | |
| 	)
 | |
| 
 | |
| 	if !ok || nodeKeyStr == "" || err != nil {
 | |
| 		log.Warn().Err(err).Msg("Failed to parse incoming nodekey")
 | |
| 
 | |
| 		writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | |
| 		writer.WriteHeader(http.StatusBadRequest)
 | |
| 		_, err := writer.Write([]byte("Wrong params"))
 | |
| 		if err != nil {
 | |
| 			log.Error().
 | |
| 				Caller().
 | |
| 				Err(err).
 | |
| 				Msg("Failed to write response")
 | |
| 		}
 | |
| 
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var content bytes.Buffer
 | |
| 	if err := registerWebAPITemplate.Execute(&content, registerWebAPITemplateConfig{
 | |
| 		Key: nodeKeyStr,
 | |
| 	}); err != nil {
 | |
| 		log.Error().
 | |
| 			Str("func", "RegisterWebAPI").
 | |
| 			Err(err).
 | |
| 			Msg("Could not render register web API template")
 | |
| 		writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | |
| 		writer.WriteHeader(http.StatusInternalServerError)
 | |
| 		_, err = writer.Write([]byte("Could not render register web API template"))
 | |
| 		if err != nil {
 | |
| 			log.Error().
 | |
| 				Caller().
 | |
| 				Err(err).
 | |
| 				Msg("Failed to write response")
 | |
| 		}
 | |
| 
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	writer.Header().Set("Content-Type", "text/html; charset=utf-8")
 | |
| 	writer.WriteHeader(http.StatusOK)
 | |
| 	_, err = writer.Write(content.Bytes())
 | |
| 	if err != nil {
 | |
| 		log.Error().
 | |
| 			Caller().
 | |
| 			Err(err).
 | |
| 			Msg("Failed to write response")
 | |
| 	}
 | |
| }
 |