mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-26 08:53:05 +00:00
feat(oidc): bind email to namespace
This commit is contained in:
parent
92ffac625e
commit
0191ea93ff
1 changed files with 79 additions and 99 deletions
178
oidc.go
178
oidc.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue