git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add possibility to encode the state param as UrlEncodedBase64 (#2312)
bc022fbJan Brezina
CHANGELOG.md+1
@@ -12,6 +12,7 @@
12- [#2269](https://github.com/oauth2-proxy/oauth2-proxy/pull/2269) Added Azure China (and other air gaped cloud) support (@mblaschke)
12- [#2269](https://github.com/oauth2-proxy/oauth2-proxy/pull/2269) Added Azure China (and other air gaped cloud) support (@mblaschke)
13- [#2237](https://github.com/oauth2-proxy/oauth2-proxy/pull/2237) adds an option to append CA certificates (@emsixteeen)
13- [#2237](https://github.com/oauth2-proxy/oauth2-proxy/pull/2237) adds an option to append CA certificates (@emsixteeen)
14- [#2128](https://github.com/oauth2-proxy/oauth2-proxy/pull/2128) Update dependencies (@vllvll)
14- [#2128](https://github.com/oauth2-proxy/oauth2-proxy/pull/2128) Update dependencies (@vllvll)
15+- [#2239](https://github.com/oauth2-proxy/oauth2-proxy/pull/2312) Add possibility to encode the state param as UrlEncodedBase64 (@brezinajn)
15- [#2274](https://github.com/oauth2-proxy/oauth2-proxy/pull/2274) Upgrade golang.org/x/net to v0.17.0 (@pierluigilenoci)
16- [#2274](https://github.com/oauth2-proxy/oauth2-proxy/pull/2274) Upgrade golang.org/x/net to v0.17.0 (@pierluigilenoci)
16- [#2278](https://github.com/oauth2-proxy/oauth2-proxy/pull/2278) Improve the Nginx auth_request example (@akunzai)
17- [#2278](https://github.com/oauth2-proxy/oauth2-proxy/pull/2278) Improve the Nginx auth_request example (@akunzai)
17- [#2282](https://github.com/oauth2-proxy/oauth2-proxy/pull/2282) Fixed checking Google Groups membership using Google Application Credentials (@kvanzuijlen)
18- [#2282](https://github.com/oauth2-proxy/oauth2-proxy/pull/2282) Fixed checking Google Groups membership using Google Application Credentials (@kvanzuijlen)
docs/docs/configuration/overview.md+1
@@ -207,6 +207,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
207| `--version` | n/a | print version string | |
207| `--version` | n/a | print version string | |
208| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` or a `*.` to allow subdomains (e.g. `.example.com`, `*.example.com`) \[[2](#footnote2)\] | |
208| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` or a `*.` to allow subdomains (e.g. `.example.com`, `*.example.com`) \[[2](#footnote2)\] | |
209| `--trusted-ip` | string \| list | list of IPs or CIDR ranges to allow to bypass authentication (may be given multiple times). When combined with `--reverse-proxy` and optionally `--real-client-ip-header` this will evaluate the trust of the IP stored in an HTTP header by a reverse proxy rather than the layer-3/4 remote address. WARNING: trusting IPs has inherent security flaws, especially when obtaining the IP address from an HTTP header (reverse-proxy mode). Use this option only if you understand the risks and how to manage them. | |
209| `--trusted-ip` | string \| list | list of IPs or CIDR ranges to allow to bypass authentication (may be given multiple times). When combined with `--reverse-proxy` and optionally `--real-client-ip-header` this will evaluate the trust of the IP stored in an HTTP header by a reverse proxy rather than the layer-3/4 remote address. WARNING: trusting IPs has inherent security flaws, especially when obtaining the IP address from an HTTP header (reverse-proxy mode). Use this option only if you understand the risks and how to manage them. | |
210+| `--encode-state` | bool | encode the state parameter as UrlEncodedBase64 | false |
210
211
211> ###### 1. Only these providers support `--cookie-refresh`: GitLab, Google and OIDC {#footnote1}
212> ###### 1. Only these providers support `--cookie-refresh`: GitLab, Google and OIDC {#footnote1}
212> ###### 2. When using the `whitelist-domain` option, any domain prefixed with a `.` or a `*.` will allow any subdomain of the specified domain as a valid redirect URL. By default, only empty ports are allowed. This translates to allowing the default port of the URLs protocol (80 for HTTP, 443 for HTTPS, etc.) since browsers omit them. To allow only a specific port, add it to the whitelisted domain: `example.com:8080`. To allow any port, use `*`: `example.com:*`. {#footnote2}
213> ###### 2. When using the `whitelist-domain` option, any domain prefixed with a `.` or a `*.` will allow any subdomain of the specified domain as a valid redirect URL. By default, only empty ports are allowed. This translates to allowing the default port of the URLs protocol (80 for HTTP, 443 for HTTPS, etc.) since browsers omit them. To allow only a specific port, add it to the whitelisted domain: `example.com:8080`. To allow any port, use `*`: `example.com:*`. {#footnote2}
oauthproxy.go+22−8
@@ -3,6 +3,7 @@ package main
3import (
3import (
4 "context"
4 "context"
5 "embed"
5 "embed"
6+ "encoding/base64"
6 "encoding/json"
7 "encoding/json"
7 "errors"
8 "errors"
8 "fmt"
9 "fmt"
@@ -110,6 +111,8 @@ type OAuthProxy struct {
110 serveMux *mux.Router
111 serveMux *mux.Router
111 redirectValidator redirect.Validator
112 redirectValidator redirect.Validator
112 appDirector redirect.AppDirector
113 appDirector redirect.AppDirector
114+
115+ encodeState bool
113}
116}
114
117
115// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
118// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
@@ -239,6 +242,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
239 upstreamProxy: upstreamProxy,
242 upstreamProxy: upstreamProxy,
240 redirectValidator: redirectValidator,
243 redirectValidator: redirectValidator,
241 appDirector: appDirector,
244 appDirector: appDirector,
245+ encodeState: opts.EncodeState,
242 }
246 }
243 p.buildServeMux(opts.ProxyPrefix)
247 p.buildServeMux(opts.ProxyPrefix)
244
248
@@ -796,7 +800,7 @@ func (p *OAuthProxy) doOAuthStart(rw http.ResponseWriter, req *http.Request, ove
796 callbackRedirect := p.getOAuthRedirectURI(req)
800 callbackRedirect := p.getOAuthRedirectURI(req)
797 loginURL := p.provider.GetLoginURL(
801 loginURL := p.provider.GetLoginURL(
798 callbackRedirect,
802 callbackRedirect,
799− encodeState(csrf.HashOAuthState(), appRedirect),
803+ encodeState(csrf.HashOAuthState(), appRedirect, p.encodeState),
800 csrf.HashOIDCNonce(),
804 csrf.HashOIDCNonce(),
801 extraParams,
805 extraParams,
802 )
806 )
@@ -854,7 +858,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
854
858
855 csrf.ClearCookie(rw, req)
859 csrf.ClearCookie(rw, req)
856
860
857− nonce, appRedirect, err := decodeState(req)
861+ nonce, appRedirect, err := decodeState(req.Form.Get("state"), p.encodeState)
858 if err != nil {
862 if err != nil {
859 logger.Errorf("Error while parsing OAuth2 state: %v", err)
863 logger.Errorf("Error while parsing OAuth2 state: %v", err)
860 p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
864 p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
@@ -1194,18 +1198,28 @@ func checkAllowedEmails(req *http.Request, s *sessionsapi.SessionState) bool {
1194
1198
1195// encodedState builds the OAuth state param out of our nonce and
1199// encodedState builds the OAuth state param out of our nonce and
1196// original application redirect
1200// original application redirect
1197−func encodeState(nonce string, redirect string) string {
1201+func encodeState(nonce string, redirect string, encode bool) string {
1198− return fmt.Sprintf("%v:%v", nonce, redirect)
1202+ rawString := fmt.Sprintf("%v:%v", nonce, redirect)
1203+ if encode {
1204+ return base64.RawURLEncoding.EncodeToString([]byte(rawString))
1205+ }
1206+ return rawString
1199}
1207}
1200
1208
1201// decodeState splits the reflected OAuth state response back into
1209// decodeState splits the reflected OAuth state response back into
1202// the nonce and original application redirect
1210// the nonce and original application redirect
1203−func decodeState(req *http.Request) (string, string, error) {
1211+func decodeState(state string, encode bool) (string, string, error) {
1212+ toParse := state
1213+ if encode {
1214+ decoded, _ := base64.RawURLEncoding.DecodeString(state)
1215+ toParse = string(decoded)
1216+ }
1217+
1204− state := strings.SplitN(req.Form.Get("state"), ":", 2)
1218+ parsedState := strings.SplitN(toParse, ":", 2)
1205− if len(state) != 2 {
1219+ if len(parsedState) != 2 {
1206 return "", "", errors.New("invalid length")
1220 return "", "", errors.New("invalid length")
1207 }
1221 }
1208− return state[0], state[1], nil
1222+ return parsedState[0], parsedState[1], nil
1209}
1223}
1210
1224
1211// addHeadersForProxying adds the appropriate headers the request / response for proxying
1225// addHeadersForProxying adds the appropriate headers the request / response for proxying
oauthproxy_test.go+24−1
@@ -413,7 +413,7 @@ func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int, cookie
413 http.MethodGet,
413 http.MethodGet,
414 fmt.Sprintf(
414 fmt.Sprintf(
415 "/oauth2/callback?code=callback_code&state=%s",
415 "/oauth2/callback?code=callback_code&state=%s",
416− encodeState(csrf.HashOAuthState(), "%2F"),
416+ encodeState(csrf.HashOAuthState(), "%2F", false),
417 ),
417 ),
418 strings.NewReader(""),
418 strings.NewReader(""),
419 )
419 )
@@ -3288,6 +3288,29 @@ func TestAuthOnlyAllowedEmailDomains(t *testing.T) {
3288 }
3288 }
3289}
3289}
3290
3290
3291+func TestStateEncodesCorrectly(t *testing.T) {
3292+ state := "some_state_to_test"
3293+ nonce := "some_nonce_to_test"
3294+
3295+ encodedResult := encodeState(nonce, state, true)
3296+ assert.Equal(t, "c29tZV9ub25jZV90b190ZXN0OnNvbWVfc3RhdGVfdG9fdGVzdA", encodedResult)
3297+
3298+ notEncodedResult := encodeState(nonce, state, false)
3299+ assert.Equal(t, "some_nonce_to_test:some_state_to_test", notEncodedResult)
3300+}
3301+
3302+func TestStateDecodesCorrectly(t *testing.T) {
3303+ nonce, redirect, _ := decodeState("c29tZV9ub25jZV90b190ZXN0OnNvbWVfc3RhdGVfdG9fdGVzdA", true)
3304+
3305+ assert.Equal(t, "some_nonce_to_test", nonce)
3306+ assert.Equal(t, "some_state_to_test", redirect)
3307+
3308+ nonce2, redirect2, _ := decodeState("some_nonce_to_test:some_state_to_test", false)
3309+
3310+ assert.Equal(t, "some_nonce_to_test", nonce2)
3311+ assert.Equal(t, "some_state_to_test", redirect2)
3312+}
3313+
3291func TestAuthOnlyAllowedEmails(t *testing.T) {
3314func TestAuthOnlyAllowedEmails(t *testing.T) {
3292 testCases := []struct {
3315 testCases := []struct {
3293 name string
3316 name string
pkg/apis/options/options.go+2
@@ -61,6 +61,7 @@ type Options struct {
61 SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
61 SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
62 SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
62 SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
63 ForceJSONErrors bool `flag:"force-json-errors" cfg:"force_json_errors"`
63 ForceJSONErrors bool `flag:"force-json-errors" cfg:"force_json_errors"`
64+ EncodeState bool `flag:"encode-state" cfg:"encode_state"`
64 AllowQuerySemicolons bool `flag:"allow-query-semicolons" cfg:"allow_query_semicolons"`
65 AllowQuerySemicolons bool `flag:"allow-query-semicolons" cfg:"allow_query_semicolons"`
65
66
66 SignatureKey string `flag:"signature-key" cfg:"signature_key"`
67 SignatureKey string `flag:"signature-key" cfg:"signature_key"`
@@ -128,6 +129,7 @@ func NewFlagSet() *pflag.FlagSet {
128 flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
129 flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
129 flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)")
130 flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)")
130 flagSet.Bool("force-json-errors", false, "will force JSON errors instead of HTTP error pages or redirects")
131 flagSet.Bool("force-json-errors", false, "will force JSON errors instead of HTTP error pages or redirects")
132+ flagSet.Bool("encode-state", false, "will encode oauth state with base64")
131 flagSet.Bool("allow-query-semicolons", false, "allow the use of semicolons in query args")
133 flagSet.Bool("allow-query-semicolons", false, "allow the use of semicolons in query args")
132 flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)")
134 flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)")
133
135