mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-05-31 23:19:50 +02:00
improved audience handling to support client credentials access tokens without aud claims (#1204)
* implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
This commit is contained in:
parent
c5a98c6d03
commit
25371ea4af
@ -10,6 +10,7 @@
|
||||
|
||||
- [#1489](https://github.com/oauth2-proxy/oauth2-proxy/pull/1489) Fix Docker Buildx push to include build version (@JoelSpeed)
|
||||
- [#1477](https://github.com/oauth2-proxy/oauth2-proxy/pull/1477) Remove provider documentation for `Microsoft Azure AD` (@omBratteng)
|
||||
- [#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)
|
||||
- [#1509](https://github.com/oauth2-proxy/oauth2-proxy/pull/1509) Update LoginGovProvider ValidateSession to pass access_token in Header (@pksheldon4)
|
||||
- [#1474](https://github.com/oauth2-proxy/oauth2-proxy/pull/1474) Support configuration of minimal acceptable TLS version (@polarctos)
|
||||
- [#1545](https://github.com/oauth2-proxy/oauth2-proxy/pull/1545) Fix issue with query string allowed group panic on skip methods (@andytson)
|
||||
|
@ -281,6 +281,8 @@ make up the header value
|
||||
| `emailClaim` | _string_ | EmailClaim indicates which claim contains the user email,<br/>default set to 'email' |
|
||||
| `groupsClaim` | _string_ | GroupsClaim indicates which claim contains the user groups<br/>default set to 'groups' |
|
||||
| `userIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID<br/>default set to 'email' |
|
||||
| `audienceClaims` | _[]string_ | AudienceClaim allows to define any claim that is verified against the client id<br/>By default `aud` claim is used for verification. |
|
||||
| `extraAudiences` | _[]string_ | ExtraAudiences is a list of additional audiences that are allowed<br/>to pass verification in addition to the client id. |
|
||||
|
||||
### Provider
|
||||
|
||||
|
@ -136,6 +136,8 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
|
||||
| `--oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | |
|
||||
| `--oidc-email-claim` | string | which OIDC claim contains the user's email | `"email"` |
|
||||
| `--oidc-groups-claim` | string | which OIDC claim contains the user groups | `"groups"` |
|
||||
| `--oidc-audience-claim` | string | which OIDC claim contains the audience | `"aud"` |
|
||||
| `--oidc-extra-audience` | string \| list | additional audiences which are allowed to pass verification | `"[]"` |
|
||||
| `--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 |
|
||||
| `--pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false |
|
||||
| `--pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true |
|
||||
|
@ -76,6 +76,8 @@ providers:
|
||||
emailClaim: email
|
||||
userIDClaim: email
|
||||
insecureSkipNonce: true
|
||||
audienceClaims: [aud]
|
||||
extraAudiences: []
|
||||
`
|
||||
|
||||
const testCoreConfig = `
|
||||
@ -148,6 +150,8 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
||||
GroupsClaim: "groups",
|
||||
EmailClaim: "email",
|
||||
UserIDClaim: "email",
|
||||
AudienceClaims: []string{"aud"},
|
||||
ExtraAudiences: []string{},
|
||||
InsecureSkipNonce: true,
|
||||
},
|
||||
ApprovalPrompt: "force",
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
|
||||
@ -1737,7 +1738,14 @@ func TestGetJwtSession(t *testing.T) {
|
||||
|
||||
keyset := NoOpKeySet{}
|
||||
verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
|
||||
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
|
||||
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true,
|
||||
SkipClientIDCheck: true})
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "https://test.myapp.com",
|
||||
ExtraAudiences: []string{},
|
||||
}
|
||||
internalVerifier := internaloidc.NewVerifier(verifier, verificationOptions)
|
||||
|
||||
test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
|
||||
opts.InjectRequestHeaders = []options.Header{
|
||||
@ -1808,7 +1816,7 @@ func TestGetJwtSession(t *testing.T) {
|
||||
},
|
||||
}
|
||||
opts.SkipJwtBearerTokens = true
|
||||
opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
|
||||
opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), internalVerifier))
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -54,6 +54,8 @@ func NewLegacyOptions() *LegacyOptions {
|
||||
UserIDClaim: "email",
|
||||
OIDCEmailClaim: "email",
|
||||
OIDCGroupsClaim: "groups",
|
||||
OIDCAudienceClaims: []string{"aud"},
|
||||
OIDCExtraAudiences: []string{},
|
||||
InsecureOIDCSkipNonce: true,
|
||||
},
|
||||
|
||||
@ -500,6 +502,8 @@ type LegacyProvider struct {
|
||||
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"`
|
||||
OIDCEmailClaim string `flag:"oidc-email-claim" cfg:"oidc_email_claim"`
|
||||
OIDCGroupsClaim string `flag:"oidc-groups-claim" cfg:"oidc_groups_claim"`
|
||||
OIDCAudienceClaims []string `flag:"oidc-audience-claim" cfg:"oidc_audience_claims"`
|
||||
OIDCExtraAudiences []string `flag:"oidc-extra-audience" cfg:"oidc_extra_audiences"`
|
||||
LoginURL string `flag:"login-url" cfg:"login_url"`
|
||||
RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
|
||||
ProfileURL string `flag:"profile-url" cfg:"profile_url"`
|
||||
@ -550,6 +554,8 @@ func legacyProviderFlagSet() *pflag.FlagSet {
|
||||
flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
|
||||
flagSet.String("oidc-groups-claim", providers.OIDCGroupsClaim, "which OIDC claim contains the user groups")
|
||||
flagSet.String("oidc-email-claim", providers.OIDCEmailClaim, "which OIDC claim contains the user's email")
|
||||
flagSet.StringSlice("oidc-audience-claim", providers.OIDCAudienceClaims, "which OIDC claims are used as audience to verify against client id")
|
||||
flagSet.StringSlice("oidc-extra-audience", []string{}, "additional audiences allowed to pass audience verification")
|
||||
flagSet.String("login-url", "", "Authentication endpoint")
|
||||
flagSet.String("redeem-url", "", "Token redemption endpoint")
|
||||
flagSet.String("profile-url", "", "Profile access endpoint")
|
||||
@ -644,6 +650,8 @@ func (l *LegacyProvider) convert() (Providers, error) {
|
||||
UserIDClaim: l.UserIDClaim,
|
||||
EmailClaim: l.OIDCEmailClaim,
|
||||
GroupsClaim: l.OIDCGroupsClaim,
|
||||
AudienceClaims: l.OIDCAudienceClaims,
|
||||
ExtraAudiences: l.OIDCExtraAudiences,
|
||||
}
|
||||
|
||||
// This part is out of the switch section because azure has a default tenant
|
||||
|
@ -116,6 +116,8 @@ var _ = Describe("Legacy Options", func() {
|
||||
opts.Providers[0].ClientID = "oauth-proxy"
|
||||
opts.Providers[0].ID = "google=oauth-proxy"
|
||||
opts.Providers[0].OIDCConfig.InsecureSkipNonce = true
|
||||
opts.Providers[0].OIDCConfig.AudienceClaims = []string{"aud"}
|
||||
opts.Providers[0].OIDCConfig.ExtraAudiences = []string{}
|
||||
|
||||
converted, err := legacyOpts.ToOptions()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -42,6 +42,7 @@ var _ = Describe("Load", func() {
|
||||
UserIDClaim: "email",
|
||||
OIDCEmailClaim: "email",
|
||||
OIDCGroupsClaim: "groups",
|
||||
OIDCAudienceClaims: []string{"aud"},
|
||||
InsecureOIDCSkipNonce: true,
|
||||
},
|
||||
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"crypto"
|
||||
"net/url"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@ -70,25 +70,29 @@ type Options struct {
|
||||
redirectURL *url.URL
|
||||
provider providers.Provider
|
||||
signatureData *SignatureData
|
||||
oidcVerifier *oidc.IDTokenVerifier
|
||||
jwtBearerVerifiers []*oidc.IDTokenVerifier
|
||||
oidcVerifier *internaloidc.IDTokenVerifier
|
||||
jwtBearerVerifiers []*internaloidc.IDTokenVerifier
|
||||
realClientIPParser ipapi.RealClientIPParser
|
||||
}
|
||||
|
||||
// Options for Getting internal values
|
||||
func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
|
||||
func (o *Options) GetProvider() providers.Provider { return o.provider }
|
||||
func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
|
||||
func (o *Options) GetOIDCVerifier() *oidc.IDTokenVerifier { return o.oidcVerifier }
|
||||
func (o *Options) GetJWTBearerVerifiers() []*oidc.IDTokenVerifier { return o.jwtBearerVerifiers }
|
||||
func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
|
||||
func (o *Options) GetProvider() providers.Provider { return o.provider }
|
||||
func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
|
||||
func (o *Options) GetOIDCVerifier() *internaloidc.IDTokenVerifier { return o.oidcVerifier }
|
||||
func (o *Options) GetJWTBearerVerifiers() []*internaloidc.IDTokenVerifier {
|
||||
return o.jwtBearerVerifiers
|
||||
}
|
||||
func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.realClientIPParser }
|
||||
|
||||
// Options for Setting internal values
|
||||
func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
|
||||
func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
|
||||
func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
|
||||
func (o *Options) SetOIDCVerifier(s *oidc.IDTokenVerifier) { o.oidcVerifier = s }
|
||||
func (o *Options) SetJWTBearerVerifiers(s []*oidc.IDTokenVerifier) { o.jwtBearerVerifiers = s }
|
||||
func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
|
||||
func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
|
||||
func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
|
||||
func (o *Options) SetOIDCVerifier(s *internaloidc.IDTokenVerifier) { o.oidcVerifier = s }
|
||||
func (o *Options) SetJWTBearerVerifiers(s []*internaloidc.IDTokenVerifier) {
|
||||
o.jwtBearerVerifiers = s
|
||||
}
|
||||
func (o *Options) SetRealClientIPParser(s ipapi.RealClientIPParser) { o.realClientIPParser = s }
|
||||
|
||||
// NewOptions constructs a new Options with defaulted values
|
||||
|
@ -164,6 +164,12 @@ type OIDCOptions struct {
|
||||
// UserIDClaim indicates which claim contains the user ID
|
||||
// default set to 'email'
|
||||
UserIDClaim string `json:"userIDClaim,omitempty"`
|
||||
// AudienceClaim allows to define any claim that is verified against the client id
|
||||
// By default `aud` claim is used for verification.
|
||||
AudienceClaims []string `json:"audienceClaims,omitempty"`
|
||||
// ExtraAudiences is a list of additional audiences that are allowed
|
||||
// to pass verification in addition to the client id.
|
||||
ExtraAudiences []string `json:"extraAudiences,omitempty"`
|
||||
}
|
||||
|
||||
type LoginGovOptions struct {
|
||||
@ -191,6 +197,8 @@ func providerDefaults() Providers {
|
||||
UserIDClaim: providers.OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
|
||||
EmailClaim: providers.OIDCEmailClaim,
|
||||
GroupsClaim: providers.OIDCGroupsClaim,
|
||||
AudienceClaims: providers.OIDCAudienceClaims,
|
||||
ExtraAudiences: []string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
16
pkg/oidc/oidc_suite_test.go
Executable file
16
pkg/oidc/oidc_suite_test.go
Executable file
@ -0,0 +1,16 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestOIDCSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "OIDC")
|
||||
}
|
100
pkg/oidc/verify.go
Executable file
100
pkg/oidc/verify.go
Executable file
@ -0,0 +1,100 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
)
|
||||
|
||||
// IDTokenVerifier Used to verify an ID Token and extends oidc.IDTokenVerifier from the underlying oidc library
|
||||
type IDTokenVerifier struct {
|
||||
*oidc.IDTokenVerifier
|
||||
*IDTokenVerificationOptions
|
||||
allowedAudiences map[string]struct{}
|
||||
}
|
||||
|
||||
// IDTokenVerificationOptions options for the oidc.IDTokenVerifier that are required to verify an ID Token
|
||||
type IDTokenVerificationOptions struct {
|
||||
AudienceClaims []string
|
||||
ClientID string
|
||||
ExtraAudiences []string
|
||||
}
|
||||
|
||||
// NewVerifier constructs a new IDTokenVerifier
|
||||
func NewVerifier(iv *oidc.IDTokenVerifier, vo *IDTokenVerificationOptions) *IDTokenVerifier {
|
||||
allowedAudiences := make(map[string]struct{})
|
||||
allowedAudiences[vo.ClientID] = struct{}{}
|
||||
for _, extraAudience := range vo.ExtraAudiences {
|
||||
allowedAudiences[extraAudience] = struct{}{}
|
||||
}
|
||||
return &IDTokenVerifier{iv, vo, allowedAudiences}
|
||||
}
|
||||
|
||||
// Verify verifies incoming ID Token
|
||||
func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) {
|
||||
token, err := v.IDTokenVerifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to verify token: %v", err)
|
||||
}
|
||||
|
||||
claims := map[string]interface{}{}
|
||||
if err := token.Claims(&claims); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse default id_token claims: %v", err)
|
||||
}
|
||||
|
||||
if isValidAudience, err := v.verifyAudience(token, claims); !isValidAudience {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, err
|
||||
}
|
||||
|
||||
func (v *IDTokenVerifier) verifyAudience(token *oidc.IDToken, claims map[string]interface{}) (bool, error) {
|
||||
for _, audienceClaim := range v.AudienceClaims {
|
||||
if audienceClaimValue, audienceClaimExists := claims[audienceClaim]; audienceClaimExists {
|
||||
|
||||
// audience claim value can be either interface{} or []interface{},
|
||||
// as per spec `aud` can be either a string or a list of strings
|
||||
switch audienceClaimValueType := audienceClaimValue.(type) {
|
||||
case []interface{}:
|
||||
token.Audience = v.interfaceSliceToString(audienceClaimValue)
|
||||
case interface{}:
|
||||
token.Audience = []string{audienceClaimValue.(string)}
|
||||
default:
|
||||
return false, fmt.Errorf("audience claim %s holds unsupported type %T",
|
||||
audienceClaim, audienceClaimValueType)
|
||||
}
|
||||
|
||||
return v.isValidAudience(audienceClaim, token.Audience, v.allowedAudiences)
|
||||
}
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("audience claims %v do not exist in claims: %v",
|
||||
v.AudienceClaims, claims)
|
||||
}
|
||||
|
||||
func (v *IDTokenVerifier) isValidAudience(claim string, audience []string, allowedAudiences map[string]struct{}) (bool, error) {
|
||||
for _, aud := range audience {
|
||||
if _, allowedAudienceExists := allowedAudiences[aud]; allowedAudienceExists {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, fmt.Errorf(
|
||||
"audience from claim %s with value %s does not match with any of allowed audiences %v",
|
||||
claim, audience, allowedAudiences)
|
||||
}
|
||||
|
||||
func (v *IDTokenVerifier) interfaceSliceToString(slice interface{}) []string {
|
||||
s := reflect.ValueOf(slice)
|
||||
if s.Kind() != reflect.Slice {
|
||||
panic(fmt.Sprintf("given a non-slice type %s", s.Kind()))
|
||||
}
|
||||
var strings []string
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
strings = append(strings, s.Index(i).Interface().(string))
|
||||
}
|
||||
return strings
|
||||
}
|
249
pkg/oidc/verify_test.go
Executable file
249
pkg/oidc/verify_test.go
Executable file
@ -0,0 +1,249 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
var _ = Describe("Verify", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
It("Succeeds with default aud behavior", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "1226737",
|
||||
ExtraAudiences: []string{},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
Aud: "1226737",
|
||||
})
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(result.Issuer).To(Equal("https://foo"))
|
||||
Expect(result.Audience).To(Equal([]string{"1226737"}))
|
||||
})
|
||||
|
||||
It("Fails with default aud behavior", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
Aud: "1226737",
|
||||
})
|
||||
Expect(err).To(MatchError("audience from claim aud with value [1226737] does not match with " +
|
||||
"any of allowed audiences map[7817818:{}]"))
|
||||
Expect(result).To(BeNil())
|
||||
})
|
||||
|
||||
It("Succeeds with extra audiences", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "1226737"},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
Aud: "1226737",
|
||||
})
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(result.Issuer).To(Equal("https://foo"))
|
||||
Expect(result.Audience).To(Equal([]string{"1226737"}))
|
||||
})
|
||||
|
||||
It("Fails with extra audiences", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "abc"},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
Aud: "1226737",
|
||||
})
|
||||
|
||||
Expect(err).To(MatchError("audience from claim aud with value [1226737] does not match with any " +
|
||||
"of allowed audiences map[7817818:{} abc:{} xyz:{}]"))
|
||||
Expect(result).To(BeNil())
|
||||
})
|
||||
|
||||
It("Succeeds with non default aud behavior", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id"},
|
||||
ClientID: "1226737",
|
||||
ExtraAudiences: []string{},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
ClientID: "1226737",
|
||||
})
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(result.Issuer).To(Equal("https://foo"))
|
||||
Expect(result.Audience).To(Equal([]string{"1226737"}))
|
||||
})
|
||||
|
||||
It("Fails with non default aud behavior", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
ClientID: "1226737",
|
||||
})
|
||||
Expect(err).To(MatchError("audience from claim client_id with value [1226737] does not match with " +
|
||||
"any of allowed audiences map[7817818:{}]"))
|
||||
Expect(result).To(BeNil())
|
||||
})
|
||||
|
||||
It("Succeeds with non default aud behavior and extra audiences", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "1226737"},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
ClientID: "1226737",
|
||||
})
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(result.Issuer).To(Equal("https://foo"))
|
||||
Expect(result.Audience).To(Equal([]string{"1226737"}))
|
||||
})
|
||||
|
||||
It("Fails with non default aud behavior and extra audiences", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "abc"},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
ClientID: "1226737",
|
||||
})
|
||||
|
||||
Expect(err).To(MatchError("audience from claim client_id with value [1226737] does not match with any " +
|
||||
"of allowed audiences map[7817818:{} abc:{} xyz:{}]"))
|
||||
Expect(result).To(BeNil())
|
||||
})
|
||||
|
||||
It("Fails if audience claim does not exist", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"not_exists"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "abc"},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
ClientID: "1226737",
|
||||
Aud: "1226737",
|
||||
})
|
||||
|
||||
Expect(err).To(MatchError("audience claims [not_exists] do not exist in claims: " +
|
||||
"map[aud:1226737 client_id:1226737 iss:https://foo]"))
|
||||
Expect(result).To(BeNil())
|
||||
})
|
||||
|
||||
It("Succeeds with multiple audiences", func() {
|
||||
var result, err = verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id", "aud"},
|
||||
ClientID: "123456789",
|
||||
ExtraAudiences: []string{"1226737"},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
ClientID: "123456789",
|
||||
Aud: []string{"1226737", "123456789"},
|
||||
})
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(result.Issuer).To(Equal("https://foo"))
|
||||
Expect(result.Audience).To(Equal([]string{"123456789"}))
|
||||
})
|
||||
|
||||
It("Succeeds if aud claim match", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id", "aud"},
|
||||
ClientID: "1226737",
|
||||
ExtraAudiences: []string{"xyz", "abc"},
|
||||
}, payload{
|
||||
Iss: "https://foo",
|
||||
ClientID: "1226737",
|
||||
Aud: "1226737",
|
||||
})
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(result.Issuer).To(Equal("https://foo"))
|
||||
Expect(result.Audience).To(Equal([]string{"1226737"}))
|
||||
})
|
||||
})
|
||||
|
||||
type payload struct {
|
||||
Iss string `json:"iss,omitempty"`
|
||||
Aud interface{} `json:"aud,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
}
|
||||
|
||||
type jwtToken struct {
|
||||
PrivateKey jose.JSONWebKey
|
||||
PublicKey jose.JSONWebKey
|
||||
Token string
|
||||
}
|
||||
|
||||
type testVerifier struct {
|
||||
jwk jose.JSONWebKey
|
||||
}
|
||||
|
||||
func (t *testVerifier) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
|
||||
jws, err := jose.ParseSigned(jwt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||
}
|
||||
return jws.Verify(&t.jwk)
|
||||
}
|
||||
|
||||
func verify(ctx context.Context, verificationOptions *IDTokenVerificationOptions, payload payload) (*oidc.IDToken, error) {
|
||||
config := &oidc.Config{
|
||||
ClientID: "1226737",
|
||||
SkipClientIDCheck: true,
|
||||
SkipExpiryCheck: true, // required to not run in expired Token error during testing
|
||||
}
|
||||
rawToken, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, _ := createToken(rawToken)
|
||||
verifier := NewVerifier(oidc.NewVerifier("https://foo", &testVerifier{jwk: token.PublicKey}, config), verificationOptions)
|
||||
return verifier.Verify(ctx, token.Token)
|
||||
}
|
||||
|
||||
func createToken(payload []byte) (*jwtToken, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 1028)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateWebKey := jose.JSONWebKey{Key: privateKey, Algorithm: string(jose.RS256), KeyID: ""}
|
||||
publicWebKey := jose.JSONWebKey{Key: privateKey.Public(), Use: "sig", Algorithm: string(jose.RS256), KeyID: ""}
|
||||
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: privateWebKey}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jws, err := signer.Sign(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := jws.CompactSerialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &jwtToken{
|
||||
PrivateKey: privateWebKey,
|
||||
PublicKey: publicWebKey,
|
||||
Token: data,
|
||||
}, nil
|
||||
}
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
|
||||
@ -102,6 +103,18 @@ func Validate(o *options.Options) error {
|
||||
}
|
||||
}
|
||||
|
||||
config := &oidc.Config{
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
|
||||
SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
|
||||
}
|
||||
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: o.Providers[0].OIDCConfig.AudienceClaims,
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
ExtraAudiences: o.Providers[0].OIDCConfig.ExtraAudiences,
|
||||
}
|
||||
|
||||
// Construct a manual IDTokenVerifier from issuer URL & JWKS URI
|
||||
// instead of metadata discovery if we enable -skip-oidc-discovery.
|
||||
// In this case we need to make sure the required endpoints for
|
||||
@ -117,21 +130,16 @@ func Validate(o *options.Options) error {
|
||||
msgs = append(msgs, "missing setting: oidc-jwks-url")
|
||||
}
|
||||
keySet := oidc.NewRemoteKeySet(ctx, o.Providers[0].OIDCConfig.JwksURL)
|
||||
o.SetOIDCVerifier(oidc.NewVerifier(o.Providers[0].OIDCConfig.IssuerURL, keySet, &oidc.Config{
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
|
||||
}))
|
||||
o.SetOIDCVerifier(internaloidc.NewVerifier(
|
||||
oidc.NewVerifier(o.Providers[0].OIDCConfig.IssuerURL, keySet, config), verificationOptions))
|
||||
} else {
|
||||
// Configure discoverable provider data.
|
||||
provider, err := oidc.NewProvider(ctx, o.Providers[0].OIDCConfig.IssuerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
|
||||
}))
|
||||
|
||||
o.SetOIDCVerifier(internaloidc.NewVerifier(provider.Verifier(config), verificationOptions))
|
||||
o.Providers[0].LoginURL = provider.Endpoint().AuthURL
|
||||
o.Providers[0].RedeemURL = provider.Endpoint().TokenURL
|
||||
}
|
||||
@ -153,7 +161,11 @@ func Validate(o *options.Options) error {
|
||||
var jwtIssuers []jwtIssuer
|
||||
jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
|
||||
for _, jwtIssuer := range jwtIssuers {
|
||||
verifier, err := newVerifierFromJwtIssuer(jwtIssuer)
|
||||
verifier, err := newVerifierFromJwtIssuer(
|
||||
o.Providers[0].OIDCConfig.AudienceClaims,
|
||||
o.Providers[0].OIDCConfig.ExtraAudiences,
|
||||
jwtIssuer,
|
||||
)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
|
||||
}
|
||||
@ -290,9 +302,14 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
|
||||
if err != nil {
|
||||
msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
|
||||
} else {
|
||||
p.Verifier = provider.Verifier(&oidc.Config{
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: o.Providers[0].OIDCConfig.AudienceClaims,
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
ExtraAudiences: o.Providers[0].OIDCConfig.ExtraAudiences,
|
||||
}
|
||||
p.Verifier = internaloidc.NewVerifier(provider.Verifier(&oidc.Config{
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
})
|
||||
}), verificationOptions)
|
||||
|
||||
p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
|
||||
p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
|
||||
@ -372,9 +389,10 @@ func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
|
||||
|
||||
// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
|
||||
// a verifier for that issuer.
|
||||
func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) {
|
||||
func newVerifierFromJwtIssuer(audienceClaims []string, extraAudiences []string, jwtIssuer jwtIssuer) (*internaloidc.IDTokenVerifier, error) {
|
||||
config := &oidc.Config{
|
||||
ClientID: jwtIssuer.audience,
|
||||
ClientID: jwtIssuer.audience,
|
||||
SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
|
||||
}
|
||||
// Try as an OpenID Connect Provider first
|
||||
var verifier *oidc.IDTokenVerifier
|
||||
@ -390,7 +408,13 @@ func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error
|
||||
} else {
|
||||
verifier = provider.Verifier(config)
|
||||
}
|
||||
return verifier, nil
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: audienceClaims,
|
||||
ClientID: jwtIssuer.audience,
|
||||
ExtraAudiences: extraAudiences,
|
||||
// ExtraAudiences: o.Providers[0].OIDCConfig.ExtraAudiences,
|
||||
}
|
||||
return internaloidc.NewVerifier(verifier, verificationOptions), nil
|
||||
}
|
||||
|
||||
// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
|
||||
|
10
providers/adfs_test.go
Normal file → Executable file
10
providers/adfs_test.go
Normal file → Executable file
@ -14,6 +14,7 @@ import (
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -41,11 +42,16 @@ func newSignedTestADFSToken(tokenClaims adfsClaims) (string, error) {
|
||||
}
|
||||
|
||||
func testADFSProvider(hostname string) *ADFSProvider {
|
||||
o := oidc.NewVerifier(
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "https://test.myapp.com",
|
||||
}
|
||||
|
||||
o := internaloidc.NewVerifier(oidc.NewVerifier(
|
||||
"https://issuer.example.com",
|
||||
fakeADFSJwks{},
|
||||
&oidc.Config{ClientID: "https://test.myapp.com"},
|
||||
)
|
||||
), verificationOptions)
|
||||
|
||||
p := NewADFSProvider(&ProviderData{
|
||||
ProviderName: "",
|
||||
|
@ -13,8 +13,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/golang-jwt/jwt"
|
||||
|
||||
oidc "github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -38,6 +41,10 @@ type azureOAuthPayload struct {
|
||||
}
|
||||
|
||||
func testAzureProvider(hostname string) *AzureProvider {
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532",
|
||||
}
|
||||
p := NewAzureProvider(
|
||||
&ProviderData{
|
||||
ProviderName: "",
|
||||
@ -48,7 +55,7 @@ func testAzureProvider(hostname string) *AzureProvider {
|
||||
ProtectedResource: &url.URL{},
|
||||
Scope: "",
|
||||
EmailClaim: "email",
|
||||
Verifier: oidc.NewVerifier(
|
||||
Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
|
||||
"https://issuer.example.com",
|
||||
fakeAzureKeySetStub{},
|
||||
&oidc.Config{
|
||||
@ -57,7 +64,7 @@ func testAzureProvider(hostname string) *AzureProvider {
|
||||
SkipIssuerCheck: true,
|
||||
SkipExpiryCheck: true,
|
||||
},
|
||||
),
|
||||
), verificationOptions),
|
||||
})
|
||||
|
||||
if hostname != "" {
|
||||
@ -285,13 +292,17 @@ func TestAzureProviderRedeem(t *testing.T) {
|
||||
accessTokenString := ""
|
||||
if testCase.EmailFromIDToken != "" {
|
||||
var err error
|
||||
token := idTokenClaims{Email: testCase.EmailFromIDToken}
|
||||
token := idTokenClaims{
|
||||
StandardClaims: jwt.StandardClaims{Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"},
|
||||
Email: testCase.EmailFromIDToken}
|
||||
idTokenString, err = newSignedTestIDToken(token)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if testCase.EmailFromAccessToken != "" {
|
||||
var err error
|
||||
token := idTokenClaims{Email: testCase.EmailFromAccessToken}
|
||||
token := idTokenClaims{
|
||||
StandardClaims: jwt.StandardClaims{Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"},
|
||||
Email: testCase.EmailFromAccessToken}
|
||||
accessTokenString, err = newSignedTestIDToken(token)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -342,7 +353,9 @@ func TestAzureProviderProtectedResourceConfigured(t *testing.T) {
|
||||
|
||||
func TestAzureProviderRefresh(t *testing.T) {
|
||||
email := "foo@example.com"
|
||||
idToken := idTokenClaims{Email: email}
|
||||
idToken := idTokenClaims{
|
||||
StandardClaims: jwt.StandardClaims{Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"},
|
||||
Email: email}
|
||||
idTokenString, err := newSignedTestIDToken(idToken)
|
||||
assert.NoError(t, err)
|
||||
timestamp, err := time.Parse(time.RFC3339, "3006-01-02T22:04:05Z")
|
||||
|
@ -12,14 +12,20 @@ import (
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
accessTokenHeader = "ewogICJhbGciOiAiUlMyNTYiLAogICJ0eXAiOiAiSldUIgp9"
|
||||
accessTokenPayload = "eyJyZWFsbV9hY2Nlc3MiOiB7InJvbGVzIjogWyJ3cml0ZSJdfSwgInJlc291cmNlX2FjY2VzcyI6IHsiZGVmYXVsdCI6IHsicm9sZXMiOiBbInJlYWQiXX19fQ"
|
||||
accessTokenSignature = "dyt0CoTl4WoVjAHI9Q_CwSKhl6d_9rhM3NrXuJttkao"
|
||||
defaultAudienceClaim = "aud"
|
||||
mockClientID = "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"
|
||||
)
|
||||
|
||||
var accessTokenPayload = base64.StdEncoding.EncodeToString([]byte(
|
||||
fmt.Sprintf(`{"%s": "%s", "realm_access": {"roles": ["write"]}, "resource_access": {"default": {"roles": ["read"]}}}`, defaultAudienceClaim, mockClientID)))
|
||||
|
||||
type DummyKeySet struct{}
|
||||
|
||||
func (DummyKeySet) VerifySignature(_ context.Context, _ string) (payload []byte, err error) {
|
||||
@ -38,6 +44,10 @@ func newTestKeycloakOIDCSetup() (*httptest.Server, *KeycloakOIDCProvider) {
|
||||
}
|
||||
|
||||
func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{defaultAudienceClaim},
|
||||
ClientID: mockClientID,
|
||||
}
|
||||
p := NewKeycloakOIDCProvider(
|
||||
&ProviderData{
|
||||
LoginURL: &url.URL{
|
||||
@ -64,12 +74,12 @@ func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
|
||||
}
|
||||
|
||||
keyset := DummyKeySet{}
|
||||
p.Verifier = oidc.NewVerifier("", keyset, &oidc.Config{
|
||||
p.Verifier = internaloidc.NewVerifier(oidc.NewVerifier("", keyset, &oidc.Config{
|
||||
ClientID: "client",
|
||||
SkipIssuerCheck: true,
|
||||
SkipClientIDCheck: true,
|
||||
SkipExpiryCheck: true,
|
||||
})
|
||||
}), verificationOptions)
|
||||
p.EmailClaim = "email"
|
||||
p.GroupsClaim = "groups"
|
||||
return p
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -26,6 +27,10 @@ type redeemTokenResponse struct {
|
||||
}
|
||||
|
||||
func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "https://test.myapp.com",
|
||||
}
|
||||
providerData := &ProviderData{
|
||||
ProviderName: "oidc",
|
||||
ClientID: oidcClientID,
|
||||
@ -49,11 +54,11 @@ func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
|
||||
Scope: "openid profile offline_access",
|
||||
EmailClaim: "email",
|
||||
GroupsClaim: "groups",
|
||||
Verifier: oidc.NewVerifier(
|
||||
Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
|
||||
oidcIssuer,
|
||||
mockJWKS{},
|
||||
&oidc.Config{ClientID: oidcClientID},
|
||||
),
|
||||
), verificationOptions),
|
||||
}
|
||||
|
||||
p := NewOIDCProvider(providerData)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
@ -20,6 +21,8 @@ const (
|
||||
OIDCGroupsClaim = "groups"
|
||||
)
|
||||
|
||||
var OIDCAudienceClaims = []string{"aud"}
|
||||
|
||||
// ProviderData contains information required to configure all implementations
|
||||
// of OAuth2 providers
|
||||
type ProviderData struct {
|
||||
@ -44,7 +47,7 @@ type ProviderData struct {
|
||||
UserClaim string
|
||||
EmailClaim string
|
||||
GroupsClaim string
|
||||
Verifier *oidc.IDTokenVerifier
|
||||
Verifier *internaloidc.IDTokenVerifier
|
||||
|
||||
// Universal Group authorization data structure
|
||||
// any provider can set to consume
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@ -155,7 +156,8 @@ func TestProviderData_verifyIDToken(t *testing.T) {
|
||||
IDToken: &failureIDToken,
|
||||
Verifier: true,
|
||||
ExpectIDToken: false,
|
||||
ExpectedError: errors.New("failed to verify signature: the validation failed for subject [123456789]"),
|
||||
ExpectedError: errors.New("failed to verify token: failed to verify signature: " +
|
||||
"the validation failed for subject [123456789]"),
|
||||
},
|
||||
"Missing ID Token": {
|
||||
IDToken: nil,
|
||||
@ -186,11 +188,15 @@ func TestProviderData_verifyIDToken(t *testing.T) {
|
||||
|
||||
provider := &ProviderData{}
|
||||
if tc.Verifier {
|
||||
provider.Verifier = oidc.NewVerifier(
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: oidcClientID,
|
||||
}
|
||||
provider.Verifier = internaloidc.NewVerifier(oidc.NewVerifier(
|
||||
oidcIssuer,
|
||||
mockJWKS{},
|
||||
&oidc.Config{ClientID: oidcClientID},
|
||||
)
|
||||
), verificationOptions)
|
||||
}
|
||||
verified, err := provider.verifyIDToken(context.Background(), token)
|
||||
if err != nil {
|
||||
@ -346,12 +352,16 @@ func TestProviderData_buildSessionFromClaims(t *testing.T) {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: oidcClientID,
|
||||
}
|
||||
provider := &ProviderData{
|
||||
Verifier: oidc.NewVerifier(
|
||||
Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
|
||||
oidcIssuer,
|
||||
mockJWKS{},
|
||||
&oidc.Config{ClientID: oidcClientID},
|
||||
),
|
||||
), verificationOptions),
|
||||
}
|
||||
provider.AllowUnverifiedEmail = tc.AllowUnverified
|
||||
provider.UserClaim = tc.UserClaim
|
||||
@ -408,12 +418,16 @@ func TestProviderData_checkNonce(t *testing.T) {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: oidcClientID,
|
||||
}
|
||||
provider := &ProviderData{
|
||||
Verifier: oidc.NewVerifier(
|
||||
Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
|
||||
oidcIssuer,
|
||||
mockJWKS{},
|
||||
&oidc.Config{ClientID: oidcClientID},
|
||||
),
|
||||
), verificationOptions),
|
||||
}
|
||||
|
||||
rawIDToken, err := newSignedTestIDToken(tc.IDToken)
|
||||
@ -501,12 +515,16 @@ func TestProviderData_extractGroups(t *testing.T) {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: oidcClientID,
|
||||
}
|
||||
provider := &ProviderData{
|
||||
Verifier: oidc.NewVerifier(
|
||||
Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
|
||||
oidcIssuer,
|
||||
mockJWKS{},
|
||||
&oidc.Config{ClientID: oidcClientID},
|
||||
),
|
||||
), verificationOptions),
|
||||
}
|
||||
provider.GroupsClaim = tc.GroupsClaim
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user