mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-03 11:45:45 +00:00
WIP towards revoking upstream refresh tokens during GC
- Discover the revocation endpoint of the upstream provider in oidc_upstream_watcher.go and save it into the cache for future use by the garbage collector controller - Adds RevokeRefreshToken to UpstreamOIDCIdentityProviderI - Implements the production version of RevokeRefreshToken - Implements test doubles for RevokeRefreshToken for future use in garbage collector's unit tests - Prefactors the crud and session storage types for future use in the garbage collector controller - See remaining TODOs in garbage_collector.go
This commit is contained in:
@@ -51,22 +51,20 @@ type JSON interface{} // document that we need valid JSON types
|
||||
|
||||
func New(resource string, secrets corev1client.SecretInterface, clock func() time.Time, lifetime time.Duration) Storage {
|
||||
return &secretsStorage{
|
||||
resource: resource,
|
||||
secretType: corev1.SecretType(fmt.Sprintf(secretTypeFormat, resource)),
|
||||
secretVersion: []byte(secretVersion),
|
||||
secrets: secrets,
|
||||
clock: clock,
|
||||
lifetime: lifetime,
|
||||
resource: resource,
|
||||
secretType: secretType(resource),
|
||||
secrets: secrets,
|
||||
clock: clock,
|
||||
lifetime: lifetime,
|
||||
}
|
||||
}
|
||||
|
||||
type secretsStorage struct {
|
||||
resource string
|
||||
secretType corev1.SecretType
|
||||
secretVersion []byte
|
||||
secrets corev1client.SecretInterface
|
||||
clock func() time.Time
|
||||
lifetime time.Duration
|
||||
resource string
|
||||
secretType corev1.SecretType
|
||||
secrets corev1client.SecretInterface
|
||||
clock func() time.Time
|
||||
lifetime time.Duration
|
||||
}
|
||||
|
||||
func (s *secretsStorage) Create(ctx context.Context, signature string, data JSON, additionalLabels map[string]string) (string, error) {
|
||||
@@ -86,28 +84,14 @@ func (s *secretsStorage) Get(ctx context.Context, signature string, data JSON) (
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get %s for signature %s: %w", s.resource, signature, err)
|
||||
}
|
||||
if err := s.validateSecret(secret); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := json.Unmarshal(secret.Data[secretDataKey], data); err != nil {
|
||||
return "", fmt.Errorf("failed to decode %s for signature %s: %w", s.resource, signature, err)
|
||||
|
||||
err = FromSecret(s.resource, secret, data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error during get for signature %s: %w", signature, err)
|
||||
}
|
||||
return secret.ResourceVersion, nil
|
||||
}
|
||||
|
||||
func (s *secretsStorage) validateSecret(secret *corev1.Secret) error {
|
||||
if secret.Type != s.secretType {
|
||||
return fmt.Errorf("%w: %s must equal %s", ErrSecretTypeMismatch, secret.Type, s.secretType)
|
||||
}
|
||||
if labelResource := secret.Labels[SecretLabelKey]; labelResource != s.resource {
|
||||
return fmt.Errorf("%w: %s must equal %s", ErrSecretLabelMismatch, labelResource, s.resource)
|
||||
}
|
||||
if !bytes.Equal(secret.Data[secretVersionKey], s.secretVersion) {
|
||||
return ErrSecretVersionMismatch // TODO should this be fatal or not?
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secretsStorage) Update(ctx context.Context, signature, resourceVersion string, data JSON) (string, error) {
|
||||
// Note: There may be a small bug here in that toSecret will move the SecretLifetimeAnnotationKey date forward
|
||||
// instead of keeping the storage resource's original SecretLifetimeAnnotationKey value. However, we only use
|
||||
@@ -154,6 +138,36 @@ func (s *secretsStorage) DeleteByLabel(ctx context.Context, labelName string, la
|
||||
return nil
|
||||
}
|
||||
|
||||
// FromSecret is similar to Get, but for when you already have a Secret in hand, e.g. from an informer.
|
||||
// It validates and unmarshals the Secret. The data parameter is filled in as the result.
|
||||
func FromSecret(resource string, secret *corev1.Secret, data JSON) error {
|
||||
if err := validateSecret(resource, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(secret.Data[secretDataKey], data); err != nil {
|
||||
return fmt.Errorf("failed to decode %s: %w", resource, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func secretType(resource string) corev1.SecretType {
|
||||
return corev1.SecretType(fmt.Sprintf(secretTypeFormat, resource))
|
||||
}
|
||||
|
||||
func validateSecret(resource string, secret *corev1.Secret) error {
|
||||
secretType := corev1.SecretType(fmt.Sprintf(secretTypeFormat, resource))
|
||||
if secret.Type != secretType {
|
||||
return fmt.Errorf("%w: %s must equal %s", ErrSecretTypeMismatch, secret.Type, secretType)
|
||||
}
|
||||
if labelResource := secret.Labels[SecretLabelKey]; labelResource != resource {
|
||||
return fmt.Errorf("%w: %s must equal %s", ErrSecretLabelMismatch, labelResource, resource)
|
||||
}
|
||||
if !bytes.Equal(secret.Data[secretVersionKey], []byte(secretVersion)) {
|
||||
return ErrSecretVersionMismatch // TODO should this be fatal or not?
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
var b32 = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
@@ -190,7 +204,7 @@ func (s *secretsStorage) toSecret(signature, resourceVersion string, data JSON,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
secretDataKey: buf,
|
||||
secretVersionKey: s.secretVersion,
|
||||
secretVersionKey: []byte(secretVersion),
|
||||
},
|
||||
Type: s.secretType,
|
||||
}, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package crud
|
||||
@@ -793,7 +793,8 @@ func TestStorage(t *testing.T) {
|
||||
require.Empty(t, rv1)
|
||||
require.Empty(t, out.Data)
|
||||
require.True(t, errors.Is(err, ErrSecretTypeMismatch))
|
||||
require.EqualError(t, err, "secret storage data has incorrect type: storage.pinniped.dev/candies-not must equal storage.pinniped.dev/candies")
|
||||
require.EqualError(t, err, "error during get for signature 5aUhdNmfWLW3yKX8Zfz5ztS5IiiWBgu36Gja-o2xl0I: "+
|
||||
"secret storage data has incorrect type: storage.pinniped.dev/candies-not must equal storage.pinniped.dev/candies")
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -856,7 +857,8 @@ func TestStorage(t *testing.T) {
|
||||
require.Empty(t, rv1)
|
||||
require.Empty(t, out.Data)
|
||||
require.True(t, errors.Is(err, ErrSecretLabelMismatch))
|
||||
require.EqualError(t, err, "secret storage data has incorrect label: candies-are-bad must equal candies")
|
||||
require.EqualError(t, err, "error during get for signature 5aUhdNmfWLW3yKX8Zfz5ztS5IiiWBgu36Gja-o2xl0I: "+
|
||||
"secret storage data has incorrect label: candies-are-bad must equal candies")
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -919,7 +921,8 @@ func TestStorage(t *testing.T) {
|
||||
require.Empty(t, rv1)
|
||||
require.Empty(t, out.Data)
|
||||
require.True(t, errors.Is(err, ErrSecretVersionMismatch))
|
||||
require.EqualError(t, err, "secret storage data has incorrect version")
|
||||
require.EqualError(t, err, "error during get for signature 5aUhdNmfWLW3yKX8Zfz5ztS5IiiWBgu36Gja-o2xl0I: "+
|
||||
"secret storage data has incorrect version")
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -981,7 +984,8 @@ func TestStorage(t *testing.T) {
|
||||
rv1, err := storage.Get(ctx, signature, out)
|
||||
require.Empty(t, rv1)
|
||||
require.Empty(t, out.Data)
|
||||
require.EqualError(t, err, "failed to decode candies for signature 5aUhdNmfWLW3yKX8Zfz5ztS5IiiWBgu36Gja-o2xl0I: invalid character '}' looking for beginning of value")
|
||||
require.EqualError(t, err, "error during get for signature 5aUhdNmfWLW3yKX8Zfz5ztS5IiiWBgu36Gja-o2xl0I: "+
|
||||
"failed to decode candies: invalid character '}' looking for beginning of value")
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -1095,3 +1099,155 @@ func errString(err error) string {
|
||||
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
func TestFromSecret(t *testing.T) {
|
||||
fakeNow := time.Date(2030, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
lifetime := time.Minute * 10
|
||||
fakeNowPlusLifetimeAsString := metav1.Time{Time: fakeNow.Add(lifetime)}.Format(time.RFC3339)
|
||||
|
||||
type testJSON struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
resource string
|
||||
secret *corev1.Secret
|
||||
wantData *testJSON
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
resource: "candies",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pinniped-storage-candies-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
||||
Namespace: "some-namespace",
|
||||
ResourceVersion: "",
|
||||
Labels: map[string]string{
|
||||
"storage.pinniped.dev/type": "candies",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"pinniped-storage-data": []byte(`{"Data":"snorlax"}`),
|
||||
"pinniped-storage-version": []byte("1"),
|
||||
},
|
||||
Type: "storage.pinniped.dev/candies",
|
||||
},
|
||||
wantData: &testJSON{Data: "snorlax"},
|
||||
},
|
||||
{
|
||||
name: "can't unmarshal",
|
||||
resource: "candies",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pinniped-storage-candies-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
||||
Namespace: "some-namespace",
|
||||
ResourceVersion: "",
|
||||
Labels: map[string]string{
|
||||
"storage.pinniped.dev/type": "candies",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"pinniped-storage-data": []byte(`not-json`),
|
||||
"pinniped-storage-version": []byte("1"),
|
||||
},
|
||||
Type: "storage.pinniped.dev/candies",
|
||||
},
|
||||
wantData: &testJSON{Data: "snorlax"},
|
||||
wantErr: "failed to decode candies: invalid character 'o' in literal null (expecting 'u')",
|
||||
},
|
||||
{
|
||||
name: "wrong storage version",
|
||||
resource: "candies",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pinniped-storage-candies-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
||||
Namespace: "some-namespace",
|
||||
ResourceVersion: "",
|
||||
Labels: map[string]string{
|
||||
"storage.pinniped.dev/type": "candies",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"pinniped-storage-data": []byte(`{"Data":"snorlax"}`),
|
||||
"pinniped-storage-version": []byte("wrong-version-here"),
|
||||
},
|
||||
Type: "storage.pinniped.dev/candies",
|
||||
},
|
||||
wantData: &testJSON{Data: "snorlax"},
|
||||
wantErr: "secret storage data has incorrect version",
|
||||
},
|
||||
{
|
||||
name: "wrong type label",
|
||||
resource: "candies",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pinniped-storage-candies-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
||||
Namespace: "some-namespace",
|
||||
ResourceVersion: "",
|
||||
Labels: map[string]string{
|
||||
"storage.pinniped.dev/type": "candies",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"pinniped-storage-data": []byte(`{"Data":"snorlax"}`),
|
||||
"pinniped-storage-version": []byte("1"),
|
||||
},
|
||||
Type: "storage.pinniped.dev/not-candies",
|
||||
},
|
||||
wantData: &testJSON{Data: "snorlax"},
|
||||
wantErr: "secret storage data has incorrect type: storage.pinniped.dev/not-candies must equal storage.pinniped.dev/candies",
|
||||
},
|
||||
{
|
||||
name: "wrong secret type",
|
||||
resource: "candies",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pinniped-storage-candies-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
||||
Namespace: "some-namespace",
|
||||
ResourceVersion: "",
|
||||
Labels: map[string]string{
|
||||
"storage.pinniped.dev/type": "candies",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"pinniped-storage-data": []byte(`{"Data":"snorlax"}`),
|
||||
"pinniped-storage-version": []byte("1"),
|
||||
},
|
||||
Type: "storage.pinniped.dev/not-candies",
|
||||
},
|
||||
wantData: &testJSON{Data: "snorlax"},
|
||||
wantErr: "secret storage data has incorrect type: storage.pinniped.dev/not-candies must equal storage.pinniped.dev/candies",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := &testJSON{}
|
||||
err := FromSecret("candies", tt.secret, data)
|
||||
if tt.wantErr == "" {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, data, tt.wantData)
|
||||
} else {
|
||||
require.EqualError(t, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user