git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #826 from oauth2-proxy/header-middlewares

Integrate new header injectors into project

commit5cba64293848866e6d09f3bd0cbbe024cf8179fb authorNick Meves <nick.meves@greenhouse.io> dateSat 7 Nov 2020, 11:14 (6 years ago) parentab3cd581270104
16 files changed+1645−578
CHANGELOG.md+1 main.go+1−29 oauthproxy.go+68−160 oauthproxy_test.go+318−301 pkg/apis/options/legacy_options.go+261 pkg/apis/options/legacy_options_test.go+471 pkg/apis/options/options.go+4−28 pkg/validation/common.go+46 pkg/validation/common_test.go+138 pkg/validation/header.go+63 pkg/validation/header_test.go+164 pkg/validation/options.go+2−8 pkg/validation/options_test.go−23 pkg/validation/sessions.go+14−11 pkg/validation/sessions_test.go+83−18 pkg/validation/utils.go+11
CHANGELOG.md+1
@@ -32,6 +32,7 @@
3232
3333## Changes since v6.1.1
3434
35+- [#826](https://github.com/oauth2-proxy/oauth2-proxy/pull/826) Integrate new header injectors into project (@JoelSpeed)
3536- [#898](https://github.com/oauth2-proxy/oauth2-proxy/pull/898) Migrate documentation to Docusaurus (@JoelSpeed)
3637- [#754](https://github.com/oauth2-proxy/oauth2-proxy/pull/754) Azure token refresh (@codablock)
3738- [#825](https://github.com/oauth2-proxy/oauth2-proxy/pull/825) Fix code coverage reporting on GitHub actions(@JoelSpeed)
main.go+1−29
@@ -3,17 +3,14 @@ package main
33import (
44 "fmt"
55 "math/rand"
6− "net"
76 "os"
87 "os/signal"
98 "runtime"
109 "syscall"
1110 "time"
1211
13− "github.com/justinas/alice"
1412 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1513 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
16− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
1714 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
1815)
1916
@@ -63,33 +60,8 @@ func main() {
6360
6461 rand.Seed(time.Now().UnixNano())
6562
66− chain := alice.New()
67−
68− if opts.ForceHTTPS {
69− _, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress)
70− if err != nil {
71− logger.Fatalf("FATAL: invalid HTTPS address %q: %v", opts.HTTPAddress, err)
72− }
73− chain = chain.Append(middleware.NewRedirectToHTTPS(httpsPort))
74− }
75−
76− healthCheckPaths := []string{opts.PingPath}
77− healthCheckUserAgents := []string{opts.PingUserAgent}
78− if opts.GCPHealthChecks {
79− healthCheckPaths = append(healthCheckPaths, "/liveness_check", "/readiness_check")
80− healthCheckUserAgents = append(healthCheckUserAgents, "GoogleHC/1.0")
81− }
82−
83− // To silence logging of health checks, register the health check handler before
84− // the logging handler
85− if opts.Logging.SilencePing {
86− chain = chain.Append(middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents), LoggingHandler)
87− } else {
88− chain = chain.Append(LoggingHandler, middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents))
89− }
90−
9163 s := &Server{
92− Handler: chain.Then(oauthproxy),
64+ Handler: oauthproxy,
9365 Opts: opts,
9466 stop: make(chan struct{}, 1),
9567 }
oauthproxy.go+68−160
@@ -2,7 +2,6 @@ package main
22
33import (
44 "context"
5− b64 "encoding/base64"
65 "encoding/json"
76 "errors"
87 "fmt"
@@ -98,7 +97,6 @@ type OAuthProxy struct {
9897 PassAuthorization bool
9998 PreferEmailToUser bool
10099 skipAuthPreflight bool
101− skipAuthStripHeaders bool
102100 skipJwtBearerTokens bool
103101 mainJwtBearerVerifier *oidc.IDTokenVerifier
104102 extraJwtBearerVerifiers []*oidc.IDTokenVerifier
@@ -110,6 +108,8 @@ type OAuthProxy struct {
110108 AllowedGroups []string
111109
112110 sessionChain alice.Chain
111+ headersChain alice.Chain
112+ preAuthChain alice.Chain
113113}
114114
115115// 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
169169 return nil, err
170170 }
171171
172+ preAuthChain, err := buildPreAuthChain(opts)
173+ if err != nil {
174+ return nil, fmt.Errorf("could not build pre-auth chain: %v", err)
175+ }
172176 sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator)
177+ headersChain, err := buildHeadersChain(opts)
178+ if err != nil {
179+ return nil, fmt.Errorf("could not build headers chain: %v", err)
180+ }
173181
174182 return &OAuthProxy{
175183 CookieName: opts.Cookie.Name,
@@ -201,20 +209,10 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
201209 allowedRoutes: allowedRoutes,
202210 whitelistDomains: opts.WhitelistDomains,
203211 skipAuthPreflight: opts.SkipAuthPreflight,
204− skipAuthStripHeaders: opts.SkipAuthStripHeaders,
205212 skipJwtBearerTokens: opts.SkipJwtBearerTokens,
206213 mainJwtBearerVerifier: opts.GetOIDCVerifier(),
207214 extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
208215 realClientIPParser: opts.GetRealClientIPParser(),
209− SetXAuthRequest: opts.SetXAuthRequest,
210− PassBasicAuth: opts.PassBasicAuth,
211− SetBasicAuth: opts.SetBasicAuth,
212− PassUserHeaders: opts.PassUserHeaders,
213− BasicAuthPassword: opts.BasicAuthPassword,
214− PassAccessToken: opts.PassAccessToken,
215− SetAuthorization: opts.SetAuthorization,
216− PassAuthorization: opts.PassAuthorization,
217− PreferEmailToUser: opts.PreferEmailToUser,
218216 SkipProviderButton: opts.SkipProviderButton,
219217 templates: templates,
220218 trustedIPs: trustedIPs,
@@ -226,12 +224,46 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
226224 basicAuthValidator: basicAuthValidator,
227225 displayHtpasswdForm: basicAuthValidator != nil,
228226 sessionChain: sessionChain,
227+ headersChain: headersChain,
228+ preAuthChain: preAuthChain,
229229 }, nil
230230}
231231
232−func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
232+// buildPreAuthChain constructs a chain that should process every request before
233+// the OAuth2 Proxy authentication logic kicks in.
234+// For example forcing HTTPS or health checks.
235+func buildPreAuthChain(opts *options.Options) (alice.Chain, error) {
233236 chain := alice.New(middleware.NewScope())
234237
238+ if opts.ForceHTTPS {
239+ _, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress)
240+ if err != nil {
241+ return alice.Chain{}, fmt.Errorf("invalid HTTPS address %q: %v", opts.HTTPAddress, err)
242+ }
243+ chain = chain.Append(middleware.NewRedirectToHTTPS(httpsPort))
244+ }
245+
246+ healthCheckPaths := []string{opts.PingPath}
247+ healthCheckUserAgents := []string{opts.PingUserAgent}
248+ if opts.GCPHealthChecks {
249+ healthCheckPaths = append(healthCheckPaths, "/liveness_check", "/readiness_check")
250+ healthCheckUserAgents = append(healthCheckUserAgents, "GoogleHC/1.0")
251+ }
252+
253+ // To silence logging of health checks, register the health check handler before
254+ // the logging handler
255+ if opts.Logging.SilencePing {
256+ chain = chain.Append(middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents), LoggingHandler)
257+ } else {
258+ chain = chain.Append(LoggingHandler, middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents))
259+ }
260+
261+ return chain, nil
262+}
263+
264+func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
265+ chain := alice.New()
266+
235267 if opts.SkipJwtBearerTokens {
236268 sessionLoaders := []middlewareapi.TokenToSessionLoader{}
237269 if opts.GetOIDCVerifier() != nil {
@@ -264,6 +296,20 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt
264296 return chain
265297}
266298
299+func buildHeadersChain(opts *options.Options) (alice.Chain, error) {
300+ requestInjector, err := middleware.NewRequestHeaderInjector(opts.InjectRequestHeaders)
301+ if err != nil {
302+ return alice.Chain{}, fmt.Errorf("error constructing request header injector: %v", err)
303+ }
304+
305+ responseInjector, err := middleware.NewResponseHeaderInjector(opts.InjectResponseHeaders)
306+ if err != nil {
307+ return alice.Chain{}, fmt.Errorf("error constructing request header injector: %v", err)
308+ }
309+
310+ return alice.New(requestInjector, responseInjector), nil
311+}
312+
267313func buildSignInMessage(opts *options.Options) string {
268314 var msg string
269315 if len(opts.Banner) >= 1 {
@@ -685,6 +731,10 @@ func (p *OAuthProxy) IsTrustedIP(req *http.Request) bool {
685731}
686732
687733func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
734+ p.preAuthChain.Then(http.HandlerFunc(p.serveHTTP)).ServeHTTP(rw, req)
735+}
736+
737+func (p *OAuthProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
688738 if req.URL.Path != p.AuthOnlyPath && strings.HasPrefix(req.URL.Path, p.ProxyPrefix) {
689739 prepareNoCache(rw)
690740 }
@@ -884,15 +934,14 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
884934
885935 // we are authenticated
886936 p.addHeadersForProxying(rw, req, session)
887− rw.WriteHeader(http.StatusAccepted)
937+ p.headersChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
938+ rw.WriteHeader(http.StatusAccepted)
939+ })).ServeHTTP(rw, req)
888940}
889941
890942// SkipAuthProxy proxies allowlisted requests and skips authentication
891943func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) {
892− if p.skipAuthStripHeaders {
893− p.stripAuthHeaders(req)
894− }
895− p.serveMux.ServeHTTP(rw, req)
944+ p.headersChain.Then(p.serveMux).ServeHTTP(rw, req)
896945}
897946
898947// 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) {
903952 case nil:
904953 // we are authenticated
905954 p.addHeadersForProxying(rw, req, session)
906− p.serveMux.ServeHTTP(rw, req)
907−
955+ p.headersChain.Then(p.serveMux).ServeHTTP(rw, req)
908956 case ErrNeedsLogin:
909957 // we need to send the user to a login screen
910958 if isAjax(req) {
@@ -961,120 +1009,6 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
9611009
9621010// addHeadersForProxying adds the appropriate headers the request / response for proxying
9631011func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) {
964− if p.PassBasicAuth {
965− if p.PreferEmailToUser && session.Email != "" {
966− req.SetBasicAuth(session.Email, p.BasicAuthPassword)
967− req.Header["X-Forwarded-User"] = []string{session.Email}
968− req.Header.Del("X-Forwarded-Email")
969− } else {
970− req.SetBasicAuth(session.User, p.BasicAuthPassword)
971− req.Header["X-Forwarded-User"] = []string{session.User}
972− if session.Email != "" {
973− req.Header["X-Forwarded-Email"] = []string{session.Email}
974− } else {
975− req.Header.Del("X-Forwarded-Email")
976− }
977− }
978− if session.PreferredUsername != "" {
979− req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername}
980− } else {
981− req.Header.Del("X-Forwarded-Preferred-Username")
982− }
983− }
984−
985− if p.PassUserHeaders {
986− if p.PreferEmailToUser && session.Email != "" {
987− req.Header["X-Forwarded-User"] = []string{session.Email}
988− req.Header.Del("X-Forwarded-Email")
989− } else {
990− req.Header["X-Forwarded-User"] = []string{session.User}
991− if session.Email != "" {
992− req.Header["X-Forwarded-Email"] = []string{session.Email}
993− } else {
994− req.Header.Del("X-Forwarded-Email")
995− }
996− }
997−
998− if session.PreferredUsername != "" {
999− req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername}
1000− } else {
1001− req.Header.Del("X-Forwarded-Preferred-Username")
1002− }
1003−
1004− if len(session.Groups) > 0 {
1005− for _, group := range session.Groups {
1006− req.Header.Add("X-Forwarded-Groups", group)
1007− }
1008− } else {
1009− req.Header.Del("X-Forwarded-Groups")
1010− }
1011− }
1012−
1013− if p.SetXAuthRequest {
1014− rw.Header().Set("X-Auth-Request-User", session.User)
1015− if session.Email != "" {
1016− rw.Header().Set("X-Auth-Request-Email", session.Email)
1017− } else {
1018− rw.Header().Del("X-Auth-Request-Email")
1019− }
1020− if session.PreferredUsername != "" {
1021− rw.Header().Set("X-Auth-Request-Preferred-Username", session.PreferredUsername)
1022− } else {
1023− rw.Header().Del("X-Auth-Request-Preferred-Username")
1024− }
1025−
1026− if p.PassAccessToken {
1027− if session.AccessToken != "" {
1028− rw.Header().Set("X-Auth-Request-Access-Token", session.AccessToken)
1029− } else {
1030− rw.Header().Del("X-Auth-Request-Access-Token")
1031− }
1032− }
1033−
1034− if len(session.Groups) > 0 {
1035− for _, group := range session.Groups {
1036− rw.Header().Add("X-Auth-Request-Groups", group)
1037− }
1038− } else {
1039− rw.Header().Del("X-Auth-Request-Groups")
1040− }
1041− }
1042−
1043− if p.PassAccessToken {
1044− if session.AccessToken != "" {
1045− req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken}
1046− } else {
1047− req.Header.Del("X-Forwarded-Access-Token")
1048− }
1049− }
1050−
1051− if p.PassAuthorization {
1052− if session.IDToken != "" {
1053− req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)}
1054− } else {
1055− req.Header.Del("Authorization")
1056− }
1057− }
1058− if p.SetBasicAuth {
1059− switch {
1060− case p.PreferEmailToUser && session.Email != "":
1061− authVal := b64.StdEncoding.EncodeToString([]byte(session.Email + ":" + p.BasicAuthPassword))
1062− rw.Header().Set("Authorization", "Basic "+authVal)
1063− case session.User != "":
1064− authVal := b64.StdEncoding.EncodeToString([]byte(session.User + ":" + p.BasicAuthPassword))
1065− rw.Header().Set("Authorization", "Basic "+authVal)
1066− default:
1067− rw.Header().Del("Authorization")
1068− }
1069− }
1070− if p.SetAuthorization {
1071− if session.IDToken != "" {
1072− rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken))
1073− } else {
1074− rw.Header().Del("Authorization")
1075− }
1076− }
1077−
10781012 if session.Email == "" {
10791013 rw.Header().Set("GAP-Auth", session.User)
10801014 } else {
@@ -1082,32 +1016,6 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
10821016 }
10831017}
10841018
1085−// stripAuthHeaders removes Auth headers for allowlisted routes from skipAuthRegex
1086−func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
1087− if p.PassBasicAuth {
1088− req.Header.Del("X-Forwarded-User")
1089− req.Header.Del("X-Forwarded-Groups")
1090− req.Header.Del("X-Forwarded-Email")
1091− req.Header.Del("X-Forwarded-Preferred-Username")
1092− req.Header.Del("Authorization")
1093− }
1094−
1095− if p.PassUserHeaders {
1096− req.Header.Del("X-Forwarded-User")
1097− req.Header.Del("X-Forwarded-Groups")
1098− req.Header.Del("X-Forwarded-Email")
1099− req.Header.Del("X-Forwarded-Preferred-Username")
1100− }
1101−
1102− if p.PassAccessToken {
1103− req.Header.Del("X-Forwarded-Access-Token")
1104− }
1105−
1106− if p.PassAuthorization {
1107− req.Header.Del("Authorization")
1108− }
1109−}
1110−
11111019// isAjax checks if a request is an ajax request
11121020func isAjax(req *http.Request) bool {
11131021 acceptValues := req.Header.Values("Accept")
oauthproxy_test.go+318−301
@@ -495,6 +495,8 @@ func TestBasicAuthPassword(t *testing.T) {
495495 t.Fatal(err)
496496 }
497497 }))
498+
499+ basicAuthPassword := "This is a secure password"
498500 opts := baseTestOptions()
499501 opts.UpstreamServers = options.Upstreams{
500502 {
@@ -505,11 +507,22 @@ func TestBasicAuthPassword(t *testing.T) {
505507 }
506508
507509 opts.Cookie.Secure = false
508− opts.PassBasicAuth = true
509− opts.SetBasicAuth = true
510− opts.PassUserHeaders = true
511− opts.PreferEmailToUser = true
512− opts.BasicAuthPassword = "This is a secure password"
510+ opts.InjectRequestHeaders = []options.Header{
511+ {
512+ Name: "Authorization",
513+ Values: []options.HeaderValue{
514+ {
515+ ClaimSource: &options.ClaimSource{
516+ Claim: "email",
517+ BasicAuthPassword: &options.SecretSource{
518+ Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthPassword))),
519+ },
520+ },
521+ },
522+ },
523+ },
524+ }
525+
513526 err := validation.Validate(opts)
514527 assert.NoError(t, err)
515528
@@ -524,148 +537,44 @@ func TestBasicAuthPassword(t *testing.T) {
524537 t.Fatal(err)
525538 }
526539
540+ // Save the required session
527541 rw := httptest.NewRecorder()
528− req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
529− req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
530− proxy.ServeHTTP(rw, req)
531− if rw.Code >= 400 {
532− t.Fatalf("expected 3xx got %d", rw.Code)
533− }
534− cookie := rw.Header().Values("Set-Cookie")[1]
535−
536− cookieName := proxy.CookieName
537− var value string
538− keyPrefix := cookieName + "="
539−
540− for _, field := range strings.Split(cookie, "; ") {
541− value = strings.TrimPrefix(field, keyPrefix)
542− if value != field {
543− break
544− } else {
545− value = ""
546− }
547− }
548−
549− req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
550− req.AddCookie(&http.Cookie{
551− Name: cookieName,
552− Value: value,
553− Path: "/",
554− Expires: time.Now().Add(time.Duration(24)),
555− HttpOnly: true,
542+ req, _ := http.NewRequest("GET", "/", nil)
543+ err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
544+ Email: emailAddress,
556545 })
557− req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
546+ assert.NoError(t, err)
558547
548+ // Extract the cookie value to inject into the test request
549+ cookie := rw.Header().Values("Set-Cookie")[0]
550+
551+ req, _ = http.NewRequest("GET", "/", nil)
552+ req.Header.Set("Cookie", cookie)
559553 rw = httptest.NewRecorder()
560554 proxy.ServeHTTP(rw, req)
561555
562556 // The username in the basic auth credentials is expected to be equal to the email address from the
563557 // auth response, so we use the same variable here.
564− expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
558+ expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+basicAuthPassword))
565559 assert.Equal(t, expectedHeader, rw.Body.String())
566560 providerServer.Close()
567561}
568562
569−func TestBasicAuthWithEmail(t *testing.T) {
570− opts := baseTestOptions()
571− opts.PassBasicAuth = true
572− opts.PassUserHeaders = false
573− opts.PreferEmailToUser = false
574− opts.BasicAuthPassword = "This is a secure password"
575− err := validation.Validate(opts)
576− assert.NoError(t, err)
577−
578− const emailAddress = "john.doe@example.com"
579− const userName = "9fcab5c9b889a557"
580−
581− // The username in the basic auth credentials is expected to be equal to the email address from the
582− expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
583− expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
584−
585− created := time.Now()
586− session := &sessions.SessionState{
587− User: userName,
588− Email: emailAddress,
589− AccessToken: "oauth_token",
590− CreatedAt: &created,
591− }
592− {
593− rw := httptest.NewRecorder()
594− req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
595− proxy, err := NewOAuthProxy(opts, func(email string) bool {
596− return email == emailAddress
597− })
598− if err != nil {
599− t.Fatal(err)
600− }
601− proxy.addHeadersForProxying(rw, req, session)
602− assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
603− assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
604− }
605−
606− opts.PreferEmailToUser = true
607− {
608− rw := httptest.NewRecorder()
609− req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
610−
611− proxy, err := NewOAuthProxy(opts, func(email string) bool {
612− return email == emailAddress
613− })
614− if err != nil {
615− t.Fatal(err)
616− }
617− proxy.addHeadersForProxying(rw, req, session)
618− assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
619− assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
620− }
621−}
622−
623−func TestPassUserHeadersWithEmail(t *testing.T) {
563+func TestPassGroupsHeadersWithGroups(t *testing.T) {
624564 opts := baseTestOptions()
625− err := validation.Validate(opts)
626− assert.NoError(t, err)
627−
628− const emailAddress = "john.doe@example.com"
629− const userName = "9fcab5c9b889a557"
630−
631− created := time.Now()
632− session := &sessions.SessionState{
633− User: userName,
634− Email: emailAddress,
635− AccessToken: "oauth_token",
636− CreatedAt: &created,
637− }
638− {
639− rw := httptest.NewRecorder()
640− req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
641− proxy, err := NewOAuthProxy(opts, func(email string) bool {
642− return email == emailAddress
643− })
644− if err != nil {
645− t.Fatal(err)
646− }
647− proxy.addHeadersForProxying(rw, req, session)
648− assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
649− }
650−
651− opts.PreferEmailToUser = true
652− {
653− rw := httptest.NewRecorder()
654− req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
655−
656− proxy, err := NewOAuthProxy(opts, func(email string) bool {
657− return email == emailAddress
658− })
659− if err != nil {
660− t.Fatal(err)
661− }
662− proxy.addHeadersForProxying(rw, req, session)
663− assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
565+ opts.InjectRequestHeaders = []options.Header{
566+ {
567+ Name: "X-Forwarded-Groups",
568+ Values: []options.HeaderValue{
569+ {
570+ ClaimSource: &options.ClaimSource{
571+ Claim: "groups",
572+ },
573+ },
574+ },
575+ },
664576 }
665−}
666577
667−func TestPassGroupsHeadersWithGroups(t *testing.T) {
668− opts := baseTestOptions()
669578 err := validation.Validate(opts)
670579 assert.NoError(t, err)
671580
@@ -681,161 +590,27 @@ func TestPassGroupsHeadersWithGroups(t *testing.T) {
681590 AccessToken: "oauth_token",
682591 CreatedAt: &created,
683592 }
684− {
685− rw := httptest.NewRecorder()
686− req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
687− proxy, err := NewOAuthProxy(opts, func(email string) bool {
688− return email == emailAddress
689− })
690− if err != nil {
691− t.Fatal(err)
692− }
693− proxy.addHeadersForProxying(rw, req, session)
694− assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
695− }
696−}
697−
698−func TestStripAuthHeaders(t *testing.T) {
699− testCases := map[string]struct {
700− SkipAuthStripHeaders bool
701− PassBasicAuth bool
702− PassUserHeaders bool
703− PassAccessToken bool
704− PassAuthorization bool
705− StrippedHeaders map[string]bool
706− }{
707− "Default options": {
708− SkipAuthStripHeaders: true,
709− PassBasicAuth: true,
710− PassUserHeaders: true,
711− PassAccessToken: false,
712− PassAuthorization: false,
713− StrippedHeaders: map[string]bool{
714− "X-Forwarded-User": true,
715− "X-Forwared-Groups": true,
716− "X-Forwarded-Email": true,
717− "X-Forwarded-Preferred-Username": true,
718− "X-Forwarded-Access-Token": false,
719− "Authorization": true,
720− },
721− },
722− "Pass access token": {
723− SkipAuthStripHeaders: true,
724− PassBasicAuth: true,
725− PassUserHeaders: true,
726− PassAccessToken: true,
727− PassAuthorization: false,
728− StrippedHeaders: map[string]bool{
729− "X-Forwarded-User": true,
730− "X-Forwared-Groups": true,
731− "X-Forwarded-Email": true,
732− "X-Forwarded-Preferred-Username": true,
733− "X-Forwarded-Access-Token": true,
734− "Authorization": true,
735− },
736− },
737− "Nothing setting Authorization": {
738− SkipAuthStripHeaders: true,
739− PassBasicAuth: false,
740− PassUserHeaders: true,
741− PassAccessToken: true,
742− PassAuthorization: false,
743− StrippedHeaders: map[string]bool{
744− "X-Forwarded-User": true,
745− "X-Forwared-Groups": true,
746− "X-Forwarded-Email": true,
747− "X-Forwarded-Preferred-Username": true,
748− "X-Forwarded-Access-Token": true,
749− "Authorization": false,
750− },
751− },
752− "Only Authorization header modified": {
753− SkipAuthStripHeaders: true,
754− PassBasicAuth: false,
755− PassUserHeaders: false,
756− PassAccessToken: false,
757− PassAuthorization: true,
758− StrippedHeaders: map[string]bool{
759− "X-Forwarded-User": false,
760− "X-Forwared-Groups": false,
761− "X-Forwarded-Email": false,
762− "X-Forwarded-Preferred-Username": false,
763− "X-Forwarded-Access-Token": false,
764− "Authorization": true,
765− },
766− },
767− "Don't strip any headers (default options)": {
768− SkipAuthStripHeaders: false,
769− PassBasicAuth: true,
770− PassUserHeaders: true,
771− PassAccessToken: false,
772− PassAuthorization: false,
773− StrippedHeaders: map[string]bool{
774− "X-Forwarded-User": false,
775− "X-Forwared-Groups": false,
776− "X-Forwarded-Email": false,
777− "X-Forwarded-Preferred-Username": false,
778− "X-Forwarded-Access-Token": false,
779− "Authorization": false,
780− },
781− },
782− "Don't strip any headers (custom options)": {
783− SkipAuthStripHeaders: false,
784− PassBasicAuth: true,
785− PassUserHeaders: true,
786− PassAccessToken: true,
787− PassAuthorization: false,
788− StrippedHeaders: map[string]bool{
789− "X-Forwarded-User": false,
790− "X-Forwared-Groups": false,
791− "X-Forwarded-Email": false,
792− "X-Forwarded-Preferred-Username": false,
793− "X-Forwarded-Access-Token": false,
794− "Authorization": false,
795− },
796− },
797− }
798593
799− initialHeaders := map[string]string{
800− "X-Forwarded-User": "9fcab5c9b889a557",
801− "X-Forwarded-Email": "john.doe@example.com",
802− "X-Forwarded-Groups": "a,b,c",
803− "X-Forwarded-Preferred-Username": "john.doe",
804− "X-Forwarded-Access-Token": "AccessToken",
805− "Authorization": "bearer IDToken",
806− }
594+ proxy, err := NewOAuthProxy(opts, func(email string) bool {
595+ return email == emailAddress
596+ })
597+ assert.NoError(t, err)
807598
808− for name, tc := range testCases {
809− t.Run(name, func(t *testing.T) {
810− opts := baseTestOptions()
811− opts.SkipAuthStripHeaders = tc.SkipAuthStripHeaders
812− opts.PassBasicAuth = tc.PassBasicAuth
813− opts.PassUserHeaders = tc.PassUserHeaders
814− opts.PassAccessToken = tc.PassAccessToken
815− opts.PassAuthorization = tc.PassAuthorization
816− err := validation.Validate(opts)
817− assert.NoError(t, err)
599+ // Save the required session
600+ rw := httptest.NewRecorder()
601+ req, _ := http.NewRequest("GET", "/", nil)
602+ err = proxy.sessionStore.Save(rw, req, session)
603+ assert.NoError(t, err)
818604
819− req, _ := http.NewRequest("GET", fmt.Sprintf("%s/testCase", opts.ProxyPrefix), nil)
820− for header, val := range initialHeaders {
821− req.Header.Set(header, val)
822− }
605+ // Extract the cookie value to inject into the test request
606+ cookie := rw.Header().Values("Set-Cookie")[0]
823607
824− proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
825− assert.NoError(t, err)
826− if proxy.skipAuthStripHeaders {
827− proxy.stripAuthHeaders(req)
828− }
608+ req, _ = http.NewRequest("GET", "/", nil)
609+ req.Header.Set("Cookie", cookie)
610+ rw = httptest.NewRecorder()
611+ proxy.ServeHTTP(rw, req)
829612
830− for header, stripped := range tc.StrippedHeaders {
831− if stripped {
832− assert.Equal(t, req.Header.Get(header), "")
833− } else {
834− assert.Equal(t, req.Header.Get(header), initialHeaders[header])
835− }
836− }
837− })
838− }
613+ assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
839614}
840615
841616type PassAccessTokenTest struct {
@@ -884,7 +659,21 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
884659 }
885660
886661 patt.opts.Cookie.Secure = false
887− patt.opts.PassAccessToken = opts.PassAccessToken
662+ if opts.PassAccessToken {
663+ patt.opts.InjectRequestHeaders = []options.Header{
664+ {
665+ Name: "X-Forwarded-Access-Token",
666+ Values: []options.HeaderValue{
667+ {
668+ ClaimSource: &options.ClaimSource{
669+ Claim: "access_token",
670+ },
671+ },
672+ },
673+ },
674+ }
675+ }
676+
888677 err := validation.Validate(patt.opts)
889678 if err != nil {
890679 return nil, err
@@ -1442,7 +1231,48 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
14421231 var pcTest ProcessCookieTest
14431232
14441233 pcTest.opts = baseTestOptions()
1445− pcTest.opts.SetXAuthRequest = true
1234+ pcTest.opts.InjectResponseHeaders = []options.Header{
1235+ {
1236+ Name: "X-Auth-Request-User",
1237+ Values: []options.HeaderValue{
1238+ {
1239+ ClaimSource: &options.ClaimSource{
1240+ Claim: "user",
1241+ },
1242+ },
1243+ },
1244+ },
1245+ {
1246+ Name: "X-Auth-Request-Email",
1247+ Values: []options.HeaderValue{
1248+ {
1249+ ClaimSource: &options.ClaimSource{
1250+ Claim: "email",
1251+ },
1252+ },
1253+ },
1254+ },
1255+ {
1256+ Name: "X-Auth-Request-Groups",
1257+ Values: []options.HeaderValue{
1258+ {
1259+ ClaimSource: &options.ClaimSource{
1260+ Claim: "groups",
1261+ },
1262+ },
1263+ },
1264+ },
1265+ {
1266+ Name: "X-Forwarded-Preferred-Username",
1267+ Values: []options.HeaderValue{
1268+ {
1269+ ClaimSource: &options.ClaimSource{
1270+ Claim: "preferred_username",
1271+ },
1272+ },
1273+ },
1274+ },
1275+ }
14461276 pcTest.opts.AllowedGroups = []string{"oauth_groups"}
14471277 err := validation.Validate(pcTest.opts)
14481278 assert.NoError(t, err)
@@ -1480,8 +1310,62 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
14801310 var pcTest ProcessCookieTest
14811311
14821312 pcTest.opts = baseTestOptions()
1483− pcTest.opts.SetXAuthRequest = true
1484− pcTest.opts.SetBasicAuth = true
1313+ pcTest.opts.InjectResponseHeaders = []options.Header{
1314+ {
1315+ Name: "X-Auth-Request-User",
1316+ Values: []options.HeaderValue{
1317+ {
1318+ ClaimSource: &options.ClaimSource{
1319+ Claim: "user",
1320+ },
1321+ },
1322+ },
1323+ },
1324+ {
1325+ Name: "X-Auth-Request-Email",
1326+ Values: []options.HeaderValue{
1327+ {
1328+ ClaimSource: &options.ClaimSource{
1329+ Claim: "email",
1330+ },
1331+ },
1332+ },
1333+ },
1334+ {
1335+ Name: "X-Auth-Request-Groups",
1336+ Values: []options.HeaderValue{
1337+ {
1338+ ClaimSource: &options.ClaimSource{
1339+ Claim: "groups",
1340+ },
1341+ },
1342+ },
1343+ },
1344+ {
1345+ Name: "X-Forwarded-Preferred-Username",
1346+ Values: []options.HeaderValue{
1347+ {
1348+ ClaimSource: &options.ClaimSource{
1349+ Claim: "preferred_username",
1350+ },
1351+ },
1352+ },
1353+ },
1354+ {
1355+ Name: "Authorization",
1356+ Values: []options.HeaderValue{
1357+ {
1358+ ClaimSource: &options.ClaimSource{
1359+ Claim: "user",
1360+ BasicAuthPassword: &options.SecretSource{
1361+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
1362+ },
1363+ },
1364+ },
1365+ },
1366+ },
1367+ }
1368+
14851369 err := validation.Validate(pcTest.opts)
14861370 assert.NoError(t, err)
14871371
@@ -1511,7 +1395,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
15111395 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
15121396 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
15131397 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1514− expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
1398+ expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
15151399 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
15161400}
15171401
@@ -1519,8 +1403,48 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
15191403 var pcTest ProcessCookieTest
15201404
15211405 pcTest.opts = baseTestOptions()
1522− pcTest.opts.SetXAuthRequest = true
1523− pcTest.opts.SetBasicAuth = false
1406+ pcTest.opts.InjectResponseHeaders = []options.Header{
1407+ {
1408+ Name: "X-Auth-Request-User",
1409+ Values: []options.HeaderValue{
1410+ {
1411+ ClaimSource: &options.ClaimSource{
1412+ Claim: "user",
1413+ },
1414+ },
1415+ },
1416+ },
1417+ {
1418+ Name: "X-Auth-Request-Email",
1419+ Values: []options.HeaderValue{
1420+ {
1421+ ClaimSource: &options.ClaimSource{
1422+ Claim: "email",
1423+ },
1424+ },
1425+ },
1426+ },
1427+ {
1428+ Name: "X-Auth-Request-Groups",
1429+ Values: []options.HeaderValue{
1430+ {
1431+ ClaimSource: &options.ClaimSource{
1432+ Claim: "groups",
1433+ },
1434+ },
1435+ },
1436+ },
1437+ {
1438+ Name: "X-Forwarded-Preferred-Username",
1439+ Values: []options.HeaderValue{
1440+ {
1441+ ClaimSource: &options.ClaimSource{
1442+ Claim: "preferred_username",
1443+ },
1444+ },
1445+ },
1446+ },
1447+ }
15241448 err := validation.Validate(pcTest.opts)
15251449 assert.NoError(t, err)
15261450
@@ -1985,9 +1909,74 @@ func TestGetJwtSession(t *testing.T) {
19851909 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
19861910
19871911 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1988− opts.PassAuthorization = true
1989− opts.SetAuthorization = true
1990− opts.SetXAuthRequest = true
1912+ opts.InjectRequestHeaders = []options.Header{
1913+ {
1914+ Name: "Authorization",
1915+ Values: []options.HeaderValue{
1916+ {
1917+ ClaimSource: &options.ClaimSource{
1918+ Claim: "id_token",
1919+ Prefix: "Bearer ",
1920+ },
1921+ },
1922+ },
1923+ },
1924+ {
1925+ Name: "X-Forwarded-User",
1926+ Values: []options.HeaderValue{
1927+ {
1928+ ClaimSource: &options.ClaimSource{
1929+ Claim: "user",
1930+ },
1931+ },
1932+ },
1933+ },
1934+ {
1935+ Name: "X-Forwarded-Email",
1936+ Values: []options.HeaderValue{
1937+ {
1938+ ClaimSource: &options.ClaimSource{
1939+ Claim: "email",
1940+ },
1941+ },
1942+ },
1943+ },
1944+ }
1945+
1946+ opts.InjectResponseHeaders = []options.Header{
1947+ {
1948+ Name: "Authorization",
1949+ Values: []options.HeaderValue{
1950+ {
1951+ ClaimSource: &options.ClaimSource{
1952+ Claim: "id_token",
1953+ Prefix: "Bearer ",
1954+ },
1955+ },
1956+ },
1957+ },
1958+ {
1959+ Name: "X-Auth-Request-User",
1960+ Values: []options.HeaderValue{
1961+ {
1962+ ClaimSource: &options.ClaimSource{
1963+ Claim: "user",
1964+ },
1965+ },
1966+ },
1967+ },
1968+ {
1969+ Name: "X-Auth-Request-Email",
1970+ Values: []options.HeaderValue{
1971+ {
1972+ ClaimSource: &options.ClaimSource{
1973+ Claim: "email",
1974+ },
1975+ },
1976+ },
1977+ },
1978+ }
1979+
19911980 opts.SkipJwtBearerTokens = true
19921981 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
19931982 })
@@ -2004,15 +1993,6 @@ func TestGetJwtSession(t *testing.T) {
20041993 "Authorization": {authHeader},
20051994 }
20061995
2007− // Bearer
2008− expires := time.Unix(1912151821, 0)
2009− session, err := test.proxy.getAuthenticatedSession(test.rw, test.req)
2010− assert.NoError(t, err)
2011− assert.Equal(t, session.User, "1234567890")
2012− assert.Equal(t, session.Email, "john@example.com")
2013− assert.Equal(t, session.ExpiresOn, &expires)
2014− assert.Equal(t, session.IDToken, goodJwt)
2015−
20161996 test.proxy.ServeHTTP(test.rw, test.req)
20171997 if test.rw.Code >= 400 {
20181998 t.Fatalf("expected 3xx got %d", test.rw.Code)
@@ -2140,6 +2120,43 @@ func baseTestOptions() *options.Options {
21402120 opts.ClientID = clientID
21412121 opts.ClientSecret = clientSecret
21422122 opts.EmailDomains = []string{"*"}
2123+
2124+ // Default injected headers for legacy configuration
2125+ opts.InjectRequestHeaders = []options.Header{
2126+ {
2127+ Name: "Authorization",
2128+ Values: []options.HeaderValue{
2129+ {
2130+ ClaimSource: &options.ClaimSource{
2131+ Claim: "user",
2132+ BasicAuthPassword: &options.SecretSource{
2133+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
2134+ },
2135+ },
2136+ },
2137+ },
2138+ },
2139+ {
2140+ Name: "X-Forwarded-User",
2141+ Values: []options.HeaderValue{
2142+ {
2143+ ClaimSource: &options.ClaimSource{
2144+ Claim: "user",
2145+ },
2146+ },
2147+ },
2148+ },
2149+ {
2150+ Name: "X-Forwarded-Email",
2151+ Values: []options.HeaderValue{
2152+ {
2153+ ClaimSource: &options.ClaimSource{
2154+ Claim: "email",
2155+ },
2156+ },
2157+ },
2158+ },
2159+ }
21432160 return opts
21442161}
21452162
pkg/apis/options/legacy_options.go+261
@@ -1,6 +1,7 @@
11package options
22
33import (
4+ "encoding/base64"
45 "fmt"
56 "net/url"
67 "strconv"
@@ -15,6 +16,9 @@ type LegacyOptions struct {
1516 // Legacy options related to upstream servers
1617 LegacyUpstreams LegacyUpstreams `cfg:",squash"`
1718
19+ // Legacy options for injecting request/response headers
20+ LegacyHeaders LegacyHeaders `cfg:",squash"`
21+
1822 Options Options `cfg:",squash"`
1923}
2024
@@ -26,6 +30,11 @@ func NewLegacyOptions() *LegacyOptions {
2630 FlushInterval: time.Duration(1) * time.Second,
2731 },
2832
33+ LegacyHeaders: LegacyHeaders{
34+ PassBasicAuth: true,
35+ PassUserHeaders: true,
36+ },
37+
2938 Options: *NewOptions(),
3039 }
3140}
@@ -37,6 +46,7 @@ func (l *LegacyOptions) ToOptions() (*Options, error) {
3746 }
3847 l.Options.UpstreamServers = upstreams
3948
49+ l.Options.InjectRequestHeaders, l.Options.InjectResponseHeaders = l.LegacyHeaders.convert()
4050 return &l.Options, nil
4151}
4252
@@ -119,3 +129,254 @@ func (l *LegacyUpstreams) convert() (Upstreams, error) {
119129
120130 return upstreams, nil
121131}
132+
133+type LegacyHeaders struct {
134+ PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
135+ PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
136+ PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
137+ PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
138+
139+ SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth"`
140+ SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
141+ SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header"`
142+
143+ PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
144+ BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
145+ SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
146+}
147+
148+func legacyHeadersFlagSet() *pflag.FlagSet {
149+ flagSet := pflag.NewFlagSet("headers", pflag.ExitOnError)
150+
151+ flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
152+ flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
153+ flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
154+ flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
155+
156+ flagSet.Bool("set-basic-auth", false, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)")
157+ flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
158+ flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)")
159+
160+ flagSet.Bool("prefer-email-to-user", false, "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers")
161+ flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
162+ flagSet.Bool("skip-auth-strip-headers", false, "strips X-Forwarded-* style authentication headers & Authorization header if they would be set by oauth2-proxy for request paths in --skip-auth-regex")
163+
164+ return flagSet
165+}
166+
167+// convert takes the legacy request/response headers and converts them to
168+// the new format for InjectRequestHeaders and InjectResponseHeaders
169+func (l *LegacyHeaders) convert() ([]Header, []Header) {
170+ return l.getRequestHeaders(), l.getResponseHeaders()
171+}
172+
173+func (l *LegacyHeaders) getRequestHeaders() []Header {
174+ requestHeaders := []Header{}
175+
176+ if l.PassBasicAuth && l.BasicAuthPassword != "" {
177+ requestHeaders = append(requestHeaders, getBasicAuthHeader(l.PreferEmailToUser, l.BasicAuthPassword))
178+ }
179+
180+ // In the old implementation, PassUserHeaders is a subset of PassBasicAuth
181+ if l.PassBasicAuth || l.PassUserHeaders {
182+ requestHeaders = append(requestHeaders, getPassUserHeaders(l.PreferEmailToUser)...)
183+ requestHeaders = append(requestHeaders, getPreferredUsernameHeader())
184+ }
185+
186+ if l.PassAccessToken {
187+ requestHeaders = append(requestHeaders, getPassAccessTokenHeader())
188+ }
189+
190+ if l.PassAuthorization {
191+ requestHeaders = append(requestHeaders, getAuthorizationHeader())
192+ }
193+
194+ for i := range requestHeaders {
195+ requestHeaders[i].PreserveRequestValue = !l.SkipAuthStripHeaders
196+ }
197+
198+ return requestHeaders
199+}
200+
201+func (l *LegacyHeaders) getResponseHeaders() []Header {
202+ responseHeaders := []Header{}
203+
204+ if l.SetXAuthRequest {
205+ responseHeaders = append(responseHeaders, getXAuthRequestHeaders(l.PassAccessToken)...)
206+ }
207+
208+ if l.SetBasicAuth {
209+ responseHeaders = append(responseHeaders, getBasicAuthHeader(l.PreferEmailToUser, l.BasicAuthPassword))
210+ }
211+
212+ if l.SetAuthorization {
213+ responseHeaders = append(responseHeaders, getAuthorizationHeader())
214+ }
215+
216+ return responseHeaders
217+}
218+
219+func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header {
220+ claim := "user"
221+ if preferEmailToUser {
222+ claim = "email"
223+ }
224+
225+ return Header{
226+ Name: "Authorization",
227+ Values: []HeaderValue{
228+ {
229+ ClaimSource: &ClaimSource{
230+ Claim: claim,
231+ BasicAuthPassword: &SecretSource{
232+ Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthPassword))),
233+ },
234+ },
235+ },
236+ },
237+ }
238+}
239+
240+func getPassUserHeaders(preferEmailToUser bool) []Header {
241+ headers := []Header{
242+ {
243+ Name: "X-Forwarded-Groups",
244+ Values: []HeaderValue{
245+ {
246+ ClaimSource: &ClaimSource{
247+ Claim: "groups",
248+ },
249+ },
250+ },
251+ },
252+ }
253+
254+ if preferEmailToUser {
255+ return append(headers,
256+ Header{
257+ Name: "X-Forwarded-User",
258+ Values: []HeaderValue{
259+ {
260+ ClaimSource: &ClaimSource{
261+ Claim: "email",
262+ },
263+ },
264+ },
265+ },
266+ )
267+ }
268+
269+ return append(headers,
270+ Header{
271+ Name: "X-Forwarded-User",
272+ Values: []HeaderValue{
273+ {
274+ ClaimSource: &ClaimSource{
275+ Claim: "user",
276+ },
277+ },
278+ },
279+ },
280+ Header{
281+ Name: "X-Forwarded-Email",
282+ Values: []HeaderValue{
283+ {
284+ ClaimSource: &ClaimSource{
285+ Claim: "email",
286+ },
287+ },
288+ },
289+ },
290+ )
291+}
292+
293+func getPassAccessTokenHeader() Header {
294+ return Header{
295+ Name: "X-Forwarded-Access-Token",
296+ Values: []HeaderValue{
297+ {
298+ ClaimSource: &ClaimSource{
299+ Claim: "access_token",
300+ },
301+ },
302+ },
303+ }
304+}
305+
306+func getAuthorizationHeader() Header {
307+ return Header{
308+ Name: "Authorization",
309+ Values: []HeaderValue{
310+ {
311+ ClaimSource: &ClaimSource{
312+ Claim: "id_token",
313+ Prefix: "Bearer ",
314+ },
315+ },
316+ },
317+ }
318+}
319+
320+func getPreferredUsernameHeader() Header {
321+ return Header{
322+ Name: "X-Forwarded-Preferred-Username",
323+ Values: []HeaderValue{
324+ {
325+ ClaimSource: &ClaimSource{
326+ Claim: "preferred_username",
327+ },
328+ },
329+ },
330+ }
331+}
332+
333+func getXAuthRequestHeaders(passAccessToken bool) []Header {
334+ headers := []Header{
335+ {
336+ Name: "X-Auth-Request-User",
337+ Values: []HeaderValue{
338+ {
339+ ClaimSource: &ClaimSource{
340+ Claim: "user",
341+ },
342+ },
343+ },
344+ },
345+ {
346+ Name: "X-Auth-Request-Email",
347+ Values: []HeaderValue{
348+ {
349+ ClaimSource: &ClaimSource{
350+ Claim: "email",
351+ },
352+ },
353+ },
354+ },
355+ getPreferredUsernameHeader(),
356+ {
357+ Name: "X-Auth-Request-Groups",
358+ Values: []HeaderValue{
359+ {
360+ ClaimSource: &ClaimSource{
361+ Claim: "groups",
362+ },
363+ },
364+ },
365+ },
366+ }
367+
368+ if passAccessToken {
369+ headers = append(headers, Header{
370+ Name: "X-Auth-Request-Access-Token",
371+ Values: []HeaderValue{
372+ {
373+ ClaimSource: &ClaimSource{
374+ Claim: "access_token",
375+ },
376+ },
377+ },
378+ })
379+ }
380+
381+ return headers
382+}
pkg/apis/options/legacy_options_test.go+471
@@ -1,6 +1,7 @@
11package options
22
33import (
4+ "encoding/base64"
45 "time"
56
67 . "github.com/onsi/ginkgo"
@@ -57,6 +58,55 @@ var _ = Describe("Legacy Options", func() {
5758 },
5859 }
5960
61+ opts.InjectRequestHeaders = []Header{
62+ {
63+ Name: "X-Forwarded-Groups",
64+ PreserveRequestValue: true,
65+ Values: []HeaderValue{
66+ {
67+ ClaimSource: &ClaimSource{
68+ Claim: "groups",
69+ },
70+ },
71+ },
72+ },
73+ {
74+ Name: "X-Forwarded-User",
75+ PreserveRequestValue: true,
76+ Values: []HeaderValue{
77+ {
78+ ClaimSource: &ClaimSource{
79+ Claim: "user",
80+ },
81+ },
82+ },
83+ },
84+ {
85+ Name: "X-Forwarded-Email",
86+ PreserveRequestValue: true,
87+ Values: []HeaderValue{
88+ {
89+ ClaimSource: &ClaimSource{
90+ Claim: "email",
91+ },
92+ },
93+ },
94+ },
95+ {
96+ Name: "X-Forwarded-Preferred-Username",
97+ PreserveRequestValue: true,
98+ Values: []HeaderValue{
99+ {
100+ ClaimSource: &ClaimSource{
101+ Claim: "preferred_username",
102+ },
103+ },
104+ },
105+ },
106+ }
107+
108+ opts.InjectResponseHeaders = []Header{}
109+
60110 converted, err := legacyOpts.ToOptions()
61111 Expect(err).ToNot(HaveOccurred())
62112 Expect(converted).To(Equal(opts))
@@ -210,4 +260,425 @@ var _ = Describe("Legacy Options", func() {
210260 }),
211261 )
212262 })
263+
264+ Context("Legacy Headers", func() {
265+ const basicAuthSecret = "super-secret-password"
266+
267+ type legacyHeadersTableInput struct {
268+ legacyHeaders *LegacyHeaders
269+ expectedRequestHeaders []Header
270+ expectedResponseHeaders []Header
271+ }
272+
273+ withPreserveRequestValue := func(h Header, preserve bool) Header {
274+ h.PreserveRequestValue = preserve
275+ return h
276+ }
277+
278+ xForwardedUser := Header{
279+ Name: "X-Forwarded-User",
280+ PreserveRequestValue: true,
281+ Values: []HeaderValue{
282+ {
283+ ClaimSource: &ClaimSource{
284+ Claim: "user",
285+ },
286+ },
287+ },
288+ }
289+
290+ xForwardedEmail := Header{
291+ Name: "X-Forwarded-Email",
292+ PreserveRequestValue: true,
293+ Values: []HeaderValue{
294+ {
295+ ClaimSource: &ClaimSource{
296+ Claim: "email",
297+ },
298+ },
299+ },
300+ }
301+
302+ xForwardedGroups := Header{
303+ Name: "X-Forwarded-Groups",
304+ PreserveRequestValue: true,
305+ Values: []HeaderValue{
306+ {
307+ ClaimSource: &ClaimSource{
308+ Claim: "groups",
309+ },
310+ },
311+ },
312+ }
313+
314+ xForwardedPreferredUsername := Header{
315+ Name: "X-Forwarded-Preferred-Username",
316+ PreserveRequestValue: true,
317+ Values: []HeaderValue{
318+ {
319+ ClaimSource: &ClaimSource{
320+ Claim: "preferred_username",
321+ },
322+ },
323+ },
324+ }
325+
326+ basicAuthHeader := Header{
327+ Name: "Authorization",
328+ PreserveRequestValue: true,
329+ Values: []HeaderValue{
330+ {
331+ ClaimSource: &ClaimSource{
332+ Claim: "user",
333+ BasicAuthPassword: &SecretSource{
334+ Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthSecret))),
335+ },
336+ },
337+ },
338+ },
339+ }
340+
341+ xForwardedUserWithEmail := Header{
342+ Name: "X-Forwarded-User",
343+ PreserveRequestValue: true,
344+ Values: []HeaderValue{
345+ {
346+ ClaimSource: &ClaimSource{
347+ Claim: "email",
348+ },
349+ },
350+ },
351+ }
352+
353+ basicAuthHeaderWithEmail := Header{
354+ Name: "Authorization",
355+ PreserveRequestValue: true,
356+ Values: []HeaderValue{
357+ {
358+ ClaimSource: &ClaimSource{
359+ Claim: "email",
360+ BasicAuthPassword: &SecretSource{
361+ Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthSecret))),
362+ },
363+ },
364+ },
365+ },
366+ }
367+
368+ xAuthRequestUser := Header{
369+ Name: "X-Auth-Request-User",
370+ PreserveRequestValue: false,
371+ Values: []HeaderValue{
372+ {
373+ ClaimSource: &ClaimSource{
374+ Claim: "user",
375+ },
376+ },
377+ },
378+ }
379+
380+ xAuthRequestEmail := Header{
381+ Name: "X-Auth-Request-Email",
382+ PreserveRequestValue: false,
383+ Values: []HeaderValue{
384+ {
385+ ClaimSource: &ClaimSource{
386+ Claim: "email",
387+ },
388+ },
389+ },
390+ }
391+
392+ xAuthRequestGroups := Header{
393+ Name: "X-Auth-Request-Groups",
394+ PreserveRequestValue: false,
395+ Values: []HeaderValue{
396+ {
397+ ClaimSource: &ClaimSource{
398+ Claim: "groups",
399+ },
400+ },
401+ },
402+ }
403+
404+ xForwardedAccessToken := Header{
405+ Name: "X-Forwarded-Access-Token",
406+ PreserveRequestValue: true,
407+ Values: []HeaderValue{
408+ {
409+ ClaimSource: &ClaimSource{
410+ Claim: "access_token",
411+ },
412+ },
413+ },
414+ }
415+
416+ xAuthRequestAccessToken := Header{
417+ Name: "X-Auth-Request-Access-Token",
418+ PreserveRequestValue: false,
419+ Values: []HeaderValue{
420+ {
421+ ClaimSource: &ClaimSource{
422+ Claim: "access_token",
423+ },
424+ },
425+ },
426+ }
427+
428+ authorizationHeader := Header{
429+ Name: "Authorization",
430+ PreserveRequestValue: true,
431+ Values: []HeaderValue{
432+ {
433+ ClaimSource: &ClaimSource{
434+ Claim: "id_token",
435+ Prefix: "Bearer ",
436+ },
437+ },
438+ },
439+ }
440+
441+ DescribeTable("should convert to injectRequestHeaders",
442+ func(in legacyHeadersTableInput) {
443+ requestHeaders, responseHeaders := in.legacyHeaders.convert()
444+ Expect(requestHeaders).To(ConsistOf(in.expectedRequestHeaders))
445+ Expect(responseHeaders).To(ConsistOf(in.expectedResponseHeaders))
446+ },
447+ Entry("with all header options off", legacyHeadersTableInput{
448+ legacyHeaders: &LegacyHeaders{
449+ PassBasicAuth: false,
450+ PassAccessToken: false,
451+ PassUserHeaders: false,
452+ PassAuthorization: false,
453+
454+ SetBasicAuth: false,
455+ SetXAuthRequest: false,
456+ SetAuthorization: false,
457+
458+ PreferEmailToUser: false,
459+ BasicAuthPassword: "",
460+ SkipAuthStripHeaders: false,
461+ },
462+ expectedRequestHeaders: []Header{},
463+ expectedResponseHeaders: []Header{},
464+ }),
465+ Entry("with basic auth enabled", legacyHeadersTableInput{
466+ legacyHeaders: &LegacyHeaders{
467+ PassBasicAuth: true,
468+ PassAccessToken: false,
469+ PassUserHeaders: false,
470+ PassAuthorization: false,
471+
472+ SetBasicAuth: true,
473+ SetXAuthRequest: false,
474+ SetAuthorization: false,
475+
476+ PreferEmailToUser: false,
477+ BasicAuthPassword: basicAuthSecret,
478+ SkipAuthStripHeaders: false,
479+ },
480+ expectedRequestHeaders: []Header{
481+ xForwardedUser,
482+ xForwardedEmail,
483+ xForwardedGroups,
484+ xForwardedPreferredUsername,
485+ basicAuthHeader,
486+ },
487+ expectedResponseHeaders: []Header{
488+ withPreserveRequestValue(basicAuthHeader, false),
489+ },
490+ }),
491+ Entry("with basic auth enabled and skipAuthStripHeaders", legacyHeadersTableInput{
492+ legacyHeaders: &LegacyHeaders{
493+ PassBasicAuth: true,
494+ PassAccessToken: false,
495+ PassUserHeaders: false,
496+ PassAuthorization: false,
497+
498+ SetBasicAuth: true,
499+ SetXAuthRequest: false,
500+ SetAuthorization: false,
501+
502+ PreferEmailToUser: false,
503+ BasicAuthPassword: basicAuthSecret,
504+ SkipAuthStripHeaders: true,
505+ },
506+ expectedRequestHeaders: []Header{
507+ withPreserveRequestValue(xForwardedUser, false),
508+ withPreserveRequestValue(xForwardedEmail, false),
509+ withPreserveRequestValue(xForwardedGroups, false),
510+ withPreserveRequestValue(xForwardedPreferredUsername, false),
511+ withPreserveRequestValue(basicAuthHeader, false),
512+ },
513+ expectedResponseHeaders: []Header{
514+ withPreserveRequestValue(basicAuthHeader, false),
515+ },
516+ }),
517+ Entry("with basic auth enabled and preferEmailToUser", legacyHeadersTableInput{
518+ legacyHeaders: &LegacyHeaders{
519+ PassBasicAuth: true,
520+ PassAccessToken: false,
521+ PassUserHeaders: false,
522+ PassAuthorization: false,
523+
524+ SetBasicAuth: true,
525+ SetXAuthRequest: false,
526+ SetAuthorization: false,
527+
528+ PreferEmailToUser: true,
529+ BasicAuthPassword: basicAuthSecret,
530+ SkipAuthStripHeaders: false,
531+ },
532+ expectedRequestHeaders: []Header{
533+ xForwardedUserWithEmail,
534+ xForwardedGroups,
535+ xForwardedPreferredUsername,
536+ basicAuthHeaderWithEmail,
537+ },
538+ expectedResponseHeaders: []Header{
539+ withPreserveRequestValue(basicAuthHeaderWithEmail, false),
540+ },
541+ }),
542+ Entry("with basic auth enabled and passUserHeaders", legacyHeadersTableInput{
543+ legacyHeaders: &LegacyHeaders{
544+ PassBasicAuth: true,
545+ PassAccessToken: false,
546+ PassUserHeaders: true,
547+ PassAuthorization: false,
548+
549+ SetBasicAuth: true,
550+ SetXAuthRequest: false,
551+ SetAuthorization: false,
552+
553+ PreferEmailToUser: false,
554+ BasicAuthPassword: basicAuthSecret,
555+ SkipAuthStripHeaders: false,
556+ },
557+ expectedRequestHeaders: []Header{
558+ xForwardedUser,
559+ xForwardedEmail,
560+ xForwardedGroups,
561+ xForwardedPreferredUsername,
562+ basicAuthHeader,
563+ },
564+ expectedResponseHeaders: []Header{
565+ withPreserveRequestValue(basicAuthHeader, false),
566+ },
567+ }),
568+ Entry("with passUserHeaders", legacyHeadersTableInput{
569+ legacyHeaders: &LegacyHeaders{
570+ PassBasicAuth: false,
571+ PassAccessToken: false,
572+ PassUserHeaders: true,
573+ PassAuthorization: false,
574+
575+ SetBasicAuth: false,
576+ SetXAuthRequest: false,
577+ SetAuthorization: false,
578+
579+ PreferEmailToUser: false,
580+ BasicAuthPassword: "",
581+ SkipAuthStripHeaders: false,
582+ },
583+ expectedRequestHeaders: []Header{
584+ xForwardedUser,
585+ xForwardedEmail,
586+ xForwardedGroups,
587+ xForwardedPreferredUsername,
588+ },
589+ expectedResponseHeaders: []Header{},
590+ }),
591+ Entry("with setXAuthRequest", legacyHeadersTableInput{
592+ legacyHeaders: &LegacyHeaders{
593+ PassBasicAuth: false,
594+ PassAccessToken: false,
595+ PassUserHeaders: false,
596+ PassAuthorization: false,
597+
598+ SetBasicAuth: false,
599+ SetXAuthRequest: true,
600+ SetAuthorization: false,
601+
602+ PreferEmailToUser: false,
603+ BasicAuthPassword: "",
604+ SkipAuthStripHeaders: false,
605+ },
606+ expectedRequestHeaders: []Header{},
607+ expectedResponseHeaders: []Header{
608+ xAuthRequestUser,
609+ xAuthRequestEmail,
610+ xAuthRequestGroups,
611+ withPreserveRequestValue(xForwardedPreferredUsername, false),
612+ },
613+ }),
614+ Entry("with passAccessToken", legacyHeadersTableInput{
615+ legacyHeaders: &LegacyHeaders{
616+ PassBasicAuth: false,
617+ PassAccessToken: true,
618+ PassUserHeaders: false,
619+ PassAuthorization: false,
620+
621+ SetBasicAuth: false,
622+ SetXAuthRequest: false,
623+ SetAuthorization: false,
624+
625+ PreferEmailToUser: false,
626+ BasicAuthPassword: "",
627+ SkipAuthStripHeaders: false,
628+ },
629+ expectedRequestHeaders: []Header{
630+ xForwardedAccessToken,
631+ },
632+ expectedResponseHeaders: []Header{},
633+ }),
634+ Entry("with passAcessToken and setXAuthRequest", legacyHeadersTableInput{
635+ legacyHeaders: &LegacyHeaders{
636+ PassBasicAuth: false,
637+ PassAccessToken: true,
638+ PassUserHeaders: false,
639+ PassAuthorization: false,
640+
641+ SetBasicAuth: false,
642+ SetXAuthRequest: true,
643+ SetAuthorization: false,
644+
645+ PreferEmailToUser: false,
646+ BasicAuthPassword: "",
647+ SkipAuthStripHeaders: false,
648+ },
649+ expectedRequestHeaders: []Header{
650+ xForwardedAccessToken,
651+ },
652+ expectedResponseHeaders: []Header{
653+ xAuthRequestUser,
654+ xAuthRequestEmail,
655+ xAuthRequestGroups,
656+ withPreserveRequestValue(xForwardedPreferredUsername, false),
657+ xAuthRequestAccessToken,
658+ },
659+ }),
660+ Entry("with authorization headers", legacyHeadersTableInput{
661+ legacyHeaders: &LegacyHeaders{
662+ PassBasicAuth: false,
663+ PassAccessToken: false,
664+ PassUserHeaders: false,
665+ PassAuthorization: true,
666+
667+ SetBasicAuth: false,
668+ SetXAuthRequest: false,
669+ SetAuthorization: true,
670+
671+ PreferEmailToUser: false,
672+ BasicAuthPassword: "",
673+ SkipAuthStripHeaders: false,
674+ },
675+ expectedRequestHeaders: []Header{
676+ authorizationHeader,
677+ },
678+ expectedResponseHeaders: []Header{
679+ withPreserveRequestValue(authorizationHeader, false),
680+ },
681+ }),
682+ )
683+ })
213684})
pkg/apis/options/options.go+4−28
@@ -65,22 +65,15 @@ type Options struct {
6565 // TODO(JoelSpeed): Rename when legacy config is removed
6666 UpstreamServers Upstreams `cfg:",internal"`
6767
68+ InjectRequestHeaders []Header `cfg:",internal"`
69+ InjectResponseHeaders []Header `cfg:",internal"`
70+
6871 SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
6972 SkipAuthRoutes []string `flag:"skip-auth-route" cfg:"skip_auth_routes"`
70− SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
7173 SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
7274 ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
73− PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
74− SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth"`
75− PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
76− BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
77− PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
7875 SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button"`
79− PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
8076 SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
81− SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
82− SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header"`
83− PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
8477 SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
8578
8679 // These options allow for other providers besides Google, with
@@ -151,15 +144,7 @@ func NewOptions() *Options {
151144 Cookie: cookieDefaults(),
152145 Session: sessionOptionsDefaults(),
153146 AzureTenant: "common",
154− SetXAuthRequest: false,
155147 SkipAuthPreflight: false,
156− PassBasicAuth: true,
157− SetBasicAuth: false,
158− PassUserHeaders: true,
159− PassAccessToken: false,
160− SetAuthorization: false,
161− PassAuthorization: false,
162− PreferEmailToUser: false,
163148 Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
164149 ApprovalPrompt: "force",
165150 UserIDClaim: "email",
@@ -183,18 +168,8 @@ func NewFlagSet() *pflag.FlagSet {
183168 flagSet.String("tls-cert-file", "", "path to certificate file")
184169 flagSet.String("tls-key-file", "", "path to private key file")
185170 flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
186− flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
187− flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
188− flagSet.Bool("set-basic-auth", false, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)")
189− flagSet.Bool("prefer-email-to-user", false, "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers")
190− flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
191− flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
192− flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
193− flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
194− flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)")
195171 flagSet.StringSlice("skip-auth-regex", []string{}, "(DEPRECATED for --skip-auth-route) bypass authentication for requests path's that match (may be given multiple times)")
196172 flagSet.StringSlice("skip-auth-route", []string{}, "bypass authentication for requests that match the method & path. Format: method=path_regex OR path_regex alone for all methods")
197− flagSet.Bool("skip-auth-strip-headers", false, "strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy for allowlisted requests (`--skip-auth-route`, `--skip-auth-regex`, `--skip-auth-preflight`, `--trusted-ip`)")
198173 flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
199174 flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
200175 flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
@@ -272,6 +247,7 @@ func NewFlagSet() *pflag.FlagSet {
272247 flagSet.AddFlagSet(cookieFlagSet())
273248 flagSet.AddFlagSet(loggingFlagSet())
274249 flagSet.AddFlagSet(legacyUpstreamsFlagSet())
250+ flagSet.AddFlagSet(legacyHeadersFlagSet())
275251
276252 return flagSet
277253}
pkg/validation/common.gonew file+46
@@ -0,0 +1,46 @@
1+package validation
2+
3+import (
4+ "encoding/base64"
5+ "fmt"
6+ "os"
7+
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
9+)
10+
11+const multipleValuesForSecretSource = "multiple values specified for secret source: specify either value, fromEnv of fromFile"
12+
13+func validateSecretSource(source options.SecretSource) string {
14+ switch {
15+ case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "":
16+ return validateSecretSourceValue(source.Value)
17+ case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "":
18+ return validateSecretSourceEnv(source.FromEnv)
19+ case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "":
20+ return validateSecretSourceFile(source.FromFile)
21+ default:
22+ return multipleValuesForSecretSource
23+ }
24+}
25+
26+func validateSecretSourceValue(value []byte) string {
27+ dst := make([]byte, len(value))
28+ if _, err := base64.StdEncoding.Decode(dst, value); err != nil {
29+ return fmt.Sprintf("error decoding secret value: %v", err)
30+ }
31+ return ""
32+}
33+
34+func validateSecretSourceEnv(key string) string {
35+ if value := os.Getenv(key); value == "" {
36+ return fmt.Sprintf("error loading secret from environent: no value for for key %q", key)
37+ }
38+ return ""
39+}
40+
41+func validateSecretSourceFile(path string) string {
42+ if _, err := os.Stat(path); err != nil {
43+ return fmt.Sprintf("error loadig secret from file: %v", err)
44+ }
45+ return ""
46+}
pkg/validation/common_test.gonew file+138
@@ -0,0 +1,138 @@
1+package validation
2+
3+import (
4+ "encoding/base64"
5+ "io/ioutil"
6+ "os"
7+
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
9+ . "github.com/onsi/ginkgo"
10+ . "github.com/onsi/ginkgo/extensions/table"
11+ . "github.com/onsi/gomega"
12+)
13+
14+var _ = Describe("Common", func() {
15+ var validSecretSourceValue []byte
16+ const validSecretSourceEnv = "OAUTH2_PROXY_TEST_SECRET_SOURCE_ENV"
17+ var validSecretSourceFile string
18+
19+ BeforeEach(func() {
20+ validSecretSourceValue = []byte(base64.StdEncoding.EncodeToString([]byte("This is a secret source value")))
21+ Expect(os.Setenv(validSecretSourceEnv, "This is a secret source env")).To(Succeed())
22+ tmp, err := ioutil.TempFile("", "oauth2-proxy-secret-source-test")
23+ Expect(err).ToNot(HaveOccurred())
24+ defer tmp.Close()
25+
26+ _, err = tmp.Write([]byte("This is a secret source file"))
27+ Expect(err).ToNot(HaveOccurred())
28+
29+ validSecretSourceFile = tmp.Name()
30+ })
31+
32+ AfterEach(func() {
33+ Expect(os.Unsetenv(validSecretSourceEnv)).To(Succeed())
34+ Expect(os.Remove(validSecretSourceFile)).To(Succeed())
35+ })
36+
37+ type validateSecretSourceTableInput struct {
38+ source func() options.SecretSource
39+ expectedMsg string
40+ }
41+
42+ DescribeTable("validateSecretSource should",
43+ func(in validateSecretSourceTableInput) {
44+ Expect(validateSecretSource(in.source())).To(Equal(in.expectedMsg))
45+ },
46+ Entry("with no entries", validateSecretSourceTableInput{
47+ source: func() options.SecretSource {
48+ return options.SecretSource{}
49+ },
50+ expectedMsg: multipleValuesForSecretSource,
51+ }),
52+ Entry("with a Value and FromEnv", validateSecretSourceTableInput{
53+ source: func() options.SecretSource {
54+ return options.SecretSource{
55+ Value: validSecretSourceValue,
56+ FromEnv: validSecretSourceEnv,
57+ }
58+ },
59+ expectedMsg: multipleValuesForSecretSource,
60+ }),
61+ Entry("with a Value and FromFile", validateSecretSourceTableInput{
62+ source: func() options.SecretSource {
63+ return options.SecretSource{
64+ Value: validSecretSourceValue,
65+ FromFile: validSecretSourceFile,
66+ }
67+ },
68+ expectedMsg: multipleValuesForSecretSource,
69+ }),
70+ Entry("with FromEnv and FromFile", validateSecretSourceTableInput{
71+ source: func() options.SecretSource {
72+ return options.SecretSource{
73+ FromEnv: validSecretSourceEnv,
74+ FromFile: validSecretSourceFile,
75+ }
76+ },
77+ expectedMsg: multipleValuesForSecretSource,
78+ }),
79+ Entry("with a Value, FromEnv and FromFile", validateSecretSourceTableInput{
80+ source: func() options.SecretSource {
81+ return options.SecretSource{
82+ Value: validSecretSourceValue,
83+ FromEnv: validSecretSourceEnv,
84+ FromFile: validSecretSourceFile,
85+ }
86+ },
87+ expectedMsg: multipleValuesForSecretSource,
88+ }),
89+ Entry("with a valid Value", validateSecretSourceTableInput{
90+ source: func() options.SecretSource {
91+ return options.SecretSource{
92+ Value: validSecretSourceValue,
93+ }
94+ },
95+ expectedMsg: "",
96+ }),
97+ Entry("with a valid FromEnv", validateSecretSourceTableInput{
98+ source: func() options.SecretSource {
99+ return options.SecretSource{
100+ FromEnv: validSecretSourceEnv,
101+ }
102+ },
103+ expectedMsg: "",
104+ }),
105+ Entry("with a valid FromFile", validateSecretSourceTableInput{
106+ source: func() options.SecretSource {
107+ return options.SecretSource{
108+ FromFile: validSecretSourceFile,
109+ }
110+ },
111+ expectedMsg: "",
112+ }),
113+ Entry("with an invalid Value", validateSecretSourceTableInput{
114+ source: func() options.SecretSource {
115+ return options.SecretSource{
116+ Value: []byte("Invalid Base64 Value"),
117+ }
118+ },
119+ expectedMsg: "error decoding secret value: illegal base64 data at input byte 7",
120+ }),
121+ Entry("with an invalid FromEnv", validateSecretSourceTableInput{
122+ source: func() options.SecretSource {
123+ return options.SecretSource{
124+ FromEnv: "INVALID_ENV",
125+ }
126+ },
127+ expectedMsg: "error loading secret from environent: no value for for key \"INVALID_ENV\"",
128+ }),
129+ Entry("with an invalid FromFile", validateSecretSourceTableInput{
130+ source: func() options.SecretSource {
131+ return options.SecretSource{
132+ FromFile: "invalidFile",
133+ }
134+ },
135+ expectedMsg: "error loadig secret from file: stat invalidFile: no such file or directory",
136+ }),
137+ )
138+})
pkg/validation/header.gonew file+63
@@ -0,0 +1,63 @@
1+package validation
2+
3+import (
4+ "fmt"
5+
6+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
7+)
8+
9+func validateHeaders(headers []options.Header) []string {
10+ msgs := []string{}
11+ names := make(map[string]struct{})
12+
13+ for _, header := range headers {
14+ msgs = append(msgs, validateHeader(header, names)...)
15+ }
16+ return msgs
17+}
18+
19+func validateHeader(header options.Header, names map[string]struct{}) []string {
20+ msgs := []string{}
21+
22+ if header.Name == "" {
23+ msgs = append(msgs, "header has empty name: names are required for all headers")
24+ }
25+
26+ if _, ok := names[header.Name]; ok {
27+ msgs = append(msgs, fmt.Sprintf("multiple headers found with name %q: header names must be unique", header.Name))
28+ }
29+ names[header.Name] = struct{}{}
30+
31+ for _, value := range header.Values {
32+ msgs = append(msgs,
33+ prefixValues(fmt.Sprintf("invalid header %q: invalid values: ", header.Name),
34+ validateHeaderValue(header.Name, value)...,
35+ )...,
36+ )
37+ }
38+ return msgs
39+}
40+
41+func validateHeaderValue(name string, value options.HeaderValue) []string {
42+ switch {
43+ case value.SecretSource != nil && value.ClaimSource == nil:
44+ return []string{validateSecretSource(*value.SecretSource)}
45+ case value.SecretSource == nil && value.ClaimSource != nil:
46+ return validateHeaderValueClaimSource(*value.ClaimSource)
47+ default:
48+ return []string{"header value has multiple entries: only one entry per value is allowed"}
49+ }
50+}
51+
52+func validateHeaderValueClaimSource(claim options.ClaimSource) []string {
53+ msgs := []string{}
54+
55+ if claim.Claim == "" {
56+ msgs = append(msgs, "claim should not be empty")
57+ }
58+
59+ if claim.BasicAuthPassword != nil {
60+ msgs = append(msgs, prefixValues("invalid basicAuthPassword: ", validateSecretSource(*claim.BasicAuthPassword))...)
61+ }
62+ return msgs
63+}
pkg/validation/header_test.gonew file+164
@@ -0,0 +1,164 @@
1+package validation
2+
3+import (
4+ "encoding/base64"
5+
6+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
7+ . "github.com/onsi/ginkgo"
8+ . "github.com/onsi/ginkgo/extensions/table"
9+ . "github.com/onsi/gomega"
10+)
11+
12+var _ = Describe("Headers", func() {
13+ type validateHeaderTableInput struct {
14+ headers []options.Header
15+ expectedMsgs []string
16+ }
17+
18+ validHeader1 := options.Header{
19+ Name: "X-Email",
20+ Values: []options.HeaderValue{
21+ {
22+ ClaimSource: &options.ClaimSource{
23+ Claim: "email",
24+ },
25+ },
26+ },
27+ }
28+
29+ validHeader2 := options.Header{
30+ Name: "X-Forwarded-Auth",
31+ Values: []options.HeaderValue{
32+ {
33+ SecretSource: &options.SecretSource{
34+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
35+ },
36+ },
37+ },
38+ }
39+
40+ validHeader3 := options.Header{
41+ Name: "Authorization",
42+ Values: []options.HeaderValue{
43+ {
44+ ClaimSource: &options.ClaimSource{
45+ Claim: "email",
46+ BasicAuthPassword: &options.SecretSource{
47+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
48+ },
49+ },
50+ },
51+ },
52+ }
53+
54+ DescribeTable("validateHeaders",
55+ func(in validateHeaderTableInput) {
56+ Expect(validateHeaders(in.headers)).To(ConsistOf(in.expectedMsgs))
57+ },
58+ Entry("with no headers", validateHeaderTableInput{
59+ headers: []options.Header{},
60+ expectedMsgs: []string{},
61+ }),
62+ Entry("with valid headers", validateHeaderTableInput{
63+ headers: []options.Header{
64+ validHeader1,
65+ validHeader2,
66+ validHeader3,
67+ },
68+ expectedMsgs: []string{},
69+ }),
70+ Entry("with multiple headers with the same name", validateHeaderTableInput{
71+ headers: []options.Header{
72+ validHeader1,
73+ validHeader1,
74+ validHeader2,
75+ validHeader2,
76+ },
77+ expectedMsgs: []string{
78+ "multiple headers found with name \"X-Email\": header names must be unique",
79+ "multiple headers found with name \"X-Forwarded-Auth\": header names must be unique",
80+ },
81+ }),
82+ Entry("with an unamed header", validateHeaderTableInput{
83+ headers: []options.Header{
84+ {},
85+ validHeader2,
86+ },
87+ expectedMsgs: []string{
88+ "header has empty name: names are required for all headers",
89+ },
90+ }),
91+ Entry("with a header which has a claim and secret source", validateHeaderTableInput{
92+ headers: []options.Header{
93+ {
94+ Name: "With-Claim-And-Secret",
95+ Values: []options.HeaderValue{
96+ {
97+ ClaimSource: &options.ClaimSource{},
98+ SecretSource: &options.SecretSource{},
99+ },
100+ },
101+ },
102+ validHeader1,
103+ },
104+ expectedMsgs: []string{
105+ "invalid header \"With-Claim-And-Secret\": invalid values: header value has multiple entries: only one entry per value is allowed",
106+ },
107+ }),
108+ Entry("with a header which has a claim without a claim", validateHeaderTableInput{
109+ headers: []options.Header{
110+ {
111+ Name: "Without-Claim",
112+ Values: []options.HeaderValue{
113+ {
114+ ClaimSource: &options.ClaimSource{
115+ Prefix: "prefix",
116+ },
117+ },
118+ },
119+ },
120+ validHeader3,
121+ },
122+ expectedMsgs: []string{
123+ "invalid header \"Without-Claim\": invalid values: claim should not be empty",
124+ },
125+ }),
126+ Entry("with a header with invalid secret source", validateHeaderTableInput{
127+ headers: []options.Header{
128+ {
129+ Name: "With-Invalid-Secret",
130+ Values: []options.HeaderValue{
131+ {
132+ SecretSource: &options.SecretSource{},
133+ },
134+ },
135+ },
136+ validHeader1,
137+ },
138+ expectedMsgs: []string{
139+ "invalid header \"With-Invalid-Secret\": invalid values: multiple values specified for secret source: specify either value, fromEnv of fromFile",
140+ },
141+ }),
142+ Entry("with a header with invalid basicAuthPassword source", validateHeaderTableInput{
143+ headers: []options.Header{
144+ {
145+ Name: "With-Invalid-Basic-Auth",
146+ Values: []options.HeaderValue{
147+ {
148+ ClaimSource: &options.ClaimSource{
149+ Claim: "user",
150+ BasicAuthPassword: &options.SecretSource{
151+ Value: []byte("secret"),
152+ },
153+ },
154+ },
155+ },
156+ },
157+ validHeader1,
158+ },
159+ expectedMsgs: []string{
160+ "invalid header \"With-Invalid-Basic-Auth\": invalid values: invalid basicAuthPassword: error decoding secret value: illegal base64 data at input byte 4",
161+ },
162+ }),
163+ )
164+})
pkg/validation/options.go+2−8
@@ -28,6 +28,8 @@ func Validate(o *options.Options) error {
2828 msgs := validateCookie(o.Cookie)
2929 msgs = append(msgs, validateSessionCookieMinimal(o)...)
3030 msgs = append(msgs, validateRedisSessionStore(o)...)
31+ msgs = append(msgs, prefixValues("injectRequestHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
32+ msgs = append(msgs, prefixValues("injectRespeonseHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
3133
3234 if o.SSLInsecureSkipVerify {
3335 // InsecureSkipVerify is a configurable option we allow
@@ -71,10 +73,6 @@ func Validate(o *options.Options) error {
7173 "\n use email-domain=* to authorize all email addresses")
7274 }
7375
74− if o.SetBasicAuth && o.SetAuthorization {
75− msgs = append(msgs, "mutually exclusive: set-basic-auth and set-authorization-header can not both be true")
76− }
77−
7876 if o.OIDCIssuerURL != "" {
7977
8078 ctx := context.Background()
@@ -159,10 +157,6 @@ func Validate(o *options.Options) error {
159157 }
160158 }
161159
162− if o.PreferEmailToUser && !o.PassBasicAuth && !o.PassUserHeaders {
163− msgs = append(msgs, "PreferEmailToUser should only be used with PassBasicAuth or PassUserHeaders")
164− }
165−
166160 if o.SkipJwtBearerTokens {
167161 // Configure extra issuers
168162 if len(o.ExtraJwtIssuers) > 0 {
pkg/validation/options_test.go−23
@@ -162,29 +162,6 @@ func TestDefaultProviderApiSettings(t *testing.T) {
162162 assert.Equal(t, "profile email", p.Scope)
163163}
164164
165−func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
166− o := testOptions()
167− assert.Equal(t, nil, Validate(o))
168−
169− assert.Equal(t, false, o.PassAccessToken)
170− o.PassAccessToken = true
171− o.Cookie.Secret = "cookie of invalid length-"
172− assert.NotEqual(t, nil, Validate(o))
173−
174− o.PassAccessToken = false
175− o.Cookie.Refresh = time.Duration(24) * time.Hour
176− assert.NotEqual(t, nil, Validate(o))
177−
178− o.Cookie.Secret = "16 bytes AES-128"
179− assert.Equal(t, nil, Validate(o))
180−
181− o.Cookie.Secret = "24 byte secret AES-192--"
182− assert.Equal(t, nil, Validate(o))
183−
184− o.Cookie.Secret = "32 byte secret for AES-256------"
185− assert.Equal(t, nil, Validate(o))
186−}
187−
188165func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
189166 o := testOptions()
190167 assert.Equal(t, nil, Validate(o))
pkg/validation/sessions.go+14−11
@@ -16,18 +16,21 @@ func validateSessionCookieMinimal(o *options.Options) []string {
1616 }
1717
1818 msgs := []string{}
19− if o.PassAuthorization {
20− msgs = append(msgs,
21− "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set")
22− }
23− if o.SetAuthorization {
24− msgs = append(msgs,
25− "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set")
26− }
27− if o.PassAccessToken {
28− msgs = append(msgs,
29− "pass_access_token requires oauth tokens in sessions. session_cookie_minimal cannot be set")
19+ for _, header := range append(o.InjectRequestHeaders, o.InjectResponseHeaders...) {
20+ for _, value := range header.Values {
21+ if value.ClaimSource != nil {
22+ if value.ClaimSource.Claim == "access_token" {
23+ msgs = append(msgs,
24+ fmt.Sprintf("access_token claim for header %q requires oauth tokens in sessions. session_cookie_minimal cannot be set", header.Name))
25+ }
26+ if value.ClaimSource.Claim == "id_token" {
27+ msgs = append(msgs,
28+ fmt.Sprintf("id_token claim for header %q requires oauth tokens in sessions. session_cookie_minimal cannot be set", header.Name))
29+ }
30+ }
31+ }
3032 }
33+
3134 if o.Cookie.Refresh != time.Duration(0) {
3235 msgs = append(msgs,
3336 "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set")
pkg/validation/sessions_test.go+83−18
@@ -13,10 +13,9 @@ import (
1313
1414var _ = Describe("Sessions", func() {
1515 const (
16− passAuthorizationMsg = "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
17− setAuthorizationMsg = "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
18− passAccessTokenMsg = "pass_access_token requires oauth tokens in sessions. session_cookie_minimal cannot be set"
19− cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set"
16+ idTokenConflictMsg = "id_token claim for header \"X-ID-Token\" requires oauth tokens in sessions. session_cookie_minimal cannot be set"
17+ accessTokenConflictMsg = "access_token claim for header \"X-Access-Token\" requires oauth tokens in sessions. session_cookie_minimal cannot be set"
18+ cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set"
2019 )
2120
2221 type cookieMinimalTableInput struct {
@@ -38,14 +37,25 @@ var _ = Describe("Sessions", func() {
3837 },
3938 errStrings: []string{},
4039 }),
41− Entry("No minimal cookie session & passAuthorization", &cookieMinimalTableInput{
40+ Entry("No minimal cookie session & request header has access_token claim", &cookieMinimalTableInput{
4241 opts: &options.Options{
4342 Session: options.SessionOptions{
4443 Cookie: options.CookieStoreOptions{
4544 Minimal: false,
4645 },
4746 },
48− PassAuthorization: true,
47+ InjectRequestHeaders: []options.Header{
48+ {
49+ Name: "X-Access-Token",
50+ Values: []options.HeaderValue{
51+ {
52+ ClaimSource: &options.ClaimSource{
53+ Claim: "access_token",
54+ },
55+ },
56+ },
57+ },
58+ },
4959 },
5060 errStrings: []string{},
5161 }),
@@ -59,38 +69,71 @@ var _ = Describe("Sessions", func() {
5969 },
6070 errStrings: []string{},
6171 }),
62− Entry("PassAuthorization conflict", &cookieMinimalTableInput{
72+ Entry("Request Header id_token conflict", &cookieMinimalTableInput{
6373 opts: &options.Options{
6474 Session: options.SessionOptions{
6575 Cookie: options.CookieStoreOptions{
6676 Minimal: true,
6777 },
6878 },
69− PassAuthorization: true,
79+ InjectRequestHeaders: []options.Header{
80+ {
81+ Name: "X-ID-Token",
82+ Values: []options.HeaderValue{
83+ {
84+ ClaimSource: &options.ClaimSource{
85+ Claim: "id_token",
86+ },
87+ },
88+ },
89+ },
90+ },
7091 },
71− errStrings: []string{passAuthorizationMsg},
92+ errStrings: []string{idTokenConflictMsg},
7293 }),
73− Entry("SetAuthorization conflict", &cookieMinimalTableInput{
94+ Entry("Response Header id_token conflict", &cookieMinimalTableInput{
7495 opts: &options.Options{
7596 Session: options.SessionOptions{
7697 Cookie: options.CookieStoreOptions{
7798 Minimal: true,
7899 },
79100 },
80− SetAuthorization: true,
101+ InjectResponseHeaders: []options.Header{
102+ {
103+ Name: "X-ID-Token",
104+ Values: []options.HeaderValue{
105+ {
106+ ClaimSource: &options.ClaimSource{
107+ Claim: "id_token",
108+ },
109+ },
110+ },
111+ },
112+ },
81113 },
82− errStrings: []string{setAuthorizationMsg},
114+ errStrings: []string{idTokenConflictMsg},
83115 }),
84− Entry("PassAccessToken conflict", &cookieMinimalTableInput{
116+ Entry("Request Header access_token conflict", &cookieMinimalTableInput{
85117 opts: &options.Options{
86118 Session: options.SessionOptions{
87119 Cookie: options.CookieStoreOptions{
88120 Minimal: true,
89121 },
90122 },
91− PassAccessToken: true,
123+ InjectRequestHeaders: []options.Header{
124+ {
125+ Name: "X-Access-Token",
126+ Values: []options.HeaderValue{
127+ {
128+ ClaimSource: &options.ClaimSource{
129+ Claim: "access_token",
130+ },
131+ },
132+ },
133+ },
134+ },
92135 },
93− errStrings: []string{passAccessTokenMsg},
136+ errStrings: []string{accessTokenConflictMsg},
94137 }),
95138 Entry("CookieRefresh conflict", &cookieMinimalTableInput{
96139 opts: &options.Options{
@@ -112,10 +155,32 @@ var _ = Describe("Sessions", func() {
112155 Minimal: true,
113156 },
114157 },
115− PassAuthorization: true,
116− PassAccessToken: true,
158+ InjectResponseHeaders: []options.Header{
159+ {
160+ Name: "X-ID-Token",
161+ Values: []options.HeaderValue{
162+ {
163+ ClaimSource: &options.ClaimSource{
164+ Claim: "id_token",
165+ },
166+ },
167+ },
168+ },
169+ },
170+ InjectRequestHeaders: []options.Header{
171+ {
172+ Name: "X-Access-Token",
173+ Values: []options.HeaderValue{
174+ {
175+ ClaimSource: &options.ClaimSource{
176+ Claim: "access_token",
177+ },
178+ },
179+ },
180+ },
181+ },
117182 },
118− errStrings: []string{passAuthorizationMsg, passAccessTokenMsg},
183+ errStrings: []string{idTokenConflictMsg, accessTokenConflictMsg},
119184 }),
120185 )
121186
pkg/validation/utils.gonew file+11
@@ -0,0 +1,11 @@
1+package validation
2+
3+func prefixValues(prefix string, values ...string) []string {
4+ msgs := []string{}
5+ for _, value := range values {
6+ if value != "" {
7+ msgs = append(msgs, prefix+value)
8+ }
9+ }
10+ return msgs
11+}