mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
✨ Support MetadataPolicy=Fetch for GCP Secrets Manager (#2111)
* Support MetadataPolicy=Fetch for GCP Secrets Manager Signed-off-by: shuheiktgw <s-kitagawa@mercari.com> * Use '.' instead of '/' to split metadata Signed-off-by: shuheiktgw <s-kitagawa@mercari.com> * Support annotations/labels Signed-off-by: shuheiktgw <s-kitagawa@mercari.com> --------- Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>
This commit is contained in:
parent
ee13e61645
commit
07f237e071
2 changed files with 280 additions and 0 deletions
|
@ -336,6 +336,10 @@ func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
|
|||
return nil, fmt.Errorf(errUninitalizedGCPProvider)
|
||||
}
|
||||
|
||||
if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
|
||||
return c.getSecretMetadata(ctx, ref)
|
||||
}
|
||||
|
||||
version := ref.Version
|
||||
if version == "" {
|
||||
version = defaultVersion
|
||||
|
@ -378,6 +382,80 @@ func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
|
|||
return []byte(val.String()), nil
|
||||
}
|
||||
|
||||
func (c *Client) getSecretMetadata(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
secret, err := c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
|
||||
Name: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, ref.Key),
|
||||
})
|
||||
|
||||
err = parseError(err)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errClientGetSecretAccess, err)
|
||||
}
|
||||
|
||||
const (
|
||||
annotations = "annotations"
|
||||
labels = "labels"
|
||||
)
|
||||
|
||||
extractMetadataKey := func(s string, p string) string {
|
||||
prefix := p + "."
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimPrefix(s, prefix)
|
||||
}
|
||||
|
||||
if annotation := extractMetadataKey(ref.Property, annotations); annotation != "" {
|
||||
v, ok := secret.GetAnnotations()[annotation]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("annotation with key %s does not exist in secret %s", annotation, ref.Key)
|
||||
}
|
||||
|
||||
return []byte(v), nil
|
||||
}
|
||||
|
||||
if label := extractMetadataKey(ref.Property, labels); label != "" {
|
||||
v, ok := secret.GetLabels()[label]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("label with key %s does not exist in secret %s", label, ref.Key)
|
||||
}
|
||||
|
||||
return []byte(v), nil
|
||||
}
|
||||
|
||||
if ref.Property == annotations {
|
||||
j, err := json.Marshal(secret.GetAnnotations())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("faild marshaling annotations into json: %w", err)
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
if ref.Property == labels {
|
||||
j, err := json.Marshal(secret.GetLabels())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("faild marshaling labels into json: %w", err)
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
if ref.Property != "" {
|
||||
return nil, fmt.Errorf("invalid property %s: metadata property should start with either %s or %s", ref.Property, annotations, labels)
|
||||
}
|
||||
|
||||
j, err := json.Marshal(map[string]map[string]string{
|
||||
"annotations": secret.GetAnnotations(),
|
||||
"labels": secret.GetLabels(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("faild marshaling metadata map into json: %w", err)
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// GetSecretMap returns multiple k/v pairs from the provider.
|
||||
func (c *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
if c.smClient == nil || c.store.ProjectID == "" {
|
||||
|
|
|
@ -193,6 +193,207 @@ func TestSecretManagerGetSecret(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ref esv1beta1.ExternalSecretDataRemoteRef
|
||||
getSecretMockReturn fakesm.GetSecretMockReturn
|
||||
expectedSecret string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "annotation is specified",
|
||||
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "bar",
|
||||
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
|
||||
Property: "annotations.managed-by",
|
||||
},
|
||||
getSecretMockReturn: fakesm.GetSecretMockReturn{
|
||||
Secret: &secretmanagerpb.Secret{
|
||||
Name: "projects/foo/secret/bar",
|
||||
Annotations: map[string]string{
|
||||
"managed-by": "external-secrets",
|
||||
},
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
expectedSecret: "external-secrets",
|
||||
},
|
||||
{
|
||||
name: "label is specified",
|
||||
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "bar",
|
||||
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
|
||||
Property: "labels.managed-by",
|
||||
},
|
||||
getSecretMockReturn: fakesm.GetSecretMockReturn{
|
||||
Secret: &secretmanagerpb.Secret{
|
||||
Name: "projects/foo/secret/bar",
|
||||
Labels: map[string]string{
|
||||
"managed-by": "external-secrets",
|
||||
},
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
expectedSecret: "external-secrets",
|
||||
},
|
||||
{
|
||||
name: "annotations is specified",
|
||||
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "bar",
|
||||
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
|
||||
Property: "annotations",
|
||||
},
|
||||
getSecretMockReturn: fakesm.GetSecretMockReturn{
|
||||
Secret: &secretmanagerpb.Secret{
|
||||
Name: "projects/foo/secret/bar",
|
||||
Annotations: map[string]string{
|
||||
"annotationKey1": "annotationValue1",
|
||||
"annotationKey2": "annotationValue2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"labelKey1": "labelValue1",
|
||||
"labelKey2": "labelValue2",
|
||||
},
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
expectedSecret: `{"annotationKey1":"annotationValue1","annotationKey2":"annotationValue2"}`,
|
||||
},
|
||||
{
|
||||
name: "labels is specified",
|
||||
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "bar",
|
||||
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
|
||||
Property: "labels",
|
||||
},
|
||||
getSecretMockReturn: fakesm.GetSecretMockReturn{
|
||||
Secret: &secretmanagerpb.Secret{
|
||||
Name: "projects/foo/secret/bar",
|
||||
Annotations: map[string]string{
|
||||
"annotationKey1": "annotationValue1",
|
||||
"annotationKey2": "annotationValue2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"labelKey1": "labelValue1",
|
||||
"labelKey2": "labelValue2",
|
||||
},
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
expectedSecret: `{"labelKey1":"labelValue1","labelKey2":"labelValue2"}`,
|
||||
},
|
||||
{
|
||||
name: "no property is specified",
|
||||
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "bar",
|
||||
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
|
||||
},
|
||||
getSecretMockReturn: fakesm.GetSecretMockReturn{
|
||||
Secret: &secretmanagerpb.Secret{
|
||||
Name: "projects/foo/secret/bar",
|
||||
Labels: map[string]string{
|
||||
"label-key": "label-value",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"annotation-key": "annotation-value",
|
||||
},
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
expectedSecret: `{"annotations":{"annotation-key":"annotation-value"},"labels":{"label-key":"label-value"}}`,
|
||||
},
|
||||
{
|
||||
name: "annotation does not exist",
|
||||
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "bar",
|
||||
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
|
||||
Property: "annotations.unknown",
|
||||
},
|
||||
getSecretMockReturn: fakesm.GetSecretMockReturn{
|
||||
Secret: &secretmanagerpb.Secret{
|
||||
Name: "projects/foo/secret/bar",
|
||||
Annotations: map[string]string{
|
||||
"managed-by": "external-secrets",
|
||||
},
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
expectedErr: "annotation with key unknown does not exist in secret bar",
|
||||
},
|
||||
{
|
||||
name: "label does not exist",
|
||||
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "bar",
|
||||
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
|
||||
Property: "labels.unknown",
|
||||
},
|
||||
getSecretMockReturn: fakesm.GetSecretMockReturn{
|
||||
Secret: &secretmanagerpb.Secret{
|
||||
Name: "projects/foo/secret/bar",
|
||||
Labels: map[string]string{
|
||||
"managed-by": "external-secrets",
|
||||
},
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
expectedErr: "label with key unknown does not exist in secret bar",
|
||||
},
|
||||
{
|
||||
name: "invalid property",
|
||||
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "bar",
|
||||
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
|
||||
Property: "invalid.managed-by",
|
||||
},
|
||||
getSecretMockReturn: fakesm.GetSecretMockReturn{
|
||||
Secret: &secretmanagerpb.Secret{
|
||||
Name: "projects/foo/secret/bar",
|
||||
Labels: map[string]string{
|
||||
"managed-by": "external-secrets",
|
||||
},
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
expectedErr: "invalid property invalid.managed-by",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
smClient := fakesm.MockSMClient{}
|
||||
smClient.NewGetSecretFn(tc.getSecretMockReturn)
|
||||
|
||||
client := Client{
|
||||
smClient: &smClient,
|
||||
store: &esv1beta1.GCPSMProvider{
|
||||
ProjectID: "foo",
|
||||
},
|
||||
}
|
||||
got, err := client.GetSecret(context.TODO(), tc.ref)
|
||||
if tc.expectedErr != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("expected to receive an error but got nit")
|
||||
}
|
||||
|
||||
if !ErrorContains(err, tc.expectedErr) {
|
||||
t.Fatalf("unexpected error: %s, expected: '%s'", err.Error(), tc.expectedErr)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if gotStr := string(got); gotStr != tc.expectedSecret {
|
||||
t.Fatalf("unexpected secret: expected %s, got %s", tc.expectedSecret, gotStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeRef struct {
|
||||
key string
|
||||
}
|
||||
|
@ -309,6 +510,7 @@ func TestDeleteSecret(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSecret(t *testing.T) {
|
||||
ref := fakeRef{key: "/baz"}
|
||||
|
||||
|
|
Loading…
Reference in a new issue