istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/jwks_resolver_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package model
    16  
    17  import (
    18  	"fmt"
    19  	"sync/atomic"
    20  	"testing"
    21  	"time"
    22  
    23  	"istio.io/istio/pilot/pkg/model/test"
    24  	"istio.io/istio/pkg/monitoring/monitortest"
    25  	"istio.io/istio/pkg/test/util/retry"
    26  )
    27  
    28  const (
    29  	testRetryInterval  = time.Millisecond * 10
    30  	testRequestTimeout = time.Second * 5
    31  )
    32  
    33  func TestResolveJwksURIUsingOpenID(t *testing.T) {
    34  	r := NewJwksResolver(JwtPubKeyEvictionDuration, JwtPubKeyRefreshInterval, JwtPubKeyRefreshIntervalOnFailure, testRetryInterval)
    35  	defer r.Close()
    36  
    37  	ms, err := test.StartNewServer()
    38  	defer ms.Stop()
    39  	if err != nil {
    40  		t.Fatal("failed to start a mock server")
    41  	}
    42  
    43  	mockCertURL := ms.URL + "/oauth2/v3/certs"
    44  	cases := []struct {
    45  		in              string
    46  		expectedJwksURI string
    47  		expectedError   bool
    48  	}{
    49  		{
    50  			in:              ms.URL,
    51  			expectedJwksURI: mockCertURL,
    52  		},
    53  		{
    54  			in:              ms.URL,
    55  			expectedJwksURI: mockCertURL,
    56  		},
    57  		{
    58  			// if the URL has a trailing slash, it should be handled.
    59  			in:              ms.URL + "/",
    60  			expectedJwksURI: mockCertURL,
    61  		},
    62  		{
    63  			in:            "http://xyz",
    64  			expectedError: true,
    65  		},
    66  	}
    67  	for _, c := range cases {
    68  		jwksURI, err := r.resolveJwksURIUsingOpenID(c.in, testRequestTimeout)
    69  		if err != nil && !c.expectedError {
    70  			t.Errorf("resolveJwksURIUsingOpenID(%+v): got error (%v)", c.in, err)
    71  		} else if err == nil && c.expectedError {
    72  			t.Errorf("resolveJwksURIUsingOpenID(%+v): expected error, got no error", c.in)
    73  		} else if c.expectedJwksURI != jwksURI {
    74  			t.Errorf("resolveJwksURIUsingOpenID(%+v): expected (%s), got (%s)",
    75  				c.in, c.expectedJwksURI, jwksURI)
    76  		}
    77  	}
    78  
    79  	// Verify mock openID discovery http://localhost:9999/.well-known/openid-configuration was called three times.
    80  	if got, want := ms.OpenIDHitNum, uint64(3); got != want {
    81  		t.Errorf("Mock OpenID discovery Hit number => expected %d but got %d", want, got)
    82  	}
    83  }
    84  
    85  func TestGetPublicKey(t *testing.T) {
    86  	r := NewJwksResolver(JwtPubKeyEvictionDuration, JwtPubKeyRefreshInterval, JwtPubKeyRefreshIntervalOnFailure, testRetryInterval)
    87  	defer r.Close()
    88  
    89  	ms, err := test.StartNewServer()
    90  	defer ms.Stop()
    91  	if err != nil {
    92  		t.Fatal("failed to start a mock server")
    93  	}
    94  
    95  	mockCertURL := ms.URL + "/oauth2/v3/certs"
    96  
    97  	cases := []struct {
    98  		in                []string
    99  		expectedJwtPubkey string
   100  	}{
   101  		{
   102  			in:                []string{"testIssuer", mockCertURL},
   103  			expectedJwtPubkey: test.JwtPubKey1,
   104  		},
   105  		{
   106  			in:                []string{"testIssuer", mockCertURL}, // Send two same request, mock server is expected to hit only once because of the cache.
   107  			expectedJwtPubkey: test.JwtPubKey1,
   108  		},
   109  	}
   110  	for _, c := range cases {
   111  		pk, err := r.GetPublicKey(c.in[0], c.in[1], testRequestTimeout)
   112  		if err != nil {
   113  			t.Errorf("GetPublicKey(\"\", %+v) fails: expected no error, got (%v)", c.in, err)
   114  		}
   115  		if c.expectedJwtPubkey != pk {
   116  			t.Errorf("GetPublicKey(\"\", %+v): expected (%s), got (%s)", c.in, c.expectedJwtPubkey, pk)
   117  		}
   118  	}
   119  
   120  	// Verify mock server http://localhost:9999/oauth2/v3/certs was only called once because of the cache.
   121  	if got, want := ms.PubKeyHitNum, uint64(1); got != want {
   122  		t.Errorf("Mock server Hit number => expected %d but got %d", want, got)
   123  	}
   124  }
   125  
   126  func TestGetPublicKeyWithTimeout(t *testing.T) {
   127  	r := NewJwksResolver(JwtPubKeyEvictionDuration, JwtPubKeyRefreshInterval, JwtPubKeyRefreshIntervalOnFailure, testRetryInterval)
   128  	defer r.Close()
   129  	serverDelay := 100 * time.Millisecond
   130  	ms, err := test.StartNewServerWithHandlerDelay(serverDelay)
   131  	defer ms.Stop()
   132  	if err != nil {
   133  		t.Fatal("failed to start a mock server")
   134  	}
   135  
   136  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   137  
   138  	cases := []struct {
   139  		in              []string
   140  		timeout         time.Duration
   141  		expectedFailure bool
   142  	}{
   143  		{
   144  			in:              []string{"testIssuer", mockCertURL},
   145  			timeout:         5 * time.Second,
   146  			expectedFailure: false,
   147  		},
   148  		{
   149  			in:              []string{"testIssuer2", mockCertURL}, // Send two same request, mock server is expected to hit only once because of the cache.
   150  			timeout:         20 * time.Millisecond,
   151  			expectedFailure: true,
   152  		},
   153  	}
   154  	for _, c := range cases {
   155  		_, err := r.GetPublicKey(c.in[0], c.in[1], c.timeout)
   156  		if c.timeout < serverDelay && err == nil {
   157  			t.Errorf("GetPublicKey(\"\", %+v) fails: did not timed out as expected", c)
   158  		} else if c.timeout >= serverDelay && err != nil {
   159  			t.Errorf("GetPublicKey(\"\", %+v) fails: expected no error, got (%v)", c, err)
   160  		}
   161  	}
   162  }
   163  
   164  func TestGetPublicKeyReorderedKey(t *testing.T) {
   165  	r := NewJwksResolver(JwtPubKeyEvictionDuration, testRetryInterval*20, testRetryInterval*10, testRetryInterval)
   166  	defer r.Close()
   167  
   168  	ms, err := test.StartNewServer()
   169  	defer ms.Stop()
   170  	if err != nil {
   171  		t.Fatal("failed to start a mock server")
   172  	}
   173  	ms.ReturnReorderedKeyAfterFirstNumHits = 1
   174  
   175  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   176  
   177  	cases := []struct {
   178  		in                []string
   179  		expectedJwtPubkey string
   180  	}{
   181  		{
   182  			in:                []string{"", mockCertURL},
   183  			expectedJwtPubkey: test.JwtPubKey1,
   184  		},
   185  		{
   186  			in:                []string{"", mockCertURL}, // Send two same request, mock server is expected to hit only once because of the cache.
   187  			expectedJwtPubkey: test.JwtPubKey1Reordered,
   188  		},
   189  	}
   190  	for _, c := range cases {
   191  		pk, err := r.GetPublicKey(c.in[0], c.in[1], testRequestTimeout)
   192  		if err != nil {
   193  			t.Errorf("GetPublicKey(\"\", %+v) fails: expected no error, got (%v)", c.in, err)
   194  		}
   195  		if c.expectedJwtPubkey != pk {
   196  			t.Errorf("GetPublicKey(\"\", %+v): expected (%s), got (%s)", c.in, c.expectedJwtPubkey, pk)
   197  		}
   198  		r.refresh()
   199  	}
   200  
   201  	// Verify refresh job key changed count is zero.
   202  	if got, want := r.refreshJobKeyChangedCount, uint64(0); got != want {
   203  		t.Errorf("JWKs Resolver Refreshed Key Count => expected %d but got %d", want, got)
   204  	}
   205  }
   206  
   207  func TestGetPublicKeyUsingTLS(t *testing.T) {
   208  	r := newJwksResolverWithCABundlePaths(
   209  		JwtPubKeyEvictionDuration,
   210  		JwtPubKeyRefreshInterval,
   211  		JwtPubKeyRefreshIntervalOnFailure,
   212  		testRetryInterval,
   213  		[]string{"./test/testcert/cert.pem"},
   214  	)
   215  	defer r.Close()
   216  
   217  	ms, err := test.StartNewTLSServer("./test/testcert/cert.pem", "./test/testcert/key.pem")
   218  	defer ms.Stop()
   219  	if err != nil {
   220  		t.Fatal("failed to start a mock server")
   221  	}
   222  
   223  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   224  	pk, err := r.GetPublicKey("", mockCertURL, testRequestTimeout)
   225  	if err != nil {
   226  		t.Errorf("GetPublicKey(\"\", %+v) fails: expected no error, got (%v)", mockCertURL, err)
   227  	}
   228  	if test.JwtPubKey1 != pk {
   229  		t.Errorf("GetPublicKey(\"\", %+v): expected (%s), got (%s)", mockCertURL, test.JwtPubKey1, pk)
   230  	}
   231  }
   232  
   233  func TestGetPublicKeyUsingTLSBadCert(t *testing.T) {
   234  	r := newJwksResolverWithCABundlePaths(
   235  		JwtPubKeyEvictionDuration,
   236  		JwtPubKeyRefreshInterval,
   237  		testRetryInterval,
   238  		testRetryInterval,
   239  		[]string{"./test/testcert/cert2.pem"},
   240  	)
   241  	defer r.Close()
   242  
   243  	ms, err := test.StartNewTLSServer("./test/testcert/cert.pem", "./test/testcert/key.pem")
   244  	defer ms.Stop()
   245  	if err != nil {
   246  		t.Fatal("failed to start a mock server")
   247  	}
   248  
   249  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   250  	_, err = r.GetPublicKey("", mockCertURL, testRequestTimeout)
   251  	if err == nil {
   252  		t.Errorf("GetPublicKey(\"\", %+v) did not fail: expected bad certificate error, got no error", mockCertURL)
   253  	}
   254  }
   255  
   256  func TestGetPublicKeyUsingTLSWithoutCABundles(t *testing.T) {
   257  	r := newJwksResolverWithCABundlePaths(
   258  		JwtPubKeyEvictionDuration,
   259  		JwtPubKeyRefreshInterval,
   260  		testRetryInterval,
   261  		testRetryInterval,
   262  		[]string{},
   263  	)
   264  	defer r.Close()
   265  
   266  	ms, err := test.StartNewTLSServer("./test/testcert/cert.pem", "./test/testcert/key.pem")
   267  	defer ms.Stop()
   268  	if err != nil {
   269  		t.Fatal("failed to start a mock server")
   270  	}
   271  
   272  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   273  	_, err = r.GetPublicKey("", mockCertURL, testRequestTimeout)
   274  	if err == nil {
   275  		t.Errorf("GetPublicKey(\"\", %+v) did not fail: expected https unsupported error, got no error", mockCertURL)
   276  	}
   277  }
   278  
   279  func TestJwtPubKeyEvictionForNotUsed(t *testing.T) {
   280  	r := NewJwksResolver(
   281  		100*time.Millisecond, /*EvictionDuration*/
   282  		2*time.Millisecond,   /*RefreshInterval*/
   283  		2*time.Millisecond,   /*RefreshIntervalOnFailure*/
   284  		testRetryInterval,
   285  	)
   286  	defer r.Close()
   287  
   288  	ms := startMockServer(t)
   289  	defer ms.Stop()
   290  
   291  	// Mock server returns JwtPubKey2 for later calls.
   292  	// Verify the refresher has run and got new key from mock server.
   293  	verifyKeyRefresh(t, r, ms, test.JwtPubKey2)
   294  
   295  	// Wait until unused keys are evicted.
   296  	key := jwtKey{jwksURI: ms.URL + "/oauth2/v3/certs", issuer: "istio-test"}
   297  
   298  	retry.UntilSuccessOrFail(t, func() error {
   299  		// Verify the public key is evicted.
   300  		if _, found := r.keyEntries.Load(key); found {
   301  			return fmt.Errorf("public key is not evicted")
   302  		}
   303  		return nil
   304  	})
   305  }
   306  
   307  func TestJwtPubKeyEvictionForNotRefreshed(t *testing.T) {
   308  	r := NewJwksResolver(
   309  		100*time.Millisecond, /*EvictionDuration*/
   310  		10*time.Millisecond,  /*RefreshInterval*/
   311  		10*time.Millisecond,  /*RefreshIntervalOnFailure*/
   312  		testRetryInterval,    /*RetryInterval*/
   313  	)
   314  	defer r.Close()
   315  
   316  	ms := startMockServer(t)
   317  	defer ms.Stop()
   318  
   319  	// Configures the mock server to return error after the first request.
   320  	ms.ReturnErrorAfterFirstNumHits = 1
   321  
   322  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   323  
   324  	pk, err := r.GetPublicKey("", mockCertURL, testRequestTimeout)
   325  	if err != nil {
   326  		t.Fatalf("GetPublicKey(\"\", %+v) fails: expected no error, got (%v)", mockCertURL, err)
   327  	}
   328  	// Mock server returns JwtPubKey1 for first call.
   329  	if test.JwtPubKey1 != pk {
   330  		t.Fatalf("GetPublicKey(\"\", %+v): expected (%s), got (%s)", mockCertURL, test.JwtPubKey1, pk)
   331  	}
   332  
   333  	// Keep getting the public key to change the lastUsedTime of the public key.
   334  	done := make(chan struct{})
   335  	go func() {
   336  		c := time.NewTicker(10 * time.Millisecond)
   337  		for {
   338  			select {
   339  			case <-done:
   340  				c.Stop()
   341  				return
   342  			case <-c.C:
   343  				_, _ = r.GetPublicKey(mockCertURL, "", testRequestTimeout)
   344  			}
   345  		}
   346  	}()
   347  	defer func() {
   348  		done <- struct{}{}
   349  	}()
   350  
   351  	// Verify the cached public key is removed after failed to refresh longer than the eviction duration.
   352  	retry.UntilSuccessOrFail(t, func() error {
   353  		_, err = r.GetPublicKey(mockCertURL, "", testRequestTimeout)
   354  		if err == nil {
   355  			return fmt.Errorf("getPublicKey(\"\", %+v) fails: expected error, got no error", mockCertURL)
   356  		}
   357  		return nil
   358  	})
   359  }
   360  
   361  func TestJwtPubKeyLastRefreshedTime(t *testing.T) {
   362  	r := NewJwksResolver(
   363  		JwtPubKeyEvictionDuration,
   364  		2*time.Millisecond, /*RefreshInterval*/
   365  		2*time.Millisecond, /*RefreshIntervalOnFailure*/
   366  		testRetryInterval,  /*RetryInterval*/
   367  	)
   368  	defer r.Close()
   369  
   370  	ms := startMockServer(t)
   371  	defer ms.Stop()
   372  
   373  	// Mock server returns JwtPubKey2 for later calls.
   374  	// Verify the refresher has run and got new key from mock server.
   375  	verifyKeyRefresh(t, r, ms, test.JwtPubKey2)
   376  
   377  	// The lastRefreshedTime should change for each successful refresh.
   378  	verifyKeyLastRefreshedTime(t, r, ms, true /* wantChanged */)
   379  }
   380  
   381  func TestJwtPubKeyRefreshWithNetworkError(t *testing.T) {
   382  	r := NewJwksResolver(
   383  		JwtPubKeyEvictionDuration,
   384  		time.Second, /*RefreshInterval*/
   385  		time.Second, /*RefreshIntervalOnFailure*/
   386  		testRetryInterval,
   387  	)
   388  	defer r.Close()
   389  
   390  	ms := startMockServer(t)
   391  	defer ms.Stop()
   392  
   393  	// Configures the mock server to return error after the first request.
   394  	ms.ReturnErrorAfterFirstNumHits = 1
   395  
   396  	// The refresh job should continue using the previously fetched public key (JwtPubKey1).
   397  	verifyKeyRefresh(t, r, ms, test.JwtPubKey1)
   398  
   399  	// The lastRefreshedTime should not change the refresh failed due to network error.
   400  	verifyKeyLastRefreshedTime(t, r, ms, false /* wantChanged */)
   401  }
   402  
   403  func TestJwtRefreshIntervalRecoverFromInitialFailOnFirstHit(t *testing.T) {
   404  	defaultRefreshInterval := 50 * time.Millisecond
   405  	refreshIntervalOnFail := 2 * time.Millisecond
   406  	r := NewJwksResolver(JwtPubKeyEvictionDuration, defaultRefreshInterval, refreshIntervalOnFail, 1*time.Millisecond)
   407  
   408  	ms := startMockServer(t)
   409  	defer ms.Stop()
   410  
   411  	// Configures the mock server to return error for the first 3 requests.
   412  	ms.ReturnErrorForFirstNumHits = 3
   413  
   414  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   415  	pk, err := r.GetPublicKey("", mockCertURL, testRequestTimeout)
   416  	if err == nil {
   417  		t.Fatalf("GetPublicKey(%q, %+v) fails: expected error, got no error: (%v)", pk, mockCertURL, err)
   418  	}
   419  
   420  	retry.UntilOrFail(t, func() bool {
   421  		pk, _ := r.GetPublicKey("", mockCertURL, testRequestTimeout)
   422  		return test.JwtPubKey2 == pk
   423  	}, retry.Delay(time.Millisecond))
   424  	r.Close()
   425  
   426  	i := 0
   427  	r.keyEntries.Range(func(_ any, _ any) bool {
   428  		i++
   429  		return true
   430  	})
   431  
   432  	expectedEntries := 1
   433  	if i != expectedEntries {
   434  		t.Errorf("expected entries in cache: %d , got %d", expectedEntries, i)
   435  	}
   436  
   437  	if r.refreshInterval != defaultRefreshInterval {
   438  		t.Errorf("expected refreshInterval to be refreshDefaultInterval: %v, got %v", defaultRefreshInterval, r.refreshInterval)
   439  	}
   440  }
   441  
   442  func TestJwtRefreshIntervalRecoverFromFail(t *testing.T) {
   443  	defaultRefreshInterval := 50 * time.Millisecond
   444  	refreshIntervalOnFail := 2 * time.Millisecond
   445  	r := NewJwksResolver(JwtPubKeyEvictionDuration, defaultRefreshInterval, refreshIntervalOnFail, 1*time.Millisecond)
   446  
   447  	ms := startMockServer(t)
   448  	defer ms.Stop()
   449  
   450  	// Configures the mock server to return error after the first request.
   451  	ms.ReturnErrorAfterFirstNumHits = 1
   452  	ms.ReturnSuccessAfterFirstNumHits = 3
   453  
   454  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   455  	_, err := r.GetPublicKey("", mockCertURL, testRequestTimeout)
   456  	if err != nil {
   457  		t.Fatalf("GetPublicKey(%q, %+v) fails: expected no error, got (%v)", "", mockCertURL, err)
   458  	}
   459  
   460  	retry.UntilOrFail(t, func() bool {
   461  		pk, _ := r.GetPublicKey("", mockCertURL, testRequestTimeout)
   462  		return test.JwtPubKey1 == pk
   463  	}, retry.Delay(time.Millisecond))
   464  	r.Close()
   465  
   466  	if r.refreshInterval != defaultRefreshInterval {
   467  		t.Errorf("expected defaultRefreshInterval: %v , got %v", defaultRefreshInterval, r.refreshInterval)
   468  	}
   469  }
   470  
   471  func TestJwtPubKeyMetric(t *testing.T) {
   472  	mt := monitortest.New(t)
   473  	defaultRefreshInterval := 50 * time.Millisecond
   474  	refreshIntervalOnFail := 2 * time.Millisecond
   475  	r := NewJwksResolver(JwtPubKeyEvictionDuration, defaultRefreshInterval, refreshIntervalOnFail, 1*time.Millisecond)
   476  	defer r.Close()
   477  
   478  	ms := startMockServer(t)
   479  	defer ms.Stop()
   480  
   481  	ms.ReturnErrorForFirstNumHits = 1
   482  
   483  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   484  	cases := []struct {
   485  		name              string
   486  		in                []string
   487  		expectedJwtPubkey string
   488  		metric            string
   489  	}{
   490  		{
   491  			name:              "fail",
   492  			in:                []string{"", mockCertURL},
   493  			expectedJwtPubkey: "",
   494  			metric:            networkFetchFailCounter.Name(),
   495  		},
   496  		{
   497  			name:              "success",
   498  			in:                []string{"", mockCertURL},
   499  			expectedJwtPubkey: test.JwtPubKey2,
   500  			metric:            networkFetchFailCounter.Name(),
   501  		},
   502  	}
   503  
   504  	// First attempt should fail, but retries cause subsequent ones to succeed
   505  	// Mock server only returns an error on the first attempt
   506  	for _, c := range cases {
   507  		retry.UntilOrFail(t, func() bool {
   508  			pk, _ := r.GetPublicKey(c.in[0], c.in[1], testRequestTimeout)
   509  			return c.expectedJwtPubkey == pk
   510  		}, retry.Delay(time.Millisecond))
   511  		mt.Assert(c.metric, nil, monitortest.AtLeast(1))
   512  	}
   513  }
   514  
   515  func startMockServer(t *testing.T) *test.MockOpenIDDiscoveryServer {
   516  	t.Helper()
   517  
   518  	ms, err := test.StartNewServer()
   519  	if err != nil {
   520  		t.Fatal("failed to start a mock server")
   521  	}
   522  	return ms
   523  }
   524  
   525  func verifyKeyRefresh(t *testing.T, r *JwksResolver, ms *test.MockOpenIDDiscoveryServer, expectedJwtPubkey string) {
   526  	t.Helper()
   527  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   528  
   529  	pk, err := r.GetPublicKey("", mockCertURL, testRequestTimeout)
   530  	if err != nil {
   531  		t.Fatalf("GetPublicKey(\"\", %+v) fails: expected no error, got (%v)", mockCertURL, err)
   532  	}
   533  	// Mock server returns JwtPubKey1 for first call.
   534  	if test.JwtPubKey1 != pk {
   535  		t.Fatalf("GetPublicKey(\"\", %+v): expected (%s), got (%s)", mockCertURL, test.JwtPubKey1, pk)
   536  	}
   537  
   538  	// Wait until refresh job at least finished once.
   539  	retry.UntilSuccessOrFail(t, func() error {
   540  		// Make sure refresh job has run and detect change or refresh happened.
   541  		if atomic.LoadUint64(&r.refreshJobKeyChangedCount) > 0 || atomic.LoadUint64(&r.refreshJobFetchFailedCount) > 0 {
   542  			return nil
   543  		}
   544  		return fmt.Errorf("refresher failed to run")
   545  	})
   546  	pk, err = r.GetPublicKey("", mockCertURL, testRequestTimeout)
   547  	if err != nil {
   548  		t.Fatalf("GetPublicKey(\"\", %+v) fails: expected no error, got (%v)", mockCertURL, err)
   549  	}
   550  	if expectedJwtPubkey != pk {
   551  		t.Fatalf("GetPublicKey(\"\", %+v): expected (%s), got (%s)", mockCertURL, expectedJwtPubkey, pk)
   552  	}
   553  }
   554  
   555  func verifyKeyLastRefreshedTime(t *testing.T, r *JwksResolver, ms *test.MockOpenIDDiscoveryServer, wantChanged bool) {
   556  	t.Helper()
   557  	mockCertURL := ms.URL + "/oauth2/v3/certs"
   558  	key := jwtKey{jwksURI: mockCertURL}
   559  
   560  	e, found := r.keyEntries.Load(key)
   561  	if !found {
   562  		t.Fatalf("No cached public key for %+v", key)
   563  	}
   564  	oldRefreshedTime := e.(jwtPubKeyEntry).lastRefreshedTime
   565  
   566  	time.Sleep(200 * time.Millisecond)
   567  
   568  	e, found = r.keyEntries.Load(key)
   569  	if !found {
   570  		t.Fatalf("No cached public key for %+v", key)
   571  	}
   572  	newRefreshedTime := e.(jwtPubKeyEntry).lastRefreshedTime
   573  
   574  	if actualChanged := oldRefreshedTime != newRefreshedTime; actualChanged != wantChanged {
   575  		t.Errorf("Want changed: %t but got %t", wantChanged, actualChanged)
   576  	}
   577  }
   578  
   579  func TestCompareJWKSResponse(t *testing.T) {
   580  	type args struct {
   581  		oldKeyString string
   582  		newKeyString string
   583  	}
   584  	tests := []struct {
   585  		name    string
   586  		args    args
   587  		want    bool
   588  		wantErr bool
   589  	}{
   590  		{"testEquivalentStrings", args{test.JwtPubKey1, test.JwtPubKey1}, false, false},
   591  		{"testReorderedKeys", args{test.JwtPubKey1, test.JwtPubKey1Reordered}, false, false},
   592  		{"testDifferentKeys", args{test.JwtPubKey1, test.JwtPubKey2}, true, false},
   593  		{"testOldJsonParseFailure", args{"This is not JSON", test.JwtPubKey1}, true, false},
   594  		{"testNewJsonParseFailure", args{test.JwtPubKey1, "This is not JSON"}, false, true},
   595  		{"testNewNoKid", args{test.JwtPubKey1, test.JwtPubKeyNoKid}, true, false},
   596  		{"testOldNoKid", args{test.JwtPubKeyNoKid, test.JwtPubKey1}, true, false},
   597  		{"testBothNoKidSame", args{test.JwtPubKeyNoKid, test.JwtPubKeyNoKid}, false, false},
   598  		{"testBothNoKidDifferent", args{test.JwtPubKeyNoKid, test.JwtPubKeyNoKid2}, true, false},
   599  		{"testNewNoKeys", args{test.JwtPubKey1, test.JwtPubKeyNoKeys}, true, false},
   600  		{"testOldNoKeys", args{test.JwtPubKeyNoKeys, test.JwtPubKey1}, true, false},
   601  		{"testBothNoKeysSame", args{test.JwtPubKeyNoKeys, test.JwtPubKeyNoKeys}, false, false},
   602  		{"testBothNoKeysDifferent", args{test.JwtPubKeyNoKeys, test.JwtPubKeyNoKeys2}, true, false},
   603  		{"testNewExtraElements", args{test.JwtPubKey1, test.JwtPubKeyExtraElements}, true, false},
   604  		{"testOldExtraElements", args{test.JwtPubKeyExtraElements, test.JwtPubKey1}, true, false},
   605  		{"testBothExtraElements", args{test.JwtPubKeyExtraElements, test.JwtPubKeyExtraElements}, false, false},
   606  	}
   607  	for _, tt := range tests {
   608  		t.Run(tt.name, func(t *testing.T) {
   609  			got, err := compareJWKSResponse(tt.args.oldKeyString, tt.args.newKeyString)
   610  			if (err != nil) != tt.wantErr {
   611  				t.Errorf("compareJWKSResponse() error = %v, wantErr %v", err, tt.wantErr)
   612  				return
   613  			}
   614  			if got != tt.want {
   615  				t.Errorf("compareJWKSResponse() got = %v, want %v", got, tt.want)
   616  			}
   617  		})
   618  	}
   619  }