From 367da0fcc2a0a5a271c1509d04602516f9b92086 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Fri, 17 Jun 2022 16:48:04 +0200 Subject: [PATCH 01/28] Remove Gin from simple endpoints for TS2019 --- api.go | 36 +++++++------ app.go | 16 +++--- platform_config.go | 127 +++++++++++++++++++++++---------------------- swagger.go | 28 ++++++---- 4 files changed, 111 insertions(+), 96 deletions(-) diff --git a/api.go b/api.go index 45fd7793..3d4c2b7c 100644 --- a/api.go +++ b/api.go @@ -32,12 +32,13 @@ const ( // KeyHandler provides the Headscale pub key // Listens in /key. -func (h *Headscale) KeyHandler(ctx *gin.Context) { - ctx.Data( - http.StatusOK, - "text/plain; charset=utf-8", - []byte(MachinePublicKeyStripPrefix(h.privateKey.Public())), - ) +func (h *Headscale) KeyHandler( + w http.ResponseWriter, + r *http.Request, +) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte(MachinePublicKeyStripPrefix(h.privateKey.Public()))) } type registerWebAPITemplateConfig struct { @@ -63,10 +64,15 @@ var registerWebAPITemplate = template.Must( // RegisterWebAPI shows a simple message in the browser to point to the CLI // Listens in /register. -func (h *Headscale) RegisterWebAPI(ctx *gin.Context) { - machineKeyStr := ctx.Query("key") +func (h *Headscale) RegisterWebAPI( + w http.ResponseWriter, + r *http.Request, +) { + machineKeyStr := r.URL.Query().Get("key") if machineKeyStr == "" { - ctx.String(http.StatusBadRequest, "Wrong params") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Wrong params")) return } @@ -79,14 +85,14 @@ func (h *Headscale) RegisterWebAPI(ctx *gin.Context) { Str("func", "RegisterWebAPI"). Err(err). Msg("Could not render register web API template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render register web API template"), - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render register web API template")) } - ctx.Data(http.StatusOK, "text/html; charset=utf-8", content.Bytes()) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(content.Bytes()) } // RegistrationHandler handles the actual registration process of a machine diff --git a/app.go b/app.go index 917d32b8..3d24d81c 100644 --- a/app.go +++ b/app.go @@ -397,18 +397,18 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine { "/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) }, ) - router.GET("/key", h.KeyHandler) - router.GET("/register", h.RegisterWebAPI) + router.GET("/key", gin.WrapF(h.KeyHandler)) + router.GET("/register", gin.WrapF(h.RegisterWebAPI)) router.POST("/machine/:id/map", h.PollNetMapHandler) router.POST("/machine/:id", h.RegistrationHandler) router.GET("/oidc/register/:mkey", h.RegisterOIDC) router.GET("/oidc/callback", h.OIDCCallback) - router.GET("/apple", h.AppleConfigMessage) - router.GET("/apple/:platform", h.ApplePlatformConfig) - router.GET("/windows", h.WindowsConfigMessage) - router.GET("/windows/tailscale.reg", h.WindowsRegConfig) - router.GET("/swagger", SwaggerUI) - router.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1) + router.GET("/apple", gin.WrapF(h.AppleConfigMessage)) + router.GET("/apple/:platform", gin.WrapF(h.ApplePlatformConfig)) + router.GET("/windows", gin.WrapF(h.WindowsConfigMessage)) + router.GET("/windows/tailscale.reg", gin.WrapF(h.WindowsRegConfig)) + router.GET("/swagger", gin.WrapF(SwaggerUI)) + router.GET("/swagger/v1/openapiv2.json", gin.WrapF(SwaggerAPIv1)) if h.cfg.DERP.ServerEnabled { router.Any("/derp", h.DERPHandler) diff --git a/platform_config.go b/platform_config.go index d36a37ce..8407feaa 100644 --- a/platform_config.go +++ b/platform_config.go @@ -6,13 +6,15 @@ import ( "net/http" textTemplate "text/template" - "github.com/gin-gonic/gin" "github.com/gofrs/uuid" "github.com/rs/zerolog/log" ) // WindowsConfigMessage shows a simple message in the browser for how to configure the Windows Tailscale client. -func (h *Headscale) WindowsConfigMessage(ctx *gin.Context) { +func (h *Headscale) WindowsConfigMessage( + w http.ResponseWriter, + r *http.Request, +) { winTemplate := template.Must(template.New("windows").Parse(` @@ -63,20 +65,24 @@ REG ADD "HKLM\Software\Tailscale IPN" /v LoginURL /t REG_SZ /d "{{.URL}}" Str("handler", "WindowsRegConfig"). Err(err). Msg("Could not render Windows index template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render Windows index template"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render Windows index template")) return } - ctx.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes()) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(payload.Bytes()) } // WindowsRegConfig generates and serves a .reg file configured with the Headscale server address. -func (h *Headscale) WindowsRegConfig(ctx *gin.Context) { +func (h *Headscale) WindowsRegConfig( + w http.ResponseWriter, + r *http.Request, +) { config := WindowsRegistryConfig{ URL: h.cfg.ServerURL, } @@ -87,24 +93,24 @@ func (h *Headscale) WindowsRegConfig(ctx *gin.Context) { Str("handler", "WindowsRegConfig"). Err(err). Msg("Could not render Apple macOS template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render Windows registry template"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render Windows registry template")) return } - ctx.Data( - http.StatusOK, - "text/x-ms-regedit; charset=utf-8", - content.Bytes(), - ) + w.Header().Set("Content-Type", "text/x-ms-regedit; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(content.Bytes()) } // AppleConfigMessage shows a simple message in the browser to point the user to the iOS/MacOS profile and instructions for how to install it. -func (h *Headscale) AppleConfigMessage(ctx *gin.Context) { +func (h *Headscale) AppleConfigMessage( + w http.ResponseWriter, + r *http.Request, +) { appleTemplate := template.Must(template.New("apple").Parse(` @@ -165,20 +171,24 @@ func (h *Headscale) AppleConfigMessage(ctx *gin.Context) { Str("handler", "AppleMobileConfig"). Err(err). Msg("Could not render Apple index template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render Apple index template"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render Apple index template")) return } - ctx.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes()) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(payload.Bytes()) } -func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) { - platform := ctx.Param("platform") +func (h *Headscale) ApplePlatformConfig( + w http.ResponseWriter, + r *http.Request, +) { + platform := r.URL.Query().Get("platform") id, err := uuid.NewV4() if err != nil { @@ -186,11 +196,10 @@ func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) { Str("handler", "ApplePlatformConfig"). Err(err). Msg("Failed not create UUID") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Failed to create UUID"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Failed to create UUID")) return } @@ -201,11 +210,10 @@ func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) { Str("handler", "ApplePlatformConfig"). Err(err). Msg("Failed not create UUID") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Failed to create UUID"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Failed to create content UUID")) return } @@ -224,11 +232,10 @@ func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) { Str("handler", "ApplePlatformConfig"). Err(err). Msg("Could not render Apple macOS template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render Apple macOS template"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render Apple macOS template")) return } @@ -238,20 +245,17 @@ func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) { Str("handler", "ApplePlatformConfig"). Err(err). Msg("Could not render Apple iOS template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render Apple iOS template"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render Apple iOS template")) return } default: - ctx.Data( - http.StatusOK, - "text/html; charset=utf-8", - []byte("Invalid platform, only ios and macos is supported"), - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Invalid platform, only ios and macos is supported")) return } @@ -268,20 +272,17 @@ func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) { Str("handler", "ApplePlatformConfig"). Err(err). Msg("Could not render Apple platform template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render Apple platform template"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render Apple platform template")) return } - ctx.Data( - http.StatusOK, - "application/x-apple-aspen-config; charset=utf-8", - content.Bytes(), - ) + w.Header().Set("Content-Type", "application/x-apple-aspen-config; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(content.Bytes()) } type WindowsRegistryConfig struct { diff --git a/swagger.go b/swagger.go index bad348db..473c834f 100644 --- a/swagger.go +++ b/swagger.go @@ -6,14 +6,16 @@ import ( "html/template" "net/http" - "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" ) //go:embed gen/openapiv2/headscale/v1/headscale.swagger.json var apiV1JSON []byte -func SwaggerUI(ctx *gin.Context) { +func SwaggerUI( + w http.ResponseWriter, + r *http.Request, +) { swaggerTemplate := template.Must(template.New("swagger").Parse(` @@ -52,18 +54,24 @@ func SwaggerUI(ctx *gin.Context) { Caller(). Err(err). Msg("Could not render Swagger") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render Swagger"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render Swagger")) return } - ctx.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes()) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(payload.Bytes()) } -func SwaggerAPIv1(ctx *gin.Context) { - ctx.Data(http.StatusOK, "application/json; charset=utf-8", apiV1JSON) +func SwaggerAPIv1( + w http.ResponseWriter, + r *http.Request, +) { + w.Header().Set("Content-Type", "application/json; charset=utf-88") + w.WriteHeader(http.StatusOK) + w.Write(apiV1JSON) } From d5e331a2fb65c62b693cd10186f469282db8a873 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Fri, 17 Jun 2022 17:42:17 +0200 Subject: [PATCH 02/28] Remove Gin from OIDC callback --- app.go | 2 +- oidc.go | 116 ++++++++++++++++++++++++++++++++------------------------ 2 files changed, 67 insertions(+), 51 deletions(-) diff --git a/app.go b/app.go index 3d24d81c..cfed67aa 100644 --- a/app.go +++ b/app.go @@ -402,7 +402,7 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine { router.POST("/machine/:id/map", h.PollNetMapHandler) router.POST("/machine/:id", h.RegistrationHandler) router.GET("/oidc/register/:mkey", h.RegisterOIDC) - router.GET("/oidc/callback", h.OIDCCallback) + router.GET("/oidc/callback", gin.WrapF(h.OIDCCallback)) router.GET("/apple", gin.WrapF(h.AppleConfigMessage)) router.GET("/apple/:platform", gin.WrapF(h.ApplePlatformConfig)) router.GET("/windows", gin.WrapF(h.WindowsConfigMessage)) diff --git a/oidc.go b/oidc.go index 38a1eb36..477fe78c 100644 --- a/oidc.go +++ b/oidc.go @@ -125,12 +125,17 @@ var oidcCallbackTemplate = template.Must( // TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities // TODO: Add groups information from OIDC tokens into machine HostInfo // Listens in /oidc/callback. -func (h *Headscale) OIDCCallback(ctx *gin.Context) { - code := ctx.Query("code") - state := ctx.Query("state") +func (h *Headscale) OIDCCallback( + w http.ResponseWriter, + r *http.Request, +) { + code := r.URL.Query().Get("code") + state := r.URL.Query().Get("state") if code == "" || state == "" { - ctx.String(http.StatusBadRequest, "Wrong params") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Wrong params")) return } @@ -141,7 +146,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { Err(err). Caller(). Msg("Could not exchange code for token") - ctx.String(http.StatusBadRequest, "Could not exchange code for token") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Could not exchange code for token")) return } @@ -154,7 +161,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { rawIDToken, rawIDTokenOK := oauth2Token.Extra("id_token").(string) if !rawIDTokenOK { - ctx.String(http.StatusBadRequest, "Could not extract ID Token") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Could not extract ID Token")) return } @@ -167,7 +176,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { Err(err). Caller(). Msg("failed to verify id token") - ctx.String(http.StatusBadRequest, "Failed to verify id token") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Failed to verify id token")) return } @@ -186,10 +197,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { Err(err). Caller(). Msg("Failed to decode id token claims") - ctx.String( - http.StatusBadRequest, - "Failed to decode id token claims", - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Failed to decode id token claims")) return } @@ -199,10 +209,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { if at := strings.LastIndex(claims.Email, "@"); at < 0 || !IsStringInSlice(h.cfg.OIDC.AllowedDomains, claims.Email[at+1:]) { log.Error().Msg("authenticated principal does not match any allowed domain") - ctx.String( - http.StatusBadRequest, - "unauthorized principal (domain mismatch)", - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("unauthorized principal (domain mismatch)")) return } @@ -212,7 +221,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { if len(h.cfg.OIDC.AllowedUsers) > 0 && !IsStringInSlice(h.cfg.OIDC.AllowedUsers, claims.Email) { log.Error().Msg("authenticated principal does not match any allowed user") - ctx.String(http.StatusBadRequest, "unauthorized principal (user mismatch)") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("unauthorized principal (user mismatch)")) return } @@ -223,7 +234,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { if !machineKeyFound { log.Error(). Msg("requested machine state key expired before authorisation completed") - ctx.String(http.StatusBadRequest, "state has expired") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("state has expired")) return } @@ -237,17 +250,18 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { if err != nil { log.Error(). Msg("could not parse machine public key") - ctx.String(http.StatusBadRequest, "could not parse public key") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("could not parse public key")) return } if !machineKeyOK { log.Error().Msg("could not get machine key from cache") - ctx.String( - http.StatusInternalServerError, - "could not get machine key from cache", - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("could not get machine key from cache")) return } @@ -276,14 +290,17 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { Str("type", "reauthenticate"). Err(err). Msg("Could not render OIDC callback template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render OIDC callback template"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render OIDC callback template")) + + return } - ctx.Data(http.StatusOK, "text/html; charset=utf-8", content.Bytes()) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(content.Bytes()) return } @@ -294,10 +311,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { ) if err != nil { log.Error().Err(err).Caller().Msgf("couldn't normalize email") - ctx.String( - http.StatusInternalServerError, - "couldn't normalize email", - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("couldn't normalize email")) return } @@ -314,10 +330,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { Err(err). Caller(). Msgf("could not create new namespace '%s'", namespaceName) - ctx.String( - http.StatusInternalServerError, - "could not create new namespace", - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("could not create namespace")) return } @@ -327,10 +342,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { Err(err). Str("namespace", namespaceName). Msg("could not find or create namespace") - ctx.String( - http.StatusInternalServerError, - "could not find or create namespace", - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("could not find or create namespace")) return } @@ -347,10 +361,9 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { Caller(). Err(err). Msg("could not register machine") - ctx.String( - http.StatusInternalServerError, - "could not register machine", - ) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("could not register machine")) return } @@ -365,12 +378,15 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { Str("type", "authenticate"). Err(err). Msg("Could not render OIDC callback template") - ctx.Data( - http.StatusInternalServerError, - "text/html; charset=utf-8", - []byte("Could not render OIDC callback template"), - ) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Could not render OIDC callback template")) + + return } - ctx.Data(http.StatusOK, "text/html; charset=utf-8", content.Bytes()) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(content.Bytes()) } From d89fb68a7a717483d03ab18a90f1233a36775176 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 18 Jun 2022 18:41:42 +0200 Subject: [PATCH 03/28] Switch to use gorilla's mux as muxer --- app.go | 137 +++++++++++++++++++++++++++---------------------- derp_server.go | 20 +++++--- go.mod | 1 + go.sum | 1 + 4 files changed, 92 insertions(+), 67 deletions(-) diff --git a/app.go b/app.go index cfed67aa..d8a4a609 100644 --- a/app.go +++ b/app.go @@ -18,6 +18,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/gin-gonic/gin" + "github.com/gorilla/mux" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" @@ -326,48 +327,56 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context, return handler(ctx, req) } -func (h *Headscale) httpAuthenticationMiddleware(ctx *gin.Context) { - log.Trace(). - Caller(). - Str("client_address", ctx.ClientIP()). - Msg("HTTP authentication invoked") - - authHeader := ctx.GetHeader("authorization") - - if !strings.HasPrefix(authHeader, AuthPrefix) { - log.Error(). +func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func( + w http.ResponseWriter, + r *http.Request, + ) { + log.Trace(). Caller(). - Str("client_address", ctx.ClientIP()). - Msg(`missing "Bearer " prefix in "Authorization" header`) - ctx.AbortWithStatus(http.StatusUnauthorized) + Str("client_address", r.RemoteAddr). + Msg("HTTP authentication invoked") - return - } + authHeader := r.Header.Get("X-Session-Token") - valid, err := h.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix)) - if err != nil { - log.Error(). - Caller(). - Err(err). - Str("client_address", ctx.ClientIP()). - Msg("failed to validate token") + if !strings.HasPrefix(authHeader, AuthPrefix) { + log.Error(). + Caller(). + Str("client_address", r.RemoteAddr). + Msg(`missing "Bearer " prefix in "Authorization" header`) + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Unauthorized")) - ctx.AbortWithStatus(http.StatusInternalServerError) + return + } - return - } + valid, err := h.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix)) + if err != nil { + log.Error(). + Caller(). + Err(err). + Str("client_address", r.RemoteAddr). + Msg("failed to validate token") - if !valid { - log.Info(). - Str("client_address", ctx.ClientIP()). - Msg("invalid token") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Unauthorized")) - ctx.AbortWithStatus(http.StatusUnauthorized) + return + } - return - } + if !valid { + log.Info(). + Str("client_address", r.RemoteAddr). + Msg("invalid token") - ctx.Next() + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Unauthorized")) + + return + } + + next.ServeHTTP(w, r) + }) } // ensureUnixSocketIsAbsent will check if the given path for headscales unix socket is clear @@ -390,39 +399,42 @@ func (h *Headscale) createPrometheusRouter() *gin.Engine { return promRouter } -func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine { - router := gin.Default() +func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router { + router := mux.NewRouter() - router.GET( + router.HandleFunc( "/health", - func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) }, - ) - router.GET("/key", gin.WrapF(h.KeyHandler)) - router.GET("/register", gin.WrapF(h.RegisterWebAPI)) - router.POST("/machine/:id/map", h.PollNetMapHandler) - router.POST("/machine/:id", h.RegistrationHandler) - router.GET("/oidc/register/:mkey", h.RegisterOIDC) - router.GET("/oidc/callback", gin.WrapF(h.OIDCCallback)) - router.GET("/apple", gin.WrapF(h.AppleConfigMessage)) - router.GET("/apple/:platform", gin.WrapF(h.ApplePlatformConfig)) - router.GET("/windows", gin.WrapF(h.WindowsConfigMessage)) - router.GET("/windows/tailscale.reg", gin.WrapF(h.WindowsRegConfig)) - router.GET("/swagger", gin.WrapF(SwaggerUI)) - router.GET("/swagger/v1/openapiv2.json", gin.WrapF(SwaggerAPIv1)) + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("{\"healthy\": \"ok\"}")) + }).Methods(http.MethodGet) + + router.HandleFunc("/key", h.KeyHandler).Methods(http.MethodGet) + router.HandleFunc("/register", h.RegisterWebAPI).Methods(http.MethodGet) + router.HandleFunc("/machine/:id/map", h.PollNetMapHandler).Methods(http.MethodPost) + router.HandleFunc("/machine/:id", h.RegistrationHandler).Methods(http.MethodPost) + router.HandleFunc("/oidc/register/:mkey", h.RegisterOIDC).Methods(http.MethodGet) + router.HandleFunc("/oidc/callback", h.OIDCCallback).Methods(http.MethodGet) + router.HandleFunc("/apple", h.AppleConfigMessage).Methods(http.MethodGet) + router.HandleFunc("/apple/:platform", h.ApplePlatformConfig).Methods(http.MethodGet) + router.HandleFunc("/windows", h.WindowsConfigMessage).Methods(http.MethodGet) + router.HandleFunc("/windows/tailscale.reg", h.WindowsRegConfig).Methods(http.MethodGet) + router.HandleFunc("/swagger", SwaggerUI).Methods(http.MethodGet) + router.HandleFunc("/swagger/v1/openapiv2.json", SwaggerAPIv1).Methods(http.MethodGet) if h.cfg.DERP.ServerEnabled { - router.Any("/derp", h.DERPHandler) - router.Any("/derp/probe", h.DERPProbeHandler) - router.Any("/bootstrap-dns", h.DERPBootstrapDNSHandler) + router.HandleFunc("/derp", h.DERPHandler) + router.HandleFunc("/derp/probe", h.DERPProbeHandler) + router.HandleFunc("/bootstrap-dns", h.DERPBootstrapDNSHandler) } - api := router.Group("/api") + api := router.PathPrefix("/api").Subrouter() api.Use(h.httpAuthenticationMiddleware) { - api.Any("/v1/*any", gin.WrapF(grpcMux.ServeHTTP)) + api.HandleFunc("/v1/*any", grpcMux.ServeHTTP) } - router.NoRoute(stdoutHandler) + router.PathPrefix("/").HandlerFunc(stdoutHandler) return router } @@ -811,13 +823,16 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time { } } -func stdoutHandler(ctx *gin.Context) { - body, _ := io.ReadAll(ctx.Request.Body) +func stdoutHandler( + w http.ResponseWriter, + r *http.Request, +) { + body, _ := io.ReadAll(r.Body) log.Trace(). - Interface("header", ctx.Request.Header). - Interface("proto", ctx.Request.Proto). - Interface("url", ctx.Request.URL). + Interface("header", r.Header). + Interface("proto", r.Proto). + Interface("url", r.URL). Bytes("body", body). Msg("Request did not match") } diff --git a/derp_server.go b/derp_server.go index d6fb47de..757dad56 100644 --- a/derp_server.go +++ b/derp_server.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" "tailscale.com/derp" "tailscale.com/net/stun" @@ -90,7 +89,10 @@ func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) { return localDERPregion, nil } -func (h *Headscale) DERPHandler(ctx *gin.Context) { +func (h *Headscale) DERPHandler( + w http.ResponseWriter, + r *http.Request, +) { log.Trace().Caller().Msgf("/derp request from %v", ctx.ClientIP()) up := strings.ToLower(ctx.Request.Header.Get("Upgrade")) if up != "websocket" && up != "derp" { @@ -143,7 +145,10 @@ func (h *Headscale) DERPHandler(ctx *gin.Context) { // DERPProbeHandler is the endpoint that js/wasm clients hit to measure // DERP latency, since they can't do UDP STUN queries. -func (h *Headscale) DERPProbeHandler(ctx *gin.Context) { +func (h *Headscale) DERPProbeHandler( + w http.ResponseWriter, + r *http.Request, +) { switch ctx.Request.Method { case "HEAD", "GET": ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") @@ -159,15 +164,18 @@ func (h *Headscale) DERPProbeHandler(ctx *gin.Context) { // The initial implementation is here https://github.com/tailscale/tailscale/pull/1406 // They have a cache, but not clear if that is really necessary at Headscale, uh, scale. // An example implementation is found here https://derp.tailscale.com/bootstrap-dns -func (h *Headscale) DERPBootstrapDNSHandler(ctx *gin.Context) { +func (h *Headscale) DERPBootstrapDNSHandler( + w http.ResponseWriter, + r *http.Request, +) { dnsEntries := make(map[string][]net.IP) resolvCtx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - var r net.Resolver + var resolver net.Resolver for _, region := range h.DERPMap.Regions { for _, node := range region.Nodes { // we don't care if we override some nodes - addrs, err := r.LookupIP(resolvCtx, "ip", node.HostName) + addrs, err := resolver.LookupIP(resolvCtx, "ip", node.HostName) if err != nil { log.Trace(). Caller(). diff --git a/go.mod b/go.mod index 70662579..98cde7ef 100644 --- a/go.mod +++ b/go.mod @@ -73,6 +73,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gookit/color v1.5.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect diff --git a/go.sum b/go.sum index 9423737e..b4d03b90 100644 --- a/go.sum +++ b/go.sum @@ -403,6 +403,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw= github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= From 6c9c9a401ff5112103160868cbae0526c65e34d5 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 18 Jun 2022 19:51:37 +0200 Subject: [PATCH 04/28] Remove gin from DERP server --- derp_server.go | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/derp_server.go b/derp_server.go index 757dad56..2cbfb746 100644 --- a/derp_server.go +++ b/derp_server.go @@ -2,6 +2,7 @@ package headscale import ( "context" + "encoding/json" "fmt" "net" "net/http" @@ -93,26 +94,27 @@ func (h *Headscale) DERPHandler( w http.ResponseWriter, r *http.Request, ) { - log.Trace().Caller().Msgf("/derp request from %v", ctx.ClientIP()) - up := strings.ToLower(ctx.Request.Header.Get("Upgrade")) + log.Trace().Caller().Msgf("/derp request from %v", r.RemoteAddr) + up := strings.ToLower(r.Header.Get("Upgrade")) if up != "websocket" && up != "derp" { if up != "" { log.Warn().Caller().Msgf("Weird websockets connection upgrade: %q", up) } - ctx.String(http.StatusUpgradeRequired, "DERP requires connection upgrade") + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusUpgradeRequired) + w.Write([]byte("DERP requires connection upgrade")) return } - fastStart := ctx.Request.Header.Get(fastStartHeader) == "1" + fastStart := r.Header.Get(fastStartHeader) == "1" - hijacker, ok := ctx.Writer.(http.Hijacker) + hijacker, ok := w.(http.Hijacker) if !ok { log.Error().Caller().Msg("DERP requires Hijacker interface from Gin") - ctx.String( - http.StatusInternalServerError, - "HTTP does not support general TCP support", - ) + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("HTTP does not support general TCP support")) return } @@ -120,10 +122,9 @@ func (h *Headscale) DERPHandler( netConn, conn, err := hijacker.Hijack() if err != nil { log.Error().Caller().Err(err).Msgf("Hijack failed") - ctx.String( - http.StatusInternalServerError, - "HTTP does not support general TCP support", - ) + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("HTTP does not support general TCP support")) return } @@ -149,11 +150,13 @@ func (h *Headscale) DERPProbeHandler( w http.ResponseWriter, r *http.Request, ) { - switch ctx.Request.Method { + switch r.Method { case "HEAD", "GET": - ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.WriteHeader(http.StatusOK) default: - ctx.String(http.StatusMethodNotAllowed, "bogus probe method") + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("bogus probe method")) } } @@ -187,7 +190,9 @@ func (h *Headscale) DERPBootstrapDNSHandler( dnsEntries[node.HostName] = addrs } } - ctx.JSON(http.StatusOK, dnsEntries) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(dnsEntries) } // ServeSTUN starts a STUN server on the configured addr. From e611063669e1bd3e5085957894a95b2d55fc3cf7 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 12:29:59 +0200 Subject: [PATCH 05/28] Migrate platform config out of Gin --- platform_config.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/platform_config.go b/platform_config.go index 8407feaa..e38996f2 100644 --- a/platform_config.go +++ b/platform_config.go @@ -7,6 +7,7 @@ import ( textTemplate "text/template" "github.com/gofrs/uuid" + "github.com/gorilla/mux" "github.com/rs/zerolog/log" ) @@ -188,7 +189,16 @@ func (h *Headscale) ApplePlatformConfig( w http.ResponseWriter, r *http.Request, ) { - platform := r.URL.Query().Get("platform") + vars := mux.Vars(r) + platform, ok := vars["platform"] + if !ok { + log.Error(). + Str("handler", "ApplePlatformConfig"). + Msg("No platform specified") + http.Error(w, "No platform specified", http.StatusBadRequest) + + return + } id, err := uuid.NewV4() if err != nil { From dedeb4c181c0b5661b6aba9caf4dfa7b67969ad6 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 12:30:41 +0200 Subject: [PATCH 06/28] Remove Gin from the Registration handler --- api.go | 114 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/api.go b/api.go index 3d4c2b7c..bd78a0a9 100644 --- a/api.go +++ b/api.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/gin-gonic/gin" + "github.com/gorilla/mux" "github.com/klauspost/compress/zstd" "github.com/rs/zerolog/log" "gorm.io/gorm" @@ -96,10 +96,23 @@ func (h *Headscale) RegisterWebAPI( } // RegistrationHandler handles the actual registration process of a machine -// Endpoint /machine/:id. -func (h *Headscale) RegistrationHandler(ctx *gin.Context) { - body, _ := io.ReadAll(ctx.Request.Body) - machineKeyStr := ctx.Param("id") +// Endpoint /machine/:mkey. +func (h *Headscale) RegistrationHandler( + w http.ResponseWriter, + r *http.Request, +) { + vars := mux.Vars(r) + machineKeyStr, ok := vars["mkey"] + if !ok || machineKeyStr == "" { + log.Error(). + Str("handler", "RegistrationHandler"). + Msg("No machine ID in request") + http.Error(w, "No machine ID in request", http.StatusBadRequest) + + return + } + + body, _ := io.ReadAll(r.Body) var machineKey key.MachinePublic err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr))) @@ -109,7 +122,7 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { Err(err). Msg("Cannot parse machine key") machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc() - ctx.String(http.StatusInternalServerError, "Sad!") + http.Error(w, "Cannot parse machine key", http.StatusBadRequest) return } @@ -121,7 +134,7 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { Err(err). Msg("Cannot decode message") machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc() - ctx.String(http.StatusInternalServerError, "Very sad!") + http.Error(w, "Cannot decode message", http.StatusBadRequest) return } @@ -135,7 +148,7 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { // If the machine has AuthKey set, handle registration via PreAuthKeys if req.Auth.AuthKey != "" { - h.handleAuthKey(ctx, machineKey, req) + h.handleAuthKey(w, r, machineKey, req) return } @@ -179,7 +192,7 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { registerCacheExpiration, ) - h.handleMachineRegistrationNew(ctx, machineKey, req) + h.handleMachineRegistrationNew(w, r, machineKey, req) return } @@ -195,7 +208,7 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { // The client sends an Expiry in the past if the client is requesting to expire the key (aka logout) // https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648 if !req.Expiry.IsZero() && req.Expiry.UTC().Before(now) { - h.handleMachineLogOut(ctx, machineKey, *machine) + h.handleMachineLogOut(w, r, machineKey, *machine) return } @@ -203,7 +216,7 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { // If machine is not expired, and is register, we have a already accepted this machine, // let it proceed with a valid registration if !machine.isExpired() { - h.handleMachineValidRegistration(ctx, machineKey, *machine) + h.handleMachineValidRegistration(w, r, machineKey, *machine) return } @@ -212,13 +225,13 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { // The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration if machine.NodeKey == NodePublicKeyStripPrefix(req.OldNodeKey) && !machine.isExpired() { - h.handleMachineRefreshKey(ctx, machineKey, req, *machine) + h.handleMachineRefreshKey(w, r, machineKey, req, *machine) return } // The machine has expired - h.handleMachineExpired(ctx, machineKey, req, *machine) + h.handleMachineExpired(w, r, machineKey, req, *machine) return } @@ -363,7 +376,8 @@ func (h *Headscale) getMapKeepAliveResponse( } func (h *Headscale) handleMachineLogOut( - ctx *gin.Context, + w http.ResponseWriter, + r *http.Request, machineKey key.MachinePublic, machine Machine, ) { @@ -384,15 +398,19 @@ func (h *Headscale) handleMachineLogOut( Caller(). Err(err). Msg("Cannot encode message") - ctx.String(http.StatusInternalServerError, "") + http.Error(w, "Internal server error", http.StatusInternalServerError) return } - ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(respBody) } func (h *Headscale) handleMachineValidRegistration( - ctx *gin.Context, + w http.ResponseWriter, + r *http.Request, machineKey key.MachinePublic, machine Machine, ) { @@ -416,17 +434,21 @@ func (h *Headscale) handleMachineValidRegistration( Msg("Cannot encode message") machineRegistrations.WithLabelValues("update", "web", "error", machine.Namespace.Name). Inc() - ctx.String(http.StatusInternalServerError, "") + http.Error(w, "Internal server error", http.StatusInternalServerError) return } machineRegistrations.WithLabelValues("update", "web", "success", machine.Namespace.Name). Inc() - ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(respBody) } func (h *Headscale) handleMachineExpired( - ctx *gin.Context, + w http.ResponseWriter, + r *http.Request, machineKey key.MachinePublic, registerRequest tailcfg.RegisterRequest, machine Machine, @@ -439,7 +461,7 @@ func (h *Headscale) handleMachineExpired( Msg("Machine registration has expired. Sending a authurl to register") if registerRequest.Auth.AuthKey != "" { - h.handleAuthKey(ctx, machineKey, registerRequest) + h.handleAuthKey(w, r, machineKey, registerRequest) return } @@ -460,17 +482,21 @@ func (h *Headscale) handleMachineExpired( Msg("Cannot encode message") machineRegistrations.WithLabelValues("reauth", "web", "error", machine.Namespace.Name). Inc() - ctx.String(http.StatusInternalServerError, "") + http.Error(w, "Internal server error", http.StatusInternalServerError) return } machineRegistrations.WithLabelValues("reauth", "web", "success", machine.Namespace.Name). Inc() - ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(respBody) } func (h *Headscale) handleMachineRefreshKey( - ctx *gin.Context, + w http.ResponseWriter, + r *http.Request, machineKey key.MachinePublic, registerRequest tailcfg.RegisterRequest, machine Machine, @@ -487,7 +513,7 @@ func (h *Headscale) handleMachineRefreshKey( Caller(). Err(err). Msg("Failed to update machine key in the database") - ctx.String(http.StatusInternalServerError, "Internal server error") + http.Error(w, "Internal server error", http.StatusInternalServerError) return } @@ -500,15 +526,19 @@ func (h *Headscale) handleMachineRefreshKey( Caller(). Err(err). Msg("Cannot encode message") - ctx.String(http.StatusInternalServerError, "Internal server error") + http.Error(w, "Internal server error", http.StatusInternalServerError) return } - ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(respBody) } func (h *Headscale) handleMachineRegistrationNew( - ctx *gin.Context, + w http.ResponseWriter, + r *http.Request, machineKey key.MachinePublic, registerRequest tailcfg.RegisterRequest, ) { @@ -535,16 +565,20 @@ func (h *Headscale) handleMachineRegistrationNew( Caller(). Err(err). Msg("Cannot encode message") - ctx.String(http.StatusInternalServerError, "") + http.Error(w, "Internal server error", http.StatusInternalServerError) return } - ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(respBody) } // TODO: check if any locks are needed around IP allocation. func (h *Headscale) handleAuthKey( - ctx *gin.Context, + w http.ResponseWriter, + r *http.Request, machineKey key.MachinePublic, registerRequest tailcfg.RegisterRequest, ) { @@ -573,14 +607,17 @@ func (h *Headscale) handleAuthKey( Str("machine", registerRequest.Hostinfo.Hostname). Err(err). Msg("Cannot encode message") - ctx.String(http.StatusInternalServerError, "") + http.Error(w, "Internal server error", http.StatusInternalServerError) machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). Inc() return } - ctx.Data(http.StatusUnauthorized, "application/json; charset=utf-8", respBody) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusUnauthorized) + w.Write(respBody) + log.Error(). Caller(). Str("func", "handleAuthKey"). @@ -654,10 +691,7 @@ func (h *Headscale) handleAuthKey( Msg("could not register machine") machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). Inc() - ctx.String( - http.StatusInternalServerError, - "could not register machine", - ) + http.Error(w, "Internal server error", http.StatusInternalServerError) return } @@ -677,13 +711,15 @@ func (h *Headscale) handleAuthKey( Msg("Cannot encode message") machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). Inc() - ctx.String(http.StatusInternalServerError, "Extremely sad!") + http.Error(w, "Internal server error", http.StatusInternalServerError) return } machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.Namespace.Name). Inc() - ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(respBody) log.Info(). Str("func", "handleAuthKey"). Str("machine", registerRequest.Hostinfo.Hostname). From 53e5c05b0a60a9cca9e5805e4c16325e45a6ee8d Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 12:30:51 +0200 Subject: [PATCH 07/28] Remove gin from the poll handlers --- poll.go | 145 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 74 insertions(+), 71 deletions(-) diff --git a/poll.go b/poll.go index 239f260b..1d215089 100644 --- a/poll.go +++ b/poll.go @@ -2,13 +2,14 @@ package headscale import ( "context" + "encoding/json" "errors" "fmt" "io" "net/http" "time" - "github.com/gin-gonic/gin" + "github.com/gorilla/mux" "github.com/rs/zerolog/log" "gorm.io/gorm" "tailscale.com/tailcfg" @@ -33,13 +34,25 @@ const machineNameContextKey = contextKey("machineName") // only after their first request (marked with the ReadOnly field). // // At this moment the updates are sent in a quite horrendous way, but they kinda work. -func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { +func (h *Headscale) PollNetMapHandler( + w http.ResponseWriter, + r *http.Request, +) { + vars := mux.Vars(r) + machineKeyStr, ok := vars["mkey"] + if !ok || machineKeyStr == "" { + log.Error(). + Str("handler", "PollNetMap"). + Msg("No machine key in request") + http.Error(w, "No machine key in request", http.StatusBadRequest) + + return + } log.Trace(). Str("handler", "PollNetMap"). - Str("id", ctx.Param("id")). + Str("id", machineKeyStr). Msg("PollNetMapHandler called") - body, _ := io.ReadAll(ctx.Request.Body) - machineKeyStr := ctx.Param("id") + body, _ := io.ReadAll(r.Body) var machineKey key.MachinePublic err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr))) @@ -48,7 +61,8 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { Str("handler", "PollNetMap"). Err(err). Msg("Cannot parse client key") - ctx.String(http.StatusBadRequest, "") + + http.Error(w, "Cannot parse client key", http.StatusBadRequest) return } @@ -59,7 +73,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { Str("handler", "PollNetMap"). Err(err). Msg("Cannot decode message") - ctx.String(http.StatusBadRequest, "") + http.Error(w, "Cannot decode message", http.StatusBadRequest) return } @@ -70,20 +84,21 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { log.Warn(). Str("handler", "PollNetMap"). Msgf("Ignoring request, cannot find machine with key %s", machineKey.String()) - ctx.String(http.StatusUnauthorized, "") + + http.Error(w, "", http.StatusUnauthorized) return } log.Error(). Str("handler", "PollNetMap"). Msgf("Failed to fetch machine from the database with Machine key: %s", machineKey.String()) - ctx.String(http.StatusInternalServerError, "") + http.Error(w, "", http.StatusInternalServerError) return } log.Trace(). Str("handler", "PollNetMap"). - Str("id", ctx.Param("id")). + Str("id", machineKeyStr). Str("machine", machine.Hostname). Msg("Found machine in database") @@ -120,11 +135,11 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { if err != nil { log.Error(). Str("handler", "PollNetMap"). - Str("id", ctx.Param("id")). + Str("id", machineKeyStr). Str("machine", machine.Hostname). Err(err). Msg("Failed to persist/update machine in the database") - ctx.String(http.StatusInternalServerError, ":(") + http.Error(w, "", http.StatusInternalServerError) return } @@ -134,11 +149,11 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { if err != nil { log.Error(). Str("handler", "PollNetMap"). - Str("id", ctx.Param("id")). + Str("id", machineKeyStr). Str("machine", machine.Hostname). Err(err). Msg("Failed to get Map response") - ctx.String(http.StatusInternalServerError, ":(") + http.Error(w, "", http.StatusInternalServerError) return } @@ -150,7 +165,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { // Details on the protocol can be found in https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L696 log.Debug(). Str("handler", "PollNetMap"). - Str("id", ctx.Param("id")). + Str("id", machineKeyStr). Str("machine", machine.Hostname). Bool("readOnly", req.ReadOnly). Bool("omitPeers", req.OmitPeers). @@ -162,7 +177,10 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { Str("handler", "PollNetMap"). Str("machine", machine.Hostname). Msg("Client is starting up. Probably interested in a DERP map") - ctx.Data(http.StatusOK, "application/json; charset=utf-8", data) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(data) return } @@ -177,7 +195,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { // Only create update channel if it has not been created log.Trace(). Str("handler", "PollNetMap"). - Str("id", ctx.Param("id")). + Str("id", machineKeyStr). Str("machine", machine.Hostname). Msg("Loading or creating update channel") @@ -194,8 +212,9 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { Str("handler", "PollNetMap"). Str("machine", machine.Hostname). Msg("Client sent endpoint update and is ok with a response without peer list") - ctx.Data(http.StatusOK, "application/json; charset=utf-8", data) - + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(data) // It sounds like we should update the nodes when we have received a endpoint update // even tho the comments in the tailscale code dont explicitly say so. updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "endpoint-update"). @@ -208,7 +227,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { Str("handler", "PollNetMap"). Str("machine", machine.Hostname). Msg("Ignoring request, don't know how to handle it") - ctx.String(http.StatusBadRequest, "") + http.Error(w, "", http.StatusBadRequest) return } @@ -232,7 +251,8 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { updateChan <- struct{}{} h.PollNetMapStream( - ctx, + w, + r, machine, req, machineKey, @@ -242,7 +262,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { ) log.Trace(). Str("handler", "PollNetMap"). - Str("id", ctx.Param("id")). + Str("id", machineKeyStr). Str("machine", machine.Hostname). Msg("Finished stream, closing PollNetMap session") } @@ -251,7 +271,8 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { // stream logic, ensuring we communicate updates and data // to the connected clients. func (h *Headscale) PollNetMapStream( - ctx *gin.Context, + w http.ResponseWriter, + r *http.Request, machine *Machine, mapRequest tailcfg.MapRequest, machineKey key.MachinePublic, @@ -259,41 +280,21 @@ func (h *Headscale) PollNetMapStream( keepAliveChan chan []byte, updateChan chan struct{}, ) { - { - machine, err := h.GetMachineByMachineKey(machineKey) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - log.Warn(). - Str("handler", "PollNetMap"). - Msgf("Ignoring request, cannot find machine with key %s", machineKey.String()) - ctx.String(http.StatusUnauthorized, "") + ctx := context.WithValue(context.Background(), machineNameContextKey, machine.Hostname) - return - } - log.Error(). - Str("handler", "PollNetMap"). - Msgf("Failed to fetch machine from the database with Machine key: %s", machineKey.String()) - ctx.String(http.StatusInternalServerError, "") + ctx, cancel := context.WithCancel(ctx) + defer cancel() - return - } + go h.scheduledPollWorker( + ctx, + updateChan, + keepAliveChan, + machineKey, + mapRequest, + machine, + ) - ctx := context.WithValue(ctx.Request.Context(), machineNameContextKey, machine.Hostname) - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - go h.scheduledPollWorker( - ctx, - updateChan, - keepAliveChan, - machineKey, - mapRequest, - machine, - ) - } - - ctx.Stream(func(writer io.Writer) bool { + for { log.Trace(). Str("handler", "PollNetMapStream"). Str("machine", machine.Hostname). @@ -312,7 +313,7 @@ func (h *Headscale) PollNetMapStream( Str("channel", "pollData"). Int("bytes", len(data)). Msg("Sending data received via pollData channel") - _, err := writer.Write(data) + _, err := w.Write(data) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). @@ -321,7 +322,7 @@ func (h *Headscale) PollNetMapStream( Err(err). Msg("Cannot write data") - return false + break } log.Trace(). Str("handler", "PollNetMapStream"). @@ -343,7 +344,7 @@ func (h *Headscale) PollNetMapStream( // client has been removed from database // since the stream opened, terminate connection. - return false + break } now := time.Now().UTC() machine.LastSeen = &now @@ -369,7 +370,7 @@ func (h *Headscale) PollNetMapStream( Msg("Machine entry in database updated successfully after sending pollData") } - return true + break case data := <-keepAliveChan: log.Trace(). @@ -378,7 +379,7 @@ func (h *Headscale) PollNetMapStream( Str("channel", "keepAlive"). Int("bytes", len(data)). Msg("Sending keep alive message") - _, err := writer.Write(data) + _, err := w.Write(data) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). @@ -387,7 +388,7 @@ func (h *Headscale) PollNetMapStream( Err(err). Msg("Cannot write keep alive message") - return false + break } log.Trace(). Str("handler", "PollNetMapStream"). @@ -409,7 +410,7 @@ func (h *Headscale) PollNetMapStream( // client has been removed from database // since the stream opened, terminate connection. - return false + break } now := time.Now().UTC() machine.LastSeen = &now @@ -430,7 +431,7 @@ func (h *Headscale) PollNetMapStream( Msg("Machine updated successfully after sending keep alive") } - return true + break case <-updateChan: log.Trace(). @@ -460,7 +461,7 @@ func (h *Headscale) PollNetMapStream( Err(err). Msg("Could not get the map update") } - _, err = writer.Write(data) + _, err = w.Write(data) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). @@ -471,7 +472,7 @@ func (h *Headscale) PollNetMapStream( updateRequestsSentToNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "failed"). Inc() - return false + return } log.Trace(). Str("handler", "PollNetMapStream"). @@ -499,7 +500,7 @@ func (h *Headscale) PollNetMapStream( // client has been removed from database // since the stream opened, terminate connection. - return false + return } now := time.Now().UTC() @@ -529,9 +530,9 @@ func (h *Headscale) PollNetMapStream( Msgf("%s is up to date", machine.Hostname) } - return true + return - case <-ctx.Request.Context().Done(): + case <-ctx.Done(): log.Info(). Str("handler", "PollNetMapStream"). Str("machine", machine.Hostname). @@ -550,7 +551,7 @@ func (h *Headscale) PollNetMapStream( // client has been removed from database // since the stream opened, terminate connection. - return false + break } now := time.Now().UTC() machine.LastSeen = &now @@ -564,9 +565,11 @@ func (h *Headscale) PollNetMapStream( Msg("Cannot update machine LastSeen") } - return false + break } - }) + } + + log.Info().Msgf("Closing poll loop to %s", machine.Hostname) } func (h *Headscale) scheduledPollWorker( From 396c3ecdf7085fecee1077d65dea388f34fe2b44 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 12:31:19 +0200 Subject: [PATCH 08/28] Remove Gin from the OIDC handlers --- app.go | 8 ++++---- oidc.go | 21 ++++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app.go b/app.go index d8a4a609..0587813c 100644 --- a/app.go +++ b/app.go @@ -411,12 +411,12 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router { router.HandleFunc("/key", h.KeyHandler).Methods(http.MethodGet) router.HandleFunc("/register", h.RegisterWebAPI).Methods(http.MethodGet) - router.HandleFunc("/machine/:id/map", h.PollNetMapHandler).Methods(http.MethodPost) - router.HandleFunc("/machine/:id", h.RegistrationHandler).Methods(http.MethodPost) - router.HandleFunc("/oidc/register/:mkey", h.RegisterOIDC).Methods(http.MethodGet) + router.HandleFunc("/machine/{mkey}/map", h.PollNetMapHandler).Methods(http.MethodPost) + router.HandleFunc("/machine/{mkey}", h.RegistrationHandler).Methods(http.MethodPost) + router.HandleFunc("/oidc/register/{mkey}", h.RegisterOIDC).Methods(http.MethodGet) router.HandleFunc("/oidc/callback", h.OIDCCallback).Methods(http.MethodGet) router.HandleFunc("/apple", h.AppleConfigMessage).Methods(http.MethodGet) - router.HandleFunc("/apple/:platform", h.ApplePlatformConfig).Methods(http.MethodGet) + router.HandleFunc("/apple/{platform}", h.ApplePlatformConfig).Methods(http.MethodGet) router.HandleFunc("/windows", h.WindowsConfigMessage).Methods(http.MethodGet) router.HandleFunc("/windows/tailscale.reg", h.WindowsRegConfig).Methods(http.MethodGet) router.HandleFunc("/swagger", SwaggerUI).Methods(http.MethodGet) diff --git a/oidc.go b/oidc.go index 477fe78c..67e3b467 100644 --- a/oidc.go +++ b/oidc.go @@ -13,7 +13,7 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" - "github.com/gin-gonic/gin" + "github.com/gorilla/mux" "github.com/rs/zerolog/log" "golang.org/x/oauth2" "tailscale.com/types/key" @@ -63,10 +63,17 @@ func (h *Headscale) initOIDC() error { // RegisterOIDC redirects to the OIDC provider for authentication // Puts machine key in cache so the callback can retrieve it using the oidc state param // Listens in /oidc/register/:mKey. -func (h *Headscale) RegisterOIDC(ctx *gin.Context) { - machineKeyStr := ctx.Param("mkey") - if machineKeyStr == "" { - ctx.String(http.StatusBadRequest, "Wrong params") +func (h *Headscale) RegisterOIDC( + w http.ResponseWriter, + r *http.Request, +) { + vars := mux.Vars(r) + machineKeyStr, ok := vars["mkey"] + if !ok || machineKeyStr == "" { + log.Error(). + Caller(). + Msg("Missing machine key in URL") + http.Error(w, "Missing machine key in URL", http.StatusBadRequest) return } @@ -81,7 +88,7 @@ func (h *Headscale) RegisterOIDC(ctx *gin.Context) { log.Error(). Caller(). Msg("could not read 16 bytes from rand") - ctx.String(http.StatusInternalServerError, "could not read 16 bytes from rand") + http.Error(w, "Internal server error", http.StatusInternalServerError) return } @@ -101,7 +108,7 @@ func (h *Headscale) RegisterOIDC(ctx *gin.Context) { authURL := h.oauth2Config.AuthCodeURL(stateStr, extras...) log.Debug().Msgf("Redirecting to %s for authentication", authURL) - ctx.Redirect(http.StatusFound, authURL) + http.Redirect(w, r, authURL, http.StatusFound) } type oidcCallbackTemplateConfig struct { From b0b919efb02870b6644124a711666c05e466b457 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 12:32:13 +0200 Subject: [PATCH 09/28] Added more logging to derp server --- derp_server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/derp_server.go b/derp_server.go index 2cbfb746..9f990337 100644 --- a/derp_server.go +++ b/derp_server.go @@ -30,6 +30,7 @@ type DERPServer struct { } func (h *Headscale) NewDERPServer() (*DERPServer, error) { + log.Trace().Caller().Msg("Creating new embedded DERP server") server := derp.NewServer(key.NodePrivate(*h.privateKey), log.Info().Msgf) region, err := h.generateRegionLocalDERP() if err != nil { @@ -87,6 +88,7 @@ func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) { } localDERPregion.Nodes[0].STUNPort = portSTUN + log.Info().Caller().Msgf("DERP region: %+v", localDERPregion) return localDERPregion, nil } @@ -128,6 +130,7 @@ func (h *Headscale) DERPHandler( return } + log.Trace().Caller().Msgf("Hijacked connection from %v", r.RemoteAddr) if !fastStart { pubKey := h.privateKey.Public() From dec51348e630c1567e98b44ee913051be50e1ed9 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 20:29:42 +0200 Subject: [PATCH 10/28] Minor status change --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index bd78a0a9..96a00b98 100644 --- a/api.go +++ b/api.go @@ -71,7 +71,7 @@ func (h *Headscale) RegisterWebAPI( machineKeyStr := r.URL.Query().Get("key") if machineKeyStr == "" { w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusUnauthorized) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Wrong params")) return From 73c16ffc65bb9f429ddf945cbbffb2e368945369 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 20:30:08 +0200 Subject: [PATCH 11/28] Fixed issue with the method used to send data --- poll.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/poll.go b/poll.go index 1d215089..af7edab6 100644 --- a/poll.go +++ b/poll.go @@ -2,7 +2,6 @@ package headscale import ( "context" - "encoding/json" "errors" "fmt" "io" @@ -180,7 +179,7 @@ func (h *Headscale) PollNetMapHandler( w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(data) + w.Write(data) return } @@ -214,7 +213,7 @@ func (h *Headscale) PollNetMapHandler( Msg("Client sent endpoint update and is ok with a response without peer list") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(data) + w.Write(data) // It sounds like we should update the nodes when we have received a endpoint update // even tho the comments in the tailscale code dont explicitly say so. updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "endpoint-update"). From 082fbead6613e2ecbe0514542c605bbd28092c63 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 21:12:23 +0200 Subject: [PATCH 12/28] Added mux dependency --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 98cde7ef..e10ae35e 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gin-gonic/gin v1.7.7 github.com/glebarez/sqlite v1.4.3 github.com/gofrs/uuid v4.2.0+incompatible + github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0 github.com/klauspost/compress v1.15.4 @@ -73,7 +74,6 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gookit/color v1.5.0 // indirect - github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect From 51b8c659f153a2ddcf61baea4f3a984e82c7a9df Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 21:13:12 +0200 Subject: [PATCH 13/28] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccd4f715..dcfb8b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Add configuration option to allow Tailscale clients to use a random WireGuard port. [kb/1181/firewalls](https://tailscale.com/kb/1181/firewalls) [#624](https://github.com/juanfont/headscale/pull/624) - Improve obtuse UX regarding missing configuration (`ephemeral_node_inactivity_timeout` not set) [#639](https://github.com/juanfont/headscale/pull/639) - Fix nodes being shown as 'offline' in `tailscale status` [648](https://github.com/juanfont/headscale/pull/648) +- Drop Gin as web framework in Headscale [648](https://github.com/juanfont/headscale/pull/648) ## 0.15.0 (2022-03-20) From 116bef25a7bfe1a397bc37c13f88bd87277faf59 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 21:19:49 +0200 Subject: [PATCH 14/28] Fixed wrong copy paste in Header --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 0587813c..695f18eb 100644 --- a/app.go +++ b/app.go @@ -337,7 +337,7 @@ func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler Str("client_address", r.RemoteAddr). Msg("HTTP authentication invoked") - authHeader := r.Header.Get("X-Session-Token") + authHeader := r.Header.Get("authorization") if !strings.HasPrefix(authHeader, AuthPrefix) { log.Error(). From 5e9004c4076420ed3ecf706200084bbb044d4d33 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 21:40:28 +0200 Subject: [PATCH 15/28] Fix issues in the poll loop --- poll.go | 77 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/poll.go b/poll.go index af7edab6..195a261c 100644 --- a/poll.go +++ b/poll.go @@ -293,17 +293,17 @@ func (h *Headscale) PollNetMapStream( machine, ) + log.Trace(). + Str("handler", "PollNetMapStream"). + Str("machine", machine.Hostname). + Msg("Waiting for data to stream...") + + log.Trace(). + Str("handler", "PollNetMapStream"). + Str("machine", machine.Hostname). + Msgf("pollData is %#v, keepAliveChan is %#v, updateChan is %#v", pollDataChan, keepAliveChan, updateChan) + for { - log.Trace(). - Str("handler", "PollNetMapStream"). - Str("machine", machine.Hostname). - Msg("Waiting for data to stream...") - - log.Trace(). - Str("handler", "PollNetMapStream"). - Str("machine", machine.Hostname). - Msgf("pollData is %#v, keepAliveChan is %#v, updateChan is %#v", pollDataChan, keepAliveChan, updateChan) - select { case data := <-pollDataChan: log.Trace(). @@ -321,8 +321,9 @@ func (h *Headscale) PollNetMapStream( Err(err). Msg("Cannot write data") - break + return } + log.Trace(). Str("handler", "PollNetMapStream"). Str("machine", machine.Hostname). @@ -343,7 +344,7 @@ func (h *Headscale) PollNetMapStream( // client has been removed from database // since the stream opened, terminate connection. - break + return } now := time.Now().UTC() machine.LastSeen = &now @@ -360,16 +361,16 @@ func (h *Headscale) PollNetMapStream( Str("channel", "pollData"). Err(err). Msg("Cannot update machine LastSuccessfulUpdate") - } else { - log.Trace(). - Str("handler", "PollNetMapStream"). - Str("machine", machine.Hostname). - Str("channel", "pollData"). - Int("bytes", len(data)). - Msg("Machine entry in database updated successfully after sending pollData") + + return } - break + log.Trace(). + Str("handler", "PollNetMapStream"). + Str("machine", machine.Hostname). + Str("channel", "pollData"). + Int("bytes", len(data)). + Msg("Machine entry in database updated successfully after sending data") case data := <-keepAliveChan: log.Trace(). @@ -387,8 +388,9 @@ func (h *Headscale) PollNetMapStream( Err(err). Msg("Cannot write keep alive message") - break + return } + log.Trace(). Str("handler", "PollNetMapStream"). Str("machine", machine.Hostname). @@ -409,7 +411,7 @@ func (h *Headscale) PollNetMapStream( // client has been removed from database // since the stream opened, terminate connection. - break + return } now := time.Now().UTC() machine.LastSeen = &now @@ -421,16 +423,16 @@ func (h *Headscale) PollNetMapStream( Str("channel", "keepAlive"). Err(err). Msg("Cannot update machine LastSeen") - } else { - log.Trace(). - Str("handler", "PollNetMapStream"). - Str("machine", machine.Hostname). - Str("channel", "keepAlive"). - Int("bytes", len(data)). - Msg("Machine updated successfully after sending keep alive") + + return } - break + log.Trace(). + Str("handler", "PollNetMapStream"). + Str("machine", machine.Hostname). + Str("channel", "keepAlive"). + Int("bytes", len(data)). + Msg("Machine updated successfully after sending keep alive") case <-updateChan: log.Trace(). @@ -440,6 +442,7 @@ func (h *Headscale) PollNetMapStream( Msg("Received a request for update") updateRequestsReceivedOnChannel.WithLabelValues(machine.Namespace.Name, machine.Hostname). Inc() + if h.isOutdated(machine) { var lastUpdate time.Time if machine.LastSuccessfulUpdate != nil { @@ -459,6 +462,8 @@ func (h *Headscale) PollNetMapStream( Str("channel", "update"). Err(err). Msg("Could not get the map update") + + return } _, err = w.Write(data) if err != nil { @@ -515,7 +520,10 @@ func (h *Headscale) PollNetMapStream( Str("channel", "update"). Err(err). Msg("Cannot update machine LastSuccessfulUpdate") + + return } + } else { var lastUpdate time.Time if machine.LastSuccessfulUpdate != nil { @@ -529,8 +537,6 @@ func (h *Headscale) PollNetMapStream( Msgf("%s is up to date", machine.Hostname) } - return - case <-ctx.Done(): log.Info(). Str("handler", "PollNetMapStream"). @@ -550,7 +556,7 @@ func (h *Headscale) PollNetMapStream( // client has been removed from database // since the stream opened, terminate connection. - break + return } now := time.Now().UTC() machine.LastSeen = &now @@ -564,11 +570,10 @@ func (h *Headscale) PollNetMapStream( Msg("Cannot update machine LastSeen") } - break + // The connection has been closed, so we can stop polling. + return } } - - log.Info().Msgf("Closing poll loop to %s", machine.Hostname) } func (h *Headscale) scheduledPollWorker( From d404ba102de22d3314ce114fbe057f96c3b59412 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Mon, 20 Jun 2022 21:47:02 +0200 Subject: [PATCH 16/28] Use request context to close when client disconnects --- poll.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poll.go b/poll.go index 195a261c..e214952a 100644 --- a/poll.go +++ b/poll.go @@ -279,7 +279,7 @@ func (h *Headscale) PollNetMapStream( keepAliveChan chan []byte, updateChan chan struct{}, ) { - ctx := context.WithValue(context.Background(), machineNameContextKey, machine.Hostname) + ctx := context.WithValue(r.Context(), machineNameContextKey, machine.Hostname) ctx, cancel := context.WithCancel(ctx) defer cancel() From 39b58f7d4cb68ae22c1442fc9f899a81990a1228 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 23 Jun 2022 19:40:07 +0200 Subject: [PATCH 17/28] Use a signal to close the longpolls on shutdown --- app.go | 5 +++++ poll.go | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/app.go b/app.go index 695f18eb..e6a5121a 100644 --- a/app.go +++ b/app.go @@ -93,6 +93,8 @@ type Headscale struct { registrationCache *cache.Cache ipAllocationMutex sync.Mutex + + shutdownChan chan struct{} } // Look up the TLS constant relative to user-supplied TLS client @@ -642,6 +644,7 @@ func (h *Headscale) Serve() error { Msgf("listening and serving metrics on: %s", h.cfg.MetricsAddr) // Handle common process-killing signals so we can gracefully shut down: + h.shutdownChan = make(chan struct{}) sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGHUP, @@ -679,6 +682,8 @@ func (h *Headscale) Serve() error { Str("signal", sig.String()). Msg("Received signal to stop, shutting down gracefully") + h.shutdownChan <- struct{}{} + // Gracefully shut down servers promHTTPServer.Shutdown(ctx) httpServer.Shutdown(ctx) diff --git a/poll.go b/poll.go index e214952a..5549afd8 100644 --- a/poll.go +++ b/poll.go @@ -572,6 +572,13 @@ func (h *Headscale) PollNetMapStream( // The connection has been closed, so we can stop polling. return + + case <-h.shutdownChan: + log.Info(). + Str("handler", "PollNetMapStream"). + Str("machine", machine.Hostname). + Msg("The long-poll handler is shutting down") + return } } } From 657fb208d6b382f36d38ad1ff1f86d9514774315 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 25 Jun 2022 20:47:42 +0200 Subject: [PATCH 18/28] Flush buffered data on polling --- poll.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/poll.go b/poll.go index 5549afd8..394f1aad 100644 --- a/poll.go +++ b/poll.go @@ -323,6 +323,7 @@ func (h *Headscale) PollNetMapStream( return } + w.(http.Flusher).Flush() log.Trace(). Str("handler", "PollNetMapStream"). @@ -390,6 +391,7 @@ func (h *Headscale) PollNetMapStream( return } + w.(http.Flusher).Flush() log.Trace(). Str("handler", "PollNetMapStream"). @@ -478,6 +480,8 @@ func (h *Headscale) PollNetMapStream( return } + w.(http.Flusher).Flush() + log.Trace(). Str("handler", "PollNetMapStream"). Str("machine", machine.Hostname). From 58c336e7f43fe4933e528bbbfcea4717cb1d32ce Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 11:31:31 +0200 Subject: [PATCH 19/28] updated nix flake go.sum --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index e7977e98..afa8c8bb 100644 --- a/flake.nix +++ b/flake.nix @@ -24,7 +24,7 @@ # When updating go.mod or go.sum, a new sha will need to be calculated, # update this if you have a mismatch after doing a change to thos files. - vendorSha256 = "sha256-j/hI6vP92UmcexFfzCe5fkGE8QUdQdNajSxMGib175Q="; + vendorSha256 = "sha256-T6rH+aqofFmCPxDfoA5xd3kNUJeZkT4GRyuFEnenps8="; ldflags = [ "-s" "-w" "-X github.com/juanfont/headscale/cmd/headscale/cli.Version=v${version}" ]; }; From 10cd87e5a25ae01dcf7161931086fe5166e5f7f6 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 11:43:17 +0200 Subject: [PATCH 20/28] Lint fixes 1/n --- acls.go | 2 +- db.go | 1 - derp_server.go | 25 ++++++------ machine.go | 3 +- machine_test.go | 1 + platform_config.go | 98 +++++++++++++++++++++++----------------------- poll.go | 1 - routes_test.go | 4 +- swagger.go | 26 ++++++------ 9 files changed, 81 insertions(+), 80 deletions(-) diff --git a/acls.go b/acls.go index c7a84afc..b485ce30 100644 --- a/acls.go +++ b/acls.go @@ -37,7 +37,7 @@ const ( expectedTokenItems = 2 ) -// For some reason golang.org/x/net/internal/iana is an internal package +// For some reason golang.org/x/net/internal/iana is an internal package. const ( protocolICMP = 1 // Internet Control Message protocolIGMP = 2 // Internet Group Management diff --git a/db.go b/db.go index 17d83237..b3222d0e 100644 --- a/db.go +++ b/db.go @@ -111,7 +111,6 @@ func (h *Headscale) initDB() error { Err(err). Msg("Failed to save normalized machine name in DB migration") } - } } } diff --git a/derp_server.go b/derp_server.go index 9f990337..97a6fc06 100644 --- a/derp_server.go +++ b/derp_server.go @@ -89,6 +89,7 @@ func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) { localDERPregion.Nodes[0].STUNPort = portSTUN log.Info().Caller().Msgf("DERP region: %+v", localDERPregion) + return localDERPregion, nil } @@ -150,16 +151,16 @@ func (h *Headscale) DERPHandler( // DERPProbeHandler is the endpoint that js/wasm clients hit to measure // DERP latency, since they can't do UDP STUN queries. func (h *Headscale) DERPProbeHandler( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - switch r.Method { + switch req.Method { case "HEAD", "GET": - w.Header().Set("Access-Control-Allow-Origin", "*") - w.WriteHeader(http.StatusOK) + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.WriteHeader(http.StatusOK) default: - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("bogus probe method")) + writer.WriteHeader(http.StatusMethodNotAllowed) + writer.Write([]byte("bogus probe method")) } } @@ -171,8 +172,8 @@ func (h *Headscale) DERPProbeHandler( // They have a cache, but not clear if that is really necessary at Headscale, uh, scale. // An example implementation is found here https://derp.tailscale.com/bootstrap-dns func (h *Headscale) DERPBootstrapDNSHandler( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { dnsEntries := make(map[string][]net.IP) @@ -193,9 +194,9 @@ func (h *Headscale) DERPBootstrapDNSHandler( dnsEntries[node.HostName] = addrs } } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(dnsEntries) + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) + json.NewEncoder(writer).Encode(dnsEntries) } // ServeSTUN starts a STUN server on the configured addr. diff --git a/machine.go b/machine.go index cf6b8876..1bed2955 100644 --- a/machine.go +++ b/machine.go @@ -27,6 +27,7 @@ const ( errCouldNotConvertMachineInterface = Error("failed to convert machine interface") errHostnameTooLong = Error("Hostname too long") MachineGivenNameHashLength = 8 + MachineGivenNameTrimSize = 2 ) const ( @@ -898,7 +899,7 @@ func (machine *Machine) RoutesToProto() *v1.Routes { func (h *Headscale) GenerateGivenName(suppliedName string) (string, error) { // If a hostname is or will be longer than 63 chars after adding the hash, // it needs to be trimmed. - trimmedHostnameLength := labelHostnameLength - MachineGivenNameHashLength - 2 + trimmedHostnameLength := labelHostnameLength - MachineGivenNameHashLength - MachineGivenNameTrimSize normalizedHostname, err := NormalizeToFQDNRules( suppliedName, diff --git a/machine_test.go b/machine_test.go index 48ccb153..0287b0c6 100644 --- a/machine_test.go +++ b/machine_test.go @@ -918,6 +918,7 @@ func TestHeadscale_GenerateGivenName(t *testing.T) { err, tt.wantErr, ) + return } diff --git a/platform_config.go b/platform_config.go index e38996f2..4aa58ec4 100644 --- a/platform_config.go +++ b/platform_config.go @@ -13,8 +13,8 @@ import ( // WindowsConfigMessage shows a simple message in the browser for how to configure the Windows Tailscale client. func (h *Headscale) WindowsConfigMessage( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { winTemplate := template.Must(template.New("windows").Parse(` @@ -67,22 +67,22 @@ REG ADD "HKLM\Software\Tailscale IPN" /v LoginURL /t REG_SZ /d "{{.URL}}" Err(err). Msg("Could not render Windows index template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render Windows index template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render Windows index template")) return } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(payload.Bytes()) + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(payload.Bytes()) } // WindowsRegConfig generates and serves a .reg file configured with the Headscale server address. func (h *Headscale) WindowsRegConfig( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { config := WindowsRegistryConfig{ URL: h.cfg.ServerURL, @@ -95,22 +95,22 @@ func (h *Headscale) WindowsRegConfig( Err(err). Msg("Could not render Apple macOS template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render Windows registry template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render Windows registry template")) return } - w.Header().Set("Content-Type", "text/x-ms-regedit; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(content.Bytes()) + writer.Header().Set("Content-Type", "text/x-ms-regedit; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(content.Bytes()) } // AppleConfigMessage shows a simple message in the browser to point the user to the iOS/MacOS profile and instructions for how to install it. func (h *Headscale) AppleConfigMessage( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { appleTemplate := template.Must(template.New("apple").Parse(` @@ -173,29 +173,29 @@ func (h *Headscale) AppleConfigMessage( Err(err). Msg("Could not render Apple index template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render Apple index template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render Apple index template")) return } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(payload.Bytes()) + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(payload.Bytes()) } func (h *Headscale) ApplePlatformConfig( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - vars := mux.Vars(r) + vars := mux.Vars(req) platform, ok := vars["platform"] if !ok { log.Error(). Str("handler", "ApplePlatformConfig"). Msg("No platform specified") - http.Error(w, "No platform specified", http.StatusBadRequest) + http.Error(writer, "No platform specified", http.StatusBadRequest) return } @@ -207,9 +207,9 @@ func (h *Headscale) ApplePlatformConfig( Err(err). Msg("Failed not create UUID") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Failed to create UUID")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Failed to create UUID")) return } @@ -221,9 +221,9 @@ func (h *Headscale) ApplePlatformConfig( Err(err). Msg("Failed not create UUID") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Failed to create content UUID")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Failed to create content UUID")) return } @@ -243,9 +243,9 @@ func (h *Headscale) ApplePlatformConfig( Err(err). Msg("Could not render Apple macOS template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render Apple macOS template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render Apple macOS template")) return } @@ -256,16 +256,16 @@ func (h *Headscale) ApplePlatformConfig( Err(err). Msg("Could not render Apple iOS template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render Apple iOS template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render Apple iOS template")) return } default: - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Invalid platform, only ios and macos is supported")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("Invalid platform, only ios and macos is supported")) return } @@ -283,16 +283,16 @@ func (h *Headscale) ApplePlatformConfig( Err(err). Msg("Could not render Apple platform template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render Apple platform template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render Apple platform template")) return } - w.Header().Set("Content-Type", "application/x-apple-aspen-config; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(content.Bytes()) + writer.Header().Set("Content-Type", "application/x-apple-aspen-config; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(content.Bytes()) } type WindowsRegistryConfig struct { diff --git a/poll.go b/poll.go index 394f1aad..bbe58c1c 100644 --- a/poll.go +++ b/poll.go @@ -527,7 +527,6 @@ func (h *Headscale) PollNetMapStream( return } - } else { var lastUpdate time.Time if machine.LastSuccessfulUpdate != nil { diff --git a/routes_test.go b/routes_test.go index 0108d888..89b712b5 100644 --- a/routes_test.go +++ b/routes_test.go @@ -28,7 +28,7 @@ func (s *Suite) TestGetRoutes(c *check.C) { MachineKey: "foo", NodeKey: "bar", DiscoKey: "faa", - Hostname: "test_get_route_machine", + Hostname: "test_get_route_machine", NamespaceID: namespace.ID, RegisterMethod: RegisterMethodAuthKey, AuthKeyID: uint(pak.ID), @@ -79,7 +79,7 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) { MachineKey: "foo", NodeKey: "bar", DiscoKey: "faa", - Hostname: "test_enable_route_machine", + Hostname: "test_enable_route_machine", NamespaceID: namespace.ID, RegisterMethod: RegisterMethodAuthKey, AuthKeyID: uint(pak.ID), diff --git a/swagger.go b/swagger.go index 473c834f..082117d4 100644 --- a/swagger.go +++ b/swagger.go @@ -13,8 +13,8 @@ import ( var apiV1JSON []byte func SwaggerUI( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { swaggerTemplate := template.Must(template.New("swagger").Parse(` @@ -55,23 +55,23 @@ func SwaggerUI( Err(err). Msg("Could not render Swagger") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render Swagger")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render Swagger")) return } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(payload.Bytes()) + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(payload.Bytes()) } func SwaggerAPIv1( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - w.Header().Set("Content-Type", "application/json; charset=utf-88") - w.WriteHeader(http.StatusOK) - w.Write(apiV1JSON) + writer.Header().Set("Content-Type", "application/json; charset=utf-88") + writer.WriteHeader(http.StatusOK) + writer.Write(apiV1JSON) } From a913d1b52125e470a943f00d5087e86450173625 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 11:55:37 +0200 Subject: [PATCH 21/28] Lint fixes 2/n --- api.go | 186 +++++++++++++++++++++++++------------------------ app.go | 40 +++++------ derp_server.go | 32 ++++----- oidc.go | 12 ++-- utils.go | 8 +-- 5 files changed, 140 insertions(+), 138 deletions(-) diff --git a/api.go b/api.go index 96a00b98..9ab6b6b2 100644 --- a/api.go +++ b/api.go @@ -33,12 +33,12 @@ const ( // KeyHandler provides the Headscale pub key // Listens in /key. func (h *Headscale) KeyHandler( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write([]byte(MachinePublicKeyStripPrefix(h.privateKey.Public()))) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write([]byte(MachinePublicKeyStripPrefix(h.privateKey.Public()))) } type registerWebAPITemplateConfig struct { @@ -65,14 +65,14 @@ var registerWebAPITemplate = template.Must( // RegisterWebAPI shows a simple message in the browser to point to the CLI // Listens in /register. func (h *Headscale) RegisterWebAPI( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - machineKeyStr := r.URL.Query().Get("key") + machineKeyStr := req.URL.Query().Get("key") if machineKeyStr == "" { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Wrong params")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("Wrong params")) return } @@ -85,34 +85,36 @@ func (h *Headscale) RegisterWebAPI( Str("func", "RegisterWebAPI"). Err(err). Msg("Could not render register web API template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render register web API template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render register web API template")) + + return } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(content.Bytes()) + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(content.Bytes()) } // RegistrationHandler handles the actual registration process of a machine // Endpoint /machine/:mkey. func (h *Headscale) RegistrationHandler( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - vars := mux.Vars(r) + vars := mux.Vars(req) machineKeyStr, ok := vars["mkey"] if !ok || machineKeyStr == "" { log.Error(). Str("handler", "RegistrationHandler"). Msg("No machine ID in request") - http.Error(w, "No machine ID in request", http.StatusBadRequest) + http.Error(writer, "No machine ID in request", http.StatusBadRequest) return } - body, _ := io.ReadAll(r.Body) + body, _ := io.ReadAll(req.Body) var machineKey key.MachinePublic err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr))) @@ -122,19 +124,19 @@ func (h *Headscale) RegistrationHandler( Err(err). Msg("Cannot parse machine key") machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc() - http.Error(w, "Cannot parse machine key", http.StatusBadRequest) + http.Error(writer, "Cannot parse machine key", http.StatusBadRequest) return } - req := tailcfg.RegisterRequest{} - err = decode(body, &req, &machineKey, h.privateKey) + registerRequest := tailcfg.RegisterRequest{} + err = decode(body, ®isterRequest, &machineKey, h.privateKey) if err != nil { log.Error(). Caller(). Err(err). Msg("Cannot decode message") machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc() - http.Error(w, "Cannot decode message", http.StatusBadRequest) + http.Error(writer, "Cannot decode message", http.StatusBadRequest) return } @@ -142,23 +144,23 @@ func (h *Headscale) RegistrationHandler( now := time.Now().UTC() machine, err := h.GetMachineByMachineKey(machineKey) if errors.Is(err, gorm.ErrRecordNotFound) { - log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine") + log.Info().Str("machine", registerRequest.Hostinfo.Hostname).Msg("New machine") machineKeyStr := MachinePublicKeyStripPrefix(machineKey) // If the machine has AuthKey set, handle registration via PreAuthKeys - if req.Auth.AuthKey != "" { - h.handleAuthKey(w, r, machineKey, req) + if registerRequest.Auth.AuthKey != "" { + h.handleAuthKey(writer, req, machineKey, registerRequest) return } - givenName, err := h.GenerateGivenName(req.Hostinfo.Hostname) + givenName, err := h.GenerateGivenName(registerRequest.Hostinfo.Hostname) if err != nil { log.Error(). Caller(). Str("func", "RegistrationHandler"). - Str("hostinfo.name", req.Hostinfo.Hostname). + Str("hostinfo.name", registerRequest.Hostinfo.Hostname). Err(err) return @@ -170,20 +172,20 @@ func (h *Headscale) RegistrationHandler( // happens newMachine := Machine{ MachineKey: machineKeyStr, - Hostname: req.Hostinfo.Hostname, + Hostname: registerRequest.Hostinfo.Hostname, GivenName: givenName, - NodeKey: NodePublicKeyStripPrefix(req.NodeKey), + NodeKey: NodePublicKeyStripPrefix(registerRequest.NodeKey), LastSeen: &now, Expiry: &time.Time{}, } - if !req.Expiry.IsZero() { + if !registerRequest.Expiry.IsZero() { log.Trace(). Caller(). - Str("machine", req.Hostinfo.Hostname). - Time("expiry", req.Expiry). + Str("machine", registerRequest.Hostinfo.Hostname). + Time("expiry", registerRequest.Expiry). Msg("Non-zero expiry time requested") - newMachine.Expiry = &req.Expiry + newMachine.Expiry = ®isterRequest.Expiry } h.registrationCache.Set( @@ -192,7 +194,7 @@ func (h *Headscale) RegistrationHandler( registerCacheExpiration, ) - h.handleMachineRegistrationNew(w, r, machineKey, req) + h.handleMachineRegistrationNew(writer, req, machineKey, registerRequest) return } @@ -204,11 +206,11 @@ func (h *Headscale) RegistrationHandler( // - Trying to log out (sending a expiry in the past) // - A valid, registered machine, looking for the node map // - Expired machine wanting to reauthenticate - if machine.NodeKey == NodePublicKeyStripPrefix(req.NodeKey) { + if machine.NodeKey == NodePublicKeyStripPrefix(registerRequest.NodeKey) { // The client sends an Expiry in the past if the client is requesting to expire the key (aka logout) // https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648 - if !req.Expiry.IsZero() && req.Expiry.UTC().Before(now) { - h.handleMachineLogOut(w, r, machineKey, *machine) + if !registerRequest.Expiry.IsZero() && registerRequest.Expiry.UTC().Before(now) { + h.handleMachineLogOut(writer, req, machineKey, *machine) return } @@ -216,22 +218,22 @@ func (h *Headscale) RegistrationHandler( // If machine is not expired, and is register, we have a already accepted this machine, // let it proceed with a valid registration if !machine.isExpired() { - h.handleMachineValidRegistration(w, r, machineKey, *machine) + h.handleMachineValidRegistration(writer, req, machineKey, *machine) return } } // The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration - if machine.NodeKey == NodePublicKeyStripPrefix(req.OldNodeKey) && + if machine.NodeKey == NodePublicKeyStripPrefix(registerRequest.OldNodeKey) && !machine.isExpired() { - h.handleMachineRefreshKey(w, r, machineKey, req, *machine) + h.handleMachineRefreshKey(writer, req, machineKey, registerRequest, *machine) return } // The machine has expired - h.handleMachineExpired(w, r, machineKey, req, *machine) + h.handleMachineExpired(writer, req, machineKey, registerRequest, *machine) return } @@ -239,12 +241,12 @@ func (h *Headscale) RegistrationHandler( func (h *Headscale) getMapResponse( machineKey key.MachinePublic, - req tailcfg.MapRequest, + mapRequest tailcfg.MapRequest, machine *Machine, ) ([]byte, error) { log.Trace(). Str("func", "getMapResponse"). - Str("machine", req.Hostinfo.Hostname). + Str("machine", mapRequest.Hostinfo.Hostname). Msg("Creating Map response") node, err := machine.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig, true) if err != nil { @@ -305,12 +307,12 @@ func (h *Headscale) getMapResponse( log.Trace(). Str("func", "getMapResponse"). - Str("machine", req.Hostinfo.Hostname). + Str("machine", mapRequest.Hostinfo.Hostname). // Interface("payload", resp). Msgf("Generated map response: %s", tailMapResponseToString(resp)) var respBody []byte - if req.Compress == "zstd" { + if mapRequest.Compress == "zstd" { src, err := json.Marshal(resp) if err != nil { log.Error(). @@ -376,8 +378,8 @@ func (h *Headscale) getMapKeepAliveResponse( } func (h *Headscale) handleMachineLogOut( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, machineKey key.MachinePublic, machine Machine, ) { @@ -398,19 +400,19 @@ func (h *Headscale) handleMachineLogOut( Caller(). Err(err). Msg("Cannot encode message") - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(respBody) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(respBody) } func (h *Headscale) handleMachineValidRegistration( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, machineKey key.MachinePublic, machine Machine, ) { @@ -434,21 +436,21 @@ func (h *Headscale) handleMachineValidRegistration( Msg("Cannot encode message") machineRegistrations.WithLabelValues("update", "web", "error", machine.Namespace.Name). Inc() - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } machineRegistrations.WithLabelValues("update", "web", "success", machine.Namespace.Name). Inc() - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(respBody) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(respBody) } func (h *Headscale) handleMachineExpired( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, machineKey key.MachinePublic, registerRequest tailcfg.RegisterRequest, machine Machine, @@ -461,7 +463,7 @@ func (h *Headscale) handleMachineExpired( Msg("Machine registration has expired. Sending a authurl to register") if registerRequest.Auth.AuthKey != "" { - h.handleAuthKey(w, r, machineKey, registerRequest) + h.handleAuthKey(writer, req, machineKey, registerRequest) return } @@ -482,21 +484,21 @@ func (h *Headscale) handleMachineExpired( Msg("Cannot encode message") machineRegistrations.WithLabelValues("reauth", "web", "error", machine.Namespace.Name). Inc() - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } machineRegistrations.WithLabelValues("reauth", "web", "success", machine.Namespace.Name). Inc() - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(respBody) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(respBody) } func (h *Headscale) handleMachineRefreshKey( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, machineKey key.MachinePublic, registerRequest tailcfg.RegisterRequest, machine Machine, @@ -513,7 +515,7 @@ func (h *Headscale) handleMachineRefreshKey( Caller(). Err(err). Msg("Failed to update machine key in the database") - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } @@ -526,19 +528,19 @@ func (h *Headscale) handleMachineRefreshKey( Caller(). Err(err). Msg("Cannot encode message") - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(respBody) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(respBody) } func (h *Headscale) handleMachineRegistrationNew( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, machineKey key.MachinePublic, registerRequest tailcfg.RegisterRequest, ) { @@ -565,20 +567,20 @@ func (h *Headscale) handleMachineRegistrationNew( Caller(). Err(err). Msg("Cannot encode message") - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(respBody) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(respBody) } // TODO: check if any locks are needed around IP allocation. func (h *Headscale) handleAuthKey( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, machineKey key.MachinePublic, registerRequest tailcfg.RegisterRequest, ) { @@ -607,16 +609,16 @@ func (h *Headscale) handleAuthKey( Str("machine", registerRequest.Hostinfo.Hostname). Err(err). Msg("Cannot encode message") - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). Inc() return } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusUnauthorized) - w.Write(respBody) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusUnauthorized) + writer.Write(respBody) log.Error(). Caller(). @@ -691,7 +693,7 @@ func (h *Headscale) handleAuthKey( Msg("could not register machine") machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). Inc() - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } @@ -711,15 +713,15 @@ func (h *Headscale) handleAuthKey( Msg("Cannot encode message") machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). Inc() - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.Namespace.Name). Inc() - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(respBody) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(respBody) log.Info(). Str("func", "handleAuthKey"). Str("machine", registerRequest.Hostinfo.Hostname). diff --git a/app.go b/app.go index 952a1803..9fc0ab5b 100644 --- a/app.go +++ b/app.go @@ -332,23 +332,23 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context, func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { log.Trace(). Caller(). - Str("client_address", r.RemoteAddr). + Str("client_address", req.RemoteAddr). Msg("HTTP authentication invoked") - authHeader := r.Header.Get("authorization") + authHeader := req.Header.Get("authorization") if !strings.HasPrefix(authHeader, AuthPrefix) { log.Error(). Caller(). - Str("client_address", r.RemoteAddr). + Str("client_address", req.RemoteAddr). Msg(`missing "Bearer " prefix in "Authorization" header`) - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("Unauthorized")) + writer.WriteHeader(http.StatusUnauthorized) + writer.Write([]byte("Unauthorized")) return } @@ -358,27 +358,27 @@ func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler log.Error(). Caller(). Err(err). - Str("client_address", r.RemoteAddr). + Str("client_address", req.RemoteAddr). Msg("failed to validate token") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Unauthorized")) + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Unauthorized")) return } if !valid { log.Info(). - Str("client_address", r.RemoteAddr). + Str("client_address", req.RemoteAddr). Msg("invalid token") - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("Unauthorized")) + writer.WriteHeader(http.StatusUnauthorized) + writer.Write([]byte("Unauthorized")) return } - next.ServeHTTP(w, r) + next.ServeHTTP(writer, req) }) } @@ -849,15 +849,15 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time { } func stdoutHandler( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - body, _ := io.ReadAll(r.Body) + body, _ := io.ReadAll(req.Body) log.Trace(). - Interface("header", r.Header). - Interface("proto", r.Proto). - Interface("url", r.URL). + Interface("header", req.Header). + Interface("proto", req.Proto). + Interface("url", req.URL). Bytes("body", body). Msg("Request did not match") } diff --git a/derp_server.go b/derp_server.go index 97a6fc06..fb0acba0 100644 --- a/derp_server.go +++ b/derp_server.go @@ -94,30 +94,30 @@ func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) { } func (h *Headscale) DERPHandler( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - log.Trace().Caller().Msgf("/derp request from %v", r.RemoteAddr) - up := strings.ToLower(r.Header.Get("Upgrade")) + log.Trace().Caller().Msgf("/derp request from %v", req.RemoteAddr) + up := strings.ToLower(req.Header.Get("Upgrade")) if up != "websocket" && up != "derp" { if up != "" { log.Warn().Caller().Msgf("Weird websockets connection upgrade: %q", up) } - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusUpgradeRequired) - w.Write([]byte("DERP requires connection upgrade")) + writer.Header().Set("Content-Type", "text/plain") + writer.WriteHeader(http.StatusUpgradeRequired) + writer.Write([]byte("DERP requires connection upgrade")) return } - fastStart := r.Header.Get(fastStartHeader) == "1" + fastStart := req.Header.Get(fastStartHeader) == "1" - hijacker, ok := w.(http.Hijacker) + hijacker, ok := writer.(http.Hijacker) if !ok { log.Error().Caller().Msg("DERP requires Hijacker interface from Gin") - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("HTTP does not support general TCP support")) + writer.Header().Set("Content-Type", "text/plain") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("HTTP does not support general TCP support")) return } @@ -125,13 +125,13 @@ func (h *Headscale) DERPHandler( netConn, conn, err := hijacker.Hijack() if err != nil { log.Error().Caller().Err(err).Msgf("Hijack failed") - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("HTTP does not support general TCP support")) + writer.Header().Set("Content-Type", "text/plain") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("HTTP does not support general TCP support")) return } - log.Trace().Caller().Msgf("Hijacked connection from %v", r.RemoteAddr) + log.Trace().Caller().Msgf("Hijacked connection from %v", req.RemoteAddr) if !fastStart { pubKey := h.privateKey.Public() diff --git a/oidc.go b/oidc.go index 67e3b467..441bfd84 100644 --- a/oidc.go +++ b/oidc.go @@ -64,16 +64,16 @@ func (h *Headscale) initOIDC() error { // Puts machine key in cache so the callback can retrieve it using the oidc state param // Listens in /oidc/register/:mKey. func (h *Headscale) RegisterOIDC( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - vars := mux.Vars(r) + vars := mux.Vars(req) machineKeyStr, ok := vars["mkey"] if !ok || machineKeyStr == "" { log.Error(). Caller(). Msg("Missing machine key in URL") - http.Error(w, "Missing machine key in URL", http.StatusBadRequest) + http.Error(writer, "Missing machine key in URL", http.StatusBadRequest) return } @@ -88,7 +88,7 @@ func (h *Headscale) RegisterOIDC( log.Error(). Caller(). Msg("could not read 16 bytes from rand") - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(writer, "Internal server error", http.StatusInternalServerError) return } @@ -108,7 +108,7 @@ func (h *Headscale) RegisterOIDC( authURL := h.oauth2Config.AuthCodeURL(stateStr, extras...) log.Debug().Msgf("Redirecting to %s for authentication", authURL) - http.Redirect(w, r, authURL, http.StatusFound) + http.Redirect(writer, req, authURL, http.StatusFound) } type oidcCallbackTemplateConfig struct { diff --git a/utils.go b/utils.go index fd4cda86..87930a16 100644 --- a/utils.go +++ b/utils.go @@ -324,18 +324,18 @@ func GenerateRandomStringURLSafe(n int) (string, error) { // It will return an error if the system's secure random // number generator fails to function correctly, in which // case the caller should not continue. -func GenerateRandomStringDNSSafe(n int) (string, error) { +func GenerateRandomStringDNSSafe(size int) (string, error) { var str string var err error - for len(str) < n { - str, err = GenerateRandomStringURLSafe(n) + for len(str) < size { + str, err = GenerateRandomStringURLSafe(size) if err != nil { return "", err } str = strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(str, "_", ""), "-", "")) } - return str[:n], nil + return str[:size], nil } func IsStringInSlice(slice []string, str string) bool { From c859bea0cf6d6d34ff62f928c98299c23534d4cd Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 12:01:04 +0200 Subject: [PATCH 22/28] Lint fixes 3/n --- app.go | 6 +-- oidc.go | 116 ++++++++++++++++++++++++++++---------------------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/app.go b/app.go index 9fc0ab5b..c3c626e5 100644 --- a/app.go +++ b/app.go @@ -407,9 +407,9 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router { router.HandleFunc( "/health", - func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("{\"healthy\": \"ok\"}")) + func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusOK) + writer.Write([]byte("{\"healthy\": \"ok\"}")) }).Methods(http.MethodGet) router.HandleFunc("/key", h.KeyHandler).Methods(http.MethodGet) diff --git a/oidc.go b/oidc.go index 441bfd84..171d4f80 100644 --- a/oidc.go +++ b/oidc.go @@ -133,16 +133,16 @@ var oidcCallbackTemplate = template.Must( // TODO: Add groups information from OIDC tokens into machine HostInfo // Listens in /oidc/callback. func (h *Headscale) OIDCCallback( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - code := r.URL.Query().Get("code") - state := r.URL.Query().Get("state") + code := req.URL.Query().Get("code") + state := req.URL.Query().Get("state") if code == "" || state == "" { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Wrong params")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("Wrong params")) return } @@ -153,9 +153,9 @@ func (h *Headscale) OIDCCallback( Err(err). Caller(). Msg("Could not exchange code for token") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Could not exchange code for token")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("Could not exchange code for token")) return } @@ -168,9 +168,9 @@ func (h *Headscale) OIDCCallback( rawIDToken, rawIDTokenOK := oauth2Token.Extra("id_token").(string) if !rawIDTokenOK { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Could not extract ID Token")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("Could not extract ID Token")) return } @@ -183,9 +183,9 @@ func (h *Headscale) OIDCCallback( Err(err). Caller(). Msg("failed to verify id token") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Failed to verify id token")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("Failed to verify id token")) return } @@ -204,9 +204,9 @@ func (h *Headscale) OIDCCallback( Err(err). Caller(). Msg("Failed to decode id token claims") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Failed to decode id token claims")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("Failed to decode id token claims")) return } @@ -216,9 +216,9 @@ func (h *Headscale) OIDCCallback( if at := strings.LastIndex(claims.Email, "@"); at < 0 || !IsStringInSlice(h.cfg.OIDC.AllowedDomains, claims.Email[at+1:]) { log.Error().Msg("authenticated principal does not match any allowed domain") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("unauthorized principal (domain mismatch)")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("unauthorized principal (domain mismatch)")) return } @@ -228,9 +228,9 @@ func (h *Headscale) OIDCCallback( if len(h.cfg.OIDC.AllowedUsers) > 0 && !IsStringInSlice(h.cfg.OIDC.AllowedUsers, claims.Email) { log.Error().Msg("authenticated principal does not match any allowed user") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("unauthorized principal (user mismatch)")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("unauthorized principal (user mismatch)")) return } @@ -241,9 +241,9 @@ func (h *Headscale) OIDCCallback( if !machineKeyFound { log.Error(). Msg("requested machine state key expired before authorisation completed") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("state has expired")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("state has expired")) return } @@ -257,18 +257,18 @@ func (h *Headscale) OIDCCallback( if err != nil { log.Error(). Msg("could not parse machine public key") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("could not parse public key")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("could not parse public key")) return } if !machineKeyOK { log.Error().Msg("could not get machine key from cache") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("could not get machine key from cache")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("could not get machine key from cache")) return } @@ -298,16 +298,16 @@ func (h *Headscale) OIDCCallback( Err(err). Msg("Could not render OIDC callback template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render OIDC callback template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render OIDC callback template")) return } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(content.Bytes()) + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(content.Bytes()) return } @@ -318,9 +318,9 @@ func (h *Headscale) OIDCCallback( ) if err != nil { log.Error().Err(err).Caller().Msgf("couldn't normalize email") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("couldn't normalize email")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("couldn't normalize email")) return } @@ -337,9 +337,9 @@ func (h *Headscale) OIDCCallback( Err(err). Caller(). Msgf("could not create new namespace '%s'", namespaceName) - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("could not create namespace")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("could not create namespace")) return } @@ -349,9 +349,9 @@ func (h *Headscale) OIDCCallback( Err(err). Str("namespace", namespaceName). Msg("could not find or create namespace") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("could not find or create namespace")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("could not find or create namespace")) return } @@ -368,9 +368,9 @@ func (h *Headscale) OIDCCallback( Caller(). Err(err). Msg("could not register machine") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("could not register machine")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("could not register machine")) return } @@ -386,14 +386,14 @@ func (h *Headscale) OIDCCallback( Err(err). Msg("Could not render OIDC callback template") - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Could not render OIDC callback template")) + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte("Could not render OIDC callback template")) return } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(content.Bytes()) + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(content.Bytes()) } From 03ced0ecfefdda949668974aa03ed12b12ae6c8d Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 12:06:25 +0200 Subject: [PATCH 23/28] Lint fixes 4/n --- poll.go | 87 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/poll.go b/poll.go index bbe58c1c..6a06c2fe 100644 --- a/poll.go +++ b/poll.go @@ -34,16 +34,16 @@ const machineNameContextKey = contextKey("machineName") // // At this moment the updates are sent in a quite horrendous way, but they kinda work. func (h *Headscale) PollNetMapHandler( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, ) { - vars := mux.Vars(r) + vars := mux.Vars(req) machineKeyStr, ok := vars["mkey"] if !ok || machineKeyStr == "" { log.Error(). Str("handler", "PollNetMap"). Msg("No machine key in request") - http.Error(w, "No machine key in request", http.StatusBadRequest) + http.Error(writer, "No machine key in request", http.StatusBadRequest) return } @@ -51,7 +51,7 @@ func (h *Headscale) PollNetMapHandler( Str("handler", "PollNetMap"). Str("id", machineKeyStr). Msg("PollNetMapHandler called") - body, _ := io.ReadAll(r.Body) + body, _ := io.ReadAll(req.Body) var machineKey key.MachinePublic err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr))) @@ -61,18 +61,18 @@ func (h *Headscale) PollNetMapHandler( Err(err). Msg("Cannot parse client key") - http.Error(w, "Cannot parse client key", http.StatusBadRequest) + http.Error(writer, "Cannot parse client key", http.StatusBadRequest) return } - req := tailcfg.MapRequest{} + mapRequest := tailcfg.MapRequest{} err = decode(body, &req, &machineKey, h.privateKey) if err != nil { log.Error(). Str("handler", "PollNetMap"). Err(err). Msg("Cannot decode message") - http.Error(w, "Cannot decode message", http.StatusBadRequest) + http.Error(writer, "Cannot decode message", http.StatusBadRequest) return } @@ -84,14 +84,14 @@ func (h *Headscale) PollNetMapHandler( Str("handler", "PollNetMap"). Msgf("Ignoring request, cannot find machine with key %s", machineKey.String()) - http.Error(w, "", http.StatusUnauthorized) + http.Error(writer, "", http.StatusUnauthorized) return } log.Error(). Str("handler", "PollNetMap"). Msgf("Failed to fetch machine from the database with Machine key: %s", machineKey.String()) - http.Error(w, "", http.StatusInternalServerError) + http.Error(writer, "", http.StatusInternalServerError) return } @@ -101,9 +101,9 @@ func (h *Headscale) PollNetMapHandler( Str("machine", machine.Hostname). Msg("Found machine in database") - machine.Hostname = req.Hostinfo.Hostname - machine.HostInfo = HostInfo(*req.Hostinfo) - machine.DiscoKey = DiscoPublicKeyStripPrefix(req.DiscoKey) + machine.Hostname = mapRequest.Hostinfo.Hostname + machine.HostInfo = HostInfo(*mapRequest.Hostinfo) + machine.DiscoKey = DiscoPublicKeyStripPrefix(mapRequest.DiscoKey) now := time.Now().UTC() // update ACLRules with peer informations (to update server tags if necessary) @@ -125,8 +125,8 @@ func (h *Headscale) PollNetMapHandler( // // The intended use is for clients to discover the DERP map at start-up // before their first real endpoint update. - if !req.ReadOnly { - machine.Endpoints = req.Endpoints + if !mapRequest.ReadOnly { + machine.Endpoints = mapRequest.Endpoints machine.LastSeen = &now } @@ -138,13 +138,13 @@ func (h *Headscale) PollNetMapHandler( Str("machine", machine.Hostname). Err(err). Msg("Failed to persist/update machine in the database") - http.Error(w, "", http.StatusInternalServerError) + http.Error(writer, "", http.StatusInternalServerError) return } } - data, err := h.getMapResponse(machineKey, req, machine) + data, err := h.getMapResponse(machineKey, mapRequest, machine) if err != nil { log.Error(). Str("handler", "PollNetMap"). @@ -152,7 +152,7 @@ func (h *Headscale) PollNetMapHandler( Str("machine", machine.Hostname). Err(err). Msg("Failed to get Map response") - http.Error(w, "", http.StatusInternalServerError) + http.Error(writer, "", http.StatusInternalServerError) return } @@ -166,20 +166,20 @@ func (h *Headscale) PollNetMapHandler( Str("handler", "PollNetMap"). Str("id", machineKeyStr). Str("machine", machine.Hostname). - Bool("readOnly", req.ReadOnly). - Bool("omitPeers", req.OmitPeers). - Bool("stream", req.Stream). + Bool("readOnly", mapRequest.ReadOnly). + Bool("omitPeers", mapRequest.OmitPeers). + Bool("stream", mapRequest.Stream). Msg("Client map request processed") - if req.ReadOnly { + if mapRequest.ReadOnly { log.Info(). Str("handler", "PollNetMap"). Str("machine", machine.Hostname). Msg("Client is starting up. Probably interested in a DERP map") - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(data) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(data) return } @@ -206,14 +206,14 @@ func (h *Headscale) PollNetMapHandler( keepAliveChan := make(chan []byte) - if req.OmitPeers && !req.Stream { + if mapRequest.OmitPeers && !mapRequest.Stream { log.Info(). Str("handler", "PollNetMap"). Str("machine", machine.Hostname). Msg("Client sent endpoint update and is ok with a response without peer list") - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write(data) + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + writer.Write(data) // It sounds like we should update the nodes when we have received a endpoint update // even tho the comments in the tailscale code dont explicitly say so. updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "endpoint-update"). @@ -221,12 +221,12 @@ func (h *Headscale) PollNetMapHandler( updateChan <- struct{}{} return - } else if req.OmitPeers && req.Stream { + } else if mapRequest.OmitPeers && mapRequest.Stream { log.Warn(). Str("handler", "PollNetMap"). Str("machine", machine.Hostname). Msg("Ignoring request, don't know how to handle it") - http.Error(w, "", http.StatusBadRequest) + http.Error(writer, "", http.StatusBadRequest) return } @@ -250,10 +250,10 @@ func (h *Headscale) PollNetMapHandler( updateChan <- struct{}{} h.PollNetMapStream( - w, - r, - machine, + writer, req, + machine, + mapRequest, machineKey, pollDataChan, keepAliveChan, @@ -270,8 +270,8 @@ func (h *Headscale) PollNetMapHandler( // stream logic, ensuring we communicate updates and data // to the connected clients. func (h *Headscale) PollNetMapStream( - w http.ResponseWriter, - r *http.Request, + writer http.ResponseWriter, + req *http.Request, machine *Machine, mapRequest tailcfg.MapRequest, machineKey key.MachinePublic, @@ -279,7 +279,7 @@ func (h *Headscale) PollNetMapStream( keepAliveChan chan []byte, updateChan chan struct{}, ) { - ctx := context.WithValue(r.Context(), machineNameContextKey, machine.Hostname) + ctx := context.WithValue(req.Context(), machineNameContextKey, machine.Hostname) ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -312,7 +312,7 @@ func (h *Headscale) PollNetMapStream( Str("channel", "pollData"). Int("bytes", len(data)). Msg("Sending data received via pollData channel") - _, err := w.Write(data) + _, err := writer.Write(data) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). @@ -323,7 +323,7 @@ func (h *Headscale) PollNetMapStream( return } - w.(http.Flusher).Flush() + writer.(http.Flusher).Flush() log.Trace(). Str("handler", "PollNetMapStream"). @@ -380,7 +380,7 @@ func (h *Headscale) PollNetMapStream( Str("channel", "keepAlive"). Int("bytes", len(data)). Msg("Sending keep alive message") - _, err := w.Write(data) + _, err := writer.Write(data) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). @@ -391,7 +391,7 @@ func (h *Headscale) PollNetMapStream( return } - w.(http.Flusher).Flush() + writer.(http.Flusher).Flush() log.Trace(). Str("handler", "PollNetMapStream"). @@ -467,7 +467,7 @@ func (h *Headscale) PollNetMapStream( return } - _, err = w.Write(data) + _, err = writer.Write(data) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). @@ -480,7 +480,7 @@ func (h *Headscale) PollNetMapStream( return } - w.(http.Flusher).Flush() + writer.(http.Flusher).Flush() log.Trace(). Str("handler", "PollNetMapStream"). @@ -581,6 +581,7 @@ func (h *Headscale) PollNetMapStream( Str("handler", "PollNetMapStream"). Str("machine", machine.Hostname). Msg("The long-poll handler is shutting down") + return } } From c810b24eb97b85916c42e1c70c294560f1110cab Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 12:21:35 +0200 Subject: [PATCH 24/28] Lint fixes 5/n --- api.go | 89 ++++++++++++++++++++++++---- app.go | 32 ++++++++-- derp_server.go | 40 +++++++++++-- oidc.go | 144 +++++++++++++++++++++++++++++++++++++++------ platform_config.go | 104 ++++++++++++++++++++++++++++---- poll.go | 16 ++++- swagger.go | 23 +++++++- 7 files changed, 392 insertions(+), 56 deletions(-) diff --git a/api.go b/api.go index 9ab6b6b2..9ed065f6 100644 --- a/api.go +++ b/api.go @@ -38,7 +38,13 @@ func (h *Headscale) KeyHandler( ) { writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write([]byte(MachinePublicKeyStripPrefix(h.privateKey.Public()))) + _, err := writer.Write([]byte(MachinePublicKeyStripPrefix(h.privateKey.Public()))) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } type registerWebAPITemplateConfig struct { @@ -72,7 +78,13 @@ func (h *Headscale) RegisterWebAPI( if machineKeyStr == "" { writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("Wrong params")) + _, err := writer.Write([]byte("Wrong params")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -87,14 +99,26 @@ func (h *Headscale) RegisterWebAPI( Msg("Could not render register web API template") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render register web API template")) + _, 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) - writer.Write(content.Bytes()) + _, err := writer.Write(content.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } // RegistrationHandler handles the actual registration process of a machine @@ -407,7 +431,13 @@ func (h *Headscale) handleMachineLogOut( writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(respBody) + _, err = writer.Write(respBody) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } func (h *Headscale) handleMachineValidRegistration( @@ -445,7 +475,13 @@ func (h *Headscale) handleMachineValidRegistration( writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(respBody) + _, err = writer.Write(respBody) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } func (h *Headscale) handleMachineExpired( @@ -493,7 +529,13 @@ func (h *Headscale) handleMachineExpired( writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(respBody) + _, err = writer.Write(respBody) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } func (h *Headscale) handleMachineRefreshKey( @@ -535,7 +577,13 @@ func (h *Headscale) handleMachineRefreshKey( writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(respBody) + _, err = writer.Write(respBody) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } func (h *Headscale) handleMachineRegistrationNew( @@ -574,7 +622,13 @@ func (h *Headscale) handleMachineRegistrationNew( writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(respBody) + _, err = writer.Write(respBody) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } // TODO: check if any locks are needed around IP allocation. @@ -618,7 +672,13 @@ func (h *Headscale) handleAuthKey( writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusUnauthorized) - writer.Write(respBody) + _, err = writer.Write(respBody) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } log.Error(). Caller(). @@ -721,7 +781,14 @@ func (h *Headscale) handleAuthKey( Inc() writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(respBody) + _, err = writer.Write(respBody) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } + log.Info(). Str("func", "handleAuthKey"). Str("machine", registerRequest.Hostinfo.Hostname). diff --git a/app.go b/app.go index c3c626e5..e4e69105 100644 --- a/app.go +++ b/app.go @@ -348,7 +348,13 @@ func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler Str("client_address", req.RemoteAddr). Msg(`missing "Bearer " prefix in "Authorization" header`) writer.WriteHeader(http.StatusUnauthorized) - writer.Write([]byte("Unauthorized")) + _, err := writer.Write([]byte("Unauthorized")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -362,7 +368,13 @@ func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler Msg("failed to validate token") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Unauthorized")) + _, err := writer.Write([]byte("Unauthorized")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -373,7 +385,13 @@ func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler Msg("invalid token") writer.WriteHeader(http.StatusUnauthorized) - writer.Write([]byte("Unauthorized")) + _, err := writer.Write([]byte("Unauthorized")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -409,7 +427,13 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router { "/health", func(writer http.ResponseWriter, req *http.Request) { writer.WriteHeader(http.StatusOK) - writer.Write([]byte("{\"healthy\": \"ok\"}")) + _, err := writer.Write([]byte("{\"healthy\": \"ok\"}")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } }).Methods(http.MethodGet) router.HandleFunc("/key", h.KeyHandler).Methods(http.MethodGet) diff --git a/derp_server.go b/derp_server.go index fb0acba0..098ca53e 100644 --- a/derp_server.go +++ b/derp_server.go @@ -105,7 +105,13 @@ func (h *Headscale) DERPHandler( } writer.Header().Set("Content-Type", "text/plain") writer.WriteHeader(http.StatusUpgradeRequired) - writer.Write([]byte("DERP requires connection upgrade")) + _, err := writer.Write([]byte("DERP requires connection upgrade")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -117,7 +123,13 @@ func (h *Headscale) DERPHandler( log.Error().Caller().Msg("DERP requires Hijacker interface from Gin") writer.Header().Set("Content-Type", "text/plain") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("HTTP does not support general TCP support")) + _, err := writer.Write([]byte("HTTP does not support general TCP support")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -127,7 +139,13 @@ func (h *Headscale) DERPHandler( log.Error().Caller().Err(err).Msgf("Hijack failed") writer.Header().Set("Content-Type", "text/plain") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("HTTP does not support general TCP support")) + _, err = writer.Write([]byte("HTTP does not support general TCP support")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -160,7 +178,13 @@ func (h *Headscale) DERPProbeHandler( writer.WriteHeader(http.StatusOK) default: writer.WriteHeader(http.StatusMethodNotAllowed) - writer.Write([]byte("bogus probe method")) + _, err := writer.Write([]byte("bogus probe method")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } } @@ -196,7 +220,13 @@ func (h *Headscale) DERPBootstrapDNSHandler( } writer.Header().Set("Content-Type", "application/json") writer.WriteHeader(http.StatusOK) - json.NewEncoder(writer).Encode(dnsEntries) + err := json.NewEncoder(writer).Encode(dnsEntries) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } // ServeSTUN starts a STUN server on the configured addr. diff --git a/oidc.go b/oidc.go index 171d4f80..09365a4d 100644 --- a/oidc.go +++ b/oidc.go @@ -142,7 +142,13 @@ func (h *Headscale) OIDCCallback( if code == "" || state == "" { writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("Wrong params")) + _, err := writer.Write([]byte("Wrong params")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -155,7 +161,13 @@ func (h *Headscale) OIDCCallback( Msg("Could not exchange code for token") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("Could not exchange code for token")) + _, err := writer.Write([]byte("Could not exchange code for token")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -170,7 +182,13 @@ func (h *Headscale) OIDCCallback( if !rawIDTokenOK { writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("Could not extract ID Token")) + _, err := writer.Write([]byte("Could not extract ID Token")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -185,7 +203,13 @@ func (h *Headscale) OIDCCallback( Msg("failed to verify id token") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("Failed to verify id token")) + _, err := writer.Write([]byte("Failed to verify id token")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -206,7 +230,13 @@ func (h *Headscale) OIDCCallback( Msg("Failed to decode id token claims") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("Failed to decode id token claims")) + _, err := writer.Write([]byte("Failed to decode id token claims")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -218,7 +248,13 @@ func (h *Headscale) OIDCCallback( log.Error().Msg("authenticated principal does not match any allowed domain") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("unauthorized principal (domain mismatch)")) + _, err := writer.Write([]byte("unauthorized principal (domain mismatch)")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -230,7 +266,13 @@ func (h *Headscale) OIDCCallback( log.Error().Msg("authenticated principal does not match any allowed user") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("unauthorized principal (user mismatch)")) + _, err := writer.Write([]byte("unauthorized principal (user mismatch)")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -243,7 +285,13 @@ func (h *Headscale) OIDCCallback( Msg("requested machine state key expired before authorisation completed") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("state has expired")) + _, err := writer.Write([]byte("state has expired")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -259,7 +307,13 @@ func (h *Headscale) OIDCCallback( Msg("could not parse machine public key") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("could not parse public key")) + _, err := writer.Write([]byte("could not parse public key")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -268,7 +322,13 @@ func (h *Headscale) OIDCCallback( log.Error().Msg("could not get machine key from cache") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("could not get machine key from cache")) + _, err := writer.Write([]byte("could not get machine key from cache")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -300,14 +360,26 @@ func (h *Headscale) OIDCCallback( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render OIDC callback template")) + _, err := writer.Write([]byte("Could not render OIDC callback 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) - writer.Write(content.Bytes()) + _, err := writer.Write(content.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -320,7 +392,13 @@ func (h *Headscale) OIDCCallback( log.Error().Err(err).Caller().Msgf("couldn't normalize email") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("couldn't normalize email")) + _, err := writer.Write([]byte("couldn't normalize email")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -339,7 +417,13 @@ func (h *Headscale) OIDCCallback( Msgf("could not create new namespace '%s'", namespaceName) writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("could not create namespace")) + _, err := writer.Write([]byte("could not create namespace")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -351,7 +435,13 @@ func (h *Headscale) OIDCCallback( Msg("could not find or create namespace") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("could not find or create namespace")) + _, err := writer.Write([]byte("could not find or create namespace")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -370,7 +460,13 @@ func (h *Headscale) OIDCCallback( Msg("could not register machine") writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("could not register machine")) + _, err := writer.Write([]byte("could not register machine")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -388,12 +484,24 @@ func (h *Headscale) OIDCCallback( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render OIDC callback template")) + _, err := writer.Write([]byte("Could not render OIDC callback 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) - writer.Write(content.Bytes()) + _, err = writer.Write(content.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } diff --git a/platform_config.go b/platform_config.go index 4aa58ec4..6bb195c7 100644 --- a/platform_config.go +++ b/platform_config.go @@ -69,14 +69,26 @@ REG ADD "HKLM\Software\Tailscale IPN" /v LoginURL /t REG_SZ /d "{{.URL}}" writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render Windows index template")) + _, err := writer.Write([]byte("Could not render Windows index 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) - writer.Write(payload.Bytes()) + _, err := writer.Write(payload.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } // WindowsRegConfig generates and serves a .reg file configured with the Headscale server address. @@ -97,14 +109,26 @@ func (h *Headscale) WindowsRegConfig( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render Windows registry template")) + _, err := writer.Write([]byte("Could not render Windows registry template")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } writer.Header().Set("Content-Type", "text/x-ms-regedit; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(content.Bytes()) + _, err := writer.Write(content.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } // AppleConfigMessage shows a simple message in the browser to point the user to the iOS/MacOS profile and instructions for how to install it. @@ -175,14 +199,26 @@ func (h *Headscale) AppleConfigMessage( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render Apple index template")) + _, err := writer.Write([]byte("Could not render Apple index 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) - writer.Write(payload.Bytes()) + _, err := writer.Write(payload.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } func (h *Headscale) ApplePlatformConfig( @@ -209,7 +245,13 @@ func (h *Headscale) ApplePlatformConfig( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Failed to create UUID")) + _, err := writer.Write([]byte("Failed to create UUID")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -223,7 +265,13 @@ func (h *Headscale) ApplePlatformConfig( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Failed to create content UUID")) + _, err := writer.Write([]byte("Failed to create content UUID")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -245,7 +293,13 @@ func (h *Headscale) ApplePlatformConfig( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render Apple macOS template")) + _, err := writer.Write([]byte("Could not render Apple macOS template")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -258,14 +312,26 @@ func (h *Headscale) ApplePlatformConfig( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render Apple iOS template")) + _, err := writer.Write([]byte("Could not render Apple iOS template")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } default: writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusBadRequest) - writer.Write([]byte("Invalid platform, only ios and macos is supported")) + _, err := writer.Write([]byte("Invalid platform, only ios and macos is supported")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -285,14 +351,26 @@ func (h *Headscale) ApplePlatformConfig( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render Apple platform template")) + _, err := writer.Write([]byte("Could not render Apple platform template")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } writer.Header().Set("Content-Type", "application/x-apple-aspen-config; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(content.Bytes()) + _, err = writer.Write(content.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } type WindowsRegistryConfig struct { diff --git a/poll.go b/poll.go index 6a06c2fe..72e293a5 100644 --- a/poll.go +++ b/poll.go @@ -179,7 +179,13 @@ func (h *Headscale) PollNetMapHandler( writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(data) + _, err := writer.Write(data) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } return } @@ -213,7 +219,13 @@ func (h *Headscale) PollNetMapHandler( Msg("Client sent endpoint update and is ok with a response without peer list") writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.WriteHeader(http.StatusOK) - writer.Write(data) + _, err := writer.Write(data) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } // It sounds like we should update the nodes when we have received a endpoint update // even tho the comments in the tailscale code dont explicitly say so. updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "endpoint-update"). diff --git a/swagger.go b/swagger.go index 082117d4..588b42ab 100644 --- a/swagger.go +++ b/swagger.go @@ -57,14 +57,26 @@ func SwaggerUI( writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.WriteHeader(http.StatusInternalServerError) - writer.Write([]byte("Could not render Swagger")) + _, err := writer.Write([]byte("Could not render Swagger")) + 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) - writer.Write(payload.Bytes()) + _, err := writer.Write(payload.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } func SwaggerAPIv1( @@ -73,5 +85,10 @@ func SwaggerAPIv1( ) { writer.Header().Set("Content-Type", "application/json; charset=utf-88") writer.WriteHeader(http.StatusOK) - writer.Write(apiV1JSON) + if _, err := writer.Write(apiV1JSON); err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } } From fa91ece5b4db47fa50026aa2fe6790230a5182a3 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 12:25:26 +0200 Subject: [PATCH 25/28] Lint fixes 6/n --- poll.go | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/poll.go b/poll.go index 72e293a5..3469e235 100644 --- a/poll.go +++ b/poll.go @@ -335,7 +335,18 @@ func (h *Headscale) PollNetMapStream( return } - writer.(http.Flusher).Flush() + + flusher, ok := writer.(http.Flusher) + if !ok { + log.Error(). + Caller(). + Str("handler", "PollNetMapStream"). + Str("machine", machine.Hostname). + Str("channel", "pollData"). + Msg("Cannot cast writer to http.Flusher") + } else { + flusher.Flush() + } log.Trace(). Str("handler", "PollNetMapStream"). @@ -403,7 +414,17 @@ func (h *Headscale) PollNetMapStream( return } - writer.(http.Flusher).Flush() + flusher, ok := writer.(http.Flusher) + if !ok { + log.Error(). + Caller(). + Str("handler", "PollNetMapStream"). + Str("machine", machine.Hostname). + Str("channel", "keepAlive"). + Msg("Cannot cast writer to http.Flusher") + } else { + flusher.Flush() + } log.Trace(). Str("handler", "PollNetMapStream"). @@ -492,7 +513,18 @@ func (h *Headscale) PollNetMapStream( return } - writer.(http.Flusher).Flush() + + flusher, ok := writer.(http.Flusher) + if !ok { + log.Error(). + Caller(). + Str("handler", "PollNetMapStream"). + Str("machine", machine.Hostname). + Str("channel", "update"). + Msg("Cannot cast writer to http.Flusher") + } else { + flusher.Flush() + } log.Trace(). Str("handler", "PollNetMapStream"). From ffcc72876c63ed134ad111752a3523350e53250c Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 12:30:52 +0200 Subject: [PATCH 26/28] Lint fixes 7/n --- api.go | 36 +++++++++++++++++++++++++++++++++--- machine_test.go | 4 +++- oidc.go | 13 +++++++++++-- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/api.go b/api.go index 9ed065f6..fc27e46b 100644 --- a/api.go +++ b/api.go @@ -413,7 +413,17 @@ func (h *Headscale) handleMachineLogOut( Str("machine", machine.Hostname). Msg("Client requested logout") - h.ExpireMachine(&machine) + err := h.ExpireMachine(&machine) + if err != nil { + log.Error(). + Caller(). + Str("func", "handleMachineLogOut"). + Err(err). + Msg("Failed to expire machine") + http.Error(writer, "Internal server error", http.StatusInternalServerError) + + return + } resp.AuthURL = "" resp.MachineAuthorized = false @@ -716,7 +726,16 @@ func (h *Headscale) handleAuthKey( machine.NodeKey = nodeKey machine.AuthKeyID = uint(pak.ID) - h.RefreshMachine(machine, registerRequest.Expiry) + err := h.RefreshMachine(machine, registerRequest.Expiry) + if err != nil { + log.Error(). + Caller(). + Str("machine", machine.Hostname). + Err(err). + Msg("Failed to refresh machine") + + return + } } else { now := time.Now().UTC() @@ -759,7 +778,18 @@ func (h *Headscale) handleAuthKey( } } - h.UsePreAuthKey(pak) + err = h.UsePreAuthKey(pak) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to use pre-auth key") + machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). + Inc() + http.Error(writer, "Internal server error", http.StatusInternalServerError) + + return + } resp.MachineAuthorized = true resp.User = *pak.Namespace.toUser() diff --git a/machine_test.go b/machine_test.go index 0287b0c6..a06d0db2 100644 --- a/machine_test.go +++ b/machine_test.go @@ -249,10 +249,12 @@ func (s *Suite) TestExpireMachine(c *check.C) { machineFromDB, err := app.GetMachine("test", "testmachine") c.Assert(err, check.IsNil) + c.Assert(machineFromDB, check.NotNil) c.Assert(machineFromDB.isExpired(), check.Equals, false) - app.ExpireMachine(machineFromDB) + err = app.ExpireMachine(machineFromDB) + c.Assert(err, check.IsNil) c.Assert(machineFromDB.isExpired(), check.Equals, true) } diff --git a/oidc.go b/oidc.go index 09365a4d..8b5f0242 100644 --- a/oidc.go +++ b/oidc.go @@ -345,7 +345,16 @@ func (h *Headscale) OIDCCallback( Str("machine", machine.Hostname). Msg("machine already registered, reauthenticating") - h.RefreshMachine(machine, time.Time{}) + err := h.RefreshMachine(machine, time.Time{}) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to refresh machine") + http.Error(writer, "Failed to refresh machine", http.StatusInternalServerError) + + return + } var content bytes.Buffer if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{ @@ -373,7 +382,7 @@ func (h *Headscale) OIDCCallback( writer.Header().Set("Content-Type", "text/html; charset=utf-8") writer.WriteHeader(http.StatusOK) - _, err := writer.Write(content.Bytes()) + _, err = writer.Write(content.Bytes()) if err != nil { log.Error(). Caller(). From 00885dffe1dc39b09d928368db525410d866e472 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 12:35:18 +0200 Subject: [PATCH 27/28] Fix implicit memory aliasing in for loop (lint 8/n) --- db.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db.go b/db.go index b3222d0e..e412468d 100644 --- a/db.go +++ b/db.go @@ -89,7 +89,7 @@ func (h *Headscale) initDB() error { log.Error().Err(err).Msg("Error accessing db") } - for _, machine := range machines { + for item, machine := range machines { if machine.GivenName == "" { normalizedHostname, err := NormalizeToFQDNRules( machine.Hostname, @@ -103,7 +103,7 @@ func (h *Headscale) initDB() error { Msg("Failed to normalize machine hostname in DB migration") } - err = h.RenameMachine(&machine, normalizedHostname) + err = h.RenameMachine(&machines[item], normalizedHostname) if err != nil { log.Error(). Caller(). From 8551b0dde098c4a19770af62a3b1db0ade85becb Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sun, 26 Jun 2022 14:24:57 +0200 Subject: [PATCH 28/28] Fixed issue when in linting rampage --- poll.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poll.go b/poll.go index 3469e235..9218495d 100644 --- a/poll.go +++ b/poll.go @@ -66,7 +66,7 @@ func (h *Headscale) PollNetMapHandler( return } mapRequest := tailcfg.MapRequest{} - err = decode(body, &req, &machineKey, h.privateKey) + err = decode(body, &mapRequest, &machineKey, h.privateKey) if err != nil { log.Error(). Str("handler", "PollNetMap").