![gcp-cherry-pick-bot[bot]](/assets/img/avatar_default.png) 43637b8a75
			
		
	
	43637b8a75
	
	
	
		
			
			internal/outpost: fix incorrect usage of golang SHA API (#14981) Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens L. <jens@goauthentik.io>
		
			
				
	
	
		
			320 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			320 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package application
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/sha256"
 | |
| 	"crypto/tls"
 | |
| 	"encoding/gob"
 | |
| 	"encoding/hex"
 | |
| 	"fmt"
 | |
| 	"html/template"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"path"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/coreos/go-oidc/v3/oidc"
 | |
| 	"github.com/getsentry/sentry-go"
 | |
| 	sentryhttp "github.com/getsentry/sentry-go/http"
 | |
| 	"github.com/gorilla/mux"
 | |
| 	"github.com/gorilla/sessions"
 | |
| 	"github.com/jellydator/ttlcache/v3"
 | |
| 	"github.com/prometheus/client_golang/prometheus"
 | |
| 	log "github.com/sirupsen/logrus"
 | |
| 	"goauthentik.io/api/v3"
 | |
| 	"goauthentik.io/internal/config"
 | |
| 	"goauthentik.io/internal/outpost/ak"
 | |
| 	"goauthentik.io/internal/outpost/proxyv2/constants"
 | |
| 	"goauthentik.io/internal/outpost/proxyv2/hs256"
 | |
| 	"goauthentik.io/internal/outpost/proxyv2/metrics"
 | |
| 	"goauthentik.io/internal/outpost/proxyv2/templates"
 | |
| 	"goauthentik.io/internal/utils/web"
 | |
| 	"golang.org/x/oauth2"
 | |
| )
 | |
| 
 | |
| type Application struct {
 | |
| 	Host                 string
 | |
| 	Cert                 *tls.Certificate
 | |
| 	UnauthenticatedRegex []*regexp.Regexp
 | |
| 
 | |
| 	endpoint      OIDCEndpoint
 | |
| 	oauthConfig   oauth2.Config
 | |
| 	tokenVerifier *oidc.IDTokenVerifier
 | |
| 	outpostName   string
 | |
| 	sessionName   string
 | |
| 
 | |
| 	sessions             sessions.Store
 | |
| 	proxyConfig          api.ProxyOutpostConfig
 | |
| 	httpClient           *http.Client
 | |
| 	publicHostHTTPClient *http.Client
 | |
| 
 | |
| 	log *log.Entry
 | |
| 	mux *mux.Router
 | |
| 	ak  *ak.APIController
 | |
| 	srv Server
 | |
| 
 | |
| 	errorTemplates  *template.Template
 | |
| 	authHeaderCache *ttlcache.Cache[string, Claims]
 | |
| 
 | |
| 	isEmbedded bool
 | |
| }
 | |
| 
 | |
| type Server interface {
 | |
| 	API() *ak.APIController
 | |
| 	Apps() []*Application
 | |
| 	CryptoStore() *ak.CryptoStore
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	gob.Register(Claims{})
 | |
| }
 | |
| 
 | |
| func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, oldApp *Application) (*Application, error) {
 | |
| 	muxLogger := log.WithField("logger", "authentik.outpost.proxyv2.application").WithField("name", p.Name)
 | |
| 
 | |
| 	externalHost, err := url.Parse(p.ExternalHost)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to parse URL, skipping provider")
 | |
| 	}
 | |
| 
 | |
| 	var ks oidc.KeySet
 | |
| 	if contains(p.OidcConfiguration.IdTokenSigningAlgValuesSupported, "HS256") {
 | |
| 		ks = hs256.NewKeySet(*p.ClientSecret)
 | |
| 	} else {
 | |
| 		ctx := context.WithValue(context.Background(), oauth2.HTTPClient, c)
 | |
| 		ks = oidc.NewRemoteKeySet(ctx, p.OidcConfiguration.JwksUri)
 | |
| 	}
 | |
| 
 | |
| 	redirectUri, _ := url.Parse(p.ExternalHost)
 | |
| 	redirectUri.Path = path.Join(redirectUri.Path, "/outpost.goauthentik.io/callback")
 | |
| 	redirectUri.RawQuery = url.Values{
 | |
| 		CallbackSignature: []string{"true"},
 | |
| 	}.Encode()
 | |
| 
 | |
| 	isEmbedded := false
 | |
| 	if m := server.API().Outpost.Managed.Get(); m != nil {
 | |
| 		isEmbedded = *m == "goauthentik.io/outposts/embedded"
 | |
| 	}
 | |
| 	// Configure an OpenID Connect aware OAuth2 client.
 | |
| 	endpoint := GetOIDCEndpoint(
 | |
| 		p,
 | |
| 		server.API().Outpost.Config["authentik_host"].(string),
 | |
| 		isEmbedded,
 | |
| 	)
 | |
| 
 | |
