Merge pull request #513 from turkenh/remove-in-tree-vault

Remove in-tree Vault implementation
This commit is contained in:
Hasan Turken 2023-08-15 09:06:07 +03:00 committed by GitHub
commit 4f3cb3d9fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 0 additions and 3024 deletions

View File

@ -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"`
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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)
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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)
}