k8s.io/apiserver@v0.31.1/pkg/authentication/token/cache/cached_token_authenticator_test.go (about)

     1  /*
     2  Copyright 2017 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 cache
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/hmac"
    23  	"crypto/rand"
    24  	"crypto/sha256"
    25  	"errors"
    26  	"fmt"
    27  	mathrand "math/rand"
    28  	"reflect"
    29  	"sync"
    30  	"sync/atomic"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/google/go-cmp/cmp"
    35  
    36  	utilrand "k8s.io/apimachinery/pkg/util/rand"
    37  	"k8s.io/apimachinery/pkg/util/uuid"
    38  	auditinternal "k8s.io/apiserver/pkg/apis/audit"
    39  	"k8s.io/apiserver/pkg/audit"
    40  	"k8s.io/apiserver/pkg/authentication/authenticator"
    41  	"k8s.io/apiserver/pkg/authentication/user"
    42  	"k8s.io/utils/clock"
    43  	testingclock "k8s.io/utils/clock/testing"
    44  )
    45  
    46  func TestCachedTokenAuthenticator(t *testing.T) {
    47  	var (
    48  		calledWithToken []string
    49  
    50  		resultUsers map[string]user.Info
    51  		resultOk    bool
    52  		resultErr   error
    53  	)
    54  	fakeAuth := authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
    55  		calledWithToken = append(calledWithToken, token)
    56  		return &authenticator.Response{User: resultUsers[token]}, resultOk, resultErr
    57  	})
    58  	fakeClock := testingclock.NewFakeClock(time.Now())
    59  
    60  	a := newWithClock(fakeAuth, true, time.Minute, 0, fakeClock)
    61  
    62  	calledWithToken, resultUsers, resultOk, resultErr = []string{}, nil, false, nil
    63  	a.AuthenticateToken(context.Background(), "bad1")
    64  	a.AuthenticateToken(context.Background(), "bad2")
    65  	a.AuthenticateToken(context.Background(), "bad3")
    66  	fakeClock.Step(2 * time.Microsecond)
    67  	a.AuthenticateToken(context.Background(), "bad1")
    68  	a.AuthenticateToken(context.Background(), "bad2")
    69  	a.AuthenticateToken(context.Background(), "bad3")
    70  	fakeClock.Step(2 * time.Microsecond)
    71  	if !reflect.DeepEqual(calledWithToken, []string{"bad1", "bad2", "bad3", "bad1", "bad2", "bad3"}) {
    72  		t.Errorf("Expected failing calls to not stay in the cache, got %v", calledWithToken)
    73  	}
    74  
    75  	// reset calls, make the backend return success for three user tokens
    76  	calledWithToken = []string{}
    77  	resultUsers, resultOk, resultErr = map[string]user.Info{}, true, nil
    78  	resultUsers["usertoken1"] = &user.DefaultInfo{Name: "user1"}
    79  	resultUsers["usertoken2"] = &user.DefaultInfo{Name: "user2"}
    80  	resultUsers["usertoken3"] = &user.DefaultInfo{Name: "user3"}
    81  
    82  	// populate cache
    83  	if resp, ok, err := a.AuthenticateToken(context.Background(), "usertoken1"); err != nil || !ok || resp.User.GetName() != "user1" {
    84  		t.Errorf("Expected user1")
    85  	}
    86  	if resp, ok, err := a.AuthenticateToken(context.Background(), "usertoken2"); err != nil || !ok || resp.User.GetName() != "user2" {
    87  		t.Errorf("Expected user2")
    88  	}
    89  	if resp, ok, err := a.AuthenticateToken(context.Background(), "usertoken3"); err != nil || !ok || resp.User.GetName() != "user3" {
    90  		t.Errorf("Expected user3")
    91  	}
    92  	if !reflect.DeepEqual(calledWithToken, []string{"usertoken1", "usertoken2", "usertoken3"}) {
    93  		t.Errorf("Expected token calls, got %v", calledWithToken)
    94  	}
    95  
    96  	// reset calls, make the backend return failures
    97  	calledWithToken = []string{}
    98  	resultUsers, resultOk, resultErr = nil, false, nil
    99  
   100  	// authenticate calls still succeed and backend is not hit
   101  	if resp, ok, err := a.AuthenticateToken(context.Background(), "usertoken1"); err != nil || !ok || resp.User.GetName() != "user1" {
   102  		t.Errorf("Expected user1")
   103  	}
   104  	if resp, ok, err := a.AuthenticateToken(context.Background(), "usertoken2"); err != nil || !ok || resp.User.GetName() != "user2" {
   105  		t.Errorf("Expected user2")
   106  	}
   107  	if resp, ok, err := a.AuthenticateToken(context.Background(), "usertoken3"); err != nil || !ok || resp.User.GetName() != "user3" {
   108  		t.Errorf("Expected user3")
   109  	}
   110  	if !reflect.DeepEqual(calledWithToken, []string{}) {
   111  		t.Errorf("Expected no token calls, got %v", calledWithToken)
   112  	}
   113  
   114  	// skip forward in time
   115  	fakeClock.Step(2 * time.Minute)
   116  
   117  	// backend is consulted again and fails
   118  	a.AuthenticateToken(context.Background(), "usertoken1")
   119  	a.AuthenticateToken(context.Background(), "usertoken2")
   120  	a.AuthenticateToken(context.Background(), "usertoken3")
   121  	if !reflect.DeepEqual(calledWithToken, []string{"usertoken1", "usertoken2", "usertoken3"}) {
   122  		t.Errorf("Expected token calls, got %v", calledWithToken)
   123  	}
   124  }
   125  
   126  func TestCachedTokenAuthenticatorWithAudiences(t *testing.T) {
   127  	resultUsers := make(map[string]user.Info)
   128  	fakeAuth := authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   129  		auds, _ := authenticator.AudiencesFrom(ctx)
   130  		return &authenticator.Response{User: resultUsers[auds[0]+token]}, true, nil
   131  	})
   132  	fakeClock := testingclock.NewFakeClock(time.Now())
   133  
   134  	a := newWithClock(fakeAuth, true, time.Minute, 0, fakeClock)
   135  
   136  	resultUsers["audAusertoken1"] = &user.DefaultInfo{Name: "user1"}
   137  	resultUsers["audBusertoken1"] = &user.DefaultInfo{Name: "user1-different"}
   138  
   139  	if u, ok, _ := a.AuthenticateToken(authenticator.WithAudiences(context.Background(), []string{"audA"}), "usertoken1"); !ok || u.User.GetName() != "user1" {
   140  		t.Errorf("Expected user1")
   141  	}
   142  	if u, ok, _ := a.AuthenticateToken(authenticator.WithAudiences(context.Background(), []string{"audB"}), "usertoken1"); !ok || u.User.GetName() != "user1-different" {
   143  		t.Errorf("Expected user1-different")
   144  	}
   145  }
   146  
   147  var bKey string
   148  
   149  // use a realistic token for benchmarking
   150  const jwtToken = `eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJvcGVuc2hpZnQtc2RuIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNkbi10b2tlbi1nNndtYyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJzZG4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIzYzM5YzNhYS1kM2Q5LTExZTktYTVkMC0wMmI3YjllODg1OWUiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6b3BlbnNoaWZ0LXNkbjpzZG4ifQ.PIs0rsUTekj5AX8yJeLDyW4vQB17YS4IOgO026yjEvsCY7Wv_2TD0lwyZWqyQh639q3jPh2_3LTQq2Cp0cReBP1PYOIGgprNm3C-3OFZRnkls-GH09kvPYE8J_-a1YwjxucOwytzJvEM5QTC9iXfEJNSTBfLge-HMYT1y0AGKs8DWTSC4rtd_2PedK3OYiAyDg_xHA8qNpG9pRNM8vfjV9VsmqJtlbnTVlTngqC0t5vyMaWrmLNRxN0rTbN2W9L3diXRnYqI8BUfgPQb7uhYcPuXGeypaFrN4d3yNN4NbgVxnkgdd2IXQ8elSJuQn6ynrvLgG0JPMmThOHnwvsZDeA`
   151  
   152  func BenchmarkKeyFunc(b *testing.B) {
   153  	randomCacheKey := make([]byte, 32)
   154  	if _, err := rand.Read(randomCacheKey); err != nil {
   155  		b.Fatal(err) // rand should never fail
   156  	}
   157  	hashPool := &sync.Pool{
   158  		New: func() interface{} {
   159  			return hmac.New(sha256.New, randomCacheKey)
   160  		},
   161  	}
   162  
   163  	// use realistic audiences for benchmarking
   164  	auds := []string{"7daf30b7-a85c-429b-8b21-e666aecbb235", "c22aa267-bdde-4acb-8505-998be7818400", "44f9b4f3-7125-4333-b04c-1446a16c6113"}
   165  
   166  	b.Run("has audiences", func(b *testing.B) {
   167  		var key string
   168  		for n := 0; n < b.N; n++ {
   169  			key = keyFunc(hashPool, auds, jwtToken)
   170  		}
   171  		bKey = key
   172  	})
   173  
   174  	b.Run("nil audiences", func(b *testing.B) {
   175  		var key string
   176  		for n := 0; n < b.N; n++ {
   177  			key = keyFunc(hashPool, nil, jwtToken)
   178  		}
   179  		bKey = key
   180  	})
   181  }
   182  
   183  func TestSharedLookup(t *testing.T) {
   184  	var chewie = &authenticator.Response{User: &user.DefaultInfo{Name: "chewbacca"}}
   185  
   186  	t.Run("actually shared", func(t *testing.T) {
   187  		var lookups uint32
   188  		c := make(chan struct{})
   189  		a := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   190  			<-c
   191  			atomic.AddUint32(&lookups, 1)
   192  			return chewie, true, nil
   193  		}), true, time.Minute, 0)
   194  
   195  		var wg sync.WaitGroup
   196  		for i := 0; i < 10; i++ {
   197  			wg.Add(1)
   198  			go func() {
   199  				defer wg.Done()
   200  				a.AuthenticateToken(context.Background(), "")
   201  			}()
   202  		}
   203  
   204  		// no good way to make sure that all the callers are queued so we sleep.
   205  		time.Sleep(1 * time.Second)
   206  		close(c)
   207  		wg.Wait()
   208  
   209  		if lookups > 3 {
   210  			t.Fatalf("unexpected number of lookups: got=%d, wanted less than 3", lookups)
   211  		}
   212  	})
   213  
   214  	t.Run("first caller bails, second caller gets result", func(t *testing.T) {
   215  		c := make(chan struct{})
   216  		a := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   217  			<-c
   218  			return chewie, true, nil
   219  		}), true, time.Minute, 0)
   220  
   221  		var wg sync.WaitGroup
   222  		wg.Add(2)
   223  
   224  		ctx1, cancel1 := context.WithCancel(context.Background())
   225  		go func() {
   226  			defer wg.Done()
   227  			a.AuthenticateToken(ctx1, "")
   228  		}()
   229  
   230  		ctx2 := context.Background()
   231  
   232  		var (
   233  			resp *authenticator.Response
   234  			ok   bool
   235  			err  error
   236  		)
   237  		go func() {
   238  			defer wg.Done()
   239  			resp, ok, err = a.AuthenticateToken(ctx2, "")
   240  		}()
   241  
   242  		time.Sleep(1 * time.Second)
   243  		cancel1()
   244  		close(c)
   245  		wg.Wait()
   246  
   247  		if want := chewie; !cmp.Equal(resp, want) {
   248  			t.Errorf("Unexpected diff: %v", cmp.Diff(resp, want))
   249  		}
   250  		if !ok {
   251  			t.Errorf("Expected ok response")
   252  		}
   253  		if err != nil {
   254  			t.Errorf("Unexpected error: %v", err)
   255  		}
   256  	})
   257  
   258  	t.Run("lookup panics", func(t *testing.T) {
   259  		a := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   260  			panic("uh oh")
   261  		}), true, time.Minute, 0)
   262  
   263  		_, _, err := a.AuthenticateToken(context.Background(), "")
   264  		if err != errAuthnCrash {
   265  			t.Errorf("expected error: %v", err)
   266  		}
   267  	})
   268  
   269  	t.Run("audiences are forwarded", func(t *testing.T) {
   270  		ctx := authenticator.WithAudiences(context.Background(), []string{"a"})
   271  		a := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   272  			auds, _ := authenticator.AudiencesFrom(ctx)
   273  			if got, want := auds, []string{"a"}; cmp.Equal(got, want) {
   274  				t.Fatalf("unexpeced audiences: %v", cmp.Diff(got, want))
   275  			}
   276  			return nil, false, nil
   277  		}), true, time.Minute, 0)
   278  
   279  		a.AuthenticateToken(ctx, "")
   280  	})
   281  }
   282  
   283  func TestCachedAuditAnnotations(t *testing.T) {
   284  	snorlax := &authenticator.Response{User: &user.DefaultInfo{Name: "snorlax"}}
   285  
   286  	t.Run("annotations from cache", func(t *testing.T) {
   287  		var lookups uint32
   288  		c := make(chan struct{})
   289  		a := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   290  			<-c
   291  			atomic.AddUint32(&lookups, 1)
   292  			audit.AddAuditAnnotation(ctx, "snorlax", "rocks")
   293  			audit.AddAuditAnnotation(ctx, "pandas", "are amazing")
   294  			return snorlax, true, nil
   295  		}), false, time.Minute, 0)
   296  
   297  		allAnnotations := make(chan map[string]string, 10)
   298  		defer close(allAnnotations)
   299  
   300  		var wg sync.WaitGroup
   301  		for i := 0; i < cap(allAnnotations); i++ {
   302  			wg.Add(1)
   303  			go func() {
   304  				defer wg.Done()
   305  
   306  				ctx := withAudit(context.Background())
   307  				_, _, _ = a.AuthenticateToken(ctx, "token")
   308  
   309  				allAnnotations <- audit.AuditEventFrom(ctx).Annotations
   310  			}()
   311  		}
   312  
   313  		// no good way to make sure that all the callers are queued so we sleep.
   314  		time.Sleep(1 * time.Second)
   315  		close(c)
   316  		wg.Wait()
   317  
   318  		want := map[string]string{"snorlax": "rocks", "pandas": "are amazing"}
   319  		for i := 0; i < cap(allAnnotations); i++ {
   320  			annotations := <-allAnnotations
   321  			if diff := cmp.Diff(want, annotations); diff != "" {
   322  				t.Errorf("%d: unexpected annotations (-want +got): %s", i, diff)
   323  			}
   324  		}
   325  
   326  		if queued := len(allAnnotations); queued != 0 {
   327  			t.Errorf("expected all annoations to be processed: %d", queued)
   328  		}
   329  
   330  		if lookups > 3 {
   331  			t.Errorf("unexpected number of lookups: got=%d, wanted less than 3", lookups)
   332  		}
   333  	})
   334  
   335  	t.Run("annotations do not change during cache TTL", func(t *testing.T) {
   336  		a := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   337  			audit.AddAuditAnnotation(ctx, "timestamp", time.Now().String())
   338  			return snorlax, true, nil
   339  		}), false, time.Minute, 0)
   340  
   341  		allAnnotations := make([]map[string]string, 0, 10)
   342  
   343  		for i := 0; i < cap(allAnnotations); i++ {
   344  			ctx := withAudit(context.Background())
   345  			_, _, _ = a.AuthenticateToken(ctx, "token")
   346  			allAnnotations = append(allAnnotations, audit.AuditEventFrom(ctx).Annotations)
   347  		}
   348  
   349  		if len(allAnnotations) != cap(allAnnotations) {
   350  			t.Errorf("failed to process all annotations")
   351  		}
   352  
   353  		want := allAnnotations[0]
   354  		if ok := len(want) == 1 && len(want["timestamp"]) > 0; !ok {
   355  			t.Errorf("invalid annotations: %v", want)
   356  		}
   357  
   358  		for i, annotations := range allAnnotations[1:] {
   359  			if diff := cmp.Diff(want, annotations); diff != "" {
   360  				t.Errorf("%d: unexpected annotations (-want +got): %s", i, diff)
   361  			}
   362  		}
   363  	})
   364  
   365  	t.Run("different tokens can have different annotations", func(t *testing.T) {
   366  		a := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   367  			audit.AddAuditAnnotation(ctx, "timestamp", time.Now().String())
   368  			return snorlax, true, nil
   369  		}), false, time.Minute, 0)
   370  
   371  		ctx1 := withAudit(context.Background())
   372  		_, _, _ = a.AuthenticateToken(ctx1, "token1")
   373  		annotations1 := audit.AuditEventFrom(ctx1).Annotations
   374  
   375  		// guarantee different now times
   376  		time.Sleep(time.Second)
   377  
   378  		ctx2 := withAudit(context.Background())
   379  		_, _, _ = a.AuthenticateToken(ctx2, "token2")
   380  		annotations2 := audit.AuditEventFrom(ctx2).Annotations
   381  
   382  		if ok := len(annotations1) == 1 && len(annotations1["timestamp"]) > 0; !ok {
   383  			t.Errorf("invalid annotations 1: %v", annotations1)
   384  		}
   385  		if ok := len(annotations2) == 1 && len(annotations2["timestamp"]) > 0; !ok {
   386  			t.Errorf("invalid annotations 2: %v", annotations2)
   387  		}
   388  
   389  		if annotations1["timestamp"] == annotations2["timestamp"] {
   390  			t.Errorf("annotations should have different timestamp value: %v", annotations1)
   391  		}
   392  	})
   393  }
   394  
   395  func BenchmarkCachedTokenAuthenticator(b *testing.B) {
   396  	tokenCount := []int{100, 500, 2500, 12500, 62500}
   397  	threadCount := []int{1, 16, 256}
   398  	for _, tokens := range tokenCount {
   399  		for _, threads := range threadCount {
   400  			newSingleBenchmark(tokens, threads).run(b)
   401  		}
   402  	}
   403  }
   404  
   405  func newSingleBenchmark(tokens, threads int) *singleBenchmark {
   406  	s := &singleBenchmark{
   407  		threadCount: threads,
   408  		tokenCount:  tokens,
   409  	}
   410  	s.makeTokens()
   411  	return s
   412  }
   413  
   414  // singleBenchmark collects all the state needed to run a benchmark. The
   415  // question this benchmark answers is, "what's the average latency added by the
   416  // cache for N concurrent tokens?"
   417  //
   418  // Given the size of the key range constructed by this test, the default go
   419  // benchtime of 1 second is often inadequate to test caching and expiration
   420  // behavior. A benchtime of 10 to 30 seconds is adequate to stress these
   421  // code paths.
   422  type singleBenchmark struct {
   423  	threadCount int
   424  	// These token.* variables are set by makeTokens()
   425  	tokenCount int
   426  	// pre-computed response for a token
   427  	tokenToResponse map[string]*cacheRecord
   428  	// include audiences for some
   429  	tokenToAuds map[string]authenticator.Audiences
   430  	// a list makes it easy to select a random one
   431  	tokens []string
   432  }
   433  
   434  func (s *singleBenchmark) makeTokens() {
   435  	s.tokenToResponse = map[string]*cacheRecord{}
   436  	s.tokenToAuds = map[string]authenticator.Audiences{}
   437  	s.tokens = []string{}
   438  
   439  	rr := mathrand.New(mathrand.NewSource(mathrand.Int63()))
   440  
   441  	for i := 0; i < s.tokenCount; i++ {
   442  		tok := fmt.Sprintf("%v-%v", jwtToken, i)
   443  		r := cacheRecord{
   444  			resp: &authenticator.Response{
   445  				User: &user.DefaultInfo{Name: fmt.Sprintf("holder of token %v", i)},
   446  			},
   447  		}
   448  		// make different combinations of audience, failures, denies for the tokens.
   449  		auds := []string{}
   450  		for i := 0; i < rr.Intn(4); i++ {
   451  			auds = append(auds, string(uuid.NewUUID()))
   452  		}
   453  		choice := rr.Float64()
   454  		switch {
   455  		case choice < 0.9:
   456  			r.ok = true
   457  			r.err = nil
   458  
   459  			// add some realistic annotations on ~20% of successful authentications
   460  			if f := rr.Float64(); f < 0.2 {
   461  				r.annotations = map[string]string{
   462  					"audience.authentication.kubernetes.io":  "e8357258-88b1-11ea-bc55-0242ac130003",
   463  					"namespace.authentication.kubernetes.io": "kube-system",
   464  					"float.authentication.kubernetes.io":     fmt.Sprint(f),
   465  				}
   466  			}
   467  		case choice < 0.99:
   468  			r.ok = false
   469  			r.err = nil
   470  		default:
   471  			r.ok = false
   472  			r.err = errors.New("I can't think of a clever error name right now")
   473  		}
   474  		s.tokens = append(s.tokens, tok)
   475  		s.tokenToResponse[tok] = &r
   476  		if len(auds) > 0 {
   477  			s.tokenToAuds[tok] = auds
   478  		}
   479  	}
   480  }
   481  
   482  func (s *singleBenchmark) lookup(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   483  	r, ok := s.tokenToResponse[token]
   484  	if !ok {
   485  		panic("test setup problem")
   486  	}
   487  	for key, val := range r.annotations {
   488  		audit.AddAuditAnnotation(ctx, key, val)
   489  	}
   490  	return r.resp, r.ok, r.err
   491  }
   492  
   493  func (s *singleBenchmark) doAuthForTokenN(n int, a authenticator.Token) {
   494  	tok := s.tokens[n]
   495  	auds := s.tokenToAuds[tok]
   496  	ctx := context.Background()
   497  	ctx = authenticator.WithAudiences(ctx, auds)
   498  	a.AuthenticateToken(ctx, tok)
   499  }
   500  
   501  func (s *singleBenchmark) run(b *testing.B) {
   502  	b.Run(fmt.Sprintf("tokens=%d threads=%d", s.tokenCount, s.threadCount), s.bench)
   503  }
   504  
   505  func (s *singleBenchmark) bench(b *testing.B) {
   506  	// Simulate slowness, qps limit, external service limitation, etc
   507  	const maxInFlight = 40
   508  	chokepoint := make(chan struct{}, maxInFlight)
   509  	// lookup count
   510  	var lookups uint64
   511  
   512  	a := newWithClock(
   513  		authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   514  			atomic.AddUint64(&lookups, 1)
   515  
   516  			chokepoint <- struct{}{}
   517  			defer func() { <-chokepoint }()
   518  
   519  			time.Sleep(1 * time.Millisecond)
   520  
   521  			return s.lookup(ctx, token)
   522  		}),
   523  		true,
   524  		4*time.Second,
   525  		500*time.Millisecond,
   526  		clock.RealClock{},
   527  	)
   528  
   529  	b.ResetTimer()
   530  	b.SetParallelism(s.threadCount)
   531  	b.RunParallel(func(pb *testing.PB) {
   532  		r := mathrand.New(mathrand.NewSource(mathrand.Int63()))
   533  		for pb.Next() {
   534  			// some problems appear with random access, some appear with many
   535  			// requests for a single entry, so we do both.
   536  			s.doAuthForTokenN(r.Intn(s.tokenCount), a)
   537  			s.doAuthForTokenN(0, a)
   538  		}
   539  	})
   540  	b.StopTimer()
   541  
   542  	b.ReportMetric(float64(lookups)/float64(b.N), "lookups/op")
   543  }
   544  
   545  // Add a test version of the audit context with a pre-populated event for easy annotation
   546  // extraction.
   547  func withAudit(ctx context.Context) context.Context {
   548  	ctx = audit.WithAuditContext(ctx)
   549  	ac := audit.AuditContextFrom(ctx)
   550  	ac.Event.Level = auditinternal.LevelMetadata
   551  	return ctx
   552  }
   553  
   554  func TestUnsafeConversions(t *testing.T) {
   555  	t.Parallel()
   556  
   557  	// needs to be large to force allocations so we pick a random value between [1024, 2048]
   558  	size := utilrand.IntnRange(1024, 2048+1)
   559  
   560  	t.Run("toBytes semantics", func(t *testing.T) {
   561  		t.Parallel()
   562  
   563  		s := utilrand.String(size)
   564  		b := toBytes(s)
   565  		if len(b) != size {
   566  			t.Errorf("unexpected length: %d", len(b))
   567  		}
   568  		if cap(b) != size {
   569  			t.Errorf("unexpected capacity: %d", cap(b))
   570  		}
   571  		if !bytes.Equal(b, []byte(s)) {
   572  			t.Errorf("unexpected equality failure: %#v", b)
   573  		}
   574  	})
   575  
   576  	t.Run("toBytes allocations", func(t *testing.T) {
   577  		t.Parallel()
   578  
   579  		s := utilrand.String(size)
   580  		f := func() {
   581  			b := toBytes(s)
   582  			if len(b) != size {
   583  				t.Errorf("invalid length: %d", len(b))
   584  			}
   585  		}
   586  		allocs := testing.AllocsPerRun(100, f)
   587  		if allocs > 0 {
   588  			t.Errorf("expected zero allocations, got %v", allocs)
   589  		}
   590  	})
   591  
   592  	t.Run("toString semantics", func(t *testing.T) {
   593  		t.Parallel()
   594  
   595  		b := make([]byte, size)
   596  		if _, err := rand.Read(b); err != nil {
   597  			t.Fatal(err)
   598  		}
   599  		s := toString(b)
   600  		if len(s) != size {
   601  			t.Errorf("unexpected length: %d", len(s))
   602  		}
   603  		if s != string(b) {
   604  			t.Errorf("unexpected equality failure: %#v", s)
   605  		}
   606  	})
   607  
   608  	t.Run("toString allocations", func(t *testing.T) {
   609  		t.Parallel()
   610  
   611  		b := make([]byte, size)
   612  		if _, err := rand.Read(b); err != nil {
   613  			t.Fatal(err)
   614  		}
   615  		f := func() {
   616  			s := toString(b)
   617  			if len(s) != size {
   618  				t.Errorf("invalid length: %d", len(s))
   619  			}
   620  		}
   621  		allocs := testing.AllocsPerRun(100, f)
   622  		if allocs > 0 {
   623  			t.Errorf("expected zero allocations, got %v", allocs)
   624  		}
   625  	})
   626  }
   627  
   628  func TestKeyFunc(t *testing.T) {
   629  	t.Parallel()
   630  
   631  	hashPool := &sync.Pool{
   632  		New: func() interface{} {
   633  			return hmac.New(sha256.New, []byte("098c9e46-b7f4-4358-bb3c-35cb7495b836")) // deterministic HMAC for testing
   634  		},
   635  	}
   636  
   637  	// use realistic audiences
   638  	auds := []string{"7daf30b7-a85c-429b-8b21-e666aecbb235", "c22aa267-bdde-4acb-8505-998be7818400", "44f9b4f3-7125-4333-b04c-1446a16c6113"}
   639  
   640  	keyWithAuds := "\"\xf7\xac\xcd\x12\xf5\x83l\xa9;@\n\xa13a;\nd\x1f\xdelL\xd1\xe1!\x8a\xdahٛ\xbb\xf0"
   641  
   642  	keyWithoutAuds := "\x054a \xa5\x8e\xea\xb2?\x8c\x88\xb9,e\n5\xe7ȵ>\xfdK\x0e\x93+\x02˿&\xf98\x1e"
   643  
   644  	t.Run("has audiences", func(t *testing.T) {
   645  		t.Parallel()
   646  
   647  		key := keyFunc(hashPool, auds, jwtToken)
   648  		if key != keyWithAuds {
   649  			t.Errorf("unexpected equality failure: %#v", key)
   650  		}
   651  	})
   652  
   653  	t.Run("nil audiences", func(t *testing.T) {
   654  		t.Parallel()
   655  
   656  		key := keyFunc(hashPool, nil, jwtToken)
   657  		if key != keyWithoutAuds {
   658  			t.Errorf("unexpected equality failure: %#v", key)
   659  		}
   660  	})
   661  }