Merge branch 'main' into username-and-subject-claims

This commit is contained in:
Matt Moyer
2020-12-16 15:23:19 -06:00
committed by GitHub
78 changed files with 3631 additions and 332 deletions
@@ -0,0 +1,104 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package generator
import (
"crypto/rand"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
)
const (
opKind = "OIDCProvider"
)
func generateSymmetricKey() ([]byte, error) {
b := make([]byte, symmetricKeySize)
if _, err := rand.Read(b); err != nil {
return nil, err
}
return b, nil
}
func isValid(secret *corev1.Secret, labels map[string]string) bool {
if secret.Type != symmetricSecretType {
return false
}
data, ok := secret.Data[symmetricSecretDataKey]
if !ok {
return false
}
if len(data) != symmetricKeySize {
return false
}
for key, value := range labels {
if secret.Labels[key] != value {
return false
}
}
return true
}
func secretDataFunc() (map[string][]byte, error) {
symmetricKey, err := generateKey()
if err != nil {
return nil, err
}
return map[string][]byte{
symmetricSecretDataKey: symmetricKey,
}, nil
}
func generateSecret(namespace, name string, labels map[string]string, secretDataFunc func() (map[string][]byte, error), owner metav1.Object) (*corev1.Secret, error) {
secretData, err := secretDataFunc()
if err != nil {
return nil, err
}
deploymentGVK := schema.GroupVersionKind{
Group: appsv1.SchemeGroupVersion.Group,
Version: appsv1.SchemeGroupVersion.Version,
Kind: "Deployment",
}
blockOwnerDeletion := true
isController := false
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: deploymentGVK.GroupVersion().String(),
Kind: deploymentGVK.Kind,
Name: owner.GetName(),
UID: owner.GetUID(),
BlockOwnerDeletion: &blockOwnerDeletion,
Controller: &isController,
},
},
Labels: labels,
},
Type: symmetricSecretType,
Data: secretData,
}, nil
}
// isOPCControlle returns whether the provided obj is controlled by an OPC.
func isOPControllee(obj metav1.Object) bool {
controller := metav1.GetControllerOf(obj)
return controller != nil &&
controller.APIVersion == configv1alpha1.SchemeGroupVersion.String() &&
controller.Kind == opKind
}
@@ -0,0 +1,232 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package generator
import (
"context"
"fmt"
"reflect"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/supervisor/clientset/versioned"
configinformers "go.pinniped.dev/generated/1.19/client/supervisor/informers/externalversions/config/v1alpha1"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/plog"
)
type oidcProviderSecretsController struct {
secretHelper SecretHelper
kubeClient kubernetes.Interface
pinnipedClient pinnipedclientset.Interface
opcInformer configinformers.OIDCProviderInformer
secretInformer corev1informers.SecretInformer
}
// NewOIDCProviderSecretsController returns a controllerlib.Controller that ensures a child Secret
// always exists for a parent OIDCProvider. It does this using the provided secretHelper, which
// provides the parent/child mapping logic.
func NewOIDCProviderSecretsController(
secretHelper SecretHelper,
kubeClient kubernetes.Interface,
pinnipedClient pinnipedclientset.Interface,
secretInformer corev1informers.SecretInformer,
opcInformer configinformers.OIDCProviderInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller {
return controllerlib.New(
controllerlib.Config{
Name: fmt.Sprintf("%s%s", secretHelper.NamePrefix(), "controller"),
Syncer: &oidcProviderSecretsController{
secretHelper: secretHelper,
kubeClient: kubeClient,
pinnipedClient: pinnipedClient,
secretInformer: secretInformer,
opcInformer: opcInformer,
},
},
// We want to be notified when a OPC's secret gets updated or deleted. When this happens, we
// should get notified via the corresponding OPC key.
withInformer(
secretInformer,
pinnipedcontroller.SimpleFilter(isOPControllee, func(obj metav1.Object) controllerlib.Key {
if isOPControllee(obj) {
controller := metav1.GetControllerOf(obj)
return controllerlib.Key{
Name: controller.Name,
Namespace: obj.GetNamespace(),
}
}
return controllerlib.Key{}
}),
controllerlib.InformerOption{},
),
// We want to be notified when anything happens to an OPC.
withInformer(
opcInformer,
pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct
controllerlib.InformerOption{},
),
)
}
func (c *oidcProviderSecretsController) Sync(ctx controllerlib.Context) error {
op, err := c.opcInformer.Lister().OIDCProviders(ctx.Key.Namespace).Get(ctx.Key.Name)
notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound {
return fmt.Errorf(
"failed to get %s/%s OIDCProvider: %w",
ctx.Key.Namespace,
ctx.Key.Name,
err,
)
}
if notFound {
// The corresponding secret to this OP should have been garbage collected since it should have
// had this OP as its owner.
plog.Debug(
"oidcprovider deleted",
"oidcprovider",
klog.KRef(ctx.Key.Namespace, ctx.Key.Name),
)
return nil
}
newSecret, err := c.secretHelper.Generate(op)
if err != nil {
return fmt.Errorf("failed to generate secret: %w", err)
}
secretNeedsUpdate, existingSecret, err := c.secretNeedsUpdate(op, newSecret.Name)
if err != nil {
return fmt.Errorf("failed to determine secret status: %w", err)
}
if !secretNeedsUpdate {
// Secret is up to date - we are good to go.
plog.Debug(
"secret is up to date",
"oidcprovider",
klog.KObj(op),
"secret",
klog.KObj(existingSecret),
)
op = c.secretHelper.ObserveActiveSecretAndUpdateParentOIDCProvider(op, existingSecret)
if err := c.updateOIDCProvider(ctx.Context, op); err != nil {
return fmt.Errorf("failed to update oidcprovider: %w", err)
}
plog.Debug("updated oidcprovider", "oidcprovider", klog.KObj(op), "secret", klog.KObj(newSecret))
return nil
}
// If the OP does not have a secret associated with it, that secret does not exist, or the secret
// is invalid, we will create a new secret.
if err := c.createOrUpdateSecret(ctx.Context, op, &newSecret); err != nil {
return fmt.Errorf("failed to create or update secret: %w", err)
}
plog.Debug("created/updated secret", "oidcprovider", klog.KObj(op), "secret", klog.KObj(newSecret))
op = c.secretHelper.ObserveActiveSecretAndUpdateParentOIDCProvider(op, newSecret)
if err := c.updateOIDCProvider(ctx.Context, op); err != nil {
return fmt.Errorf("failed to update oidcprovider: %w", err)
}
plog.Debug("updated oidcprovider", "oidcprovider", klog.KObj(op), "secret", klog.KObj(newSecret))
return nil
}
// secretNeedsUpdate returns whether or not the Secret, with name secretName, for OIDCProvider op
// needs to be updated. It returns the existing secret as its second argument.
func (c *oidcProviderSecretsController) secretNeedsUpdate(
op *configv1alpha1.OIDCProvider,
secretName string,
) (bool, *corev1.Secret, error) {
// This OPC says it has a secret associated with it. Let's try to get it from the cache.
secret, err := c.secretInformer.Lister().Secrets(op.Namespace).Get(secretName)
notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound {
return false, nil, fmt.Errorf("cannot get secret: %w", err)
}
if notFound {
// If we can't find the secret, let's assume we need to create it.
return true, nil, nil
}
if !c.secretHelper.IsValid(op, secret) {
// If this secret is invalid, we need to generate a new one.
return true, secret, nil
}
return false, secret, nil
}
func (c *oidcProviderSecretsController) createOrUpdateSecret(
ctx context.Context,
op *configv1alpha1.OIDCProvider,
newSecret **corev1.Secret,
) error {
secretClient := c.kubeClient.CoreV1().Secrets((*newSecret).Namespace)
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
oldSecret, err := secretClient.Get(ctx, (*newSecret).Name, metav1.GetOptions{})
notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound {
return fmt.Errorf("failed to get secret %s/%s: %w", (*newSecret).Namespace, (*newSecret).Name, err)
}
if notFound {
// New secret doesn't exist, so create it.
_, err := secretClient.Create(ctx, *newSecret, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("failed to create secret %s/%s: %w", (*newSecret).Namespace, (*newSecret).Name, err)
}
return nil
}
// New secret already exists, so ensure it is up to date.
if c.secretHelper.IsValid(op, oldSecret) {
// If the secret already has valid a valid Secret, then we are good to go and we don't need an
// update.
*newSecret = oldSecret
return nil
}
oldSecret.Labels = (*newSecret).Labels
oldSecret.Type = (*newSecret).Type
oldSecret.Data = (*newSecret).Data
*newSecret = oldSecret
_, err = secretClient.Update(ctx, oldSecret, metav1.UpdateOptions{})
return err
})
}
func (c *oidcProviderSecretsController) updateOIDCProvider(
ctx context.Context,
newOP *configv1alpha1.OIDCProvider,
) error {
opcClient := c.pinnipedClient.ConfigV1alpha1().OIDCProviders(newOP.Namespace)
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
oldOP, err := opcClient.Get(ctx, newOP.Name, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get oidcprovider %s/%s: %w", newOP.Namespace, newOP.Name, err)
}
if reflect.DeepEqual(newOP.Status.Secrets, oldOP.Status.Secrets) {
return nil
}
oldOP.Status.Secrets = newOP.Status.Secrets
_, err = opcClient.Update(ctx, oldOP, metav1.UpdateOptions{})
return err
})
}
@@ -0,0 +1,625 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package generator
import (
"context"
"errors"
"fmt"
"sync"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
kubeinformers "k8s.io/client-go/informers"
kubernetesfake "k8s.io/client-go/kubernetes/fake"
kubetesting "k8s.io/client-go/testing"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
pinnipedfake "go.pinniped.dev/generated/1.19/client/supervisor/clientset/versioned/fake"
pinnipedinformers "go.pinniped.dev/generated/1.19/client/supervisor/informers/externalversions"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/mocks/mocksecrethelper"
"go.pinniped.dev/internal/testutil"
)
func TestOIDCProviderControllerFilterSecret(t *testing.T) {
t.Parallel()
tests := []struct {
name string
secret corev1.Secret
wantAdd bool
wantUpdate bool
wantDelete bool
wantParent controllerlib.Key
}{
{
name: "no owner reference",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{},
},
},
{
name: "owner reference without correct APIVersion",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
Kind: "OIDCProvider",
Name: "some-name",
Controller: boolPtr(true),
},
},
},
},
},
{
name: "owner reference without correct Kind",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
Name: "some-name",
Controller: boolPtr(true),
},
},
},
},
},
{
name: "owner reference without controller set to true",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
Kind: "OIDCProvider",
Name: "some-name",
},
},
},
},
},
{
name: "correct owner reference",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
Kind: "OIDCProvider",
Name: "some-name",
Controller: boolPtr(true),
},
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
wantParent: controllerlib.Key{Namespace: "some-namespace", Name: "some-name"},
},
{
name: "multiple owner references",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
Kind: "UnrelatedKind",
},
{
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
Kind: "OIDCProvider",
Name: "some-name",
Controller: boolPtr(true),
},
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
wantParent: controllerlib.Key{Namespace: "some-namespace", Name: "some-name"},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)
secretHelper := mocksecrethelper.NewMockSecretHelper(ctrl)
secretHelper.EXPECT().NamePrefix().Times(1).Return("some-name")
secretInformer := kubeinformers.NewSharedInformerFactory(
kubernetesfake.NewSimpleClientset(),
0,
).Core().V1().Secrets()
opcInformer := pinnipedinformers.NewSharedInformerFactory(
pinnipedfake.NewSimpleClientset(),
0,
).Config().V1alpha1().OIDCProviders()
withInformer := testutil.NewObservableWithInformerOption()
_ = NewOIDCProviderSecretsController(
secretHelper,
nil, // kubeClient, not needed
nil, // pinnipedClient, not needed
secretInformer,
opcInformer,
withInformer.WithInformer,
)
unrelated := corev1.Secret{}
filter := withInformer.GetFilterForInformer(secretInformer)
require.Equal(t, test.wantAdd, filter.Add(&test.secret))
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.secret))
require.Equal(t, test.wantUpdate, filter.Update(&test.secret, &unrelated))
require.Equal(t, test.wantDelete, filter.Delete(&test.secret))
require.Equal(t, test.wantParent, filter.Parent(&test.secret))
})
}
}
func TestNewOIDCProviderSecretsControllerFilterOPC(t *testing.T) {
t.Parallel()
tests := []struct {
name string
opc configv1alpha1.OIDCProvider
wantAdd bool
wantUpdate bool
wantDelete bool
wantParent controllerlib.Key
}{
{
name: "anything goes",
opc: configv1alpha1.OIDCProvider{},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
wantParent: controllerlib.Key{},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)
secretHelper := mocksecrethelper.NewMockSecretHelper(ctrl)
secretHelper.EXPECT().NamePrefix().Times(1).Return("some-name")
secretInformer := kubeinformers.NewSharedInformerFactory(
kubernetesfake.NewSimpleClientset(),
0,
).Core().V1().Secrets()
opcInformer := pinnipedinformers.NewSharedInformerFactory(
pinnipedfake.NewSimpleClientset(),
0,
).Config().V1alpha1().OIDCProviders()
withInformer := testutil.NewObservableWithInformerOption()
_ = NewOIDCProviderSecretsController(
secretHelper,
nil, // kubeClient, not needed
nil, // pinnipedClient, not needed
secretInformer,
opcInformer,
withInformer.WithInformer,
)
unrelated := configv1alpha1.OIDCProvider{}
filter := withInformer.GetFilterForInformer(opcInformer)
require.Equal(t, test.wantAdd, filter.Add(&test.opc))
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.opc))
require.Equal(t, test.wantUpdate, filter.Update(&test.opc, &unrelated))
require.Equal(t, test.wantDelete, filter.Delete(&test.opc))
require.Equal(t, test.wantParent, filter.Parent(&test.opc))
})
}
}
func TestOIDCProviderSecretsControllerSync(t *testing.T) {
t.Parallel()
const (
namespace = "some-namespace"
opName = "op-name"
opUID = "op-uid"
secretName = "secret-name"
secretUID = "secret-uid"
)
opGVR := schema.GroupVersionResource{
Group: configv1alpha1.SchemeGroupVersion.Group,
Version: configv1alpha1.SchemeGroupVersion.Version,
Resource: "oidcproviders",
}
secretGVR := schema.GroupVersionResource{
Group: corev1.SchemeGroupVersion.Group,
Version: corev1.SchemeGroupVersion.Version,
Resource: "secrets",
}
goodOP := &configv1alpha1.OIDCProvider{
ObjectMeta: metav1.ObjectMeta{
Name: opName,
Namespace: namespace,
UID: opUID,
},
}
goodSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
UID: secretUID,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: opGVR.GroupVersion().String(),
Kind: "OIDCProvider",
Name: opName,
UID: opUID,
BlockOwnerDeletion: boolPtr(true),
Controller: boolPtr(true),
},
},
Labels: map[string]string{
"some-key-0": "some-value-0",
"some-key-1": "some-value-1",
},
},
Type: "some-secret-type",
Data: map[string][]byte{
"some-key": []byte("some-value"),
},
}
goodOPWithStatus := goodOP.DeepCopy()
goodOPWithStatus.Status.Secrets.TokenSigningKey.Name = goodSecret.Name
invalidSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
UID: secretUID,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: opGVR.GroupVersion().String(),
Kind: "OIDCProvider",
Name: opName,
UID: opUID,
BlockOwnerDeletion: boolPtr(true),
Controller: boolPtr(true),
},
},
},
}
tests := []struct {
name string
storage func(**configv1alpha1.OIDCProvider, **corev1.Secret)
client func(*pinnipedfake.Clientset, *kubernetesfake.Clientset)
secretHelper func(*mocksecrethelper.MockSecretHelper)
wantOPActions []kubetesting.Action
wantSecretActions []kubetesting.Action
wantError string
}{
{
name: "OIDCProvider does not exist and secret does not exist",
storage: func(op **configv1alpha1.OIDCProvider, s **corev1.Secret) {
*op = nil
*s = nil
},
},
{
name: "OIDCProvider does not exist and secret exists",
storage: func(op **configv1alpha1.OIDCProvider, s **corev1.Secret) {
*op = nil
},
},
{
name: "OIDCProvider exists and secret does not exist",
storage: func(op **configv1alpha1.OIDCProvider, s **corev1.Secret) {
*s = nil
},
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentOIDCProvider(goodOP, goodSecret).Times(1).Return(goodOPWithStatus)
},
wantOPActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithStatus),
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewCreateAction(secretGVR, namespace, goodSecret),
},
},
{
name: "OIDCProvider exists and invalid secret exists",
storage: func(op **configv1alpha1.OIDCProvider, s **corev1.Secret) {
*s = invalidSecret.DeepCopy()
},
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentOIDCProvider(goodOP, goodSecret).Times(1).Return(goodOPWithStatus)
},
wantOPActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithStatus),
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
},
},
{
name: "OIDCProvider exists and generating a secret fails",
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(nil, errors.New("some generate error"))
},
wantError: "failed to generate secret: some generate error",
},
{
name: "OIDCProvider exists and invalid secret exists and upon update we learn of a valid secret",
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
otherSecret := goodSecret.DeepCopy()
otherSecret.UID = "other-secret-uid"
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(otherSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(false)
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(true)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentOIDCProvider(goodOP, goodSecret).Times(1).Return(goodOPWithStatus)
},
wantOPActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithStatus),
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
},
},
{
name: "OIDCProvider exists and invalid secret exists and getting secret fails",
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(false)
},
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
c.PrependReactor("get", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some get error")
})
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
},
wantError: fmt.Sprintf("failed to create or update secret: failed to get secret %s/%s: some get error", namespace, goodSecret.Name),
},
{
name: "OIDCProvider exists and secret does not exist and creating secret fails",
storage: func(op **configv1alpha1.OIDCProvider, s **corev1.Secret) {
*s = nil
},
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
},
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
c.PrependReactor("create", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some create error")
})
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewCreateAction(secretGVR, namespace, goodSecret),
},
wantError: fmt.Sprintf("failed to create or update secret: failed to create secret %s/%s: some create error", namespace, goodSecret.Name),
},
{
name: "OIDCProvider exists and invalid secret exists and updating secret fails",
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(2).Return(false)
},
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
c.PrependReactor("update", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some update error")
})
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
},
wantError: "failed to create or update secret: some update error",
},
{
name: "OIDCProvider exists and invalid secret exists and updating secret fails due to conflict",
storage: func(op **configv1alpha1.OIDCProvider, s **corev1.Secret) {
*s = invalidSecret.DeepCopy()
},
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(3).Return(false)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentOIDCProvider(goodOP, goodSecret).Times(1).Return(goodOPWithStatus)
},
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
once := sync.Once{}
c.PrependReactor("update", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
var err error
once.Do(func() { err = k8serrors.NewConflict(secretGVR.GroupResource(), namespace, errors.New("some error")) })
return true, nil, err
})
},
wantOPActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithStatus),
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
},
},
{
name: "OIDCProvider exists and invalid secret exists and getting OIDCProvider fails",
storage: func(op **configv1alpha1.OIDCProvider, s **corev1.Secret) {
*s = invalidSecret.DeepCopy()
},
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentOIDCProvider(goodOP, goodSecret).Times(1).Return(goodOPWithStatus)
},
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
c.PrependReactor("get", "oidcproviders", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some get error")
})
},
wantOPActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
},
wantError: fmt.Sprintf("failed to update oidcprovider: failed to get oidcprovider %s/%s: some get error", goodOPWithStatus.Namespace, goodOPWithStatus.Name),
},
{
name: "OIDCProvider exists and invalid secret exists and updating OIDCProvider fails due to conflict",
storage: func(op **configv1alpha1.OIDCProvider, s **corev1.Secret) {
*s = invalidSecret.DeepCopy()
},
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentOIDCProvider(goodOP, goodSecret).Times(1).Return(goodOPWithStatus)
},
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
once := sync.Once{}
c.PrependReactor("update", "oidcproviders", func(_ kubetesting.Action) (bool, runtime.Object, error) {
var err error
once.Do(func() { err = k8serrors.NewConflict(secretGVR.GroupResource(), namespace, errors.New("some error")) })
return true, nil, err
})
},
wantOPActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithStatus),
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithStatus),
},
wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
pinnipedAPIClient := pinnipedfake.NewSimpleClientset()
pinnipedInformerClient := pinnipedfake.NewSimpleClientset()
kubeAPIClient := kubernetesfake.NewSimpleClientset()
kubeInformerClient := kubernetesfake.NewSimpleClientset()
op := goodOP.DeepCopy()
secret := goodSecret.DeepCopy()
if test.storage != nil {
test.storage(&op, &secret)
}
if op != nil {
require.NoError(t, pinnipedAPIClient.Tracker().Add(op))
require.NoError(t, pinnipedInformerClient.Tracker().Add(op))
}
if secret != nil {
require.NoError(t, kubeAPIClient.Tracker().Add(secret))
require.NoError(t, kubeInformerClient.Tracker().Add(secret))
}
if test.client != nil {
test.client(pinnipedAPIClient, kubeAPIClient)
}
kubeInformers := kubeinformers.NewSharedInformerFactory(
kubeInformerClient,
0,
)
pinnipedInformers := pinnipedinformers.NewSharedInformerFactory(
pinnipedInformerClient,
0,
)
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)
secretHelper := mocksecrethelper.NewMockSecretHelper(ctrl)
secretHelper.EXPECT().NamePrefix().Times(1).Return("some-name")
if test.secretHelper != nil {
test.secretHelper(secretHelper)
}
c := NewOIDCProviderSecretsController(
secretHelper,
kubeAPIClient,
pinnipedAPIClient,
kubeInformers.Core().V1().Secrets(),
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
controllerlib.WithInformer,
)
// Must start informers before calling TestRunSynchronously().
kubeInformers.Start(ctx.Done())
pinnipedInformers.Start(ctx.Done())
controllerlib.TestRunSynchronously(t, c)
err := controllerlib.TestSync(t, c, controllerlib.Context{
Context: ctx,
Key: controllerlib.Key{
Namespace: namespace,
Name: opName,
},
})
if test.wantError != "" {
require.EqualError(t, err, test.wantError)
return
}
require.NoError(t, err)
if test.wantOPActions == nil {
test.wantOPActions = []kubetesting.Action{}
}
require.Equal(t, test.wantOPActions, pinnipedAPIClient.Actions())
if test.wantSecretActions == nil {
test.wantSecretActions = []kubetesting.Action{}
}
require.Equal(t, test.wantSecretActions, kubeAPIClient.Actions())
})
}
}
func boolPtr(b bool) *bool { return &b }
@@ -0,0 +1,151 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package generator
import (
"fmt"
"io"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
"go.pinniped.dev/internal/plog"
)
// SecretHelper describes an object that can Generate() a Secret and determine whether a Secret
// IsValid(). It can also be Notify()'d about a Secret being persisted.
//
// A SecretHelper has a NamePrefix() that can be used to identify it from other SecretHelper instances.
type SecretHelper interface {
NamePrefix() string
Generate(*configv1alpha1.OIDCProvider) (*corev1.Secret, error)
IsValid(*configv1alpha1.OIDCProvider, *corev1.Secret) bool
ObserveActiveSecretAndUpdateParentOIDCProvider(*configv1alpha1.OIDCProvider, *corev1.Secret) *configv1alpha1.OIDCProvider
}
const (
// symmetricSecretType is corev1.Secret.Type of all corev1.Secret's generated by this helper.
symmetricSecretType = "secrets.pinniped.dev/symmetric"
// symmetricSecretDataKey is the corev1.Secret.Data key for the symmetric key value generated by this helper.
symmetricSecretDataKey = "key"
// symmetricKeySize is the default length, in bytes, of generated keys. It is set to 32 since this
// seems like reasonable entropy for our keys, and a 32-byte key will allow for AES-256
// to be used in our codecs (see dynamiccodec.Codec).
symmetricKeySize = 32
)
// SecretUsage describes how a cryptographic secret is going to be used. It is currently used to
// indicate to a SecretHelper which status field to set on the parent OIDCProvider for a Secret.
type SecretUsage int
const (
SecretUsageTokenSigningKey SecretUsage = iota
SecretUsageStateSigningKey
SecretUsageStateEncryptionKey
)
// New returns a SecretHelper that has been parameterized with common symmetric secret generation
// knobs.
func NewSymmetricSecretHelper(
namePrefix string,
labels map[string]string,
rand io.Reader,
secretUsage SecretUsage,
updateCacheFunc func(cacheKey string, cacheValue []byte),
) SecretHelper {
return &symmetricSecretHelper{
namePrefix: namePrefix,
labels: labels,
rand: rand,
secretUsage: secretUsage,
updateCacheFunc: updateCacheFunc,
}
}
type symmetricSecretHelper struct {
namePrefix string
labels map[string]string
rand io.Reader
secretUsage SecretUsage
updateCacheFunc func(cacheKey string, cacheValue []byte)
}
func (s *symmetricSecretHelper) NamePrefix() string { return s.namePrefix }
// Generate implements SecretHelper.Generate().
func (s *symmetricSecretHelper) Generate(parent *configv1alpha1.OIDCProvider) (*corev1.Secret, error) {
key := make([]byte, symmetricKeySize)
if _, err := s.rand.Read(key); err != nil {
return nil, err
}
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s%s", s.namePrefix, parent.UID),
Namespace: parent.Namespace,
Labels: s.labels,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(parent, schema.GroupVersionKind{
Group: configv1alpha1.SchemeGroupVersion.Group,
Version: configv1alpha1.SchemeGroupVersion.Version,
Kind: "OIDCProvider",
}),
},
},
Type: symmetricSecretType,
Data: map[string][]byte{
symmetricSecretDataKey: key,
},
}, nil
}
// IsValid implements SecretHelper.IsValid().
func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.OIDCProvider, secret *corev1.Secret) bool {
if !metav1.IsControlledBy(secret, parent) {
return false
}
if secret.Type != symmetricSecretType {
return false
}
key, ok := secret.Data[symmetricSecretDataKey]
if !ok {
return false
}
if len(key) != symmetricKeySize {
return false
}
return true
}
// ObserveActiveSecretAndUpdateParentOIDCProvider implements SecretHelper.ObserveActiveSecretAndUpdateParentOIDCProvider().
func (s *symmetricSecretHelper) ObserveActiveSecretAndUpdateParentOIDCProvider(
op *configv1alpha1.OIDCProvider,
secret *corev1.Secret,
) *configv1alpha1.OIDCProvider {
var cacheKey string
if op != nil {
cacheKey = op.Spec.Issuer
}
s.updateCacheFunc(cacheKey, secret.Data[symmetricSecretDataKey])
switch s.secretUsage {
case SecretUsageTokenSigningKey:
op.Status.Secrets.TokenSigningKey.Name = secret.Name
case SecretUsageStateSigningKey:
op.Status.Secrets.StateSigningKey.Name = secret.Name
case SecretUsageStateEncryptionKey:
op.Status.Secrets.StateEncryptionKey.Name = secret.Name
default:
plog.Warning("unknown secret usage enum value: %d", s.secretUsage)
}
return op
}
@@ -0,0 +1,197 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package generator
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
)
const keyWith32Bytes = "0123456789abcdef0123456789abcdef"
func TestSymmetricSecretHelper(t *testing.T) {
t.Parallel()
tests := []struct {
name string
secretUsage SecretUsage
wantSetOIDCProviderField func(*configv1alpha1.OIDCProvider) string
}{
{
name: "token signing key",
secretUsage: SecretUsageTokenSigningKey,
wantSetOIDCProviderField: func(op *configv1alpha1.OIDCProvider) string {
return op.Status.Secrets.TokenSigningKey.Name
},
},
{
name: "state signing key",
secretUsage: SecretUsageStateSigningKey,
wantSetOIDCProviderField: func(op *configv1alpha1.OIDCProvider) string {
return op.Status.Secrets.StateSigningKey.Name
},
},
{
name: "state encryption key",
secretUsage: SecretUsageStateEncryptionKey,
wantSetOIDCProviderField: func(op *configv1alpha1.OIDCProvider) string {
return op.Status.Secrets.StateEncryptionKey.Name
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
labels := map[string]string{
"some-label-key-1": "some-label-value-1",
"some-label-key-2": "some-label-value-2",
}
randSource := strings.NewReader(keyWith32Bytes)
var oidcProviderIssuerValue string
var symmetricKeyValue []byte
h := NewSymmetricSecretHelper(
"some-name-prefix-",
labels,
randSource,
test.secretUsage,
func(oidcProviderIssuer string, symmetricKey []byte) {
require.True(t, oidcProviderIssuer == "" && symmetricKeyValue == nil, "expected notify func not to have been called yet")
oidcProviderIssuerValue = oidcProviderIssuer
symmetricKeyValue = symmetricKey
},
)
parent := &configv1alpha1.OIDCProvider{
ObjectMeta: metav1.ObjectMeta{
UID: "some-uid",
Namespace: "some-namespace",
},
}
child, err := h.Generate(parent)
require.NoError(t, err)
require.Equal(t, child, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "some-name-prefix-some-uid",
Namespace: "some-namespace",
Labels: labels,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(parent, schema.GroupVersionKind{
Group: configv1alpha1.SchemeGroupVersion.Group,
Version: configv1alpha1.SchemeGroupVersion.Version,
Kind: "OIDCProvider",
}),
},
},
Type: "secrets.pinniped.dev/symmetric",
Data: map[string][]byte{
"key": []byte(keyWith32Bytes),
},
})
require.True(t, h.IsValid(parent, child))
h.ObserveActiveSecretAndUpdateParentOIDCProvider(parent, child)
require.Equal(t, parent.Spec.Issuer, oidcProviderIssuerValue)
require.Equal(t, child.Name, test.wantSetOIDCProviderField(parent))
require.Equal(t, child.Data["key"], symmetricKeyValue)
})
}
}
func TestSymmetricSecretHelperIsValid(t *testing.T) {
tests := []struct {
name string
child func(*corev1.Secret)
parent func(*configv1alpha1.OIDCProvider)
want bool
}{
{
name: "wrong type",
child: func(s *corev1.Secret) {
s.Type = "wrong"
},
want: false,
},
{
name: "empty type",
child: func(s *corev1.Secret) {
s.Type = ""
},
want: false,
},
{
name: "data key is too short",
child: func(s *corev1.Secret) {
s.Data["key"] = []byte("short")
},
want: false,
},
{
name: "data key does not exist",
child: func(s *corev1.Secret) {
delete(s.Data, "key")
},
want: false,
},
{
name: "child not owned by parent",
parent: func(op *configv1alpha1.OIDCProvider) {
op.UID = "wrong"
},
want: false,
},
{
name: "happy path",
want: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
h := NewSymmetricSecretHelper("none of these args matter", nil, nil, SecretUsageTokenSigningKey, nil)
parent := &configv1alpha1.OIDCProvider{
ObjectMeta: metav1.ObjectMeta{
Name: "some-parent-name",
Namespace: "some-namespace",
UID: "some-parent-uid",
},
}
child := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "some-name-prefix-some-uid",
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(parent, schema.GroupVersionKind{
Group: configv1alpha1.SchemeGroupVersion.Group,
Version: configv1alpha1.SchemeGroupVersion.Version,
Kind: "OIDCProvider",
}),
},
},
Type: "secrets.pinniped.dev/symmetric",
Data: map[string][]byte{
"key": []byte(keyWith32Bytes),
},
}
if test.child != nil {
test.child(child)
}
if test.parent != nil {
test.parent(parent)
}
require.Equalf(t, test.want, h.IsValid(parent, child), "child: %#v", child)
})
}
}
@@ -0,0 +1,145 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package secretgenerator provides a supervisorSecretsController that can ensure existence of a generated secret.
package generator
import (
"context"
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/plog"
)
// generateKey is stubbed out for the purpose of testing. The default behavior is to generate a symmetric key.
//nolint:gochecknoglobals
var generateKey = generateSymmetricKey
type supervisorSecretsController struct {
owner *appsv1.Deployment
labels map[string]string
kubeClient kubernetes.Interface
secretInformer corev1informers.SecretInformer
setCacheFunc func(secret []byte)
}
// NewSupervisorSecretsController instantiates a new controllerlib.Controller which will ensure existence of a generated secret.
func NewSupervisorSecretsController(
owner *appsv1.Deployment,
labels map[string]string,
kubeClient kubernetes.Interface,
secretInformer corev1informers.SecretInformer,
setCacheFunc func(secret []byte),
withInformer pinnipedcontroller.WithInformerOptionFunc,
initialEventFunc pinnipedcontroller.WithInitialEventOptionFunc,
) controllerlib.Controller {
c := supervisorSecretsController{
owner: owner,
labels: labels,
kubeClient: kubeClient,
secretInformer: secretInformer,
setCacheFunc: setCacheFunc,
}
return controllerlib.New(
controllerlib.Config{Name: owner.Name + "-secret-generator", Syncer: &c},
withInformer(
secretInformer,
pinnipedcontroller.SimpleFilter(func(obj metav1.Object) bool {
ownerReferences := obj.GetOwnerReferences()
for i := range obj.GetOwnerReferences() {
if ownerReferences[i].UID == owner.GetUID() {
return true
}
}
return false
}, nil),
controllerlib.InformerOption{},
),
initialEventFunc(controllerlib.Key{
Namespace: owner.Namespace,
Name: owner.Name + "-key",
}),
)
}
// Sync implements controllerlib.Syncer.Sync().
func (c *supervisorSecretsController) Sync(ctx controllerlib.Context) error {
secret, err := c.secretInformer.Lister().Secrets(ctx.Key.Namespace).Get(ctx.Key.Name)
isNotFound := k8serrors.IsNotFound(err)
if !isNotFound && err != nil {
return fmt.Errorf("failed to list secret %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err)
}
secretNeedsUpdate := isNotFound || !isValid(secret, c.labels)
if !secretNeedsUpdate {
plog.Debug("secret is up to date", "secret", klog.KObj(secret))
c.setCacheFunc(secret.Data[symmetricSecretDataKey])
return nil
}
newSecret, err := generateSecret(ctx.Key.Namespace, ctx.Key.Name, c.labels, secretDataFunc, c.owner)
if err != nil {
return fmt.Errorf("failed to generate secret: %w", err)
}
if isNotFound {
err = c.createSecret(ctx.Context, newSecret)
} else {
err = c.updateSecret(ctx.Context, &newSecret, ctx.Key.Name)
}
if err != nil {
return fmt.Errorf("failed to create/update secret %s/%s: %w", newSecret.Namespace, newSecret.Name, err)
}
c.setCacheFunc(newSecret.Data[symmetricSecretDataKey])
return nil
}
func (c *supervisorSecretsController) createSecret(ctx context.Context, newSecret *corev1.Secret) error {
_, err := c.kubeClient.CoreV1().Secrets(newSecret.Namespace).Create(ctx, newSecret, metav1.CreateOptions{})
return err
}
func (c *supervisorSecretsController) updateSecret(ctx context.Context, newSecret **corev1.Secret, secretName string) error {
secrets := c.kubeClient.CoreV1().Secrets((*newSecret).Namespace)
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
currentSecret, err := secrets.Get(ctx, secretName, metav1.GetOptions{})
isNotFound := k8serrors.IsNotFound(err)
if !isNotFound && err != nil {
return fmt.Errorf("failed to get secret: %w", err)
}
if isNotFound {
if err := c.createSecret(ctx, *newSecret); err != nil {
return fmt.Errorf("failed to create secret: %w", err)
}
return nil
}
if isValid(currentSecret, c.labels) {
*newSecret = currentSecret
return nil
}
currentSecret.Type = (*newSecret).Type
currentSecret.Data = (*newSecret).Data
for key, value := range c.labels {
currentSecret.Labels[key] = value
}
_, err = secrets.Update(ctx, currentSecret, metav1.UpdateOptions{})
return err
})
}
@@ -0,0 +1,578 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package generator
import (
"context"
"errors"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
kubeinformers "k8s.io/client-go/informers"
kubernetesfake "k8s.io/client-go/kubernetes/fake"
kubetesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/testutil"
)
var (
owner = &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "some-owner-name",
Namespace: "some-namespace",
UID: "some-owner-uid",
},
}
ownerGVK = schema.GroupVersionKind{
Group: appsv1.SchemeGroupVersion.Group,
Version: appsv1.SchemeGroupVersion.Version,
Kind: "Deployment",
}
labels = map[string]string{
"some-label-key-1": "some-label-value-1",
"some-label-key-2": "some-label-value-2",
}
)
func TestSupervisorSecretsControllerFilterSecret(t *testing.T) {
t.Parallel()
tests := []struct {
name string
secret corev1.Secret
wantAdd bool
wantUpdate bool
wantDelete bool
}{
{
name: "owner reference is missing",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
},
},
},
{
name: "owner reference with incorrect `APIVersion`",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
Name: owner.GetName(),
Kind: ownerGVK.Kind,
UID: owner.GetUID(),
},
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
{
name: "owner reference with incorrect `Kind`",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: ownerGVK.String(),
Name: owner.GetName(),
Kind: "IncorrectKind",
UID: owner.GetUID(),
},
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
{
name: "owner reference with `Controller`: true",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(owner, ownerGVK),
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
{
name: "expected owner reference with incorrect `UID`",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: ownerGVK.String(),
Name: owner.GetName(),
Kind: ownerGVK.Kind,
UID: "DOES_NOT_MATCH",
},
},
},
},
},
{
name: "expected owner reference - where `Controller`: false",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: ownerGVK.String(),
Name: owner.GetName(),
Kind: ownerGVK.Kind,
UID: owner.GetUID(),
},
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
{
name: "multiple owner references (expected owner reference, and one more)",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
Kind: "UnrelatedKind",
},
{
APIVersion: ownerGVK.String(),
Name: owner.GetName(),
Kind: ownerGVK.Kind,
UID: owner.GetUID(),
},
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
secretInformer := kubeinformers.NewSharedInformerFactory(
kubernetesfake.NewSimpleClientset(),
0,
).Core().V1().Secrets()
withInformer := testutil.NewObservableWithInformerOption()
_ = NewSupervisorSecretsController(
owner,
labels,
nil, // kubeClient, not needed
secretInformer,
nil, // setCache, not needed
withInformer.WithInformer,
testutil.NewObservableWithInitialEventOption().WithInitialEvent,
)
unrelated := corev1.Secret{}
filter := withInformer.GetFilterForInformer(secretInformer)
require.Equal(t, test.wantAdd, filter.Add(&test.secret))
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.secret))
require.Equal(t, test.wantUpdate, filter.Update(&test.secret, &unrelated))
require.Equal(t, test.wantDelete, filter.Delete(&test.secret))
})
}
}
func TestSupervisorSecretsControllerInitialEvent(t *testing.T) {
initialEventOption := testutil.NewObservableWithInitialEventOption()
secretInformer := kubeinformers.NewSharedInformerFactory(
kubernetesfake.NewSimpleClientset(),
0,
).Core().V1().Secrets()
_ = NewSupervisorSecretsController(
owner,
nil,
nil, // kubeClient, not needed
secretInformer,
nil, // setCache, not needed
testutil.NewObservableWithInformerOption().WithInformer,
initialEventOption.WithInitialEvent,
)
require.Equal(t, &controllerlib.Key{
Namespace: owner.Namespace,
Name: owner.Name + "-key",
}, initialEventOption.GetInitialEventKey())
}
func TestSupervisorSecretsControllerSync(t *testing.T) {
const (
generatedSecretNamespace = "some-namespace"
generatedSecretName = "some-name-abc123"
)
var (
secretsGVR = schema.GroupVersionResource{
Group: corev1.SchemeGroupVersion.Group,
Version: corev1.SchemeGroupVersion.Version,
Resource: "secrets",
}
generatedSymmetricKey = []byte("some-neato-32-byte-generated-key")
otherGeneratedSymmetricKey = []byte("some-funio-32-byte-generated-key")
blockOwnerDeletion = true
isController = false
generatedSecret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: generatedSecretName,
Namespace: generatedSecretNamespace,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: ownerGVK.GroupVersion().String(),
Kind: ownerGVK.Kind,
Name: owner.GetName(),
UID: owner.GetUID(),
BlockOwnerDeletion: &blockOwnerDeletion,
Controller: &isController,
},
},
Labels: labels,
},
Type: "secrets.pinniped.dev/symmetric",
Data: map[string][]byte{
"key": generatedSymmetricKey,
},
}
otherGeneratedSecret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: generatedSecretName,
Namespace: generatedSecretNamespace,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: ownerGVK.GroupVersion().String(),
Kind: ownerGVK.Kind,
Name: owner.GetName(),
UID: owner.GetUID(),
BlockOwnerDeletion: &blockOwnerDeletion,
Controller: &isController,
},
},
Labels: labels,
},
Type: "secrets.pinniped.dev/symmetric",
Data: map[string][]byte{
"key": otherGeneratedSymmetricKey,
},
}
)
// Add an extra label to make sure we don't overwrite existing labels on a Secret.
generatedSecret.Labels["extra-label-key"] = "extra-label-value"
once := sync.Once{}
tests := []struct {
name string
storedSecret func(**corev1.Secret)
generateKey func() ([]byte, error)
apiClient func(*testing.T, *kubernetesfake.Clientset)
wantError string
wantActions []kubetesting.Action
wantCallbackSecret []byte
}{
{
name: "when the secrets does not exist, it gets generated",
storedSecret: func(secret **corev1.Secret) {
*secret = nil
},
wantActions: []kubetesting.Action{
kubetesting.NewCreateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "when a valid secret exists, nothing happens",
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "secret gets updated when the type is wrong",
storedSecret: func(secret **corev1.Secret) {
(*secret).Type = "wrong"
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewUpdateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "secret gets updated when the key data does not exist",
storedSecret: func(secret **corev1.Secret) {
delete((*secret).Data, "key")
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewUpdateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "secret gets updated when the key data is too short",
storedSecret: func(secret **corev1.Secret) {
(*secret).Data["key"] = []byte("too short")
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewUpdateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "an error is returned when creating fails",
storedSecret: func(secret **corev1.Secret) {
*secret = nil
},
apiClient: func(t *testing.T, client *kubernetesfake.Clientset) {
client.PrependReactor("create", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some create error")
})
},
wantActions: []kubetesting.Action{
kubetesting.NewCreateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantError: "failed to create/update secret some-namespace/some-name-abc123: some create error",
},
{
name: "an error is returned when updating fails",
storedSecret: func(secret **corev1.Secret) {
(*secret).Data["key"] = []byte("too short") // force updating
},
apiClient: func(t *testing.T, client *kubernetesfake.Clientset) {
client.PrependReactor("update", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some update error")
})
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewUpdateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantError: "failed to create/update secret some-namespace/some-name-abc123: some update error",
},
{
name: "an error is returned when getting fails",
storedSecret: func(secret **corev1.Secret) {
(*secret).Data["key"] = []byte("too short") // force updating
},
apiClient: func(t *testing.T, client *kubernetesfake.Clientset) {
client.PrependReactor("get", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some get error")
})
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
},
wantError: "failed to create/update secret some-namespace/some-name-abc123: failed to get secret: some get error",
},
{
name: "the update is retried when it fails due to a conflict",
storedSecret: func(secret **corev1.Secret) {
(*secret).Data["key"] = []byte("too short") // force updating
},
apiClient: func(t *testing.T, client *kubernetesfake.Clientset) {
client.PrependReactor("update", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
var err error
once.Do(func() {
err = k8serrors.NewConflict(secretsGVR.GroupResource(), generatedSecretName, errors.New("some error"))
})
return true, nil, err
})
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewUpdateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewUpdateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "upon updating we discover that a valid secret exists",
storedSecret: func(secret **corev1.Secret) {
(*secret).Data["key"] = []byte("too short") // force updating
},
apiClient: func(t *testing.T, client *kubernetesfake.Clientset) {
client.PrependReactor("get", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
return true, otherGeneratedSecret, nil
})
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
},
wantCallbackSecret: otherGeneratedSymmetricKey,
},
{
name: "upon updating we discover that a secret with missing labels exists",
storedSecret: func(secret **corev1.Secret) {
delete((*secret).Labels, "some-label-key-1")
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewUpdateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "upon updating we discover that a secret with incorrect labels exists",
storedSecret: func(secret **corev1.Secret) {
(*secret).Labels["some-label-key-1"] = "incorrect"
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewUpdateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "upon updating we discover that the secret has been deleted",
storedSecret: func(secret **corev1.Secret) {
(*secret).Data["key"] = []byte("too short") // force updating
},
apiClient: func(t *testing.T, client *kubernetesfake.Clientset) {
client.PrependReactor("get", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, k8serrors.NewNotFound(secretsGVR.GroupResource(), generatedSecretName)
})
client.PrependReactor("create", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, nil
})
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewCreateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantCallbackSecret: generatedSymmetricKey,
},
{
name: "upon updating we discover that the secret has been deleted and our create fails",
storedSecret: func(secret **corev1.Secret) {
(*secret).Data["key"] = []byte("too short") // force updating
},
apiClient: func(t *testing.T, client *kubernetesfake.Clientset) {
client.PrependReactor("get", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, k8serrors.NewNotFound(secretsGVR.GroupResource(), generatedSecretName)
})
client.PrependReactor("create", "secrets", func(action kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some create error")
})
},
wantActions: []kubetesting.Action{
kubetesting.NewGetAction(secretsGVR, generatedSecretNamespace, generatedSecretName),
kubetesting.NewCreateAction(secretsGVR, generatedSecretNamespace, generatedSecret),
},
wantError: "failed to create/update secret some-namespace/some-name-abc123: failed to create secret: some create error",
},
{
name: "when generating the secret fails, we return an error",
storedSecret: func(secret **corev1.Secret) {
*secret = nil
},
generateKey: func() ([]byte, error) {
return nil, errors.New("some generate error")
},
wantError: "failed to generate secret: some generate error",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
// We cannot currently run this test in parallel since it uses the global generateKey function.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
if test.generateKey != nil {
generateKey = test.generateKey
} else {
generateKey = func() ([]byte, error) {
return generatedSymmetricKey, nil
}
}
apiClient := kubernetesfake.NewSimpleClientset()
if test.apiClient != nil {
test.apiClient(t, apiClient)
}
informerClient := kubernetesfake.NewSimpleClientset()
storedSecret := generatedSecret.DeepCopy()
if test.storedSecret != nil {
test.storedSecret(&storedSecret)
}
if storedSecret != nil {
require.NoError(t, apiClient.Tracker().Add(storedSecret))
require.NoError(t, informerClient.Tracker().Add(storedSecret))
}
informers := kubeinformers.NewSharedInformerFactory(informerClient, 0)
secrets := informers.Core().V1().Secrets()
var callbackSecret []byte
c := NewSupervisorSecretsController(
owner,
labels,
apiClient,
secrets,
func(secret []byte) {
require.Nil(t, callbackSecret, "callback was called twice")
callbackSecret = secret
},
testutil.NewObservableWithInformerOption().WithInformer,
testutil.NewObservableWithInitialEventOption().WithInitialEvent,
)
// Must start informers before calling TestRunSynchronously().
informers.Start(ctx.Done())
controllerlib.TestRunSynchronously(t, c)
err := controllerlib.TestSync(t, c, controllerlib.Context{
Context: ctx,
Key: controllerlib.Key{
Namespace: generatedSecretNamespace,
Name: generatedSecretName,
},
})
if test.wantError != "" {
require.EqualError(t, err, test.wantError)
} else {
require.NoError(t, err)
}
if test.wantActions == nil {
test.wantActions = []kubetesting.Action{}
}
require.Equal(t, test.wantActions, apiClient.Actions())
require.Equal(t, test.wantCallbackSecret, callbackSecret)
})
}
}
@@ -76,7 +76,7 @@ func (c *jwksObserverController) Sync(ctx controllerlib.Context) error {
issuerToActiveJWKMap := map[string]*jose.JSONWebKey{}
for _, provider := range allProviders {
secretRef := provider.Status.JWKSSecret
secretRef := provider.Status.Secrets.JWKS
jwksSecret, err := c.secretInformer.Lister().Secrets(ns).Get(secretRef.Name)
if err != nil {
plog.Debug("jwksObserverController Sync could not find JWKS secret", "namespace", ns, "secretName", secretRef.Name)
@@ -202,7 +202,7 @@ func TestJWKSObserverControllerSync(t *testing.T) {
Namespace: installedInNamespace,
},
Spec: v1alpha1.OIDCProviderSpec{Issuer: "https://no-secret-issuer1.com"},
Status: v1alpha1.OIDCProviderStatus{}, // no JWKSSecret field
Status: v1alpha1.OIDCProviderStatus{}, // no Secrets.JWKS field
}
oidcProviderWithoutSecret2 := &v1alpha1.OIDCProvider{
ObjectMeta: metav1.ObjectMeta{
@@ -219,7 +219,9 @@ func TestJWKSObserverControllerSync(t *testing.T) {
},
Spec: v1alpha1.OIDCProviderSpec{Issuer: "https://bad-secret-issuer.com"},
Status: v1alpha1.OIDCProviderStatus{
JWKSSecret: corev1.LocalObjectReference{Name: "bad-secret-name"},
Secrets: v1alpha1.OIDCProviderSecrets{
JWKS: corev1.LocalObjectReference{Name: "bad-secret-name"},
},
},
}
oidcProviderWithBadJWKSSecret := &v1alpha1.OIDCProvider{
@@ -229,7 +231,9 @@ func TestJWKSObserverControllerSync(t *testing.T) {
},
Spec: v1alpha1.OIDCProviderSpec{Issuer: "https://bad-jwks-secret-issuer.com"},
Status: v1alpha1.OIDCProviderStatus{
JWKSSecret: corev1.LocalObjectReference{Name: "bad-jwks-secret-name"},
Secrets: v1alpha1.OIDCProviderSecrets{
JWKS: corev1.LocalObjectReference{Name: "bad-jwks-secret-name"},
},
},
}
oidcProviderWithBadActiveJWKSecret := &v1alpha1.OIDCProvider{
@@ -239,7 +243,9 @@ func TestJWKSObserverControllerSync(t *testing.T) {
},
Spec: v1alpha1.OIDCProviderSpec{Issuer: "https://bad-active-jwk-secret-issuer.com"},
Status: v1alpha1.OIDCProviderStatus{
JWKSSecret: corev1.LocalObjectReference{Name: "bad-active-jwk-secret-name"},
Secrets: v1alpha1.OIDCProviderSecrets{
JWKS: corev1.LocalObjectReference{Name: "bad-active-jwk-secret-name"},
},
},
}
oidcProviderWithGoodSecret1 := &v1alpha1.OIDCProvider{
@@ -249,7 +255,9 @@ func TestJWKSObserverControllerSync(t *testing.T) {
},
Spec: v1alpha1.OIDCProviderSpec{Issuer: "https://issuer-with-good-secret1.com"},
Status: v1alpha1.OIDCProviderStatus{
JWKSSecret: corev1.LocalObjectReference{Name: "good-jwks-secret-name1"},
Secrets: v1alpha1.OIDCProviderSecrets{
JWKS: corev1.LocalObjectReference{Name: "good-jwks-secret-name1"},
},
},
}
oidcProviderWithGoodSecret2 := &v1alpha1.OIDCProvider{
@@ -259,7 +267,9 @@ func TestJWKSObserverControllerSync(t *testing.T) {
},
Spec: v1alpha1.OIDCProviderSpec{Issuer: "https://issuer-with-good-secret2.com"},
Status: v1alpha1.OIDCProviderStatus{
JWKSSecret: corev1.LocalObjectReference{Name: "good-jwks-secret-name2"},
Secrets: v1alpha1.OIDCProviderSecrets{
JWKS: corev1.LocalObjectReference{Name: "good-jwks-secret-name2"},
},
},
}
expectedJWK1 = string(readJWKJSON(t, "testdata/public-jwk.json"))
@@ -169,7 +169,7 @@ func (c *jwksWriterController) Sync(ctx controllerlib.Context) error {
// Ensure that the OPC points to the secret.
newOPC := opc.DeepCopy()
newOPC.Status.JWKSSecret.Name = secret.Name
newOPC.Status.Secrets.JWKS.Name = secret.Name
if err := c.updateOPC(ctx.Context, newOPC); err != nil {
return fmt.Errorf("cannot update opc: %w", err)
}
@@ -179,13 +179,13 @@ func (c *jwksWriterController) Sync(ctx controllerlib.Context) error {
}
func (c *jwksWriterController) secretNeedsUpdate(opc *configv1alpha1.OIDCProvider) (bool, error) {
if opc.Status.JWKSSecret.Name == "" {
if opc.Status.Secrets.JWKS.Name == "" {
// If the OPC says it doesn't have a secret associated with it, then let's create one.
return true, nil
}
// This OPC says it has a secret associated with it. Let's try to get it from the cache.
secret, err := c.secretInformer.Lister().Secrets(opc.Namespace).Get(opc.Status.JWKSSecret.Name)
secret, err := c.secretInformer.Lister().Secrets(opc.Namespace).Get(opc.Status.Secrets.JWKS.Name)
notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound {
return false, fmt.Errorf("cannot get secret: %w", err)
@@ -301,12 +301,12 @@ func (c *jwksWriterController) updateOPC(
return fmt.Errorf("cannot get opc: %w", err)
}
if newOPC.Status.JWKSSecret.Name == oldOPC.Status.JWKSSecret.Name {
if newOPC.Status.Secrets.JWKS.Name == oldOPC.Status.Secrets.JWKS.Name {
// If the existing OPC is up to date, we don't need to update it.
return nil
}
oldOPC.Status.JWKSSecret.Name = newOPC.Status.JWKSSecret.Name
oldOPC.Status.Secrets.JWKS.Name = newOPC.Status.Secrets.JWKS.Name
_, err = opcClient.Update(ctx, oldOPC, metav1.UpdateOptions{})
return err
})
@@ -253,7 +253,7 @@ func TestJWKSWriterControllerSync(t *testing.T) {
},
}
goodOPCWithStatus := goodOPC.DeepCopy()
goodOPCWithStatus.Status.JWKSSecret.Name = goodOPCWithStatus.Name + "-jwks"
goodOPCWithStatus.Status.Secrets.JWKS.Name = goodOPCWithStatus.Name + "-jwks"
secretGVR := schema.GroupVersionResource{
Group: corev1.SchemeGroupVersion.Group,
@@ -264,7 +264,7 @@ func TestJWKSWriterControllerSync(t *testing.T) {
newSecret := func(activeJWKPath, jwksPath string) *corev1.Secret {
s := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: goodOPCWithStatus.Status.JWKSSecret.Name,
Name: goodOPCWithStatus.Status.Secrets.JWKS.Name,
Namespace: namespace,
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",