mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-10 17:12:33 +08:00
2bb34751d1
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
168 lines
4.1 KiB
Go
168 lines
4.1 KiB
Go
package headscale
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"html/template"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/rs/zerolog/log"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
const (
|
|
// TODO(juan): remove this once https://github.com/juanfont/headscale/issues/727 is fixed.
|
|
registrationHoldoff = time.Second * 5
|
|
reservedResponseHeaderSize = 4
|
|
RegisterMethodAuthKey = "authkey"
|
|
RegisterMethodOIDC = "oidc"
|
|
RegisterMethodCLI = "cli"
|
|
ErrRegisterMethodCLIDoesNotSupportExpire = Error(
|
|
"machines registered with CLI does not support expire",
|
|
)
|
|
)
|
|
|
|
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.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 -n NAMESPACE nodes register --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 !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(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")
|
|
}
|
|
}
|