feat(authn-oidc): use full urls for login callback/logout options

This commit is contained in:
2024-05-23 15:17:05 +02:00
parent 499bb3696d
commit 544326a4b7
11 changed files with 270 additions and 62 deletions

View File

@ -1,11 +1,16 @@
package oidc
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net/http"
"net/url"
"text/template"
"time"
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn"
"forge.cadoles.com/cadoles/bouncer/internal/store"
@ -17,7 +22,9 @@ import (
)
type Authenticator struct {
store sessions.Store
store sessions.Store
httpTransport *http.Transport
httpClientTimeout time.Duration
}
func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request, layer *store.Layer) error {
@ -28,7 +35,9 @@ func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request
return errors.WithStack(err)
}
options, err := fromStoreOptions(layer.Options)
baseURL := originalURL.Scheme + "://" + originalURL.Host
options, err := fromStoreOptions(layer.Options, baseURL)
if err != nil {
return errors.WithStack(err)
}
@ -38,16 +47,30 @@ func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request
logger.Error(ctx, "could not retrieve session", logger.E(errors.WithStack(err)))
}
redirectURL := a.getRedirectURL(layer.Proxy, layer.Name, originalURL, options)
logoutURL := a.getLogoutURL(layer.Proxy, layer.Name, originalURL, options)
client, err := a.getClient(options, redirectURL.String())
loginCallbackURL, err := a.getLoginCallbackURL(layer.Proxy, layer.Name, options)
if err != nil {
return errors.WithStack(err)
}
switch r.URL.Path {
case redirectURL.Path:
client, err := a.getClient(options, loginCallbackURL.String())
if err != nil {
return errors.WithStack(err)
}
loginCallbackURLPattern, err := a.templatize(options.OIDC.MatchLoginCallbackURL, layer.Proxy, layer.Name)
if err != nil {
return errors.WithStack(err)
}
logoutURLPattern, err := a.templatize(options.OIDC.MatchLogoutURL, layer.Proxy, layer.Name)
if err != nil {
return errors.WithStack(err)
}
logger.Debug(ctx, "checking url", logger.F("loginCallbackURLPattern", loginCallbackURLPattern), logger.F("logoutURLPattern", logoutURLPattern))
switch {
case wildcard.Match(originalURL.String(), loginCallbackURLPattern):
if err := client.HandleCallback(w, r, sess); err != nil {
return errors.WithStack(err)
}
@ -57,7 +80,7 @@ func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request
metricLabelProxy: string(layer.Proxy),
}).Add(1)
case logoutURL.Path:
case wildcard.Match(originalURL.String(), logoutURLPattern):
postLogoutRedirectURL := options.OIDC.PostLogoutRedirectURL
if options.OIDC.PostLogoutRedirectURL == "" {
postLogoutRedirectURL = originalURL.Scheme + "://" + originalURL.Host
@ -85,7 +108,9 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay
return nil, errors.WithStack(err)
}
options, err := fromStoreOptions(layer.Options)
baseURL := originalURL.Scheme + "://" + originalURL.Host
options, err := fromStoreOptions(layer.Options, baseURL)
if err != nil {
return nil, errors.WithStack(err)
}
@ -117,9 +142,12 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay
sess.Options.SameSite = http.SameSiteDefaultMode
}
redirectURL := a.getRedirectURL(layer.Proxy, layer.Name, originalURL, options)
loginCallbackURL, err := a.getLoginCallbackURL(layer.Proxy, layer.Name, options)
if err != nil {
return nil, errors.WithStack(err)
}
client, err := a.getClient(options, redirectURL.String())
client, err := a.getClient(options, loginCallbackURL.String())
if err != nil {
return nil, errors.WithStack(err)
}
@ -138,7 +166,7 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay
return nil, errors.WithStack(err)
}
user, err := a.toUser(idToken, layer.Proxy, layer.Name, originalURL, options, sess)
user, err := a.toUser(idToken, layer.Proxy, layer.Name, options, sess)
if err != nil {
return nil, errors.WithStack(err)
}
@ -196,7 +224,7 @@ func (c claims) AsAttrs() map[string]any {
return attrs
}
func (a *Authenticator) toUser(idToken *oidc.IDToken, proxyName store.ProxyName, layerName store.LayerName, originalURL *url.URL, options *LayerOptions, sess *sessions.Session) (*authn.User, error) {
func (a *Authenticator) toUser(idToken *oidc.IDToken, proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions, sess *sessions.Session) (*authn.User, error) {
var claims claims
if err := idToken.Claims(&claims); err != nil {
@ -209,7 +237,11 @@ func (a *Authenticator) toUser(idToken *oidc.IDToken, proxyName store.ProxyName,
attrs := claims.AsAttrs()
logoutURL := a.getLogoutURL(proxyName, layerName, originalURL, options)
logoutURL, err := a.getLogoutURL(proxyName, layerName, options)
if err != nil {
return nil, errors.WithStack(err)
}
attrs["logout_url"] = logoutURL.String()
if accessToken, exists := sess.Values[sessionKeyAccessToken]; exists && accessToken != nil {
@ -229,25 +261,80 @@ func (a *Authenticator) toUser(idToken *oidc.IDToken, proxyName store.ProxyName,
return user, nil
}
func (a *Authenticator) getRedirectURL(proxyName store.ProxyName, layerName store.LayerName, u *url.URL, options *LayerOptions) *url.URL {
return &url.URL{
Scheme: u.Scheme,
Host: u.Host,
Path: fmt.Sprintf(options.OIDC.LoginCallbackPath, fmt.Sprintf("%s/%s", proxyName, layerName)),
func (a *Authenticator) getLoginCallbackURL(proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions) (*url.URL, error) {
url, err := a.generateURL(options.OIDC.LoginCallbackURL, proxyName, layerName)
if err != nil {
return nil, errors.WithStack(err)
}
return url, nil
}
func (a *Authenticator) getLogoutURL(proxyName store.ProxyName, layerName store.LayerName, u *url.URL, options *LayerOptions) *url.URL {
return &url.URL{
Scheme: u.Scheme,
Host: u.Host,
Path: fmt.Sprintf(options.OIDC.LogoutPath, fmt.Sprintf("%s/%s", proxyName, layerName)),
func (a *Authenticator) getLogoutURL(proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions) (*url.URL, error) {
url, err := a.generateURL(options.OIDC.LogoutURL, proxyName, layerName)
if err != nil {
return nil, errors.WithStack(err)
}
return url, nil
}
func (a *Authenticator) generateURL(rawURLTemplate string, proxyName store.ProxyName, layerName store.LayerName) (*url.URL, error) {
rawURL, err := a.templatize(rawURLTemplate, proxyName, layerName)
if err != nil {
return nil, errors.WithStack(err)
}
url, err := url.Parse(rawURL)
if err != nil {
return nil, errors.WithStack(err)
}
return url, nil
}
func (a *Authenticator) templatize(rawTemplate string, proxyName store.ProxyName, layerName store.LayerName) (string, error) {
tmpl, err := template.New("").Parse(rawTemplate)
if err != nil {
return "", errors.WithStack(err)
}
var raw bytes.Buffer
err = tmpl.Execute(&raw, struct {
ProxyName store.ProxyName
LayerName store.LayerName
}{
ProxyName: proxyName,
LayerName: layerName,
})
if err != nil {
return "", errors.WithStack(err)
}
return raw.String(), nil
}
func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*Client, error) {
ctx := context.Background()
transport := a.httpTransport.Clone()
if options.OIDC.TLSInsecureSkipVerify {
if transport.TLSClientConfig == nil {
transport.TLSClientConfig = &tls.Config{}
}
transport.TLSClientConfig.InsecureSkipVerify = true
}
httpClient := &http.Client{
Timeout: a.httpClientTimeout,
Transport: transport,
}
ctx = oidc.ClientContext(ctx, httpClient)
if options.OIDC.SkipIssuerVerification {
ctx = oidc.InsecureIssuerURLContext(ctx, options.OIDC.IssuerURL)
}
@ -263,6 +350,7 @@ func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*C
WithRedirectURL(redirectURL),
WithScopes(options.OIDC.Scopes...),
WithAuthParams(options.OIDC.AuthParams),
WithHTTPClient(httpClient),
)
return client, nil