1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

Add PushSecret UpdatePolicy (to replace PR #3100) (#3117)

* Add PushSecret UpdatePolicy

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>

* Adjust description of UpdatePolicy in PushSecret Spec

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>

* Restructure PushSecret Status

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>

* Refactor PushSecret controller method

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>

* Add missing methods for new providers

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>

* Add missing method to onboardbase client

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>

* Add docs on PushSecret UpdatePolicy

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>

* Use constant for error message

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>

---------

Signed-off-by: Carolin Dohmen <carodohmen@gmail.com>
This commit is contained in:
Carolin Dohmen 2024-03-08 11:17:31 +01:00 committed by GitHub
parent de78ea175f
commit 29e5f71d8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 734 additions and 109 deletions

View file

@ -41,6 +41,14 @@ type PushSecretStoreRef struct {
Kind string `json:"kind,omitempty"`
}
// +kubebuilder:validation:Enum=Replace;IfNotExists
type PushSecretUpdatePolicy string
const (
PushSecretUpdatePolicyReplace PushSecretUpdatePolicy = "Replace"
PushSecretUpdatePolicyIfNotExists PushSecretUpdatePolicy = "IfNotExists"
)
// +kubebuilder:validation:Enum=Delete;None
type PushSecretDeletionPolicy string
@ -54,6 +62,10 @@ type PushSecretSpec struct {
// The Interval to which External Secrets will try to push a secret definition
RefreshInterval *metav1.Duration `json:"refreshInterval,omitempty"`
SecretStoreRefs []PushSecretStoreRef `json:"secretStoreRefs"`
// UpdatePolicy to handle Secrets in the provider. Possible Values: "Replace/IfNotExists". Defaults to "Replace".
// +kubebuilder:default="Replace"
// +optional
UpdatePolicy PushSecretUpdatePolicy `json:"updatePolicy,omitempty"`
// Deletion Policy to handle Secrets in the provider. Possible Values: "Delete/None". Defaults to "None".
// +kubebuilder:default="None"
// +optional
@ -148,6 +160,7 @@ type PushSecretStatusCondition struct {
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}
type SyncedPushSecretsMap map[string]map[string]PushSecretData
// PushSecretStatus indicates the history of the status of PushSecret.
@ -159,7 +172,8 @@ type PushSecretStatus struct {
// SyncedResourceVersion keeps track of the last synced version.
SyncedResourceVersion string `json:"syncedResourceVersion,omitempty"`
// Synced Push Secrets for later deletion. Matches Secret Stores to PushSecretData that was stored to that secretStore.
// Synced PushSecrets, including secrets that already exist in provider.
// Matches secret stores to PushSecretData that was stored to that secret store.
// +optional
SyncedPushSecrets SyncedPushSecretsMap `json:"syncedPushSecrets,omitempty"`
// +optional

View file

@ -79,6 +79,9 @@ type SecretsClient interface {
// DeleteSecret will delete the secret from a provider
DeleteSecret(ctx context.Context, remoteRef PushSecretRemoteRef) error
// SecretExists checks if a secret is already present in the provider at the given location.
SecretExists(ctx context.Context, remoteRef PushSecretRemoteRef) (bool, error)
// Validate checks if the client is configured correctly
// and is able to retrieve secrets from the provider.
// If the validation result is unknown it will be ignored.

View file

@ -47,6 +47,11 @@ func (p *PP) DeleteSecret(_ context.Context, _ PushSecretRemoteRef) error {
return nil
}
// Exists checks if a secret is already present in the provider at the given location.
func (p *PP) SecretExists(_ context.Context, _ PushSecretRemoteRef) (bool, error) {
return false, nil
}
// GetSecret returns a single secret from the provider.
func (p *PP) GetSecret(_ context.Context, _ ExternalSecretDataRemoteRef) ([]byte, error) {
return []byte("NOOP"), nil

View file

@ -266,6 +266,14 @@ spec:
type:
type: string
type: object
updatePolicy:
default: Replace
description: 'UpdatePolicy to handle Secrets in the provider. Possible
Values: "Replace/IfNotExists". Defaults to "Replace".'
enum:
- Replace
- IfNotExists
type: string
required:
- secretStoreRefs
- selector
@ -339,8 +347,9 @@ spec:
- match
type: object
type: object
description: Synced Push Secrets for later deletion. Matches Secret
Stores to PushSecretData that was stored to that secretStore.
description: |-
Synced PushSecrets, including secrets that already exist in provider.
Matches secret stores to PushSecretData that was stored to that secret store.
type: object
syncedResourceVersion:
description: SyncedResourceVersion keeps track of the last synced

View file

@ -5668,6 +5668,13 @@ spec:
type:
type: string
type: object
updatePolicy:
default: Replace
description: 'UpdatePolicy to handle Secrets in the provider. Possible Values: "Replace/IfNotExists". Defaults to "Replace".'
enum:
- Replace
- IfNotExists
type: string
required:
- secretStoreRefs
- selector
@ -5737,7 +5744,9 @@ spec:
- match
type: object
type: object
description: Synced Push Secrets for later deletion. Matches Secret Stores to PushSecretData that was stored to that secretStore.
description: |-
Synced PushSecrets, including secrets that already exist in provider.
Matches secret stores to PushSecretData that was stored to that secret store.
type: object
syncedResourceVersion:
description: SyncedResourceVersion keeps track of the last synced version.

View file

@ -1,9 +1,10 @@
Contrary to what `ExternalSecret` does by pulling secrets from secret providers and creating `kind=Secret` in your cluster, `PushSecret` reads a local `kind=Secret` and pushes its content to a secret provider.
If there's already a secret in the secrets provided with the intended name of the secret to be created by the `PushSecret` you'll see the `PushSecret` in Error state, and when described you'll see a message saying `secret not managed by external-secrets`.
The update behavior of `PushSecret` is controlled by `spec.updatePolicy`. The default policy is `Replace`, such that secrets are overwritten in the provider, regardless of whether there already is a secret present in the provider at the given location. If you do not want `PushSecret` to overwrite existing secrets in the provider, you can set `spec.UpdatePolicy` to `IfNotExists`. With this policy, the provider becomes the source of truth. Please note that with using `spec.updatePolicy=IfNotExists` it is possible that the secret value referenced by the `PushSecret` within the cluster differs from the secret value at the given location in the provider.
By default, the secret created in the secret provided will not be deleted even after deleting the `PushSecret`, unless you set `spec.deletionPolicy` to `Delete`.
By default, the secret created in the secret provided will not be deleted even after deleting the `PushSecret`, unless you set `spec.deletionPolicy` to Delete.
``` yaml
{% include 'full-pushsecret.yaml' %}

View file

@ -5,6 +5,7 @@ metadata:
name: pushsecret-example # Customisable
namespace: default # Same of the SecretStores
spec:
updatePolicy: Replace # Policy to overwrite existing secrets in the provider on sync
deletionPolicy: Delete # the provider' secret will be deleted if the PushSecret is deleted
refreshInterval: 10s # Refresh interval for which push secret will reconcile
secretStoreRefs: # A list of secret stores to push secrets to

View file

@ -185,24 +185,27 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
return ctrl.Result{RequeueAfter: refreshInt}, nil
}
func (r *Reconciler) markAsFailed(msg string, ps *esapi.PushSecret, badSyncState esapi.SyncedPushSecretsMap) {
func (r *Reconciler) markAsFailed(msg string, ps *esapi.PushSecret, syncState esapi.SyncedPushSecretsMap) {
cond := newPushSecretCondition(esapi.PushSecretReady, v1.ConditionFalse, esapi.ReasonErrored, msg)
setPushSecretCondition(ps, *cond)
if badSyncState != nil {
r.setSyncedSecrets(ps, badSyncState)
if syncState != nil {
r.setSecrets(ps, syncState)
}
r.recorder.Event(ps, v1.EventTypeWarning, esapi.ReasonErrored, msg)
}
func (r *Reconciler) markAsDone(ps *esapi.PushSecret, syncedSecrets esapi.SyncedPushSecretsMap) {
func (r *Reconciler) markAsDone(ps *esapi.PushSecret, secrets esapi.SyncedPushSecretsMap) {
msg := "PushSecret synced successfully"
if ps.Spec.UpdatePolicy == esapi.PushSecretUpdatePolicyIfNotExists {
msg += ". Existing secrets in providers unchanged."
}
cond := newPushSecretCondition(esapi.PushSecretReady, v1.ConditionTrue, esapi.ReasonSynced, msg)
setPushSecretCondition(ps, *cond)
r.setSyncedSecrets(ps, syncedSecrets)
r.setSecrets(ps, secrets)
r.recorder.Event(ps, v1.EventTypeNormal, esapi.ReasonSynced, msg)
}
func (r *Reconciler) setSyncedSecrets(ps *esapi.PushSecret, status esapi.SyncedPushSecretsMap) {
func (r *Reconciler) setSecrets(ps *esapi.PushSecret, status esapi.SyncedPushSecretsMap) {
ps.Status.SyncedPushSecrets = status
}
@ -269,35 +272,57 @@ func (r *Reconciler) DeleteSecretFromStore(ctx context.Context, client v1beta1.S
}
func (r *Reconciler) PushSecretToProviders(ctx context.Context, stores map[esapi.PushSecretStoreRef]v1beta1.GenericStore, ps esapi.PushSecret, secret *v1.Secret, mgr *secretstore.Manager) (esapi.SyncedPushSecretsMap, error) {
out := esapi.SyncedPushSecretsMap{}
out := make(esapi.SyncedPushSecretsMap)
for ref, store := range stores {
storeKey := fmt.Sprintf("%v/%v", ref.Kind, store.GetName())
out[storeKey] = make(map[string]esapi.PushSecretData)
storeRef := v1beta1.SecretStoreRef{
Name: store.GetName(),
Kind: ref.Kind,
}
secretClient, err := mgr.Get(ctx, storeRef, ps.GetNamespace(), nil)
out, err := r.handlePushSecretDataForStore(ctx, ps, secret, out, mgr, store.GetName(), ref.Kind)
if err != nil {
return out, fmt.Errorf("could not get secrets client for store %v: %w", store.GetName(), err)
}
for _, data := range ps.Spec.Data {
if data.Match.SecretKey != "" {
if _, ok := secret.Data[data.Match.SecretKey]; !ok {
return out, fmt.Errorf("secret key %v does not exist", data.Match.SecretKey)
}
}
if err := secretClient.PushSecret(ctx, secret, data); err != nil {
return out, fmt.Errorf(errSetSecretFailed, data.Match.SecretKey, store.GetName(), err)
}
out[storeKey][statusRef(data)] = data
return out, err
}
}
return out, nil
}
func (r *Reconciler) handlePushSecretDataForStore(ctx context.Context, ps esapi.PushSecret, secret *v1.Secret, out esapi.SyncedPushSecretsMap, mgr *secretstore.Manager, storeName, refKind string) (esapi.SyncedPushSecretsMap, error) {
storeKey := fmt.Sprintf("%v/%v", refKind, storeName)
out[storeKey] = make(map[string]esapi.PushSecretData)
storeRef := v1beta1.SecretStoreRef{
Name: storeName,
Kind: refKind,
}
secretClient, err := mgr.Get(ctx, storeRef, ps.GetNamespace(), nil)
if err != nil {
return out, fmt.Errorf("could not get secrets client for store %v: %w", storeName, err)
}
for _, data := range ps.Spec.Data {
key := data.GetSecretKey()
if !secretKeyExists(key, secret) {
return out, fmt.Errorf("secret key %v does not exist", key)
}
switch ps.Spec.UpdatePolicy {
case esapi.PushSecretUpdatePolicyIfNotExists:
exists, err := secretClient.SecretExists(ctx, data.Match.RemoteRef)
if err != nil {
return out, fmt.Errorf("could not verify if secret exists in store: %w", err)
} else if exists {
out[storeKey][statusRef(data)] = data
continue
}
case esapi.PushSecretUpdatePolicyReplace:
default:
}
if err := secretClient.PushSecret(ctx, secret, data); err != nil {
return out, fmt.Errorf(errSetSecretFailed, key, storeName, err)
}
out[storeKey][statusRef(data)] = data
}
return out, nil
}
func secretKeyExists(key string, secret *v1.Secret) bool {
_, ok := secret.Data[key]
return key == "" || ok
}
func (r *Reconciler) GetSecret(ctx context.Context, ps esapi.PushSecret) (*v1.Secret, error) {
secretName := types.NamespacedName{Name: ps.Spec.Selector.Secret.Name, Namespace: ps.Namespace}
secret := &v1.Secret{}

View file

@ -128,6 +128,18 @@ var _ = Describe("ExternalSecret controller", func() {
})).To(Succeed())
})
const (
defaultKey = "key"
defaultVal = "value"
defaultPath = "path/to/key"
otherKey = "other-key"
otherVal = "other-value"
otherPath = "path/to/other-key"
newKey = "new-key"
newVal = "new-value"
storePrefixTemplate = "SecretStore/%v"
)
makeDefaultTestcase := func() *testCase {
return &testCase{
pushsecret: &v1alpha1.PushSecret{
@ -150,9 +162,9 @@ var _ = Describe("ExternalSecret controller", func() {
Data: []v1alpha1.PushSecretData{
{
Match: v1alpha1.PushSecretMatch{
SecretKey: "key",
SecretKey: defaultKey,
RemoteRef: v1alpha1.PushSecretRemoteRef{
RemoteKey: "path/to/key",
RemoteKey: defaultPath,
},
},
},
@ -165,7 +177,7 @@ var _ = Describe("ExternalSecret controller", func() {
Namespace: PushSecretNamespace,
},
Data: map[string][]byte{
"key": []byte("value"),
defaultKey: []byte(defaultVal),
},
},
store: &v1beta1.SecretStore{
@ -195,7 +207,7 @@ var _ = Describe("ExternalSecret controller", func() {
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
Eventually(func() bool {
By("checking if Provider value got updated")
secretValue := secret.Data["key"]
secretValue := secret.Data[defaultKey]
providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
if !ok {
return false
@ -207,6 +219,157 @@ var _ = Describe("ExternalSecret controller", func() {
}
}
updateIfNotExists := func(tc *testCase) {
fakeProvider.SetSecretFn = func() error {
return nil
}
fakeProvider.SecretExistsFn = func(ctx context.Context, ref v1beta1.PushSecretRemoteRef) (bool, error) {
_, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
return ok, nil
}
tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
tc.secret.Data[defaultKey] = []byte(newVal)
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
Eventually(func() bool {
By("checking if Provider value did not get updated")
Expect(k8sClient.Update(context.Background(), secret, &client.UpdateOptions{})).Should(Succeed())
providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
if !ok {
return false
}
got := providerValue.Value
return bytes.Equal(got, initialValue)
}, time.Second*10, time.Second).Should(BeTrue())
return true
}
}
updateIfNotExistsPartialSecrets := func(tc *testCase) {
fakeProvider.SetSecretFn = func() error {
return nil
}
fakeProvider.SecretExistsFn = func(ctx context.Context, ref v1beta1.PushSecretRemoteRef) (bool, error) {
_, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
return ok, nil
}
tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
tc.pushsecret.Spec.Data = append(tc.pushsecret.Spec.Data, v1alpha1.PushSecretData{
Match: v1alpha1.PushSecretMatch{
SecretKey: otherKey,
RemoteRef: v1alpha1.PushSecretRemoteRef{
RemoteKey: otherPath,
},
},
})
initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
tc.secret.Data[defaultKey] = []byte(newVal) // change initial value in secret
tc.secret.Data[otherKey] = []byte(otherVal)
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
Eventually(func() bool {
By("checking if only not existing Provider value got updated")
Expect(k8sClient.Update(context.Background(), secret, &client.UpdateOptions{})).Should(Succeed())
providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
if !ok {
return false
}
got := providerValue.Value
otherProviderValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[1].Match.RemoteRef.RemoteKey]
if !ok {
return false
}
gotOther := otherProviderValue.Value
return bytes.Equal(gotOther, tc.secret.Data[otherKey]) && bytes.Equal(got, initialValue)
}, time.Second*10, time.Second).Should(BeTrue())
return true
}
}
updateIfNotExistsSyncStatus := func(tc *testCase) {
fakeProvider.SetSecretFn = func() error {
return nil
}
fakeProvider.SecretExistsFn = func(ctx context.Context, ref v1beta1.PushSecretRemoteRef) (bool, error) {
_, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
return ok, nil
}
tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
tc.pushsecret.Spec.Data = append(tc.pushsecret.Spec.Data, v1alpha1.PushSecretData{
Match: v1alpha1.PushSecretMatch{
SecretKey: otherKey,
RemoteRef: v1alpha1.PushSecretRemoteRef{
RemoteKey: otherPath,
},
},
})
tc.secret.Data[defaultKey] = []byte(newVal)
tc.secret.Data[otherKey] = []byte(otherVal)
updatedPS := &v1alpha1.PushSecret{}
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
Eventually(func() bool {
By("checking if PushSecret status gets updated correctly with UpdatePolicy=IfNotExists")
Expect(k8sClient.Update(context.Background(), secret, &client.UpdateOptions{})).Should(Succeed())
psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
err := k8sClient.Get(context.Background(), psKey, updatedPS)
if err != nil {
return false
}
_, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][defaultPath]
if !ok {
return false
}
_, ok = updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][otherPath]
if !ok {
return false
}
expected := v1alpha1.PushSecretStatusCondition{
Type: v1alpha1.PushSecretReady,
Status: v1.ConditionTrue,
Reason: v1alpha1.ReasonSynced,
Message: "PushSecret synced successfully. Existing secrets in providers unchanged.",
}
return checkCondition(ps.Status, expected)
}, time.Second*10, time.Second).Should(BeTrue())
return true
}
}
updateIfNotExistsSyncFailed := func(tc *testCase) {
fakeProvider.SetSecretFn = func() error {
return nil
}
fakeProvider.SecretExistsFn = func(ctx context.Context, ref v1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("don't know")
}
tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
tc.secret.Data[defaultKey] = []byte(newVal)
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
Eventually(func() bool {
By("checking if sync failed if secret existence cannot be verified in Provider")
providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
if !ok {
return false
}
got := providerValue.Value
expected := v1alpha1.PushSecretStatusCondition{
Type: v1alpha1.PushSecretReady,
Status: v1.ConditionFalse,
Reason: v1alpha1.ReasonErrored,
Message: "set secret failed: could not verify if secret exists in store: don't know",
}
return checkCondition(ps.Status, expected) && bytes.Equal(got, initialValue)
}, time.Second*10, time.Second).Should(BeTrue())
return true
}
}
// if target Secret name is not specified it should use the ExternalSecret name.
syncSuccessfullyWithTemplate := func(tc *testCase) {
fakeProvider.SetSecretFn = func() error {
@ -232,9 +395,9 @@ var _ = Describe("ExternalSecret controller", func() {
Data: []v1alpha1.PushSecretData{
{
Match: v1alpha1.PushSecretMatch{
SecretKey: "key",
SecretKey: defaultKey,
RemoteRef: v1alpha1.PushSecretRemoteRef{
RemoteKey: "path/to/key",
RemoteKey: defaultPath,
},
},
},
@ -251,7 +414,7 @@ var _ = Describe("ExternalSecret controller", func() {
Type: v1.SecretTypeOpaque,
EngineVersion: v1beta1.TemplateEngineV2,
Data: map[string]string{
"key": "{{ .key | toString | upper }} was templated",
defaultKey: "{{ .key | toString | upper }} was templated",
},
},
},
@ -269,6 +432,7 @@ var _ = Describe("ExternalSecret controller", func() {
return true
}
}
// if target Secret name is not specified it should use the ExternalSecret name.
syncAndDeleteSuccessfully := func(tc *testCase) {
fakeProvider.SetSecretFn = func() error {
@ -295,9 +459,9 @@ var _ = Describe("ExternalSecret controller", func() {
Data: []v1alpha1.PushSecretData{
{
Match: v1alpha1.PushSecretMatch{
SecretKey: "key",
SecretKey: defaultKey,
RemoteRef: v1alpha1.PushSecretRemoteRef{
RemoteKey: "path/to/key",
RemoteKey: defaultPath,
},
},
},
@ -305,7 +469,7 @@ var _ = Describe("ExternalSecret controller", func() {
},
}
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
ps.Spec.Data[0].Match.RemoteRef.RemoteKey = "different-key"
ps.Spec.Data[0].Match.RemoteRef.RemoteKey = newKey
updatedPS := &v1alpha1.PushSecret{}
Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
Eventually(func() bool {
@ -315,11 +479,11 @@ var _ = Describe("ExternalSecret controller", func() {
if err != nil {
return false
}
key, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf("SecretStore/%v", PushSecretStore)]["different-key"]
key, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][newKey]
if !ok {
return false
}
return key.Match.SecretKey == "key"
return key.Match.SecretKey == defaultKey
}, time.Second*10, time.Second).Should(BeTrue())
return true
}
@ -352,9 +516,9 @@ var _ = Describe("ExternalSecret controller", func() {
Data: []v1alpha1.PushSecretData{
{
Match: v1alpha1.PushSecretMatch{
SecretKey: "key",
SecretKey: defaultKey,
RemoteRef: v1alpha1.PushSecretRemoteRef{
RemoteKey: "path/to/key",
RemoteKey: defaultPath,
},
},
},
@ -362,7 +526,7 @@ var _ = Describe("ExternalSecret controller", func() {
},
}
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
ps.Spec.Data[0].Match.RemoteRef.RemoteKey = "different-key"
ps.Spec.Data[0].Match.RemoteRef.RemoteKey = newKey
updatedPS := &v1alpha1.PushSecret{}
Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
Eventually(func() bool {
@ -372,11 +536,11 @@ var _ = Describe("ExternalSecret controller", func() {
if err != nil {
return false
}
_, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf("SecretStore/%v", PushSecretStore)]["different-key"]
_, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][newKey]
if !ok {
return false
}
_, ok = updatedPS.Status.SyncedPushSecrets[fmt.Sprintf("SecretStore/%v", PushSecretStore)]["path/to/key"]
_, ok = updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][defaultPath]
return ok
}, time.Second*10, time.Second).Should(BeTrue())
return true
@ -460,7 +624,7 @@ var _ = Describe("ExternalSecret controller", func() {
if err != nil {
return false
}
key, ok := updatedPS.Status.SyncedPushSecrets["SecretStore/new-store"]["path/to/key"]
key, ok := updatedPS.Status.SyncedPushSecrets["SecretStore/new-store"][defaultPath]
if !ok {
return false
}
@ -468,7 +632,7 @@ var _ = Describe("ExternalSecret controller", func() {
if syncedLen != 1 {
return false
}
return key.Match.SecretKey == "key"
return key.Match.SecretKey == defaultKey
}, time.Second*10, time.Second).Should(BeTrue())
return true
}
@ -505,9 +669,9 @@ var _ = Describe("ExternalSecret controller", func() {
Data: []v1alpha1.PushSecretData{
{
Match: v1alpha1.PushSecretMatch{
SecretKey: "key",
SecretKey: defaultKey,
RemoteRef: v1alpha1.PushSecretRemoteRef{
RemoteKey: "path/to/key",
RemoteKey: defaultPath,
},
},
},
@ -534,7 +698,7 @@ var _ = Describe("ExternalSecret controller", func() {
},
}
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
secretValue := secret.Data["key"]
secretValue := secret.Data[defaultKey]
providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
expected := v1alpha1.PushSecretStatusCondition{
Type: v1alpha1.PushSecretReady,
@ -566,7 +730,7 @@ var _ = Describe("ExternalSecret controller", func() {
}
tc.pushsecret.Spec.SecretStoreRefs[0].Kind = "ClusterSecretStore"
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
secretValue := secret.Data["key"]
secretValue := secret.Data[defaultKey]
providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
expected := v1alpha1.PushSecretStatusCondition{
Type: v1alpha1.PushSecretReady,
@ -606,9 +770,9 @@ var _ = Describe("ExternalSecret controller", func() {
Data: []v1alpha1.PushSecretData{
{
Match: v1alpha1.PushSecretMatch{
SecretKey: "key",
SecretKey: defaultKey,
RemoteRef: v1alpha1.PushSecretRemoteRef{
RemoteKey: "path/to/key",
RemoteKey: defaultPath,
},
},
},
@ -631,7 +795,7 @@ var _ = Describe("ExternalSecret controller", func() {
},
}
tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
secretValue := secret.Data["key"]
secretValue := secret.Data[defaultKey]
providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
expected := v1alpha1.PushSecretStatusCondition{
Type: v1alpha1.PushSecretReady,
@ -768,6 +932,10 @@ var _ = Describe("ExternalSecret controller", func() {
// this must be optional so we can test faulty es configuration
},
Entry("should sync", syncSuccessfully),
Entry("should not update existing secret if UpdatePolicy=IfNotExists", updateIfNotExists),
Entry("should only update parts of secret that don't already exist if UpdatePolicy=IfNotExists", updateIfNotExistsPartialSecrets),
Entry("should update the PushSecret status correctly if UpdatePolicy=IfNotExists", updateIfNotExistsSyncStatus),
Entry("should fail if secret existence cannot be verified if UpdatePolicy=IfNotExists", updateIfNotExistsSyncFailed),
Entry("should sync with template", syncSuccessfullyWithTemplate),
Entry("should delete if DeletionPolicy=Delete", syncAndDeleteSuccessfully),
Entry("should track deletion tasks if Delete fails", failDelete),

View file

@ -349,6 +349,10 @@ func (c *MockFakeClient) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretR
return nil
}
func (c *MockFakeClient) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, nil
}
func (c *MockFakeClient) GetSecret(_ context.Context, _ esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
return nil, nil
}

View file

@ -44,7 +44,8 @@ import (
)
const (
defaultAPIUrl = "https://api.akeyless.io"
defaultAPIUrl = "https://api.akeyless.io"
errNotImplemented = "not implemented"
)
// https://github.com/external-secrets/external-secrets/issues/644
@ -236,11 +237,15 @@ func (a *Akeyless) Validate() (esv1beta1.ValidationResult, error) {
}
func (a *Akeyless) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (a *Akeyless) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (a *Akeyless) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf(errNotImplemented)
}
// Implements store.Client.GetSecret Interface.

View file

@ -39,6 +39,7 @@ const (
errUninitalizedAlibabaProvider = "provider Alibaba is not initialized"
errFetchAccessKeyID = "could not fetch AccessKeyID secret: %w"
errFetchAccessKeySecret = "could not fetch AccessKeySecret secret: %w"
errNotImplemented = "not implemented"
)
// https://github.com/external-secrets/external-secrets/issues/644
@ -56,17 +57,21 @@ type SMInterface interface {
}
func (kms *KeyManagementService) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (kms *KeyManagementService) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (kms *KeyManagementService) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf(errNotImplemented)
}
// Empty GetAllSecrets.
func (kms *KeyManagementService) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
// TO be implemented
return nil, fmt.Errorf("GetAllSecrets not implemented")
return nil, fmt.Errorf(errNotImplemented)
}
// GetSecret returns a single secret from the provider.

View file

@ -133,6 +133,10 @@ func (pm *ParameterStore) DeleteSecret(ctx context.Context, remoteRef esv1beta1.
return nil
}
func (pm *ParameterStore) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (pm *ParameterStore) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
parameterType := "String"
overwrite := true

View file

@ -204,6 +204,10 @@ func (sm *SecretsManager) DeleteSecret(ctx context.Context, remoteRef esv1beta1.
return err
}
func (sm *SecretsManager) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (sm *SecretsManager) PushSecret(ctx context.Context, secret *corev1.Secret, psd esv1beta1.PushSecretData) error {
if psd.GetSecretKey() == "" {
return fmt.Errorf("pushing the whole secret is not yet implemented")

View file

@ -310,6 +310,10 @@ func (a *Azure) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushSecret
}
}
func (a *Azure) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func getCertificateFromValue(value []byte) (*x509.Certificate, error) {
// 1st: try decode pkcs12
_, localCert, err := pkcs12.Decode(value, "")

View file

@ -59,6 +59,7 @@ const (
errStoreValidateFailed = "unable to validate provided store. Check if username, serverUrl and privateKey are correct"
errServerURLNoEndSlash = "serverurl does not end with slash(/)"
errInvalidDataform = "invalid key format in dataForm section. Expected only 'databagName'"
errNotImplemented = "not implemented"
ProviderChef = "Chef"
CallChefGetDataBagItem = "GetDataBagItem"
@ -329,12 +330,16 @@ func getChefProvider(store v1beta1.GenericStore) (*v1beta1.ChefProvider, error)
// Not Implemented DeleteSecret.
func (providerchef *Providerchef) DeleteSecret(_ context.Context, _ v1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
// Not Implemented PushSecret.
func (providerchef *Providerchef) PushSecret(_ context.Context, _ *corev1.Secret, _ v1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (providerchef *Providerchef) SecretExists(_ context.Context, _ v1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf(errNotImplemented)
}
// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).

View file

@ -193,6 +193,10 @@ func (p *Client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef
return nil
}
func (p *Client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
// GetSecretMap returns multiple k/v pairs from the provider.
func (p *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
// Gets a secret as normal, expecting secret value to be a json object

View file

@ -71,6 +71,10 @@ func (c *client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef
return errors.New("deleting secrets is not supported by Delinea DevOps Secrets Vault")
}
func (c *client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, errors.New("not implemented")
}
func (c *client) Validate() (esv1beta1.ValidationResult, error) {
return esv1beta1.ValidationResultReady, nil
}

View file

@ -118,6 +118,10 @@ func (c *Client) DeleteSecret(_ context.Context, ref esv1beta1.PushSecretRemoteR
return nil
}
func (c *Client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (c *Client) PushSecret(_ context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
value := secret.Data[data.GetSecretKey()]

View file

@ -112,6 +112,11 @@ func (p *Provider) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteR
return nil
}
func (p *Provider) SecretExists(_ context.Context, ref esv1beta1.PushSecretRemoteRef) (bool, error) {
_, ok := p.config[ref.GetRemoteKey()]
return ok, nil
}
func (p *Provider) PushSecret(_ context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
value := secret.Data[data.GetSecretKey()]
currentData, ok := p.config[data.GetRemoteKey()]

View file

@ -27,6 +27,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
testingfake "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
)
@ -393,6 +394,62 @@ func TestSetSecret(t *testing.T) {
}
}
type secretExistsTestCase struct {
name string
input []esv1beta1.FakeProviderData
request esv1alpha1.PushSecretRemoteRef
expExists bool
}
func TestSecretExists(t *testing.T) {
gomega.RegisterTestingT(t)
p := &Provider{}
tbl := []secretExistsTestCase{
{
name: "return false, nil if no existing secret",
input: []esv1beta1.FakeProviderData{},
request: esv1alpha1.PushSecretRemoteRef{
RemoteKey: "/foo",
},
expExists: false,
},
{
name: "return true, nil if existing secret",
input: []esv1beta1.FakeProviderData{
{
Key: "/foo",
Value: "bar",
},
},
request: esv1alpha1.PushSecretRemoteRef{
RemoteKey: "/foo",
},
expExists: true,
},
}
for i, row := range tbl {
t.Run(row.name, func(t *testing.T) {
cl, err := p.NewClient(context.Background(), &esv1beta1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("secret-store-%v", i),
},
Spec: esv1beta1.SecretStoreSpec{
Provider: &esv1beta1.SecretStoreProvider{
Fake: &esv1beta1.FakeProvider{
Data: row.input,
},
},
},
}, nil, "")
gomega.Expect(err).ToNot(gomega.HaveOccurred())
exists, err := cl.SecretExists(context.TODO(), row.request)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Expect(exists).To(gomega.Equal(row.expExists))
})
}
}
type testMapCase struct {
name string
input []esv1beta1.FakeProviderData

View file

@ -78,6 +78,10 @@ func (c *client) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.Pus
return errors.New(errPushSecretsNotSupported)
}
func (c *client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, errors.New(errPushSecretsNotSupported)
}
func (c *client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return errors.New(errDeleteSecretsNotSupported)
}

View file

@ -129,6 +129,10 @@ func parseError(err error) error {
return err
}
func (c *Client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
// PushSecret pushes a kubernetes secret key into gcp provider Secret.
func (c *Client) PushSecret(ctx context.Context, secret *corev1.Secret, pushSecretData esv1beta1.PushSecretData) error {
if pushSecretData.GetSecretKey() == "" {

View file

@ -49,6 +49,7 @@ const (
errTagsOnlyEnvironmentSupported = "'find.tags' only supports 'environment_scope'"
errPathNotImplemented = "'find.path' is not implemented in the GitLab provider"
errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
errNotImplemented = "not implemented"
)
// https://github.com/external-secrets/external-secrets/issues/644
@ -88,11 +89,15 @@ func (g *gitlabBase) getAuth(ctx context.Context) (string, error) {
}
func (g *gitlabBase) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (g *gitlabBase) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf(errNotImplemented)
}
func (g *gitlabBase) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
// GetAllSecrets syncs all gitlab project and group variables into a single Kubernetes Secret.

View file

@ -60,6 +60,7 @@ const (
errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
errJSONSecretMarshal = "unable to marshal secret: %w"
errExtractingSecret = "unable to extract the fetched secret %s of type %s while performing %s"
errNotImplemented = "not implemented"
)
var contextTimeout = time.Minute * 2
@ -97,18 +98,22 @@ func (c *client) setAuth(ctx context.Context) error {
}
func (ibm *providerIBM) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (ibm *providerIBM) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf(errNotImplemented)
}
// Not Implemented PushSecret.
func (ibm *providerIBM) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
// Empty GetAllSecrets.
func (ibm *providerIBM) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
// TO be implemented
return nil, fmt.Errorf("GetAllSecrets not implemented")
return nil, fmt.Errorf(errNotImplemented)
}
func (ibm *providerIBM) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {

View file

@ -212,6 +212,10 @@ func (c *Client) DeleteSecret(_ context.Context, remoteRef esv1beta1.PushSecretR
return nil
}
func (c *Client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (c *Client) buildSecretNameAndKey(remoteRef esv1beta1.PushSecretRemoteRef) ([]string, error) {
parts := strings.Split(remoteRef.GetRemoteKey(), "/")
if len(parts) != 2 {

View file

@ -100,6 +100,10 @@ func (c *Client) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushSecre
return c.fullDelete(ctx, remoteRef.GetRemoteKey())
}
func (c *Client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (c *Client) PushSecret(ctx context.Context, secret *v1.Secret, data esv1beta1.PushSecretData) error {
if data.GetProperty() == "" && data.GetSecretKey() != "" {
return fmt.Errorf("requires property in RemoteRef to push secret value if secret key is defined")

View file

@ -126,6 +126,11 @@ func (c *Client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef
return nil
}
func (c *Client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
// not implemented
return false, nil
}
func (c *Client) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
// not implemented
return nil

View file

@ -208,6 +208,10 @@ func (provider *ProviderOnePassword) DeleteSecret(_ context.Context, ref esv1bet
return nil
}
func (provider *ProviderOnePassword) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
const (
passwordLabel = "password"
)

View file

@ -159,6 +159,10 @@ func (vms *VaultManagementService) DeleteSecret(ctx context.Context, remoteRef e
}
}
func (vms *VaultManagementService) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (vms *VaultManagementService) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
var page *string
var summaries []vault.SecretSummary

View file

@ -61,6 +61,10 @@ func (c *client) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.Pus
return errors.New(errPushSecretsNotSupported)
}
func (c *client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, errors.New(errPushSecretsNotSupported)
}
func (c *client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return errors.New(errDeleteSecretsNotSupported)
}

View file

@ -264,6 +264,10 @@ func (c *client) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushSecre
return nil
}
func (c *client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (c *client) Validate() (esv1beta1.ValidationResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

View file

@ -19,7 +19,6 @@ import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
@ -80,6 +79,7 @@ var (
errInvalidResponseBody = errors.New("invalid HTTP response body received from senhasegura")
errInvalidHTTPCode = errors.New("received invalid HTTP code from senhasegura")
errApplicationError = errors.New("received application error from senhasegura")
errNotImplemented = errors.New("not implemented")
)
/*
@ -93,12 +93,16 @@ func New(isoSession *senhaseguraAuth.SenhaseguraIsoSession) (*DSM, error) {
}
func (dsm *DSM) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
return errNotImplemented
}
func (dsm *DSM) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, errNotImplemented
}
// Not Implemented PushSecret.
func (dsm *DSM) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
return errNotImplemented
}
/*
@ -165,7 +169,7 @@ TODO: GetAllSecrets functionality is to get secrets from either regexp-matching
https://github.com/external-secrets/external-secrets/pull/830#discussion_r858657107
*/
func (dsm *DSM) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (secretData map[string][]byte, err error) {
return nil, fmt.Errorf("GetAllSecrets not implemented yet")
return nil, errNotImplemented
}
/*

View file

@ -38,6 +38,7 @@ type Client struct {
GetSecretFn func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error)
GetSecretMapFn func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
GetAllSecretsFn func(context.Context, esv1beta1.ExternalSecretFind) (map[string][]byte, error)
SecretExistsFn func(context.Context, esv1beta1.PushSecretRemoteRef) (bool, error)
SetSecretFn func() error
DeleteSecretFn func() error
}
@ -54,6 +55,9 @@ func New() *Client {
GetAllSecretsFn: func(context.Context, esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
return nil, nil
},
SecretExistsFn: func(context.Context, esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, nil
},
SetSecretFn: func() error {
return nil
},
@ -92,6 +96,10 @@ func (v *Client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef
return v.DeleteSecretFn()
}
func (v *Client) SecretExists(ctx context.Context, ref esv1beta1.PushSecretRemoteRef) (bool, error) {
return v.SecretExistsFn(ctx, ref)
}
// GetSecret implements the provider.Provider interface.
func (v *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
return v.GetSecretFn(ctx, ref)

View file

@ -70,32 +70,7 @@ func (c *client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
}
}
// Return nil if secret value is null
if data == nil {
return nil, esv1beta1.NoSecretError{}
}
jsonStr, err := json.Marshal(data)
if err != nil {
return nil, err
}
// (1): return raw json if no property is defined
if ref.Property == "" {
return jsonStr, nil
}
// For backwards compatibility we want the
// actual keys to take precedence over gjson syntax
// (2): extract key from secret with property
if _, ok := data[ref.Property]; ok {
return utils.GetByteValueFromMap(data, ref.Property)
}
// (3): extract key from secret using gjson
val := gjson.Get(string(jsonStr), ref.Property)
if !val.Exists() {
return nil, fmt.Errorf(errSecretKeyFmt, ref.Property)
}
return []byte(val.String()), nil
return getSecretValue(data, ref.Property)
}
// GetSecretMap supports two modes of operation:
@ -123,6 +98,25 @@ func (c *client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretD
return byteMap, nil
}
func (c *client) SecretExists(ctx context.Context, ref esv1beta1.PushSecretRemoteRef) (bool, error) {
path := c.buildPath(ref.GetRemoteKey())
data, err := c.readSecret(ctx, path, "")
if err != nil {
if errors.Is(err, esv1beta1.NoSecretError{}) {
return false, nil
}
return false, err
}
value, err := getSecretValue(data, ref.GetProperty())
if err != nil {
if errors.Is(err, esv1beta1.NoSecretError{}) || err.Error() == fmt.Sprintf(errSecretKeyFmt, ref.GetProperty()) {
return false, nil
}
return false, err
}
return value != nil, nil
}
func (c *client) readSecret(ctx context.Context, path, version string) (map[string]interface{}, error) {
dataPath := c.buildPath(path)
@ -162,6 +156,34 @@ func (c *client) readSecret(ctx context.Context, path, version string) (map[stri
return secretData, nil
}
func getSecretValue(data map[string]interface{}, property string) ([]byte, error) {
if data == nil {
return nil, esv1beta1.NoSecretError{}
}
jsonStr, err := json.Marshal(data)
if err != nil {
return nil, err
}
// (1): return raw json if no property is defined
if property == "" {
return jsonStr, nil
}
// For backwards compatibility we want the
// actual keys to take precedence over gjson syntax
// (2): extract key from secret with property
if _, ok := data[property]; ok {
return utils.GetByteValueFromMap(data, property)
}
// (3): extract key from secret using gjson
val := gjson.Get(string(jsonStr), property)
if !val.Exists() {
return nil, fmt.Errorf(errSecretKeyFmt, property)
}
return []byte(val.String()), nil
}
func (c *client) readSecretMetadata(ctx context.Context, path string) (map[string]string, error) {
metadata := make(map[string]string)
url, err := c.buildMetadataPath(path)

View file

@ -27,6 +27,7 @@ import (
kclient "sigs.k8s.io/controller-runtime/pkg/client"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
testingfake "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
"github.com/external-secrets/external-secrets/pkg/provider/vault/fake"
"github.com/external-secrets/external-secrets/pkg/provider/vault/util"
)
@ -695,6 +696,193 @@ func TestGetSecretPath(t *testing.T) {
}
}
func TestSecretExists(t *testing.T) {
secret := map[string]interface{}{
"foo": "bar",
}
secretWithNil := map[string]interface{}{
"hi": nil,
}
errNope := errors.New("nope")
type args struct {
store *esv1beta1.VaultProvider
vClient util.Logical
}
type want struct {
exists bool
err error
}
tests := map[string]struct {
reason string
args args
ref *testingfake.PushSecretData
want want
}{
"NoExistingSecretV1": {
reason: "Should return false, nil if secret does not exist in provider.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, esv1beta1.NoSecretError{}),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret"},
want: want{
exists: false,
err: nil,
},
},
"NoExistingSecretV2": {
reason: "Should return false, nil if secret does not exist in provider.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, esv1beta1.NoSecretError{}),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret"},
want: want{
exists: false,
err: nil,
},
},
"NoExistingSecretWithPropertyV2": {
reason: "Should return false, nil if secret with property does not exist in provider.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{
"data": secret,
}, nil),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: "different"},
want: want{
exists: false,
err: nil,
},
},
"NoExistingSecretWithPropertyV1": {
reason: "Should return false, nil if secret with property does not exist in provider.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(secret, nil),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: "different"},
want: want{
exists: false,
err: nil,
},
},
"ExistingSecretV1": {
reason: "Should return true, nil if secret exists in provider.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(secret, nil),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret"},
want: want{
exists: true,
err: nil,
},
},
"ExistingSecretV2": {
reason: "Should return true, nil if secret exists in provider.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{
"data": secret,
}, nil),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret"},
want: want{
exists: true,
err: nil,
},
},
"ExistingSecretWithNilV1": {
reason: "Should return false, nil if secret in provider has nil value.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(secretWithNil, nil),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: "hi"},
want: want{
exists: false,
err: nil,
},
},
"ExistingSecretWithNilV2": {
reason: "Should return false, nil if secret in provider has nil value.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{
"data": secretWithNil,
}, nil),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: "hi"},
want: want{
exists: false,
err: nil,
},
},
"ErrorReadingSecretV1": {
reason: "Should return error if secret existence cannot be verified.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, errNope),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret"},
want: want{
exists: false,
err: fmt.Errorf(errReadSecret, errNope),
},
},
"ErrorReadingSecretV2": {
reason: "Should return error if secret existence cannot be verified.",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.Logical{
ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, errNope),
},
},
ref: &testingfake.PushSecretData{RemoteKey: "secret"},
want: want{
exists: false,
err: fmt.Errorf(errReadSecret, errNope),
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
client := &client{
logical: tc.args.vClient,
store: tc.args.store,
}
exists, err := client.SecretExists(context.Background(), tc.ref)
if diff := cmp.Diff(exists, tc.want.exists); diff != "" {
t.Errorf("\n%s\nvault.SecretExists(...): -want exists, +got exists:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.err, err, EquateErrors()); diff != "" {
t.Errorf("\n%s\nvault.GetSecret(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}
// EquateErrors returns true if the supplied errors are of the same type and
// produce identical strings. This mirrors the error comparison behavior of
// https://github.com/go-test/deep, which most Crossplane tests targeted before

View file

@ -31,6 +31,10 @@ import (
"github.com/external-secrets/external-secrets/pkg/utils"
)
const (
errNotImplemented = "not implemented"
)
// https://github.com/external-secrets/external-secrets/issues/644
var _ esv1beta1.SecretsClient = &WebHook{}
var _ esv1beta1.Provider = &Provider{}
@ -101,18 +105,22 @@ func getProvider(store esv1beta1.GenericStore) (*webhook.Spec, error) {
}
func (w *WebHook) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (w *WebHook) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf(errNotImplemented)
}
// Not Implemented PushSecret.
func (w *WebHook) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
// Empty GetAllSecrets.
func (w *WebHook) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
// TO be implemented
return nil, fmt.Errorf("GetAllSecrets not implemented")
return nil, fmt.Errorf(errNotImplemented)
}
func (w *WebHook) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {

View file

@ -23,6 +23,10 @@ import (
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
)
const (
errNotImplemented = "not implemented"
)
// https://github.com/external-secrets/external-secrets/issues/644
var _ esv1beta1.SecretsClient = &yandexCloudSecretsClient{}
@ -38,11 +42,15 @@ func (c *yandexCloudSecretsClient) GetSecret(ctx context.Context, ref esv1beta1.
}
func (c *yandexCloudSecretsClient) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (c *yandexCloudSecretsClient) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
return false, fmt.Errorf(errNotImplemented)
}
func (c *yandexCloudSecretsClient) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
return fmt.Errorf(errNotImplemented)
}
func (c *yandexCloudSecretsClient) Validate() (esv1beta1.ValidationResult, error) {
@ -55,7 +63,7 @@ func (c *yandexCloudSecretsClient) GetSecretMap(ctx context.Context, ref esv1bet
func (c *yandexCloudSecretsClient) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
// TO be implemented
return nil, fmt.Errorf("GetAllSecrets not supported")
return nil, fmt.Errorf(errNotImplemented)
}
func (c *yandexCloudSecretsClient) Close(_ context.Context) error {