diff --git a/apis/common/v1/connection_details.go b/apis/common/v1/connection_details.go index 2b28f9ff1..9adbc41cc 100644 --- a/apis/common/v1/connection_details.go +++ b/apis/common/v1/connection_details.go @@ -93,9 +93,6 @@ const ( // Secrets. SecretStoreKubernetes SecretStoreType = "Kubernetes" - // SecretStoreVault indicates that secret store type is Vault. - SecretStoreVault SecretStoreType = "Vault" - // SecretStorePlugin indicates that secret store type is Plugin and will be used with external secret stores. SecretStorePlugin SecretStoreType = "Plugin" ) @@ -122,13 +119,6 @@ type SecretStoreConfig struct { // +optional Kubernetes *KubernetesSecretStoreConfig `json:"kubernetes,omitempty"` - // Vault configures a Vault secret store. - // Deprecated: This API is scheduled to be removed in a future release. - // Vault should be used as a plugin going forward. See - // https://github.com/crossplane-contrib/ess-plugin-vault for more information. - // +optional - Vault *VaultSecretStoreConfig `json:"vault,omitempty"` - // Plugin configures External secret store as a plugin. // +optional Plugin *PluginStoreConfig `json:"plugin,omitempty"` @@ -173,83 +163,3 @@ type KubernetesSecretStoreConfig struct { // TODO(turkenh): Support additional identities like // https://github.com/crossplane-contrib/provider-kubernetes/blob/4d722ef914e6964e80e190317daca9872ae98738/apis/v1alpha1/types.go#L34 } - -// VaultAuthMethod represent a Vault authentication method. -// https://www.vaultproject.io/docs/auth -type VaultAuthMethod string - -const ( - // VaultAuthToken indicates that "Token Auth" will be used to - // authenticate to Vault. - // https://www.vaultproject.io/docs/auth/token - VaultAuthToken VaultAuthMethod = "Token" -) - -// VaultAuthTokenConfig represents configuration for Vault Token Auth Method. -// https://www.vaultproject.io/docs/auth/token -type VaultAuthTokenConfig struct { - // Source of the credentials. - // +kubebuilder:validation:Enum=None;Secret;Environment;Filesystem - Source CredentialsSource `json:"source"` - - // CommonCredentialSelectors provides common selectors for extracting - // credentials. - CommonCredentialSelectors `json:",inline"` -} - -// VaultAuthConfig required to authenticate to a Vault API. -type VaultAuthConfig struct { - // Method configures which auth method will be used. - Method VaultAuthMethod `json:"method"` - // Token configures Token Auth for Vault. - // +optional - Token *VaultAuthTokenConfig `json:"token,omitempty"` -} - -// VaultCABundleConfig represents configuration for configuring a CA bundle. -type VaultCABundleConfig struct { - // Source of the credentials. - // +kubebuilder:validation:Enum=None;Secret;Environment;Filesystem - Source CredentialsSource `json:"source"` - - // CommonCredentialSelectors provides common selectors for extracting - // credentials. - CommonCredentialSelectors `json:",inline"` -} - -// VaultKVVersion represent API version of the Vault KV engine -// https://www.vaultproject.io/docs/secrets/kv -type VaultKVVersion string - -const ( - // VaultKVVersionV1 indicates that Secret API is KV Secrets Engine Version 1 - // https://www.vaultproject.io/docs/secrets/kv/kv-v1 - VaultKVVersionV1 VaultKVVersion = "v1" - - // VaultKVVersionV2 indicates that Secret API is KV Secrets Engine Version 2 - // https://www.vaultproject.io/docs/secrets/kv/kv-v2 - VaultKVVersionV2 VaultKVVersion = "v2" -) - -// VaultSecretStoreConfig represents the required configuration for a Vault -// secret store. -type VaultSecretStoreConfig struct { - // Server is the url of the Vault server, e.g. "https://vault.acme.org" - Server string `json:"server"` - - // MountPath is the mount path of the KV secrets engine. - MountPath string `json:"mountPath"` - - // Version of the KV Secrets engine of Vault. - // https://www.vaultproject.io/docs/secrets/kv - // +optional - // +kubebuilder:default=v2 - Version *VaultKVVersion `json:"version,omitempty"` - - // CABundle configures CA bundle for Vault Server. - // +optional - CABundle *VaultCABundleConfig `json:"caBundle,omitempty"` - - // Auth configures an authentication method for Vault. - Auth VaultAuthConfig `json:"auth"` -} diff --git a/apis/common/v1/zz_generated.deepcopy.go b/apis/common/v1/zz_generated.deepcopy.go index fa26f38d5..2437c7082 100644 --- a/apis/common/v1/zz_generated.deepcopy.go +++ b/apis/common/v1/zz_generated.deepcopy.go @@ -482,11 +482,6 @@ func (in *SecretStoreConfig) DeepCopyInto(out *SecretStoreConfig) { *out = new(KubernetesSecretStoreConfig) (*in).DeepCopyInto(*out) } - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(VaultSecretStoreConfig) - (*in).DeepCopyInto(*out) - } if in.Plugin != nil { in, out := &in.Plugin, &out.Plugin *out = new(PluginStoreConfig) @@ -591,81 +586,3 @@ func (in *TypedReference) DeepCopy() *TypedReference { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultAuthConfig) DeepCopyInto(out *VaultAuthConfig) { - *out = *in - if in.Token != nil { - in, out := &in.Token, &out.Token - *out = new(VaultAuthTokenConfig) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuthConfig. -func (in *VaultAuthConfig) DeepCopy() *VaultAuthConfig { - if in == nil { - return nil - } - out := new(VaultAuthConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultAuthTokenConfig) DeepCopyInto(out *VaultAuthTokenConfig) { - *out = *in - in.CommonCredentialSelectors.DeepCopyInto(&out.CommonCredentialSelectors) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuthTokenConfig. -func (in *VaultAuthTokenConfig) DeepCopy() *VaultAuthTokenConfig { - if in == nil { - return nil - } - out := new(VaultAuthTokenConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultCABundleConfig) DeepCopyInto(out *VaultCABundleConfig) { - *out = *in - in.CommonCredentialSelectors.DeepCopyInto(&out.CommonCredentialSelectors) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultCABundleConfig. -func (in *VaultCABundleConfig) DeepCopy() *VaultCABundleConfig { - if in == nil { - return nil - } - out := new(VaultCABundleConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultSecretStoreConfig) DeepCopyInto(out *VaultSecretStoreConfig) { - *out = *in - if in.Version != nil { - in, out := &in.Version, &out.Version - *out = new(VaultKVVersion) - **out = **in - } - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = new(VaultCABundleConfig) - (*in).DeepCopyInto(*out) - } - in.Auth.DeepCopyInto(&out.Auth) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultSecretStoreConfig. -func (in *VaultSecretStoreConfig) DeepCopy() *VaultSecretStoreConfig { - if in == nil { - return nil - } - out := new(VaultSecretStoreConfig) - in.DeepCopyInto(out) - return out -} diff --git a/go.mod b/go.mod index c591c8818..8222ac653 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/bufbuild/buf v1.25.1 github.com/go-logr/logr v1.2.4 github.com/google/go-cmp v0.5.9 - github.com/hashicorp/vault/api v1.9.2 github.com/spf13/afero v1.9.5 golang.org/x/time v0.3.0 google.golang.org/grpc v1.57.0 @@ -29,7 +28,6 @@ require ( github.com/bufbuild/connect-go v1.9.0 // indirect github.com/bufbuild/connect-opentelemetry-go v0.4.0 // indirect github.com/bufbuild/protocompile v0.6.0 // indirect - github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect @@ -46,7 +44,6 @@ require ( github.com/felixge/fgprof v0.9.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-chi/chi/v5 v5.0.10 // indirect - github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -61,15 +58,6 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.1 // indirect - github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect - github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 // indirect @@ -82,7 +70,6 @@ require ( github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -99,7 +86,6 @@ require ( github.com/prometheus/procfs v0.10.0 // indirect github.com/rs/cors v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index ac03a5a5a..a5ddc6430 100644 --- a/go.sum +++ b/go.sum @@ -47,11 +47,9 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/buf v1.25.1 h1:8ed5AjZ+zPIJf72rxtfsDit/MtaBimaSRn9Y+5G++y0= github.com/bufbuild/buf v1.25.1/go.mod h1:UMPncXMWgrmIM+0QpwTEwjNr2SA0z2YIVZZsmNflvB4= github.com/bufbuild/connect-go v1.9.0 h1:JIgAeNuFpo+SUPfU19Yt5TcWlznsN5Bv10/gI/6Pjoc= @@ -61,8 +59,6 @@ github.com/bufbuild/connect-opentelemetry-go v0.4.0/go.mod h1:nwPXYoDOoc2DGyKE/6 github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= -github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -109,7 +105,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= @@ -123,8 +118,6 @@ github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNIT github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= -github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -139,7 +132,6 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= @@ -223,34 +215,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= -github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.9.2 h1:YjkZLJ7K3inKgMZ0wzCU9OHqc+UqMQyXsPXnf3Cl2as= -github.com/hashicorp/vault/api v1.9.2/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= @@ -285,22 +251,15 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -331,7 +290,6 @@ github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDj github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -348,9 +306,6 @@ github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -365,11 +320,9 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -413,7 +366,6 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -514,7 +466,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/connection/store/vault/fake/mocks.go b/pkg/connection/store/vault/fake/mocks.go deleted file mode 100644 index 05331f375..000000000 --- a/pkg/connection/store/vault/fake/mocks.go +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2022 The Crossplane Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -// Package fake is a fake Vault KVClient. -package fake - -import ( - "github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv" -) - -// KVClient is a fake KVClient. -type KVClient struct { - GetFn func(path string, secret *kv.Secret) error - ApplyFn func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error - DeleteFn func(path string) error -} - -// Get fetches a secret at a given path. -func (k *KVClient) Get(path string, secret *kv.Secret) error { - return k.GetFn(path, secret) -} - -// Apply creates or updates a secret at a given path. -func (k *KVClient) Apply(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - return k.ApplyFn(path, secret, ao...) -} - -// Delete deletes a secret at a given path. -func (k *KVClient) Delete(path string) error { - return k.DeleteFn(path) -} diff --git a/pkg/connection/store/vault/kv/fake/mocks.go b/pkg/connection/store/vault/kv/fake/mocks.go deleted file mode 100644 index 04feccb60..000000000 --- a/pkg/connection/store/vault/kv/fake/mocks.go +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2022 The Crossplane Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -// Package fake is a fake Vault LogicalClient. -package fake - -import ( - "github.com/hashicorp/vault/api" -) - -// LogicalClient is a fake LogicalClient -type LogicalClient struct { - ReadFn func(path string) (*api.Secret, error) - WriteFn func(path string, data map[string]any) (*api.Secret, error) - DeleteFn func(path string) (*api.Secret, error) -} - -// Read reads secret at the given path. -func (l *LogicalClient) Read(path string) (*api.Secret, error) { - return l.ReadFn(path) -} - -// Write writes data to the given path. -func (l *LogicalClient) Write(path string, data map[string]any) (*api.Secret, error) { - return l.WriteFn(path, data) -} - -// Delete deletes secret at the given path. -func (l *LogicalClient) Delete(path string) (*api.Secret, error) { - return l.DeleteFn(path) -} diff --git a/pkg/connection/store/vault/kv/secret.go b/pkg/connection/store/vault/kv/secret.go deleted file mode 100644 index e0b5a8f68..000000000 --- a/pkg/connection/store/vault/kv/secret.go +++ /dev/null @@ -1,100 +0,0 @@ -/* - Copyright 2022 The Crossplane Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -// Package kv represents Vault key-value pairs. -package kv - -import ( - "encoding/json" - - "github.com/hashicorp/vault/api" - - "github.com/crossplane/crossplane-runtime/pkg/resource" -) - -const ( - errGet = "cannot get secret" - errDelete = "cannot delete secret" - errRead = "cannot read secret" - errWriteData = "cannot write secret Data" - errUpdateNotAllowed = "update not allowed" - - // ErrNotFound is the error returned when secret does not exist. - ErrNotFound = "secret not found" -) - -// LogicalClient is a client to perform logical backend operations on Vault. -type LogicalClient interface { - Read(path string) (*api.Secret, error) - Write(path string, data map[string]any) (*api.Secret, error) - Delete(path string) (*api.Secret, error) -} - -// Secret is a Vault KV secret. -type Secret struct { - CustomMeta map[string]string - Data map[string]string - version json.Number -} - -// NewSecret returns a new Secret. -func NewSecret(data map[string]string, meta map[string]string) *Secret { - return &Secret{ - Data: data, - CustomMeta: meta, - } -} - -// AddData adds supplied key value as data. -func (kv *Secret) AddData(key string, val string) { - if kv.Data == nil { - kv.Data = map[string]string{} - } - kv.Data[key] = val -} - -// AddMetadata adds supplied key value as metadata. -func (kv *Secret) AddMetadata(key string, val string) { - if kv.CustomMeta == nil { - kv.CustomMeta = map[string]string{} - } - kv.CustomMeta[key] = val -} - -// An ApplyOption is called before patching the current secret to match the -// desired secret. ApplyOptions are not called if no current object exists. -type ApplyOption func(current, desired *Secret) error - -// AllowUpdateIf will only update the current object if the supplied fn returns -// true. An error that satisfies IsNotAllowed will be returned if the supplied -// function returns false. Creation of a desired object that does not currently -// exist is always allowed. -func AllowUpdateIf(fn func(current, desired *Secret) bool) ApplyOption { - return func(current, desired *Secret) error { - if fn(current, desired) { - return nil - } - return resource.NewNotAllowed(errUpdateNotAllowed) - } -} - -// IsNotFound returns whether given error is a "Not Found" error or not. -func IsNotFound(err error) bool { - if err == nil { - return false - } - return err.Error() == ErrNotFound -} diff --git a/pkg/connection/store/vault/kv/v1.go b/pkg/connection/store/vault/kv/v1.go deleted file mode 100644 index 0988ecef7..000000000 --- a/pkg/connection/store/vault/kv/v1.go +++ /dev/null @@ -1,133 +0,0 @@ -/* - Copyright 2022 The Crossplane Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package kv - -import ( - "path/filepath" - "strings" - - "github.com/hashicorp/vault/api" - - "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/crossplane/crossplane-runtime/pkg/resource" -) - -// We use this prefix to store metadata of v1 secrets as there is no dedicated -// metadata. Considering a connection key cannot contain ":" (since it is not -// in the set of allowed chars for a k8s secret key), it is safe to assume -// there is no actual connection data starting with this prefix. -const metadataPrefix = "metadata:" - -// V1Client is a Vault KV V1 Secrets Engine client. -// https://www.vaultproject.io/api-docs/secret/kv/kv-v1 -type V1Client struct { - client LogicalClient - mountPath string -} - -// NewV1Client returns a new V1Client. -func NewV1Client(logical LogicalClient, mountPath string) *V1Client { - kv := &V1Client{ - client: logical, - mountPath: mountPath, - } - - return kv -} - -// Get returns a Secret at a given path. -func (c *V1Client) Get(path string, secret *Secret) error { - s, err := c.client.Read(filepath.Join(c.mountPath, path)) - if err != nil { - return errors.Wrap(err, errRead) - } - if s == nil { - return errors.New(ErrNotFound) - } - return c.parseAsSecret(s, secret) -} - -// Apply applies given Secret at path by patching its Data and setting -// provided custom metadata. -func (c *V1Client) Apply(path string, secret *Secret, ao ...ApplyOption) error { - existing := &Secret{} - err := c.Get(path, existing) - - if resource.Ignore(IsNotFound, err) != nil { - return errors.Wrap(err, errGet) - } - if !IsNotFound(err) { - for _, o := range ao { - if err = o(existing, secret); err != nil { - return err - } - } - } - - dp, changed := payloadV1(existing, secret) - if !changed { - return nil - } - _, err = c.client.Write(filepath.Join(c.mountPath, path), dp) - return errors.Wrap(err, errWriteData) - -} - -// Delete deletes Secret at the given path. -func (c *V1Client) Delete(path string) error { - _, err := c.client.Delete(filepath.Join(c.mountPath, path)) - return errors.Wrap(err, errDelete) -} - -func (c *V1Client) parseAsSecret(s *api.Secret, kv *Secret) error { - for key, val := range s.Data { - if sVal, ok := val.(string); ok { - if strings.HasPrefix(key, metadataPrefix) { - kv.AddMetadata(strings.TrimPrefix(key, metadataPrefix), sVal) - continue - } - kv.AddData(key, sVal) - } - } - return nil -} - -func payloadV1(existing, new *Secret) (map[string]any, bool) { - payload := make(map[string]any, len(existing.Data)+len(new.Data)) - for k, v := range existing.Data { - // Only transfer existing data, metadata updates are not additive. - if !strings.HasPrefix(k, metadataPrefix) { - payload[k] = v - } - } - changed := false - for k, v := range new.Data { - if ev, ok := existing.Data[k]; !ok || ev != v { - changed = true - payload[k] = v - } - } - for k, v := range new.CustomMeta { - // kv secret engine v1 does not have metadata. So, we store them as data - // by prefixing with "metadata:" - if val, ok := existing.CustomMeta[k]; !ok && val != v { - changed = true - } - payload[metadataPrefix+k] = v - } - return payload, changed -} diff --git a/pkg/connection/store/vault/kv/v1_test.go b/pkg/connection/store/vault/kv/v1_test.go deleted file mode 100644 index 97960ac79..000000000 --- a/pkg/connection/store/vault/kv/v1_test.go +++ /dev/null @@ -1,491 +0,0 @@ -/* - Copyright 2022 The Crossplane Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package kv - -import ( - "path/filepath" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/vault/api" - - "github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv/fake" - "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/crossplane/crossplane-runtime/pkg/resource" - "github.com/crossplane/crossplane-runtime/pkg/test" -) - -func TestV1ClientGet(t *testing.T) { - type args struct { - client LogicalClient - path string - } - type want struct { - err error - out *Secret - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrorWhileGettingSecret": { - reason: "Should return a proper error if getting secret failed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - }, - want: want{ - err: errors.Wrap(errBoom, errRead), - out: NewSecret(nil, nil), - }, - }, - "SecretNotFound": { - reason: "Should return a notFound error if secret does not exist.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - // Vault logical client returns both error and secret as - // nil if secret does not exist. - return nil, nil - }, - }, - path: secretName, - }, - want: want{ - err: errors.New(ErrNotFound), - out: NewSecret(nil, nil), - }, - }, - "SuccessfulGet": { - reason: "Should successfully return secret from v1 KV engine.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return &api.Secret{ - Data: map[string]any{ - "foo": "bar", - metadataPrefix + "owner": "jdoe", - metadataPrefix + "mission_critical": "false", - }, - }, nil - }, - }, - path: secretName, - }, - want: want{ - out: NewSecret(map[string]string{ - "foo": "bar", - }, map[string]string{ - "owner": "jdoe", - "mission_critical": "false", - }), - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - k := NewV1Client(tc.args.client, mountPath) - - s := Secret{} - err := k.Get(tc.args.path, &s) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nv1Client.Get(...): -want error, +got error:\n%s", tc.reason, diff) - } - - if diff := cmp.Diff(tc.want.out, &s, cmpopts.IgnoreUnexported(Secret{})); diff != "" { - t.Errorf("\n%s\nv1Client.Get(...): -want, +got:\n%s", tc.reason, diff) - } - }) - } -} - -func TestV1ClientApply(t *testing.T) { - type args struct { - client LogicalClient - in *Secret - path string - - ao []ApplyOption - } - type want struct { - err error - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrorWhileReadingSecret": { - reason: "Should return a proper error if reading secret failed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - }, - want: want{ - err: errors.Wrap(errors.Wrap(errBoom, errRead), errGet), - }, - }, - "ErrorWhileWritingData": { - reason: "Should return a proper error if writing secret failed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "key1": "val1", - "key2": "val2", - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1updated", - "key3": "val3", - }, nil), - }, - want: want{ - err: errors.Wrap(errBoom, errWriteData), - }, - }, - "AlreadyUpToDate": { - reason: "Should not perform a write if a v1 secret is already up to date.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "foo": "bar", - metadataPrefix + "owner": "jdoe", - metadataPrefix + "mission_critical": "false", - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - return nil, errors.New("no write operation expected") - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "foo": "bar", - }, map[string]string{ - "owner": "jdoe", - "mission_critical": "false", - }), - }, - want: want{ - err: nil, - }, - }, - "SuccessfulCreate": { - reason: "Should successfully create with new data if secret does not exists.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - // Vault logical client returns both error and secret as - // nil if secret does not exist. - return nil, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "key1": "val1", - "key2": "val2", - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1", - "key2": "val2", - }, nil), - }, - want: want{ - err: nil, - }, - }, - "UpdateNotAllowed": { - reason: "Should return not allowed error if update is not allowed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "key1": "val1", - "key2": "val2", - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "key1": "val1updated", - "key2": "val2", - "key3": "val3", - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1updated", - "key3": "val3", - }, nil), - ao: []ApplyOption{ - AllowUpdateIf(func(current, desired *Secret) bool { - return false - }), - }, - }, - want: want{ - err: resource.NewNotAllowed(errUpdateNotAllowed), - }, - }, - "SuccessfulUpdate": { - reason: "Should successfully update by appending new data to existing ones.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "key1": "val1", - "key2": "val2", - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "key1": "val1updated", - "key2": "val2", - "key3": "val3", - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1updated", - "key3": "val3", - }, nil), - ao: []ApplyOption{ - AllowUpdateIf(func(current, desired *Secret) bool { - return true - }), - }, - }, - want: want{ - err: nil, - }, - }, - "SuccessfulAddMetadata": { - reason: "Should successfully add new metadata.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "key1": "val1", - "key2": "val2", - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "key1": "val1", - "key2": "val2", - metadataPrefix + "foo": "bar", - metadataPrefix + "baz": "qux", - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1", - "key2": "val2", - }, map[string]string{ - "foo": "bar", - "baz": "qux", - }), - }, - want: want{ - err: nil, - }, - }, - "SuccessfulUpdateMetadata": { - reason: "Should successfully update metadata by overriding the existing ones.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "key1": "val1", - "key2": "val2", - metadataPrefix + "old": "meta", - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "key1": "val1", - "key2": "val2", - metadataPrefix + "old": "meta", - metadataPrefix + "foo": "bar", - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1", - "key2": "val2", - }, map[string]string{ - "old": "meta", - "foo": "bar", - }), - }, - want: want{ - err: nil, - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - k := NewV1Client(tc.args.client, mountPath) - - err := k.Apply(tc.args.path, tc.args.in, tc.args.ao...) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nv1Client.Apply(...): -want error, +got error:\n%s", tc.reason, diff) - } - }) - } -} - -func TestV1ClientDelete(t *testing.T) { - type args struct { - client LogicalClient - path string - } - type want struct { - err error - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrorWhileDeletingSecret": { - reason: "Should return a proper error if deleting secret failed.", - args: args{ - client: &fake.LogicalClient{ - DeleteFn: func(path string) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - }, - want: want{ - err: errors.Wrap(errBoom, errDelete), - }, - }, - "SecretAlreadyDeleted": { - reason: "Should return success if secret already deleted.", - args: args{ - client: &fake.LogicalClient{ - DeleteFn: func(path string) (*api.Secret, error) { - // Vault logical client returns both error and secret as - // nil if secret does not exist. - return nil, nil - }, - }, - path: secretName, - }, - want: want{ - err: nil, - }, - }, - "SuccessfulDelete": { - reason: "Should return no error after successful deletion of a v1 secret.", - args: args{ - client: &fake.LogicalClient{ - DeleteFn: func(path string) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return &api.Secret{ - Data: map[string]any{ - "foo": "bar", - }, - }, nil - }, - }, - path: secretName, - }, - want: want{}, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - k := NewV1Client(tc.args.client, mountPath) - - err := k.Delete(tc.args.path) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nv1Client.Get(...): -want error, +got error:\n%s", tc.reason, diff) - } - }) - } -} diff --git a/pkg/connection/store/vault/kv/v2.go b/pkg/connection/store/vault/kv/v2.go deleted file mode 100644 index 72a38a7e3..000000000 --- a/pkg/connection/store/vault/kv/v2.go +++ /dev/null @@ -1,215 +0,0 @@ -/* - Copyright 2022 The Crossplane Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package kv - -import ( - "encoding/json" - "path/filepath" - - "github.com/hashicorp/vault/api" - - "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" - "github.com/crossplane/crossplane-runtime/pkg/resource" -) - -const ( - errWriteMetadata = "cannot write secret metadata Data" -) - -// V2Client is a Vault KV V2 Secrets Engine client. -// https://www.vaultproject.io/api/secret/kv/kv-v2 -type V2Client struct { - client LogicalClient - mountPath string -} - -// NewV2Client returns a new V2Client. -func NewV2Client(logical LogicalClient, mountPath string) *V2Client { - kv := &V2Client{ - client: logical, - mountPath: mountPath, - } - - return kv -} - -// Get returns a Secret at a given path. -func (c *V2Client) Get(path string, secret *Secret) error { - s, err := c.client.Read(c.dataPath(path)) - if err != nil { - return errors.Wrap(err, errRead) - } - if s == nil { - return errors.New(ErrNotFound) - } - return c.parseAsKVSecret(s, secret) -} - -// Apply applies given Secret at path by patching its Data and setting -// provided custom metadata. -func (c *V2Client) Apply(path string, secret *Secret, ao ...ApplyOption) error { - existing := &Secret{} - err := c.Get(path, existing) - - if resource.Ignore(IsNotFound, err) != nil { - return errors.Wrap(err, errGet) - } - if !IsNotFound(err) { - for _, o := range ao { - if err = o(existing, secret); err != nil { - return err - } - } - } - - // We write metadata first to ensure we set ownership (with the label) of - // the secret before writing any data. This is to prevent situations where - // secret create with some data but owner not set. - mp, changed := metadataPayload(existing.CustomMeta, secret.CustomMeta) - if changed { - if _, err := c.client.Write(c.metadataPath(path), mp); err != nil { - return errors.Wrap(err, errWriteMetadata) - } - } - - dp, changed := dataPayload(existing, secret) - if changed { - if _, err := c.client.Write(c.dataPath(path), dp); err != nil { - return errors.Wrap(err, errWriteData) - } - } - - return nil -} - -// Delete deletes Secret at the given path. -func (c *V2Client) Delete(path string) error { - // Note(turkenh): With V2Client, we need to delete metadata and all versions: - // https://www.vaultproject.io/api-docs/secret/kv/kv-v2#delete-metadata-and-all-versions - _, err := c.client.Delete(c.metadataPath(path)) - return errors.Wrap(err, errDelete) -} - -func dataPayload(existing, new *Secret) (map[string]any, bool) { - data := make(map[string]string, len(existing.Data)+len(new.Data)) - for k, v := range existing.Data { - data[k] = v - } - changed := false - for k, v := range new.Data { - if ev, ok := existing.Data[k]; !ok || ev != v { - changed = true - data[k] = v - } - } - ver := json.Number("0") - if existing.version != "" { - ver = existing.version - } - return map[string]any{ - "options": map[string]any{ - "cas": ver, - }, - "data": data, - }, changed -} - -func metadataPayload(existing, new map[string]string) (map[string]any, bool) { - payload := map[string]any{ - "custom_metadata": new, - } - if len(existing) != len(new) { - return payload, true - } - for k, v := range new { - if ev, ok := existing[k]; !ok || ev != v { - return payload, true - } - } - return payload, false -} - -func (c *V2Client) parseAsKVSecret(s *api.Secret, kv *Secret) error { - // Note(turkenh): kv v2 secrets contains another "data" and "metadata" - // blocks inside the top level generic "Data" field. - // https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1 - paved := fieldpath.Pave(s.Data) - if err := parseSecretData(paved, kv); err != nil { - return err - } - return parseSecretMeta(paved, kv) -} - -func parseSecretData(payload *fieldpath.Paved, kv *Secret) error { - sData := map[string]any{} - err := payload.GetValueInto("data", &sData) - if fieldpath.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - - kv.Data = make(map[string]string, len(sData)) - for key, val := range sData { - if sVal, ok := val.(string); ok { - kv.Data[key] = sVal - } - } - return nil -} - -func parseSecretMeta(payload *fieldpath.Paved, kv *Secret) error { - sMeta := map[string]any{} - err := payload.GetValueInto("metadata", &sMeta) - if fieldpath.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - - pavedMeta := fieldpath.Pave(sMeta) - if err = pavedMeta.GetValueInto("version", &kv.version); resource.Ignore(fieldpath.IsNotFound, err) != nil { - return err - } - - customMeta := map[string]any{} - err = pavedMeta.GetValueInto("custom_metadata", &customMeta) - if fieldpath.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - kv.CustomMeta = make(map[string]string, len(customMeta)) - for key, val := range customMeta { - if sVal, ok := val.(string); ok { - kv.CustomMeta[key] = sVal - } - } - return nil -} - -func (c *V2Client) dataPath(secretPath string) string { - return filepath.Join(c.mountPath, "data", secretPath) -} - -func (c *V2Client) metadataPath(secretPath string) string { - return filepath.Join(c.mountPath, "metadata", secretPath) -} diff --git a/pkg/connection/store/vault/kv/v2_test.go b/pkg/connection/store/vault/kv/v2_test.go deleted file mode 100644 index 016c88417..000000000 --- a/pkg/connection/store/vault/kv/v2_test.go +++ /dev/null @@ -1,682 +0,0 @@ -/* - Copyright 2022 The Crossplane Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package kv - -import ( - "encoding/json" - "path/filepath" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/vault/api" - - "github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv/fake" - "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/crossplane/crossplane-runtime/pkg/resource" - "github.com/crossplane/crossplane-runtime/pkg/test" -) - -const ( - mountPath = "test-secrets/" - - secretName = "conn-unittests" -) - -var ( - errBoom = errors.New("boom") -) - -func TestV2ClientGet(t *testing.T) { - type args struct { - client LogicalClient - path string - } - type want struct { - err error - out *Secret - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrorWhileGettingSecret": { - reason: "Should return a proper error if getting secret failed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - }, - want: want{ - err: errors.Wrap(errBoom, errRead), - out: NewSecret(nil, nil), - }, - }, - "SecretNotFound": { - reason: "Should return a notFound error if secret does not exist.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - // Vault logical client returns both error and secret as - // nil if secret does not exist. - return nil, nil - }, - }, - path: secretName, - }, - want: want{ - err: errors.New(ErrNotFound), - out: NewSecret(nil, nil), - }, - }, - "SuccessfulGetNoData": { - reason: "Should successfully return secret from v2 KV engine even it only contains metadata.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return &api.Secret{ - // Using sample response here: - // https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1 - Data: map[string]any{ - "metadata": map[string]any{ - "created_time": "2018-03-22T02:24:06.945319214Z", - "custom_metadata": map[string]any{ - "owner": "jdoe", - "mission_critical": "false", - }, - "deletion_time": "", - "destroyed": false, - }, - }, - }, nil - }, - }, - path: secretName, - }, - want: want{ - out: NewSecret(nil, map[string]string{ - "owner": "jdoe", - "mission_critical": "false", - }), - }, - }, - "SuccessfulGetNoMetadata": { - reason: "Should successfully return secret from v2 KV engine even it only contains data.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return &api.Secret{ - // Using sample response here: - // https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1 - Data: map[string]any{ - "data": map[string]any{ - "foo": "bar", - }, - }, - }, nil - }, - }, - path: secretName, - }, - want: want{ - out: NewSecret(map[string]string{ - "foo": "bar", - }, nil), - }, - }, - "SuccessfulGet": { - reason: "Should successfully return secret from v2 KV engine.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return &api.Secret{ - // Using sample response here: - // https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1 - Data: map[string]any{ - "data": map[string]any{ - "foo": "bar", - }, - "metadata": map[string]any{ - "created_time": "2018-03-22T02:24:06.945319214Z", - "custom_metadata": map[string]any{ - "owner": "jdoe", - "mission_critical": "false", - }, - "deletion_time": "", - "destroyed": false, - "version": 2, - }, - }, - }, nil - }, - }, - path: secretName, - }, - want: want{ - out: NewSecret(map[string]string{ - "foo": "bar", - }, map[string]string{ - "owner": "jdoe", - "mission_critical": "false", - }), - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - k := NewV2Client(tc.args.client, mountPath) - - s := Secret{} - err := k.Get(tc.args.path, &s) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nv2Client.Get(...): -want error, +got error:\n%s", tc.reason, diff) - } - - if diff := cmp.Diff(tc.want.out, &s, cmpopts.IgnoreUnexported(Secret{})); diff != "" { - t.Errorf("\n%s\nv2Client.Get(...): -want, +got:\n%s", tc.reason, diff) - } - }) - } -} - -func TestV2ClientApply(t *testing.T) { - type args struct { - client LogicalClient - in *Secret - path string - - ao []ApplyOption - } - type want struct { - err error - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrorWhileReadingSecret": { - reason: "Should return a proper error if reading secret failed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - }, - want: want{ - err: errors.Wrap(errors.Wrap(errBoom, errRead), errGet), - }, - }, - "ErrorWhileWritingData": { - reason: "Should return a proper error if writing secret failed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "data": map[string]string{ - "key1": "val1", - "key2": "val2", - }, - "metadata": map[string]any{ - "custom_metadata": map[string]any{ - "foo": "bar", - "baz": "qux", - }, - "version": json.Number("2"), - }, - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1updated", - "key3": "val3", - }, map[string]string{ - "foo": "bar", - "baz": "qux", - }), - }, - want: want{ - err: errors.Wrap(errBoom, errWriteData), - }, - }, - "ErrorWhileWritingMetadata": { - reason: "Should return a proper error if writing secret metadata failed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "data": map[string]string{ - "key1": "val1", - "key2": "val2", - }, - "metadata": map[string]any{ - "custom_metadata": map[string]any{ - "foo": "bar", - }, - "version": json.Number("2"), - }, - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1", - "key2": "val2", - }, map[string]string{ - "foo": "baz", - }), - }, - want: want{ - err: errors.Wrap(errBoom, errWriteMetadata), - }, - }, - "AlreadyUpToDate": { - reason: "Should not perform a write if a v2 secret is already up to date.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - // Using sample response here: - // https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1 - Data: map[string]any{ - "data": map[string]any{ - "foo": "bar", - }, - "metadata": map[string]any{ - "created_time": "2018-03-22T02:24:06.945319214Z", - "custom_metadata": map[string]any{ - "owner": "jdoe", - "mission_critical": "false", - }, - "deletion_time": "", - "destroyed": false, - "version": 2, - }, - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - return nil, errors.New("no write operation expected") - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "foo": "bar", - }, map[string]string{ - "owner": "jdoe", - "mission_critical": "false", - }), - }, - want: want{ - err: nil, - }, - }, - "SuccessfulCreate": { - reason: "Should successfully create with new data if secret does not exists.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - // Vault logical client returns both error and secret as - // nil if secret does not exist. - return nil, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "data": map[string]string{ - "key1": "val1", - "key2": "val2", - }, - "options": map[string]any{ - "cas": json.Number("0"), - }, - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1", - "key2": "val2", - }, nil), - }, - want: want{ - err: nil, - }, - }, - "UpdateNotAllowed": { - reason: "Should return not allowed error if update is not allowed.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "data": map[string]any{ - "key1": "val1", - "key2": "val2", - }, - "metadata": map[string]any{ - "custom_metadata": map[string]any{ - "foo": "bar", - "baz": "qux", - }, - "version": json.Number("2"), - }, - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "data": map[string]string{ - "key1": "val1updated", - "key2": "val2", - "key3": "val3", - }, - "options": map[string]any{ - "cas": json.Number("2"), - }, - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1updated", - "key3": "val3", - }, map[string]string{ - "foo": "bar", - "baz": "qux", - }), - ao: []ApplyOption{ - AllowUpdateIf(func(current, desired *Secret) bool { - return false - }), - }, - }, - want: want{ - err: resource.NewNotAllowed(errUpdateNotAllowed), - }, - }, - "SuccessfulUpdateData": { - reason: "Should successfully update by appending new data to existing ones.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "data": map[string]any{ - "key1": "val1", - "key2": "val2", - }, - "metadata": map[string]any{ - "custom_metadata": map[string]any{ - "foo": "bar", - "baz": "qux", - }, - "version": json.Number("2"), - }, - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "data": map[string]string{ - "key1": "val1updated", - "key2": "val2", - "key3": "val3", - }, - "options": map[string]any{ - "cas": json.Number("2"), - }, - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1updated", - "key3": "val3", - }, map[string]string{ - "foo": "bar", - "baz": "qux", - }), - ao: []ApplyOption{ - AllowUpdateIf(func(current, desired *Secret) bool { - return true - }), - }, - }, - want: want{ - err: nil, - }, - }, - "SuccessfulAddMetadata": { - reason: "Should successfully add new metadata.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "data": map[string]any{ - "key1": "val1", - "key2": "val2", - }, - "metadata": map[string]any{ - "version": json.Number("2"), - }, - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "metadata", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "custom_metadata": map[string]string{ - "foo": "bar", - "baz": "qux", - }, - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1", - "key2": "val2", - }, map[string]string{ - "foo": "bar", - "baz": "qux", - }), - }, - want: want{ - err: nil, - }, - }, - "SuccessfulUpdateMetadata": { - reason: "Should successfully update metadata by overriding the existing ones.", - args: args{ - client: &fake.LogicalClient{ - ReadFn: func(path string) (*api.Secret, error) { - return &api.Secret{ - Data: map[string]any{ - "data": map[string]any{ - "key1": "val1", - "key2": "val2", - }, - "metadata": map[string]any{ - "custom_metadata": map[string]any{ - "old": "meta", - }, - "version": json.Number("2"), - }, - }, - }, nil - }, - WriteFn: func(path string, data map[string]any) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "metadata", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]any{ - "custom_metadata": map[string]string{ - "foo": "bar", - "baz": "qux", - }, - }, data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - in: NewSecret(map[string]string{ - "key1": "val1", - "key2": "val2", - }, map[string]string{ - "foo": "bar", - "baz": "qux", - }), - }, - want: want{ - err: nil, - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - k := NewV2Client(tc.args.client, mountPath) - - err := k.Apply(tc.args.path, tc.args.in, tc.args.ao...) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nv2Client.Apply(...): -want error, +got error:\n%s", tc.reason, diff) - } - }) - } -} - -func TestV2ClientDelete(t *testing.T) { - type args struct { - client LogicalClient - path string - } - type want struct { - err error - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrorWhileDeletingSecret": { - reason: "Should return a proper error if deleting secret failed.", - args: args{ - client: &fake.LogicalClient{ - DeleteFn: func(path string) (*api.Secret, error) { - return nil, errBoom - }, - }, - path: secretName, - }, - want: want{ - err: errors.Wrap(errBoom, errDelete), - }, - }, - "SecretAlreadyDeleted": { - reason: "Should return success if secret already deleted.", - args: args{ - client: &fake.LogicalClient{ - DeleteFn: func(path string) (*api.Secret, error) { - // Vault logical client returns both error and secret as - // nil if secret does not exist. - return nil, nil - }, - }, - path: secretName, - }, - want: want{ - err: nil, - }, - }, - "SuccessfulDelete": { - reason: "Should return no error after successful deletion of a v2 secret.", - args: args{ - client: &fake.LogicalClient{ - DeleteFn: func(path string) (*api.Secret, error) { - if diff := cmp.Diff(filepath.Join(mountPath, "metadata", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil, nil - }, - }, - path: secretName, - }, - want: want{}, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - k := NewV2Client(tc.args.client, mountPath) - - err := k.Delete(tc.args.path) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nv2Client.Get(...): -want error, +got error:\n%s", tc.reason, diff) - } - }) - } -} diff --git a/pkg/connection/store/vault/store.go b/pkg/connection/store/vault/store.go deleted file mode 100644 index 16ca5a3f0..000000000 --- a/pkg/connection/store/vault/store.go +++ /dev/null @@ -1,249 +0,0 @@ -/* -Copyright 2022 The Crossplane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package vault implements a secret store backed by HashiCorp Vault. -package vault - -import ( - "context" - "crypto/tls" - "crypto/x509" - "net/http" - "path/filepath" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/vault/api" - "sigs.k8s.io/controller-runtime/pkg/client" - - v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/connection/store" - "github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv" - "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/crossplane/crossplane-runtime/pkg/resource" -) - -// Error strings. -const ( - errNoConfig = "no Vault config provided" - errNewClient = "cannot create new client" - errExtractCABundle = "cannot extract ca bundle" - errAppendCABundle = "cannot append ca bundle" - errExtractToken = "cannot extract token" - errNoTokenProvided = "token auth configured but no token provided" - - errGet = "cannot get secret" - errApply = "cannot apply secret" - errDelete = "cannot delete secret" -) - -// KVClient is a Vault AdditiveKVClient Secrets engine client that supports both v1 and v2. -type KVClient interface { - Get(path string, secret *kv.Secret) error - Apply(path string, secret *kv.Secret, ao ...kv.ApplyOption) error - Delete(path string) error -} - -// SecretStore is a Vault Secret Store. -type SecretStore struct { - client KVClient - - defaultParentPath string -} - -// NewSecretStore returns a new Vault SecretStore. -func NewSecretStore(ctx context.Context, kube client.Client, _ *tls.Config, cfg v1.SecretStoreConfig) (*SecretStore, error) { //nolint: gocyclo // See note below. - // NOTE(turkenh): Adding linter exception for gocyclo since this function - // went a little over the limit due to the switch statements not because of - // some complex logic. - if cfg.Vault == nil { - return nil, errors.New(errNoConfig) - } - vCfg := api.DefaultConfig() - vCfg.Address = cfg.Vault.Server - - if cfg.Vault.CABundle != nil { - ca, err := resource.CommonCredentialExtractor(ctx, cfg.Vault.CABundle.Source, kube, cfg.Vault.CABundle.CommonCredentialSelectors) - if err != nil { - return nil, errors.Wrap(err, errExtractCABundle) - } - pool := x509.NewCertPool() - if ok := pool.AppendCertsFromPEM(ca); !ok { - return nil, errors.Wrap(err, errAppendCABundle) - } - vCfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = pool - } - - c, err := api.NewClient(vCfg) - if err != nil { - return nil, errors.Wrap(err, errNewClient) - } - - switch cfg.Vault.Auth.Method { - case v1.VaultAuthToken: - if cfg.Vault.Auth.Token == nil { - return nil, errors.New(errNoTokenProvided) - } - t, err := resource.CommonCredentialExtractor(ctx, cfg.Vault.Auth.Token.Source, kube, cfg.Vault.Auth.Token.CommonCredentialSelectors) - if err != nil { - return nil, errors.Wrap(err, errExtractToken) - } - c.SetToken(string(t)) - default: - return nil, errors.Errorf("%q is not supported as an auth method", cfg.Vault.Auth.Method) - } - - var kvClient KVClient - switch *cfg.Vault.Version { - case v1.VaultKVVersionV1: - kvClient = kv.NewV1Client(c.Logical(), cfg.Vault.MountPath) - case v1.VaultKVVersionV2: - kvClient = kv.NewV2Client(c.Logical(), cfg.Vault.MountPath) - } - - return &SecretStore{ - client: kvClient, - defaultParentPath: cfg.DefaultScope, - }, nil -} - -// ReadKeyValues reads and returns key value pairs for a given Vault Secret. -func (ss *SecretStore) ReadKeyValues(_ context.Context, n store.ScopedName, s *store.Secret) error { - kvs := &kv.Secret{} - if err := ss.client.Get(ss.path(n), kvs); resource.Ignore(kv.IsNotFound, err) != nil { - return errors.Wrap(err, errGet) - } - - s.ScopedName = n - s.Data = keyValuesFromData(kvs.Data) - if len(kvs.CustomMeta) > 0 { - s.Metadata = &v1.ConnectionSecretMetadata{ - Labels: kvs.CustomMeta, - } - } - return nil -} - -// WriteKeyValues writes key value pairs to a given Vault Secret. -func (ss *SecretStore) WriteKeyValues(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (changed bool, err error) { - ao := applyOptions(ctx, wo...) - ao = append(ao, kv.AllowUpdateIf(func(current, desired *kv.Secret) bool { - return !cmp.Equal(current, desired, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(kv.Secret{})) - })) - - err = ss.client.Apply(ss.path(s.ScopedName), kv.NewSecret(dataFromKeyValues(s.Data), s.GetLabels()), ao...) - if resource.IsNotAllowed(err) { - // The update was not allowed because it was a no-op. - return false, nil - } - if err != nil { - return false, errors.Wrap(err, errApply) - } - return true, nil -} - -// DeleteKeyValues delete key value pairs from a given Vault Secret. -// If no kv specified, the whole secret instance is deleted. -// If kv specified, those would be deleted and secret instance will be deleted -// only if there is no Data left. -func (ss *SecretStore) DeleteKeyValues(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error { - Secret := &kv.Secret{} - err := ss.client.Get(ss.path(s.ScopedName), Secret) - if kv.IsNotFound(err) { - // Secret already deleted, nothing to do. - return nil - } - if err != nil { - return errors.Wrap(err, errGet) - } - - for _, o := range do { - if err = o(ctx, s); err != nil { - return err - } - } - - for k := range s.Data { - delete(Secret.Data, k) - } - if len(s.Data) == 0 || len(Secret.Data) == 0 { - // Secret is deleted only if: - // - No kv to delete specified as input - // - No data left in the secret - return errors.Wrap(ss.client.Delete(ss.path(s.ScopedName)), errDelete) - } - // If there are still keys left, update the secret with the remaining. - return errors.Wrap(ss.client.Apply(ss.path(s.ScopedName), Secret), errApply) -} - -func (ss *SecretStore) path(s store.ScopedName) string { - if s.Scope != "" { - return filepath.Join(s.Scope, s.Name) - } - return filepath.Join(ss.defaultParentPath, s.Name) -} - -func applyOptions(ctx context.Context, wo ...store.WriteOption) []kv.ApplyOption { - ao := make([]kv.ApplyOption, len(wo)) - for i := range wo { - o := wo[i] - ao[i] = func(current, desired *kv.Secret) error { - cs := &store.Secret{ - Metadata: &v1.ConnectionSecretMetadata{ - Labels: current.CustomMeta, - }, - Data: keyValuesFromData(current.Data), - } - ds := &store.Secret{ - Metadata: &v1.ConnectionSecretMetadata{ - Labels: desired.CustomMeta, - }, - Data: keyValuesFromData(desired.Data), - } - if err := o(ctx, cs, ds); err != nil { - return err - } - desired.CustomMeta = ds.GetLabels() - desired.Data = dataFromKeyValues(ds.Data) - return nil - } - } - return ao -} - -func keyValuesFromData(data map[string]string) store.KeyValues { - if len(data) == 0 { - return nil - } - kv := make(store.KeyValues, len(data)) - for k, v := range data { - kv[k] = []byte(v) - } - return kv -} - -func dataFromKeyValues(kv store.KeyValues) map[string]string { - if len(kv) == 0 { - return nil - } - data := make(map[string]string, len(kv)) - for k, v := range kv { - // NOTE(turkenh): vault stores values as strings. So we convert []byte - // to string before writing to Vault. - data[k] = string(v) - } - return data -} diff --git a/pkg/connection/store/vault/store_test.go b/pkg/connection/store/vault/store_test.go deleted file mode 100644 index f506d14cb..000000000 --- a/pkg/connection/store/vault/store_test.go +++ /dev/null @@ -1,827 +0,0 @@ -/* - Copyright 2022 The Crossplane Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package vault - -import ( - "context" - "path/filepath" - "testing" - - "github.com/google/go-cmp/cmp" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" - - v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/connection/store" - "github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/fake" - "github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv" - "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/crossplane/crossplane-runtime/pkg/test" -) - -const ( - parentPathDefault = "crossplane-system" - - secretName = "conn-unittests" -) - -var ( - errBoom = errors.New("boom") -) - -func TestSecretStoreReadKeyValues(t *testing.T) { - type args struct { - client KVClient - defaultParentPath string - name store.ScopedName - } - type want struct { - out *store.Secret - err error - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrorWhileGetting": { - reason: "Should return a proper error if secret cannot be obtained", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - return errBoom - }, - }, - defaultParentPath: parentPathDefault, - name: store.ScopedName{ - Name: secretName, - }, - }, - want: want{ - out: &store.Secret{}, - err: errors.Wrap(errBoom, errGet), - }, - }, - "SuccessfulGetWithDefaultScope": { - reason: "Should return key values from a secret with default scope", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - if diff := cmp.Diff(filepath.Join(parentPathDefault, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - secret.Data = map[string]string{ - "key1": "val1", - "key2": "val2", - } - return nil - }, - }, - defaultParentPath: parentPathDefault, - name: store.ScopedName{ - Name: secretName, - }, - }, - want: want{ - out: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - err: nil, - }, - }, - "SuccessfulGetWithCustomScope": { - reason: "Should return key values from a secret with custom scope", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - if diff := cmp.Diff(filepath.Join("another-scope", secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - secret.Data = map[string]string{ - "key1": "val1", - "key2": "val2", - } - return nil - }, - }, - defaultParentPath: parentPathDefault, - name: store.ScopedName{ - Name: secretName, - Scope: "another-scope", - }, - }, - want: want{ - out: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - Scope: "another-scope", - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - err: nil, - }, - }, - "SuccessfulGetWithMetadata": { - reason: "Should return both data and metadata.", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - if diff := cmp.Diff(filepath.Join(parentPathDefault, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - secret.Data = map[string]string{ - "key1": "val1", - "key2": "val2", - } - secret.CustomMeta = map[string]string{ - "foo": "bar", - } - return nil - }, - }, - defaultParentPath: parentPathDefault, - name: store.ScopedName{ - Name: secretName, - }, - }, - want: want{ - out: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - Metadata: &v1.ConnectionSecretMetadata{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - err: nil, - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - ss := &SecretStore{ - client: tc.args.client, - defaultParentPath: tc.args.defaultParentPath, - } - s := &store.Secret{} - err := ss.ReadKeyValues(context.Background(), tc.args.name, s) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nss.ReadKeyValues(...): -want error, +got error:\n%s", tc.reason, diff) - } - - if diff := cmp.Diff(tc.want.out, s); diff != "" { - t.Errorf("\n%s\nss.ReadKeyValues(...): -want, +got:\n%s", tc.reason, diff) - } - }) - } -} - -func TestSecretStoreWriteKeyValues(t *testing.T) { - type args struct { - client KVClient - defaultParentPath string - secret *store.Secret - - wo []store.WriteOption - } - type want struct { - changed bool - err error - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrWhileApplying": { - reason: "Should successfully write key values", - args: args{ - client: &fake.KVClient{ - ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - return errBoom - }, - }, - defaultParentPath: parentPathDefault, - - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - }, - want: want{ - err: errors.Wrap(errBoom, errApply), - }, - }, - "FailedWriteOption": { - reason: "Should return a proper error if supplied write option fails", - args: args{ - client: &fake.KVClient{ - ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - for _, o := range ao { - if err := o(&kv.Secret{}, secret); err != nil { - return err - } - } - return nil - }, - }, - defaultParentPath: parentPathDefault, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - wo: []store.WriteOption{ - func(ctx context.Context, current, desired *store.Secret) error { - return errBoom - }, - }, - }, - want: want{ - changed: false, - err: errors.Wrap(errBoom, errApply), - }, - }, - "SuccessfulWriteOption": { - reason: "Should return a no error if supplied write option succeeds", - args: args{ - client: &fake.KVClient{ - ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - for _, o := range ao { - if err := o(&kv.Secret{ - Data: map[string]string{ - "key1": "val1", - "key2": "val2", - }, - CustomMeta: map[string]string{ - "foo": "bar", - }, - }, secret); err != nil { - return err - } - } - return nil - }, - }, - defaultParentPath: parentPathDefault, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - wo: []store.WriteOption{ - func(ctx context.Context, current, desired *store.Secret) error { - desired.Data["customkey"] = []byte("customval") - desired.Metadata = &v1.ConnectionSecretMetadata{ - Labels: map[string]string{ - "foo": "baz", - }, - } - return nil - }, - }, - }, - want: want{ - changed: true, - }, - }, - "AlreadyUpToDate": { - reason: "Should return no error and changed as false if secret is already up to date", - args: args{ - client: &fake.KVClient{ - ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - for _, o := range ao { - if err := o(&kv.Secret{ - Data: map[string]string{ - "key1": "val1", - "key2": "val2", - }, - }, secret); err != nil { - return err - } - } - return nil - }, - }, - defaultParentPath: parentPathDefault, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - }, - want: want{ - changed: false, - err: nil, - }, - }, - "SuccessfulWrite": { - reason: "Should successfully write key values", - args: args{ - client: &fake.KVClient{ - ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - if diff := cmp.Diff(filepath.Join(parentPathDefault, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]string{ - "key1": "val1", - "key2": "val2", - }, secret.Data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil - }, - }, - defaultParentPath: parentPathDefault, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - }, - want: want{ - changed: true, - }, - }, - "SuccessfulWriteWithMetadata": { - reason: "Should successfully write key values", - args: args{ - client: &fake.KVClient{ - ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - if diff := cmp.Diff(filepath.Join(parentPathDefault, secretName), path); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]string{ - "key1": "val1", - "key2": "val2", - }, secret.Data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - if diff := cmp.Diff(map[string]string{ - "foo": "bar", - }, secret.CustomMeta); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil - }, - }, - defaultParentPath: parentPathDefault, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Metadata: &v1.ConnectionSecretMetadata{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Data: store.KeyValues{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - }, - want: want{ - changed: true, - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - ss := &SecretStore{ - client: tc.args.client, - defaultParentPath: tc.args.defaultParentPath, - } - changed, err := ss.WriteKeyValues(context.Background(), tc.args.secret, tc.args.wo...) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nss.WriteKeyValues(...): -want error, +got error:\n%s", tc.reason, diff) - } - if diff := cmp.Diff(tc.want.changed, changed); diff != "" { - t.Errorf("\n%s\nss.WriteKeyValues(...): -want changed, +got changed:\n%s", tc.reason, diff) - } - }) - } -} - -func TestSecretStoreDeleteKeyValues(t *testing.T) { - type args struct { - client KVClient - defaultParentPath string - secret *store.Secret - - do []store.DeleteOption - } - type want struct { - err error - } - cases := map[string]struct { - reason string - args - want - }{ - "ErrorGettingSecret": { - reason: "Should return a proper error if getting secret fails.", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - return errBoom - }, - }, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - }, - }, - want: want{ - err: errors.Wrap(errBoom, errGet), - }, - }, - "AlreadyDeleted": { - reason: "Should return no error if connection secret already deleted.", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - return errors.New(kv.ErrNotFound) - }, - }, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - }, - }, - want: want{ - err: nil, - }, - }, - "DeletesSecretIfNoKVProvided": { - reason: "Should delete whole secret if no kv provided as input", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - secret.Data = map[string]string{ - "key1": "val1", - "key2": "val2", - "key3": "val3", - } - return nil - }, - DeleteFn: func(path string) error { - return nil - }, - }, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - }, - }, - want: want{ - err: nil, - }, - }, - "ErrorUpdatingSecretWithRemaining": { - reason: "Should return a proper error if updating secret with remaining keys fails.", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - secret.Data = map[string]string{ - "key1": "val1", - "key2": "val2", - "key3": "val3", - } - return nil - }, - ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - return errBoom - }, - DeleteFn: func(path string) error { - return errors.New("unexpected delete call") - }, - }, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: map[string][]byte{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - }, - want: want{ - err: errors.Wrap(errBoom, errApply), - }, - }, - "UpdatesSecretByRemovingProvidedKeys": { - reason: "Should only delete provided keys and should not delete secret if kv provided as input.", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - secret.Data = map[string]string{ - "key1": "val1", - "key2": "val2", - "key3": "val3", - } - return nil - }, - ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error { - if diff := cmp.Diff(map[string]string{ - "key3": "val3", - }, secret.Data); diff != "" { - t.Errorf("r: -want, +got:\n%s", diff) - } - return nil - }, - DeleteFn: func(path string) error { - return errors.New("unexpected delete call") - }, - }, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: map[string][]byte{ - "key1": []byte("val1"), - "key2": []byte("val2"), - }, - }, - }, - want: want{ - err: nil, - }, - }, - "ErrorDeletingSecret": { - reason: "Should return a proper error if deleting the secret after no keys left fails.", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - secret.Data = map[string]string{ - "key1": "val1", - "key2": "val2", - "key3": "val3", - } - return nil - }, - DeleteFn: func(path string) error { - return errBoom - }, - }, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: map[string][]byte{ - "key1": []byte("val1"), - "key2": []byte("val2"), - "key3": []byte("val3"), - }, - }, - }, - want: want{ - err: errors.Wrap(errBoom, errDelete), - }, - }, - "FailedDeleteOption": { - reason: "Should return a proper error if provided delete option fails.", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - secret.Data = map[string]string{ - "key1": "val1", - } - return nil - }, - DeleteFn: func(path string) error { - return nil - }, - }, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - }, - do: []store.DeleteOption{ - func(ctx context.Context, secret *store.Secret) error { - return errBoom - }, - }, - }, - want: want{ - err: errBoom, - }, - }, - "SuccessfulDeleteNoKeysLeft": { - reason: "Should delete the secret if no keys left.", - args: args{ - client: &fake.KVClient{ - GetFn: func(path string, secret *kv.Secret) error { - secret.Data = map[string]string{ - "key1": "val1", - "key2": "val2", - "key3": "val3", - } - return nil - }, - DeleteFn: func(path string) error { - return nil - }, - }, - secret: &store.Secret{ - ScopedName: store.ScopedName{ - Name: secretName, - }, - Data: map[string][]byte{ - "key1": []byte("val1"), - "key2": []byte("val2"), - "key3": []byte("val3"), - }, - }, - do: []store.DeleteOption{ - func(ctx context.Context, secret *store.Secret) error { - return nil - }, - }, - }, - want: want{ - err: nil, - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - ss := &SecretStore{ - client: tc.args.client, - defaultParentPath: tc.args.defaultParentPath, - } - err := ss.DeleteKeyValues(context.Background(), tc.args.secret, tc.args.do...) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nss.ReadKeyValues(...): -want error, +got error:\n%s", tc.reason, diff) - } - }) - } -} - -func TestNewSecretStore(t *testing.T) { - kvv2 := v1.VaultKVVersionV2 - type args struct { - kube client.Client - cfg v1.SecretStoreConfig - } - type want struct { - err error - } - cases := map[string]struct { - reason string - args - want - }{ - "InvalidAuthConfig": { - reason: "Should return a proper error if vault auth configuration is not valid.", - args: args{ - cfg: v1.SecretStoreConfig{ - Vault: &v1.VaultSecretStoreConfig{ - Auth: v1.VaultAuthConfig{ - Method: v1.VaultAuthToken, - Token: nil, - }, - }, - }, - }, - want: want{ - err: errors.New(errNoTokenProvided), - }, - }, - "NoTokenSecret": { - reason: "Should return a proper error if configured vault token secret does not exist.", - args: args{ - kube: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { - return kerrors.NewNotFound(schema.GroupResource{}, "vault-token") - }), - }, - cfg: v1.SecretStoreConfig{ - Vault: &v1.VaultSecretStoreConfig{ - Auth: v1.VaultAuthConfig{ - Method: v1.VaultAuthToken, - Token: &v1.VaultAuthTokenConfig{ - Source: v1.CredentialsSourceSecret, - CommonCredentialSelectors: v1.CommonCredentialSelectors{ - SecretRef: &v1.SecretKeySelector{ - SecretReference: v1.SecretReference{ - Name: "vault-token", - Namespace: "crossplane-system", - }, - Key: "token", - }, - }, - }, - }, - }, - }, - }, - want: want{ - err: errors.Wrap(errors.Wrap(kerrors.NewNotFound(schema.GroupResource{}, "vault-token"), "cannot get credentials secret"), errExtractToken), - }, - }, - "SuccessfulStore": { - reason: "Should return no error after building store successfully.", - args: args{ - kube: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { - *obj.(*corev1.Secret) = corev1.Secret{ - Data: map[string][]byte{ - "token": []byte("t0ps3cr3t"), - }, - } - return nil - }), - }, - cfg: v1.SecretStoreConfig{ - Vault: &v1.VaultSecretStoreConfig{ - Version: &kvv2, - Auth: v1.VaultAuthConfig{ - Method: v1.VaultAuthToken, - Token: &v1.VaultAuthTokenConfig{ - Source: v1.CredentialsSourceSecret, - CommonCredentialSelectors: v1.CommonCredentialSelectors{ - SecretRef: &v1.SecretKeySelector{ - SecretReference: v1.SecretReference{ - Name: "vault-token", - Namespace: "crossplane-system", - }, - Key: "token", - }, - }, - }, - }, - }, - }, - }, - want: want{ - err: nil, - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - _, err := NewSecretStore(context.Background(), tc.args.kube, nil, tc.args.cfg) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nNewSecretStore(...): -want error, +got error:\n%s", tc.reason, diff) - } - }) - } -} diff --git a/pkg/connection/stores.go b/pkg/connection/stores.go index 416b47014..924d800f3 100644 --- a/pkg/connection/stores.go +++ b/pkg/connection/stores.go @@ -25,7 +25,6 @@ import ( v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/connection/store/kubernetes" "github.com/crossplane/crossplane-runtime/pkg/connection/store/plugin" - "github.com/crossplane/crossplane-runtime/pkg/connection/store/vault" "github.com/crossplane/crossplane-runtime/pkg/errors" ) @@ -41,8 +40,6 @@ func RuntimeStoreBuilder(ctx context.Context, local client.Client, tcfg *tls.Con switch *cfg.Type { case v1.SecretStoreKubernetes: return kubernetes.NewSecretStore(ctx, local, nil, cfg) - case v1.SecretStoreVault: - return vault.NewSecretStore(ctx, local, nil, cfg) case v1.SecretStorePlugin: return plugin.NewSecretStore(ctx, local, tcfg, cfg) }