| 	verifier := oidc.NewVerifier(endpoint.Issuer, ks, &oidc.Config{
 | |
| 		ClientID:             *p.ClientId,
 | |
| 		SupportedSigningAlgs: []string{"RS256", "HS256"},
 | |
| 	})
 | |
| 
 | |
| 	oauth2Config := oauth2.Config{
 | |
| 		ClientID:     *p.ClientId,
 | |
| 		ClientSecret: *p.ClientSecret,
 | |
| 		RedirectURL:  redirectUri.String(),
 | |
| 		Endpoint:     endpoint.Endpoint,
 | |
| 		Scopes:       p.ScopesToRequest,
 | |
| 	}
 | |
| 	mux := mux.NewRouter()
 | |
| 
 | |
| 	// Save cookie name, based on hashed client ID
 | |
| 	hs := sha256.Sum256([]byte(*p.ClientId))
 | |
| 	bs := hex.EncodeToString(hs[:])
 | |
| 	sessionName := fmt.Sprintf("authentik_proxy_%s", bs[:8])
 | |
| 
 | |
| 	// When HOST_BROWSER is set, use that as Host header for token requests to make the issuer match
 | |
| 	// otherwise we use the internally configured authentik_host
 | |
| 	tokenEndpointHost := server.API().Outpost.Config["authentik_host"].(string)
 | |
| 	if config.Get().AuthentikHostBrowser != "" {
 | |
| 		tokenEndpointHost = config.Get().AuthentikHostBrowser
 | |
| 	}
 | |
| 	publicHTTPClient := web.NewHostInterceptor(c, tokenEndpointHost)
 | |
| 
 | |
| 	a := &Application{
 | |
| 		Host:                 externalHost.Host,
 | |
| 		log:                  muxLogger,
 | |
| 		outpostName:          server.API().Outpost.Name,
 | |
| 		sessionName:          sessionName,
 | |
| 		endpoint:             endpoint,
 | |
| 		oauthConfig:          oauth2Config,
 | |
| 		tokenVerifier:        verifier,
 | |
| 		proxyConfig:          p,
 | |
| 		httpClient:           c,
 | |
| 		publicHostHTTPClient: publicHTTPClient,
 | |
| 		mux:                  mux,
 | |
| 		errorTemplates:       templates.GetTemplates(),
 | |
| 		ak:                   server.API(),
 | |
| 		authHeaderCache:      ttlcache.New(ttlcache.WithDisableTouchOnHit[string, Claims]()),
 | |
| 		srv:                  server,
 | |
| 		isEmbedded:           isEmbedded,
 | |
| 	}
 | |
| 	go a.authHeaderCache.Start()
 | |
| 	if oldApp != nil && oldApp.sessions != nil {
 | |
| 		a.sessions = oldApp.sessions
 | |
| 	} else {
 | |
| 		sess, err := a.getStore(p, externalHost)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		a.sessions = sess
 | |
| 	}
 | |
| 	mux.Use(web.NewLoggingHandler(muxLogger, func(l *log.Entry, r *http.Request) *log.Entry {
 | |
| 		c := a.getClaimsFromSession(r)
 | |
| 		if c == nil {
 | |
| 			return l
 | |
| 		}
 | |
| 		if c.PreferredUsername != "" {
 | |
| 			return l.WithField("user", c.PreferredUsername)
 | |
| 		}
 | |
| 		return l.WithField("user", c.Sub)
 | |
| 	}))
 | |
| 	mux.Use(func(inner http.Handler) http.Handler {
 | |
| 		return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 | |
| 			c := a.getClaimsFromSession(r)
 | |
| 			user := ""
 | |
| 			if c != nil {
 | |
| 				user = c.PreferredUsername
 | |
| 				hub := sentry.GetHubFromContext(r.Context())
 | |
| 				if hub == nil {
 | |
| 					hub = sentry.CurrentHub()
 | |
| 				}
 | |
| 				hub.Scope().SetUser(sentry.User{
 | |
| 					Username:  user,
 | |
| 					ID:        c.Sub,
 | |
| 					IPAddress: r.RemoteAddr,
 | |
| 				})
 | |
| 			}
 | |
| 			before := time.Now()
 | |
| 			inner.ServeHTTP(rw, r)
 | |
| 			elapsed := time.Since(before)
 | |
| 			metrics.Requests.With(prometheus.Labels{
 | |
| 				"outpost_name": a.outpostName,
 | |
| 				"type":         "app",
 | |
| 				"method":       r.Method,
 | |
| 				"host":         web.GetHost(r),
 | |
| 			}).Observe(float64(elapsed) / float64(time.Second))
 | |
| 		})
 | |
| 	})
 | |
| 	if server.API().GlobalConfig.ErrorReporting.Enabled {
 | |
| 		mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
 | |
| 	}
 | |
