mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-21 16:21:41 +02:00
auth: generalise auth flow and introduce AuthVerdict
Generalise the registration pipeline to a more general auth pipeline supporting both node registrations and SSH check auth requests. Rename RegistrationID to AuthID, unexport AuthRequest fields, and introduce AuthVerdict to unify the auth finish API. Add the urlParam generic helper for extracting typed URL parameters from chi routes, used by the new auth request handler. Updates #1850
This commit is contained in:
@@ -20,7 +20,9 @@ import (
|
||||
|
||||
type AuthProvider interface {
|
||||
RegisterHandler(w http.ResponseWriter, r *http.Request)
|
||||
AuthURL(regID types.RegistrationID) string
|
||||
AuthHandler(w http.ResponseWriter, r *http.Request)
|
||||
RegisterURL(authID types.AuthID) string
|
||||
AuthURL(authID types.AuthID) string
|
||||
}
|
||||
|
||||
func (h *Headscale) handleRegister(
|
||||
@@ -263,22 +265,24 @@ func (h *Headscale) waitForFollowup(
|
||||
return nil, NewHTTPError(http.StatusUnauthorized, "invalid followup URL", err)
|
||||
}
|
||||
|
||||
followupReg, err := types.RegistrationIDFromString(strings.ReplaceAll(fu.Path, "/register/", ""))
|
||||
followupReg, err := types.AuthIDFromString(strings.ReplaceAll(fu.Path, "/register/", ""))
|
||||
if err != nil {
|
||||
return nil, NewHTTPError(http.StatusUnauthorized, "invalid registration ID", err)
|
||||
}
|
||||
|
||||
if reg, ok := h.state.GetRegistrationCacheEntry(followupReg); ok {
|
||||
if reg, ok := h.state.GetAuthCacheEntry(followupReg); ok {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, NewHTTPError(http.StatusUnauthorized, "registration timed out", err)
|
||||
case node := <-reg.Registered:
|
||||
if node == nil {
|
||||
// registration is expired in the cache, instruct the client to try a new registration
|
||||
return h.reqToNewRegisterResponse(req, machineKey)
|
||||
}
|
||||
case verdict := <-reg.WaitForAuth():
|
||||
if verdict.Accept() {
|
||||
if !verdict.Node.Valid() {
|
||||
// registration is expired in the cache, instruct the client to try a new registration
|
||||
return h.reqToNewRegisterResponse(req, machineKey)
|
||||
}
|
||||
|
||||
return nodeToRegisterResponse(node.View()), nil
|
||||
return nodeToRegisterResponse(verdict.Node), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,14 +297,14 @@ func (h *Headscale) reqToNewRegisterResponse(
|
||||
req tailcfg.RegisterRequest,
|
||||
machineKey key.MachinePublic,
|
||||
) (*tailcfg.RegisterResponse, error) {
|
||||
newRegID, err := types.NewRegistrationID()
|
||||
newAuthID, err := types.NewAuthID()
|
||||
if err != nil {
|
||||
return nil, NewHTTPError(http.StatusInternalServerError, "failed to generate registration ID", err)
|
||||
}
|
||||
|
||||
// Ensure we have a valid hostname
|
||||
hostname := util.EnsureHostname(
|
||||
req.Hostinfo,
|
||||
req.Hostinfo.View(),
|
||||
machineKey.String(),
|
||||
req.NodeKey.String(),
|
||||
)
|
||||
@@ -309,25 +313,25 @@ func (h *Headscale) reqToNewRegisterResponse(
|
||||
hostinfo := cmp.Or(req.Hostinfo, &tailcfg.Hostinfo{})
|
||||
hostinfo.Hostname = hostname
|
||||
|
||||
nodeToRegister := types.NewRegisterNode(
|
||||
types.Node{
|
||||
Hostname: hostname,
|
||||
MachineKey: machineKey,
|
||||
NodeKey: req.NodeKey,
|
||||
Hostinfo: hostinfo,
|
||||
LastSeen: new(time.Now()),
|
||||
},
|
||||
)
|
||||
|
||||
if !req.Expiry.IsZero() {
|
||||
nodeToRegister.Node.Expiry = &req.Expiry
|
||||
nodeToRegister := types.Node{
|
||||
Hostname: hostname,
|
||||
MachineKey: machineKey,
|
||||
NodeKey: req.NodeKey,
|
||||
Hostinfo: hostinfo,
|
||||
LastSeen: new(time.Now()),
|
||||
}
|
||||
|
||||
log.Info().Msgf("new followup node registration using key: %s", newRegID)
|
||||
h.state.SetRegistrationCacheEntry(newRegID, nodeToRegister)
|
||||
if !req.Expiry.IsZero() {
|
||||
nodeToRegister.Expiry = &req.Expiry
|
||||
}
|
||||
|
||||
authRegReq := types.NewRegisterAuthRequest(nodeToRegister)
|
||||
|
||||
log.Info().Msgf("new followup node registration using key: %s", newAuthID)
|
||||
h.state.SetAuthCacheEntry(newAuthID, authRegReq)
|
||||
|
||||
return &tailcfg.RegisterResponse{
|
||||
AuthURL: h.authProvider.AuthURL(newRegID),
|
||||
AuthURL: h.authProvider.RegisterURL(newAuthID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -378,13 +382,6 @@ func (h *Headscale) handleRegisterWithAuthKey(
|
||||
// Send both changes. Empty changes are ignored by Change().
|
||||
h.Change(changed, routesChange)
|
||||
|
||||
// TODO(kradalby): I think this is covered above, but we need to validate that.
|
||||
// // If policy changed due to node registration, send a separate policy change
|
||||
// if policyChanged {
|
||||
// policyChange := change.PolicyChange()
|
||||
// h.Change(policyChange)
|
||||
// }
|
||||
|
||||
resp := &tailcfg.RegisterResponse{
|
||||
MachineAuthorized: true,
|
||||
NodeKeyExpired: node.IsExpired(),
|
||||
@@ -406,14 +403,14 @@ func (h *Headscale) handleRegisterInteractive(
|
||||
req tailcfg.RegisterRequest,
|
||||
machineKey key.MachinePublic,
|
||||
) (*tailcfg.RegisterResponse, error) {
|
||||
registrationId, err := types.NewRegistrationID()
|
||||
authID, err := types.NewAuthID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generating registration ID: %w", err)
|
||||
}
|
||||
|
||||
// Ensure we have a valid hostname
|
||||
hostname := util.EnsureHostname(
|
||||
req.Hostinfo,
|
||||
req.Hostinfo.View(),
|
||||
machineKey.String(),
|
||||
req.NodeKey.String(),
|
||||
)
|
||||
@@ -436,28 +433,28 @@ func (h *Headscale) handleRegisterInteractive(
|
||||
|
||||
hostinfo.Hostname = hostname
|
||||
|
||||
nodeToRegister := types.NewRegisterNode(
|
||||
types.Node{
|
||||
Hostname: hostname,
|
||||
MachineKey: machineKey,
|
||||
NodeKey: req.NodeKey,
|
||||
Hostinfo: hostinfo,
|
||||
LastSeen: new(time.Now()),
|
||||
},
|
||||
)
|
||||
|
||||
if !req.Expiry.IsZero() {
|
||||
nodeToRegister.Node.Expiry = &req.Expiry
|
||||
nodeToRegister := types.Node{
|
||||
Hostname: hostname,
|
||||
MachineKey: machineKey,
|
||||
NodeKey: req.NodeKey,
|
||||
Hostinfo: hostinfo,
|
||||
LastSeen: new(time.Now()),
|
||||
}
|
||||
|
||||
h.state.SetRegistrationCacheEntry(
|
||||
registrationId,
|
||||
nodeToRegister,
|
||||
if !req.Expiry.IsZero() {
|
||||
nodeToRegister.Expiry = &req.Expiry
|
||||
}
|
||||
|
||||
authRegReq := types.NewRegisterAuthRequest(nodeToRegister)
|
||||
|
||||
h.state.SetAuthCacheEntry(
|
||||
authID,
|
||||
authRegReq,
|
||||
)
|
||||
|
||||
log.Info().Msgf("starting node registration using key: %s", registrationId)
|
||||
log.Info().Msgf("starting node registration using key: %s", authID)
|
||||
|
||||
return &tailcfg.RegisterResponse{
|
||||
AuthURL: h.authProvider.AuthURL(registrationId),
|
||||
AuthURL: h.authProvider.RegisterURL(authID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user