git.iko.soy/typeable/oauth2-proxy
on master help sign in
Strip X-Forwarded auth headers from whitelisted paths (#624)
abeb023Nick Meves
CHANGELOG.md+1
@@ -11,6 +11,7 @@
11
11
12## Changes since v6.0.0
12## Changes since v6.0.0
13
13
14+- [#624](https://github.com/oauth2-proxy/oauth2-proxy/pull/624) Allow stripping authentication headers from whitelisted requests with `--skip-auth-strip-headers` (@NickMeves)
14- [#673](https://github.com/oauth2-proxy/oauth2-proxy/pull/673) Add --session-cookie-minimal option to create session cookies with no tokens (@NickMeves)
15- [#673](https://github.com/oauth2-proxy/oauth2-proxy/pull/673) Add --session-cookie-minimal option to create session cookies with no tokens (@NickMeves)
15- [#632](https://github.com/oauth2-proxy/oauth2-proxy/pull/632) Reduce session size by encoding with MessagePack and using LZ4 compression (@NickMeves)
16- [#632](https://github.com/oauth2-proxy/oauth2-proxy/pull/632) Reduce session size by encoding with MessagePack and using LZ4 compression (@NickMeves)
16- [#675](https://github.com/oauth2-proxy/oauth2-proxy/pull/675) Fix required ruby version and deprecated option for building docs (@mkontani)
17- [#675](https://github.com/oauth2-proxy/oauth2-proxy/pull/675) Fix required ruby version and deprecated option for building docs (@mkontani)
docs/configuration/configuration.md+1
@@ -116,6 +116,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
116| `--silence-ping-logging` | bool | disable logging of requests to ping endpoint | false |
116| `--silence-ping-logging` | bool | disable logging of requests to ping endpoint | false |
117| `--skip-auth-preflight` | bool | will skip authentication for OPTIONS requests | false |
117| `--skip-auth-preflight` | bool | will skip authentication for OPTIONS requests | false |
118| `--skip-auth-regex` | string | bypass authentication for requests paths that match (may be given multiple times) | |
118| `--skip-auth-regex` | string | bypass authentication for requests paths that match (may be given multiple times) | |
119+| `--skip-auth-strip-headers` | bool | strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy for request paths in `--skip-auth-regex` | false |
119| `--skip-jwt-bearer-tokens` | bool | will skip requests that have verified JWT bearer tokens | false |
120| `--skip-jwt-bearer-tokens` | bool | will skip requests that have verified JWT bearer tokens | false |
120| `--skip-oidc-discovery` | bool | bypass OIDC endpoint discovery. `--login-url`, `--redeem-url` and `--oidc-jwks-url` must be configured in this case | false |
121| `--skip-oidc-discovery` | bool | bypass OIDC endpoint discovery. `--login-url`, `--redeem-url` and `--oidc-jwks-url` must be configured in this case | false |
121| `--skip-provider-button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false |
122| `--skip-provider-button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false |
oauthproxy.go+35−1
@@ -111,6 +111,7 @@ type OAuthProxy struct {
111 PreferEmailToUser bool
111 PreferEmailToUser bool
112 skipAuthRegex []string
112 skipAuthRegex []string
113 skipAuthPreflight bool
113 skipAuthPreflight bool
114+ skipAuthStripHeaders bool
114 skipJwtBearerTokens bool
115 skipJwtBearerTokens bool
115 mainJwtBearerVerifier *oidc.IDTokenVerifier
116 mainJwtBearerVerifier *oidc.IDTokenVerifier
116 extraJwtBearerVerifiers []*oidc.IDTokenVerifier
117 extraJwtBearerVerifiers []*oidc.IDTokenVerifier
@@ -343,6 +344,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
343 whitelistDomains: opts.WhitelistDomains,
344 whitelistDomains: opts.WhitelistDomains,
344 skipAuthRegex: opts.SkipAuthRegex,
345 skipAuthRegex: opts.SkipAuthRegex,
345 skipAuthPreflight: opts.SkipAuthPreflight,
346 skipAuthPreflight: opts.SkipAuthPreflight,
347+ skipAuthStripHeaders: opts.SkipAuthStripHeaders,
346 skipJwtBearerTokens: opts.SkipJwtBearerTokens,
348 skipJwtBearerTokens: opts.SkipJwtBearerTokens,
347 mainJwtBearerVerifier: opts.GetOIDCVerifier(),
349 mainJwtBearerVerifier: opts.GetOIDCVerifier(),
348 extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
350 extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
@@ -718,7 +720,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
718 case path == p.RobotsPath:
720 case path == p.RobotsPath:
719 p.RobotsTxt(rw)
721 p.RobotsTxt(rw)
720 case p.IsWhitelistedRequest(req):
722 case p.IsWhitelistedRequest(req):
721− p.serveMux.ServeHTTP(rw, req)
723+ p.SkipAuthProxy(rw, req)
722 case path == p.SignInPath:
724 case path == p.SignInPath:
723 p.SignIn(rw, req)
725 p.SignIn(rw, req)
724 case path == p.SignOutPath:
726 case path == p.SignOutPath:
@@ -891,6 +893,14 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
891 rw.WriteHeader(http.StatusAccepted)
893 rw.WriteHeader(http.StatusAccepted)
892}
894}
893
895
896+// SkipAuthProxy proxies whitelisted requests and skips authentication
897+func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) {
898+ if p.skipAuthStripHeaders {
899+ p.stripAuthHeaders(req)
900+ }
901+ p.serveMux.ServeHTTP(rw, req)
902+}
903+
894// Proxy proxies the user request if the user is authenticated else it prompts
904// Proxy proxies the user request if the user is authenticated else it prompts
895// them to authenticate
905// them to authenticate
896func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
906func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
@@ -1122,6 +1132,30 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
1122 }
1132 }
1123}
1133}
1124
1134
1135+// stripAuthHeaders removes Auth headers for whitelisted routes from skipAuthRegex
1136+func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
1137+ if p.PassBasicAuth {
1138+ req.Header.Del("X-Forwarded-User")
1139+ req.Header.Del("X-Forwarded-Email")
1140+ req.Header.Del("X-Forwarded-Preferred-Username")
1141+ req.Header.Del("Authorization")
1142+ }
1143+
1144+ if p.PassUserHeaders {
1145+ req.Header.Del("X-Forwarded-User")
1146+ req.Header.Del("X-Forwarded-Email")
1147+ req.Header.Del("X-Forwarded-Preferred-Username")
1148+ }
1149+
1150+ if p.PassAccessToken {
1151+ req.Header.Del("X-Forwarded-Access-Token")
1152+ }
1153+
1154+ if p.PassAuthorization {
1155+ req.Header.Del("Authorization")
1156+ }
1157+}
1158+
1125// CheckBasicAuth checks the requests Authorization header for basic auth
1159// CheckBasicAuth checks the requests Authorization header for basic auth
1126// credentials and authenticates these against the proxies HtpasswdFile
1160// credentials and authenticates these against the proxies HtpasswdFile
1127func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionState, error) {
1161func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionState, error) {
oauthproxy_test.go+136
@@ -723,6 +723,142 @@ func TestPassUserHeadersWithEmail(t *testing.T) {
723 }
723 }
724}
724}
725
725
726+func TestStripAuthHeaders(t *testing.T) {
727+ testCases := map[string]struct {
728+ SkipAuthStripHeaders bool
729+ PassBasicAuth bool
730+ PassUserHeaders bool
731+ PassAccessToken bool
732+ PassAuthorization bool
733+ StrippedHeaders map[string]bool
734+ }{
735+ "Default options": {
736+ SkipAuthStripHeaders: true,
737+ PassBasicAuth: true,
738+ PassUserHeaders: true,
739+ PassAccessToken: false,
740+ PassAuthorization: false,
741+ StrippedHeaders: map[string]bool{
742+ "X-Forwarded-User": true,
743+ "X-Forwarded-Email": true,
744+ "X-Forwarded-Preferred-Username": true,
745+ "X-Forwarded-Access-Token": false,
746+ "Authorization": true,
747+ },
748+ },
749+ "Pass access token": {
750+ SkipAuthStripHeaders: true,
751+ PassBasicAuth: true,
752+ PassUserHeaders: true,
753+ PassAccessToken: true,
754+ PassAuthorization: false,
755+ StrippedHeaders: map[string]bool{
756+ "X-Forwarded-User": true,
757+ "X-Forwarded-Email": true,
758+ "X-Forwarded-Preferred-Username": true,
759+ "X-Forwarded-Access-Token": true,
760+ "Authorization": true,
761+ },
762+ },
763+ "Nothing setting Authorization": {
764+ SkipAuthStripHeaders: true,
765+ PassBasicAuth: false,
766+ PassUserHeaders: true,
767+ PassAccessToken: true,
768+ PassAuthorization: false,
769+ StrippedHeaders: map[string]bool{
770+ "X-Forwarded-User": true,
771+ "X-Forwarded-Email": true,
772+ "X-Forwarded-Preferred-Username": true,
773+ "X-Forwarded-Access-Token": true,
774+ "Authorization": false,
775+ },
776+ },
777+ "Only Authorization header modified": {
778+ SkipAuthStripHeaders: true,
779+ PassBasicAuth: false,
780+ PassUserHeaders: false,
781+ PassAccessToken: false,
782+ PassAuthorization: true,
783+ StrippedHeaders: map[string]bool{
784+ "X-Forwarded-User": false,
785+ "X-Forwarded-Email": false,
786+ "X-Forwarded-Preferred-Username": false,
787+ "X-Forwarded-Access-Token": false,
788+ "Authorization": true,
789+ },
790+ },
791+ "Don't strip any headers (default options)": {
792+ SkipAuthStripHeaders: false,
793+ PassBasicAuth: true,
794+ PassUserHeaders: true,
795+ PassAccessToken: false,
796+ PassAuthorization: false,
797+ StrippedHeaders: map[string]bool{
798+ "X-Forwarded-User": false,
799+ "X-Forwarded-Email": false,
800+ "X-Forwarded-Preferred-Username": false,
801+ "X-Forwarded-Access-Token": false,
802+ "Authorization": false,
803+ },
804+ },
805+ "Don't strip any headers (custom options)": {
806+ SkipAuthStripHeaders: false,
807+ PassBasicAuth: true,
808+ PassUserHeaders: true,
809+ PassAccessToken: true,
810+ PassAuthorization: false,
811+ StrippedHeaders: map[string]bool{
812+ "X-Forwarded-User": false,
813+ "X-Forwarded-Email": false,
814+ "X-Forwarded-Preferred-Username": false,
815+ "X-Forwarded-Access-Token": false,
816+ "Authorization": false,
817+ },
818+ },
819+ }
820+
821+ initialHeaders := map[string]string{
822+ "X-Forwarded-User": "9fcab5c9b889a557",
823+ "X-Forwarded-Email": "john.doe@example.com",
824+ "X-Forwarded-Preferred-Username": "john.doe",
825+ "X-Forwarded-Access-Token": "AccessToken",
826+ "Authorization": "bearer IDToken",
827+ }
828+
829+ for name, tc := range testCases {
830+ t.Run(name, func(t *testing.T) {
831+ opts := baseTestOptions()
832+ opts.SkipAuthStripHeaders = tc.SkipAuthStripHeaders
833+ opts.PassBasicAuth = tc.PassBasicAuth
834+ opts.PassUserHeaders = tc.PassUserHeaders
835+ opts.PassAccessToken = tc.PassAccessToken
836+ opts.PassAuthorization = tc.PassAuthorization
837+ err := validation.Validate(opts)
838+ assert.NoError(t, err)
839+
840+ req, _ := http.NewRequest("GET", fmt.Sprintf("%s/testCase", opts.ProxyPrefix), nil)
841+ for header, val := range initialHeaders {
842+ req.Header.Set(header, val)
843+ }
844+
845+ proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
846+ assert.NoError(t, err)
847+ if proxy.skipAuthStripHeaders {
848+ proxy.stripAuthHeaders(req)
849+ }
850+
851+ for header, stripped := range tc.StrippedHeaders {
852+ if stripped {
853+ assert.Equal(t, req.Header.Get(header), "")
854+ } else {
855+ assert.Equal(t, req.Header.Get(header), initialHeaders[header])
856+ }
857+ }
858+ })
859+ }
860+}
861+
726type PassAccessTokenTest struct {
862type PassAccessTokenTest struct {
727 providerServer *httptest.Server
863 providerServer *httptest.Server
728 proxy *OAuthProxy
864 proxy *OAuthProxy
pkg/apis/options/options.go+3
@@ -66,6 +66,7 @@ type Options struct {
66
66
67 Upstreams []string `flag:"upstream" cfg:"upstreams"`
67 Upstreams []string `flag:"upstream" cfg:"upstreams"`
68 SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
68 SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
69+ SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
69 SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
70 SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
70 ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
71 ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
71 PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
72 PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
@@ -159,6 +160,7 @@ func NewOptions() *Options {
159 AzureTenant: "common",
160 AzureTenant: "common",
160 SetXAuthRequest: false,
161 SetXAuthRequest: false,
161 SkipAuthPreflight: false,
162 SkipAuthPreflight: false,
163+ SkipAuthStripHeaders: false,
162 FlushInterval: time.Duration(1) * time.Second,
164 FlushInterval: time.Duration(1) * time.Second,
163 PassBasicAuth: true,
165 PassBasicAuth: true,
164 SetBasicAuth: false,
166 SetBasicAuth: false,
@@ -202,6 +204,7 @@ func NewFlagSet() *pflag.FlagSet {
202 flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
204 flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
203 flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)")
205 flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)")
204 flagSet.StringSlice("skip-auth-regex", []string{}, "bypass authentication for requests path's that match (may be given multiple times)")
206 flagSet.StringSlice("skip-auth-regex", []string{}, "bypass authentication for requests path's that match (may be given multiple times)")
207+ 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")
205 flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
208 flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
206 flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
209 flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
207 flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
210 flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")