mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	support multiple audiences with jwt authenticator
Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com>
This commit is contained in:
		@@ -143,21 +143,23 @@ func validateAudiences(audiences []string, audienceMatchPolicy api.AudienceMatch
 | 
				
			|||||||
		allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf(atLeastOneRequiredErrFmt, fldPath)))
 | 
							allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf(atLeastOneRequiredErrFmt, fldPath)))
 | 
				
			||||||
		return allErrs
 | 
							return allErrs
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// This stricter validation is because the --oidc-client-id flag option is singular.
 | 
					 | 
				
			||||||
	// This will be removed when we support multiple audiences with the StructuredAuthenticationConfiguration feature gate.
 | 
					 | 
				
			||||||
	if len(audiences) > 1 {
 | 
					 | 
				
			||||||
		allErrs = append(allErrs, field.TooMany(fldPath, len(audiences), 1))
 | 
					 | 
				
			||||||
		return allErrs
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						seenAudiences := sets.NewString()
 | 
				
			||||||
	for i, audience := range audiences {
 | 
						for i, audience := range audiences {
 | 
				
			||||||
		fldPath := fldPath.Index(i)
 | 
							fldPath := fldPath.Index(i)
 | 
				
			||||||
		if len(audience) == 0 {
 | 
							if len(audience) == 0 {
 | 
				
			||||||
			allErrs = append(allErrs, field.Required(fldPath, "audience can't be empty"))
 | 
								allErrs = append(allErrs, field.Required(fldPath, "audience can't be empty"))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if seenAudiences.Has(audience) {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Duplicate(fldPath, audience))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							seenAudiences.Insert(audience)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(audienceMatchPolicy) > 0 && audienceMatchPolicy != api.AudienceMatchPolicyMatchAny {
 | 
						if len(audiences) > 1 && audienceMatchPolicy != api.AudienceMatchPolicyMatchAny {
 | 
				
			||||||
 | 
							allErrs = append(allErrs, field.Invalid(audienceMatchPolicyFldPath, audienceMatchPolicy, "audienceMatchPolicy must be MatchAny for multiple audiences"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(audiences) == 1 && (len(audienceMatchPolicy) > 0 && audienceMatchPolicy != api.AudienceMatchPolicyMatchAny) {
 | 
				
			||||||
		allErrs = append(allErrs, field.Invalid(audienceMatchPolicyFldPath, audienceMatchPolicy, "audienceMatchPolicy must be empty or MatchAny for single audience"))
 | 
							allErrs = append(allErrs, field.Invalid(audienceMatchPolicyFldPath, audienceMatchPolicy, "audienceMatchPolicy must be empty or MatchAny for single audience"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -304,6 +304,23 @@ func TestValidateAudiences(t *testing.T) {
 | 
				
			|||||||
			matchPolicy: "MatchAny",
 | 
								matchPolicy: "MatchAny",
 | 
				
			||||||
			want:        "",
 | 
								want:        "",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:        "duplicate audience",
 | 
				
			||||||
 | 
								in:          []string{"audience", "audience"},
 | 
				
			||||||
 | 
								matchPolicy: "MatchAny",
 | 
				
			||||||
 | 
								want:        `issuer.audiences[1]: Duplicate value: "audience"`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "match policy not set with multiple audiences",
 | 
				
			||||||
 | 
								in:   []string{"audience1", "audience2"},
 | 
				
			||||||
 | 
								want: `issuer.audienceMatchPolicy: Invalid value: "": audienceMatchPolicy must be MatchAny for multiple audiences`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:        "valid multiple audiences",
 | 
				
			||||||
 | 
								in:          []string{"audience1", "audience2"},
 | 
				
			||||||
 | 
								matchPolicy: "MatchAny",
 | 
				
			||||||
 | 
								want:        "",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tt := range testCases {
 | 
						for _, tt := range testCases {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,7 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
						"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/net"
 | 
						"k8s.io/apimachinery/pkg/util/net"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/sets"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/apis/apiserver"
 | 
						"k8s.io/apiserver/pkg/apis/apiserver"
 | 
				
			||||||
	apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
 | 
						apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
 | 
				
			||||||
@@ -99,12 +100,12 @@ type CAContentProvider interface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// initVerifier creates a new ID token verifier for the given configuration and issuer URL.  On success, calls setVerifier with the
 | 
					// initVerifier creates a new ID token verifier for the given configuration and issuer URL.  On success, calls setVerifier with the
 | 
				
			||||||
// resulting verifier.
 | 
					// resulting verifier.
 | 
				
			||||||
func initVerifier(ctx context.Context, config *oidc.Config, iss string) (*oidc.IDTokenVerifier, error) {
 | 
					func initVerifier(ctx context.Context, config *oidc.Config, iss string, audiences sets.Set[string]) (*idTokenVerifier, error) {
 | 
				
			||||||
	provider, err := oidc.NewProvider(ctx, iss)
 | 
						provider, err := oidc.NewProvider(ctx, iss)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("init verifier failed: %v", err)
 | 
							return nil, fmt.Errorf("init verifier failed: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return provider.Verifier(config), nil
 | 
						return &idTokenVerifier{provider.Verifier(config), audiences}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// asyncIDTokenVerifier is an ID token verifier that allows async initialization
 | 
					// asyncIDTokenVerifier is an ID token verifier that allows async initialization
 | 
				
			||||||
@@ -115,13 +116,13 @@ type asyncIDTokenVerifier struct {
 | 
				
			|||||||
	// v is the ID token verifier initialized asynchronously.  It remains nil
 | 
						// v is the ID token verifier initialized asynchronously.  It remains nil
 | 
				
			||||||
	// up until it is eventually initialized.
 | 
						// up until it is eventually initialized.
 | 
				
			||||||
	// Guarded by m
 | 
						// Guarded by m
 | 
				
			||||||
	v *oidc.IDTokenVerifier
 | 
						v *idTokenVerifier
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// newAsyncIDTokenVerifier creates a new asynchronous token verifier.  The
 | 
					// newAsyncIDTokenVerifier creates a new asynchronous token verifier.  The
 | 
				
			||||||
// verifier is available immediately, but may remain uninitialized for some time
 | 
					// verifier is available immediately, but may remain uninitialized for some time
 | 
				
			||||||
// after creation.
 | 
					// after creation.
 | 
				
			||||||
func newAsyncIDTokenVerifier(ctx context.Context, c *oidc.Config, iss string) *asyncIDTokenVerifier {
 | 
					func newAsyncIDTokenVerifier(ctx context.Context, c *oidc.Config, iss string, audiences sets.Set[string]) *asyncIDTokenVerifier {
 | 
				
			||||||
	t := &asyncIDTokenVerifier{}
 | 
						t := &asyncIDTokenVerifier{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sync := make(chan struct{})
 | 
						sync := make(chan struct{})
 | 
				
			||||||
@@ -129,7 +130,7 @@ func newAsyncIDTokenVerifier(ctx context.Context, c *oidc.Config, iss string) *a
 | 
				
			|||||||
	// verifier, or until context canceled.
 | 
						// verifier, or until context canceled.
 | 
				
			||||||
	initFn := func() (done bool, err error) {
 | 
						initFn := func() (done bool, err error) {
 | 
				
			||||||
		klog.V(4).Infof("oidc authenticator: attempting init: iss=%v", iss)
 | 
							klog.V(4).Infof("oidc authenticator: attempting init: iss=%v", iss)
 | 
				
			||||||
		v, err := initVerifier(ctx, c, iss)
 | 
							v, err := initVerifier(ctx, c, iss, audiences)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			klog.Errorf("oidc authenticator: async token verifier for issuer: %q: %v", iss, err)
 | 
								klog.Errorf("oidc authenticator: async token verifier for issuer: %q: %v", iss, err)
 | 
				
			||||||
			return false, nil
 | 
								return false, nil
 | 
				
			||||||
@@ -155,7 +156,7 @@ func newAsyncIDTokenVerifier(ctx context.Context, c *oidc.Config, iss string) *a
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// verifier returns the underlying ID token verifier, or nil if one is not yet initialized.
 | 
					// verifier returns the underlying ID token verifier, or nil if one is not yet initialized.
 | 
				
			||||||
func (a *asyncIDTokenVerifier) verifier() *oidc.IDTokenVerifier {
 | 
					func (a *asyncIDTokenVerifier) verifier() *idTokenVerifier {
 | 
				
			||||||
	a.m.Lock()
 | 
						a.m.Lock()
 | 
				
			||||||
	defer a.m.Unlock()
 | 
						defer a.m.Unlock()
 | 
				
			||||||
	return a.v
 | 
						return a.v
 | 
				
			||||||
@@ -181,13 +182,20 @@ type Authenticator struct {
 | 
				
			|||||||
	requiredClaims map[string]string
 | 
						requiredClaims map[string]string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *Authenticator) setVerifier(v *oidc.IDTokenVerifier) {
 | 
					// idTokenVerifier is a wrapper around oidc.IDTokenVerifier. It uses the oidc.IDTokenVerifier
 | 
				
			||||||
 | 
					// to verify the raw ID token and then performs audience validation locally.
 | 
				
			||||||
 | 
					type idTokenVerifier struct {
 | 
				
			||||||
 | 
						verifier  *oidc.IDTokenVerifier
 | 
				
			||||||
 | 
						audiences sets.Set[string]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Authenticator) setVerifier(v *idTokenVerifier) {
 | 
				
			||||||
	a.verifier.Store(v)
 | 
						a.verifier.Store(v)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *Authenticator) idTokenVerifier() (*oidc.IDTokenVerifier, bool) {
 | 
					func (a *Authenticator) idTokenVerifier() (*idTokenVerifier, bool) {
 | 
				
			||||||
	if v := a.verifier.Load(); v != nil {
 | 
						if v := a.verifier.Load(); v != nil {
 | 
				
			||||||
		return v.(*oidc.IDTokenVerifier), true
 | 
							return v.(*idTokenVerifier), true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil, false
 | 
						return nil, false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -265,16 +273,26 @@ func New(opts Options) (*Authenticator, error) {
 | 
				
			|||||||
		now = time.Now
 | 
							now = time.Now
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						audiences := sets.New[string](opts.JWTAuthenticator.Issuer.Audiences...)
 | 
				
			||||||
	verifierConfig := &oidc.Config{
 | 
						verifierConfig := &oidc.Config{
 | 
				
			||||||
		ClientID:             opts.JWTAuthenticator.Issuer.Audiences[0],
 | 
							ClientID:             opts.JWTAuthenticator.Issuer.Audiences[0],
 | 
				
			||||||
		SupportedSigningAlgs: supportedSigningAlgs,
 | 
							SupportedSigningAlgs: supportedSigningAlgs,
 | 
				
			||||||
		Now:                  now,
 | 
							Now:                  now,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if audiences.Len() > 1 {
 | 
				
			||||||
 | 
							verifierConfig.ClientID = ""
 | 
				
			||||||
 | 
							// SkipClientIDCheck is set to true because we want to support multiple audiences
 | 
				
			||||||
 | 
							// in the authentication configuration.
 | 
				
			||||||
 | 
							// The go oidc library does not support validating
 | 
				
			||||||
 | 
							// multiple audiences, so we have to skip the client ID check and do it ourselves.
 | 
				
			||||||
 | 
							// xref: https://github.com/coreos/go-oidc/issues/397
 | 
				
			||||||
 | 
							verifierConfig.SkipClientIDCheck = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var resolver *claimResolver
 | 
						var resolver *claimResolver
 | 
				
			||||||
	groupsClaim := opts.JWTAuthenticator.ClaimMappings.Groups.Claim
 | 
						groupsClaim := opts.JWTAuthenticator.ClaimMappings.Groups.Claim
 | 
				
			||||||
	if groupsClaim != "" {
 | 
						if groupsClaim != "" {
 | 
				
			||||||
		resolver = newClaimResolver(groupsClaim, client, verifierConfig)
 | 
							resolver = newClaimResolver(groupsClaim, client, verifierConfig, audiences)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	requiredClaims := make(map[string]string)
 | 
						requiredClaims := make(map[string]string)
 | 
				
			||||||
@@ -294,7 +312,10 @@ func New(opts Options) (*Authenticator, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if opts.KeySet != nil {
 | 
						if opts.KeySet != nil {
 | 
				
			||||||
		// We already have a key set, synchronously initialize the verifier.
 | 
							// We already have a key set, synchronously initialize the verifier.
 | 
				
			||||||
		authenticator.setVerifier(oidc.NewVerifier(opts.JWTAuthenticator.Issuer.URL, opts.KeySet, verifierConfig))
 | 
							authenticator.setVerifier(&idTokenVerifier{
 | 
				
			||||||
 | 
								oidc.NewVerifier(opts.JWTAuthenticator.Issuer.URL, opts.KeySet, verifierConfig),
 | 
				
			||||||
 | 
								audiences,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// Asynchronously attempt to initialize the authenticator. This enables
 | 
							// Asynchronously attempt to initialize the authenticator. This enables
 | 
				
			||||||
		// self-hosted providers, providers that run on top of Kubernetes itself.
 | 
							// self-hosted providers, providers that run on top of Kubernetes itself.
 | 
				
			||||||
@@ -306,7 +327,7 @@ func New(opts Options) (*Authenticator, error) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			verifier := provider.Verifier(verifierConfig)
 | 
								verifier := provider.Verifier(verifierConfig)
 | 
				
			||||||
			authenticator.setVerifier(verifier)
 | 
								authenticator.setVerifier(&idTokenVerifier{verifier, audiences})
 | 
				
			||||||
			return true, nil
 | 
								return true, nil
 | 
				
			||||||
		}, ctx.Done())
 | 
							}, ctx.Done())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -374,6 +395,10 @@ type claimResolver struct {
 | 
				
			|||||||
	// claim is the distributed claim that may be resolved.
 | 
						// claim is the distributed claim that may be resolved.
 | 
				
			||||||
	claim string
 | 
						claim string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// audiences is the set of acceptable audiences the JWT must be issued to.
 | 
				
			||||||
 | 
						// At least one of the entries must match the "aud" claim in presented JWTs.
 | 
				
			||||||
 | 
						audiences sets.Set[string]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// client is the to use for resolving distributed claims
 | 
						// client is the to use for resolving distributed claims
 | 
				
			||||||
	client *http.Client
 | 
						client *http.Client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -390,19 +415,25 @@ type claimResolver struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// newClaimResolver creates a new resolver for distributed claims.
 | 
					// newClaimResolver creates a new resolver for distributed claims.
 | 
				
			||||||
func newClaimResolver(claim string, client *http.Client, config *oidc.Config) *claimResolver {
 | 
					func newClaimResolver(claim string, client *http.Client, config *oidc.Config, audiences sets.Set[string]) *claimResolver {
 | 
				
			||||||
	return &claimResolver{claim: claim, client: client, config: config, verifierPerIssuer: map[string]*asyncIDTokenVerifier{}}
 | 
						return &claimResolver{
 | 
				
			||||||
 | 
							claim:             claim,
 | 
				
			||||||
 | 
							audiences:         audiences,
 | 
				
			||||||
 | 
							client:            client,
 | 
				
			||||||
 | 
							config:            config,
 | 
				
			||||||
 | 
							verifierPerIssuer: map[string]*asyncIDTokenVerifier{},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Verifier returns either the verifier for the specified issuer, or error.
 | 
					// Verifier returns either the verifier for the specified issuer, or error.
 | 
				
			||||||
func (r *claimResolver) Verifier(iss string) (*oidc.IDTokenVerifier, error) {
 | 
					func (r *claimResolver) Verifier(iss string) (*idTokenVerifier, error) {
 | 
				
			||||||
	r.m.Lock()
 | 
						r.m.Lock()
 | 
				
			||||||
	av := r.verifierPerIssuer[iss]
 | 
						av := r.verifierPerIssuer[iss]
 | 
				
			||||||
	if av == nil {
 | 
						if av == nil {
 | 
				
			||||||
		// This lazy init should normally be very quick.
 | 
							// This lazy init should normally be very quick.
 | 
				
			||||||
		// TODO: Make this context cancelable.
 | 
							// TODO: Make this context cancelable.
 | 
				
			||||||
		ctx := oidc.ClientContext(context.Background(), r.client)
 | 
							ctx := oidc.ClientContext(context.Background(), r.client)
 | 
				
			||||||
		av = newAsyncIDTokenVerifier(ctx, r.config, iss)
 | 
							av = newAsyncIDTokenVerifier(ctx, r.config, iss, r.audiences)
 | 
				
			||||||
		r.verifierPerIssuer[iss] = av
 | 
							r.verifierPerIssuer[iss] = av
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	r.m.Unlock()
 | 
						r.m.Unlock()
 | 
				
			||||||
@@ -520,6 +551,39 @@ func (r *claimResolver) resolve(ctx context.Context, endpoint endpoint, allClaim
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *idTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) {
 | 
				
			||||||
 | 
						t, err := v.verifier.Verify(ctx, rawIDToken)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := v.verifyAudience(t); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return t, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// verifyAudience verifies the audience field in the ID token matches the expected audience.
 | 
				
			||||||
 | 
					// This is added based on https://github.com/coreos/go-oidc/blob/b203e58c24394ddf5e816706a7645f01280245c7/oidc/verify.go#L275-L281
 | 
				
			||||||
 | 
					// with the difference that we allow multiple audiences.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// AuthenticationConfiguration has a audienceMatchPolicy field, but the only supported value now is "MatchAny".
 | 
				
			||||||
 | 
					// So, The default match behavior is to match at least one of the audiences in the ID token.
 | 
				
			||||||
 | 
					func (v *idTokenVerifier) verifyAudience(t *oidc.IDToken) error {
 | 
				
			||||||
 | 
						// We validate audience field is not empty in the authentication configuration.
 | 
				
			||||||
 | 
						// This check ensures callers of "Verify" using idTokenVerifier are not passing
 | 
				
			||||||
 | 
						// an empty audience.
 | 
				
			||||||
 | 
						if v.audiences.Len() == 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("oidc: invalid configuration, audiences cannot be empty")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, aud := range t.Audience {
 | 
				
			||||||
 | 
							if v.audiences.Has(aud) {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Errorf("oidc: expected audience in %q got %q", sets.List(v.audiences), t.Audience)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *Authenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
 | 
					func (a *Authenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
 | 
				
			||||||
	if !hasCorrectIssuer(a.jwtAuthenticator.Issuer.URL, token) {
 | 
						if !hasCorrectIssuer(a.jwtAuthenticator.Issuer.URL, token) {
 | 
				
			||||||
		return nil, false, nil
 | 
							return nil, false, nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1521,6 +1521,70 @@ func TestToken(t *testing.T) {
 | 
				
			|||||||
				Name: "jane",
 | 
									Name: "jane",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multiple-audiences in authentication config",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:                 "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences:           []string{"random-client", "my-client"},
 | 
				
			||||||
 | 
											AudienceMatchPolicy: "MatchAny",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "username",
 | 
				
			||||||
 | 
												Prefix: pointer.String(""),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									now: func() time.Time { return now },
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
 | 
				
			||||||
 | 
								pubKeys: []*jose.JSONWebKey{
 | 
				
			||||||
 | 
									loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								claims: fmt.Sprintf(`{
 | 
				
			||||||
 | 
									"iss": "https://auth.example.com",
 | 
				
			||||||
 | 
									"aud": ["not-my-client", "my-client"],
 | 
				
			||||||
 | 
									"azp": "not-my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multiple-audiences in authentication config, no match",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:                 "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences:           []string{"random-client", "my-client"},
 | 
				
			||||||
 | 
											AudienceMatchPolicy: "MatchAny",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "username",
 | 
				
			||||||
 | 
												Prefix: pointer.String(""),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									now: func() time.Time { return now },
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
 | 
				
			||||||
 | 
								pubKeys: []*jose.JSONWebKey{
 | 
				
			||||||
 | 
									loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								claims: fmt.Sprintf(`{
 | 
				
			||||||
 | 
									"iss": "https://auth.example.com",
 | 
				
			||||||
 | 
									"aud": ["not-my-client"],
 | 
				
			||||||
 | 
									"azp": "not-my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								wantErr: `oidc: verify token: oidc: expected audience in ["my-client" "random-client"] got ["not-my-client"]`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "invalid-issuer",
 | 
								name: "invalid-issuer",
 | 
				
			||||||
			options: Options{
 | 
								options: Options{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -432,6 +432,8 @@ jwt:
 | 
				
			|||||||
    url: %s
 | 
					    url: %s
 | 
				
			||||||
    audiences:
 | 
					    audiences:
 | 
				
			||||||
    - %s
 | 
					    - %s
 | 
				
			||||||
 | 
					    - another-audience
 | 
				
			||||||
 | 
					    audienceMatchPolicy: MatchAny
 | 
				
			||||||
    certificateAuthority: |
 | 
					    certificateAuthority: |
 | 
				
			||||||
        %s
 | 
					        %s
 | 
				
			||||||
  claimMappings:
 | 
					  claimMappings:
 | 
				
			||||||
@@ -475,6 +477,8 @@ jwt:
 | 
				
			|||||||
    url: %s
 | 
					    url: %s
 | 
				
			||||||
    audiences:
 | 
					    audiences:
 | 
				
			||||||
    - %s
 | 
					    - %s
 | 
				
			||||||
 | 
					    - another-audience
 | 
				
			||||||
 | 
					    audienceMatchPolicy: MatchAny
 | 
				
			||||||
    certificateAuthority: |
 | 
					    certificateAuthority: |
 | 
				
			||||||
        %s
 | 
					        %s
 | 
				
			||||||
  claimMappings:
 | 
					  claimMappings:
 | 
				
			||||||
@@ -522,6 +526,8 @@ jwt:
 | 
				
			|||||||
    url: %s
 | 
					    url: %s
 | 
				
			||||||
    audiences:
 | 
					    audiences:
 | 
				
			||||||
    - %s
 | 
					    - %s
 | 
				
			||||||
 | 
					    - another-audience
 | 
				
			||||||
 | 
					    audienceMatchPolicy: MatchAny
 | 
				
			||||||
    certificateAuthority: |
 | 
					    certificateAuthority: |
 | 
				
			||||||
        %s
 | 
					        %s
 | 
				
			||||||
  claimMappings:
 | 
					  claimMappings:
 | 
				
			||||||
@@ -565,6 +571,8 @@ jwt:
 | 
				
			|||||||
    url: %s
 | 
					    url: %s
 | 
				
			||||||
    audiences:
 | 
					    audiences:
 | 
				
			||||||
    - %s
 | 
					    - %s
 | 
				
			||||||
 | 
					    - another-audience
 | 
				
			||||||
 | 
					    audienceMatchPolicy: MatchAny
 | 
				
			||||||
    certificateAuthority: |
 | 
					    certificateAuthority: |
 | 
				
			||||||
        %s
 | 
					        %s
 | 
				
			||||||
  claimMappings:
 | 
					  claimMappings:
 | 
				
			||||||
@@ -621,6 +629,8 @@ jwt:
 | 
				
			|||||||
    url: %s
 | 
					    url: %s
 | 
				
			||||||
    audiences:
 | 
					    audiences:
 | 
				
			||||||
    - %s
 | 
					    - %s
 | 
				
			||||||
 | 
					    - another-audience
 | 
				
			||||||
 | 
					    audienceMatchPolicy: MatchAny
 | 
				
			||||||
    certificateAuthority: |
 | 
					    certificateAuthority: |
 | 
				
			||||||
        %s
 | 
					        %s
 | 
				
			||||||
  claimMappings:
 | 
					  claimMappings:
 | 
				
			||||||
@@ -668,6 +678,8 @@ jwt:
 | 
				
			|||||||
    url: %s
 | 
					    url: %s
 | 
				
			||||||
    audiences:
 | 
					    audiences:
 | 
				
			||||||
    - %s
 | 
					    - %s
 | 
				
			||||||
 | 
					    - another-audience
 | 
				
			||||||
 | 
					    audienceMatchPolicy: MatchAny
 | 
				
			||||||
    certificateAuthority: |
 | 
					    certificateAuthority: |
 | 
				
			||||||
        %s
 | 
					        %s
 | 
				
			||||||
  claimMappings:
 | 
					  claimMappings:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user