feat(oidc): bind email to namespace

This commit is contained in:
Adrien Raffin-Caboisse 2022-02-22 12:46:45 +01:00
parent 92ffac625e
commit 0191ea93ff
No known key found for this signature in database
GPG key ID: 7FB60532DEBEAD6A

178
oidc.go
View file

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
"regexp"
"strings" "strings"
"time" "time"
@ -282,109 +281,90 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
now := time.Now().UTC() now := time.Now().UTC()
if namespaceName, ok := h.getNamespaceFromEmail(claims.Email); ok { namespaceName, err := NormalizeNamespaceName(claims.Email)
// register the machine if it's new if err != nil {
if !machine.Registered { log.Error().Err(err).Caller().Msgf("couldn't normalize email")
log.Debug().Msg("Registering new machine after successful callback") ctx.String(
http.StatusInternalServerError,
namespace, err := h.GetNamespace(namespaceName) "couldn't normalize email",
if errors.Is(err, gorm.ErrRecordNotFound) { )
namespace, err = h.CreateNamespace(namespaceName)
if err != nil {
log.Error().
Err(err).
Caller().
Msgf("could not create new namespace '%s'", namespaceName)
ctx.String(
http.StatusInternalServerError,
"could not create new namespace",
)
return
}
} else if err != nil {
log.Error().
Caller().
Err(err).
Str("namespace", namespaceName).
Msg("could not find or create namespace")
ctx.String(
http.StatusInternalServerError,
"could not find or create namespace",
)
return
}
ips, err := h.getAvailableIPs()
if err != nil {
log.Error().
Caller().
Err(err).
Msg("could not get an IP from the pool")
ctx.String(
http.StatusInternalServerError,
"could not get an IP from the pool",
)
return
}
machine.IPAddresses = ips
machine.NamespaceID = namespace.ID
machine.Registered = true
machine.RegisterMethod = RegisterMethodOIDC
machine.LastSuccessfulUpdate = &now
machine.Expiry = &requestedTime
h.db.Save(&machine)
}
var content bytes.Buffer
if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{
User: claims.Email,
Verb: "Authenticated",
}); err != nil {
log.Error().
Str("func", "OIDCCallback").
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"),
)
}
ctx.Data(http.StatusOK, "text/html; charset=utf-8", content.Bytes())
return return
} }
// register the machine if it's new
if !machine.Registered {
log.Debug().Msg("Registering new machine after successful callback")
log.Error(). namespace, err := h.GetNamespace(namespaceName)
Caller(). if errors.Is(err, gorm.ErrRecordNotFound) {
Str("email", claims.Email). namespace, err = h.CreateNamespace(namespaceName)
Str("username", claims.Username).
Str("machine", machine.Name).
Msg("Email could not be mapped to a namespace")
ctx.String(
http.StatusBadRequest,
"email from claim could not be mapped to a namespace",
)
}
// getNamespaceFromEmail passes the users email through a list of "matchers" if err != nil {
// and iterates through them until it matches and returns a namespace. log.Error().
// If no match is found, an empty string will be returned. Err(err).
// TODO(kradalby): golang Maps key order is not stable, so this list is _not_ deterministic. Find a way to make the list of keys stable, preferably in the order presented in a users configuration. Caller().
func (h *Headscale) getNamespaceFromEmail(email string) (string, bool) { Msgf("could not create new namespace '%s'", namespaceName)
for match, namespace := range h.cfg.OIDC.MatchMap { ctx.String(
regex := regexp.MustCompile(match) http.StatusInternalServerError,
if regex.MatchString(email) { "could not create new namespace",
return namespace, true )
return
}
} else if err != nil {
log.Error().
Caller().
Err(err).
Str("namespace", namespaceName).
Msg("could not find or create namespace")
ctx.String(
http.StatusInternalServerError,
"could not find or create namespace",
)
return
} }
ips, err := h.getAvailableIPs()
if err != nil {
log.Error().
Caller().
Err(err).
Msg("could not get an IP from the pool")
ctx.String(
http.StatusInternalServerError,
"could not get an IP from the pool",
)
return
}
machine.IPAddresses = ips
machine.NamespaceID = namespace.ID
machine.Registered = true
machine.RegisterMethod = RegisterMethodOIDC
machine.LastSuccessfulUpdate = &now
machine.Expiry = &requestedTime
h.db.Save(&machine)
} }
return "", false var content bytes.Buffer
if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{
User: claims.Email,
Verb: "Authenticated",
}); err != nil {
log.Error().
Str("func", "OIDCCallback").
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"),
)
}
ctx.Data(http.StatusOK, "text/html; charset=utf-8", content.Bytes())
return
} }