e0ecdc004b
This is only a first commit towards making this feature work. - Hook dynamic clients into fosite by returning them from the storage interface (after finding and validating them) - In the auth endpoint, prevent the use of the username and password headers for dynamic clients to force them to use the browser-based login flows for all the upstream types - Add happy path integration tests in supervisor_login_test.go - Add lots of comments (and some small refactors) in supervisor_login_test.go to make it much easier to understand - Add lots of unit tests for the auth endpoint regarding dynamic clients (more unit tests to be added for other endpoints in follow-up commits) - Enhance crud.go to make lifetime=0 mean never garbage collect, since we want client secret storage Secrets to last forever - Move the OIDCClient validation code to a package where it can be shared between the controller and the fosite storage interface - Make shared test helpers for tests that need to create OIDC client secret storage Secrets - Create a public const for "pinniped-cli" now that we are using that string in several places in the production code
1321 lines
48 KiB
Go
1321 lines
48 KiB
Go
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package crud
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ory/fosite/compose"
|
|
"github.com/stretchr/testify/require"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
"k8s.io/apimachinery/pkg/api/validation"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
coretesting "k8s.io/client-go/testing"
|
|
clocktesting "k8s.io/utils/clock/testing"
|
|
)
|
|
|
|
func TestStorage(t *testing.T) {
|
|
ctx := context.Background()
|
|
secretsGVR := schema.GroupVersionResource{
|
|
Group: "",
|
|
Version: "v1",
|
|
Resource: "secrets",
|
|
}
|
|
|
|
type testJSON struct {
|
|
Data string
|
|
}
|
|
|
|
type mocker interface {
|
|
AddReactor(verb, resource string, reaction coretesting.ReactionFunc)
|
|
PrependReactor(verb, resource string, reaction coretesting.ReactionFunc)
|
|
Tracker() coretesting.ObjectTracker
|
|
}
|
|
|
|
hmac := compose.NewOAuth2HMACStrategy(&compose.Config{}, []byte("super-secret-32-byte-for-testing"), nil)
|
|
// test data generation via:
|
|
// code, signature, err := hmac.GenerateAuthorizeCode(ctx, nil)
|
|
|
|
validateSecretName := validation.NameIsDNSSubdomain // matches k/k
|
|
|
|
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)
|
|
|
|
const (
|
|
namespace = "test-ns"
|
|
authorizationCode1 = "81qE408EKL-e99gcXo3UnXBz9W05yGm92_hBmvXeadM.R5h38Bmw7yOaWNy0ypB3feh9toM-3T2zlwMXQyeE9B0"
|
|
authorizationCode2 = "p7aIiOLy-btBBlCro5RWm1QABANKCiC0JmDPhUtfOY4.XXJsYsMWhnSMJi9TXJcPO6SDVO2R_QXImwroxxnQPA8"
|
|
authorizationCode3 = "skKp1RjGgIwZhT3vaB_k1F3cIj2yp7U8a7UD0xAaemU.5aUhdNmfWLW3yKX8Zfz5ztS5IiiWBgu36Gja-o2xl0I"
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
resource string
|
|
mocks func(*testing.T, mocker)
|
|
lifetime func() time.Duration
|
|
run func(*testing.T, Storage, *clocktesting.FakeClock) error
|
|
wantActions []coretesting.Action
|
|
wantSecrets []corev1.Secret
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "get non-existent",
|
|
resource: "authcode",
|
|
mocks: nil,
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
_, err := storage.Get(ctx, "not-exists", nil)
|
|
return err
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-authcode-t2fx46yyvs3a"),
|
|
},
|
|
wantSecrets: nil,
|
|
wantErr: `failed to get authcode for signature not-exists: secrets "pinniped-storage-authcode-t2fx46yyvs3a" not found`,
|
|
},
|
|
{
|
|
name: "delete non-existent",
|
|
resource: "tokens",
|
|
mocks: nil,
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
return storage.Delete(ctx, "not-a-token")
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewDeleteAction(secretsGVR, namespace, "pinniped-storage-tokens-t2fx427lnci6s"),
|
|
},
|
|
wantSecrets: nil,
|
|
wantErr: `failed to delete tokens for signature not-a-token: secrets "pinniped-storage-tokens-t2fx427lnci6s" not found`,
|
|
},
|
|
{
|
|
name: "delete non-existent by label",
|
|
resource: "tokens",
|
|
mocks: nil,
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
return storage.DeleteByLabel(ctx, "additionalLabel", "matching-value")
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewListAction(secretsGVR, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"}, namespace, metav1.ListOptions{
|
|
LabelSelector: "storage.pinniped.dev/type=tokens,additionalLabel=matching-value",
|
|
}),
|
|
},
|
|
wantSecrets: nil,
|
|
wantErr: `failed to delete secrets for resource "tokens" matching label "additionalLabel=matching-value": none found`,
|
|
},
|
|
{
|
|
name: "create and get",
|
|
resource: "access-tokens",
|
|
mocks: nil,
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode1)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
data := &testJSON{Data: "create-and-get"}
|
|
rv1, err := storage.Create(ctx, signature, data, nil)
|
|
require.Empty(t, rv1) // fake client does not set this
|
|
require.NoError(t, err)
|
|
|
|
out := &testJSON{}
|
|
rv2, err := storage.Get(ctx, signature, out)
|
|
require.Empty(t, rv2) // fake client does not set this
|
|
require.NoError(t, err)
|
|
require.Equal(t, data, out)
|
|
|
|
return nil
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewCreateAction(secretsGVR, namespace, &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq",
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create-and-get"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
}),
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create-and-get"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "create multiple, each gets the correct lifetime timestamp",
|
|
resource: "access-tokens",
|
|
mocks: nil,
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
data := &testJSON{Data: "create1"}
|
|
rv1, err := storage.Create(ctx, "sig1", data, nil)
|
|
require.Empty(t, rv1) // fake client does not set this
|
|
require.NoError(t, err)
|
|
|
|
fakeClock.Step(42 * time.Minute) // simulate that a known amount of time has passed
|
|
|
|
data = &testJSON{Data: "create2"}
|
|
rv1, err = storage.Create(ctx, "sig2", data, nil)
|
|
require.Empty(t, rv1) // fake client does not set this
|
|
require.NoError(t, err)
|
|
|
|
return nil
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewCreateAction(secretsGVR, namespace, &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-wiudk",
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create1"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
}),
|
|
coretesting.NewCreateAction(secretsGVR, namespace, &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-wiudm",
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": metav1.Time{Time: fakeNow.Add(42 * time.Minute).Add(lifetime)}.Format(time.RFC3339),
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create2"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
}),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-wiudk",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create1"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-wiudm",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": metav1.Time{Time: fakeNow.Add(42 * time.Minute).Add(lifetime)}.Format(time.RFC3339),
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create2"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "create and get with additional labels",
|
|
resource: "access-tokens",
|
|
mocks: nil,
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode1)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
data := &testJSON{Data: "create-and-get"}
|
|
rv1, err := storage.Create(ctx, signature, data, map[string]string{"label1": "value1", "label2": "value2"})
|
|
require.Empty(t, rv1) // fake client does not set this
|
|
require.NoError(t, err)
|
|
|
|
out := &testJSON{}
|
|
rv2, err := storage.Get(ctx, signature, out)
|
|
require.Empty(t, rv2) // fake client does not set this
|
|
require.NoError(t, err)
|
|
require.Equal(t, data, out)
|
|
|
|
return nil
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewCreateAction(secretsGVR, namespace, &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq",
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
"label1": "value1",
|
|
"label2": "value2",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create-and-get"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
}),
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
"label1": "value1",
|
|
"label2": "value2",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create-and-get"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "get existing",
|
|
resource: "pandas-are-best",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
err := mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-pandas-are-best-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "pandas-are-best",
|
|
},
|
|
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/pandas-are-best",
|
|
})
|
|
require.NoError(t, err)
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode2)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
data := &testJSON{Data: "snorlax"}
|
|
out := &testJSON{}
|
|
rv1, err := storage.Get(ctx, signature, out)
|
|
require.Empty(t, rv1) // fake client does not set this
|
|
require.NoError(t, err)
|
|
require.Equal(t, data, out)
|
|
|
|
return nil
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-pandas-are-best-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-pandas-are-best-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "pandas-are-best",
|
|
},
|
|
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/pandas-are-best",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "update existing",
|
|
resource: "stores",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
err := mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-stores-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "35",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "stores",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"pants"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/stores",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
mock.PrependReactor("update", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
|
secret := action.(coretesting.UpdateAction).GetObject().(*corev1.Secret)
|
|
secret.ResourceVersion = "45"
|
|
return false, nil, nil // we mutated the secret in place but we do not "handle" it
|
|
})
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
data := &testJSON{Data: "pants"}
|
|
out := &testJSON{}
|
|
rv1, err := storage.Get(ctx, signature, out)
|
|
require.Equal(t, "35", rv1) // set in mock above
|
|
require.NoError(t, err)
|
|
require.Equal(t, data, out)
|
|
|
|
newData := &testJSON{Data: "shirts"}
|
|
rv2, err := storage.Update(ctx, signature, rv1, newData)
|
|
require.Equal(t, "45", rv2) // mock sets to a higher value on update
|
|
require.NoError(t, err)
|
|
|
|
newOut := &testJSON{}
|
|
rv3, err := storage.Get(ctx, signature, newOut)
|
|
require.Equal(t, "45", rv3) // we should see new rv now
|
|
require.NoError(t, err)
|
|
require.Equal(t, newData, newOut)
|
|
|
|
return nil
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-stores-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba"),
|
|
coretesting.NewUpdateAction(secretsGVR, namespace, &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-stores-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
ResourceVersion: "35", // update at initial RV
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "stores",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"shirts"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/stores",
|
|
}),
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-stores-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-stores-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "45", // final list at new RV
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "stores",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"shirts"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/stores",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "delete existing",
|
|
resource: "seals",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
err := mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "seals",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"sad-seal"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/seals",
|
|
})
|
|
require.NoError(t, err)
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode2)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
return storage.Delete(ctx, signature)
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewDeleteAction(secretsGVR, namespace, "pinniped-storage-seals-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq"),
|
|
},
|
|
wantSecrets: nil,
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "delete existing by label",
|
|
resource: "seals",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
require.NoError(t, mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "seals",
|
|
"additionalLabel": "matching-value",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"sad-seal"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/seals",
|
|
}))
|
|
require.NoError(t, mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-abcdywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "seals",
|
|
"additionalLabel": "matching-value",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"happy-seal"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/seals",
|
|
}))
|
|
require.NoError(t, mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-12345wdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "seals", // same type as above
|
|
"additionalLabel": "non-matching-value", // different value for the same label
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"sad-seal2"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/seals",
|
|
}))
|
|
require.NoError(t, mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-54321wdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "walruses", // different type from above
|
|
"additionalLabel": "matching-value", // same value for the same label as above
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"sad-seal3"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/walruses",
|
|
}))
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
return storage.DeleteByLabel(ctx, "additionalLabel", "matching-value")
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewListAction(secretsGVR, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"}, namespace, metav1.ListOptions{
|
|
LabelSelector: "storage.pinniped.dev/type=seals,additionalLabel=matching-value",
|
|
}),
|
|
coretesting.NewDeleteAction(secretsGVR, namespace, "pinniped-storage-seals-abcdywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq"),
|
|
coretesting.NewDeleteAction(secretsGVR, namespace, "pinniped-storage-seals-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
// the secret of the same type whose label did not match is not deleted
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-12345wdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "seals", // same type as above
|
|
"additionalLabel": "non-matching-value", // different value for the same label
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"sad-seal2"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/seals",
|
|
},
|
|
// the secrets of other types are not deleted, even if they have a matching label
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-54321wdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "walruses", // different type from above
|
|
"additionalLabel": "matching-value", // same value for the same label as above
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"sad-seal3"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/walruses",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "when there is an error performing the delete while deleting by label",
|
|
resource: "seals",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
require.NoError(t, mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "seals",
|
|
"additionalLabel": "matching-value",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"sad-seal"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/seals",
|
|
}))
|
|
mock.PrependReactor("delete", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
|
return true, nil, fmt.Errorf("some delete error")
|
|
})
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
return storage.DeleteByLabel(ctx, "additionalLabel", "matching-value")
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewListAction(secretsGVR, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"}, namespace, metav1.ListOptions{
|
|
LabelSelector: "storage.pinniped.dev/type=seals,additionalLabel=matching-value",
|
|
}),
|
|
coretesting.NewDeleteAction(secretsGVR, namespace, "pinniped-storage-seals-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-seals-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "seals",
|
|
"additionalLabel": "matching-value",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"sad-seal"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/seals",
|
|
},
|
|
},
|
|
wantErr: `failed to delete secrets for resource "seals" matching label "additionalLabel=matching-value" with name pinniped-storage-seals-lvzgyywdc2dhjdbgf5jvzfyphosigvhnsh6qlse3blumogoqhqhq: some delete error`,
|
|
},
|
|
{
|
|
name: "when there is an error listing secrets during a delete by label operation",
|
|
resource: "seals",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
mock.PrependReactor("list", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
|
listAction := action.(coretesting.ListActionImpl)
|
|
labelRestrictions := listAction.GetListRestrictions().Labels
|
|
requiresExactMatch, found := labelRestrictions.RequiresExactMatch("additionalLabel")
|
|
if !found || requiresExactMatch != "matching-value" {
|
|
// this list action did not use label selector additionalLabel=matching-value, so allow it to proceed without intervention
|
|
return false, nil, nil
|
|
}
|
|
requiresExactMatch, found = labelRestrictions.RequiresExactMatch("storage.pinniped.dev/type")
|
|
if !found || requiresExactMatch != "seals" {
|
|
// this list action did not use label selector storage.pinniped.dev/type=seals, so allow it to proceed without intervention
|
|
return false, nil, nil
|
|
}
|
|
// this list action was the one that did use the expected label selectors so cause it to error
|
|
return true, nil, fmt.Errorf("some listing error")
|
|
})
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
return storage.DeleteByLabel(ctx, "additionalLabel", "matching-value")
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewListAction(secretsGVR, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"}, namespace, metav1.ListOptions{
|
|
LabelSelector: "storage.pinniped.dev/type=seals,additionalLabel=matching-value",
|
|
}),
|
|
},
|
|
wantErr: `failed to list secrets for resource "seals" matching label "additionalLabel=matching-value": some listing error`,
|
|
},
|
|
{
|
|
name: "invalid exiting secret type",
|
|
resource: "candies",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
err := mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "55",
|
|
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":"twizzlers"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/candies-not",
|
|
})
|
|
require.NoError(t, err)
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
out := &testJSON{}
|
|
rv1, err := storage.Get(ctx, signature, out)
|
|
require.Empty(t, rv1)
|
|
require.Empty(t, out.Data)
|
|
require.True(t, errors.Is(err, ErrSecretTypeMismatch))
|
|
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
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "55",
|
|
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":"twizzlers"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/candies-not",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "invalid exiting secret wrong label",
|
|
resource: "candies",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
err := mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "55",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "candies-are-bad",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"twizzlers"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/candies",
|
|
})
|
|
require.NoError(t, err)
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
out := &testJSON{}
|
|
rv1, err := storage.Get(ctx, signature, out)
|
|
require.Empty(t, rv1)
|
|
require.Empty(t, out.Data)
|
|
require.True(t, errors.Is(err, ErrSecretLabelMismatch))
|
|
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
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "55",
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "candies-are-bad",
|
|
},
|
|
Annotations: map[string]string{
|
|
"storage.pinniped.dev/garbage-collect-after": fakeNowPlusLifetimeAsString,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"twizzlers"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/candies",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "invalid exiting secret wrong version",
|
|
resource: "candies",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
err := mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "55",
|
|
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":"twizzlers"}`),
|
|
"pinniped-storage-version": []byte("77"),
|
|
},
|
|
Type: "storage.pinniped.dev/candies",
|
|
})
|
|
require.NoError(t, err)
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
out := &testJSON{}
|
|
rv1, err := storage.Get(ctx, signature, out)
|
|
require.Empty(t, rv1)
|
|
require.Empty(t, out.Data)
|
|
require.True(t, errors.Is(err, ErrSecretVersionMismatch))
|
|
require.EqualError(t, err, "error during get for signature 5aUhdNmfWLW3yKX8Zfz5ztS5IiiWBgu36Gja-o2xl0I: "+
|
|
"secret storage data has incorrect version")
|
|
|
|
return nil
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "55",
|
|
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":"twizzlers"}`),
|
|
"pinniped-storage-version": []byte("77"),
|
|
},
|
|
Type: "storage.pinniped.dev/candies",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "invalid exiting secret not json",
|
|
resource: "candies",
|
|
mocks: func(t *testing.T, mock mocker) {
|
|
err := mock.Tracker().Add(&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "55",
|
|
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(`}}bad data{{`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/candies",
|
|
})
|
|
require.NoError(t, err)
|
|
},
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
out := &testJSON{}
|
|
rv1, err := storage.Get(ctx, signature, out)
|
|
require.Empty(t, rv1)
|
|
require.Empty(t, out.Data)
|
|
require.EqualError(t, err, "error during get for signature 5aUhdNmfWLW3yKX8Zfz5ztS5IiiWBgu36Gja-o2xl0I: "+
|
|
"failed to decode candies: invalid character '}' looking for beginning of value")
|
|
|
|
return nil
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-candies-4wssc5gzt5mlln6iux6gl7hzz3klsirisydaxn7indnpvdnrs5ba",
|
|
Namespace: namespace,
|
|
ResourceVersion: "55",
|
|
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(`}}bad data{{`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/candies",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
{
|
|
name: "create and get with infinite lifetime when lifetime is specified as zero",
|
|
resource: "access-tokens",
|
|
mocks: nil,
|
|
lifetime: func() time.Duration { return 0 }, // 0 == infinity
|
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
|
signature := hmac.AuthorizeCodeSignature(authorizationCode1)
|
|
require.NotEmpty(t, signature)
|
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
|
|
|
data := &testJSON{Data: "create-and-get"}
|
|
rv1, err := storage.Create(ctx, signature, data, nil)
|
|
require.Empty(t, rv1) // fake client does not set this
|
|
require.NoError(t, err)
|
|
|
|
out := &testJSON{}
|
|
rv2, err := storage.Get(ctx, signature, out)
|
|
require.Empty(t, rv2) // fake client does not set this
|
|
require.NoError(t, err)
|
|
require.Equal(t, data, out)
|
|
|
|
return nil
|
|
},
|
|
wantActions: []coretesting.Action{
|
|
coretesting.NewCreateAction(secretsGVR, namespace, &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq",
|
|
ResourceVersion: "",
|
|
// No garbage collection annotation was added.
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create-and-get"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
}),
|
|
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq"),
|
|
},
|
|
wantSecrets: []corev1.Secret{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pinniped-storage-access-tokens-i6mhp4azwdxshgsy3s2mvedxpxuh3nudh3ot3m4xamlugj4e6qoq",
|
|
Namespace: namespace,
|
|
ResourceVersion: "",
|
|
// No garbage collection annotation was added.
|
|
Labels: map[string]string{
|
|
"storage.pinniped.dev/type": "access-tokens",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"pinniped-storage-data": []byte(`{"Data":"create-and-get"}`),
|
|
"pinniped-storage-version": []byte("1"),
|
|
},
|
|
Type: "storage.pinniped.dev/access-tokens",
|
|
},
|
|
},
|
|
wantErr: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := fake.NewSimpleClientset()
|
|
if tt.mocks != nil {
|
|
tt.mocks(t, client)
|
|
}
|
|
useLifetime := lifetime
|
|
if tt.lifetime != nil {
|
|
useLifetime = tt.lifetime()
|
|
}
|
|
secrets := client.CoreV1().Secrets(namespace)
|
|
fakeClock := clocktesting.NewFakeClock(fakeNow)
|
|
storage := New(tt.resource, secrets, fakeClock.Now, useLifetime)
|
|
|
|
err := tt.run(t, storage, fakeClock)
|
|
|
|
require.Equal(t, tt.wantErr, errString(err))
|
|
require.Equal(t, tt.wantActions, client.Actions())
|
|
checkSecretActionNames(t, client.Actions())
|
|
actualSecrets, err := secrets.List(ctx, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.wantSecrets, actualSecrets.Items)
|
|
checkSecretListNames(t, actualSecrets.Items)
|
|
})
|
|
}
|
|
}
|
|
|
|
func checkSecretActionNames(t *testing.T, actions []coretesting.Action) {
|
|
t.Helper()
|
|
|
|
for _, action := range actions {
|
|
_, ok := action.(coretesting.ListActionImpl)
|
|
if !ok { // list action don't have names, so skip these assertions for list actions
|
|
name := getName(t, action)
|
|
assertValidName(t, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func checkSecretListNames(t *testing.T, secrets []corev1.Secret) {
|
|
t.Helper()
|
|
|
|
for _, secret := range secrets {
|
|
assertValidName(t, secret.Name)
|
|
}
|
|
}
|
|
|
|
func assertValidName(t *testing.T, name string) {
|
|
t.Helper()
|
|
|
|
validateSecretName := validation.NameIsDNSSubdomain // matches k/k
|
|
|
|
require.NotEmpty(t, name)
|
|
require.Empty(t, validateSecretName(name, false))
|
|
require.Empty(t, validateSecretName(name, true)) // I do not think we actually care about this case
|
|
}
|
|
|
|
func getName(t *testing.T, action coretesting.Action) string {
|
|
t.Helper()
|
|
|
|
if getter, ok := action.(interface {
|
|
GetName() string
|
|
}); ok {
|
|
return getter.GetName()
|
|
}
|
|
|
|
if getter, ok := action.(interface {
|
|
GetObject() runtime.Object
|
|
}); ok {
|
|
accessor, err := meta.Accessor(getter.GetObject())
|
|
require.NoError(t, err)
|
|
return accessor.GetName()
|
|
}
|
|
|
|
t.Fatalf("failed to get name for action: %#v", action)
|
|
panic("unreachable")
|
|
}
|
|
|
|
func errString(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|