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

fix: ensure existing labels are retained for secrets in GCP secrets m… (#4160)

* fix: ensure existing labels are retained for secrets in GCP secrets manager for existing secrets (#4016)

Signed-off-by: Craig Newton <newtondev@gmail.com>

* fix: ensure existing labels are retained for secrets in GCP secrets manager for existing secrets (#4016)

Signed-off-by: Craig Newton <newtondev@gmail.com>

* fix: add missing header to push_secret_test.go

Signed-off-by: Craig Newton <newtondev@gmail.com>

---------

Signed-off-by: Craig Newton <newtondev@gmail.com>
This commit is contained in:
Craig Newton 2024-12-11 06:51:30 +01:00 committed by GitHub
parent 34f526f134
commit 388158a4d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 166 additions and 5 deletions

View file

@ -111,3 +111,44 @@ The operator will fetch the GCP Secret Manager secret and inject it as a `Kind=S
```
kubectl get secret secret-to-be-created -n <namespace> -o jsonpath='{.data.dev-secret-test}' | base64 -d
```
### PushSecret owning an existing Google Secret Manager Secret
There are some use cases where you want to use PushSecret for an existing Google Secret Manager Secret that already has labels defined. For example when the creation of the secret is managed by another controller like Kubernetes Config Connector (KCC) and the updating of the secret is managed by ESO.
To allow ESO to take ownership of the existing Google Secret Manager Secret, you need to add the label `"managed-by": "external-secrets"`.
By default, the PushSecret spec will replace any existing labels on the existing GCP Secret Manager Secret. To prevent this, a new field was added to the `spec.data.metadata` object called `mergePolicy` which defaults to `Replace` to ensure that there are no breaking changes and is backward compatible. The other option for this field is `Merge` which will merge the existing labels on the Google Secret Manager Secret with the labels defined in the PushSecret spec. This ensures that the existing labels defined on the Google Secret Manager Secret are retained.
Example of using the `mergePolicy` field:
```yaml
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
name: pushsecret-example
namespace: default
spec:
updatePolicy: Replace
deletionPolicy: None
refreshInterval: 1h
secretStoreRefs:
- name: gcp-secretstore
kind: SecretStore
selector:
secret:
name: bestpokemon
template:
data:
bestpokemon: "{{ .bestpokemon }}"
data:
- conversionStrategy: None
metadata:
mergePolicy: Merge
labels:
anotherLabel: anotherValue
match:
secretKey: bestpokemon
remoteRef:
remoteKey: best-pokemon
```

View file

@ -19,6 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
"github.com/tidwall/sjson"
@ -26,10 +27,18 @@ import (
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
)
type PushSecretMetadataMergePolicy string
const (
PushSecretMetadataMergePolicyReplace PushSecretMetadataMergePolicy = "Replace"
PushSecretMetadataMergePolicyMerge PushSecretMetadataMergePolicy = "Merge"
)
type Metadata struct {
Annotations map[string]string `json:"annotations"`
Labels map[string]string `json:"labels"`
Topics []string `json:"topics,omitempty"`
Annotations map[string]string `json:"annotations"`
Labels map[string]string `json:"labels"`
Topics []string `json:"topics,omitempty"`
MergePolicy PushSecretMetadataMergePolicy `json:"mergePolicy,omitempty"`
}
func newPushSecretBuilder(payload []byte, data esv1beta1.PushSecretData) (pushSecretBuilder, error) {
@ -75,11 +84,18 @@ func (b *psBuilder) buildMetadata(_, labels map[string]string, _ []*secretmanage
if err := decoder.Decode(&metadata); err != nil {
return nil, nil, nil, fmt.Errorf("failed to decode PushSecret metadata: %w", err)
}
if metadata.MergePolicy == "" {
// Set default MergePolicy to be Replace
metadata.MergePolicy = PushSecretMetadataMergePolicyReplace
}
}
newLabels := map[string]string{}
if metadata.Labels != nil {
newLabels = metadata.Labels
maps.Copy(newLabels, metadata.Labels)
if metadata.MergePolicy == PushSecretMetadataMergePolicyMerge {
// Keep labels from the existing GCP Secret Manager Secret
maps.Copy(newLabels, labels)
}
newLabels[managedByKey] = managedByValue

View file

@ -0,0 +1,104 @@
/*
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 secretmanager
import (
"testing"
"github.com/stretchr/testify/assert"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
testingfake "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
)
func TestBuildMetadata(t *testing.T) {
tests := []struct {
name string
labels map[string]string
metadata *apiextensionsv1.JSON
expectedError bool
expectedLabels map[string]string
expectedAnnotations map[string]string
expectedTopics []string
}{
{
name: "secret not managed by external secrets",
labels: map[string]string{
"someKey": "someValue",
},
expectedError: true,
},
{
name: "metadata with default MergePolicy of Replace",
labels: map[string]string{
managedByKey: managedByValue,
"someOtherKey": "someOtherValue",
},
metadata: &apiextensionsv1.JSON{
Raw: []byte(`{"annotations":{"key1":"value1"},"labels":{"key2":"value2"}}`),
},
expectedError: false,
expectedLabels: map[string]string{
managedByKey: managedByValue,
"key2": "value2",
},
expectedAnnotations: map[string]string{
"key1": "value1",
},
expectedTopics: nil,
},
{
name: "metadata with merge policy",
labels: map[string]string{
managedByKey: managedByValue,
"existingKey": "existingValue",
},
metadata: &apiextensionsv1.JSON{
Raw: []byte(`{"annotations":{"key1":"value1"},"labels":{"key2":"value2"},"mergePolicy":"Merge"}`),
},
expectedError: false,
expectedLabels: map[string]string{
managedByKey: managedByValue,
"existingKey": "existingValue",
"key2": "value2",
},
expectedAnnotations: map[string]string{
"key1": "value1",
},
expectedTopics: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
psData := testingfake.PushSecretData{
Metadata: tt.metadata,
}
builder := &psBuilder{
pushSecretData: psData,
}
annotations, labels, topics, err := builder.buildMetadata(nil, tt.labels, nil)
if tt.expectedError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedLabels, labels)
assert.Equal(t, tt.expectedAnnotations, annotations)
assert.Equal(t, tt.expectedTopics, topics)
}
})
}
}