k8s.io/apiserver@v0.31.1/plugin/pkg/authenticator/token/oidc/oidc_test.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package oidc
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto"
    23  	"crypto/x509"
    24  	"encoding/hex"
    25  	"encoding/json"
    26  	"encoding/pem"
    27  	"fmt"
    28  	"net/http"
    29  	"net/http/httptest"
    30  	"os"
    31  	"reflect"
    32  	"strings"
    33  	"testing"
    34  	"text/template"
    35  	"time"
    36  
    37  	"gopkg.in/square/go-jose.v2"
    38  
    39  	"k8s.io/apimachinery/pkg/util/wait"
    40  	"k8s.io/apiserver/pkg/apis/apiserver"
    41  	"k8s.io/apiserver/pkg/authentication/user"
    42  	"k8s.io/apiserver/pkg/features"
    43  	"k8s.io/apiserver/pkg/server/dynamiccertificates"
    44  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    45  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    46  	"k8s.io/component-base/metrics/testutil"
    47  	"k8s.io/klog/v2"
    48  	"k8s.io/utils/pointer"
    49  )
    50  
    51  // utilities for loading JOSE keys.
    52  
    53  func loadRSAKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey {
    54  	return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) {
    55  		key, err := x509.ParsePKCS1PrivateKey(b)
    56  		if err != nil {
    57  			return nil, err
    58  		}
    59  		return key.Public(), nil
    60  	})
    61  }
    62  
    63  func loadRSAPrivKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey {
    64  	return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) {
    65  		return x509.ParsePKCS1PrivateKey(b)
    66  	})
    67  }
    68  
    69  func loadECDSAKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey {
    70  	return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) {
    71  		key, err := x509.ParseECPrivateKey(b)
    72  		if err != nil {
    73  			return nil, err
    74  		}
    75  		return key.Public(), nil
    76  	})
    77  }
    78  
    79  func loadECDSAPrivKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey {
    80  	return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) {
    81  		return x509.ParseECPrivateKey(b)
    82  	})
    83  }
    84  
    85  func loadKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm, unmarshal func([]byte) (interface{}, error)) *jose.JSONWebKey {
    86  	data, err := os.ReadFile(filepath)
    87  	if err != nil {
    88  		t.Fatalf("load file: %v", err)
    89  	}
    90  	block, _ := pem.Decode(data)
    91  	if block == nil {
    92  		t.Fatalf("file contained no PEM encoded data: %s", filepath)
    93  	}
    94  	priv, err := unmarshal(block.Bytes)
    95  	if err != nil {
    96  		t.Fatalf("unmarshal key: %v", err)
    97  	}
    98  	key := &jose.JSONWebKey{Key: priv, Use: "sig", Algorithm: string(alg)}
    99  	thumbprint, err := key.Thumbprint(crypto.SHA256)
   100  	if err != nil {
   101  		t.Fatalf("computing thumbprint: %v", err)
   102  	}
   103  	key.KeyID = hex.EncodeToString(thumbprint)
   104  	return key
   105  }
   106  
   107  // staticKeySet implements oidc.KeySet.
   108  type staticKeySet struct {
   109  	keys []*jose.JSONWebKey
   110  }
   111  
   112  func (s *staticKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
   113  	jws, err := jose.ParseSigned(jwt)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	if len(jws.Signatures) == 0 {
   118  		return nil, fmt.Errorf("jwt contained no signatures")
   119  	}
   120  	kid := jws.Signatures[0].Header.KeyID
   121  
   122  	for _, key := range s.keys {
   123  		if key.KeyID == kid {
   124  			return jws.Verify(key)
   125  		}
   126  	}
   127  
   128  	return nil, fmt.Errorf("no keys matches jwk keyid")
   129  }
   130  
   131  var (
   132  	expired, _ = time.Parse(time.RFC3339Nano, "2009-11-10T22:00:00Z")
   133  	now, _     = time.Parse(time.RFC3339Nano, "2009-11-10T23:00:00Z")
   134  	valid, _   = time.Parse(time.RFC3339Nano, "2009-11-11T00:00:00Z")
   135  )
   136  
   137  type claimsTest struct {
   138  	name                string
   139  	options             Options
   140  	optsFunc            func(*Options)
   141  	signingKey          *jose.JSONWebKey
   142  	pubKeys             []*jose.JSONWebKey
   143  	claims              string
   144  	want                *user.DefaultInfo
   145  	wantSkip            bool
   146  	wantErr             string
   147  	wantInitErr         string
   148  	wantHealthErrPrefix string
   149  	claimToResponseMap  map[string]string
   150  	openIDConfig        string
   151  	fetchKeysFromRemote bool
   152  }
   153  
   154  // Replace formats the contents of v into the provided template.
   155  func replace(tmpl string, v interface{}) string {
   156  	t := template.Must(template.New("test").Parse(tmpl))
   157  	buf := bytes.NewBuffer(nil)
   158  	t.Execute(buf, &v)
   159  	ret := buf.String()
   160  	klog.V(4).Infof("Replaced: %v into: %v", tmpl, ret)
   161  	return ret
   162  }
   163  
   164  // newClaimServer returns a new test HTTPS server, which is rigged to return
   165  // OIDC responses to requests that resolve distributed claims. signer is the
   166  // signer used for the served JWT tokens.  claimToResponseMap is a map of
   167  // responses that the server will return for each claim it is given.
   168  func newClaimServer(t *testing.T, keys jose.JSONWebKeySet, signer jose.Signer, claimToResponseMap map[string]string, openIDConfig *string) *httptest.Server {
   169  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   170  		klog.V(5).Infof("request: %+v", *r)
   171  		switch r.URL.Path {
   172  		case "/.testing/keys":
   173  			w.Header().Set("Content-Type", "application/json")
   174  			keyBytes, err := json.Marshal(keys)
   175  			if err != nil {
   176  				t.Fatalf("unexpected error while marshaling keys: %v", err)
   177  			}
   178  			klog.V(5).Infof("%v: returning: %+v", r.URL, string(keyBytes))
   179  			w.Write(keyBytes)
   180  
   181  		// /c/d/bar/.well-known/openid-configuration is used to test issuer url and discovery url with a path
   182  		case "/.well-known/openid-configuration", "/c/d/bar/.well-known/openid-configuration":
   183  			w.Header().Set("Content-Type", "application/json")
   184  			klog.V(5).Infof("%v: returning: %+v", r.URL, *openIDConfig)
   185  			w.Write([]byte(*openIDConfig))
   186  		// These claims are tested in the unit tests.
   187  		case "/groups":
   188  			fallthrough
   189  		case "/rabbits":
   190  			if claimToResponseMap == nil {
   191  				t.Errorf("no claims specified in response")
   192  			}
   193  			claim := r.URL.Path[1:] // "/groups" -> "groups"
   194  			expectedAuth := fmt.Sprintf("Bearer %v_token", claim)
   195  			auth := r.Header.Get("Authorization")
   196  			if auth != expectedAuth {
   197  				t.Errorf("bearer token expected: %q, was %q", expectedAuth, auth)
   198  			}
   199  			jws, err := signer.Sign([]byte(claimToResponseMap[claim]))
   200  			if err != nil {
   201  				t.Errorf("while signing response token: %v", err)
   202  			}
   203  			token, err := jws.CompactSerialize()
   204  			if err != nil {
   205  				t.Errorf("while serializing response token: %v", err)
   206  			}
   207  			w.Write([]byte(token))
   208  		default:
   209  			w.WriteHeader(http.StatusNotFound)
   210  			fmt.Fprintf(w, "unexpected URL: %v", r.URL)
   211  		}
   212  	}))
   213  	klog.V(4).Infof("Serving OIDC at: %v", ts.URL)
   214  	return ts
   215  }
   216  
   217  func toKeySet(keys []*jose.JSONWebKey) jose.JSONWebKeySet {
   218  	ret := jose.JSONWebKeySet{}
   219  	for _, k := range keys {
   220  		ret.Keys = append(ret.Keys, *k)
   221  	}
   222  	return ret
   223  }
   224  
   225  func (c *claimsTest) run(t *testing.T) {
   226  	var (
   227  		signer jose.Signer
   228  		err    error
   229  	)
   230  	if c.signingKey != nil {
   231  		// Initialize the signer only in the tests that make use of it.  We can
   232  		// not defer this initialization because the test server uses it too.
   233  		signer, err = jose.NewSigner(jose.SigningKey{
   234  			Algorithm: jose.SignatureAlgorithm(c.signingKey.Algorithm),
   235  			Key:       c.signingKey,
   236  		}, nil)
   237  		if err != nil {
   238  			t.Fatalf("initialize signer: %v", err)
   239  		}
   240  	}
   241  	// The HTTPS server used for requesting distributed groups claims.
   242  	ts := newClaimServer(t, toKeySet(c.pubKeys), signer, c.claimToResponseMap, &c.openIDConfig)
   243  	defer ts.Close()
   244  
   245  	// Make the certificate of the helper server available to the authenticator
   246  	caBundle := pem.EncodeToMemory(&pem.Block{
   247  		Type:  "CERTIFICATE",
   248  		Bytes: ts.Certificate().Raw,
   249  	})
   250  	caContent, err := dynamiccertificates.NewStaticCAContent("oidc-authenticator", caBundle)
   251  	if err != nil {
   252  		t.Fatalf("initialize ca: %v", err)
   253  	}
   254  	c.options.CAContentProvider = caContent
   255  
   256  	// Allow claims to refer to the serving URL of the test server.  For this,
   257  	// substitute all references to {{.URL}} in appropriate places.
   258  	// Use {{.Expired}} to handle the token expiry date string with correct timezone handling.
   259  	v := struct {
   260  		URL     string
   261  		Expired string
   262  	}{
   263  		URL:     ts.URL,
   264  		Expired: fmt.Sprintf("%v", time.Unix(expired.Unix(), 0)),
   265  	}
   266  	c.claims = replace(c.claims, &v)
   267  	c.openIDConfig = replace(c.openIDConfig, &v)
   268  	c.options.JWTAuthenticator.Issuer.URL = replace(c.options.JWTAuthenticator.Issuer.URL, &v)
   269  	c.options.JWTAuthenticator.Issuer.DiscoveryURL = replace(c.options.JWTAuthenticator.Issuer.DiscoveryURL, &v)
   270  	for claim, response := range c.claimToResponseMap {
   271  		c.claimToResponseMap[claim] = replace(response, &v)
   272  	}
   273  	c.wantErr = replace(c.wantErr, &v)
   274  	c.wantInitErr = replace(c.wantInitErr, &v)
   275  
   276  	if !c.fetchKeysFromRemote {
   277  		// Set the verifier to use the public key set instead of reading from a remote.
   278  		c.options.KeySet = &staticKeySet{keys: c.pubKeys}
   279  	}
   280  
   281  	if c.optsFunc != nil {
   282  		c.optsFunc(&c.options)
   283  	}
   284  
   285  	expectInitErr := len(c.wantInitErr) > 0
   286  
   287  	ctx := testContext(t)
   288  
   289  	// Initialize the authenticator.
   290  	a, err := New(ctx, c.options)
   291  	if err != nil {
   292  		if !expectInitErr {
   293  			t.Fatalf("initialize authenticator: %v", err)
   294  		}
   295  		if got := err.Error(); c.wantInitErr != got {
   296  			t.Fatalf("expected initialization error %q but got %q", c.wantInitErr, got)
   297  		}
   298  		return
   299  	}
   300  	if expectInitErr {
   301  		t.Fatalf("wanted initialization error %q but got none", c.wantInitErr)
   302  	}
   303  
   304  	if len(c.wantHealthErrPrefix) > 0 {
   305  		if err := wait.PollUntilContextTimeout(ctx, time.Second, time.Minute, true, func(context.Context) (bool, error) {
   306  			healthErr := a.HealthCheck()
   307  			if healthErr == nil {
   308  				return false, fmt.Errorf("authenticator reported healthy when it should not")
   309  			}
   310  
   311  			if strings.HasPrefix(healthErr.Error(), c.wantHealthErrPrefix) {
   312  				return true, nil
   313  			}
   314  
   315  			t.Logf("saw health error prefix that did not match: want=%q got=%q", c.wantHealthErrPrefix, healthErr.Error())
   316  			return false, nil
   317  		}); err != nil {
   318  			t.Fatalf("authenticator did not match wanted health error: %v", err)
   319  		}
   320  		return
   321  	}
   322  
   323  	claims := struct{}{}
   324  	if err := json.Unmarshal([]byte(c.claims), &claims); err != nil {
   325  		t.Fatalf("failed to unmarshal claims: %v", err)
   326  	}
   327  
   328  	// Sign and serialize the claims in a JWT.
   329  	jws, err := signer.Sign([]byte(c.claims))
   330  	if err != nil {
   331  		t.Fatalf("sign claims: %v", err)
   332  	}
   333  	token, err := jws.CompactSerialize()
   334  	if err != nil {
   335  		t.Fatalf("serialize token: %v", err)
   336  	}
   337  
   338  	// wait for the authenticator to be healthy
   339  	err = wait.PollUntilContextCancel(ctx, time.Millisecond, true, func(context.Context) (bool, error) {
   340  		return a.HealthCheck() == nil, nil
   341  	})
   342  	if err != nil {
   343  		t.Fatalf("failed to initialize the authenticator: %v", err)
   344  	}
   345  
   346  	got, ok, err := a.AuthenticateToken(ctx, token)
   347  
   348  	expectErr := len(c.wantErr) > 0
   349  
   350  	if err != nil {
   351  		if !expectErr {
   352  			t.Fatalf("authenticate token: %v", err)
   353  		}
   354  		if got := err.Error(); c.wantErr != got {
   355  			t.Fatalf("expected error %q when authenticating token but got %q", c.wantErr, got)
   356  		}
   357  		return
   358  	}
   359  
   360  	if expectErr {
   361  		t.Fatalf("expected error %q when authenticating token but got none", c.wantErr)
   362  	}
   363  	if !ok {
   364  		if !c.wantSkip {
   365  			// We don't have any cases where we return (nil, false, nil)
   366  			t.Fatalf("no error but token not authenticated")
   367  		}
   368  		return
   369  	}
   370  	if c.wantSkip {
   371  		t.Fatalf("expected authenticator to skip token")
   372  	}
   373  
   374  	gotUser := got.User.(*user.DefaultInfo)
   375  	if !reflect.DeepEqual(gotUser, c.want) {
   376  		t.Fatalf("wanted user=%#v, got=%#v", c.want, gotUser)
   377  	}
   378  }
   379  
   380  func TestToken(t *testing.T) {
   381  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthenticationConfiguration, true)
   382  
   383  	synchronizeTokenIDVerifierForTest = true
   384  	tests := []claimsTest{
   385  		{
   386  			name: "token",
   387  			options: Options{
   388  				JWTAuthenticator: apiserver.JWTAuthenticator{
   389  					Issuer: apiserver.Issuer{
   390  						URL:       "https://auth.example.com",
   391  						Audiences: []string{"my-client"},
   392  					},
   393  					ClaimMappings: apiserver.ClaimMappings{
   394  						Username: apiserver.PrefixedClaimOrExpression{
   395  							Claim:  "username",
   396  							Prefix: pointer.String(""),
   397  						},
   398  					},
   399  				},
   400  				now: func() time.Time { return now },
   401  			},
   402  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   403  			pubKeys: []*jose.JSONWebKey{
   404  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   405  			},
   406  			claims: fmt.Sprintf(`{
   407  				"iss": "https://auth.example.com",
   408  				"aud": "my-client",
   409  				"username": "jane",
   410  				"exp": %d
   411  			}`, valid.Unix()),
   412  			want: &user.DefaultInfo{
   413  				Name: "jane",
   414  			},
   415  		},
   416  		{
   417  			name: "no-username",
   418  			options: Options{
   419  				JWTAuthenticator: apiserver.JWTAuthenticator{
   420  					Issuer: apiserver.Issuer{
   421  						URL:       "https://auth.example.com",
   422  						Audiences: []string{"my-client"},
   423  					},
   424  					ClaimMappings: apiserver.ClaimMappings{
   425  						Username: apiserver.PrefixedClaimOrExpression{
   426  							Claim:  "username",
   427  							Prefix: pointer.String("prefix:"),
   428  						},
   429  					},
   430  				},
   431  				now: func() time.Time { return now },
   432  			},
   433  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   434  			pubKeys: []*jose.JSONWebKey{
   435  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   436  			},
   437  			claims: fmt.Sprintf(`{
   438  				"iss": "https://auth.example.com",
   439  				"aud": "my-client",
   440  				"exp": %d
   441  			}`, valid.Unix()),
   442  			wantErr: `oidc: parse username claims "username": claim not present`,
   443  		},
   444  		{
   445  			name: "email",
   446  			options: Options{
   447  				JWTAuthenticator: apiserver.JWTAuthenticator{
   448  					Issuer: apiserver.Issuer{
   449  						URL:       "https://auth.example.com",
   450  						Audiences: []string{"my-client"},
   451  					},
   452  					ClaimMappings: apiserver.ClaimMappings{
   453  						Username: apiserver.PrefixedClaimOrExpression{
   454  							Claim:  "email",
   455  							Prefix: pointer.String(""),
   456  						},
   457  					},
   458  				},
   459  				now: func() time.Time { return now },
   460  			},
   461  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   462  			pubKeys: []*jose.JSONWebKey{
   463  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   464  			},
   465  			claims: fmt.Sprintf(`{
   466  				"iss": "https://auth.example.com",
   467  				"aud": "my-client",
   468  				"email": "jane@example.com",
   469  				"email_verified": true,
   470  				"exp": %d
   471  			}`, valid.Unix()),
   472  			want: &user.DefaultInfo{
   473  				Name: "jane@example.com",
   474  			},
   475  		},
   476  		{
   477  			name: "email-not-verified",
   478  			options: Options{
   479  				JWTAuthenticator: apiserver.JWTAuthenticator{
   480  					Issuer: apiserver.Issuer{
   481  						URL:       "https://auth.example.com",
   482  						Audiences: []string{"my-client"},
   483  					},
   484  					ClaimMappings: apiserver.ClaimMappings{
   485  						Username: apiserver.PrefixedClaimOrExpression{
   486  							Claim:  "email",
   487  							Prefix: pointer.String(""),
   488  						},
   489  					},
   490  				},
   491  				now: func() time.Time { return now },
   492  			},
   493  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   494  			pubKeys: []*jose.JSONWebKey{
   495  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   496  			},
   497  			claims: fmt.Sprintf(`{
   498  				"iss": "https://auth.example.com",
   499  				"aud": "my-client",
   500  				"email": "jane@example.com",
   501  				"email_verified": false,
   502  				"exp": %d
   503  			}`, valid.Unix()),
   504  			wantErr: "oidc: email not verified",
   505  		},
   506  		{
   507  			// If "email_verified" isn't present, assume true
   508  			name: "no-email-verified-claim",
   509  			options: Options{
   510  				JWTAuthenticator: apiserver.JWTAuthenticator{
   511  					Issuer: apiserver.Issuer{
   512  						URL:       "https://auth.example.com",
   513  						Audiences: []string{"my-client"},
   514  					},
   515  					ClaimMappings: apiserver.ClaimMappings{
   516  						Username: apiserver.PrefixedClaimOrExpression{
   517  							Claim:  "email",
   518  							Prefix: pointer.String(""),
   519  						},
   520  					},
   521  				},
   522  				now: func() time.Time { return now },
   523  			},
   524  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   525  			pubKeys: []*jose.JSONWebKey{
   526  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   527  			},
   528  			claims: fmt.Sprintf(`{
   529  				"iss": "https://auth.example.com",
   530  				"aud": "my-client",
   531  				"email": "jane@example.com",
   532  				"exp": %d
   533  			}`, valid.Unix()),
   534  			want: &user.DefaultInfo{
   535  				Name: "jane@example.com",
   536  			},
   537  		},
   538  		{
   539  			name: "invalid-email-verified-claim",
   540  			options: Options{
   541  				JWTAuthenticator: apiserver.JWTAuthenticator{
   542  					Issuer: apiserver.Issuer{
   543  						URL:       "https://auth.example.com",
   544  						Audiences: []string{"my-client"},
   545  					},
   546  					ClaimMappings: apiserver.ClaimMappings{
   547  						Username: apiserver.PrefixedClaimOrExpression{
   548  							Claim:  "email",
   549  							Prefix: pointer.String(""),
   550  						},
   551  					},
   552  				},
   553  				now: func() time.Time { return now },
   554  			},
   555  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   556  			pubKeys: []*jose.JSONWebKey{
   557  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   558  			},
   559  			// string value for "email_verified"
   560  			claims: fmt.Sprintf(`{
   561  				"iss": "https://auth.example.com",
   562  				"aud": "my-client",
   563  				"email": "jane@example.com",
   564  				"email_verified": "false",
   565  				"exp": %d
   566  			}`, valid.Unix()),
   567  			wantErr: "oidc: parse 'email_verified' claim: json: cannot unmarshal string into Go value of type bool",
   568  		},
   569  		{
   570  			name: "groups",
   571  			options: Options{
   572  				JWTAuthenticator: apiserver.JWTAuthenticator{
   573  					Issuer: apiserver.Issuer{
   574  						URL:       "https://auth.example.com",
   575  						Audiences: []string{"my-client"},
   576  					},
   577  					ClaimMappings: apiserver.ClaimMappings{
   578  						Username: apiserver.PrefixedClaimOrExpression{
   579  							Claim:  "username",
   580  							Prefix: pointer.String(""),
   581  						},
   582  						Groups: apiserver.PrefixedClaimOrExpression{
   583  							Claim:  "groups",
   584  							Prefix: pointer.String(""),
   585  						},
   586  					},
   587  				},
   588  				now: func() time.Time { return now },
   589  			},
   590  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   591  			pubKeys: []*jose.JSONWebKey{
   592  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   593  			},
   594  			claims: fmt.Sprintf(`{
   595  				"iss": "https://auth.example.com",
   596  				"aud": "my-client",
   597  				"username": "jane",
   598  				"groups": ["team1", "team2"],
   599  				"exp": %d
   600  			}`, valid.Unix()),
   601  			want: &user.DefaultInfo{
   602  				Name:   "jane",
   603  				Groups: []string{"team1", "team2"},
   604  			},
   605  		},
   606  		{
   607  			name: "groups-distributed",
   608  			options: Options{
   609  				JWTAuthenticator: apiserver.JWTAuthenticator{
   610  					Issuer: apiserver.Issuer{
   611  						URL:       "{{.URL}}",
   612  						Audiences: []string{"my-client"},
   613  					},
   614  					ClaimMappings: apiserver.ClaimMappings{
   615  						Username: apiserver.PrefixedClaimOrExpression{
   616  							Claim:  "username",
   617  							Prefix: pointer.String(""),
   618  						},
   619  						Groups: apiserver.PrefixedClaimOrExpression{
   620  							Claim:  "groups",
   621  							Prefix: pointer.String(""),
   622  						},
   623  					},
   624  				},
   625  				now: func() time.Time { return now },
   626  			},
   627  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   628  			pubKeys: []*jose.JSONWebKey{
   629  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   630  			},
   631  			claims: fmt.Sprintf(`{
   632  				"iss": "{{.URL}}",
   633  				"aud": "my-client",
   634  				"username": "jane",
   635  				"_claim_names": {
   636  						"groups": "src1"
   637  				},
   638  				"_claim_sources": {
   639  						"src1": {
   640  								"endpoint": "{{.URL}}/groups",
   641  								"access_token": "groups_token"
   642  						}
   643  				},
   644  				"exp": %d
   645  			}`, valid.Unix()),
   646  			claimToResponseMap: map[string]string{
   647  				"groups": fmt.Sprintf(`{
   648  					"iss": "{{.URL}}",
   649  				    "aud": "my-client",
   650  					"groups": ["team1", "team2"],
   651  					"exp": %d
   652  			     }`, valid.Unix()),
   653  			},
   654  			openIDConfig: `{
   655  					"issuer": "{{.URL}}",
   656  					"jwks_uri": "{{.URL}}/.testing/keys"
   657  			}`,
   658  			want: &user.DefaultInfo{
   659  				Name:   "jane",
   660  				Groups: []string{"team1", "team2"},
   661  			},
   662  		},
   663  		{
   664  			name: "groups-distributed invalid client",
   665  			options: Options{
   666  				JWTAuthenticator: apiserver.JWTAuthenticator{
   667  					Issuer: apiserver.Issuer{
   668  						URL:       "{{.URL}}",
   669  						Audiences: []string{"my-client"},
   670  					},
   671  					ClaimMappings: apiserver.ClaimMappings{
   672  						Username: apiserver.PrefixedClaimOrExpression{
   673  							Claim:  "username",
   674  							Prefix: pointer.String(""),
   675  						},
   676  						Groups: apiserver.PrefixedClaimOrExpression{
   677  							Claim:  "groups",
   678  							Prefix: pointer.String(""),
   679  						},
   680  					},
   681  				},
   682  				Client: &http.Client{Transport: errTransport("some unexpected oidc error")}, // return an error that we can assert against
   683  				now:    func() time.Time { return now },
   684  			},
   685  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   686  			pubKeys: []*jose.JSONWebKey{
   687  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   688  			},
   689  			claims: fmt.Sprintf(`{
   690  				"iss": "{{.URL}}",
   691  				"aud": "my-client",
   692  				"username": "jane",
   693  				"_claim_names": {
   694  						"groups": "src1"
   695  				},
   696  				"_claim_sources": {
   697  						"src1": {
   698  								"endpoint": "{{.URL}}/groups",
   699  								"access_token": "groups_token"
   700  						}
   701  				},
   702  				"exp": %d
   703  			}`, valid.Unix()),
   704  			claimToResponseMap: map[string]string{
   705  				"groups": fmt.Sprintf(`{
   706  					"iss": "{{.URL}}",
   707  				    "aud": "my-client",
   708  					"groups": ["team1", "team2"],
   709  					"exp": %d
   710  			     }`, valid.Unix()),
   711  			},
   712  			openIDConfig: `{
   713  					"issuer": "{{.URL}}",
   714  					"jwks_uri": "{{.URL}}/.testing/keys"
   715  			}`,
   716  			optsFunc: func(opts *Options) {
   717  				opts.CAContentProvider = nil // unset CA automatically set by the test to allow us to use a custom client
   718  			},
   719  			wantErr: `oidc: could not expand distributed claims: while getting distributed claim "groups": Get "{{.URL}}/groups": some unexpected oidc error`,
   720  		},
   721  		{
   722  			name: "groups-distributed-malformed-claim-names",
   723  			options: Options{
   724  				JWTAuthenticator: apiserver.JWTAuthenticator{
   725  					Issuer: apiserver.Issuer{
   726  						URL:       "{{.URL}}",
   727  						Audiences: []string{"my-client"},
   728  					},
   729  					ClaimMappings: apiserver.ClaimMappings{
   730  						Username: apiserver.PrefixedClaimOrExpression{
   731  							Claim:  "username",
   732  							Prefix: pointer.String(""),
   733  						},
   734  						Groups: apiserver.PrefixedClaimOrExpression{
   735  							Claim:  "groups",
   736  							Prefix: pointer.String(""),
   737  						},
   738  					},
   739  				},
   740  				now: func() time.Time { return now },
   741  			},
   742  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   743  			pubKeys: []*jose.JSONWebKey{
   744  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   745  			},
   746  			claims: fmt.Sprintf(`{
   747  				"iss": "{{.URL}}",
   748  				"aud": "my-client",
   749  				"username": "jane",
   750  				"_claim_names": {
   751  						"groups": "nonexistent-claim-source"
   752  				},
   753  				"_claim_sources": {
   754  						"src1": {
   755  								"endpoint": "{{.URL}}/groups",
   756  								"access_token": "groups_token"
   757  						}
   758  				},
   759  				"exp": %d
   760  			}`, valid.Unix()),
   761  			claimToResponseMap: map[string]string{
   762  				"groups": fmt.Sprintf(`{
   763  					"iss": "{{.URL}}",
   764  				    "aud": "my-client",
   765  					"groups": ["team1", "team2"],
   766  					"exp": %d
   767  			     }`, valid.Unix()),
   768  			},
   769  			openIDConfig: `{
   770  					"issuer": "{{.URL}}",
   771  					"jwks_uri": "{{.URL}}/.testing/keys"
   772  			}`,
   773  			wantErr: "oidc: verify token: oidc: source does not exist",
   774  		},
   775  		{
   776  			name: "groups-distributed-malformed-names-and-sources",
   777  			options: Options{
   778  				JWTAuthenticator: apiserver.JWTAuthenticator{
   779  					Issuer: apiserver.Issuer{
   780  						URL:       "{{.URL}}",
   781  						Audiences: []string{"my-client"},
   782  					},
   783  					ClaimMappings: apiserver.ClaimMappings{
   784  						Username: apiserver.PrefixedClaimOrExpression{
   785  							Claim:  "username",
   786  							Prefix: pointer.String(""),
   787  						},
   788  						Groups: apiserver.PrefixedClaimOrExpression{
   789  							Claim:  "groups",
   790  							Prefix: pointer.String(""),
   791  						},
   792  					},
   793  				},
   794  				now: func() time.Time { return now },
   795  			},
   796  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   797  			pubKeys: []*jose.JSONWebKey{
   798  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   799  			},
   800  			claims: fmt.Sprintf(`{
   801  				"iss": "{{.URL}}",
   802  				"aud": "my-client",
   803  				"username": "jane",
   804  				"_claim_names": {
   805  						"groups": "src1"
   806  				},
   807  				"exp": %d
   808  			}`, valid.Unix()),
   809  			claimToResponseMap: map[string]string{
   810  				"groups": fmt.Sprintf(`{
   811  					"iss": "{{.URL}}",
   812  				    "aud": "my-client",
   813  					"groups": ["team1", "team2"],
   814  					"exp": %d
   815  			     }`, valid.Unix()),
   816  			},
   817  			openIDConfig: `{
   818  					"issuer": "{{.URL}}",
   819  					"jwks_uri": "{{.URL}}/.testing/keys"
   820  			}`,
   821  			wantErr: "oidc: verify token: oidc: source does not exist",
   822  		},
   823  		{
   824  			name: "groups-distributed-malformed-distributed-claim",
   825  			options: Options{
   826  				JWTAuthenticator: apiserver.JWTAuthenticator{
   827  					Issuer: apiserver.Issuer{
   828  						URL:       "{{.URL}}",
   829  						Audiences: []string{"my-client"},
   830  					},
   831  					ClaimMappings: apiserver.ClaimMappings{
   832  						Username: apiserver.PrefixedClaimOrExpression{
   833  							Claim:  "username",
   834  							Prefix: pointer.String(""),
   835  						},
   836  						Groups: apiserver.PrefixedClaimOrExpression{
   837  							Claim:  "groups",
   838  							Prefix: pointer.String(""),
   839  						},
   840  					},
   841  				},
   842  				now: func() time.Time { return now },
   843  			},
   844  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   845  			pubKeys: []*jose.JSONWebKey{
   846  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   847  			},
   848  			claims: fmt.Sprintf(`{
   849  				"iss": "{{.URL}}",
   850  				"aud": "my-client",
   851  				"username": "jane",
   852  				"_claim_names": {
   853  						"groups": "src1"
   854  				},
   855  				"_claim_sources": {
   856  						"src1": {
   857  								"endpoint": "{{.URL}}/groups",
   858  								"access_token": "groups_token"
   859  						}
   860  				},
   861  				"exp": %d
   862  			}`, valid.Unix()),
   863  			claimToResponseMap: map[string]string{
   864  				// Doesn't contain the "groups" claim as it promises.
   865  				"groups": fmt.Sprintf(`{
   866  					"iss": "{{.URL}}",
   867  				    "aud": "my-client",
   868  					"exp": %d
   869  			     }`, valid.Unix()),
   870  			},
   871  			openIDConfig: `{
   872  					"issuer": "{{.URL}}",
   873  					"jwks_uri": "{{.URL}}/.testing/keys"
   874  			}`,
   875  			wantErr: `oidc: could not expand distributed claims: jwt returned by distributed claim endpoint "{{.URL}}/groups" did not contain claim: groups`,
   876  		},
   877  		{
   878  			name: "groups-distributed-unusual-name",
   879  			options: Options{
   880  				JWTAuthenticator: apiserver.JWTAuthenticator{
   881  					Issuer: apiserver.Issuer{
   882  						URL:       "{{.URL}}",
   883  						Audiences: []string{"my-client"},
   884  					},
   885  					ClaimMappings: apiserver.ClaimMappings{
   886  						Username: apiserver.PrefixedClaimOrExpression{
   887  							Claim:  "username",
   888  							Prefix: pointer.String(""),
   889  						},
   890  						Groups: apiserver.PrefixedClaimOrExpression{
   891  							Claim:  "rabbits",
   892  							Prefix: pointer.String(""),
   893  						},
   894  					},
   895  				},
   896  				now: func() time.Time { return now },
   897  			},
   898  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   899  			pubKeys: []*jose.JSONWebKey{
   900  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   901  			},
   902  			claims: fmt.Sprintf(`{
   903  				"iss": "{{.URL}}",
   904  				"aud": "my-client",
   905  				"username": "jane",
   906  				"_claim_names": {
   907  						"rabbits": "src1"
   908  				},
   909  				"_claim_sources": {
   910  						"src1": {
   911  								"endpoint": "{{.URL}}/rabbits",
   912  								"access_token": "rabbits_token"
   913  						}
   914  				},
   915  				"exp": %d
   916  			}`, valid.Unix()),
   917  			claimToResponseMap: map[string]string{
   918  				"rabbits": fmt.Sprintf(`{
   919  					"iss": "{{.URL}}",
   920  				    "aud": "my-client",
   921  					"rabbits": ["team1", "team2"],
   922  					"exp": %d
   923  			     }`, valid.Unix()),
   924  			},
   925  			openIDConfig: `{
   926  					"issuer": "{{.URL}}",
   927  					"jwks_uri": "{{.URL}}/.testing/keys"
   928  			}`,
   929  			want: &user.DefaultInfo{
   930  				Name:   "jane",
   931  				Groups: []string{"team1", "team2"},
   932  			},
   933  		},
   934  		{
   935  			name: "groups-distributed-wrong-audience",
   936  			options: Options{
   937  				JWTAuthenticator: apiserver.JWTAuthenticator{
   938  					Issuer: apiserver.Issuer{
   939  						URL:       "{{.URL}}",
   940  						Audiences: []string{"my-client"},
   941  					},
   942  					ClaimMappings: apiserver.ClaimMappings{
   943  						Username: apiserver.PrefixedClaimOrExpression{
   944  							Claim:  "username",
   945  							Prefix: pointer.String(""),
   946  						},
   947  						Groups: apiserver.PrefixedClaimOrExpression{
   948  							Claim:  "groups",
   949  							Prefix: pointer.String(""),
   950  						},
   951  					},
   952  				},
   953  				now: func() time.Time { return now },
   954  			},
   955  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
   956  			pubKeys: []*jose.JSONWebKey{
   957  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
   958  			},
   959  			claims: fmt.Sprintf(`{
   960  				"iss": "{{.URL}}",
   961  				"aud": "my-client",
   962  				"username": "jane",
   963  				"_claim_names": {
   964  						"groups": "src1"
   965  				},
   966  				"_claim_sources": {
   967  						"src1": {
   968  								"endpoint": "{{.URL}}/groups",
   969  								"access_token": "groups_token"
   970  						}
   971  				},
   972  				"exp": %d
   973  			}`, valid.Unix()),
   974  			claimToResponseMap: map[string]string{
   975  				// Note mismatching "aud"
   976  				"groups": fmt.Sprintf(`{
   977  					"iss": "{{.URL}}",
   978  				    "aud": "your-client",
   979  					"groups": ["team1", "team2"],
   980  					"exp": %d
   981  			     }`, valid.Unix()),
   982  			},
   983  			openIDConfig: `{
   984  					"issuer": "{{.URL}}",
   985  					"jwks_uri": "{{.URL}}/.testing/keys"
   986  			}`,
   987  			wantErr: `oidc: could not expand distributed claims: verify distributed claim token: oidc: expected audience "my-client" got ["your-client"]`,
   988  		},
   989  		{
   990  			name: "groups-distributed-expired-token",
   991  			options: Options{
   992  				JWTAuthenticator: apiserver.JWTAuthenticator{
   993  					Issuer: apiserver.Issuer{
   994  						URL:       "{{.URL}}",
   995  						Audiences: []string{"my-client"},
   996  					},
   997  					ClaimMappings: apiserver.ClaimMappings{
   998  						Username: apiserver.PrefixedClaimOrExpression{
   999  							Claim:  "username",
  1000  							Prefix: pointer.String(""),
  1001  						},
  1002  						Groups: apiserver.PrefixedClaimOrExpression{
  1003  							Claim:  "groups",
  1004  							Prefix: pointer.String(""),
  1005  						},
  1006  					},
  1007  				},
  1008  				now: func() time.Time { return now },
  1009  			},
  1010  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1011  			pubKeys: []*jose.JSONWebKey{
  1012  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1013  			},
  1014  			claims: fmt.Sprintf(`{
  1015  				"iss": "{{.URL}}",
  1016  				"aud": "my-client",
  1017  				"username": "jane",
  1018  				"_claim_names": {
  1019  						"groups": "src1"
  1020  				},
  1021  				"_claim_sources": {
  1022  						"src1": {
  1023  								"endpoint": "{{.URL}}/groups",
  1024  								"access_token": "groups_token"
  1025  						}
  1026  				},
  1027  				"exp": %d
  1028  			}`, valid.Unix()),
  1029  			claimToResponseMap: map[string]string{
  1030  				// Note expired timestamp.
  1031  				"groups": fmt.Sprintf(`{
  1032  					"iss": "{{.URL}}",
  1033  				    "aud": "my-client",
  1034  					"groups": ["team1", "team2"],
  1035  					"exp": %d
  1036  			     }`, expired.Unix()),
  1037  			},
  1038  			openIDConfig: `{
  1039  					"issuer": "{{.URL}}",
  1040  					"jwks_uri": "{{.URL}}/.testing/keys"
  1041  			}`,
  1042  			wantErr: "oidc: could not expand distributed claims: verify distributed claim token: oidc: token is expired (Token Expiry: {{.Expired}})",
  1043  		},
  1044  		{
  1045  			// Specs are unclear about this behavior.  We adopt a behavior where
  1046  			// normal claim wins over a distributed claim by the same name.
  1047  			name: "groups-distributed-normal-claim-wins",
  1048  			options: Options{
  1049  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1050  					Issuer: apiserver.Issuer{
  1051  						URL:       "{{.URL}}",
  1052  						Audiences: []string{"my-client"},
  1053  					},
  1054  					ClaimMappings: apiserver.ClaimMappings{
  1055  						Username: apiserver.PrefixedClaimOrExpression{
  1056  							Claim:  "username",
  1057  							Prefix: pointer.String(""),
  1058  						},
  1059  						Groups: apiserver.PrefixedClaimOrExpression{
  1060  							Claim:  "groups",
  1061  							Prefix: pointer.String(""),
  1062  						},
  1063  					},
  1064  				},
  1065  				now: func() time.Time { return now },
  1066  			},
  1067  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1068  			pubKeys: []*jose.JSONWebKey{
  1069  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1070  			},
  1071  			claims: fmt.Sprintf(`{
  1072  				"iss": "{{.URL}}",
  1073  				"aud": "my-client",
  1074  				"username": "jane",
  1075  				"groups": "team1",
  1076  				"_claim_names": {
  1077  						"groups": "src1"
  1078  				},
  1079  				"_claim_sources": {
  1080  						"src1": {
  1081  								"endpoint": "{{.URL}}/groups",
  1082  								"access_token": "groups_token"
  1083  						}
  1084  				},
  1085  				"exp": %d
  1086  			}`, valid.Unix()),
  1087  			claimToResponseMap: map[string]string{
  1088  				"groups": fmt.Sprintf(`{
  1089  					"iss": "{{.URL}}",
  1090  				    "aud": "my-client",
  1091  					"groups": ["team2"],
  1092  					"exp": %d
  1093  			     }`, valid.Unix()),
  1094  			},
  1095  			openIDConfig: `{
  1096  					"issuer": "{{.URL}}",
  1097  					"jwks_uri": "{{.URL}}/.testing/keys"
  1098  			}`,
  1099  			want: &user.DefaultInfo{
  1100  				Name: "jane",
  1101  				// "team1" is from the normal "groups" claim.
  1102  				Groups: []string{"team1"},
  1103  			},
  1104  		},
  1105  		{
  1106  			// Groups should be able to be a single string, not just a slice.
  1107  			name: "group-string-claim",
  1108  			options: Options{
  1109  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1110  					Issuer: apiserver.Issuer{
  1111  						URL:       "https://auth.example.com",
  1112  						Audiences: []string{"my-client"},
  1113  					},
  1114  					ClaimMappings: apiserver.ClaimMappings{
  1115  						Username: apiserver.PrefixedClaimOrExpression{
  1116  							Claim:  "username",
  1117  							Prefix: pointer.String(""),
  1118  						},
  1119  						Groups: apiserver.PrefixedClaimOrExpression{
  1120  							Claim:  "groups",
  1121  							Prefix: pointer.String(""),
  1122  						},
  1123  					},
  1124  				},
  1125  				now: func() time.Time { return now },
  1126  			},
  1127  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1128  			pubKeys: []*jose.JSONWebKey{
  1129  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1130  			},
  1131  			claims: fmt.Sprintf(`{
  1132  				"iss": "https://auth.example.com",
  1133  				"aud": "my-client",
  1134  				"username": "jane",
  1135  				"groups": "team1",
  1136  				"exp": %d
  1137  			}`, valid.Unix()),
  1138  			want: &user.DefaultInfo{
  1139  				Name:   "jane",
  1140  				Groups: []string{"team1"},
  1141  			},
  1142  		},
  1143  		{
  1144  			// Groups should be able to be a single string, not just a slice.
  1145  			name: "group-string-claim-distributed",
  1146  			options: Options{
  1147  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1148  					Issuer: apiserver.Issuer{
  1149  						URL:       "{{.URL}}",
  1150  						Audiences: []string{"my-client"},
  1151  					},
  1152  					ClaimMappings: apiserver.ClaimMappings{
  1153  						Username: apiserver.PrefixedClaimOrExpression{
  1154  							Claim:  "username",
  1155  							Prefix: pointer.String(""),
  1156  						},
  1157  						Groups: apiserver.PrefixedClaimOrExpression{
  1158  							Claim:  "groups",
  1159  							Prefix: pointer.String(""),
  1160  						},
  1161  					},
  1162  				},
  1163  				now: func() time.Time { return now },
  1164  			},
  1165  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1166  			pubKeys: []*jose.JSONWebKey{
  1167  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1168  			},
  1169  			claims: fmt.Sprintf(`{
  1170  				"iss": "{{.URL}}",
  1171  				"aud": "my-client",
  1172  				"username": "jane",
  1173  				"_claim_names": {
  1174  						"groups": "src1"
  1175  				},
  1176  				"_claim_sources": {
  1177  						"src1": {
  1178  								"endpoint": "{{.URL}}/groups",
  1179  								"access_token": "groups_token"
  1180  						}
  1181  				},
  1182  				"exp": %d
  1183  			}`, valid.Unix()),
  1184  			claimToResponseMap: map[string]string{
  1185  				"groups": fmt.Sprintf(`{
  1186  					"iss": "{{.URL}}",
  1187  				    "aud": "my-client",
  1188  					"groups": "team1",
  1189  					"exp": %d
  1190  			     }`, valid.Unix()),
  1191  			},
  1192  			openIDConfig: `{
  1193  					"issuer": "{{.URL}}",
  1194  					"jwks_uri": "{{.URL}}/.testing/keys"
  1195  			}`,
  1196  			want: &user.DefaultInfo{
  1197  				Name:   "jane",
  1198  				Groups: []string{"team1"},
  1199  			},
  1200  		},
  1201  		{
  1202  			name: "group-string-claim-aggregated-not-supported",
  1203  			options: Options{
  1204  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1205  					Issuer: apiserver.Issuer{
  1206  						URL:       "https://auth.example.com",
  1207  						Audiences: []string{"my-client"},
  1208  					},
  1209  					ClaimMappings: apiserver.ClaimMappings{
  1210  						Username: apiserver.PrefixedClaimOrExpression{
  1211  							Claim:  "username",
  1212  							Prefix: pointer.String(""),
  1213  						},
  1214  						Groups: apiserver.PrefixedClaimOrExpression{
  1215  							Claim:  "groups",
  1216  							Prefix: pointer.String(""),
  1217  						},
  1218  					},
  1219  				},
  1220  				now: func() time.Time { return now },
  1221  			},
  1222  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1223  			pubKeys: []*jose.JSONWebKey{
  1224  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1225  			},
  1226  			claims: fmt.Sprintf(`{
  1227  				"iss": "https://auth.example.com",
  1228  				"aud": "my-client",
  1229  				"username": "jane",
  1230  				"_claim_names": {
  1231  						"groups": "src1"
  1232  				},
  1233  				"_claim_sources": {
  1234  						"src1": {
  1235  								"JWT": "some.jwt.token"
  1236  						}
  1237  				},
  1238  				"exp": %d
  1239  			}`, valid.Unix()),
  1240  			want: &user.DefaultInfo{
  1241  				Name: "jane",
  1242  			},
  1243  		},
  1244  		{
  1245  			// if the groups claim isn't provided, this shouldn't error out
  1246  			name: "no-groups-claim",
  1247  			options: Options{
  1248  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1249  					Issuer: apiserver.Issuer{
  1250  						URL:       "https://auth.example.com",
  1251  						Audiences: []string{"my-client"},
  1252  					},
  1253  					ClaimMappings: apiserver.ClaimMappings{
  1254  						Username: apiserver.PrefixedClaimOrExpression{
  1255  							Claim:  "username",
  1256  							Prefix: pointer.String(""),
  1257  						},
  1258  						Groups: apiserver.PrefixedClaimOrExpression{
  1259  							Claim:  "groups",
  1260  							Prefix: pointer.String(""),
  1261  						},
  1262  					},
  1263  				},
  1264  				now: func() time.Time { return now },
  1265  			},
  1266  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1267  			pubKeys: []*jose.JSONWebKey{
  1268  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1269  			},
  1270  			claims: fmt.Sprintf(`{
  1271  				"iss": "https://auth.example.com",
  1272  				"aud": "my-client",
  1273  				"username": "jane",
  1274  				"exp": %d
  1275  			}`, valid.Unix()),
  1276  			want: &user.DefaultInfo{
  1277  				Name: "jane",
  1278  			},
  1279  		},
  1280  		{
  1281  			name: "invalid-groups-claim",
  1282  			options: Options{
  1283  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1284  					Issuer: apiserver.Issuer{
  1285  						URL:       "https://auth.example.com",
  1286  						Audiences: []string{"my-client"},
  1287  					},
  1288  					ClaimMappings: apiserver.ClaimMappings{
  1289  						Username: apiserver.PrefixedClaimOrExpression{
  1290  							Claim:  "username",
  1291  							Prefix: pointer.String(""),
  1292  						},
  1293  						Groups: apiserver.PrefixedClaimOrExpression{
  1294  							Claim:  "groups",
  1295  							Prefix: pointer.String(""),
  1296  						},
  1297  					},
  1298  				},
  1299  				now: func() time.Time { return now },
  1300  			},
  1301  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1302  			pubKeys: []*jose.JSONWebKey{
  1303  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1304  			},
  1305  			claims: fmt.Sprintf(`{
  1306  				"iss": "https://auth.example.com",
  1307  				"aud": "my-client",
  1308  				"username": "jane",
  1309  				"groups": 42,
  1310  				"exp": %d
  1311  			}`, valid.Unix()),
  1312  			wantErr: `oidc: parse groups claim "groups": json: cannot unmarshal number into Go value of type string`,
  1313  		},
  1314  		{
  1315  			name: "required-claim",
  1316  			options: Options{
  1317  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1318  					Issuer: apiserver.Issuer{
  1319  						URL:       "https://auth.example.com",
  1320  						Audiences: []string{"my-client"},
  1321  					},
  1322  					ClaimMappings: apiserver.ClaimMappings{
  1323  						Username: apiserver.PrefixedClaimOrExpression{
  1324  							Claim:  "username",
  1325  							Prefix: pointer.String(""),
  1326  						},
  1327  						Groups: apiserver.PrefixedClaimOrExpression{
  1328  							Claim:  "groups",
  1329  							Prefix: pointer.String(""),
  1330  						},
  1331  					},
  1332  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  1333  						{
  1334  							Claim:         "hd",
  1335  							RequiredValue: "example.com",
  1336  						},
  1337  						{
  1338  							Claim:         "sub",
  1339  							RequiredValue: "test",
  1340  						},
  1341  					},
  1342  				},
  1343  				now: func() time.Time { return now },
  1344  			},
  1345  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1346  			pubKeys: []*jose.JSONWebKey{
  1347  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1348  			},
  1349  			claims: fmt.Sprintf(`{
  1350  				"iss": "https://auth.example.com",
  1351  				"aud": "my-client",
  1352  				"username": "jane",
  1353  				"hd": "example.com",
  1354  				"sub": "test",
  1355  				"exp": %d
  1356  			}`, valid.Unix()),
  1357  			want: &user.DefaultInfo{
  1358  				Name: "jane",
  1359  			},
  1360  		},
  1361  		{
  1362  			name: "no-required-claim",
  1363  			options: Options{
  1364  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1365  					Issuer: apiserver.Issuer{
  1366  						URL:       "https://auth.example.com",
  1367  						Audiences: []string{"my-client"},
  1368  					},
  1369  					ClaimMappings: apiserver.ClaimMappings{
  1370  						Username: apiserver.PrefixedClaimOrExpression{
  1371  							Claim:  "username",
  1372  							Prefix: pointer.String(""),
  1373  						},
  1374  						Groups: apiserver.PrefixedClaimOrExpression{
  1375  							Claim:  "groups",
  1376  							Prefix: pointer.String(""),
  1377  						},
  1378  					},
  1379  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  1380  						{
  1381  							Claim:         "hd",
  1382  							RequiredValue: "example.com",
  1383  						},
  1384  					},
  1385  				},
  1386  				now: func() time.Time { return now },
  1387  			},
  1388  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1389  			pubKeys: []*jose.JSONWebKey{
  1390  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1391  			},
  1392  			claims: fmt.Sprintf(`{
  1393  				"iss": "https://auth.example.com",
  1394  				"aud": "my-client",
  1395  				"username": "jane",
  1396  				"exp": %d
  1397  			}`, valid.Unix()),
  1398  			wantErr: "oidc: required claim hd not present in ID token",
  1399  		},
  1400  		{
  1401  			name: "invalid-required-claim",
  1402  			options: Options{
  1403  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1404  					Issuer: apiserver.Issuer{
  1405  						URL:       "https://auth.example.com",
  1406  						Audiences: []string{"my-client"},
  1407  					},
  1408  					ClaimMappings: apiserver.ClaimMappings{
  1409  						Username: apiserver.PrefixedClaimOrExpression{
  1410  							Claim:  "username",
  1411  							Prefix: pointer.String(""),
  1412  						},
  1413  						Groups: apiserver.PrefixedClaimOrExpression{
  1414  							Claim:  "groups",
  1415  							Prefix: pointer.String(""),
  1416  						},
  1417  					},
  1418  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  1419  						{
  1420  							Claim:         "hd",
  1421  							RequiredValue: "example.com",
  1422  						},
  1423  					},
  1424  				},
  1425  				now: func() time.Time { return now },
  1426  			},
  1427  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1428  			pubKeys: []*jose.JSONWebKey{
  1429  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1430  			},
  1431  			claims: fmt.Sprintf(`{
  1432  				"iss": "https://auth.example.com",
  1433  				"aud": "my-client",
  1434  				"username": "jane",
  1435  				"hd": "example.org",
  1436  				"exp": %d
  1437  			}`, valid.Unix()),
  1438  			wantErr: "oidc: required claim hd value does not match. Got = example.org, want = example.com",
  1439  		},
  1440  		{
  1441  			name: "invalid-signature",
  1442  			options: Options{
  1443  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1444  					Issuer: apiserver.Issuer{
  1445  						URL:       "https://auth.example.com",
  1446  						Audiences: []string{"my-client"},
  1447  					},
  1448  					ClaimMappings: apiserver.ClaimMappings{
  1449  						Username: apiserver.PrefixedClaimOrExpression{
  1450  							Claim:  "username",
  1451  							Prefix: pointer.String("prefix:"),
  1452  						},
  1453  					},
  1454  				},
  1455  				now: func() time.Time { return now },
  1456  			},
  1457  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1458  			pubKeys: []*jose.JSONWebKey{
  1459  				loadRSAKey(t, "testdata/rsa_2.pem", jose.RS256),
  1460  			},
  1461  			claims: fmt.Sprintf(`{
  1462  				"iss": "https://auth.example.com",
  1463  				"aud": "my-client",
  1464  				"username": "jane",
  1465  				"exp": %d
  1466  			}`, valid.Unix()),
  1467  			wantErr: "oidc: verify token: failed to verify signature: no keys matches jwk keyid",
  1468  		},
  1469  		{
  1470  			name: "expired",
  1471  			options: Options{
  1472  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1473  					Issuer: apiserver.Issuer{
  1474  						URL:       "https://auth.example.com",
  1475  						Audiences: []string{"my-client"},
  1476  					},
  1477  					ClaimMappings: apiserver.ClaimMappings{
  1478  						Username: apiserver.PrefixedClaimOrExpression{
  1479  							Claim:  "username",
  1480  							Prefix: pointer.String("prefix:"),
  1481  						},
  1482  					},
  1483  				},
  1484  				now: func() time.Time { return now },
  1485  			},
  1486  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1487  			pubKeys: []*jose.JSONWebKey{
  1488  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1489  			},
  1490  			claims: fmt.Sprintf(`{
  1491  				"iss": "https://auth.example.com",
  1492  				"aud": "my-client",
  1493  				"username": "jane",
  1494  				"exp": %d
  1495  			}`, expired.Unix()),
  1496  			wantErr: `oidc: verify token: oidc: token is expired (Token Expiry: {{.Expired}})`,
  1497  		},
  1498  		{
  1499  			name: "invalid-aud",
  1500  			options: Options{
  1501  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1502  					Issuer: apiserver.Issuer{
  1503  						URL:       "https://auth.example.com",
  1504  						Audiences: []string{"my-client"},
  1505  					},
  1506  					ClaimMappings: apiserver.ClaimMappings{
  1507  						Username: apiserver.PrefixedClaimOrExpression{
  1508  							Claim:  "username",
  1509  							Prefix: pointer.String("prefix:"),
  1510  						},
  1511  					},
  1512  				},
  1513  				now: func() time.Time { return now },
  1514  			},
  1515  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1516  			pubKeys: []*jose.JSONWebKey{
  1517  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1518  			},
  1519  			claims: fmt.Sprintf(`{
  1520  				"iss": "https://auth.example.com",
  1521  				"aud": "not-my-client",
  1522  				"username": "jane",
  1523  				"exp": %d
  1524  			}`, valid.Unix()),
  1525  			wantErr: `oidc: verify token: oidc: expected audience "my-client" got ["not-my-client"]`,
  1526  		},
  1527  		{
  1528  			// ID tokens may contain multiple audiences:
  1529  			// https://openid.net/specs/openid-connect-core-1_0.html#IDToken
  1530  			name: "multiple-audiences",
  1531  			options: Options{
  1532  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1533  					Issuer: apiserver.Issuer{
  1534  						URL:       "https://auth.example.com",
  1535  						Audiences: []string{"my-client"},
  1536  					},
  1537  					ClaimMappings: apiserver.ClaimMappings{
  1538  						Username: apiserver.PrefixedClaimOrExpression{
  1539  							Claim:  "username",
  1540  							Prefix: pointer.String(""),
  1541  						},
  1542  					},
  1543  				},
  1544  				now: func() time.Time { return now },
  1545  			},
  1546  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1547  			pubKeys: []*jose.JSONWebKey{
  1548  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1549  			},
  1550  			claims: fmt.Sprintf(`{
  1551  				"iss": "https://auth.example.com",
  1552  				"aud": ["not-my-client", "my-client"],
  1553  				"azp": "not-my-client",
  1554  				"username": "jane",
  1555  				"exp": %d
  1556  			}`, valid.Unix()),
  1557  			want: &user.DefaultInfo{
  1558  				Name: "jane",
  1559  			},
  1560  		},
  1561  		{
  1562  			name: "multiple-audiences in authentication config",
  1563  			options: Options{
  1564  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1565  					Issuer: apiserver.Issuer{
  1566  						URL:                 "https://auth.example.com",
  1567  						Audiences:           []string{"random-client", "my-client"},
  1568  						AudienceMatchPolicy: "MatchAny",
  1569  					},
  1570  					ClaimMappings: apiserver.ClaimMappings{
  1571  						Username: apiserver.PrefixedClaimOrExpression{
  1572  							Claim:  "username",
  1573  							Prefix: pointer.String(""),
  1574  						},
  1575  					},
  1576  				},
  1577  				now: func() time.Time { return now },
  1578  			},
  1579  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1580  			pubKeys: []*jose.JSONWebKey{
  1581  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1582  			},
  1583  			claims: fmt.Sprintf(`{
  1584  				"iss": "https://auth.example.com",
  1585  				"aud": ["not-my-client", "my-client"],
  1586  				"azp": "not-my-client",
  1587  				"username": "jane",
  1588  				"exp": %d
  1589  			}`, valid.Unix()),
  1590  			want: &user.DefaultInfo{
  1591  				Name: "jane",
  1592  			},
  1593  		},
  1594  		{
  1595  			name: "multiple-audiences in authentication config, multiple matches",
  1596  			options: Options{
  1597  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1598  					Issuer: apiserver.Issuer{
  1599  						URL:                 "https://auth.example.com",
  1600  						Audiences:           []string{"random-client", "my-client", "other-client"},
  1601  						AudienceMatchPolicy: "MatchAny",
  1602  					},
  1603  					ClaimMappings: apiserver.ClaimMappings{
  1604  						Username: apiserver.PrefixedClaimOrExpression{
  1605  							Claim:  "username",
  1606  							Prefix: pointer.String(""),
  1607  						},
  1608  					},
  1609  				},
  1610  				now: func() time.Time { return now },
  1611  			},
  1612  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1613  			pubKeys: []*jose.JSONWebKey{
  1614  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1615  			},
  1616  			claims: fmt.Sprintf(`{
  1617  				"iss": "https://auth.example.com",
  1618  				"aud": ["not-my-client", "my-client", "other-client"],
  1619  				"azp": "not-my-client",
  1620  				"username": "jane",
  1621  				"exp": %d
  1622  			}`, valid.Unix()),
  1623  			want: &user.DefaultInfo{
  1624  				Name: "jane",
  1625  			},
  1626  		},
  1627  		{
  1628  			name: "multiple-audiences in authentication config, no match",
  1629  			options: Options{
  1630  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1631  					Issuer: apiserver.Issuer{
  1632  						URL:                 "https://auth.example.com",
  1633  						Audiences:           []string{"random-client", "my-client"},
  1634  						AudienceMatchPolicy: "MatchAny",
  1635  					},
  1636  					ClaimMappings: apiserver.ClaimMappings{
  1637  						Username: apiserver.PrefixedClaimOrExpression{
  1638  							Claim:  "username",
  1639  							Prefix: pointer.String(""),
  1640  						},
  1641  					},
  1642  				},
  1643  				now: func() time.Time { return now },
  1644  			},
  1645  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1646  			pubKeys: []*jose.JSONWebKey{
  1647  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1648  			},
  1649  			claims: fmt.Sprintf(`{
  1650  				"iss": "https://auth.example.com",
  1651  				"aud": ["not-my-client"],
  1652  				"azp": "not-my-client",
  1653  				"username": "jane",
  1654  				"exp": %d
  1655  			}`, valid.Unix()),
  1656  			wantErr: `oidc: verify token: oidc: expected audience in ["my-client" "random-client"] got ["not-my-client"]`,
  1657  		},
  1658  		{
  1659  			name: "nuanced audience validation using claim validation rules",
  1660  			options: Options{
  1661  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1662  					Issuer: apiserver.Issuer{
  1663  						URL:                 "https://auth.example.com",
  1664  						Audiences:           []string{"bar", "foo", "baz"},
  1665  						AudienceMatchPolicy: "MatchAny",
  1666  					},
  1667  					ClaimMappings: apiserver.ClaimMappings{
  1668  						Username: apiserver.PrefixedClaimOrExpression{
  1669  							Claim:  "username",
  1670  							Prefix: pointer.String(""),
  1671  						},
  1672  					},
  1673  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  1674  						{
  1675  							Expression: `sets.equivalent(claims.aud, ["bar", "foo", "baz"])`,
  1676  							Message:    "audience must exactly contain [bar, foo, baz]",
  1677  						},
  1678  					},
  1679  				},
  1680  				now: func() time.Time { return now },
  1681  			},
  1682  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1683  			pubKeys: []*jose.JSONWebKey{
  1684  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1685  			},
  1686  			claims: fmt.Sprintf(`{
  1687  				"iss": "https://auth.example.com",
  1688  				"aud": ["foo", "bar", "baz"],
  1689  				"azp": "not-my-client",
  1690  				"username": "jane",
  1691  				"exp": %d
  1692  			}`, valid.Unix()),
  1693  			want: &user.DefaultInfo{
  1694  				Name: "jane",
  1695  			},
  1696  		},
  1697  		{
  1698  			name: "audience validation using claim validation rules fails",
  1699  			options: Options{
  1700  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1701  					Issuer: apiserver.Issuer{
  1702  						URL:                 "https://auth.example.com",
  1703  						Audiences:           []string{"bar", "foo", "baz"},
  1704  						AudienceMatchPolicy: "MatchAny",
  1705  					},
  1706  					ClaimMappings: apiserver.ClaimMappings{
  1707  						Username: apiserver.PrefixedClaimOrExpression{
  1708  							Claim:  "username",
  1709  							Prefix: pointer.String(""),
  1710  						},
  1711  					},
  1712  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  1713  						{
  1714  							Expression: `sets.equivalent(claims.aud, ["bar", "foo", "baz"])`,
  1715  							Message:    "audience must exactly contain [bar, foo, baz]",
  1716  						},
  1717  					},
  1718  				},
  1719  				now: func() time.Time { return now },
  1720  			},
  1721  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1722  			pubKeys: []*jose.JSONWebKey{
  1723  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1724  			},
  1725  			claims: fmt.Sprintf(`{
  1726  				"iss": "https://auth.example.com",
  1727  				"aud": ["foo", "baz"],
  1728  				"azp": "not-my-client",
  1729  				"username": "jane",
  1730  				"exp": %d
  1731  			}`, valid.Unix()),
  1732  			wantErr: `oidc: error evaluating claim validation expression: validation expression 'sets.equivalent(claims.aud, ["bar", "foo", "baz"])' failed: audience must exactly contain [bar, foo, baz]`,
  1733  		},
  1734  		{
  1735  			name: "invalid-issuer",
  1736  			options: Options{
  1737  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1738  					Issuer: apiserver.Issuer{
  1739  						URL:       "https://auth.example.com",
  1740  						Audiences: []string{"my-client"},
  1741  					},
  1742  					ClaimMappings: apiserver.ClaimMappings{
  1743  						Username: apiserver.PrefixedClaimOrExpression{
  1744  							Claim:  "username",
  1745  							Prefix: pointer.String("prefix:"),
  1746  						},
  1747  					},
  1748  				},
  1749  				now: func() time.Time { return now },
  1750  			},
  1751  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1752  			pubKeys: []*jose.JSONWebKey{
  1753  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1754  			},
  1755  			claims: fmt.Sprintf(`{
  1756  				"iss": "https://example.com",
  1757  				"aud": "my-client",
  1758  				"username": "jane",
  1759  				"exp": %d
  1760  			}`, valid.Unix()),
  1761  			wantSkip: true,
  1762  		},
  1763  		{
  1764  			name: "username-prefix",
  1765  			options: Options{
  1766  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1767  					Issuer: apiserver.Issuer{
  1768  						URL:       "https://auth.example.com",
  1769  						Audiences: []string{"my-client"},
  1770  					},
  1771  					ClaimMappings: apiserver.ClaimMappings{
  1772  						Username: apiserver.PrefixedClaimOrExpression{
  1773  							Claim:  "username",
  1774  							Prefix: pointer.String("oidc:"),
  1775  						},
  1776  					},
  1777  				},
  1778  				now: func() time.Time { return now },
  1779  			},
  1780  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1781  			pubKeys: []*jose.JSONWebKey{
  1782  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1783  			},
  1784  			claims: fmt.Sprintf(`{
  1785  				"iss": "https://auth.example.com",
  1786  				"aud": "my-client",
  1787  				"username": "jane",
  1788  				"exp": %d
  1789  			}`, valid.Unix()),
  1790  			want: &user.DefaultInfo{
  1791  				Name: "oidc:jane",
  1792  			},
  1793  		},
  1794  		{
  1795  			name: "groups-prefix",
  1796  			options: Options{
  1797  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1798  					Issuer: apiserver.Issuer{
  1799  						URL:       "https://auth.example.com",
  1800  						Audiences: []string{"my-client"},
  1801  					},
  1802  					ClaimMappings: apiserver.ClaimMappings{
  1803  						Username: apiserver.PrefixedClaimOrExpression{
  1804  							Claim:  "username",
  1805  							Prefix: pointer.String("oidc:"),
  1806  						},
  1807  						Groups: apiserver.PrefixedClaimOrExpression{
  1808  							Claim:  "groups",
  1809  							Prefix: pointer.String("groups:"),
  1810  						},
  1811  					},
  1812  				},
  1813  				now: func() time.Time { return now },
  1814  			},
  1815  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1816  			pubKeys: []*jose.JSONWebKey{
  1817  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1818  			},
  1819  			claims: fmt.Sprintf(`{
  1820  				"iss": "https://auth.example.com",
  1821  				"aud": "my-client",
  1822  				"username": "jane",
  1823  				"groups": ["team1", "team2"],
  1824  				"exp": %d
  1825  			}`, valid.Unix()),
  1826  			want: &user.DefaultInfo{
  1827  				Name:   "oidc:jane",
  1828  				Groups: []string{"groups:team1", "groups:team2"},
  1829  			},
  1830  		},
  1831  		{
  1832  			name: "groups-prefix-distributed",
  1833  			options: Options{
  1834  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1835  					Issuer: apiserver.Issuer{
  1836  						URL:       "{{.URL}}",
  1837  						Audiences: []string{"my-client"},
  1838  					},
  1839  					ClaimMappings: apiserver.ClaimMappings{
  1840  						Username: apiserver.PrefixedClaimOrExpression{
  1841  							Claim:  "username",
  1842  							Prefix: pointer.String("oidc:"),
  1843  						},
  1844  						Groups: apiserver.PrefixedClaimOrExpression{
  1845  							Claim:  "groups",
  1846  							Prefix: pointer.String("groups:"),
  1847  						},
  1848  					},
  1849  				},
  1850  				now: func() time.Time { return now },
  1851  			},
  1852  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  1853  			pubKeys: []*jose.JSONWebKey{
  1854  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1855  			},
  1856  			claims: fmt.Sprintf(`{
  1857  				"iss": "{{.URL}}",
  1858  				"aud": "my-client",
  1859  				"username": "jane",
  1860  				"_claim_names": {
  1861  						"groups": "src1"
  1862  				},
  1863  				"_claim_sources": {
  1864  						"src1": {
  1865  								"endpoint": "{{.URL}}/groups",
  1866  								"access_token": "groups_token"
  1867  						}
  1868  				},
  1869  				"exp": %d
  1870  			}`, valid.Unix()),
  1871  			claimToResponseMap: map[string]string{
  1872  				"groups": fmt.Sprintf(`{
  1873  					"iss": "{{.URL}}",
  1874  				    "aud": "my-client",
  1875  					"groups": ["team1", "team2"],
  1876  					"exp": %d
  1877  			     }`, valid.Unix()),
  1878  			},
  1879  			openIDConfig: `{
  1880  					"issuer": "{{.URL}}",
  1881  					"jwks_uri": "{{.URL}}/.testing/keys"
  1882  			}`,
  1883  			want: &user.DefaultInfo{
  1884  				Name:   "oidc:jane",
  1885  				Groups: []string{"groups:team1", "groups:team2"},
  1886  			},
  1887  		},
  1888  		{
  1889  			name: "invalid-signing-alg",
  1890  			options: Options{
  1891  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1892  					Issuer: apiserver.Issuer{
  1893  						URL:       "https://auth.example.com",
  1894  						Audiences: []string{"my-client"},
  1895  					},
  1896  					ClaimMappings: apiserver.ClaimMappings{
  1897  						Username: apiserver.PrefixedClaimOrExpression{
  1898  							Claim:  "username",
  1899  							Prefix: pointer.String("prefix:"),
  1900  						},
  1901  					},
  1902  				},
  1903  				now: func() time.Time { return now },
  1904  			},
  1905  			// Correct key but invalid signature algorithm "PS256"
  1906  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.PS256),
  1907  			pubKeys: []*jose.JSONWebKey{
  1908  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  1909  			},
  1910  			claims: fmt.Sprintf(`{
  1911  				"iss": "https://auth.example.com",
  1912  				"aud": "my-client",
  1913  				"username": "jane",
  1914  				"exp": %d
  1915  			}`, valid.Unix()),
  1916  			wantErr: `oidc: verify token: oidc: id token signed with unsupported algorithm, expected ["RS256"] got "PS256"`,
  1917  		},
  1918  		{
  1919  			name: "ps256",
  1920  			options: Options{
  1921  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1922  					Issuer: apiserver.Issuer{
  1923  						URL:       "https://auth.example.com",
  1924  						Audiences: []string{"my-client"},
  1925  					},
  1926  					ClaimMappings: apiserver.ClaimMappings{
  1927  						Username: apiserver.PrefixedClaimOrExpression{
  1928  							Claim:  "username",
  1929  							Prefix: pointer.String(""),
  1930  						},
  1931  					},
  1932  				},
  1933  				SupportedSigningAlgs: []string{"PS256"},
  1934  				now:                  func() time.Time { return now },
  1935  			},
  1936  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.PS256),
  1937  			pubKeys: []*jose.JSONWebKey{
  1938  				loadRSAKey(t, "testdata/rsa_1.pem", jose.PS256),
  1939  			},
  1940  			claims: fmt.Sprintf(`{
  1941  				"iss": "https://auth.example.com",
  1942  				"aud": "my-client",
  1943  				"username": "jane",
  1944  				"exp": %d
  1945  			}`, valid.Unix()),
  1946  			want: &user.DefaultInfo{
  1947  				Name: "jane",
  1948  			},
  1949  		},
  1950  		{
  1951  			name: "es512",
  1952  			options: Options{
  1953  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1954  					Issuer: apiserver.Issuer{
  1955  						URL:       "https://auth.example.com",
  1956  						Audiences: []string{"my-client"},
  1957  					},
  1958  					ClaimMappings: apiserver.ClaimMappings{
  1959  						Username: apiserver.PrefixedClaimOrExpression{
  1960  							Claim:  "username",
  1961  							Prefix: pointer.String(""),
  1962  						},
  1963  					},
  1964  				},
  1965  				SupportedSigningAlgs: []string{"ES512"},
  1966  				now:                  func() time.Time { return now },
  1967  			},
  1968  			signingKey: loadECDSAPrivKey(t, "testdata/ecdsa_2.pem", jose.ES512),
  1969  			pubKeys: []*jose.JSONWebKey{
  1970  				loadECDSAKey(t, "testdata/ecdsa_1.pem", jose.ES512),
  1971  				loadECDSAKey(t, "testdata/ecdsa_2.pem", jose.ES512),
  1972  			},
  1973  			claims: fmt.Sprintf(`{
  1974  				"iss": "https://auth.example.com",
  1975  				"aud": "my-client",
  1976  				"username": "jane",
  1977  				"exp": %d
  1978  			}`, valid.Unix()),
  1979  			want: &user.DefaultInfo{
  1980  				Name: "jane",
  1981  			},
  1982  		},
  1983  		{
  1984  			name: "not-https",
  1985  			options: Options{
  1986  				JWTAuthenticator: apiserver.JWTAuthenticator{
  1987  					Issuer: apiserver.Issuer{
  1988  						URL:       "http://auth.example.com",
  1989  						Audiences: []string{"my-client"},
  1990  					},
  1991  					ClaimMappings: apiserver.ClaimMappings{
  1992  						Username: apiserver.PrefixedClaimOrExpression{
  1993  							Claim:  "username",
  1994  							Prefix: pointer.String("prefix:"),
  1995  						},
  1996  					},
  1997  				},
  1998  				now: func() time.Time { return now },
  1999  			},
  2000  			pubKeys: []*jose.JSONWebKey{
  2001  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2002  			},
  2003  			wantInitErr: `issuer.url: Invalid value: "http://auth.example.com": URL scheme must be https`,
  2004  		},
  2005  		{
  2006  			name: "no-username-claim",
  2007  			options: Options{
  2008  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2009  					Issuer: apiserver.Issuer{
  2010  						URL:       "https://auth.example.com",
  2011  						Audiences: []string{"my-client"},
  2012  					},
  2013  					ClaimMappings: apiserver.ClaimMappings{
  2014  						Username: apiserver.PrefixedClaimOrExpression{
  2015  							Prefix: pointer.String(""),
  2016  						},
  2017  					},
  2018  				},
  2019  				now: func() time.Time { return now },
  2020  			},
  2021  			pubKeys: []*jose.JSONWebKey{
  2022  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2023  			},
  2024  			wantInitErr: `claimMappings.username: Required value: claim or expression is required`,
  2025  		},
  2026  		{
  2027  			name: "invalid-sig-alg",
  2028  			options: Options{
  2029  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2030  					Issuer: apiserver.Issuer{
  2031  						URL:       "https://auth.example.com",
  2032  						Audiences: []string{"my-client"},
  2033  					},
  2034  					ClaimMappings: apiserver.ClaimMappings{
  2035  						Username: apiserver.PrefixedClaimOrExpression{
  2036  							Claim:  "username",
  2037  							Prefix: pointer.String("prefix:"),
  2038  						},
  2039  					},
  2040  				},
  2041  				SupportedSigningAlgs: []string{"HS256"},
  2042  				now:                  func() time.Time { return now },
  2043  			},
  2044  			pubKeys: []*jose.JSONWebKey{
  2045  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2046  			},
  2047  			wantInitErr: `oidc: unsupported signing alg: "HS256"`,
  2048  		},
  2049  		{
  2050  			name: "client and ca mutually exclusive",
  2051  			options: Options{
  2052  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2053  					Issuer: apiserver.Issuer{
  2054  						URL:       "https://auth.example.com",
  2055  						Audiences: []string{"my-client"},
  2056  					},
  2057  					ClaimMappings: apiserver.ClaimMappings{
  2058  						Username: apiserver.PrefixedClaimOrExpression{
  2059  							Claim:  "username",
  2060  							Prefix: pointer.String("prefix:"),
  2061  						},
  2062  					},
  2063  				},
  2064  				SupportedSigningAlgs: []string{"RS256"},
  2065  				now:                  func() time.Time { return now },
  2066  				Client:               http.DefaultClient, // test automatically sets CAContentProvider
  2067  			},
  2068  			pubKeys: []*jose.JSONWebKey{
  2069  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2070  			},
  2071  			wantInitErr: "oidc: Client and CAContentProvider are mutually exclusive",
  2072  		},
  2073  		{
  2074  			name: "keyset and discovery URL mutually exclusive",
  2075  			options: Options{
  2076  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2077  					Issuer: apiserver.Issuer{
  2078  						URL:          "https://auth.example.com",
  2079  						DiscoveryURL: "https://auth.example.com/foo",
  2080  						Audiences:    []string{"my-client"},
  2081  					},
  2082  					ClaimMappings: apiserver.ClaimMappings{
  2083  						Username: apiserver.PrefixedClaimOrExpression{
  2084  							Claim:  "username",
  2085  							Prefix: pointer.String("prefix:"),
  2086  						},
  2087  					},
  2088  				},
  2089  				SupportedSigningAlgs: []string{"RS256"},
  2090  				now:                  func() time.Time { return now },
  2091  				KeySet:               &staticKeySet{},
  2092  			},
  2093  			pubKeys: []*jose.JSONWebKey{
  2094  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2095  			},
  2096  			wantInitErr: "oidc: KeySet and DiscoveryURL are mutually exclusive",
  2097  		},
  2098  		{
  2099  			name: "health check failure",
  2100  			options: Options{
  2101  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2102  					Issuer: apiserver.Issuer{
  2103  						URL:       "https://this-will-not-work.notatld",
  2104  						Audiences: []string{"my-client"},
  2105  					},
  2106  					ClaimMappings: apiserver.ClaimMappings{
  2107  						Username: apiserver.PrefixedClaimOrExpression{
  2108  							Claim:  "username",
  2109  							Prefix: pointer.String("prefix:"),
  2110  						},
  2111  					},
  2112  				},
  2113  				SupportedSigningAlgs: []string{"RS256"},
  2114  			},
  2115  			fetchKeysFromRemote: true,
  2116  			wantHealthErrPrefix: `oidc: authenticator for issuer "https://this-will-not-work.notatld" is not healthy: Get "https://this-will-not-work.notatld/.well-known/openid-configuration": dial tcp: lookup this-will-not-work.notatld`,
  2117  		},
  2118  		{
  2119  			name: "accounts.google.com issuer",
  2120  			options: Options{
  2121  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2122  					Issuer: apiserver.Issuer{
  2123  						URL:       "https://accounts.google.com",
  2124  						Audiences: []string{"my-client"},
  2125  					},
  2126  					ClaimMappings: apiserver.ClaimMappings{
  2127  						Username: apiserver.PrefixedClaimOrExpression{
  2128  							Claim:  "email",
  2129  							Prefix: pointer.String(""),
  2130  						},
  2131  					},
  2132  				},
  2133  				now: func() time.Time { return now },
  2134  			},
  2135  			claims: fmt.Sprintf(`{
  2136  				"iss": "accounts.google.com",
  2137  				"email": "thomas.jefferson@gmail.com",
  2138  				"aud": "my-client",
  2139  				"exp": %d
  2140  			}`, valid.Unix()),
  2141  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2142  			pubKeys: []*jose.JSONWebKey{
  2143  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2144  			},
  2145  			want: &user.DefaultInfo{
  2146  				Name: "thomas.jefferson@gmail.com",
  2147  			},
  2148  		},
  2149  		{
  2150  			name: "good token with bad client id",
  2151  			options: Options{
  2152  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2153  					Issuer: apiserver.Issuer{
  2154  						URL:       "https://auth.example.com",
  2155  						Audiences: []string{"my-client"},
  2156  					},
  2157  					ClaimMappings: apiserver.ClaimMappings{
  2158  						Username: apiserver.PrefixedClaimOrExpression{
  2159  							Claim:  "username",
  2160  							Prefix: pointer.String("prefix:"),
  2161  						},
  2162  					},
  2163  				},
  2164  				now: func() time.Time { return now },
  2165  			},
  2166  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2167  			pubKeys: []*jose.JSONWebKey{
  2168  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2169  			},
  2170  			claims: fmt.Sprintf(`{
  2171  				"iss": "https://auth.example.com",
  2172  				"aud": "my-wrong-client",
  2173  				"username": "jane",
  2174  				"exp": %d
  2175  			}`, valid.Unix()),
  2176  			wantErr: `oidc: verify token: oidc: expected audience "my-client" got ["my-wrong-client"]`,
  2177  		},
  2178  		{
  2179  			name: "user validation rule fails for user.username",
  2180  			options: Options{
  2181  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2182  					Issuer: apiserver.Issuer{
  2183  						URL:       "https://auth.example.com",
  2184  						Audiences: []string{"my-client"},
  2185  					},
  2186  					ClaimMappings: apiserver.ClaimMappings{
  2187  						Username: apiserver.PrefixedClaimOrExpression{
  2188  							Claim:  "username",
  2189  							Prefix: pointer.String("system:"),
  2190  						},
  2191  					},
  2192  					UserValidationRules: []apiserver.UserValidationRule{
  2193  						{
  2194  							Expression: "!user.username.startsWith('system:')",
  2195  							Message:    "username cannot used reserved system: prefix",
  2196  						},
  2197  					},
  2198  				},
  2199  				now: func() time.Time { return now },
  2200  			},
  2201  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2202  			pubKeys: []*jose.JSONWebKey{
  2203  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2204  			},
  2205  			claims: fmt.Sprintf(`{
  2206  				"iss": "https://auth.example.com",
  2207  				"aud": "my-client",
  2208  				"username": "jane",
  2209  				"exp": %d
  2210  			}`, valid.Unix()),
  2211  			wantErr: `oidc: error evaluating user info validation rule: validation expression '!user.username.startsWith('system:')' failed: username cannot used reserved system: prefix`,
  2212  		},
  2213  		{
  2214  			name: "user validation rule fails for user.groups",
  2215  			options: Options{
  2216  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2217  					Issuer: apiserver.Issuer{
  2218  						URL:       "https://auth.example.com",
  2219  						Audiences: []string{"my-client"},
  2220  					},
  2221  					ClaimMappings: apiserver.ClaimMappings{
  2222  						Username: apiserver.PrefixedClaimOrExpression{
  2223  							Expression: "claims.username",
  2224  						},
  2225  						Groups: apiserver.PrefixedClaimOrExpression{
  2226  							Claim:  "groups",
  2227  							Prefix: pointer.String("system:"),
  2228  						},
  2229  					},
  2230  					UserValidationRules: []apiserver.UserValidationRule{
  2231  						{
  2232  							Expression: "user.groups.all(group, !group.startsWith('system:'))",
  2233  							Message:    "groups cannot used reserved system: prefix",
  2234  						},
  2235  					},
  2236  				},
  2237  				now: func() time.Time { return now },
  2238  			},
  2239  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2240  			pubKeys: []*jose.JSONWebKey{
  2241  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2242  			},
  2243  			claims: fmt.Sprintf(`{
  2244  				"iss": "https://auth.example.com",
  2245  				"aud": "my-client",
  2246  				"username": "jane",
  2247  				"exp": %d,
  2248  				"groups": ["team1", "team2"]
  2249  			}`, valid.Unix()),
  2250  			wantErr: `oidc: error evaluating user info validation rule: validation expression 'user.groups.all(group, !group.startsWith('system:'))' failed: groups cannot used reserved system: prefix`,
  2251  		},
  2252  		{
  2253  			name: "claim validation rule with expression fails",
  2254  			options: Options{
  2255  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2256  					Issuer: apiserver.Issuer{
  2257  						URL:       "https://auth.example.com",
  2258  						Audiences: []string{"my-client"},
  2259  					},
  2260  					ClaimMappings: apiserver.ClaimMappings{
  2261  						Username: apiserver.PrefixedClaimOrExpression{
  2262  							Claim:  "username",
  2263  							Prefix: pointer.String(""),
  2264  						},
  2265  					},
  2266  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  2267  						{
  2268  							Expression: `claims.hd == "example.com"`,
  2269  							Message:    "hd claim must be example.com",
  2270  						},
  2271  					},
  2272  				},
  2273  				now: func() time.Time { return now },
  2274  			},
  2275  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2276  			pubKeys: []*jose.JSONWebKey{
  2277  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2278  			},
  2279  			claims: fmt.Sprintf(`{
  2280  				"iss": "https://auth.example.com",
  2281  				"aud": "my-client",
  2282  				"username": "jane",
  2283  				"exp": %d
  2284  			}`, valid.Unix()),
  2285  			wantErr: `oidc: error evaluating claim validation expression: expression 'claims.hd == "example.com"' resulted in error: no such key: hd`,
  2286  		},
  2287  		{
  2288  			name: "claim validation rule with expression",
  2289  			options: Options{
  2290  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2291  					Issuer: apiserver.Issuer{
  2292  						URL:       "https://auth.example.com",
  2293  						Audiences: []string{"my-client"},
  2294  					},
  2295  					ClaimMappings: apiserver.ClaimMappings{
  2296  						Username: apiserver.PrefixedClaimOrExpression{
  2297  							Claim:  "username",
  2298  							Prefix: pointer.String(""),
  2299  						},
  2300  					},
  2301  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  2302  						{
  2303  							Expression: `claims.hd == "example.com"`,
  2304  							Message:    "hd claim must be example.com",
  2305  						},
  2306  					},
  2307  				},
  2308  				now: func() time.Time { return now },
  2309  			},
  2310  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2311  			pubKeys: []*jose.JSONWebKey{
  2312  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2313  			},
  2314  			claims: fmt.Sprintf(`{
  2315  				"iss": "https://auth.example.com",
  2316  				"aud": "my-client",
  2317  				"username": "jane",
  2318  				"exp": %d,
  2319  				"hd": "example.com"
  2320  			}`, valid.Unix()),
  2321  			want: &user.DefaultInfo{
  2322  				Name: "jane",
  2323  			},
  2324  		},
  2325  		{
  2326  			name: "claim validation rule with expression and nested claims",
  2327  			options: Options{
  2328  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2329  					Issuer: apiserver.Issuer{
  2330  						URL:       "https://auth.example.com",
  2331  						Audiences: []string{"my-client"},
  2332  					},
  2333  					ClaimMappings: apiserver.ClaimMappings{
  2334  						Username: apiserver.PrefixedClaimOrExpression{
  2335  							Claim:  "username",
  2336  							Prefix: pointer.String(""),
  2337  						},
  2338  					},
  2339  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  2340  						{
  2341  							Expression: `claims.foo.bar == "baz"`,
  2342  							Message:    "foo.bar claim must be baz",
  2343  						},
  2344  					},
  2345  				},
  2346  				now: func() time.Time { return now },
  2347  			},
  2348  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2349  			pubKeys: []*jose.JSONWebKey{
  2350  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2351  			},
  2352  			claims: fmt.Sprintf(`{
  2353  				"iss": "https://auth.example.com",
  2354  				"aud": "my-client",
  2355  				"username": "jane",
  2356  				"exp": %d,
  2357  				"hd": "example.com",
  2358  				"foo": {
  2359  					"bar": "baz"
  2360  				}
  2361  			}`, valid.Unix()),
  2362  			want: &user.DefaultInfo{
  2363  				Name: "jane",
  2364  			},
  2365  		},
  2366  		{
  2367  			name: "claim validation rule with mix of expression and claim",
  2368  			options: Options{
  2369  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2370  					Issuer: apiserver.Issuer{
  2371  						URL:       "https://auth.example.com",
  2372  						Audiences: []string{"my-client"},
  2373  					},
  2374  					ClaimMappings: apiserver.ClaimMappings{
  2375  						Username: apiserver.PrefixedClaimOrExpression{
  2376  							Claim:  "username",
  2377  							Prefix: pointer.String(""),
  2378  						},
  2379  					},
  2380  					ClaimValidationRules: []apiserver.ClaimValidationRule{
  2381  						{
  2382  							Expression: `claims.foo.bar == "baz"`,
  2383  							Message:    "foo.bar claim must be baz",
  2384  						},
  2385  						{
  2386  							Claim:         "hd",
  2387  							RequiredValue: "example.com",
  2388  						},
  2389  					},
  2390  				},
  2391  				now: func() time.Time { return now },
  2392  			},
  2393  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2394  			pubKeys: []*jose.JSONWebKey{
  2395  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2396  			},
  2397  			claims: fmt.Sprintf(`{
  2398  				"iss": "https://auth.example.com",
  2399  				"aud": "my-client",
  2400  				"username": "jane",
  2401  				"exp": %d,
  2402  				"hd": "example.com",
  2403  				"foo": {
  2404  					"bar": "baz"
  2405  				}
  2406  			}`, valid.Unix()),
  2407  			want: &user.DefaultInfo{
  2408  				Name: "jane",
  2409  			},
  2410  		},
  2411  		{
  2412  			name: "username claim mapping with expression",
  2413  			options: Options{
  2414  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2415  					Issuer: apiserver.Issuer{
  2416  						URL:       "https://auth.example.com",
  2417  						Audiences: []string{"my-client"},
  2418  					},
  2419  					ClaimMappings: apiserver.ClaimMappings{
  2420  						Username: apiserver.PrefixedClaimOrExpression{
  2421  							Expression: "claims.username",
  2422  						},
  2423  					},
  2424  				},
  2425  				now: func() time.Time { return now },
  2426  			},
  2427  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2428  			pubKeys: []*jose.JSONWebKey{
  2429  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2430  			},
  2431  			claims: fmt.Sprintf(`{
  2432  				"iss": "https://auth.example.com",
  2433  				"aud": "my-client",
  2434  				"username": "jane",
  2435  				"exp": %d
  2436  			}`, valid.Unix()),
  2437  			want: &user.DefaultInfo{
  2438  				Name: "jane",
  2439  			},
  2440  		},
  2441  		{
  2442  			name: "username claim mapping with expression and nested claim",
  2443  			options: Options{
  2444  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2445  					Issuer: apiserver.Issuer{
  2446  						URL:       "https://auth.example.com",
  2447  						Audiences: []string{"my-client"},
  2448  					},
  2449  					ClaimMappings: apiserver.ClaimMappings{
  2450  						Username: apiserver.PrefixedClaimOrExpression{
  2451  							Expression: "claims.foo.username",
  2452  						},
  2453  					},
  2454  				},
  2455  				now: func() time.Time { return now },
  2456  			},
  2457  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2458  			pubKeys: []*jose.JSONWebKey{
  2459  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2460  			},
  2461  			claims: fmt.Sprintf(`{
  2462  				"iss": "https://auth.example.com",
  2463  				"aud": "my-client",
  2464  				"username": "jane",
  2465  				"exp": %d,
  2466  				"foo": {
  2467  					"username": "jane"
  2468  				}
  2469  			}`, valid.Unix()),
  2470  			want: &user.DefaultInfo{
  2471  				Name: "jane",
  2472  			},
  2473  		},
  2474  		{
  2475  			name: "groups claim mapping with expression",
  2476  			options: Options{
  2477  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2478  					Issuer: apiserver.Issuer{
  2479  						URL:       "https://auth.example.com",
  2480  						Audiences: []string{"my-client"},
  2481  					},
  2482  					ClaimMappings: apiserver.ClaimMappings{
  2483  						Username: apiserver.PrefixedClaimOrExpression{
  2484  							Expression: "claims.username",
  2485  						},
  2486  						Groups: apiserver.PrefixedClaimOrExpression{
  2487  							Expression: "claims.groups",
  2488  						},
  2489  					},
  2490  				},
  2491  				now: func() time.Time { return now },
  2492  			},
  2493  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2494  			pubKeys: []*jose.JSONWebKey{
  2495  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2496  			},
  2497  			claims: fmt.Sprintf(`{
  2498  				"iss": "https://auth.example.com",
  2499  				"aud": "my-client",
  2500  				"username": "jane",
  2501  				"groups": ["team1", "team2"],
  2502  				"exp": %d
  2503  			}`, valid.Unix()),
  2504  			want: &user.DefaultInfo{
  2505  				Name:   "jane",
  2506  				Groups: []string{"team1", "team2"},
  2507  			},
  2508  		},
  2509  		{
  2510  			name: "groups claim with expression",
  2511  			options: Options{
  2512  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2513  					Issuer: apiserver.Issuer{
  2514  						URL:       "https://auth.example.com",
  2515  						Audiences: []string{"my-client"},
  2516  					},
  2517  					ClaimMappings: apiserver.ClaimMappings{
  2518  						Username: apiserver.PrefixedClaimOrExpression{
  2519  							Claim:  "username",
  2520  							Prefix: pointer.String("oidc:"),
  2521  						},
  2522  						Groups: apiserver.PrefixedClaimOrExpression{
  2523  							Expression: `(claims.roles.split(",") + claims.other_roles.split(",")).map(role, "groups:" + role)`,
  2524  						},
  2525  					},
  2526  				},
  2527  				now: func() time.Time { return now },
  2528  			},
  2529  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2530  			pubKeys: []*jose.JSONWebKey{
  2531  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2532  			},
  2533  			claims: fmt.Sprintf(`{
  2534  				"iss": "https://auth.example.com",
  2535  				"aud": "my-client",
  2536  				"username": "jane",
  2537  				"roles": "foo,bar",
  2538  				"other_roles": "baz,qux",
  2539  				"exp": %d
  2540  			}`, valid.Unix()),
  2541  			want: &user.DefaultInfo{
  2542  				Name:   "oidc:jane",
  2543  				Groups: []string{"groups:foo", "groups:bar", "groups:baz", "groups:qux"},
  2544  			},
  2545  		},
  2546  		{
  2547  			name: "uid claim mapping with expression",
  2548  			options: Options{
  2549  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2550  					Issuer: apiserver.Issuer{
  2551  						URL:       "https://auth.example.com",
  2552  						Audiences: []string{"my-client"},
  2553  					},
  2554  					ClaimMappings: apiserver.ClaimMappings{
  2555  						Username: apiserver.PrefixedClaimOrExpression{
  2556  							Expression: "claims.username",
  2557  						},
  2558  						Groups: apiserver.PrefixedClaimOrExpression{
  2559  							Expression: "claims.groups",
  2560  						},
  2561  						UID: apiserver.ClaimOrExpression{
  2562  							Expression: "claims.uid",
  2563  						},
  2564  					},
  2565  				},
  2566  				now: func() time.Time { return now },
  2567  			},
  2568  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2569  			pubKeys: []*jose.JSONWebKey{
  2570  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2571  			},
  2572  			claims: fmt.Sprintf(`{
  2573  				"iss": "https://auth.example.com",
  2574  				"aud": "my-client",
  2575  				"username": "jane",
  2576  				"groups": ["team1", "team2"],
  2577  				"exp": %d,
  2578  				"uid": "1234"
  2579  			}`, valid.Unix()),
  2580  			want: &user.DefaultInfo{
  2581  				Name:   "jane",
  2582  				Groups: []string{"team1", "team2"},
  2583  				UID:    "1234",
  2584  			},
  2585  		},
  2586  		{
  2587  			name: "uid claim mapping with claim",
  2588  			options: Options{
  2589  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2590  					Issuer: apiserver.Issuer{
  2591  						URL:       "https://auth.example.com",
  2592  						Audiences: []string{"my-client"},
  2593  					},
  2594  					ClaimMappings: apiserver.ClaimMappings{
  2595  						Username: apiserver.PrefixedClaimOrExpression{
  2596  							Expression: "claims.username",
  2597  						},
  2598  						Groups: apiserver.PrefixedClaimOrExpression{
  2599  							Expression: "claims.groups",
  2600  						},
  2601  						UID: apiserver.ClaimOrExpression{
  2602  							Claim: "uid",
  2603  						},
  2604  					},
  2605  				},
  2606  				now: func() time.Time { return now },
  2607  			},
  2608  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2609  			pubKeys: []*jose.JSONWebKey{
  2610  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2611  			},
  2612  			claims: fmt.Sprintf(`{
  2613  				"iss": "https://auth.example.com",
  2614  				"aud": "my-client",
  2615  				"username": "jane",
  2616  				"groups": ["team1", "team2"],
  2617  				"exp": %d,
  2618  				"uid": "1234"
  2619  			}`, valid.Unix()),
  2620  			want: &user.DefaultInfo{
  2621  				Name:   "jane",
  2622  				Groups: []string{"team1", "team2"},
  2623  				UID:    "1234",
  2624  			},
  2625  		},
  2626  		{
  2627  			name: "extra claim mapping with expression",
  2628  			options: Options{
  2629  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2630  					Issuer: apiserver.Issuer{
  2631  						URL:       "https://auth.example.com",
  2632  						Audiences: []string{"my-client"},
  2633  					},
  2634  					ClaimMappings: apiserver.ClaimMappings{
  2635  						Username: apiserver.PrefixedClaimOrExpression{
  2636  							Expression: "claims.username",
  2637  						},
  2638  						Groups: apiserver.PrefixedClaimOrExpression{
  2639  							Expression: "claims.groups",
  2640  						},
  2641  						UID: apiserver.ClaimOrExpression{
  2642  							Expression: "claims.uid",
  2643  						},
  2644  						Extra: []apiserver.ExtraMapping{
  2645  							{
  2646  								Key:             "example.org/foo",
  2647  								ValueExpression: "claims.foo",
  2648  							},
  2649  							{
  2650  								Key:             "example.org/bar",
  2651  								ValueExpression: "claims.bar",
  2652  							},
  2653  						},
  2654  					},
  2655  				},
  2656  				now: func() time.Time { return now },
  2657  			},
  2658  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2659  			pubKeys: []*jose.JSONWebKey{
  2660  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2661  			},
  2662  			claims: fmt.Sprintf(`{
  2663  				"iss": "https://auth.example.com",
  2664  				"aud": "my-client",
  2665  				"username": "jane",
  2666  				"groups": ["team1", "team2"],
  2667  				"exp": %d,
  2668  				"uid": "1234",
  2669  				"foo": "bar",
  2670  				"bar": [
  2671  					"baz",
  2672  					"qux"
  2673  				]
  2674  			}`, valid.Unix()),
  2675  			want: &user.DefaultInfo{
  2676  				Name:   "jane",
  2677  				Groups: []string{"team1", "team2"},
  2678  				UID:    "1234",
  2679  				Extra: map[string][]string{
  2680  					"example.org/foo": {"bar"},
  2681  					"example.org/bar": {"baz", "qux"},
  2682  				},
  2683  			},
  2684  		},
  2685  		{
  2686  			name: "extra claim mapping, value derived from claim value",
  2687  			options: Options{
  2688  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2689  					Issuer: apiserver.Issuer{
  2690  						URL:       "https://auth.example.com",
  2691  						Audiences: []string{"my-client"},
  2692  					},
  2693  					ClaimMappings: apiserver.ClaimMappings{
  2694  						Username: apiserver.PrefixedClaimOrExpression{
  2695  							Expression: "claims.username",
  2696  						},
  2697  						Extra: []apiserver.ExtraMapping{
  2698  							{
  2699  								Key:             "example.org/admin",
  2700  								ValueExpression: `(has(claims.is_admin) && claims.is_admin) ? "true":""`,
  2701  							},
  2702  							{
  2703  								Key:             "example.org/admin_1",
  2704  								ValueExpression: `claims.?is_admin.orValue(false) == true ? "true":""`,
  2705  							},
  2706  							{
  2707  								Key:             "example.org/non_existent",
  2708  								ValueExpression: `claims.?non_existent.orValue("default") == "default" ? "true":""`,
  2709  							},
  2710  						},
  2711  					},
  2712  				},
  2713  				now: func() time.Time { return now },
  2714  			},
  2715  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2716  			pubKeys: []*jose.JSONWebKey{
  2717  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2718  			},
  2719  			claims: fmt.Sprintf(`{
  2720  				"iss": "https://auth.example.com",
  2721  				"aud": "my-client",
  2722  				"username": "jane",
  2723  				"exp": %d,
  2724  				"is_admin": true
  2725  			}`, valid.Unix()),
  2726  			want: &user.DefaultInfo{
  2727  				Name: "jane",
  2728  				Extra: map[string][]string{
  2729  					"example.org/admin":        {"true"},
  2730  					"example.org/admin_1":      {"true"},
  2731  					"example.org/non_existent": {"true"},
  2732  				},
  2733  			},
  2734  		},
  2735  		{
  2736  			name: "hardcoded extra claim mapping",
  2737  			options: Options{
  2738  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2739  					Issuer: apiserver.Issuer{
  2740  						URL:       "https://auth.example.com",
  2741  						Audiences: []string{"my-client"},
  2742  					},
  2743  					ClaimMappings: apiserver.ClaimMappings{
  2744  						Username: apiserver.PrefixedClaimOrExpression{
  2745  							Expression: "claims.username",
  2746  						},
  2747  						Extra: []apiserver.ExtraMapping{
  2748  							{
  2749  								Key:             "example.org/admin",
  2750  								ValueExpression: `"true"`,
  2751  							},
  2752  						},
  2753  					},
  2754  				},
  2755  				now: func() time.Time { return now },
  2756  			},
  2757  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2758  			pubKeys: []*jose.JSONWebKey{
  2759  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2760  			},
  2761  			claims: fmt.Sprintf(`{
  2762  				"iss": "https://auth.example.com",
  2763  				"aud": "my-client",
  2764  				"username": "jane",
  2765  				"exp": %d,
  2766  				"is_admin": true
  2767  			}`, valid.Unix()),
  2768  			want: &user.DefaultInfo{
  2769  				Name: "jane",
  2770  				Extra: map[string][]string{
  2771  					"example.org/admin": {"true"},
  2772  				},
  2773  			},
  2774  		},
  2775  		{
  2776  			name: "extra claim mapping, multiple expressions for same key",
  2777  			options: Options{
  2778  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2779  					Issuer: apiserver.Issuer{
  2780  						URL:       "https://auth.example.com",
  2781  						Audiences: []string{"my-client"},
  2782  					},
  2783  					ClaimMappings: apiserver.ClaimMappings{
  2784  						Username: apiserver.PrefixedClaimOrExpression{
  2785  							Expression: "claims.username",
  2786  						},
  2787  						Groups: apiserver.PrefixedClaimOrExpression{
  2788  							Expression: "claims.groups",
  2789  						},
  2790  						UID: apiserver.ClaimOrExpression{
  2791  							Expression: "claims.uid",
  2792  						},
  2793  						Extra: []apiserver.ExtraMapping{
  2794  							{
  2795  								Key:             "example.org/foo",
  2796  								ValueExpression: "claims.foo",
  2797  							},
  2798  							{
  2799  								Key:             "example.org/bar",
  2800  								ValueExpression: "claims.bar",
  2801  							},
  2802  							{
  2803  								Key:             "example.org/foo",
  2804  								ValueExpression: "claims.bar",
  2805  							},
  2806  						},
  2807  					},
  2808  				},
  2809  				now: func() time.Time { return now },
  2810  			},
  2811  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2812  			pubKeys: []*jose.JSONWebKey{
  2813  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2814  			},
  2815  			claims: fmt.Sprintf(`{
  2816  				"iss": "https://auth.example.com",
  2817  				"aud": "my-client",
  2818  				"username": "jane",
  2819  				"groups": ["team1", "team2"],
  2820  				"exp": %d,
  2821  				"uid": "1234",
  2822  				"foo": "bar",
  2823  				"bar": [
  2824  					"baz",
  2825  					"qux"
  2826  				]
  2827  			}`, valid.Unix()),
  2828  			wantInitErr: `claimMappings.extra[2].key: Duplicate value: "example.org/foo"`,
  2829  		},
  2830  		{
  2831  			name: "disallowed issuer via configured value",
  2832  			options: Options{
  2833  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2834  					Issuer: apiserver.Issuer{
  2835  						URL:       "https://auth.example.com",
  2836  						Audiences: []string{"my-client"},
  2837  					},
  2838  					ClaimMappings: apiserver.ClaimMappings{
  2839  						Username: apiserver.PrefixedClaimOrExpression{
  2840  							Expression: "claims.username",
  2841  						},
  2842  						Groups: apiserver.PrefixedClaimOrExpression{
  2843  							Expression: "claims.groups",
  2844  						},
  2845  						UID: apiserver.ClaimOrExpression{
  2846  							Expression: "claims.uid",
  2847  						},
  2848  						Extra: []apiserver.ExtraMapping{
  2849  							{
  2850  								Key:             "example.org/foo",
  2851  								ValueExpression: "claims.foo",
  2852  							},
  2853  							{
  2854  								Key:             "example.org/bar",
  2855  								ValueExpression: "claims.bar",
  2856  							},
  2857  						},
  2858  					},
  2859  				},
  2860  				DisallowedIssuers: []string{"https://auth.example.com"},
  2861  				now:               func() time.Time { return now },
  2862  			},
  2863  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2864  			pubKeys: []*jose.JSONWebKey{
  2865  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2866  			},
  2867  			claims: fmt.Sprintf(`{
  2868  				"iss": "https://auth.example.com",
  2869  				"aud": "my-client",
  2870  				"username": "jane",
  2871  				"groups": ["team1", "team2"],
  2872  				"exp": %d,
  2873  				"uid": "1234",
  2874  				"foo": "bar",
  2875  				"bar": [
  2876  					"baz",
  2877  					"qux"
  2878  				]
  2879  			}`, valid.Unix()),
  2880  			wantInitErr: `issuer.url: Invalid value: "https://auth.example.com": URL must not overlap with disallowed issuers: [https://auth.example.com]`,
  2881  		},
  2882  		{
  2883  			name: "extra claim mapping, empty string value for key",
  2884  			options: Options{
  2885  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2886  					Issuer: apiserver.Issuer{
  2887  						URL:       "https://auth.example.com",
  2888  						Audiences: []string{"my-client"},
  2889  					},
  2890  					ClaimMappings: apiserver.ClaimMappings{
  2891  						Username: apiserver.PrefixedClaimOrExpression{
  2892  							Expression: "claims.username",
  2893  						},
  2894  						Groups: apiserver.PrefixedClaimOrExpression{
  2895  							Expression: "claims.groups",
  2896  						},
  2897  						UID: apiserver.ClaimOrExpression{
  2898  							Expression: "claims.uid",
  2899  						},
  2900  						Extra: []apiserver.ExtraMapping{
  2901  							{
  2902  								Key:             "example.org/foo",
  2903  								ValueExpression: "claims.foo",
  2904  							},
  2905  							{
  2906  								Key:             "example.org/bar",
  2907  								ValueExpression: "claims.bar",
  2908  							},
  2909  						},
  2910  					},
  2911  				},
  2912  				now: func() time.Time { return now },
  2913  			},
  2914  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2915  			pubKeys: []*jose.JSONWebKey{
  2916  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2917  			},
  2918  			claims: fmt.Sprintf(`{
  2919  				"iss": "https://auth.example.com",
  2920  				"aud": "my-client",
  2921  				"username": "jane",
  2922  				"groups": ["team1", "team2"],
  2923  				"exp": %d,
  2924  				"uid": "1234",
  2925  				"foo": "",
  2926  				"bar": [
  2927  					"baz",
  2928  					"qux"
  2929  				]
  2930  			}`, valid.Unix()),
  2931  			want: &user.DefaultInfo{
  2932  				Name:   "jane",
  2933  				Groups: []string{"team1", "team2"},
  2934  				UID:    "1234",
  2935  				Extra: map[string][]string{
  2936  					"example.org/bar": {"baz", "qux"},
  2937  				},
  2938  			},
  2939  		},
  2940  		{
  2941  			name: "extra claim mapping with user validation rule succeeds",
  2942  			options: Options{
  2943  				JWTAuthenticator: apiserver.JWTAuthenticator{
  2944  					Issuer: apiserver.Issuer{
  2945  						URL:       "https://auth.example.com",
  2946  						Audiences: []string{"my-client"},
  2947  					},
  2948  					ClaimMappings: apiserver.ClaimMappings{
  2949  						Username: apiserver.PrefixedClaimOrExpression{
  2950  							Expression: "claims.username",
  2951  						},
  2952  						Groups: apiserver.PrefixedClaimOrExpression{
  2953  							Expression: "claims.groups",
  2954  						},
  2955  						UID: apiserver.ClaimOrExpression{
  2956  							Expression: "claims.uid",
  2957  						},
  2958  						Extra: []apiserver.ExtraMapping{
  2959  							{
  2960  								Key:             "example.org/foo",
  2961  								ValueExpression: "'bar'",
  2962  							},
  2963  							{
  2964  								Key:             "example.org/baz",
  2965  								ValueExpression: "claims.baz",
  2966  							},
  2967  						},
  2968  					},
  2969  					UserValidationRules: []apiserver.UserValidationRule{
  2970  						{
  2971  							Expression: "'bar' in user.extra['example.org/foo'] && 'qux' in user.extra['example.org/baz']",
  2972  							Message:    "example.org/foo must be bar and example.org/baz must be qux",
  2973  						},
  2974  					},
  2975  				},
  2976  				now: func() time.Time { return now },
  2977  			},
  2978  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  2979  			pubKeys: []*jose.JSONWebKey{
  2980  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  2981  			},
  2982  			claims: fmt.Sprintf(`{
  2983  				"iss": "https://auth.example.com",
  2984  				"aud": "my-client",
  2985  				"username": "jane",
  2986  				"groups": ["team1", "team2"],
  2987  				"exp": %d,
  2988  				"uid": "1234",
  2989  				"baz": "qux"
  2990  			}`, valid.Unix()),
  2991  			want: &user.DefaultInfo{
  2992  				Name:   "jane",
  2993  				Groups: []string{"team1", "team2"},
  2994  				UID:    "1234",
  2995  				Extra: map[string][]string{
  2996  					"example.org/foo": {"bar"},
  2997  					"example.org/baz": {"qux"},
  2998  				},
  2999  			},
  3000  		},
  3001  		{
  3002  			name: "groups expression returns null",
  3003  			options: Options{
  3004  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3005  					Issuer: apiserver.Issuer{
  3006  						URL:       "https://auth.example.com",
  3007  						Audiences: []string{"my-client"},
  3008  					},
  3009  					ClaimMappings: apiserver.ClaimMappings{
  3010  						Username: apiserver.PrefixedClaimOrExpression{
  3011  							Expression: "claims.username",
  3012  						},
  3013  						Groups: apiserver.PrefixedClaimOrExpression{
  3014  							Expression: "claims.groups",
  3015  						},
  3016  					},
  3017  				},
  3018  				now: func() time.Time { return now },
  3019  			},
  3020  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3021  			pubKeys: []*jose.JSONWebKey{
  3022  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3023  			},
  3024  			claims: fmt.Sprintf(`{
  3025  				"iss": "https://auth.example.com",
  3026  				"aud": "my-client",
  3027  				"username": "jane",
  3028  				"groups": null,
  3029  				"exp": %d,
  3030  				"uid": "1234",
  3031  				"baz": "qux"
  3032  			}`, valid.Unix()),
  3033  			want: &user.DefaultInfo{
  3034  				Name: "jane",
  3035  			},
  3036  		},
  3037  		// test to ensure omitempty fields not included in user info
  3038  		// are set and accessible for CEL evaluation.
  3039  		{
  3040  			name: "test user validation rule doesn't fail when user info is empty except username",
  3041  			options: Options{
  3042  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3043  					Issuer: apiserver.Issuer{
  3044  						URL:       "https://auth.example.com",
  3045  						Audiences: []string{"my-client"},
  3046  					},
  3047  					ClaimMappings: apiserver.ClaimMappings{
  3048  						Username: apiserver.PrefixedClaimOrExpression{
  3049  							Expression: "claims.username",
  3050  						},
  3051  						Groups: apiserver.PrefixedClaimOrExpression{
  3052  							Expression: "claims.groups",
  3053  						},
  3054  					},
  3055  					UserValidationRules: []apiserver.UserValidationRule{
  3056  						{
  3057  							Expression: `user.username == " "`,
  3058  							Message:    "username must be single space",
  3059  						},
  3060  						{
  3061  							Expression: `user.uid == ""`,
  3062  							Message:    "uid must be empty string",
  3063  						},
  3064  						{
  3065  							Expression: `!('bar' in user.groups)`,
  3066  							Message:    "groups must not contain bar",
  3067  						},
  3068  						{
  3069  							Expression: `!('bar' in user.extra)`,
  3070  							Message:    "extra must not contain bar",
  3071  						},
  3072  					},
  3073  				},
  3074  				now: func() time.Time { return now },
  3075  			},
  3076  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3077  			pubKeys: []*jose.JSONWebKey{
  3078  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3079  			},
  3080  			claims: fmt.Sprintf(`{
  3081  				"iss": "https://auth.example.com",
  3082  				"aud": "my-client",
  3083  				"username": " ",
  3084  				"groups": null,
  3085  				"exp": %d,
  3086  				"baz": "qux"
  3087  			}`, valid.Unix()),
  3088  			want: &user.DefaultInfo{Name: " "},
  3089  		},
  3090  		{
  3091  			name: "empty username is allowed via claim",
  3092  			options: Options{
  3093  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3094  					Issuer: apiserver.Issuer{
  3095  						URL:       "https://auth.example.com",
  3096  						Audiences: []string{"my-client"},
  3097  					},
  3098  					ClaimMappings: apiserver.ClaimMappings{
  3099  						Username: apiserver.PrefixedClaimOrExpression{
  3100  							Claim:  "username",
  3101  							Prefix: pointer.String(""),
  3102  						},
  3103  						Groups: apiserver.PrefixedClaimOrExpression{
  3104  							Expression: "claims.groups",
  3105  						},
  3106  					},
  3107  					UserValidationRules: []apiserver.UserValidationRule{
  3108  						{
  3109  							Expression: `user.username == ""`,
  3110  							Message:    "username must be empty string",
  3111  						},
  3112  						{
  3113  							Expression: `user.uid == ""`,
  3114  							Message:    "uid must be empty string",
  3115  						},
  3116  						{
  3117  							Expression: `!('bar' in user.groups)`,
  3118  							Message:    "groups must not contain bar",
  3119  						},
  3120  						{
  3121  							Expression: `!('bar' in user.extra)`,
  3122  							Message:    "extra must not contain bar",
  3123  						},
  3124  					},
  3125  				},
  3126  				now: func() time.Time { return now },
  3127  			},
  3128  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3129  			pubKeys: []*jose.JSONWebKey{
  3130  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3131  			},
  3132  			claims: fmt.Sprintf(`{
  3133  				"iss": "https://auth.example.com",
  3134  				"aud": "my-client",
  3135  				"username": "",
  3136  				"groups": null,
  3137  				"exp": %d,
  3138  				"baz": "qux"
  3139  			}`, valid.Unix()),
  3140  			want: &user.DefaultInfo{},
  3141  		},
  3142  		// test to assert the minimum valid jwt payload
  3143  		// the required claims are iss, aud, exp and <claimMappings.Username> (in this case user).
  3144  		{
  3145  			name: "minimum valid jwt payload",
  3146  			options: Options{
  3147  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3148  					Issuer: apiserver.Issuer{
  3149  						URL:       "https://auth.example.com",
  3150  						Audiences: []string{"my-client"},
  3151  					},
  3152  					ClaimMappings: apiserver.ClaimMappings{
  3153  						Username: apiserver.PrefixedClaimOrExpression{
  3154  							Expression: "claims.user",
  3155  						},
  3156  					},
  3157  				},
  3158  				now: func() time.Time { return now },
  3159  			},
  3160  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3161  			pubKeys: []*jose.JSONWebKey{
  3162  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3163  			},
  3164  			claims: fmt.Sprintf(`{
  3165  				"iss": "https://auth.example.com",
  3166  				"aud": "my-client",
  3167  				"user": "jane",
  3168  				"exp": %d
  3169  			}`, valid.Unix()),
  3170  			want: &user.DefaultInfo{
  3171  				Name: "jane",
  3172  			},
  3173  		},
  3174  		{
  3175  			name: "discovery-url",
  3176  			options: Options{
  3177  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3178  					Issuer: apiserver.Issuer{
  3179  						URL:          "https://auth.example.com",
  3180  						DiscoveryURL: "{{.URL}}/.well-known/openid-configuration",
  3181  						Audiences:    []string{"my-client"},
  3182  					},
  3183  					ClaimMappings: apiserver.ClaimMappings{
  3184  						Username: apiserver.PrefixedClaimOrExpression{
  3185  							Claim:  "username",
  3186  							Prefix: pointer.String(""),
  3187  						},
  3188  					},
  3189  				},
  3190  				now: func() time.Time { return now },
  3191  			},
  3192  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3193  			pubKeys: []*jose.JSONWebKey{
  3194  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3195  			},
  3196  			claims: fmt.Sprintf(`{
  3197  				"iss": "https://auth.example.com",
  3198  				"aud": "my-client",
  3199  				"username": "jane",
  3200  				"exp": %d
  3201  			}`, valid.Unix()),
  3202  			openIDConfig: `{
  3203  					"issuer": "https://auth.example.com",
  3204  					"jwks_uri": "{{.URL}}/.testing/keys"
  3205  			}`,
  3206  			fetchKeysFromRemote: true,
  3207  			want: &user.DefaultInfo{
  3208  				Name: "jane",
  3209  			},
  3210  		},
  3211  		{
  3212  			name: "discovery url, issuer has a path",
  3213  			options: Options{
  3214  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3215  					Issuer: apiserver.Issuer{
  3216  						URL:          "https://auth.example.com/a/b/foo",
  3217  						DiscoveryURL: "{{.URL}}/.well-known/openid-configuration",
  3218  						Audiences:    []string{"my-client"},
  3219  					},
  3220  					ClaimMappings: apiserver.ClaimMappings{
  3221  						Username: apiserver.PrefixedClaimOrExpression{
  3222  							Claim:  "username",
  3223  							Prefix: pointer.String(""),
  3224  						},
  3225  					},
  3226  				},
  3227  				now: func() time.Time { return now },
  3228  			},
  3229  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3230  			pubKeys: []*jose.JSONWebKey{
  3231  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3232  			},
  3233  			claims: fmt.Sprintf(`{
  3234  				"iss": "https://auth.example.com/a/b/foo",
  3235  				"aud": "my-client",
  3236  				"username": "jane",
  3237  				"exp": %d
  3238  			}`, valid.Unix()),
  3239  			openIDConfig: `{
  3240  					"issuer": "https://auth.example.com/a/b/foo",
  3241  					"jwks_uri": "{{.URL}}/.testing/keys"
  3242  			}`,
  3243  			fetchKeysFromRemote: true,
  3244  			want: &user.DefaultInfo{
  3245  				Name: "jane",
  3246  			},
  3247  		},
  3248  		{
  3249  			name: "discovery url has a path, issuer url has no path",
  3250  			options: Options{
  3251  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3252  					Issuer: apiserver.Issuer{
  3253  						URL:          "https://auth.example.com",
  3254  						DiscoveryURL: "{{.URL}}/c/d/bar/.well-known/openid-configuration",
  3255  						Audiences:    []string{"my-client"},
  3256  					},
  3257  					ClaimMappings: apiserver.ClaimMappings{
  3258  						Username: apiserver.PrefixedClaimOrExpression{
  3259  							Claim:  "username",
  3260  							Prefix: pointer.String(""),
  3261  						},
  3262  					},
  3263  				},
  3264  				now: func() time.Time { return now },
  3265  			},
  3266  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3267  			pubKeys: []*jose.JSONWebKey{
  3268  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3269  			},
  3270  			claims: fmt.Sprintf(`{
  3271  				"iss": "https://auth.example.com",
  3272  				"aud": "my-client",
  3273  				"username": "jane",
  3274  				"exp": %d
  3275  			}`, valid.Unix()),
  3276  			openIDConfig: `{
  3277  					"issuer": "https://auth.example.com",
  3278  					"jwks_uri": "{{.URL}}/.testing/keys"
  3279  			}`,
  3280  			fetchKeysFromRemote: true,
  3281  			want: &user.DefaultInfo{
  3282  				Name: "jane",
  3283  			},
  3284  		},
  3285  		{
  3286  			name: "discovery url and issuer url have paths",
  3287  			options: Options{
  3288  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3289  					Issuer: apiserver.Issuer{
  3290  						URL:          "https://auth.example.com/a/b/foo",
  3291  						DiscoveryURL: "{{.URL}}/c/d/bar/.well-known/openid-configuration",
  3292  						Audiences:    []string{"my-client"},
  3293  					},
  3294  					ClaimMappings: apiserver.ClaimMappings{
  3295  						Username: apiserver.PrefixedClaimOrExpression{
  3296  							Claim:  "username",
  3297  							Prefix: pointer.String(""),
  3298  						},
  3299  					},
  3300  				},
  3301  				now: func() time.Time { return now },
  3302  			},
  3303  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3304  			pubKeys: []*jose.JSONWebKey{
  3305  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3306  			},
  3307  			claims: fmt.Sprintf(`{
  3308  				"iss": "https://auth.example.com/a/b/foo",
  3309  				"aud": "my-client",
  3310  				"username": "jane",
  3311  				"exp": %d
  3312  			}`, valid.Unix()),
  3313  			openIDConfig: `{
  3314  					"issuer": "https://auth.example.com/a/b/foo",
  3315  					"jwks_uri": "{{.URL}}/.testing/keys"
  3316  			}`,
  3317  			fetchKeysFromRemote: true,
  3318  			want: &user.DefaultInfo{
  3319  				Name: "jane",
  3320  			},
  3321  		},
  3322  		{
  3323  			name: "discovery url and issuer url have paths, issuer url has trailing slash",
  3324  			options: Options{
  3325  				JWTAuthenticator: apiserver.JWTAuthenticator{
  3326  					Issuer: apiserver.Issuer{
  3327  						URL:          "https://auth.example.com/a/b/foo/",
  3328  						DiscoveryURL: "{{.URL}}/c/d/bar/.well-known/openid-configuration",
  3329  						Audiences:    []string{"my-client"},
  3330  					},
  3331  					ClaimMappings: apiserver.ClaimMappings{
  3332  						Username: apiserver.PrefixedClaimOrExpression{
  3333  							Claim:  "username",
  3334  							Prefix: pointer.String(""),
  3335  						},
  3336  					},
  3337  				},
  3338  				now: func() time.Time { return now },
  3339  			},
  3340  			signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256),
  3341  			pubKeys: []*jose.JSONWebKey{
  3342  				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
  3343  			},
  3344  			claims: fmt.Sprintf(`{
  3345  				"iss": "https://auth.example.com/a/b/foo/",
  3346  				"aud": "my-client",
  3347  				"username": "jane",
  3348  				"exp": %d
  3349  			}`, valid.Unix()),
  3350  			openIDConfig: `{
  3351  					"issuer": "https://auth.example.com/a/b/foo/",
  3352  					"jwks_uri": "{{.URL}}/.testing/keys"
  3353  			}`,
  3354  			fetchKeysFromRemote: true,
  3355  			want: &user.DefaultInfo{
  3356  				Name: "jane",
  3357  			},
  3358  		},
  3359  	}
  3360  
  3361  	var successTestCount, failureTestCount int
  3362  	for _, test := range tests {
  3363  		t.Run(test.name, test.run)
  3364  		if test.wantSkip || len(test.wantInitErr) > 0 || len(test.wantHealthErrPrefix) > 0 {
  3365  			continue
  3366  		}
  3367  		// check metrics for success and failure
  3368  		if test.wantErr == "" {
  3369  			successTestCount++
  3370  			testutil.AssertHistogramTotalCount(t, "apiserver_authentication_jwt_authenticator_latency_seconds", map[string]string{"result": "success"}, successTestCount)
  3371  		} else {
  3372  			failureTestCount++
  3373  			testutil.AssertHistogramTotalCount(t, "apiserver_authentication_jwt_authenticator_latency_seconds", map[string]string{"result": "failure"}, failureTestCount)
  3374  		}
  3375  	}
  3376  }
  3377  
  3378  func TestUnmarshalClaimError(t *testing.T) {
  3379  	// Ensure error strings returned by unmarshaling claims don't include the claim.
  3380  	const token = "96bb299a-02e9-11e8-8673-54ee7553240e" // Fake token for testing.
  3381  	payload := fmt.Sprintf(`{
  3382  		"token": "%s"
  3383  	}`, token)
  3384  
  3385  	var c claims
  3386  	if err := json.Unmarshal([]byte(payload), &c); err != nil {
  3387  		t.Fatal(err)
  3388  	}
  3389  	var n int
  3390  	err := c.unmarshalClaim("token", &n)
  3391  	if err == nil {
  3392  		t.Fatal("expected error")
  3393  	}
  3394  
  3395  	if strings.Contains(err.Error(), token) {
  3396  		t.Fatalf("unmarshal error included token")
  3397  	}
  3398  }
  3399  
  3400  func TestUnmarshalClaim(t *testing.T) {
  3401  	tests := []struct {
  3402  		name    string
  3403  		claims  string
  3404  		do      func(claims) (interface{}, error)
  3405  		want    interface{}
  3406  		wantErr bool
  3407  	}{
  3408  		{
  3409  			name:   "string claim",
  3410  			claims: `{"aud":"foo"}`,
  3411  			do: func(c claims) (interface{}, error) {
  3412  				var s string
  3413  				err := c.unmarshalClaim("aud", &s)
  3414  				return s, err
  3415  			},
  3416  			want: "foo",
  3417  		},
  3418  		{
  3419  			name:   "mismatched types",
  3420  			claims: `{"aud":"foo"}`,
  3421  			do: func(c claims) (interface{}, error) {
  3422  				var n int
  3423  				err := c.unmarshalClaim("aud", &n)
  3424  				return n, err
  3425  
  3426  			},
  3427  			wantErr: true,
  3428  		},
  3429  		{
  3430  			name:   "bool claim",
  3431  			claims: `{"email":"foo@coreos.com","email_verified":true}`,
  3432  			do: func(c claims) (interface{}, error) {
  3433  				var verified bool
  3434  				err := c.unmarshalClaim("email_verified", &verified)
  3435  				return verified, err
  3436  			},
  3437  			want: true,
  3438  		},
  3439  		{
  3440  			name:   "strings claim",
  3441  			claims: `{"groups":["a","b","c"]}`,
  3442  			do: func(c claims) (interface{}, error) {
  3443  				var groups []string
  3444  				err := c.unmarshalClaim("groups", &groups)
  3445  				return groups, err
  3446  			},
  3447  			want: []string{"a", "b", "c"},
  3448  		},
  3449  	}
  3450  
  3451  	for _, test := range tests {
  3452  		t.Run(test.name, func(t *testing.T) {
  3453  			var c claims
  3454  			if err := json.Unmarshal([]byte(test.claims), &c); err != nil {
  3455  				t.Fatal(err)
  3456  			}
  3457  
  3458  			got, err := test.do(c)
  3459  			if err != nil {
  3460  				if test.wantErr {
  3461  					return
  3462  				}
  3463  				t.Fatalf("unexpected error: %v", err)
  3464  			}
  3465  			if test.wantErr {
  3466  				t.Fatalf("expected error")
  3467  			}
  3468  
  3469  			if !reflect.DeepEqual(got, test.want) {
  3470  				t.Errorf("wanted=%#v, got=%#v", test.want, got)
  3471  			}
  3472  		})
  3473  	}
  3474  }
  3475  
  3476  type errTransport string
  3477  
  3478  func (e errTransport) RoundTrip(_ *http.Request) (*http.Response, error) {
  3479  	return nil, fmt.Errorf("%s", e)
  3480  }
  3481  
  3482  func testContext(t *testing.T) context.Context {
  3483  	ctx, cancel := context.WithCancel(context.Background())
  3484  	t.Cleanup(cancel)
  3485  	return ctx
  3486  }