SecretsManager: Changes to specs as ref, description, system keeper (#105319)
* SecretsManager: Changes to specs as ref, description, system keeper Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com> Co-authored-by: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> * SecretsManager: Changes to rest storage for spec ref, description, system keeper Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com> Co-authored-by: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> * SecretsManager: Changes to rest storage for spec description Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com> Co-authored-by: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> * SecretsManager: Changes to rest storage for spec description Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com> Co-authored-by: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> --------- Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
This commit is contained in:
co-authored by
PoorlyDefinedBehaviour
Matheus Macabu
parent
9d6ce37f68
commit
5158dce936
@@ -11,6 +11,7 @@ require (
|
||||
k8s.io/apimachinery v0.32.3
|
||||
k8s.io/apiserver v0.32.3
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -94,7 +95,6 @@ require (
|
||||
k8s.io/client-go v0.32.3 // indirect
|
||||
k8s.io/component-base v0.32.3 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
|
||||
@@ -16,26 +16,52 @@ type Keeper struct {
|
||||
// This is the actual keeper schema.
|
||||
// +patchStrategy=replace
|
||||
// +patchMergeKey=name
|
||||
Spec KeeperSpec `json:"spec,omitempty" patchStrategy:"replace" patchMergeKey:"name"`
|
||||
Spec KeeperSpec `json:"spec" patchStrategy:"replace" patchMergeKey:"name"`
|
||||
}
|
||||
|
||||
func (k *Keeper) IsSqlKeeper() bool {
|
||||
return k.Spec.SQL != nil && k.Spec.SQL.Encryption != nil
|
||||
// KeeperType represents the type of a Keeper.
|
||||
type KeeperType string
|
||||
|
||||
const (
|
||||
AWSKeeperType KeeperType = "aws"
|
||||
AzureKeeperType KeeperType = "azure"
|
||||
GCPKeeperType KeeperType = "gcp"
|
||||
HashiCorpKeeperType KeeperType = "hashicorp"
|
||||
)
|
||||
|
||||
func (kt KeeperType) String() string {
|
||||
return string(kt)
|
||||
}
|
||||
|
||||
// KeeperConfig is an interface that all keeper config types must implement.
|
||||
type KeeperConfig interface {
|
||||
Type() string
|
||||
Type() KeeperType
|
||||
}
|
||||
|
||||
type KeeperSpec struct {
|
||||
// Human friendly name for the keeper.
|
||||
Title string `json:"title"`
|
||||
// Short description for the Keeper.
|
||||
// +k8s:validation:minLength=1
|
||||
// +k8s:validation:maxLength=253
|
||||
Description string `json:"description"`
|
||||
|
||||
// You can only chose one of the following.
|
||||
SQL *SQLKeeperConfig `json:"sql,omitempty"`
|
||||
AWS *AWSKeeperConfig `json:"aws,omitempty"`
|
||||
Azure *AzureKeeperConfig `json:"azurekeyvault,omitempty"`
|
||||
GCP *GCPKeeperConfig `json:"gcp,omitempty"`
|
||||
// AWS Keeper Configuration.
|
||||
// +structType=atomic
|
||||
// +optional
|
||||
AWS *AWSKeeperConfig `json:"aws,omitempty"`
|
||||
|
||||
// Azure Keeper Configuration.
|
||||
// +structType=atomic
|
||||
// +optional
|
||||
Azure *AzureKeeperConfig `json:"azurekeyvault,omitempty"`
|
||||
|
||||
// GCP Keeper Configuration.
|
||||
// +structType=atomic
|
||||
// +optional
|
||||
GCP *GCPKeeperConfig `json:"gcp,omitempty"`
|
||||
|
||||
// HashiCorp Vault Keeper Configuration.
|
||||
// +structType=atomic
|
||||
// +optional
|
||||
HashiCorp *HashiCorpKeeperConfig `json:"hashivault,omitempty"`
|
||||
}
|
||||
|
||||
@@ -52,25 +78,6 @@ type KeeperList struct {
|
||||
Items []Keeper `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
// The default SQL keeper.
|
||||
type SQLKeeperConfig struct {
|
||||
Encryption *Encryption `json:"encryption,omitempty"`
|
||||
}
|
||||
|
||||
func (s *SQLKeeperConfig) Type() string {
|
||||
return "sql"
|
||||
}
|
||||
|
||||
// Encryption of default SQL keeper.
|
||||
type Encryption struct {
|
||||
Envelope *Envelope `json:"envelope,omitempty"` // TODO: what would this be
|
||||
|
||||
AWS *AWSCredentials `json:"aws,omitempty"`
|
||||
Azure *AzureCredentials `json:"azure,omitempty"`
|
||||
GCP *GCPCredentials `json:"gcp,omitempty"`
|
||||
HashiCorp *HashiCorpCredentials `json:"hashicorp,omitempty"`
|
||||
}
|
||||
|
||||
// Credentials of remote keepers.
|
||||
type AWSCredentials struct {
|
||||
AccessKeyID CredentialValue `json:"accessKeyId"`
|
||||
@@ -99,15 +106,19 @@ type HashiCorpCredentials struct {
|
||||
type Envelope struct{}
|
||||
|
||||
// Holds the way credentials are obtained.
|
||||
// +union
|
||||
type CredentialValue struct {
|
||||
// The name of the secure value that holds the actual value.
|
||||
// +optional
|
||||
SecureValueName string `json:"secureValueName,omitempty"`
|
||||
|
||||
// The value is taken from the environment variable.
|
||||
// +optional
|
||||
ValueFromEnv string `json:"valueFromEnv,omitempty"`
|
||||
|
||||
// The value is taken from the Grafana config file.
|
||||
// TODO: how do we explain that this is a path to the config file?
|
||||
// +optional
|
||||
ValueFromConfig string `json:"valueFromConfig,omitempty"`
|
||||
}
|
||||
|
||||
@@ -128,18 +139,18 @@ type HashiCorpKeeperConfig struct {
|
||||
HashiCorpCredentials `json:",inline"`
|
||||
}
|
||||
|
||||
func (s *AWSKeeperConfig) Type() string {
|
||||
return "aws"
|
||||
func (s *AWSKeeperConfig) Type() KeeperType {
|
||||
return AWSKeeperType
|
||||
}
|
||||
|
||||
func (s *AzureKeeperConfig) Type() string {
|
||||
return "azure"
|
||||
func (s *AzureKeeperConfig) Type() KeeperType {
|
||||
return AzureKeeperType
|
||||
}
|
||||
|
||||
func (s *GCPKeeperConfig) Type() string {
|
||||
return "gcp"
|
||||
func (s *GCPKeeperConfig) Type() KeeperType {
|
||||
return GCPKeeperType
|
||||
}
|
||||
|
||||
func (s *HashiCorpKeeperConfig) Type() string {
|
||||
return "hashicorp"
|
||||
func (s *HashiCorpKeeperConfig) Type() KeeperType {
|
||||
return HashiCorpKeeperType
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ var SecureValuesResourceInfo = utils.NewResourceInfo(
|
||||
// This defines the fields we view in `kubectl get`. Not related with the storage layer.
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Title", Type: "string", Format: "string", Description: "The display name of the secure value"},
|
||||
{Name: "Description", Type: "string", Format: "string", Description: "Short description that explains the purpose of this SecureValue"},
|
||||
{Name: "Keeper", Type: "string", Format: "string", Description: "Storage of the secure value"},
|
||||
{Name: "Ref", Type: "string", Format: "string", Description: "If present, the reference to a secret"},
|
||||
{Name: "Status", Type: "string", Format: "string", Description: "The status of the secure value"},
|
||||
@@ -41,7 +41,7 @@ var SecureValuesResourceInfo = utils.NewResourceInfo(
|
||||
if ok {
|
||||
return []interface{}{
|
||||
r.Name,
|
||||
r.Spec.Title,
|
||||
r.Spec.Description,
|
||||
r.Spec.Keeper,
|
||||
r.Spec.Ref,
|
||||
r.Status.Phase,
|
||||
@@ -65,7 +65,7 @@ var KeeperResourceInfo = utils.NewResourceInfo(
|
||||
// This defines the fields we view in `kubectl get`. Not related with the storage layer.
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Title", Type: "string", Format: "string", Description: "The display name of the keeper"},
|
||||
{Name: "Description", Type: "string", Format: "string", Description: "Short description for the Keeper"},
|
||||
},
|
||||
// Decodes the object into a concrete type. Return order in the slice must be the same as in `Definition`.
|
||||
Reader: func(obj any) ([]interface{}, error) {
|
||||
@@ -73,7 +73,7 @@ var KeeperResourceInfo = utils.NewResourceInfo(
|
||||
if ok {
|
||||
return []interface{}{
|
||||
r.Name,
|
||||
r.Spec.Title,
|
||||
r.Spec.Description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ type SecureValue struct {
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// This is the actual secure value schema.
|
||||
Spec SecureValueSpec `json:"spec,omitempty"`
|
||||
Spec SecureValueSpec `json:"spec"`
|
||||
|
||||
// Read-only observed status of the `SecureValue`.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||
Status SecureValueStatus `json:"status"`
|
||||
Status SecureValueStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +enum
|
||||
@@ -46,32 +46,40 @@ type SecureValueStatus struct {
|
||||
// Only applicable if the `phase=Failed`.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
// +optional
|
||||
ExternalID string `json:"externalId,omitempty"`
|
||||
}
|
||||
|
||||
type SecureValueSpec struct {
|
||||
// Human friendly name for the secure value.
|
||||
Title string `json:"title"`
|
||||
// Short description that explains the purpose of this SecureValue.
|
||||
// +k8s:validation:minLength=1
|
||||
// +k8s:validation:maxLength=253
|
||||
Description string `json:"description"`
|
||||
|
||||
// The raw value is only valid for write. Read/List will always be empty.
|
||||
// There is no support for mixing `value` and `ref`, you can't create a secret in a third-party keeper with a specified `ref`.
|
||||
// +k8s:validation:minLength=1
|
||||
Value ExposedSecureValue `json:"value,omitempty"`
|
||||
|
||||
// When using a remote Key manager, the ref is used to reference a value inside the remote storage.
|
||||
// When using a third-party keeper, the `ref` is used to reference a value inside the remote storage.
|
||||
// This should not contain sensitive information.
|
||||
Ref string `json:"ref,omitempty"`
|
||||
// +k8s:validation:minLength=1
|
||||
// +k8s:validation:maxLength=1024
|
||||
// +optional
|
||||
Ref *string `json:"ref,omitempty"`
|
||||
|
||||
// Name of the keeper, being the actual storage of the secure value.
|
||||
Keeper string `json:"keeper,omitempty"`
|
||||
// If not specified, the default keeper for the namespace will be used.
|
||||
// +k8s:validation:minLength=1
|
||||
// +k8s:validation:maxLength=253
|
||||
// +optional
|
||||
Keeper *string `json:"keeper,omitempty"`
|
||||
|
||||
// The Decrypters that are allowed to decrypt this secret.
|
||||
// An empty list means no service can decrypt it.
|
||||
// Support and behavior is still TBD, but could likely look like:
|
||||
// * testdata.grafana.app/{name1}
|
||||
// * testdata.grafana.app/{name2}
|
||||
// * runner.k6.grafana.app/* -- allow any k6 test runner
|
||||
// Rather than a string pattern, we may want a more explicit object:
|
||||
// [{ group:"testdata.grafana.app", name="name1"},
|
||||
// { group:"runner.k6.grafana.app"}]
|
||||
// +k8s:validation:maxItems=64
|
||||
// +k8s:validation:uniqueItems=true
|
||||
// +listType=atomic
|
||||
// +optional
|
||||
Decrypters []string `json:"decrypters"`
|
||||
|
||||
@@ -96,47 +96,6 @@ func (in *CredentialValue) DeepCopy() *CredentialValue {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Encryption) DeepCopyInto(out *Encryption) {
|
||||
*out = *in
|
||||
if in.Envelope != nil {
|
||||
in, out := &in.Envelope, &out.Envelope
|
||||
*out = new(Envelope)
|
||||
**out = **in
|
||||
}
|
||||
if in.AWS != nil {
|
||||
in, out := &in.AWS, &out.AWS
|
||||
*out = new(AWSCredentials)
|
||||
**out = **in
|
||||
}
|
||||
if in.Azure != nil {
|
||||
in, out := &in.Azure, &out.Azure
|
||||
*out = new(AzureCredentials)
|
||||
**out = **in
|
||||
}
|
||||
if in.GCP != nil {
|
||||
in, out := &in.GCP, &out.GCP
|
||||
*out = new(GCPCredentials)
|
||||
**out = **in
|
||||
}
|
||||
if in.HashiCorp != nil {
|
||||
in, out := &in.HashiCorp, &out.HashiCorp
|
||||
*out = new(HashiCorpCredentials)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Encryption.
|
||||
func (in *Encryption) DeepCopy() *Encryption {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Encryption)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Envelope) DeepCopyInto(out *Envelope) {
|
||||
*out = *in
|
||||
@@ -283,11 +242,6 @@ func (in *KeeperList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KeeperSpec) DeepCopyInto(out *KeeperSpec) {
|
||||
*out = *in
|
||||
if in.SQL != nil {
|
||||
in, out := &in.SQL, &out.SQL
|
||||
*out = new(SQLKeeperConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AWS != nil {
|
||||
in, out := &in.AWS, &out.AWS
|
||||
*out = new(AWSKeeperConfig)
|
||||
@@ -321,27 +275,6 @@ func (in *KeeperSpec) DeepCopy() *KeeperSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SQLKeeperConfig) DeepCopyInto(out *SQLKeeperConfig) {
|
||||
*out = *in
|
||||
if in.Encryption != nil {
|
||||
in, out := &in.Encryption, &out.Encryption
|
||||
*out = new(Encryption)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLKeeperConfig.
|
||||
func (in *SQLKeeperConfig) DeepCopy() *SQLKeeperConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SQLKeeperConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecureValue) DeepCopyInto(out *SecureValue) {
|
||||
*out = *in
|
||||
@@ -406,6 +339,16 @@ func (in *SecureValueList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecureValueSpec) DeepCopyInto(out *SecureValueSpec) {
|
||||
*out = *in
|
||||
if in.Ref != nil {
|
||||
in, out := &in.Ref, &out.Ref
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Keeper != nil {
|
||||
in, out := &in.Keeper, &out.Keeper
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Decrypters != nil {
|
||||
in, out := &in.Decrypters, &out.Decrypters
|
||||
*out = make([]string, len(*in))
|
||||
|
||||
@@ -10,6 +10,7 @@ package v0alpha1
|
||||
import (
|
||||
common "k8s.io/kube-openapi/pkg/common"
|
||||
spec "k8s.io/kube-openapi/pkg/validation/spec"
|
||||
ptr "k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||
@@ -19,7 +20,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureCredentials": schema_pkg_apis_secret_v0alpha1_AzureCredentials(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureKeeperConfig": schema_pkg_apis_secret_v0alpha1_AzureKeeperConfig(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.CredentialValue": schema_pkg_apis_secret_v0alpha1_CredentialValue(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Encryption": schema_pkg_apis_secret_v0alpha1_Encryption(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Envelope": schema_pkg_apis_secret_v0alpha1_Envelope(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPCredentials": schema_pkg_apis_secret_v0alpha1_GCPCredentials(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPKeeperConfig": schema_pkg_apis_secret_v0alpha1_GCPKeeperConfig(ref),
|
||||
@@ -28,7 +28,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Keeper": schema_pkg_apis_secret_v0alpha1_Keeper(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.KeeperList": schema_pkg_apis_secret_v0alpha1_KeeperList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.KeeperSpec": schema_pkg_apis_secret_v0alpha1_KeeperSpec(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SQLKeeperConfig": schema_pkg_apis_secret_v0alpha1_SQLKeeperConfig(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SecureValue": schema_pkg_apis_secret_v0alpha1_SecureValue(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SecureValueList": schema_pkg_apis_secret_v0alpha1_SecureValueList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SecureValueSpec": schema_pkg_apis_secret_v0alpha1_SecureValueSpec(ref),
|
||||
@@ -218,47 +217,20 @@ func schema_pkg_apis_secret_v0alpha1_CredentialValue(ref common.ReferenceCallbac
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_secret_v0alpha1_Encryption(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Encryption of default SQL keeper.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"envelope": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Envelope"),
|
||||
},
|
||||
},
|
||||
"aws": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AWSCredentials"),
|
||||
},
|
||||
},
|
||||
"azure": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureCredentials"),
|
||||
},
|
||||
},
|
||||
"gcp": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPCredentials"),
|
||||
},
|
||||
},
|
||||
"hashicorp": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpCredentials"),
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-unions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"fields-to-discriminateBy": map[string]interface{}{
|
||||
"secureValueName": "SecureValueName",
|
||||
"valueFromConfig": "ValueFromConfig",
|
||||
"valueFromEnv": "ValueFromEnv",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AWSCredentials", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureCredentials", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Envelope", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPCredentials", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpCredentials"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,6 +396,7 @@ func schema_pkg_apis_secret_v0alpha1_Keeper(ref common.ReferenceCallback) common
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"spec"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
@@ -486,66 +459,66 @@ func schema_pkg_apis_secret_v0alpha1_KeeperSpec(ref common.ReferenceCallback) co
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"title": {
|
||||
"description": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Human friendly name for the keeper.",
|
||||
Description: "Short description for the Keeper.",
|
||||
Default: "",
|
||||
MinLength: ptr.To[int64](1),
|
||||
MaxLength: ptr.To[int64](253),
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sql": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "You can only chose one of the following.",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SQLKeeperConfig"),
|
||||
},
|
||||
},
|
||||
"aws": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-map-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AWSKeeperConfig"),
|
||||
Description: "AWS Keeper Configuration.",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AWSKeeperConfig"),
|
||||
},
|
||||
},
|
||||
"azurekeyvault": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-map-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureKeeperConfig"),
|
||||
Description: "Azure Keeper Configuration.",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureKeeperConfig"),
|
||||
},
|
||||
},
|
||||
"gcp": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-map-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPKeeperConfig"),
|
||||
Description: "GCP Keeper Configuration.",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPKeeperConfig"),
|
||||
},
|
||||
},
|
||||
"hashivault": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-map-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpKeeperConfig"),
|
||||
Description: "HashiCorp Vault Keeper Configuration.",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpKeeperConfig"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"title"},
|
||||
Required: []string{"description"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AWSKeeperConfig", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureKeeperConfig", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPKeeperConfig", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpKeeperConfig", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SQLKeeperConfig"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_secret_v0alpha1_SQLKeeperConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The default SQL keeper.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"encryption": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Encryption"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Encryption"},
|
||||
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AWSKeeperConfig", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureKeeperConfig", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPKeeperConfig", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpKeeperConfig"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,7 +564,7 @@ func schema_pkg_apis_secret_v0alpha1_SecureValue(ref common.ReferenceCallback) c
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"status"},
|
||||
Required: []string{"spec"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
@@ -654,10 +627,12 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueSpec(ref common.ReferenceCallbac
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"title": {
|
||||
"description": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Human friendly name for the secure value.",
|
||||
Description: "Short description that explains the purpose of this SecureValue.",
|
||||
Default: "",
|
||||
MinLength: ptr.To[int64](1),
|
||||
MaxLength: ptr.To[int64](253),
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
@@ -665,20 +640,25 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueSpec(ref common.ReferenceCallbac
|
||||
"value": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The raw value is only valid for write. Read/List will always be empty. There is no support for mixing `value` and `ref`, you can't create a secret in a third-party keeper with a specified `ref`.",
|
||||
MinLength: ptr.To[int64](1),
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"ref": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "When using a remote Key manager, the ref is used to reference a value inside the remote storage. This should not contain sensitive information.",
|
||||
Description: "When using a third-party keeper, the `ref` is used to reference a value inside the remote storage. This should not contain sensitive information.",
|
||||
MinLength: ptr.To[int64](1),
|
||||
MaxLength: ptr.To[int64](1024),
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"keeper": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the keeper, being the actual storage of the secure value.",
|
||||
Description: "Name of the keeper, being the actual storage of the secure value. If not specified, the default keeper for the namespace will be used.",
|
||||
MinLength: ptr.To[int64](1),
|
||||
MaxLength: ptr.To[int64](253),
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
@@ -690,7 +670,9 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueSpec(ref common.ReferenceCallbac
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The Decrypters that are allowed to decrypt this secret. An empty list means no service can decrypt it. Support and behavior is still TBD, but could likely look like: * testdata.grafana.app/{name1} * testdata.grafana.app/{name2} * runner.k6.grafana.app/* -- allow any k6 test runner Rather than a string pattern, we may want a more explicit object: [{ group:\"testdata.grafana.app\", name=\"name1\"},\n { group:\"runner.k6.grafana.app\"}]",
|
||||
Description: "The Decrypters that are allowed to decrypt this secret. An empty list means no service can decrypt it.",
|
||||
MaxItems: ptr.To[int64](64),
|
||||
UniqueItems: true,
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
@@ -704,7 +686,7 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueSpec(ref common.ReferenceCallbac
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"title"},
|
||||
Required: []string{"description"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -732,6 +714,12 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueStatus(ref common.ReferenceCallb
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"externalId": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"phase"},
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alp
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,AWSCredentials,KMSKeyID
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,AzureCredentials,ClientID
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,AzureCredentials,TenantID
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,Encryption,HashiCorp
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,GCPCredentials,ProjectID
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,KeeperSpec,Azure
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,KeeperSpec,HashiCorp
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,SecureValueStatus,ExternalID
|
||||
|
||||
@@ -209,8 +209,8 @@ func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation
|
||||
|
||||
errs := make(field.ErrorList, 0)
|
||||
|
||||
if keeper.Spec.Title == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "title"), "a `title` is required"))
|
||||
if keeper.Spec.Description == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "description"), "a `description` is required"))
|
||||
}
|
||||
|
||||
// Only one keeper type can be configured. Return early and don't validate the specific keeper fields.
|
||||
@@ -220,28 +220,6 @@ func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation
|
||||
return errs
|
||||
}
|
||||
|
||||
// TODO: Improve SQL keeper validation.
|
||||
// SQL keeper is not allowed to use `secureValueName` in credentials fields to avoid depending on another keeper.
|
||||
if keeper.IsSqlKeeper() {
|
||||
if keeper.Spec.SQL.Encryption.AWS != nil {
|
||||
if keeper.Spec.SQL.Encryption.AWS.AccessKeyID.SecureValueName != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "aws", "accessKeyId"), "secureValueName cannot be used with SQL keeper"))
|
||||
}
|
||||
|
||||
if keeper.Spec.SQL.Encryption.AWS.SecretAccessKey.SecureValueName != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "aws", "secretAccessKey"), "secureValueName cannot be used with SQL keeper"))
|
||||
}
|
||||
}
|
||||
|
||||
if keeper.Spec.SQL.Encryption.Azure != nil && keeper.Spec.SQL.Encryption.Azure.ClientSecret.SecureValueName != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "azure", "clientSecret"), "secureValueName cannot be used with SQL keeper"))
|
||||
}
|
||||
|
||||
if keeper.Spec.SQL.Encryption.HashiCorp != nil && keeper.Spec.SQL.Encryption.HashiCorp.Token.SecureValueName != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "hashicorp", "token"), "secureValueName cannot be used with SQL keeper"))
|
||||
}
|
||||
}
|
||||
|
||||
if keeper.Spec.AWS != nil {
|
||||
if err := validateCredentialValue(field.NewPath("spec", "aws", "accessKeyId"), keeper.Spec.AWS.AccessKeyID); err != nil {
|
||||
errs = append(errs, err)
|
||||
@@ -295,7 +273,6 @@ func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation
|
||||
|
||||
func validateKeepers(keeper *secretv0alpha1.Keeper) *field.Error {
|
||||
availableKeepers := map[string]bool{
|
||||
"sql": keeper.Spec.SQL != nil,
|
||||
"aws": keeper.Spec.AWS != nil,
|
||||
"azure": keeper.Spec.Azure != nil,
|
||||
"gcp": keeper.Spec.GCP != nil,
|
||||
|
||||
@@ -10,28 +10,33 @@ import (
|
||||
|
||||
func TestValidateKeeper(t *testing.T) {
|
||||
t.Run("when creating a new keeper", func(t *testing.T) {
|
||||
t.Run("the `title` must be present", func(t *testing.T) {
|
||||
t.Run("the `description` must be present", func(t *testing.T) {
|
||||
keeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
SQL: &secretv0alpha1.SQLKeeperConfig{},
|
||||
AWS: &secretv0alpha1.AWSKeeperConfig{
|
||||
AWSCredentials: secretv0alpha1.AWSCredentials{
|
||||
AccessKeyID: secretv0alpha1.CredentialValue{ValueFromEnv: "some-value"},
|
||||
SecretAccessKey: secretv0alpha1.CredentialValue{ValueFromEnv: "some-value"},
|
||||
KMSKeyID: "kms-key-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.title", errs[0].Field)
|
||||
require.Equal(t, "spec.description", errs[0].Field)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("only one `keeper` must be present", func(t *testing.T) {
|
||||
keeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
SQL: &secretv0alpha1.SQLKeeperConfig{},
|
||||
AWS: &secretv0alpha1.AWSKeeperConfig{},
|
||||
Azure: &secretv0alpha1.AzureKeeperConfig{},
|
||||
GCP: &secretv0alpha1.GCPKeeperConfig{},
|
||||
HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{},
|
||||
Description: "short description",
|
||||
AWS: &secretv0alpha1.AWSKeeperConfig{},
|
||||
Azure: &secretv0alpha1.AzureKeeperConfig{},
|
||||
GCP: &secretv0alpha1.GCPKeeperConfig{},
|
||||
HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -43,7 +48,7 @@ func TestValidateKeeper(t *testing.T) {
|
||||
t.Run("at least one `keeper` must be present", func(t *testing.T) {
|
||||
keeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -55,7 +60,7 @@ func TestValidateKeeper(t *testing.T) {
|
||||
t.Run("aws keeper validation", func(t *testing.T) {
|
||||
validKeeperAWS := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
AWS: &secretv0alpha1.AWSKeeperConfig{
|
||||
AWSCredentials: secretv0alpha1.AWSCredentials{
|
||||
AccessKeyID: secretv0alpha1.CredentialValue{
|
||||
@@ -122,7 +127,7 @@ func TestValidateKeeper(t *testing.T) {
|
||||
t.Run("azure keeper validation", func(t *testing.T) {
|
||||
validKeeperAzure := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
Azure: &secretv0alpha1.AzureKeeperConfig{
|
||||
AzureCredentials: secretv0alpha1.AzureCredentials{
|
||||
KeyVaultName: "kv-name",
|
||||
@@ -191,7 +196,7 @@ func TestValidateKeeper(t *testing.T) {
|
||||
t.Run("gcp keeper validation", func(t *testing.T) {
|
||||
validKeeperGCP := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
GCP: &secretv0alpha1.GCPKeeperConfig{
|
||||
GCPCredentials: secretv0alpha1.GCPCredentials{
|
||||
ProjectID: "project-id",
|
||||
@@ -223,7 +228,7 @@ func TestValidateKeeper(t *testing.T) {
|
||||
t.Run("hashicorp keeper validation", func(t *testing.T) {
|
||||
validKeeperHashiCorp := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{
|
||||
HashiCorpCredentials: secretv0alpha1.HashiCorpCredentials{
|
||||
Address: "http://address",
|
||||
@@ -268,65 +273,4 @@ func TestValidateKeeper(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("sql keeper validation", func(t *testing.T) {
|
||||
t.Run("does not allow usage of `secureValueName` in credentials", func(t *testing.T) {
|
||||
providers := []struct {
|
||||
name string
|
||||
enc secretv0alpha1.Encryption
|
||||
expectedErrors int
|
||||
}{
|
||||
{
|
||||
name: "aws",
|
||||
enc: secretv0alpha1.Encryption{
|
||||
AWS: &secretv0alpha1.AWSCredentials{
|
||||
AccessKeyID: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "not-empty",
|
||||
},
|
||||
SecretAccessKey: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "not-empty",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 2,
|
||||
},
|
||||
{
|
||||
name: "azure",
|
||||
enc: secretv0alpha1.Encryption{
|
||||
Azure: &secretv0alpha1.AzureCredentials{
|
||||
ClientSecret: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "not-empty",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "hashicorp",
|
||||
enc: secretv0alpha1.Encryption{
|
||||
HashiCorp: &secretv0alpha1.HashiCorpCredentials{
|
||||
Token: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "not-empty",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range providers {
|
||||
t.Run("when using credentials for "+tc.name, func(t *testing.T) {
|
||||
keeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
SQL: &secretv0alpha1.SQLKeeperConfig{Encryption: &tc.enc},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, tc.expectedErrors)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -218,19 +218,15 @@ func ValidateSecureValue(sv, oldSv *secretv0alpha1.SecureValue, operation admiss
|
||||
func validateSecureValueCreate(sv *secretv0alpha1.SecureValue) field.ErrorList {
|
||||
errs := make(field.ErrorList, 0)
|
||||
|
||||
if sv.Spec.Title == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "title"), "a `title` is required"))
|
||||
if sv.Spec.Description == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "description"), "a `description` is required"))
|
||||
}
|
||||
|
||||
if sv.Spec.Keeper == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "keeper"), "a `keeper` is required"))
|
||||
}
|
||||
|
||||
if sv.Spec.Value == "" && sv.Spec.Ref == "" {
|
||||
if sv.Spec.Value == "" && (sv.Spec.Ref == nil || (sv.Spec.Ref != nil && *sv.Spec.Ref == "")) {
|
||||
errs = append(errs, field.Required(field.NewPath("spec"), "either a `value` or `ref` is required"))
|
||||
}
|
||||
|
||||
if sv.Spec.Value != "" && sv.Spec.Ref != "" {
|
||||
if sv.Spec.Value != "" && (sv.Spec.Ref != nil && *sv.Spec.Ref != "") {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec"), "only one of `value` or `ref` can be set"))
|
||||
}
|
||||
|
||||
@@ -249,12 +245,12 @@ func validateSecureValueUpdate(sv, oldSv *secretv0alpha1.SecureValue) field.Erro
|
||||
}
|
||||
|
||||
// Only validate if one of the fields is being changed/set.
|
||||
if sv.Spec.Value != "" || sv.Spec.Ref != "" {
|
||||
if oldSv.Spec.Ref != "" && sv.Spec.Value != "" {
|
||||
if sv.Spec.Value != "" || (sv.Spec.Ref != nil && *sv.Spec.Ref != "") {
|
||||
if (oldSv.Spec.Ref != nil && *oldSv.Spec.Ref != "") && sv.Spec.Value != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `value` when `ref` was already previously set"))
|
||||
}
|
||||
|
||||
if oldSv.Spec.Ref == "" && sv.Spec.Ref != "" {
|
||||
if (oldSv.Spec.Ref == nil || (oldSv.Spec.Ref != nil && *oldSv.Spec.Ref == "")) && (sv.Spec.Ref != nil && *sv.Spec.Ref != "") {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `ref` when `value` was already previously set"))
|
||||
}
|
||||
}
|
||||
@@ -271,6 +267,17 @@ func validateSecureValueUpdate(sv, oldSv *secretv0alpha1.SecureValue) field.Erro
|
||||
func validateDecrypters(decrypters []string, decryptersAllowList map[string]struct{}) field.ErrorList {
|
||||
errs := make(field.ErrorList, 0)
|
||||
|
||||
// Limit the number of decrypters to 64 to not have it unbounded.
|
||||
// The number was chosen arbitrarily and should be enough.
|
||||
if len(decrypters) > 64 {
|
||||
errs = append(
|
||||
errs,
|
||||
field.TooMany(field.NewPath("spec", "decrypters"), len(decrypters), 64),
|
||||
)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
decrypterNames := make(map[string]struct{}, 0)
|
||||
|
||||
for i, decrypter := range decrypters {
|
||||
|
||||
@@ -14,44 +14,37 @@ import (
|
||||
|
||||
func TestValidateSecureValue(t *testing.T) {
|
||||
t.Run("when creating a new securevalue", func(t *testing.T) {
|
||||
keeper := "keeper"
|
||||
validSecureValue := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title",
|
||||
Value: "value",
|
||||
Keeper: "keeper",
|
||||
Decrypters: []string{"actor_app1", "actor_app2"},
|
||||
Description: "description",
|
||||
Value: "value",
|
||||
Keeper: &keeper,
|
||||
Decrypters: []string{"actor_app1", "actor_app2"},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("the `title` must be present", func(t *testing.T) {
|
||||
t.Run("the `description` must be present", func(t *testing.T) {
|
||||
sv := validSecureValue.DeepCopy()
|
||||
sv.Spec.Title = ""
|
||||
sv.Spec.Description = ""
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.title", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("the `keeper` must be present", func(t *testing.T) {
|
||||
sv := validSecureValue.DeepCopy()
|
||||
sv.Spec.Keeper = ""
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.keeper", errs[0].Field)
|
||||
require.Equal(t, "spec.description", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("either a `value` or `ref` must be present but not both", func(t *testing.T) {
|
||||
sv := validSecureValue.DeepCopy()
|
||||
sv.Spec.Value = ""
|
||||
sv.Spec.Ref = ""
|
||||
sv.Spec.Ref = nil
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
|
||||
ref := "value"
|
||||
sv.Spec.Value = "value"
|
||||
sv.Spec.Ref = "value"
|
||||
sv.Spec.Ref = &ref
|
||||
|
||||
errs = ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
@@ -63,13 +56,14 @@ func TestValidateSecureValue(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 := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Ref: "", // empty `ref` means a `value` was present.
|
||||
Ref: nil, // empty `ref` means a `value` was present.
|
||||
},
|
||||
}
|
||||
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Ref: "ref",
|
||||
Ref: &ref,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -79,9 +73,10 @@ 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 := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Ref: "non-empty",
|
||||
Ref: &ref,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -97,16 +92,18 @@ 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 := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Ref: "non-empty",
|
||||
Ref: &refNonEmpty,
|
||||
},
|
||||
}
|
||||
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Value: "value",
|
||||
Ref: "ref",
|
||||
Ref: &ref,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -128,13 +125,13 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
t.Run("when no changes are made, it returns no errors", func(t *testing.T) {
|
||||
oldSv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "old-title",
|
||||
Description: "old-description",
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "new-title",
|
||||
Description: "new-description",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -151,15 +148,17 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("when trying to change the `keeper`, it returns an error", func(t *testing.T) {
|
||||
keeperA := "a-keeper"
|
||||
keeperAnother := "another-keeper"
|
||||
oldSv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Keeper: "a-keeper",
|
||||
Keeper: &keeperA,
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Keeper: "another-keeper",
|
||||
Keeper: &keeperAnother,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -170,9 +169,10 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("`decrypters` must have unique items", func(t *testing.T) {
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
Decrypters: []string{
|
||||
"actor_app1",
|
||||
@@ -187,9 +187,10 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("`decrypters` must match the expected format", func(t *testing.T) {
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
Decrypters: []string{
|
||||
"app1",
|
||||
@@ -215,9 +216,10 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
decrypters := slices.Collect(maps.Keys(allowList))
|
||||
|
||||
t.Run("no matches, returns an error", func(t *testing.T) {
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
Decrypters: []string{"actor_app3"},
|
||||
},
|
||||
@@ -228,9 +230,10 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("no decrypters, returns no error", func(t *testing.T) {
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
Decrypters: []string{},
|
||||
},
|
||||
@@ -241,9 +244,10 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("one match, returns no errors", func(t *testing.T) {
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
Decrypters: []string{decrypters[0]},
|
||||
},
|
||||
@@ -254,9 +258,10 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("all matches, returns no errors", func(t *testing.T) {
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
Decrypters: decrypters,
|
||||
},
|
||||
@@ -266,4 +271,24 @@ func TestValidateSecureValue(t *testing.T) {
|
||||
require.Empty(t, errs)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("`decrypters` cannot have more than 64 items", func(t *testing.T) {
|
||||
decrypters := make([]string, 0, 64+1)
|
||||
for i := 0; i < 64+1; i++ {
|
||||
decrypters = append(decrypters, fmt.Sprintf("actor_app%d", i))
|
||||
}
|
||||
|
||||
ref := "ref"
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Description: "description", Ref: &ref,
|
||||
|
||||
Decrypters: decrypters,
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.decrypters", errs[0].Field)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user