Integrate new header injectors with OAuth2 Proxy

This commit is contained in:
Joel Speed
2020-11-07 17:16:58 +00:00
parent d26c65ba8d
commit 8059a812cd
7 changed files with 485 additions and 551 deletions
+68 -160
View File
@@ -2,7 +2,6 @@ package main
import (
"context"
b64 "encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -98,7 +97,6 @@ type OAuthProxy struct {
PassAuthorization bool
PreferEmailToUser bool
skipAuthPreflight bool
skipAuthStripHeaders bool
skipJwtBearerTokens bool
mainJwtBearerVerifier *oidc.IDTokenVerifier
extraJwtBearerVerifiers []*oidc.IDTokenVerifier
@@ -110,6 +108,8 @@ type OAuthProxy struct {
AllowedGroups []string
sessionChain alice.Chain
headersChain alice.Chain
preAuthChain alice.Chain
}
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
@@ -169,7 +169,15 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
return nil, err
}
preAuthChain, err := buildPreAuthChain(opts)
if err != nil {
return nil, fmt.Errorf("could not build pre-auth chain: %v", err)
}
sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator)
headersChain, err := buildHeadersChain(opts)
if err != nil {
return nil, fmt.Errorf("could not build headers chain: %v", err)
}
return &OAuthProxy{
CookieName: opts.Cookie.Name,
@@ -201,20 +209,10 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
allowedRoutes: allowedRoutes,
whitelistDomains: opts.WhitelistDomains,
skipAuthPreflight: opts.SkipAuthPreflight,
skipAuthStripHeaders: opts.SkipAuthStripHeaders,
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
mainJwtBearerVerifier: opts.GetOIDCVerifier(),
extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
realClientIPParser: opts.GetRealClientIPParser(),
SetXAuthRequest: opts.SetXAuthRequest,
PassBasicAuth: opts.PassBasicAuth,
SetBasicAuth: opts.SetBasicAuth,
PassUserHeaders: opts.PassUserHeaders,
BasicAuthPassword: opts.BasicAuthPassword,
PassAccessToken: opts.PassAccessToken,
SetAuthorization: opts.SetAuthorization,
PassAuthorization: opts.PassAuthorization,
PreferEmailToUser: opts.PreferEmailToUser,
SkipProviderButton: opts.SkipProviderButton,
templates: templates,
trustedIPs: trustedIPs,
@@ -226,12 +224,46 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
basicAuthValidator: basicAuthValidator,
displayHtpasswdForm: basicAuthValidator != nil,
sessionChain: sessionChain,
headersChain: headersChain,
preAuthChain: preAuthChain,
}, nil
}
func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
// buildPreAuthChain constructs a chain that should process every request before
// the OAuth2 Proxy authentication logic kicks in.
// For example forcing HTTPS or health checks.
func buildPreAuthChain(opts *options.Options) (alice.Chain, error) {
chain := alice.New(middleware.NewScope())
if opts.ForceHTTPS {
_, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress)
if err != nil {
return alice.Chain{}, fmt.Errorf("invalid HTTPS address %q: %v", opts.HTTPAddress, err)
}
chain = chain.Append(middleware.NewRedirectToHTTPS(httpsPort))
}
healthCheckPaths := []string{opts.PingPath}
healthCheckUserAgents := []string{opts.PingUserAgent}
if opts.GCPHealthChecks {
healthCheckPaths = append(healthCheckPaths, "/liveness_check", "/readiness_check")
healthCheckUserAgents = append(healthCheckUserAgents, "GoogleHC/1.0")
}
// To silence logging of health checks, register the health check handler before
// the logging handler
if opts.Logging.SilencePing {
chain = chain.Append(middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents), LoggingHandler)
} else {
chain = chain.Append(LoggingHandler, middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents))
}
return chain, nil
}
func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
chain := alice.New()
if opts.SkipJwtBearerTokens {
sessionLoaders := []middlewareapi.TokenToSessionLoader{}
if opts.GetOIDCVerifier() != nil {
@@ -264,6 +296,20 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt
return chain
}
func buildHeadersChain(opts *options.Options) (alice.Chain, error) {
requestInjector, err := middleware.NewRequestHeaderInjector(opts.InjectRequestHeaders)
if err != nil {
return alice.Chain{}, fmt.Errorf("error constructing request header injector: %v", err)
}
responseInjector, err := middleware.NewResponseHeaderInjector(opts.InjectResponseHeaders)
if err != nil {
return alice.Chain{}, fmt.Errorf("error constructing request header injector: %v", err)
}
return alice.New(requestInjector, responseInjector), nil
}
func buildSignInMessage(opts *options.Options) string {
var msg string
if len(opts.Banner) >= 1 {
@@ -685,6 +731,10 @@ func (p *OAuthProxy) IsTrustedIP(req *http.Request) bool {
}
func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
p.preAuthChain.Then(http.HandlerFunc(p.serveHTTP)).ServeHTTP(rw, req)
}
func (p *OAuthProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
if req.URL.Path != p.AuthOnlyPath && strings.HasPrefix(req.URL.Path, p.ProxyPrefix) {
prepareNoCache(rw)
}
@@ -884,15 +934,14 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
// we are authenticated
p.addHeadersForProxying(rw, req, session)
rw.WriteHeader(http.StatusAccepted)
p.headersChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusAccepted)
})).ServeHTTP(rw, req)
}
// SkipAuthProxy proxies allowlisted requests and skips authentication
func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) {
if p.skipAuthStripHeaders {
p.stripAuthHeaders(req)
}
p.serveMux.ServeHTTP(rw, req)
p.headersChain.Then(p.serveMux).ServeHTTP(rw, req)
}
// Proxy proxies the user request if the user is authenticated else it prompts
@@ -903,8 +952,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
case nil:
// we are authenticated
p.addHeadersForProxying(rw, req, session)
p.serveMux.ServeHTTP(rw, req)
p.headersChain.Then(p.serveMux).ServeHTTP(rw, req)
case ErrNeedsLogin:
// we need to send the user to a login screen
if isAjax(req) {
@@ -961,120 +1009,6 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
// addHeadersForProxying adds the appropriate headers the request / response for proxying
func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) {
if p.PassBasicAuth {
if p.PreferEmailToUser && session.Email != "" {
req.SetBasicAuth(session.Email, p.BasicAuthPassword)
req.Header["X-Forwarded-User"] = []string{session.Email}
req.Header.Del("X-Forwarded-Email")
} else {
req.SetBasicAuth(session.User, p.BasicAuthPassword)
req.Header["X-Forwarded-User"] = []string{session.User}
if session.Email != "" {
req.Header["X-Forwarded-Email"] = []string{session.Email}
} else {
req.Header.Del("X-Forwarded-Email")
}
}
if session.PreferredUsername != "" {
req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername}
} else {
req.Header.Del("X-Forwarded-Preferred-Username")
}
}
if p.PassUserHeaders {
if p.PreferEmailToUser && session.Email != "" {
req.Header["X-Forwarded-User"] = []string{session.Email}
req.Header.Del("X-Forwarded-Email")
} else {
req.Header["X-Forwarded-User"] = []string{session.User}
if session.Email != "" {
req.Header["X-Forwarded-Email"] = []string{session.Email}
} else {
req.Header.Del("X-Forwarded-Email")
}
}
if session.PreferredUsername != "" {
req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername}
} else {
req.Header.Del("X-Forwarded-Preferred-Username")
}
if len(session.Groups) > 0 {
for _, group := range session.Groups {
req.Header.Add("X-Forwarded-Groups", group)
}
} else {
req.Header.Del("X-Forwarded-Groups")
}
}
if p.SetXAuthRequest {
rw.Header().Set("X-Auth-Request-User", session.User)
if session.Email != "" {
rw.Header().Set("X-Auth-Request-Email", session.Email)
} else {
rw.Header().Del("X-Auth-Request-Email")
}
if session.PreferredUsername != "" {
rw.Header().Set("X-Auth-Request-Preferred-Username", session.PreferredUsername)
} else {
rw.Header().Del("X-Auth-Request-Preferred-Username")
}
if p.PassAccessToken {
if session.AccessToken != "" {
rw.Header().Set("X-Auth-Request-Access-Token", session.AccessToken)
} else {
rw.Header().Del("X-Auth-Request-Access-Token")
}
}
if len(session.Groups) > 0 {
for _, group := range session.Groups {
rw.Header().Add("X-Auth-Request-Groups", group)
}
} else {
rw.Header().Del("X-Auth-Request-Groups")
}
}
if p.PassAccessToken {
if session.AccessToken != "" {
req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken}
} else {
req.Header.Del("X-Forwarded-Access-Token")
}
}
if p.PassAuthorization {
if session.IDToken != "" {
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)}
} else {
req.Header.Del("Authorization")
}
}
if p.SetBasicAuth {
switch {
case p.PreferEmailToUser && session.Email != "":
authVal := b64.StdEncoding.EncodeToString([]byte(session.Email + ":" + p.BasicAuthPassword))
rw.Header().Set("Authorization", "Basic "+authVal)
case session.User != "":
authVal := b64.StdEncoding.EncodeToString([]byte(session.User + ":" + p.BasicAuthPassword))
rw.Header().Set("Authorization", "Basic "+authVal)
default:
rw.Header().Del("Authorization")
}
}
if p.SetAuthorization {
if session.IDToken != "" {
rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken))
} else {
rw.Header().Del("Authorization")
}
}
if session.Email == "" {
rw.Header().Set("GAP-Auth", session.User)
} else {
@@ -1082,32 +1016,6 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
}
}
// stripAuthHeaders removes Auth headers for allowlisted routes from skipAuthRegex
func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
if p.PassBasicAuth {
req.Header.Del("X-Forwarded-User")
req.Header.Del("X-Forwarded-Groups")
req.Header.Del("X-Forwarded-Email")
req.Header.Del("X-Forwarded-Preferred-Username")
req.Header.Del("Authorization")
}
if p.PassUserHeaders {
req.Header.Del("X-Forwarded-User")
req.Header.Del("X-Forwarded-Groups")
req.Header.Del("X-Forwarded-Email")
req.Header.Del("X-Forwarded-Preferred-Username")
}
if p.PassAccessToken {
req.Header.Del("X-Forwarded-Access-Token")
}
if p.PassAuthorization {
req.Header.Del("Authorization")
}
}
// isAjax checks if a request is an ajax request
func isAjax(req *http.Request) bool {
acceptValues := req.Header.Values("Accept")