git.iko.soy/typeable/oauth2-proxy
on master help sign in
improved audience handling to support client credentials access tokens without aud claims (#1204)
25371eaKevin Schu
CHANGELOG.md+1
@@ -10,6 +10,7 @@
10
10
11- [#1489](https://github.com/oauth2-proxy/oauth2-proxy/pull/1489) Fix Docker Buildx push to include build version (@JoelSpeed)
11- [#1489](https://github.com/oauth2-proxy/oauth2-proxy/pull/1489) Fix Docker Buildx push to include build version (@JoelSpeed)
12- [#1477](https://github.com/oauth2-proxy/oauth2-proxy/pull/1477) Remove provider documentation for `Microsoft Azure AD` (@omBratteng)
12- [#1477](https://github.com/oauth2-proxy/oauth2-proxy/pull/1477) Remove provider documentation for `Microsoft Azure AD` (@omBratteng)
13+- [#1204](https://github.com/oauth2-proxy/oauth2-proxy/pull/1204) Added configuration for audience claim (`--oidc-extra-audience`) and ability to specify extra audiences (`--oidc-extra-audience`) allowed passing audience verification. This enables support for AWS Cognito and other issuers that have custom audience claims. Also, this adds the ability to allow multiple audiences. (@kschu91)
13- [#1509](https://github.com/oauth2-proxy/oauth2-proxy/pull/1509) Update LoginGovProvider ValidateSession to pass access_token in Header (@pksheldon4)
14- [#1509](https://github.com/oauth2-proxy/oauth2-proxy/pull/1509) Update LoginGovProvider ValidateSession to pass access_token in Header (@pksheldon4)
14- [#1474](https://github.com/oauth2-proxy/oauth2-proxy/pull/1474) Support configuration of minimal acceptable TLS version (@polarctos)
15- [#1474](https://github.com/oauth2-proxy/oauth2-proxy/pull/1474) Support configuration of minimal acceptable TLS version (@polarctos)
15- [#1545](https://github.com/oauth2-proxy/oauth2-proxy/pull/1545) Fix issue with query string allowed group panic on skip methods (@andytson)
16- [#1545](https://github.com/oauth2-proxy/oauth2-proxy/pull/1545) Fix issue with query string allowed group panic on skip methods (@andytson)
docs/docs/configuration/alpha_config.md+2
@@ -281,6 +281,8 @@ make up the header value
281| `emailClaim` | _string_ | EmailClaim indicates which claim contains the user email,<br/>default set to 'email' |
281| `emailClaim` | _string_ | EmailClaim indicates which claim contains the user email,<br/>default set to 'email' |
282| `groupsClaim` | _string_ | GroupsClaim indicates which claim contains the user groups<br/>default set to 'groups' |
282| `groupsClaim` | _string_ | GroupsClaim indicates which claim contains the user groups<br/>default set to 'groups' |
283| `userIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID<br/>default set to 'email' |
283| `userIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID<br/>default set to 'email' |
284+| `audienceClaims` | _[]string_ | AudienceClaim allows to define any claim that is verified against the client id<br/>By default `aud` claim is used for verification. |
285+| `extraAudiences` | _[]string_ | ExtraAudiences is a list of additional audiences that are allowed<br/>to pass verification in addition to the client id. |
284
286
285### Provider
287### Provider
286
288
docs/docs/configuration/overview.md+2
@@ -136,6 +136,8 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
136| `--oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | |
136| `--oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | |
137| `--oidc-email-claim` | string | which OIDC claim contains the user's email | `"email"` |
137| `--oidc-email-claim` | string | which OIDC claim contains the user's email | `"email"` |
138| `--oidc-groups-claim` | string | which OIDC claim contains the user groups | `"groups"` |
138| `--oidc-groups-claim` | string | which OIDC claim contains the user groups | `"groups"` |
139+| `--oidc-audience-claim` | string | which OIDC claim contains the audience | `"aud"` |
140+| `--oidc-extra-audience` | string \| list | additional audiences which are allowed to pass verification | `"[]"` |
139| `--pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header. When used with `--set-xauthrequest` this adds the X-Auth-Request-Access-Token header to the response | false |
141| `--pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header. When used with `--set-xauthrequest` this adds the X-Auth-Request-Access-Token header to the response | false |
140| `--pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false |
142| `--pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false |
141| `--pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true |
143| `--pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true |
main_test.go+4
@@ -76,6 +76,8 @@ providers:
76 emailClaim: email
76 emailClaim: email
77 userIDClaim: email
77 userIDClaim: email
78 insecureSkipNonce: true
78 insecureSkipNonce: true
79+ audienceClaims: [aud]
80+ extraAudiences: []
79`
81`
80
82
81 const testCoreConfig = `
83 const testCoreConfig = `
@@ -148,6 +150,8 @@ redirect_url="http://localhost:4180/oauth2/callback"
148 GroupsClaim: "groups",
150 GroupsClaim: "groups",
149 EmailClaim: "email",
151 EmailClaim: "email",
150 UserIDClaim: "email",
152 UserIDClaim: "email",
153+ AudienceClaims: []string{"aud"},
154+ ExtraAudiences: []string{},
151 InsecureSkipNonce: true,
155 InsecureSkipNonce: true,
152 },
156 },
153 ApprovalPrompt: "force",
157 ApprovalPrompt: "force",
oauthproxy_test.go+10−2
@@ -21,6 +21,7 @@ import (
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
23 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
23 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
24+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
24 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
25 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
25 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
26 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
26 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
27 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
@@ -1737,7 +1738,14 @@ func TestGetJwtSession(t *testing.T) {
1737
1738
1738 keyset := NoOpKeySet{}
1739 keyset := NoOpKeySet{}
1739 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1740 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1740− &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1741+ &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true,
1742+ SkipClientIDCheck: true})
1743+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
1744+ AudienceClaims: []string{"aud"},
1745+ ClientID: "https://test.myapp.com",
1746+ ExtraAudiences: []string{},
1747+ }
1748+ internalVerifier := internaloidc.NewVerifier(verifier, verificationOptions)
1741
1749
1742 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1750 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1743 opts.InjectRequestHeaders = []options.Header{
1751 opts.InjectRequestHeaders = []options.Header{
@@ -1808,7 +1816,7 @@ func TestGetJwtSession(t *testing.T) {
1808 },
1816 },
1809 }
1817 }
1810 opts.SkipJwtBearerTokens = true
1818 opts.SkipJwtBearerTokens = true
1811− opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1819+ opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), internalVerifier))
1812 })
1820 })
1813 if err != nil {
1821 if err != nil {
1814 t.Fatal(err)
1822 t.Fatal(err)
pkg/apis/options/legacy_options.go+8
@@ -54,6 +54,8 @@ func NewLegacyOptions() *LegacyOptions {
54 UserIDClaim: "email",
54 UserIDClaim: "email",
55 OIDCEmailClaim: "email",
55 OIDCEmailClaim: "email",
56 OIDCGroupsClaim: "groups",
56 OIDCGroupsClaim: "groups",
57+ OIDCAudienceClaims: []string{"aud"},
58+ OIDCExtraAudiences: []string{},
57 InsecureOIDCSkipNonce: true,
59 InsecureOIDCSkipNonce: true,
58 },
60 },
59
61
@@ -500,6 +502,8 @@ type LegacyProvider struct {
500 OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"`
502 OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"`
501 OIDCEmailClaim string `flag:"oidc-email-claim" cfg:"oidc_email_claim"`
503 OIDCEmailClaim string `flag:"oidc-email-claim" cfg:"oidc_email_claim"`
502 OIDCGroupsClaim string `flag:"oidc-groups-claim" cfg:"oidc_groups_claim"`
504 OIDCGroupsClaim string `flag:"oidc-groups-claim" cfg:"oidc_groups_claim"`
505+ OIDCAudienceClaims []string `flag:"oidc-audience-claim" cfg:"oidc_audience_claims"`
506+ OIDCExtraAudiences []string `flag:"oidc-extra-audience" cfg:"oidc_extra_audiences"`
503 LoginURL string `flag:"login-url" cfg:"login_url"`
507 LoginURL string `flag:"login-url" cfg:"login_url"`
504 RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
508 RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
505 ProfileURL string `flag:"profile-url" cfg:"profile_url"`
509 ProfileURL string `flag:"profile-url" cfg:"profile_url"`
@@ -550,6 +554,8 @@ func legacyProviderFlagSet() *pflag.FlagSet {
550 flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
554 flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
551 flagSet.String("oidc-groups-claim", providers.OIDCGroupsClaim, "which OIDC claim contains the user groups")
555 flagSet.String("oidc-groups-claim", providers.OIDCGroupsClaim, "which OIDC claim contains the user groups")
552 flagSet.String("oidc-email-claim", providers.OIDCEmailClaim, "which OIDC claim contains the user's email")
556 flagSet.String("oidc-email-claim", providers.OIDCEmailClaim, "which OIDC claim contains the user's email")
557+ flagSet.StringSlice("oidc-audience-claim", providers.OIDCAudienceClaims, "which OIDC claims are used as audience to verify against client id")
558+ flagSet.StringSlice("oidc-extra-audience", []string{}, "additional audiences allowed to pass audience verification")
553 flagSet.String("login-url", "", "Authentication endpoint")
559 flagSet.String("login-url", "", "Authentication endpoint")
554 flagSet.String("redeem-url", "", "Token redemption endpoint")
560 flagSet.String("redeem-url", "", "Token redemption endpoint")
555 flagSet.String("profile-url", "", "Profile access endpoint")
561 flagSet.String("profile-url", "", "Profile access endpoint")
@@ -644,6 +650,8 @@ func (l *LegacyProvider) convert() (Providers, error) {
644 UserIDClaim: l.UserIDClaim,
650 UserIDClaim: l.UserIDClaim,
645 EmailClaim: l.OIDCEmailClaim,
651 EmailClaim: l.OIDCEmailClaim,
646 GroupsClaim: l.OIDCGroupsClaim,
652 GroupsClaim: l.OIDCGroupsClaim,
653+ AudienceClaims: l.OIDCAudienceClaims,
654+ ExtraAudiences: l.OIDCExtraAudiences,
647 }
655 }
648
656
649 // This part is out of the switch section because azure has a default tenant
657 // This part is out of the switch section because azure has a default tenant
pkg/apis/options/legacy_options_test.go+2
@@ -116,6 +116,8 @@ var _ = Describe("Legacy Options", func() {
116 opts.Providers[0].ClientID = "oauth-proxy"
116 opts.Providers[0].ClientID = "oauth-proxy"
117 opts.Providers[0].ID = "google=oauth-proxy"
117 opts.Providers[0].ID = "google=oauth-proxy"
118 opts.Providers[0].OIDCConfig.InsecureSkipNonce = true
118 opts.Providers[0].OIDCConfig.InsecureSkipNonce = true
119+ opts.Providers[0].OIDCConfig.AudienceClaims = []string{"aud"}
120+ opts.Providers[0].OIDCConfig.ExtraAudiences = []string{}
119
121
120 converted, err := legacyOpts.ToOptions()
122 converted, err := legacyOpts.ToOptions()
121 Expect(err).ToNot(HaveOccurred())
123 Expect(err).ToNot(HaveOccurred())
pkg/apis/options/load_test.go+1
@@ -42,6 +42,7 @@ var _ = Describe("Load", func() {
42 UserIDClaim: "email",
42 UserIDClaim: "email",
43 OIDCEmailClaim: "email",
43 OIDCEmailClaim: "email",
44 OIDCGroupsClaim: "groups",
44 OIDCGroupsClaim: "groups",
45+ OIDCAudienceClaims: []string{"aud"},
45 InsecureOIDCSkipNonce: true,
46 InsecureOIDCSkipNonce: true,
46 },
47 },
47
48
pkg/apis/options/options.go+17−13
@@ -4,8 +4,8 @@ import (
4 "crypto"
4 "crypto"
5 "net/url"
5 "net/url"
6
6
7− "github.com/coreos/go-oidc/v3/oidc"
8 ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
7 ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
8+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
9 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
9 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
10 "github.com/spf13/pflag"
10 "github.com/spf13/pflag"
11)
11)
@@ -70,25 +70,29 @@ type Options struct {
70 redirectURL *url.URL
70 redirectURL *url.URL
71 provider providers.Provider
71 provider providers.Provider
72 signatureData *SignatureData
72 signatureData *SignatureData
73− oidcVerifier *oidc.IDTokenVerifier
73+ oidcVerifier *internaloidc.IDTokenVerifier
74− jwtBearerVerifiers []*oidc.IDTokenVerifier
74+ jwtBearerVerifiers []*internaloidc.IDTokenVerifier
75 realClientIPParser ipapi.RealClientIPParser
75 realClientIPParser ipapi.RealClientIPParser
76}
76}
77
77
78// Options for Getting internal values
78// Options for Getting internal values
79−func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
79+func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
80−func (o *Options) GetProvider() providers.Provider { return o.provider }
80+func (o *Options) GetProvider() providers.Provider { return o.provider }
81−func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
81+func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
82−func (o *Options) GetOIDCVerifier() *oidc.IDTokenVerifier { return o.oidcVerifier }
82+func (o *Options) GetOIDCVerifier() *internaloidc.IDTokenVerifier { return o.oidcVerifier }
83−func (o *Options) GetJWTBearerVerifiers() []*oidc.IDTokenVerifier { return o.jwtBearerVerifiers }
83+func (o *Options) GetJWTBearerVerifiers() []*internaloidc.IDTokenVerifier {
84+ return o.jwtBearerVerifiers
85+}
84func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.realClientIPParser }
86func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.realClientIPParser }
85
87
86// Options for Setting internal values
88// Options for Setting internal values
87−func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
89+func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
88−func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
90+func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
89−func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
91+func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
90−func (o *Options) SetOIDCVerifier(s *oidc.IDTokenVerifier) { o.oidcVerifier = s }
92+func (o *Options) SetOIDCVerifier(s *internaloidc.IDTokenVerifier) { o.oidcVerifier = s }
91−func (o *Options) SetJWTBearerVerifiers(s []*oidc.IDTokenVerifier) { o.jwtBearerVerifiers = s }
93+func (o *Options) SetJWTBearerVerifiers(s []*internaloidc.IDTokenVerifier) {
94+ o.jwtBearerVerifiers = s
95+}
92func (o *Options) SetRealClientIPParser(s ipapi.RealClientIPParser) { o.realClientIPParser = s }
96func (o *Options) SetRealClientIPParser(s ipapi.RealClientIPParser) { o.realClientIPParser = s }
93
97
94// NewOptions constructs a new Options with defaulted values
98// NewOptions constructs a new Options with defaulted values
pkg/apis/options/providers.go+8
@@ -164,6 +164,12 @@ type OIDCOptions struct {
164 // UserIDClaim indicates which claim contains the user ID
164 // UserIDClaim indicates which claim contains the user ID
165 // default set to 'email'
165 // default set to 'email'
166 UserIDClaim string `json:"userIDClaim,omitempty"`
166 UserIDClaim string `json:"userIDClaim,omitempty"`
167+ // AudienceClaim allows to define any claim that is verified against the client id
168+ // By default `aud` claim is used for verification.
169+ AudienceClaims []string `json:"audienceClaims,omitempty"`
170+ // ExtraAudiences is a list of additional audiences that are allowed
171+ // to pass verification in addition to the client id.
172+ ExtraAudiences []string `json:"extraAudiences,omitempty"`
167}
173}
168
174
169type LoginGovOptions struct {
175type LoginGovOptions struct {
@@ -191,6 +197,8 @@ func providerDefaults() Providers {
191 UserIDClaim: providers.OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
197 UserIDClaim: providers.OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
192 EmailClaim: providers.OIDCEmailClaim,
198 EmailClaim: providers.OIDCEmailClaim,
193 GroupsClaim: providers.OIDCGroupsClaim,
199 GroupsClaim: providers.OIDCGroupsClaim,
200+ AudienceClaims: providers.OIDCAudienceClaims,
201+ ExtraAudiences: []string{},
194 },
202 },
195 },
203 },
196 }
204 }
pkg/oidc/oidc_suite_test.gonew file+16
@@ -0,0 +1,16 @@
1+package oidc
2+
3+import (
4+ "testing"
5+
6+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
7+ . "github.com/onsi/ginkgo"
8+ . "github.com/onsi/gomega"
9+)
10+
11+func TestOIDCSuite(t *testing.T) {
12+ logger.SetOutput(GinkgoWriter)
13+
14+ RegisterFailHandler(Fail)
15+ RunSpecs(t, "OIDC")
16+}
pkg/oidc/verify.gonew file+100
@@ -0,0 +1,100 @@
1+package oidc
2+
3+import (
4+ "context"
5+ "fmt"
6+ "reflect"
7+
8+ "github.com/coreos/go-oidc/v3/oidc"
9+)
10+
11+// IDTokenVerifier Used to verify an ID Token and extends oidc.IDTokenVerifier from the underlying oidc library
12+type IDTokenVerifier struct {
13+ *oidc.IDTokenVerifier
14+ *IDTokenVerificationOptions
15+ allowedAudiences map[string]struct{}
16+}
17+
18+// IDTokenVerificationOptions options for the oidc.IDTokenVerifier that are required to verify an ID Token
19+type IDTokenVerificationOptions struct {
20+ AudienceClaims []string
21+ ClientID string
22+ ExtraAudiences []string
23+}
24+
25+// NewVerifier constructs a new IDTokenVerifier
26+func NewVerifier(iv *oidc.IDTokenVerifier, vo *IDTokenVerificationOptions) *IDTokenVerifier {
27+ allowedAudiences := make(map[string]struct{})
28+ allowedAudiences[vo.ClientID] = struct{}{}
29+ for _, extraAudience := range vo.ExtraAudiences {
30+ allowedAudiences[extraAudience] = struct{}{}
31+ }
32+ return &IDTokenVerifier{iv, vo, allowedAudiences}
33+}
34+
35+// Verify verifies incoming ID Token
36+func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) {
37+ token, err := v.IDTokenVerifier.Verify(ctx, rawIDToken)
38+ if err != nil {
39+ return nil, fmt.Errorf("failed to verify token: %v", err)
40+ }
41+
42+ claims := map[string]interface{}{}
43+ if err := token.Claims(&claims); err != nil {
44+ return nil, fmt.Errorf("failed to parse default id_token claims: %v", err)
45+ }
46+
47+ if isValidAudience, err := v.verifyAudience(token, claims); !isValidAudience {
48+ return nil, err
49+ }
50+
51+ return token, err
52+}
53+
54+func (v *IDTokenVerifier) verifyAudience(token *oidc.IDToken, claims map[string]interface{}) (bool, error) {
55+ for _, audienceClaim := range v.AudienceClaims {
56+ if audienceClaimValue, audienceClaimExists := claims[audienceClaim]; audienceClaimExists {
57+
58+ // audience claim value can be either interface{} or []interface{},
59+ // as per spec `aud` can be either a string or a list of strings
60+ switch audienceClaimValueType := audienceClaimValue.(type) {
61+ case []interface{}:
62+ token.Audience = v.interfaceSliceToString(audienceClaimValue)
63+ case interface{}:
64+ token.Audience = []string{audienceClaimValue.(string)}
65+ default:
66+ return false, fmt.Errorf("audience claim %s holds unsupported type %T",
67+ audienceClaim, audienceClaimValueType)
68+ }
69+
70+ return v.isValidAudience(audienceClaim, token.Audience, v.allowedAudiences)
71+ }
72+ }
73+
74+ return false, fmt.Errorf("audience claims %v do not exist in claims: %v",
75+ v.AudienceClaims, claims)
76+}
77+
78+func (v *IDTokenVerifier) isValidAudience(claim string, audience []string, allowedAudiences map[string]struct{}) (bool, error) {
79+ for _, aud := range audience {
80+ if _, allowedAudienceExists := allowedAudiences[aud]; allowedAudienceExists {
81+ return true, nil
82+ }
83+ }
84+
85+ return false, fmt.Errorf(
86+ "audience from claim %s with value %s does not match with any of allowed audiences %v",
87+ claim, audience, allowedAudiences)
88+}
89+
90+func (v *IDTokenVerifier) interfaceSliceToString(slice interface{}) []string {
91+ s := reflect.ValueOf(slice)
92+ if s.Kind() != reflect.Slice {
93+ panic(fmt.Sprintf("given a non-slice type %s", s.Kind()))
94+ }
95+ var strings []string
96+ for i := 0; i < s.Len(); i++ {
97+ strings = append(strings, s.Index(i).Interface().(string))
98+ }
99+ return strings
100+}
pkg/oidc/verify_test.gonew file+249
@@ -0,0 +1,249 @@
1+package oidc
2+
3+import (
4+ "context"
5+ "crypto/rand"
6+ "crypto/rsa"
7+ "encoding/json"
8+ "fmt"
9+
10+ "github.com/coreos/go-oidc/v3/oidc"
11+ . "github.com/onsi/ginkgo"
12+ . "github.com/onsi/gomega"
13+ "gopkg.in/square/go-jose.v2"
14+)
15+
16+var _ = Describe("Verify", func() {
17+ ctx := context.Background()
18+
19+ It("Succeeds with default aud behavior", func() {
20+ result, err := verify(ctx, &IDTokenVerificationOptions{
21+ AudienceClaims: []string{"aud"},
22+ ClientID: "1226737",
23+ ExtraAudiences: []string{},
24+ }, payload{
25+ Iss: "https://foo",
26+ Aud: "1226737",
27+ })
28+
29+ Expect(err).ToNot(HaveOccurred())
30+ Expect(result.Issuer).To(Equal("https://foo"))
31+ Expect(result.Audience).To(Equal([]string{"1226737"}))
32+ })
33+
34+ It("Fails with default aud behavior", func() {
35+ result, err := verify(ctx, &IDTokenVerificationOptions{
36+ AudienceClaims: []string{"aud"},
37+ ClientID: "7817818",
38+ ExtraAudiences: []string{},
39+ }, payload{
40+ Iss: "https://foo",
41+ Aud: "1226737",
42+ })
43+ Expect(err).To(MatchError("audience from claim aud with value [1226737] does not match with " +
44+ "any of allowed audiences map[7817818:{}]"))
45+ Expect(result).To(BeNil())
46+ })
47+
48+ It("Succeeds with extra audiences", func() {
49+ result, err := verify(ctx, &IDTokenVerificationOptions{
50+ AudienceClaims: []string{"aud"},
51+ ClientID: "7817818",
52+ ExtraAudiences: []string{"xyz", "1226737"},
53+ }, payload{
54+ Iss: "https://foo",
55+ Aud: "1226737",
56+ })
57+
58+ Expect(err).ToNot(HaveOccurred())
59+ Expect(result.Issuer).To(Equal("https://foo"))
60+ Expect(result.Audience).To(Equal([]string{"1226737"}))
61+ })
62+
63+ It("Fails with extra audiences", func() {
64+ result, err := verify(ctx, &IDTokenVerificationOptions{
65+ AudienceClaims: []string{"aud"},
66+ ClientID: "7817818",
67+ ExtraAudiences: []string{"xyz", "abc"},
68+ }, payload{
69+ Iss: "https://foo",
70+ Aud: "1226737",
71+ })
72+
73+ Expect(err).To(MatchError("audience from claim aud with value [1226737] does not match with any " +
74+ "of allowed audiences map[7817818:{} abc:{} xyz:{}]"))
75+ Expect(result).To(BeNil())
76+ })
77+
78+ It("Succeeds with non default aud behavior", func() {
79+ result, err := verify(ctx, &IDTokenVerificationOptions{
80+ AudienceClaims: []string{"client_id"},
81+ ClientID: "1226737",
82+ ExtraAudiences: []string{},
83+ }, payload{
84+ Iss: "https://foo",
85+ ClientID: "1226737",
86+ })
87+
88+ Expect(err).ToNot(HaveOccurred())
89+ Expect(result.Issuer).To(Equal("https://foo"))
90+ Expect(result.Audience).To(Equal([]string{"1226737"}))
91+ })
92+
93+ It("Fails with non default aud behavior", func() {
94+ result, err := verify(ctx, &IDTokenVerificationOptions{
95+ AudienceClaims: []string{"client_id"},
96+ ClientID: "7817818",
97+ ExtraAudiences: []string{},
98+ }, payload{
99+ Iss: "https://foo",
100+ ClientID: "1226737",
101+ })
102+ Expect(err).To(MatchError("audience from claim client_id with value [1226737] does not match with " +
103+ "any of allowed audiences map[7817818:{}]"))
104+ Expect(result).To(BeNil())
105+ })
106+
107+ It("Succeeds with non default aud behavior and extra audiences", func() {
108+ result, err := verify(ctx, &IDTokenVerificationOptions{
109+ AudienceClaims: []string{"client_id"},
110+ ClientID: "7817818",
111+ ExtraAudiences: []string{"xyz", "1226737"},
112+ }, payload{
113+ Iss: "https://foo",
114+ ClientID: "1226737",
115+ })
116+
117+ Expect(err).ToNot(HaveOccurred())
118+ Expect(result.Issuer).To(Equal("https://foo"))
119+ Expect(result.Audience).To(Equal([]string{"1226737"}))
120+ })
121+
122+ It("Fails with non default aud behavior and extra audiences", func() {
123+ result, err := verify(ctx, &IDTokenVerificationOptions{
124+ AudienceClaims: []string{"client_id"},
125+ ClientID: "7817818",
126+ ExtraAudiences: []string{"xyz", "abc"},
127+ }, payload{
128+ Iss: "https://foo",
129+ ClientID: "1226737",
130+ })
131+
132+ Expect(err).To(MatchError("audience from claim client_id with value [1226737] does not match with any " +
133+ "of allowed audiences map[7817818:{} abc:{} xyz:{}]"))
134+ Expect(result).To(BeNil())
135+ })
136+
137+ It("Fails if audience claim does not exist", func() {
138+ result, err := verify(ctx, &IDTokenVerificationOptions{
139+ AudienceClaims: []string{"not_exists"},
140+ ClientID: "7817818",
141+ ExtraAudiences: []string{"xyz", "abc"},
142+ }, payload{
143+ Iss: "https://foo",
144+ ClientID: "1226737",
145+ Aud: "1226737",
146+ })
147+
148+ Expect(err).To(MatchError("audience claims [not_exists] do not exist in claims: " +
149+ "map[aud:1226737 client_id:1226737 iss:https://foo]"))
150+ Expect(result).To(BeNil())
151+ })
152+
153+ It("Succeeds with multiple audiences", func() {
154+ var result, err = verify(ctx, &IDTokenVerificationOptions{
155+ AudienceClaims: []string{"client_id", "aud"},
156+ ClientID: "123456789",
157+ ExtraAudiences: []string{"1226737"},
158+ }, payload{
159+ Iss: "https://foo",
160+ ClientID: "123456789",
161+ Aud: []string{"1226737", "123456789"},
162+ })
163+
164+ Expect(err).ToNot(HaveOccurred())
165+ Expect(result.Issuer).To(Equal("https://foo"))
166+ Expect(result.Audience).To(Equal([]string{"123456789"}))
167+ })
168+
169+ It("Succeeds if aud claim match", func() {
170+ result, err := verify(ctx, &IDTokenVerificationOptions{
171+ AudienceClaims: []string{"client_id", "aud"},
172+ ClientID: "1226737",
173+ ExtraAudiences: []string{"xyz", "abc"},
174+ }, payload{
175+ Iss: "https://foo",
176+ ClientID: "1226737",
177+ Aud: "1226737",
178+ })
179+
180+ Expect(err).ToNot(HaveOccurred())
181+ Expect(result.Issuer).To(Equal("https://foo"))
182+ Expect(result.Audience).To(Equal([]string{"1226737"}))
183+ })
184+})
185+
186+type payload struct {
187+ Iss string `json:"iss,omitempty"`
188+ Aud interface{} `json:"aud,omitempty"`
189+ ClientID string `json:"client_id,omitempty"`
190+}
191+
192+type jwtToken struct {
193+ PrivateKey jose.JSONWebKey
194+ PublicKey jose.JSONWebKey
195+ Token string
196+}
197+
198+type testVerifier struct {
199+ jwk jose.JSONWebKey
200+}
201+
202+func (t *testVerifier) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
203+ jws, err := jose.ParseSigned(jwt)
204+ if err != nil {
205+ return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
206+ }
207+ return jws.Verify(&t.jwk)
208+}
209+
210+func verify(ctx context.Context, verificationOptions *IDTokenVerificationOptions, payload payload) (*oidc.IDToken, error) {
211+ config := &oidc.Config{
212+ ClientID: "1226737",
213+ SkipClientIDCheck: true,
214+ SkipExpiryCheck: true, // required to not run in expired Token error during testing
215+ }
216+ rawToken, err := json.Marshal(payload)
217+ if err != nil {
218+ return nil, err
219+ }
220+ token, _ := createToken(rawToken)
221+ verifier := NewVerifier(oidc.NewVerifier("https://foo", &testVerifier{jwk: token.PublicKey}, config), verificationOptions)
222+ return verifier.Verify(ctx, token.Token)
223+}
224+
225+func createToken(payload []byte) (*jwtToken, error) {
226+ privateKey, err := rsa.GenerateKey(rand.Reader, 1028)
227+ if err != nil {
228+ return nil, err
229+ }
230+ privateWebKey := jose.JSONWebKey{Key: privateKey, Algorithm: string(jose.RS256), KeyID: ""}
231+ publicWebKey := jose.JSONWebKey{Key: privateKey.Public(), Use: "sig", Algorithm: string(jose.RS256), KeyID: ""}
232+ signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: privateWebKey}, nil)
233+ if err != nil {
234+ return nil, err
235+ }
236+ jws, err := signer.Sign(payload)
237+ if err != nil {
238+ return nil, err
239+ }
240+ data, err := jws.CompactSerialize()
241+ if err != nil {
242+ return nil, err
243+ }
244+ return &jwtToken{
245+ PrivateKey: privateWebKey,
246+ PublicKey: publicWebKey,
247+ Token: data,
248+ }, nil
249+}
pkg/validation/options.go+38−14
@@ -16,6 +16,7 @@ import (
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
18 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
18 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
19+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
19 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
20 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
20 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
@@ -102,6 +103,18 @@ func Validate(o *options.Options) error {
102 }
103 }
103 }
104 }
104
105
106+ config := &oidc.Config{
107+ ClientID: o.Providers[0].ClientID,
108+ SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
109+ SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
110+ }
111+
112+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
113+ AudienceClaims: o.Providers[0].OIDCConfig.AudienceClaims,
114+ ClientID: o.Providers[0].ClientID,
115+ ExtraAudiences: o.Providers[0].OIDCConfig.ExtraAudiences,
116+ }
117+
105 // Construct a manual IDTokenVerifier from issuer URL & JWKS URI
118 // Construct a manual IDTokenVerifier from issuer URL & JWKS URI
106 // instead of metadata discovery if we enable -skip-oidc-discovery.
119 // instead of metadata discovery if we enable -skip-oidc-discovery.
107 // In this case we need to make sure the required endpoints for
120 // In this case we need to make sure the required endpoints for
@@ -117,21 +130,16 @@ func Validate(o *options.Options) error {
117 msgs = append(msgs, "missing setting: oidc-jwks-url")
130 msgs = append(msgs, "missing setting: oidc-jwks-url")
118 }
131 }
119 keySet := oidc.NewRemoteKeySet(ctx, o.Providers[0].OIDCConfig.JwksURL)
132 keySet := oidc.NewRemoteKeySet(ctx, o.Providers[0].OIDCConfig.JwksURL)
133+ o.SetOIDCVerifier(internaloidc.NewVerifier(
120− o.SetOIDCVerifier(oidc.NewVerifier(o.Providers[0].OIDCConfig.IssuerURL, keySet, &oidc.Config{
134+ oidc.NewVerifier(o.Providers[0].OIDCConfig.IssuerURL, keySet, config), verificationOptions))
121− ClientID: o.Providers[0].ClientID,
122− SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
123− }))
124 } else {
135 } else {
125 // Configure discoverable provider data.
136 // Configure discoverable provider data.
126 provider, err := oidc.NewProvider(ctx, o.Providers[0].OIDCConfig.IssuerURL)
137 provider, err := oidc.NewProvider(ctx, o.Providers[0].OIDCConfig.IssuerURL)
127 if err != nil {
138 if err != nil {
128 return err
139 return err
129 }
140 }
130− o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
131− ClientID: o.Providers[0].ClientID,
132− SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
133− }))
134
141
142+ o.SetOIDCVerifier(internaloidc.NewVerifier(provider.Verifier(config), verificationOptions))
135 o.Providers[0].LoginURL = provider.Endpoint().AuthURL
143 o.Providers[0].LoginURL = provider.Endpoint().AuthURL
136 o.Providers[0].RedeemURL = provider.Endpoint().TokenURL
144 o.Providers[0].RedeemURL = provider.Endpoint().TokenURL
137 }
145 }
@@ -153,7 +161,11 @@ func Validate(o *options.Options) error {
153 var jwtIssuers []jwtIssuer
161 var jwtIssuers []jwtIssuer
154 jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
162 jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
155 for _, jwtIssuer := range jwtIssuers {
163 for _, jwtIssuer := range jwtIssuers {
156− verifier, err := newVerifierFromJwtIssuer(jwtIssuer)
164+ verifier, err := newVerifierFromJwtIssuer(
165+ o.Providers[0].OIDCConfig.AudienceClaims,
166+ o.Providers[0].OIDCConfig.ExtraAudiences,
167+ jwtIssuer,
168+ )
157 if err != nil {
169 if err != nil {
158 msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
170 msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
159 }
171 }
@@ -290,9 +302,14 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
290 if err != nil {
302 if err != nil {
291 msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
303 msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
292 } else {
304 } else {
305+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
306+ AudienceClaims: o.Providers[0].OIDCConfig.AudienceClaims,
307+ ClientID: o.Providers[0].ClientID,
308+ ExtraAudiences: o.Providers[0].OIDCConfig.ExtraAudiences,
309+ }
293− p.Verifier = provider.Verifier(&oidc.Config{
310+ p.Verifier = internaloidc.NewVerifier(provider.Verifier(&oidc.Config{
294 ClientID: o.Providers[0].ClientID,
311 ClientID: o.Providers[0].ClientID,
295− })
312+ }), verificationOptions)
296
313
297 p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
314 p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
298 p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
315 p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
@@ -372,9 +389,10 @@ func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
372
389
373// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
390// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
374// a verifier for that issuer.
391// a verifier for that issuer.
375−func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) {
392+func newVerifierFromJwtIssuer(audienceClaims []string, extraAudiences []string, jwtIssuer jwtIssuer) (*internaloidc.IDTokenVerifier, error) {
376 config := &oidc.Config{
393 config := &oidc.Config{
377− ClientID: jwtIssuer.audience,
394+ ClientID: jwtIssuer.audience,
395+ SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
378 }
396 }
379 // Try as an OpenID Connect Provider first
397 // Try as an OpenID Connect Provider first
380 var verifier *oidc.IDTokenVerifier
398 var verifier *oidc.IDTokenVerifier
@@ -390,7 +408,13 @@ func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error
390 } else {
408 } else {
391 verifier = provider.Verifier(config)
409 verifier = provider.Verifier(config)
392 }
410 }
411+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
412+ AudienceClaims: audienceClaims,
413+ ClientID: jwtIssuer.audience,
414+ ExtraAudiences: extraAudiences,
415+ // ExtraAudiences: o.Providers[0].OIDCConfig.ExtraAudiences,
416+ }
393− return verifier, nil
417+ return internaloidc.NewVerifier(verifier, verificationOptions), nil
394}
418}
395
419
396// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
420// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
providers/adfs_test.gomode 100644 → 100755+8−2
@@ -14,6 +14,7 @@ import (
14 "github.com/coreos/go-oidc/v3/oidc"
14 "github.com/coreos/go-oidc/v3/oidc"
15 "github.com/golang-jwt/jwt"
15 "github.com/golang-jwt/jwt"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
17+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
17 . "github.com/onsi/ginkgo"
18 . "github.com/onsi/ginkgo"
18 . "github.com/onsi/ginkgo/extensions/table"
19 . "github.com/onsi/ginkgo/extensions/table"
19 . "github.com/onsi/gomega"
20 . "github.com/onsi/gomega"
@@ -41,11 +42,16 @@ func newSignedTestADFSToken(tokenClaims adfsClaims) (string, error) {
41}
42}
42
43
43func testADFSProvider(hostname string) *ADFSProvider {
44func testADFSProvider(hostname string) *ADFSProvider {
45+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
46+ AudienceClaims: []string{"aud"},
47+ ClientID: "https://test.myapp.com",
48+ }
49+
44− o := oidc.NewVerifier(
50+ o := internaloidc.NewVerifier(oidc.NewVerifier(
45 "https://issuer.example.com",
51 "https://issuer.example.com",
46 fakeADFSJwks{},
52 fakeADFSJwks{},
47 &oidc.Config{ClientID: "https://test.myapp.com"},
53 &oidc.Config{ClientID: "https://test.myapp.com"},
48− )
54+ ), verificationOptions)
49
55
50 p := NewADFSProvider(&ProviderData{
56 p := NewADFSProvider(&ProviderData{
51 ProviderName: "",
57 ProviderName: "",
providers/azure_test.go+19−6
@@ -13,8 +13,11 @@ import (
13 "testing"
13 "testing"
14 "time"
14 "time"
15
15
16+ "github.com/golang-jwt/jwt"
17+
16− "github.com/coreos/go-oidc/v3/oidc"
18+ oidc "github.com/coreos/go-oidc/v3/oidc"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
19 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
20+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
18
21
19 . "github.com/onsi/gomega"
22 . "github.com/onsi/gomega"
20 "github.com/stretchr/testify/assert"
23 "github.com/stretchr/testify/assert"
@@ -38,6 +41,10 @@ type azureOAuthPayload struct {
38}
41}
39
42
40func testAzureProvider(hostname string) *AzureProvider {
43func testAzureProvider(hostname string) *AzureProvider {
44+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
45+ AudienceClaims: []string{"aud"},
46+ ClientID: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532",
47+ }
41 p := NewAzureProvider(
48 p := NewAzureProvider(
42 &ProviderData{
49 &ProviderData{
43 ProviderName: "",
50 ProviderName: "",
@@ -48,7 +55,7 @@ func testAzureProvider(hostname string) *AzureProvider {
48 ProtectedResource: &url.URL{},
55 ProtectedResource: &url.URL{},
49 Scope: "",
56 Scope: "",
50 EmailClaim: "email",
57 EmailClaim: "email",
51− Verifier: oidc.NewVerifier(
58+ Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
52 "https://issuer.example.com",
59 "https://issuer.example.com",
53 fakeAzureKeySetStub{},
60 fakeAzureKeySetStub{},
54 &oidc.Config{
61 &oidc.Config{
@@ -57,7 +64,7 @@ func testAzureProvider(hostname string) *AzureProvider {
57 SkipIssuerCheck: true,
64 SkipIssuerCheck: true,
58 SkipExpiryCheck: true,
65 SkipExpiryCheck: true,
59 },
66 },
60− ),
67+ ), verificationOptions),
61 })
68 })
62
69
63 if hostname != "" {
70 if hostname != "" {
@@ -285,13 +292,17 @@ func TestAzureProviderRedeem(t *testing.T) {
285 accessTokenString := ""
292 accessTokenString := ""
286 if testCase.EmailFromIDToken != "" {
293 if testCase.EmailFromIDToken != "" {
287 var err error
294 var err error
295+ token := idTokenClaims{
296+ StandardClaims: jwt.StandardClaims{Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"},
288− token := idTokenClaims{Email: testCase.EmailFromIDToken}
297+ Email: testCase.EmailFromIDToken}
289 idTokenString, err = newSignedTestIDToken(token)
298 idTokenString, err = newSignedTestIDToken(token)
290 assert.NoError(t, err)
299 assert.NoError(t, err)
291 }
300 }
292 if testCase.EmailFromAccessToken != "" {
301 if testCase.EmailFromAccessToken != "" {
293 var err error
302 var err error
303+ token := idTokenClaims{
304+ StandardClaims: jwt.StandardClaims{Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"},
294− token := idTokenClaims{Email: testCase.EmailFromAccessToken}
305+ Email: testCase.EmailFromAccessToken}
295 accessTokenString, err = newSignedTestIDToken(token)
306 accessTokenString, err = newSignedTestIDToken(token)
296 assert.NoError(t, err)
307 assert.NoError(t, err)
297 }
308 }
@@ -342,7 +353,9 @@ func TestAzureProviderProtectedResourceConfigured(t *testing.T) {
342
353
343func TestAzureProviderRefresh(t *testing.T) {
354func TestAzureProviderRefresh(t *testing.T) {
344 email := "foo@example.com"
355 email := "foo@example.com"
345− idToken := idTokenClaims{Email: email}
356+ idToken := idTokenClaims{
357+ StandardClaims: jwt.StandardClaims{Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"},
358+ Email: email}
346 idTokenString, err := newSignedTestIDToken(idToken)
359 idTokenString, err := newSignedTestIDToken(idToken)
347 assert.NoError(t, err)
360 assert.NoError(t, err)
348 timestamp, err := time.Parse(time.RFC3339, "3006-01-02T22:04:05Z")
361 timestamp, err := time.Parse(time.RFC3339, "3006-01-02T22:04:05Z")
providers/keycloak_oidc_test.go+13−3
@@ -12,14 +12,20 @@ import (
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13 . "github.com/onsi/ginkgo"
13 . "github.com/onsi/ginkgo"
14 . "github.com/onsi/gomega"
14 . "github.com/onsi/gomega"
15+
16+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
15)
17)
16
18
17const (
19const (
18 accessTokenHeader = "ewogICJhbGciOiAiUlMyNTYiLAogICJ0eXAiOiAiSldUIgp9"
20 accessTokenHeader = "ewogICJhbGciOiAiUlMyNTYiLAogICJ0eXAiOiAiSldUIgp9"
19− accessTokenPayload = "eyJyZWFsbV9hY2Nlc3MiOiB7InJvbGVzIjogWyJ3cml0ZSJdfSwgInJlc291cmNlX2FjY2VzcyI6IHsiZGVmYXVsdCI6IHsicm9sZXMiOiBbInJlYWQiXX19fQ"
20 accessTokenSignature = "dyt0CoTl4WoVjAHI9Q_CwSKhl6d_9rhM3NrXuJttkao"
21 accessTokenSignature = "dyt0CoTl4WoVjAHI9Q_CwSKhl6d_9rhM3NrXuJttkao"
22+ defaultAudienceClaim = "aud"
23+ mockClientID = "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"
21)
24)
22
25
26+var accessTokenPayload = base64.StdEncoding.EncodeToString([]byte(
27+ fmt.Sprintf(`{"%s": "%s", "realm_access": {"roles": ["write"]}, "resource_access": {"default": {"roles": ["read"]}}}`, defaultAudienceClaim, mockClientID)))
28+
23type DummyKeySet struct{}
29type DummyKeySet struct{}
24
30
25func (DummyKeySet) VerifySignature(_ context.Context, _ string) (payload []byte, err error) {
31func (DummyKeySet) VerifySignature(_ context.Context, _ string) (payload []byte, err error) {
@@ -38,6 +44,10 @@ func newTestKeycloakOIDCSetup() (*httptest.Server, *KeycloakOIDCProvider) {
38}
44}
39
45
40func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
46func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
47+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
48+ AudienceClaims: []string{defaultAudienceClaim},
49+ ClientID: mockClientID,
50+ }
41 p := NewKeycloakOIDCProvider(
51 p := NewKeycloakOIDCProvider(
42 &ProviderData{
52 &ProviderData{
43 LoginURL: &url.URL{
53 LoginURL: &url.URL{
@@ -64,12 +74,12 @@ func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
64 }
74 }
65
75
66 keyset := DummyKeySet{}
76 keyset := DummyKeySet{}
67− p.Verifier = oidc.NewVerifier("", keyset, &oidc.Config{
77+ p.Verifier = internaloidc.NewVerifier(oidc.NewVerifier("", keyset, &oidc.Config{
68 ClientID: "client",
78 ClientID: "client",
69 SkipIssuerCheck: true,
79 SkipIssuerCheck: true,
70 SkipClientIDCheck: true,
80 SkipClientIDCheck: true,
71 SkipExpiryCheck: true,
81 SkipExpiryCheck: true,
72− })
82+ }), verificationOptions)
73 p.EmailClaim = "email"
83 p.EmailClaim = "email"
74 p.GroupsClaim = "groups"
84 p.GroupsClaim = "groups"
75 return p
85 return p
providers/oidc_test.go+7−2
@@ -14,6 +14,7 @@ import (
14 "github.com/coreos/go-oidc/v3/oidc"
14 "github.com/coreos/go-oidc/v3/oidc"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
17+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
17 "github.com/stretchr/testify/assert"
18 "github.com/stretchr/testify/assert"
18)
19)
19
20
@@ -26,6 +27,10 @@ type redeemTokenResponse struct {
26}
27}
27
28
28func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
29func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
30+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
31+ AudienceClaims: []string{"aud"},
32+ ClientID: "https://test.myapp.com",
33+ }
29 providerData := &ProviderData{
34 providerData := &ProviderData{
30 ProviderName: "oidc",
35 ProviderName: "oidc",
31 ClientID: oidcClientID,
36 ClientID: oidcClientID,
@@ -49,11 +54,11 @@ func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
49 Scope: "openid profile offline_access",
54 Scope: "openid profile offline_access",
50 EmailClaim: "email",
55 EmailClaim: "email",
51 GroupsClaim: "groups",
56 GroupsClaim: "groups",
52− Verifier: oidc.NewVerifier(
57+ Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
53 oidcIssuer,
58 oidcIssuer,
54 mockJWKS{},
59 mockJWKS{},
55 &oidc.Config{ClientID: oidcClientID},
60 &oidc.Config{ClientID: oidcClientID},
56− ),
61+ ), verificationOptions),
57 }
62 }
58
63
59 p := NewOIDCProvider(providerData)
64 p := NewOIDCProvider(providerData)
providers/provider_data.go+4−1
@@ -12,6 +12,7 @@ import (
12 "github.com/coreos/go-oidc/v3/oidc"
12 "github.com/coreos/go-oidc/v3/oidc"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
15+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
15 "golang.org/x/oauth2"
16 "golang.org/x/oauth2"
16)
17)
17
18
@@ -20,6 +21,8 @@ const (
20 OIDCGroupsClaim = "groups"
21 OIDCGroupsClaim = "groups"
21)
22)
22
23
24+var OIDCAudienceClaims = []string{"aud"}
25+
23// ProviderData contains information required to configure all implementations
26// ProviderData contains information required to configure all implementations
24// of OAuth2 providers
27// of OAuth2 providers
25type ProviderData struct {
28type ProviderData struct {
@@ -44,7 +47,7 @@ type ProviderData struct {
44 UserClaim string
47 UserClaim string
45 EmailClaim string
48 EmailClaim string
46 GroupsClaim string
49 GroupsClaim string
47− Verifier *oidc.IDTokenVerifier
50+ Verifier *internaloidc.IDTokenVerifier
48
51
49 // Universal Group authorization data structure
52 // Universal Group authorization data structure
50 // any provider can set to consume
53 // any provider can set to consume
providers/provider_data_test.go+27−9
@@ -16,6 +16,7 @@ import (
16 "github.com/golang-jwt/jwt"
16 "github.com/golang-jwt/jwt"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
18 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
18 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
19+ internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
19 . "github.com/onsi/gomega"
20 . "github.com/onsi/gomega"
20 "golang.org/x/oauth2"
21 "golang.org/x/oauth2"
21)
22)
@@ -155,7 +156,8 @@ func TestProviderData_verifyIDToken(t *testing.T) {
155 IDToken: &failureIDToken,
156 IDToken: &failureIDToken,
156 Verifier: true,
157 Verifier: true,
157 ExpectIDToken: false,
158 ExpectIDToken: false,
159+ ExpectedError: errors.New("failed to verify token: failed to verify signature: " +
158− ExpectedError: errors.New("failed to verify signature: the validation failed for subject [123456789]"),
160+ "the validation failed for subject [123456789]"),
159 },
161 },
160 "Missing ID Token": {
162 "Missing ID Token": {
161 IDToken: nil,
163 IDToken: nil,
@@ -186,11 +188,15 @@ func TestProviderData_verifyIDToken(t *testing.T) {
186
188
187 provider := &ProviderData{}
189 provider := &ProviderData{}
188 if tc.Verifier {
190 if tc.Verifier {
191+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
192+ AudienceClaims: []string{"aud"},
193+ ClientID: oidcClientID,
194+ }
189− provider.Verifier = oidc.NewVerifier(
195+ provider.Verifier = internaloidc.NewVerifier(oidc.NewVerifier(
190 oidcIssuer,
196 oidcIssuer,
191 mockJWKS{},
197 mockJWKS{},
192 &oidc.Config{ClientID: oidcClientID},
198 &oidc.Config{ClientID: oidcClientID},
193− )
199+ ), verificationOptions)
194 }
200 }
195 verified, err := provider.verifyIDToken(context.Background(), token)
201 verified, err := provider.verifyIDToken(context.Background(), token)
196 if err != nil {
202 if err != nil {
@@ -346,12 +352,16 @@ func TestProviderData_buildSessionFromClaims(t *testing.T) {
346 t.Run(testName, func(t *testing.T) {
352 t.Run(testName, func(t *testing.T) {
347 g := NewWithT(t)
353 g := NewWithT(t)
348
354
355+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
356+ AudienceClaims: []string{"aud"},
357+ ClientID: oidcClientID,
358+ }
349 provider := &ProviderData{
359 provider := &ProviderData{
350− Verifier: oidc.NewVerifier(
360+ Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
351 oidcIssuer,
361 oidcIssuer,
352 mockJWKS{},
362 mockJWKS{},
353 &oidc.Config{ClientID: oidcClientID},
363 &oidc.Config{ClientID: oidcClientID},
354− ),
364+ ), verificationOptions),
355 }
365 }
356 provider.AllowUnverifiedEmail = tc.AllowUnverified
366 provider.AllowUnverifiedEmail = tc.AllowUnverified
357 provider.UserClaim = tc.UserClaim
367 provider.UserClaim = tc.UserClaim
@@ -408,12 +418,16 @@ func TestProviderData_checkNonce(t *testing.T) {
408 t.Run(testName, func(t *testing.T) {
418 t.Run(testName, func(t *testing.T) {
409 g := NewWithT(t)
419 g := NewWithT(t)
410
420
421+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
422+ AudienceClaims: []string{"aud"},
423+ ClientID: oidcClientID,
424+ }
411 provider := &ProviderData{
425 provider := &ProviderData{
412− Verifier: oidc.NewVerifier(
426+ Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
413 oidcIssuer,
427 oidcIssuer,
414 mockJWKS{},
428 mockJWKS{},
415 &oidc.Config{ClientID: oidcClientID},
429 &oidc.Config{ClientID: oidcClientID},
416− ),
430+ ), verificationOptions),
417 }
431 }
418
432
419 rawIDToken, err := newSignedTestIDToken(tc.IDToken)
433 rawIDToken, err := newSignedTestIDToken(tc.IDToken)
@@ -501,12 +515,16 @@ func TestProviderData_extractGroups(t *testing.T) {
501 t.Run(testName, func(t *testing.T) {
515 t.Run(testName, func(t *testing.T) {
502 g := NewWithT(t)
516 g := NewWithT(t)
503
517
518+ verificationOptions := &internaloidc.IDTokenVerificationOptions{
519+ AudienceClaims: []string{"aud"},
520+ ClientID: oidcClientID,
521+ }
504 provider := &ProviderData{
522 provider := &ProviderData{
505− Verifier: oidc.NewVerifier(
523+ Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
506 oidcIssuer,
524 oidcIssuer,
507 mockJWKS{},
525 mockJWKS{},
508 &oidc.Config{ClientID: oidcClientID},
526 &oidc.Config{ClientID: oidcClientID},
509− ),
527+ ), verificationOptions),
510 }
528 }
511 provider.GroupsClaim = tc.GroupsClaim
529 provider.GroupsClaim = tc.GroupsClaim
512
530