Secrets: Add single tenant SecureValueClient (#108099)
* Secrets: Add single tenant SecureValueClient * SecureValueClient: Rename file * SecureValueClient: Move original type to contracts package and export it by aliasing
This commit is contained in:
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"k8s.io/client-go/dynamic"
|
||||
|
||||
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
)
|
||||
@@ -45,3 +47,7 @@ type SecureValueService interface {
|
||||
Update(ctx context.Context, newSecureValue *secretv1beta1.SecureValue, actorUID string) (*secretv1beta1.SecureValue, bool, error)
|
||||
Delete(ctx context.Context, namespace xkube.Namespace, name string) (*secretv1beta1.SecureValue, error)
|
||||
}
|
||||
|
||||
type SecureValueClient interface {
|
||||
Client(ctx context.Context, namespace string) (dynamic.ResourceInterface, error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/dynamic"
|
||||
|
||||
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
)
|
||||
|
||||
// SecureValueClient is a CRUD client for the secure value API.
|
||||
type SecureValueClient = contracts.SecureValueClient
|
||||
|
||||
type secureValueClient struct {
|
||||
namespace string
|
||||
service contracts.SecureValueService
|
||||
validator contracts.SecureValueValidator
|
||||
}
|
||||
|
||||
var _ SecureValueClient = &secureValueClient{}
|
||||
|
||||
func ProvideSecureValueClient(service contracts.SecureValueService, validator contracts.SecureValueValidator) SecureValueClient {
|
||||
return &secureValueClient{
|
||||
service: service,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
// Client returns a resource interface that is scoped to a specific namespace.
|
||||
func (c *secureValueClient) Client(ctx context.Context, namespace string) (dynamic.ResourceInterface, error) {
|
||||
return c.Namespace(namespace), nil
|
||||
}
|
||||
|
||||
// Namespace returns a resource interface that is scoped to a specific namespace.
|
||||
func (c *secureValueClient) Namespace(ns string) dynamic.ResourceInterface {
|
||||
info, err := claims.ParseNamespace(ns)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(info.Value) == 0 {
|
||||
panic("namespace is required")
|
||||
}
|
||||
|
||||
ret := *c
|
||||
ret.namespace = ns
|
||||
return &ret
|
||||
}
|
||||
|
||||
// Create a new secure value. Options and subresources are not supported and ignored.
|
||||
func (c *secureValueClient) Create(ctx context.Context, obj *unstructured.Unstructured, _ metav1.CreateOptions, _ ...string) (*unstructured.Unstructured, error) {
|
||||
if len(c.namespace) == 0 {
|
||||
return nil, fmt.Errorf("namespace is required")
|
||||
}
|
||||
|
||||
sv, err := fromUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sv.Namespace != c.namespace {
|
||||
return nil, fmt.Errorf("namespace mismatch")
|
||||
}
|
||||
if errs := c.validator.Validate(sv, nil, admission.Create); len(errs) > 0 {
|
||||
return nil, fmt.Errorf("invalid secure value: %w", errs.ToAggregate())
|
||||
}
|
||||
|
||||
user, ok := claims.AuthInfoFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing auth info in context")
|
||||
}
|
||||
|
||||
createdSv, err := c.service.Create(ctx, sv, user.GetUID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUnstructured(createdSv)
|
||||
}
|
||||
|
||||
// Get a secure value by name. Options and subresources are not supported and ignored.
|
||||
func (c *secureValueClient) Get(ctx context.Context, name string, _ metav1.GetOptions, _ ...string) (*unstructured.Unstructured, error) {
|
||||
if len(c.namespace) == 0 {
|
||||
return nil, fmt.Errorf("namespace is required")
|
||||
}
|
||||
if len(name) == 0 {
|
||||
return nil, fmt.Errorf("name is required")
|
||||
}
|
||||
|
||||
sv, err := c.service.Read(ctx, xkube.Namespace(c.namespace), name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUnstructured(sv)
|
||||
}
|
||||
|
||||
// Update a secure value. Options and subresources are not supported and ignored.
|
||||
func (c *secureValueClient) Update(ctx context.Context, obj *unstructured.Unstructured, _ metav1.UpdateOptions, _ ...string) (*unstructured.Unstructured, error) {
|
||||
if len(c.namespace) == 0 {
|
||||
return nil, fmt.Errorf("namespace is required")
|
||||
}
|
||||
|
||||
oldUnstructured, err := c.Get(ctx, obj.GetName(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldSv, err := fromUnstructured(oldUnstructured)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv, err := fromUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sv.Namespace != c.namespace {
|
||||
return nil, fmt.Errorf("namespace mismatch")
|
||||
}
|
||||
if errs := c.validator.Validate(sv, oldSv, admission.Update); len(errs) > 0 {
|
||||
return nil, fmt.Errorf("invalid secure value: %w", errs.ToAggregate())
|
||||
}
|
||||
|
||||
user, ok := claims.AuthInfoFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing auth info in context")
|
||||
}
|
||||
|
||||
updatedSv, _, err := c.service.Update(ctx, sv, user.GetUID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUnstructured(updatedSv)
|
||||
}
|
||||
|
||||
// Delete a secure value by name. Options and subresources are not supported and ignored.
|
||||
func (c *secureValueClient) Delete(ctx context.Context, name string, _ metav1.DeleteOptions, _ ...string) error {
|
||||
if len(c.namespace) == 0 {
|
||||
return fmt.Errorf("namespace is required")
|
||||
}
|
||||
if len(name) == 0 {
|
||||
return fmt.Errorf("name is required")
|
||||
}
|
||||
|
||||
_, err := c.service.Delete(ctx, xkube.Namespace(c.namespace), name)
|
||||
return err
|
||||
}
|
||||
|
||||
// List all secure values in the namespace. Options and subresources are not supported and ignored.
|
||||
func (c *secureValueClient) List(ctx context.Context, _ metav1.ListOptions) (*unstructured.UnstructuredList, error) {
|
||||
if len(c.namespace) == 0 {
|
||||
return nil, fmt.Errorf("namespace is required")
|
||||
}
|
||||
|
||||
list, err := c.service.List(ctx, xkube.Namespace(c.namespace))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]unstructured.Unstructured, 0, len(list.Items))
|
||||
for _, sv := range list.Items {
|
||||
u, err := toUnstructured(&sv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items = append(items, *u)
|
||||
}
|
||||
|
||||
return &unstructured.UnstructuredList{
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteCollection is not supported and returns an error.
|
||||
func (c *secureValueClient) DeleteCollection(_ context.Context, _ metav1.DeleteOptions, _ metav1.ListOptions) error {
|
||||
return fmt.Errorf("deleteCollection is not supported")
|
||||
}
|
||||
|
||||
// Watch is not supported and returns an error.
|
||||
func (c *secureValueClient) Watch(_ context.Context, _ metav1.ListOptions) (watch.Interface, error) {
|
||||
return nil, fmt.Errorf("watch is not supported")
|
||||
}
|
||||
|
||||
// Patch is not supported and returns an error.
|
||||
func (c *secureValueClient) Patch(_ context.Context, _ string, _ types.PatchType, _ []byte, _ metav1.PatchOptions, _ ...string) (*unstructured.Unstructured, error) {
|
||||
return nil, fmt.Errorf("patch is not supported")
|
||||
}
|
||||
|
||||
// Apply is not supported and returns an error.
|
||||
func (c *secureValueClient) Apply(_ context.Context, _ string, _ *unstructured.Unstructured, _ metav1.ApplyOptions, _ ...string) (*unstructured.Unstructured, error) {
|
||||
return nil, fmt.Errorf("apply is not supported")
|
||||
}
|
||||
|
||||
// UpdateStatus is not supported and returns an error.
|
||||
func (c *secureValueClient) UpdateStatus(_ context.Context, _ *unstructured.Unstructured, _ metav1.UpdateOptions) (*unstructured.Unstructured, error) {
|
||||
return nil, fmt.Errorf("updateStatus is not supported")
|
||||
}
|
||||
|
||||
// ApplyStatus is not supported and returns an error.
|
||||
func (c *secureValueClient) ApplyStatus(_ context.Context, _ string, _ *unstructured.Unstructured, _ metav1.ApplyOptions) (*unstructured.Unstructured, error) {
|
||||
return nil, fmt.Errorf("applyStatus is not supported")
|
||||
}
|
||||
|
||||
func toUnstructured(sv *secretv1beta1.SecureValue) (*unstructured.Unstructured, error) {
|
||||
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &unstructured.Unstructured{Object: unstructuredObj}, nil
|
||||
}
|
||||
|
||||
func fromUnstructured(u *unstructured.Unstructured) (*secretv1beta1.SecureValue, error) {
|
||||
sv := new(secretv1beta1.SecureValue)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, sv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sv, nil
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/testutils"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/validator"
|
||||
)
|
||||
|
||||
func TestIntegration_SecureValueClient_CRUD(t *testing.T) {
|
||||
setup := testutils.Setup(t)
|
||||
|
||||
validator := validator.ProvideSecureValueValidator()
|
||||
|
||||
client := ProvideSecureValueClient(
|
||||
setup.SecureValueService,
|
||||
validator,
|
||||
)
|
||||
|
||||
ns := "stacks-1234"
|
||||
ctx := testutils.CreateUserAuthContext(t.Context(), ns, map[string][]string{
|
||||
"securevalues:read": {"securevalues:uid:*"},
|
||||
})
|
||||
|
||||
nsClient, err := client.Client(ctx, ns)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, nsClient)
|
||||
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-sv",
|
||||
Namespace: ns,
|
||||
},
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "test-description",
|
||||
Value: ptr.To(secretv1beta1.NewExposedSecureValue("test-value")),
|
||||
},
|
||||
}
|
||||
|
||||
unstructured, err := toUnstructured(sv)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create
|
||||
created, err := nsClient.Create(ctx, unstructured, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdSv, err := fromUnstructured(created)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEmpty(t, createdSv.UID)
|
||||
require.Nil(t, createdSv.Spec.Value)
|
||||
require.Equal(t, sv.Name, createdSv.Name)
|
||||
require.Equal(t, sv.Namespace, createdSv.Namespace)
|
||||
|
||||
// Read
|
||||
read, err := nsClient.Get(ctx, createdSv.Name, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
readSv, err := fromUnstructured(read)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, createdSv, readSv)
|
||||
|
||||
// Update
|
||||
updatedSv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: createdSv.Name,
|
||||
Namespace: createdSv.Namespace,
|
||||
},
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "test-description-updated",
|
||||
Value: ptr.To(secretv1beta1.NewExposedSecureValue("test-value-updated")),
|
||||
},
|
||||
}
|
||||
|
||||
unstructured, err = toUnstructured(updatedSv)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = nsClient.Update(ctx, unstructured, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
read, err = nsClient.Get(ctx, createdSv.Name, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
readSv, err = fromUnstructured(read)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, updatedSv.Spec.Description, readSv.Spec.Description)
|
||||
require.Equal(t, updatedSv.Name, readSv.Name)
|
||||
require.Equal(t, updatedSv.Namespace, readSv.Namespace)
|
||||
|
||||
// List
|
||||
list, err := nsClient.List(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Items, 1)
|
||||
require.Equal(t, createdSv.Name, list.Items[0].GetName())
|
||||
require.Equal(t, createdSv.Namespace, list.Items[0].GetNamespace())
|
||||
|
||||
// Delete
|
||||
err = nsClient.Delete(ctx, createdSv.Name, metav1.DeleteOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
read, err = nsClient.Get(ctx, createdSv.Name, metav1.GetOptions{})
|
||||
require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
|
||||
require.Nil(t, read)
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func TestValidateKeeper(t *testing.T) {
|
||||
validator := ProvideKeeperValidator()
|
||||
|
||||
t.Run("when creating a new keeper", func(t *testing.T) {
|
||||
t.Run("the `description` must be present", func(t *testing.T) {
|
||||
keeper := &secretv1beta1.Keeper{
|
||||
@@ -23,7 +25,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.description", errs[0].Field)
|
||||
@@ -41,7 +42,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -54,7 +54,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -81,7 +80,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperAWS.DeepCopy()
|
||||
keeper.Spec.Aws.AccessKeyID = secretv1beta1.KeeperCredentialValue{}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.aws.accessKeyID", errs[0].Field)
|
||||
@@ -95,7 +93,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
ValueFromConfig: "c",
|
||||
}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.aws.accessKeyID", errs[0].Field)
|
||||
@@ -107,7 +104,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperAWS.DeepCopy()
|
||||
keeper.Spec.Aws.SecretAccessKey = secretv1beta1.KeeperCredentialValue{}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field)
|
||||
@@ -121,7 +117,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
ValueFromConfig: "c",
|
||||
}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field)
|
||||
@@ -148,7 +143,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.KeyVaultName = ""
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.keyVaultName", errs[0].Field)
|
||||
@@ -158,7 +152,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.TenantID = ""
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.tenantID", errs[0].Field)
|
||||
@@ -168,7 +161,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.ClientID = ""
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.clientID", errs[0].Field)
|
||||
@@ -179,7 +171,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.ClientSecret = secretv1beta1.KeeperCredentialValue{}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.clientSecret", errs[0].Field)
|
||||
@@ -193,7 +184,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
ValueFromConfig: "c",
|
||||
}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.clientSecret", errs[0].Field)
|
||||
@@ -216,7 +206,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperGCP.DeepCopy()
|
||||
keeper.Spec.Gcp.ProjectID = ""
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.gcp.projectID", errs[0].Field)
|
||||
@@ -226,7 +215,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperGCP.DeepCopy()
|
||||
keeper.Spec.Gcp.CredentialsFile = ""
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.gcp.credentialsFile", errs[0].Field)
|
||||
@@ -250,7 +238,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperHashiCorp.DeepCopy()
|
||||
keeper.Spec.HashiCorpVault.Address = ""
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.hashiCorpVault.address", errs[0].Field)
|
||||
@@ -261,7 +248,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
keeper := validKeeperHashiCorp.DeepCopy()
|
||||
keeper.Spec.HashiCorpVault.Token = secretv1beta1.KeeperCredentialValue{}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.hashiCorpVault.token", errs[0].Field)
|
||||
@@ -275,7 +261,6 @@ func TestValidateKeeper(t *testing.T) {
|
||||
ValueFromConfig: "c",
|
||||
}
|
||||
|
||||
validator := ProvideKeeperValidator()
|
||||
errs := validator.Validate(keeper, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.hashiCorpVault.token", errs[0].Field)
|
||||
|
||||
@@ -38,6 +38,13 @@ func (v *secureValueValidator) Validate(sv, oldSv *secretv1beta1.SecureValue, op
|
||||
}
|
||||
|
||||
// General validations.
|
||||
if sv.Name == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("metadata", "name"), "a `name` is required"))
|
||||
}
|
||||
if sv.Namespace == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("metadata", "namespace"), "a `namespace` is required"))
|
||||
}
|
||||
|
||||
if sv.Spec.Value != nil && len(*sv.Spec.Value) > contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES {
|
||||
errs = append(
|
||||
errs,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
@@ -14,9 +15,13 @@ import (
|
||||
)
|
||||
|
||||
func TestValidateSecureValue(t *testing.T) {
|
||||
objectMeta := metav1.ObjectMeta{Name: "test", Namespace: "test"}
|
||||
validator := ProvideSecureValueValidator()
|
||||
|
||||
t.Run("when creating a new securevalue", func(t *testing.T) {
|
||||
keeper := "keeper"
|
||||
validSecureValue := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "description",
|
||||
Value: ptr.To(secretv1beta1.NewExposedSecureValue("value")),
|
||||
@@ -29,7 +34,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
sv := validSecureValue.DeepCopy()
|
||||
sv.Spec.Description = ""
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.description", errs[0].Field)
|
||||
@@ -41,7 +45,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
sv.Spec.Value = nil
|
||||
sv.Spec.Ref = nil
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -50,7 +53,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
sv.Spec.Value = ptr.To(secretv1beta1.NewExposedSecureValue(""))
|
||||
sv.Spec.Ref = nil
|
||||
|
||||
validator = ProvideSecureValueValidator()
|
||||
errs = validator.Validate(sv, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -60,7 +62,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
sv.Spec.Value = ptr.To(secretv1beta1.NewExposedSecureValue("value"))
|
||||
sv.Spec.Ref = &ref
|
||||
|
||||
validator = ProvideSecureValueValidator()
|
||||
errs = validator.Validate(sv, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -71,7 +72,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
sv.Spec.Value = ptr.To(secretv1beta1.NewExposedSecureValue(strings.Repeat("a", contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES+1)))
|
||||
sv.Spec.Ref = nil
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.value", errs[0].Field)
|
||||
@@ -81,6 +81,7 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
t.Run("when updating a securevalue", func(t *testing.T) {
|
||||
t.Run("when trying to switch from a `value` (old) to a `ref` (new), it returns an error", func(t *testing.T) {
|
||||
oldSv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Ref: nil, // empty `ref` means a `value` was present.
|
||||
},
|
||||
@@ -88,6 +89,7 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
|
||||
ref := "ref"
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Ref: &ref,
|
||||
},
|
||||
@@ -102,18 +104,19 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
t.Run("when trying to switch from a `ref` (old) to a `value` (new), it returns an error", func(t *testing.T) {
|
||||
ref := "non-empty"
|
||||
oldSv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Ref: &ref,
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Value: ptr.To(secretv1beta1.NewExposedSecureValue("value")),
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, oldSv, admission.Update)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -122,6 +125,7 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
t.Run("when both `value` and `ref` are set, it returns an error", func(t *testing.T) {
|
||||
refNonEmpty := "non-empty"
|
||||
oldSv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Ref: &refNonEmpty,
|
||||
},
|
||||
@@ -129,24 +133,24 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
|
||||
ref := "ref"
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Value: ptr.To(secretv1beta1.NewExposedSecureValue("value")),
|
||||
Ref: &ref,
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, oldSv, admission.Update)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
|
||||
oldSv = &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Value: ptr.To(secretv1beta1.NewExposedSecureValue("non-empty")),
|
||||
},
|
||||
}
|
||||
|
||||
validator = ProvideSecureValueValidator()
|
||||
errs = validator.Validate(sv, oldSv, admission.Update)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -154,26 +158,26 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
|
||||
t.Run("when no changes are made, it returns no errors", func(t *testing.T) {
|
||||
oldSv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "old-description",
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "new-description",
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, oldSv, admission.Update)
|
||||
require.Empty(t, errs)
|
||||
})
|
||||
|
||||
t.Run("when the old object is `nil` it returns an error", func(t *testing.T) {
|
||||
sv := &secretv1beta1.SecureValue{}
|
||||
sv := &secretv1beta1.SecureValue{ObjectMeta: objectMeta}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, nil, admission.Update)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -183,18 +187,19 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
keeperA := "a-keeper"
|
||||
keeperAnother := "another-keeper"
|
||||
oldSv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Keeper: &keeperA,
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Keeper: &keeperAnother,
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, oldSv, admission.Update)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
@@ -204,6 +209,7 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
t.Run("`decrypters` must have unique items", func(t *testing.T) {
|
||||
ref := "ref"
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
@@ -214,7 +220,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.decrypters.[1]", errs[0].Field)
|
||||
@@ -223,6 +228,7 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
t.Run("`decrypters` list can be empty", func(t *testing.T) {
|
||||
ref := "ref"
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
@@ -230,7 +236,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, nil, admission.Create)
|
||||
require.Empty(t, errs)
|
||||
})
|
||||
@@ -251,6 +256,7 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
|
||||
ref := "ref"
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
@@ -258,7 +264,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, nil, admission.Create)
|
||||
require.Len(t, errs, 3)
|
||||
})
|
||||
@@ -271,6 +276,7 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
|
||||
ref := "ref"
|
||||
sv := &secretv1beta1.SecureValue{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: secretv1beta1.SecureValueSpec{
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
@@ -278,7 +284,6 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := ProvideSecureValueValidator()
|
||||
errs := validator.Validate(sv, nil, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.decrypters", errs[0].Field)
|
||||
|
||||
Reference in New Issue
Block a user