From d4c2870d7ea13416bc0aa382673c4e7424dd761e Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 5 Jun 2021 12:13:55 +0200 Subject: [PATCH 1/3] Handle client sending new NodeKey (fixes #32) --- api.go | 132 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 58 deletions(-) diff --git a/api.go b/api.go index 4d54e1b2..a66a6488 100644 --- a/api.go +++ b/api.go @@ -81,51 +81,65 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) { return } defer db.Close() - var m Machine - resp := tailcfg.RegisterResponse{} + var m Machine if db.First(&m, "machine_key = ?", mKey.HexString()).RecordNotFound() { log.Println("New Machine!") - h.handleNewServer(c, db, mKey, req) + m = Machine{ + Expiry: &req.Expiry, + MachineKey: mKey.HexString(), + Name: req.Hostinfo.Hostname, + NodeKey: wgcfg.Key(req.NodeKey).HexString(), + } + if err := db.Create(&m).Error; err != nil { + log.Printf("Could not create row: %s", err) + return + } + } + + if !m.Registered && req.Auth.AuthKey != "" { + h.handleAuthKey(c, db, mKey, req, m) return } - // We do have the updated key! + resp := tailcfg.RegisterResponse{} + + // We have the updated key! if m.NodeKey == wgcfg.Key(req.NodeKey).HexString() { if m.Registered { - log.Printf("[%s] Client is registered and we have the current key. All clear to /map\n", m.Name) + log.Printf("[%s] Client is registered and we have the current NodeKey. All clear to /map", m.Name) resp.AuthURL = "" - resp.User = *m.Namespace.toUser() resp.MachineAuthorized = true + resp.User = *m.Namespace.toUser() respBody, err := encode(resp, &mKey, h.privateKey) if err != nil { log.Printf("Cannot encode message: %s", err) - c.String(http.StatusInternalServerError, "Extremely sad!") + c.String(http.StatusInternalServerError, "") return } c.Data(200, "application/json; charset=utf-8", respBody) return } - log.Println("Hey! Not registered. Not asking for key rotation. Send a passive-aggressive authurl to register") + log.Printf("[%s] Not registered and not NodeKey rotation. Sending a authurl to register", m.Name) resp.AuthURL = fmt.Sprintf("%s/register?key=%s", h.cfg.ServerURL, mKey.HexString()) respBody, err := encode(resp, &mKey, h.privateKey) if err != nil { log.Printf("Cannot encode message: %s", err) - c.String(http.StatusInternalServerError, "Extremely sad!") + c.String(http.StatusInternalServerError, "") return } c.Data(200, "application/json; charset=utf-8", respBody) return - } - // We dont have the updated key in the DB. Lets try with the old one. + // The NodeKey we have matches OldNodeKey, which means this is a refresh after an key expiration if m.NodeKey == wgcfg.Key(req.OldNodeKey).HexString() { - log.Println("Key rotation!") + log.Printf("[%s] We have the NodeKey in the database. This is a key refresh", m.Name) m.NodeKey = wgcfg.Key(req.NodeKey).HexString() db.Save(&m) + resp.AuthURL = "" resp.User = *m.Namespace.toUser() respBody, err := encode(resp, &mKey, h.privateKey) @@ -138,8 +152,32 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) { return } - log.Println("We dont know anything about the new key. WTF") - // spew.Dump(req) + // We arrive here after a client is restarted without finalizing the authentication flow or + // when headscale is stopped in the middle of the auth process. + if m.Registered { + log.Printf("[%s] The node is sending us a new NodeKey, but machine is registered. All clear for /map", m.Name) + resp.AuthURL = "" + resp.MachineAuthorized = true + resp.User = *m.Namespace.toUser() + respBody, err := encode(resp, &mKey, h.privateKey) + if err != nil { + log.Printf("Cannot encode message: %s", err) + c.String(http.StatusInternalServerError, "") + return + } + c.Data(200, "application/json; charset=utf-8", respBody) + return + } + log.Printf("[%s] The node is sending us a new NodeKey, sending auth url", m.Name) + resp.AuthURL = fmt.Sprintf("%s/register?key=%s", + h.cfg.ServerURL, mKey.HexString()) + respBody, err := encode(resp, &mKey, h.privateKey) + if err != nil { + log.Printf("Cannot encode message: %s", err) + c.String(http.StatusInternalServerError, "") + return + } + c.Data(200, "application/json; charset=utf-8", respBody) } // PollNetMapHandler takes care of /machine/:id/map @@ -390,61 +428,37 @@ func (h *Headscale) getMapKeepAliveResponse(mKey wgcfg.Key, req tailcfg.MapReque return &data, nil } -func (h *Headscale) handleNewServer(c *gin.Context, db *gorm.DB, idKey wgcfg.Key, req tailcfg.RegisterRequest) { - m := Machine{ - MachineKey: idKey.HexString(), - NodeKey: wgcfg.Key(req.NodeKey).HexString(), - Expiry: &req.Expiry, - Name: req.Hostinfo.Hostname, - } - if err := db.Create(&m).Error; err != nil { - log.Printf("Could not create row: %s", err) - return - } - +func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgcfg.Key, req tailcfg.RegisterRequest, m Machine) { resp := tailcfg.RegisterResponse{} - - if req.Auth.AuthKey != "" { - pak, err := h.checkKeyValidity(req.Auth.AuthKey) - if err != nil { - resp.MachineAuthorized = false - respBody, err := encode(resp, &idKey, h.privateKey) - if err != nil { - log.Printf("Cannot encode message: %s", err) - c.String(http.StatusInternalServerError, "") - return - } - c.Data(200, "application/json; charset=utf-8", respBody) - return - } - ip, err := h.getAvailableIP() - if err != nil { - log.Println(err) - return - } - - m.IPAddress = ip.String() - m.NamespaceID = pak.NamespaceID - m.AuthKeyID = uint(pak.ID) - m.RegisterMethod = "authKey" - m.Registered = true - db.Save(&m) - - resp.MachineAuthorized = true - resp.User = *pak.Namespace.toUser() + pak, err := h.checkKeyValidity(req.Auth.AuthKey) + if err != nil { + resp.MachineAuthorized = false respBody, err := encode(resp, &idKey, h.privateKey) if err != nil { log.Printf("Cannot encode message: %s", err) - c.String(http.StatusInternalServerError, "Extremely sad!") + c.String(http.StatusInternalServerError, "") return } c.Data(200, "application/json; charset=utf-8", respBody) + log.Printf("[%s] Failed authentication via AuthKey", m.Name) + return + } + ip, err := h.getAvailableIP() + if err != nil { + log.Println(err) return } - resp.AuthURL = fmt.Sprintf("%s/register?key=%s", - h.cfg.ServerURL, idKey.HexString()) + m.AuthKeyID = uint(pak.ID) + m.IPAddress = ip.String() + m.NamespaceID = pak.NamespaceID + m.NodeKey = wgcfg.Key(req.NodeKey).HexString() // we update it just in case + m.Registered = true + m.RegisterMethod = "authKey" + db.Save(&m) + resp.MachineAuthorized = true + resp.User = *pak.Namespace.toUser() respBody, err := encode(resp, &idKey, h.privateKey) if err != nil { log.Printf("Cannot encode message: %s", err) @@ -452,4 +466,6 @@ func (h *Headscale) handleNewServer(c *gin.Context, db *gorm.DB, idKey wgcfg.Key return } c.Data(200, "application/json; charset=utf-8", respBody) + log.Printf("[%s] Successfully authenticated via AuthKey", m.Name) + return } From 47b22f395042326c78099289c0e3892d4a744319 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 5 Jun 2021 12:19:48 +0200 Subject: [PATCH 2/3] Minor improvement on login --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index a66a6488..2d7dee79 100644 --- a/api.go +++ b/api.go @@ -136,7 +136,7 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) { // The NodeKey we have matches OldNodeKey, which means this is a refresh after an key expiration if m.NodeKey == wgcfg.Key(req.OldNodeKey).HexString() { - log.Printf("[%s] We have the NodeKey in the database. This is a key refresh", m.Name) + log.Printf("[%s] We have the OldNodeKey in the database. This is a key refresh", m.Name) m.NodeKey = wgcfg.Key(req.NodeKey).HexString() db.Save(&m) From aab0bfe2d5108d80f382db62aa53434269832a1d Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 5 Jun 2021 12:21:49 +0200 Subject: [PATCH 3/3] Removed redundant statement --- api.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api.go b/api.go index 2d7dee79..39d57392 100644 --- a/api.go +++ b/api.go @@ -467,5 +467,4 @@ func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgcfg.Key, } c.Data(200, "application/json; charset=utf-8", respBody) log.Printf("[%s] Successfully authenticated via AuthKey", m.Name) - return }