Merge pull request #513 from turkenh/remove-in-tree-vault
Remove in-tree Vault implementation
This commit is contained in:
commit
4f3cb3d9fd
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
14
go.mod
14
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
|
||||
|
|
|
|||
49
go.sum
49
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=
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue