package validator import ( "strings" "testing" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" "k8s.io/utils/ptr" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/services/featuremgmt" ) func TestValidateKeeper(t *testing.T) { objectMeta := metav1.ObjectMeta{Name: "test", Namespace: "test"} validator := ProvideKeeperValidator(featuremgmt.WithFeatures(featuremgmt.FlagSecretsManagementAppPlatformAwsKeeper)) 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{ ObjectMeta: objectMeta, Spec: secretv1beta1.KeeperSpec{ Aws: &secretv1beta1.KeeperAWSConfig{ Region: "us-east-1", AccessKey: &secretv1beta1.KeeperAWSAccessKey{ AccessKeyID: secretv1beta1.KeeperCredentialValue{ValueFromEnv: "some-value"}, SecretAccessKey: secretv1beta1.KeeperCredentialValue{ValueFromEnv: "some-value"}, }, KmsKeyID: ptr.To("kms-key-id"), }, }, } errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.description", errs[0].Field) }) }) t.Run("aws keeper validation", func(t *testing.T) { validKeeperAWS := &secretv1beta1.Keeper{ ObjectMeta: objectMeta, Spec: secretv1beta1.KeeperSpec{ Description: "description", Aws: &secretv1beta1.KeeperAWSConfig{ Region: "us-east-1", AccessKey: &secretv1beta1.KeeperAWSAccessKey{ AccessKeyID: secretv1beta1.KeeperCredentialValue{ ValueFromEnv: "some-value", }, SecretAccessKey: secretv1beta1.KeeperCredentialValue{ SecureValueName: "some-value", }, }, KmsKeyID: ptr.To("optional"), }, }, } t.Run("aws keeper feature flag must be enabled", func(t *testing.T) { // Validator with feature disabled validator := ProvideKeeperValidator(featuremgmt.WithFeatures()) errs := validator.Validate(validKeeperAWS.DeepCopy(), nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws", errs[0].Field) require.Contains(t, errs[0].Detail, "secretsManagementAppPlatformAwsKeeper") }) t.Run("`accessKeyID` must be present", func(t *testing.T) { t.Run("at least one of the credential value must be present", func(t *testing.T) { keeper := validKeeperAWS.DeepCopy() keeper.Spec.Aws.AccessKey.AccessKeyID = secretv1beta1.KeeperCredentialValue{} errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws.accessKey.accessKeyID", errs[0].Field) }) t.Run("at most one of the credential value must be present", func(t *testing.T) { keeper := validKeeperAWS.DeepCopy() keeper.Spec.Aws.AccessKey.AccessKeyID = secretv1beta1.KeeperCredentialValue{ SecureValueName: "a", ValueFromEnv: "b", ValueFromConfig: "c", } errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws.accessKey.accessKeyID", errs[0].Field) }) }) t.Run("`secretAccessKey` must be present", func(t *testing.T) { t.Run("at least one of the credential value must be present", func(t *testing.T) { keeper := validKeeperAWS.DeepCopy() keeper.Spec.Aws.AccessKey.SecretAccessKey = secretv1beta1.KeeperCredentialValue{} errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws.accessKey.secretAccessKey", errs[0].Field) }) t.Run("at most one of the credential value must be present", func(t *testing.T) { keeper := validKeeperAWS.DeepCopy() keeper.Spec.Aws.AccessKey.SecretAccessKey = secretv1beta1.KeeperCredentialValue{ SecureValueName: "a", ValueFromEnv: "b", ValueFromConfig: "c", } errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws.accessKey.secretAccessKey", errs[0].Field) }) t.Run("only one of accessKey or assumeRole can be present", func(t *testing.T) { keeper := validKeeperAWS.DeepCopy() keeper.Spec.Aws.AccessKey.SecretAccessKey = secretv1beta1.KeeperCredentialValue{ SecureValueName: "a", } keeper.Spec.Aws.AssumeRole = &secretv1beta1.KeeperAWSAssumeRole{ AssumeRoleArn: "arn", ExternalID: "id", } errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws", errs[0].Field) require.Equal(t, "only one of `accessKey` or `assumeRole` can be present", errs[0].Detail) }) }) }) t.Run("azure keeper validation", func(t *testing.T) { validKeeperAzure := &secretv1beta1.Keeper{ ObjectMeta: objectMeta, Spec: secretv1beta1.KeeperSpec{ Description: "description", Azure: &secretv1beta1.KeeperAzureConfig{ KeyVaultName: "kv-name", TenantID: "tenant-id", ClientID: "client-id", ClientSecret: secretv1beta1.KeeperCredentialValue{ ValueFromConfig: "config.path.value", }, }, }, } t.Run("`keyVaultName` must be present", func(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.KeyVaultName = "" errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.keyVaultName", errs[0].Field) }) t.Run("`tenantID` must be present", func(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.TenantID = "" errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.tenantID", errs[0].Field) }) t.Run("`clientID` must be present", func(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.ClientID = "" errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.clientID", errs[0].Field) }) t.Run("`clientSecret` must be present", func(t *testing.T) { t.Run("at least one of the credential value must be present", func(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.ClientSecret = secretv1beta1.KeeperCredentialValue{} errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.clientSecret", errs[0].Field) }) t.Run("at most one of the credential value must be present", func(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.ClientSecret = secretv1beta1.KeeperCredentialValue{ SecureValueName: "a", ValueFromEnv: "b", ValueFromConfig: "c", } errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.clientSecret", errs[0].Field) }) }) }) t.Run("gcp keeper validation", func(t *testing.T) { validKeeperGCP := &secretv1beta1.Keeper{ ObjectMeta: objectMeta, Spec: secretv1beta1.KeeperSpec{ Description: "description", Gcp: &secretv1beta1.KeeperGCPConfig{ ProjectID: "project-id", CredentialsFile: "/path/to/credentials/file.json", }, }, } t.Run("`projectID` must be present", func(t *testing.T) { keeper := validKeeperGCP.DeepCopy() keeper.Spec.Gcp.ProjectID = "" errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.gcp.projectID", errs[0].Field) }) t.Run("`credentialsFile` must be present", func(t *testing.T) { keeper := validKeeperGCP.DeepCopy() keeper.Spec.Gcp.CredentialsFile = "" errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.gcp.credentialsFile", errs[0].Field) }) }) t.Run("hashicorp keeper validation", func(t *testing.T) { validKeeperHashiCorp := &secretv1beta1.Keeper{ ObjectMeta: objectMeta, Spec: secretv1beta1.KeeperSpec{ Description: "description", HashiCorpVault: &secretv1beta1.KeeperHashiCorpConfig{ Address: "http://address", Token: secretv1beta1.KeeperCredentialValue{ ValueFromConfig: "config.path.value", }, }, }, } t.Run("`address` must be present", func(t *testing.T) { keeper := validKeeperHashiCorp.DeepCopy() keeper.Spec.HashiCorpVault.Address = "" errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.hashiCorpVault.address", errs[0].Field) }) t.Run("`token` must be present", func(t *testing.T) { t.Run("at least one of the credential value must be present", func(t *testing.T) { keeper := validKeeperHashiCorp.DeepCopy() keeper.Spec.HashiCorpVault.Token = secretv1beta1.KeeperCredentialValue{} errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.hashiCorpVault.token", errs[0].Field) }) t.Run("at most one of the credential value must be present", func(t *testing.T) { keeper := validKeeperHashiCorp.DeepCopy() keeper.Spec.HashiCorpVault.Token = secretv1beta1.KeeperCredentialValue{ SecureValueName: "a", ValueFromEnv: "b", ValueFromConfig: "c", } errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.hashiCorpVault.token", errs[0].Field) }) }) }) t.Run("invalid name", func(t *testing.T) { keeper := &secretv1beta1.Keeper{ ObjectMeta: metav1.ObjectMeta{ Namespace: objectMeta.Namespace, }, Spec: secretv1beta1.KeeperSpec{ Description: "description", HashiCorpVault: &secretv1beta1.KeeperHashiCorpConfig{ Address: "http://address", Token: secretv1beta1.KeeperCredentialValue{ ValueFromConfig: "config.path.value", }, }, }, } keeper.Name = "" errs := validator.Validate(keeper, nil, admission.Delete) require.Len(t, errs, 1) require.Equal(t, "metadata.name", errs[0].Field) keeper.Name = "invalid/name-" errs = validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "metadata.name", errs[0].Field) keeper.Name = strings.Repeat("a", 253+1) errs = validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "metadata.name", errs[0].Field) }) t.Run("invalid namespace", func(t *testing.T) { keeper := &secretv1beta1.Keeper{ ObjectMeta: metav1.ObjectMeta{ Name: objectMeta.Name, }, Spec: secretv1beta1.KeeperSpec{ Description: "description", HashiCorpVault: &secretv1beta1.KeeperHashiCorpConfig{ Address: "http://address", Token: secretv1beta1.KeeperCredentialValue{ ValueFromConfig: "config.path.value", }, }, }, } keeper.Namespace = "" errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "metadata.namespace", errs[0].Field) keeper.Namespace = "invalid/namespace-" errs = validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "metadata.namespace", errs[0].Field) keeper.Namespace = strings.Repeat("a", 253+1) errs = validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "metadata.namespace", errs[0].Field) }) t.Run("keeper name `system` is reserved", func(t *testing.T) { keeper := &secretv1beta1.Keeper{ ObjectMeta: metav1.ObjectMeta{ Name: "system", Namespace: "ns1", }, Spec: secretv1beta1.KeeperSpec{ Description: "description", HashiCorpVault: &secretv1beta1.KeeperHashiCorpConfig{ Address: "http://address", Token: secretv1beta1.KeeperCredentialValue{ ValueFromConfig: "config.path.value", }, }, }, } errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "name", errs[0].Field) require.Equal(t, "the keeper name `system` is reserved", errs[0].Detail) }) }