git.iko.soy/typeable/oauth2-proxy
on master help sign in
feat: add allowed_* constraint option to proxy endpoint query string (#2841)

* Add check for constraints to the proxy endpoint * Add tests for allowed_groups query string * Add this feature to the changelog * Apply suggestions from code review Co-authored-by: Jan Larwig <jan@larwig.com> * Use explicit key names in TestProxyAllowedGroups * Document the query parameters on proxy endpoint * Comment was copied from the AuthOnly handler but on closer inspection is not relevant here replacing comment with one more relevant --------- Signed-off-by: Jan Larwig <jan@larwig.com> Co-authored-by: Jan Larwig <jan@larwig.com>

commitfcc2db040e2da5781919f21577f721911a384419 authorJacob Alberty <jacob.alberty@gmail.com> dateSat 8 Nov 2025, 06:58 (1 year ago) parent22053dc
4 files changed+88−5
CHANGELOG.md+1 docs/docs/features/endpoints.md+12 oauthproxy.go+7 oauthproxy_test.go+68−5
CHANGELOG.md+1
@@ -14,6 +14,7 @@
1414- [#3237](https://github.com/oauth2-proxy/oauth2-proxy/pull/3237) - feat: add option to use organization id for preferred username in Google Provider (@pixeldrew)
1515- [GHSA-vjrc-mh2v-45x6](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-vjrc-mh2v-45x6) fix: request header smuggling by stripping all normalized header variants (@tuunit)
1616- [#1933](https://github.com/oauth2-proxy/oauth2-proxy/pull/1933) fix: validation of refreshed sessions using the access_token in the OIDC provider (@gysel / @tuunit)
17+- [#2841](https://github.com/oauth2-proxy/oauth2-proxy/pull/2841) feat: add allowed_* constraint option to proxy endpoint query string (@jacobalberty)
1718
1819# V7.12.0
1920
docs/docs/features/endpoints.md+12
@@ -5,6 +5,7 @@ title: Endpoints
55
66OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated. The `/oauth2` prefix can be changed with the `--proxy-prefix` config variable.
77
8+- / - the proxy endpoint provides authentication and returns the appropriate 40x error if not authenticated or authorized then passes the request upstream.
89- /robots.txt - returns a 200 OK response that disallows all User-agents from all paths; see [robotstxt.org](http://www.robotstxt.org/) for more info
910- /ping - returns a 200 OK response, which is intended for use with health checks
1011- /ready - returns a 200 OK response if all the underlying connections (e.g., Redis store) are connected
@@ -45,3 +46,14 @@ It can be configured using the following query parameters:
4546- `allowed_groups`: comma separated list of allowed groups
4647- `allowed_email_domains`: comma separated list of allowed email domains
4748- `allowed_emails`: comma separated list of allowed emails
49+
50+### Proxy (/)
51+
52+This endpoint returns the upstream response if authenticated.
53+If unauthenticated it returns a 401 Unauthorized. If the authenticatd user
54+is not in one of the allowed groups, or emails then it returns a 403 forbidden
55+
56+It can be configured using the following query parameters:
57+- `allowed_groups`: comma separated list of allowed groups
58+- `allowed_email_domains`: comma separated list of allowed email domains
59+- `allowed_emails`: comma separated list of allowed emails
oauthproxy.go+7
@@ -1012,6 +1012,13 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
10121012 session, err := p.getAuthenticatedSession(rw, req)
10131013 switch err {
10141014 case nil:
1015+ // Check against our authorization constraints and return forbidden
1016+ // if this request fails to satisfy them.
1017+ if !authOnlyAuthorize(req, session) {
1018+ http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)
1019+ return
1020+ }
1021+
10151022 // we are authenticated
10161023 p.addHeadersForProxying(rw, session)
10171024 p.headersChain.Then(p.upstreamProxy).ServeHTTP(rw, req)
oauthproxy_test.go+68−5
@@ -2938,12 +2938,75 @@ func TestProxyAllowedGroups(t *testing.T) {
29382938 name string
29392939 allowedGroups []string
29402940 groups []string
2941+ querystring string
29412942 expectUnauthorized bool
29422943 }{
2943− {"NoAllowedGroups", []string{}, []string{}, false},
2944− {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2945− {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2946− {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2944+ {
2945+ name: "NoAllowedGroups",
2946+ allowedGroups: []string{},
2947+ groups: []string{},
2948+ querystring: "",
2949+ expectUnauthorized: false},
2950+ {
2951+ name: "NoAllowedGroupsUserHasGroups",
2952+ allowedGroups: []string{},
2953+ groups: []string{"a", "b"},
2954+ querystring: "",
2955+ expectUnauthorized: false},
2956+ {
2957+ name: "UserInAllowedGroup",
2958+ allowedGroups: []string{"a"},
2959+ groups: []string{"a", "b"},
2960+ querystring: "",
2961+ expectUnauthorized: false},
2962+ {
2963+ name: "UserNotInAllowedGroup",
2964+ allowedGroups: []string{"a"},
2965+ groups: []string{"c"},
2966+ querystring: "",
2967+ expectUnauthorized: true},
2968+ {
2969+ name: "UserInQuerystringGroup",
2970+ allowedGroups: []string{"a", "b"},
2971+ groups: []string{"a", "c"},
2972+ querystring: "?allowed_groups=a",
2973+ expectUnauthorized: false},
2974+ {
2975+ name: "UserInMultiParamQuerystringGroup",
2976+ allowedGroups: []string{"a", "b"},
2977+ groups: []string{"b"},
2978+ querystring: "?allowed_groups=a&allowed_groups=b,d",
2979+ expectUnauthorized: false},
2980+ {
2981+ name: "UserInOnlyQuerystringGroup",
2982+ allowedGroups: []string{},
2983+ groups: []string{"a", "c"},
2984+ querystring: "?allowed_groups=a,b",
2985+ expectUnauthorized: false},
2986+ {
2987+ name: "UserInDelimitedQuerystringGroup",
2988+ allowedGroups: []string{"a", "b", "c"},
2989+ groups: []string{"c"},
2990+ querystring: "?allowed_groups=a,c",
2991+ expectUnauthorized: false},
2992+ {
2993+ name: "UserNotInQuerystringGroup",
2994+ allowedGroups: []string{},
2995+ groups: []string{"c"},
2996+ querystring: "?allowed_groups=a,b",
2997+ expectUnauthorized: true},
2998+ {
2999+ name: "UserInConfigGroupNotInQuerystringGroup",
3000+ allowedGroups: []string{"a", "b", "c"},
3001+ groups: []string{"c"},
3002+ querystring: "?allowed_groups=a,b",
3003+ expectUnauthorized: true},
3004+ {
3005+ name: "UserInQuerystringGroupNotInConfigGroup",
3006+ allowedGroups: []string{"a", "b"},
3007+ groups: []string{"c"},
3008+ querystring: "?allowed_groups=b,c",
3009+ expectUnauthorized: true},
29473010 }
29483011
29493012 for _, tt := range tests {
@@ -2979,7 +3042,7 @@ func TestProxyAllowedGroups(t *testing.T) {
29793042 t.Fatal(err)
29803043 }
29813044
2982− test.req, _ = http.NewRequest("GET", "/", nil)
3045+ test.req, _ = http.NewRequest("GET", fmt.Sprintf("/%s", tt.querystring), nil)
29833046
29843047 test.req.Header.Add("accept", applicationJSON)
29853048 err = test.SaveSession(session)