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"
|
2021-12-23 02:43:53 +00:00
|
|
|
"html/template"
|
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"
|
2023-05-11 07:09:18 +00:00
|
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
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
|
|
|
)
|
2021-11-14 17:31:51 +00:00
|
|
|
|
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
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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>
|
|
|
|
</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>
|
2023-01-20 00:37:37 +00:00
|
|
|
<pre><code>headscale nodes register --user USERNAME --key {{.Key}}</code></pre>
|
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)
|
|
|
|
nodeKeyStr, ok := vars["nkey"]
|
2022-09-23 08:39:42 +00:00
|
|
|
|
2023-05-11 07:09:18 +00:00
|
|
|
if !util.NodePublicKeyRegex.Match([]byte(nodeKeyStr)) {
|
2022-09-23 09:51:38 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
var nodeKey key.NodePublic
|
|
|
|
err := nodeKey.UnmarshalText(
|
2023-05-11 07:09:18 +00:00
|
|
|
[]byte(util.NodePublicKeyEnsurePrefix(nodeKeyStr)),
|
2022-09-23 08:39:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if !ok || nodeKeyStr == "" || err != nil {
|
|
|
|
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{
|
2022-08-10 13:35:26 +00:00
|
|
|
Key: nodeKeyStr,
|
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
|
|
|
}
|