Path: blob/main/components/public-api-server/pkg/identityprovider/cache_test.go
2500 views
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package identityprovider56import (7"context"8"crypto/rand"9"crypto/rsa"10"encoding/base64"11"encoding/json"12"sort"13"testing"14"time"1516"github.com/alicebob/miniredis/v2"17"github.com/google/go-cmp/cmp"18"github.com/redis/go-redis/v9"19"gopkg.in/square/go-jose.v2"20)2122func testKeyID(k *rsa.PrivateKey) string {23return base64.RawURLEncoding.EncodeToString(k.PublicKey.N.Bytes())[0:12]24}2526func sortKeys(jwks *jose.JSONWebKeySet) {27sort.Slice(jwks.Keys, func(i, j int) bool {28var (29ki = jwks.Keys[i]30kj = jwks.Keys[j]31)32return ki.KeyID < kj.KeyID33})34}3536func TestRedisCachePublicKeys(t *testing.T) {37var (38jwks jose.JSONWebKeySet39threeKeys []*rsa.PrivateKey40)41for i := 0; i < 3; i++ {42key, err := rsa.GenerateKey(rand.Reader, 2048)43if err != nil {44panic(err)45}46threeKeys = append(threeKeys, key)47jwks.Keys = append(jwks.Keys, jose.JSONWebKey{48Key: &key.PublicKey,49Algorithm: string(jose.RS256),50KeyID: testKeyID(key),51Use: "sig",52})53}54sortKeys(&jwks)55threeKeysExpectation, err := json.Marshal(jwks)56if err != nil {57panic(err)58}5960type Expectation struct {61Error string62Response []byte63}64type Test struct {65Name string66Keys []*rsa.PrivateKey67StateMod func(*redis.Client) error68Expectation Expectation69}70tests := []Test{71{72Name: "redis down",73Keys: threeKeys,74StateMod: func(c *redis.Client) error {75return c.FlushAll(context.Background()).Err()76},77Expectation: Expectation{78Response: func() []byte {79fc, err := serializePublicKeyAsJSONWebKey(testKeyID(threeKeys[2]), &threeKeys[2].PublicKey)80if err != nil {81panic(err)82}83return []byte(`{"keys":[` + string(fc) + `]}`)84}(),85},86},87{88Name: "no keys",89Expectation: Expectation{90Response: []byte(`{"keys":[]}`),91},92},93{94Name: "no key in memory",95StateMod: func(c *redis.Client) error {96return c.Set(context.Background(), redisIDPKeyPrefix+"foo", `{"use":"sig","kty":"RSA","kid":"fpp","alg":"RS256","n":"VGVsbCBDaHJpcyB5b3UgZm91bmQgdGhpcyAtIGRyaW5rJ3Mgb24gbWU","e":"AQAB"}`, 0).Err()97},98Expectation: Expectation{99Response: []byte(`{"keys":[{"use":"sig","kty":"RSA","kid":"fpp","alg":"RS256","n":"VGVsbCBDaHJpcyB5b3UgZm91bmQgdGhpcyAtIGRyaW5rJ3Mgb24gbWU","e":"AQAB"}]}`),100},101},102{103Name: "multiple keys",104Keys: threeKeys,105Expectation: Expectation{106Response: threeKeysExpectation,107},108},109}110111for _, test := range tests {112t.Run(test.Name, func(t *testing.T) {113s := miniredis.RunT(t)114ctx, cancel := context.WithCancel(context.Background())115t.Cleanup(func() {116cancel()117})118client := redis.NewClient(&redis.Options{Addr: s.Addr()})119cache := NewRedisCache(ctx, client)120cache.keyID = testKeyID121for _, key := range test.Keys {122err := cache.Set(context.Background(), key)123if err != nil {124t.Fatal(err)125}126}127if test.StateMod != nil {128err := test.StateMod(client)129if err != nil {130t.Fatal(err)131}132}133134var (135act Expectation136err error137)138fc, err := cache.PublicKeys(context.Background())139if err != nil {140act.Error = err.Error()141}142if len(fc) > 0 {143var res jose.JSONWebKeySet144err = json.Unmarshal(fc, &res)145if err != nil {146t.Fatal(err)147}148sortKeys(&res)149act.Response, err = json.Marshal(&res)150if err != nil {151t.Fatal(err)152}153}154155if diff := cmp.Diff(test.Expectation, act); diff != "" {156t.Errorf("PublicKeys() mismatch (-want +got):\n%s", diff)157}158})159}160}161162func TestRedisCacheSigner(t *testing.T) {163s := miniredis.RunT(t)164client := redis.NewClient(&redis.Options{Addr: s.Addr()})165cache := NewRedisCache(context.Background(), client)166167sig, err := cache.Signer(context.Background())168if sig != nil {169t.Error("Signer() returned a signer despite having no key set")170}171if err != nil {172t.Errorf("Signer() returned an despite having no key set: %v", err)173}174175key, err := rsa.GenerateKey(rand.Reader, 2048)176if err != nil {177t.Fatal(err)178}179err = cache.Set(context.Background(), key)180if err != nil {181t.Fatalf("RedisCache failed to Set current key but shouldn't have: %v", err)182}183184sig, err = cache.Signer(context.Background())185if sig == nil {186t.Error("Signer() returned nil even though a key was set")187}188if err != nil {189t.Error("Signer() returned an error even though a key was set")190}191192signature, err := sig.Sign([]byte("foo"))193if err != nil {194t.Fatal(err)195}196_, err = signature.Verify(&key.PublicKey)197if err != nil {198t.Errorf("Returned signer does not sign with currently set key")199}200201err = client.FlushAll(context.Background()).Err()202if err != nil {203t.Fatal(err)204}205_, err = cache.Signer(context.Background())206if err != nil {207t.Fatal(err)208}209keys := client.Keys(context.Background(), redisIDPKeyPrefix+"*").Val()210if len(keys) == 0 {211t.Error("getting a new signer did not repersist the key")212}213}214215func TestRedisPeriodicallySync(t *testing.T) {216s := miniredis.RunT(t)217client := redis.NewClient(&redis.Options{Addr: s.Addr()})218cache := NewRedisCache(context.Background(), client, WithRefreshPeriod(1*time.Second))219220key, err := rsa.GenerateKey(rand.Reader, 2048)221if err != nil {222t.Fatal(err)223}224err = cache.Set(context.Background(), key)225if err != nil {226t.Fatalf("RedisCache failed to Set current key but shouldn't have: %v", err)227}228err = client.FlushAll(context.Background()).Err()229if err != nil {230t.Fatal(err)231}232time.Sleep(3 * time.Second)233keys := client.Keys(context.Background(), redisIDPKeyPrefix+"*").Val()234if len(keys) == 0 {235t.Error("redis periodically sync won't work")236}237}238239240