| 	mux.Use(func(inner http.Handler) http.Handler {
 | |
| 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 			if strings.EqualFold(r.URL.Query().Get(CallbackSignature), "true") {
 | |
| 				a.log.Debug("handling OAuth Callback from querystring signature")
 | |
| 				a.handleAuthCallback(w, r)
 | |
| 			} else if strings.EqualFold(r.URL.Query().Get(LogoutSignature), "true") {
 | |
| 				a.log.Debug("handling OAuth Logout from querystring signature")
 | |
| 				a.handleSignOut(w, r)
 | |
| 			} else {
 | |
| 				inner.ServeHTTP(w, r)
 | |
| 			}
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	mux.HandleFunc("/outpost.goauthentik.io/start", func(w http.ResponseWriter, r *http.Request) {
 | |
| 		fwd := ""
 | |
| 		// This should only really be hit for nginx forward_auth
 | |
| 		// as for that the auth start redirect URL is generated by the
 | |
| 		// reverse proxy, and as such we won't have a request we just
 | |
| 		// denied to reference for final URL
 | |
| 		rd, ok := a.checkRedirectParam(r)
 | |
| 		if ok {
 | |
| 			a.log.WithField("rd", rd).Trace("Setting redirect")
 | |
| 			fwd = rd
 | |
| 		}
 | |
| 		a.handleAuthStart(w, r, fwd)
 | |
| 	})
 | |
| 	mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleAuthCallback)
 | |
| 	mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut)
 | |
| 	switch *p.Mode {
 | |
| 	case api.PROXYMODE_PROXY:
 | |
| 		err = a.configureProxy()
 | |
| 	case api.PROXYMODE_FORWARD_SINGLE:
 | |
| 		fallthrough
 | |
| 	case api.PROXYMODE_FORWARD_DOMAIN:
 | |
| 		err = a.configureForward()
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to configure application mode: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if kp := p.Certificate.Get(); kp != nil {
 | |
| 		err := server.CryptoStore().AddKeypair(*kp)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("failed to initially fetch certificate: %w", err)
 | |
| 		}
 | |
| 		a.Cert = server.CryptoStore().Get(*kp)
 | |
| 	}
 | |
| 
 | |
| 	if *p.SkipPathRegex != "" {
 | |
| 		a.UnauthenticatedRegex = make([]*regexp.Regexp, 0)
 | |
| 		for _, regex := range strings.Split(*p.SkipPathRegex, "\n") {
 | |
| 			re, err := regexp.Compile(regex)
 | |
| 			if err != nil {
 | |
| 				// TODO: maybe create event for this?
 | |
| 				a.log.WithError(err).Warning("failed to compile SkipPathRegex")
 | |
| 				continue
 | |
| 			}
 | |
| 			a.UnauthenticatedRegex = append(a.UnauthenticatedRegex, re)
 | |
| 		}
 | |
| 	}
 | |
| 	return a, nil
 | |
| }
 | |
| 
 | |
| func (a *Application) Mode() api.ProxyMode {
 | |
| 	return *a.proxyConfig.Mode
 | |
| }
 | |
| 
 | |
| func (a *Application) ShouldHandleURL(r *http.Request) bool {
 | |
| 	if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io") {
 | |
| 		return true
 | |
| 	}
 | |
| 	if strings.EqualFold(r.URL.Query().Get(CallbackSignature), "true") {
 | |
| 		return true
 | |
| 	}
 | |
| 	if strings.EqualFold(r.URL.Query().Get(LogoutSignature), "true") {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (a *Application) ProxyConfig() api.ProxyOutpostConfig {
 | |
| 	return a.proxyConfig
 | |
| }
 | |
| 
 | |
| func (a *Application) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
 | |
| 	a.mux.ServeHTTP(rw, r)
 | |
| }
 | |
| 
 | |
| func (a *Application) Stop() {
 | |
| 	a.authHeaderCache.Stop()
 | |
| }
 | |
| 
 | |
| func (a *Application) handleSignOut(rw http.ResponseWriter, r *http.Request) {
 | |
| 	redirect := a.endpoint.EndSessionEndpoint
 | |
| 	s, err := a.sessions.Get(r, a.SessionName())
 | |
| 	if err != nil {
 | |
| 		a.redirectToStart(rw, r)
 | |
| 		return
 | |
| 	}
 | |
| 	c, exists := s.Values[constants.SessionClaims]
 | |
| 	if c == nil && !exists {
 | |
| 		a.redirectToStart(rw, r)
 | |
| 		return
 | |
| 	}
 | |
| 	cc := c.(Claims)
 | |
| 	uv := url.Values{
 | |
| 		"id_token_hint": []string{cc.RawToken},
 | |
| 	}
 | |
| 	redirect += "?" + uv.Encode()
 | |
| 	err = a.Logout(r.Context(), func(c Claims) bool {
 | |
| 		return c.Sub == cc.Sub
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		a.log.WithError(err).Warning("failed to logout of other sessions")
 | |
| 	}
 | |
| 	http.Redirect(rw, r, redirect, http.StatusFound)
 | |
| }
 |