mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-03 11:45:45 +00:00
Add fosite kube storage for access and refresh tokens
Also switched the token_handler_test.go to use kube storage. Signed-off-by: Aram Price <pricear@vmware.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
||||
"go.pinniped.dev/internal/constable"
|
||||
@@ -34,10 +35,11 @@ const (
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
Create(ctx context.Context, signature string, data JSON) (resourceVersion string, err error)
|
||||
Create(ctx context.Context, signature string, data JSON, additionalLabels map[string]string) (resourceVersion string, err error)
|
||||
Get(ctx context.Context, signature string, data JSON) (resourceVersion string, err error)
|
||||
Update(ctx context.Context, signature, resourceVersion string, data JSON) (newResourceVersion string, err error)
|
||||
Delete(ctx context.Context, signature string) error
|
||||
DeleteByLabel(ctx context.Context, labelName string, labelValue string) error
|
||||
}
|
||||
|
||||
type JSON interface{} // document that we need valid JSON types
|
||||
@@ -58,8 +60,8 @@ type secretsStorage struct {
|
||||
secrets corev1client.SecretInterface
|
||||
}
|
||||
|
||||
func (s *secretsStorage) Create(ctx context.Context, signature string, data JSON) (string, error) {
|
||||
secret, err := s.toSecret(signature, "", data)
|
||||
func (s *secretsStorage) Create(ctx context.Context, signature string, data JSON, additionalLabels map[string]string) (string, error) {
|
||||
secret, err := s.toSecret(signature, "", data, additionalLabels)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -98,7 +100,7 @@ func (s *secretsStorage) validateSecret(secret *corev1.Secret) error {
|
||||
}
|
||||
|
||||
func (s *secretsStorage) Update(ctx context.Context, signature, resourceVersion string, data JSON) (string, error) {
|
||||
secret, err := s.toSecret(signature, resourceVersion, data)
|
||||
secret, err := s.toSecret(signature, resourceVersion, data, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -116,6 +118,28 @@ func (s *secretsStorage) Delete(ctx context.Context, signature string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secretsStorage) DeleteByLabel(ctx context.Context, labelName string, labelValue string) error {
|
||||
list, err := s.secrets.List(ctx, metav1.ListOptions{
|
||||
LabelSelector: labels.Set{
|
||||
secretLabelKey: s.resource,
|
||||
labelName: labelValue,
|
||||
}.String(),
|
||||
})
|
||||
if err != nil {
|
||||
//nolint:err113 // there's nothing wrong with this error
|
||||
return fmt.Errorf(`failed to list secrets for resource "%s" matching label "%s=%s": %w`, s.resource, labelName, labelValue, err)
|
||||
}
|
||||
// TODO try to delete all of the items and consolidate all of the errors and return them all
|
||||
for _, secret := range list.Items {
|
||||
err = s.secrets.Delete(ctx, secret.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
//nolint:err113 // there's nothing wrong with this error
|
||||
return fmt.Errorf(`failed to delete secrets for resource "%s" matching label "%s=%s" with name %s: %w`, s.resource, labelName, labelValue, secret.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
var b32 = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
@@ -127,18 +151,24 @@ func (s *secretsStorage) getName(signature string) string {
|
||||
return fmt.Sprintf(secretNameFormat, s.resource, signatureAsValidName)
|
||||
}
|
||||
|
||||
func (s *secretsStorage) toSecret(signature, resourceVersion string, data JSON) (*corev1.Secret, error) {
|
||||
func (s *secretsStorage) toSecret(signature, resourceVersion string, data JSON, additionalLabels map[string]string) (*corev1.Secret, error) {
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode secret data for %s: %w", s.getName(signature), err)
|
||||
}
|
||||
|
||||
labels := map[string]string{
|
||||
secretLabelKey: s.resource, // make it easier to find this stuff via kubectl
|
||||
}
|
||||
for labelName, labelValue := range additionalLabels {
|
||||
labels[labelName] = labelValue
|
||||
}
|
||||
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: s.getName(signature),
|
||||
ResourceVersion: resourceVersion,
|
||||
Labels: map[string]string{
|
||||
secretLabelKey: s.resource, // make it easier to find this stuff via kubectl
|
||||
},
|
||||
Labels: labels,
|
||||
OwnerReferences: nil,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
|
||||
@@ -6,6 +6,7 @@ package crud
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ory/fosite/compose"
|
||||
@@ -87,6 +88,7 @@ func TestStorage(t *testing.T) {
|
||||
wantSecrets: nil,
|
||||
wantErr: `failed to delete tokens for signature not-a-token: secrets "pinniped-storage-tokens-t2fx427lnci6s" not found`,
|
||||
},
|
||||
// TODO make a delete non-existent test for DeleteByLabel
|
||||
{
|
||||
name: "create and get",
|
||||
resource: "access-tokens",
|
||||
@@ -97,7 +99,7 @@ func TestStorage(t *testing.T) {
|
||||
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)
|
||||
rv1, err := storage.Create(ctx, signature, data, nil)
|
||||
require.Empty(t, rv1) // fake client does not set this
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -145,6 +147,68 @@ func TestStorage(t *testing.T) {
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "create and get with additional labels",
|
||||
resource: "access-tokens",
|
||||
mocks: nil,
|
||||
run: func(t *testing.T, storage Storage) 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": "access-tokens",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
},
|
||||
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": "access-tokens",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
},
|
||||
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",
|
||||
@@ -325,6 +389,207 @@ func TestStorage(t *testing.T) {
|
||||
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": "seals",
|
||||
"additionalLabel": "matching-value",
|
||||
},
|
||||
},
|
||||
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": "seals",
|
||||
"additionalLabel": "matching-value",
|
||||
},
|
||||
},
|
||||
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": "seals", // same type as above
|
||||
"additionalLabel": "non-matching-value", // different value for the same label
|
||||
},
|
||||
},
|
||||
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": "walruses", // different type from above
|
||||
"additionalLabel": "matching-value", // same value for the same label as above
|
||||
},
|
||||
},
|
||||
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) 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=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": "seals", // same type as above
|
||||
"additionalLabel": "non-matching-value", // different value for the same label
|
||||
},
|
||||
},
|
||||
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": "walruses", // different type from above
|
||||
"additionalLabel": "matching-value", // same value for the same label as above
|
||||
},
|
||||
},
|
||||
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": "seals",
|
||||
"additionalLabel": "matching-value",
|
||||
},
|
||||
},
|
||||
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) 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=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": "seals",
|
||||
"additionalLabel": "matching-value",
|
||||
},
|
||||
},
|
||||
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")
|
||||
if !found || requiresExactMatch != "seals" {
|
||||
// this list action did not use label selector storage.pinniped.dev=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) 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=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",
|
||||
@@ -582,8 +847,11 @@ func checkSecretActionNames(t *testing.T, actions []coretesting.Action) {
|
||||
t.Helper()
|
||||
|
||||
for _, action := range actions {
|
||||
name := getName(t, action)
|
||||
assertValidName(t, name)
|
||||
_, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user