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:
Ryan Richard
2020-12-04 14:31:06 -08:00
committed by Aram Price
parent 8d5f4a93ed
commit ac83633888
13 changed files with 1192 additions and 82 deletions

View File

@@ -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{

View File

@@ -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)
}
}
}