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

Support PushSecret Property for GCP (#2465)

* Support PushSecret Property for GCP

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>

* Take over the ownership if the label does not exist

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>

---------

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>
This commit is contained in:
Shuhei Kitagawa 2023-08-01 21:38:47 +09:00 committed by GitHub
parent f777a85156
commit 12a4470949
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 363 additions and 120 deletions

View file

@ -141,6 +141,7 @@ require (
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/net v0.11.0 // indirect

View file

@ -396,6 +396,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -403,6 +404,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=

5
go.mod
View file

@ -44,8 +44,8 @@ require (
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.10.0
golang.org/x/oauth2 v0.9.0
google.golang.org/api v0.129.0
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect
google.golang.org/api v0.128.0
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc
google.golang.org/grpc v1.56.1
gopkg.in/yaml.v3 v3.0.1
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919
@ -83,6 +83,7 @@ require (
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17
github.com/sethvargo/go-password v0.2.0
github.com/spf13/pflag v1.0.5
github.com/tidwall/sjson v1.2.5
sigs.k8s.io/yaml v1.3.0
)

11
go.sum
View file

@ -617,6 +617,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -624,6 +625,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
@ -987,8 +990,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
google.golang.org/api v0.129.0 h1:2XbdjjNfFPXQyufzQVwPf1RRnHH8Den2pfNE2jw7L8w=
google.golang.org/api v0.129.0/go.mod h1:dFjiXlanKwWE3612X97llhsoI36FAoIiRj3aTl5b/zE=
google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg=
google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1040,8 +1043,8 @@ google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaE
google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk=
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=

View file

@ -44,6 +44,7 @@ const (
CallGCPSMGetSecret = "GetSecret"
CallGCPSMDeleteSecret = "DeleteSecret"
CallGCPSMCreateSecret = "CreateSecret"
CallGCPSMUpdateSecret = "UpdateSecret"
CallGCPSMAccessSecretVersion = "AccessSecretVersion"
CallGCPSMAddSecretVersion = "AddSecretVersion"
CallGCPSMListSecrets = "ListSecrets"

View file

@ -27,8 +27,11 @@ import (
"github.com/googleapis/gax-go/v2"
"github.com/googleapis/gax-go/v2/apierror"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"google.golang.org/api/iterator"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
ctrl "sigs.k8s.io/controller-runtime"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
@ -61,6 +64,9 @@ const (
errInvalidAuthSecretRef = "invalid auth secret ref: %w"
errInvalidWISARef = "invalid workload identity service account reference: %w"
errUnexpectedFindOperator = "unexpected find operator"
managedByKey = "managed-by"
managedByValue = "external-secrets"
)
type Client struct {
@ -82,32 +88,25 @@ type GoogleSecretManagerClient interface {
CreateSecret(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
Close() error
GetSecret(ctx context.Context, req *secretmanagerpb.GetSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
UpdateSecret(context.Context, *secretmanagerpb.UpdateSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error)
}
var log = ctrl.Log.WithName("provider").WithName("gcp").WithName("secretsmanager")
func (c *Client) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
var gcpSecret *secretmanagerpb.Secret
var err error
gcpSecret, err = c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
gcpSecret, err := c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
Name: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, remoteRef.GetRemoteKey()),
})
metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMGetSecret, err)
var gErr *apierror.APIError
if errors.As(err, &gErr) {
if gErr.GRPCStatus().Code() == codes.NotFound {
if err != nil {
if status.Code(err) == codes.NotFound {
return nil
}
return err
}
if err != nil {
return err
}
manager, ok := gcpSecret.Labels["managed-by"]
if !ok || manager != "external-secrets" {
return err
}
if manager, ok := gcpSecret.Labels[managedByKey]; !ok || manager != managedByValue {
return nil
}
@ -129,47 +128,60 @@ func parseError(err error) error {
// PushSecret pushes a kubernetes secret key into gcp provider Secret.
func (c *Client) PushSecret(ctx context.Context, payload []byte, remoteRef esv1beta1.PushRemoteRef) error {
createSecretReq := &secretmanagerpb.CreateSecretRequest{
Parent: fmt.Sprintf("projects/%s", c.store.ProjectID),
SecretId: remoteRef.GetRemoteKey(),
Secret: &secretmanagerpb.Secret{
Labels: map[string]string{
"managed-by": "external-secrets",
},
Replication: &secretmanagerpb.Replication{
Replication: &secretmanagerpb.Replication_Automatic_{
Automatic: &secretmanagerpb.Replication_Automatic{},
},
},
},
}
var gcpSecret *secretmanagerpb.Secret
var err error
gcpSecret, err = c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
gcpSecret, err := c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
Name: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, remoteRef.GetRemoteKey()),
})
metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMGetSecret, err)
var gErr *apierror.APIError
if err != nil {
if status.Code(err) != codes.NotFound {
return err
}
if err != nil && errors.As(err, &gErr) {
if gErr.GRPCStatus().Code() == codes.NotFound {
gcpSecret, err = c.smClient.CreateSecret(ctx, createSecretReq)
metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMCreateSecret, err)
if err != nil {
return err
}
} else {
gcpSecret, err = c.smClient.CreateSecret(ctx, &secretmanagerpb.CreateSecretRequest{
Parent: fmt.Sprintf("projects/%s", c.store.ProjectID),
SecretId: remoteRef.GetRemoteKey(),
Secret: &secretmanagerpb.Secret{
Labels: map[string]string{
managedByKey: managedByValue,
},
Replication: &secretmanagerpb.Replication{
Replication: &secretmanagerpb.Replication_Automatic_{
Automatic: &secretmanagerpb.Replication_Automatic{},
},
},
},
})
metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMCreateSecret, err)
if err != nil {
return err
}
}
manager, ok := gcpSecret.Labels["managed-by"]
manager, ok := gcpSecret.Labels[managedByKey]
if !ok || manager != managedByValue {
if remoteRef.GetProperty() == "" {
return fmt.Errorf("secret %v is not managed by external secrets", remoteRef.GetRemoteKey())
}
if !ok || manager != "external-secrets" {
return fmt.Errorf("secret %v is not managed by external secrets", remoteRef.GetRemoteKey())
labels := map[string]string{}
for k, v := range gcpSecret.Labels {
labels[k] = v
}
labels[managedByKey] = managedByValue
_, err = c.smClient.UpdateSecret(ctx, &secretmanagerpb.UpdateSecretRequest{
Secret: &secretmanagerpb.Secret{
Name: gcpSecret.Name,
Labels: labels,
},
UpdateMask: &field_mask.FieldMask{
Paths: []string{"labels"},
},
})
metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMUpdateSecret, err)
if err != nil {
return err
}
}
gcpVersion, err := c.smClient.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
@ -177,32 +189,46 @@ func (c *Client) PushSecret(ctx context.Context, payload []byte, remoteRef esv1b
})
metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMAccessSecretVersion, err)
if errors.As(err, &gErr) {
if err != nil && gErr.GRPCStatus().Code() != codes.NotFound {
return err
}
} else if err != nil {
if err != nil && status.Code(err) != codes.NotFound {
return err
}
if gcpVersion != nil && gcpVersion.Payload != nil && bytes.Equal(payload, gcpVersion.Payload.Data) {
return nil
if gcpVersion != nil && gcpVersion.Payload != nil {
if remoteRef.GetProperty() == "" && bytes.Equal(payload, gcpVersion.Payload.Data) {
return nil
}
if remoteRef.GetProperty() != "" {
val := getDataByProperty(gcpVersion, remoteRef.GetProperty())
if val.Exists() && val.String() == string(payload) {
return nil
}
}
}
data := payload
if remoteRef.GetProperty() != "" {
var base []byte
if gcpVersion != nil && gcpVersion.Payload != nil {
base = gcpVersion.Payload.Data
}
data, err = sjson.SetBytes(base, remoteRef.GetProperty(), payload)
if err != nil {
return err
}
}
addSecretVersionReq := &secretmanagerpb.AddSecretVersionRequest{
Parent: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, remoteRef.GetRemoteKey()),
Payload: &secretmanagerpb.SecretPayload{
Data: payload,
Data: data,
},
}
_, err = c.smClient.AddSecretVersion(ctx, addSecretVersionReq)
metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMAddSecretVersion, err)
if err != nil {
return err
}
return nil
return err
}
// GetAllSecrets syncs multiple secrets from gcp provider into a single Kubernetes Secret.
@ -363,20 +389,7 @@ func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
return nil, fmt.Errorf("invalid secret received. no secret string for key: %s", ref.Key)
}
var payload string
if result.Payload.Data != nil {
payload = string(result.Payload.Data)
}
idx := strings.Index(ref.Property, ".")
refProperty := ref.Property
if idx > 0 {
refProperty = strings.ReplaceAll(refProperty, ".", "\\.")
val := gjson.Get(payload, refProperty)
if val.Exists() {
return []byte(val.String()), nil
}
}
val := gjson.Get(payload, ref.Property)
val := getDataByProperty(result, ref.Property)
if !val.Exists() {
return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
}
@ -509,3 +522,20 @@ func (c *Client) Validate() (esv1beta1.ValidationResult, error) {
}
return esv1beta1.ValidationResultReady, nil
}
func getDataByProperty(resp *secretmanagerpb.AccessSecretVersionResponse, property string) gjson.Result {
var payload string
if resp.Payload.Data != nil {
payload = string(resp.Payload.Data)
}
idx := strings.Index(property, ".")
refProperty := property
if idx > 0 {
refProperty = strings.ReplaceAll(refProperty, ".", "\\.")
val := gjson.Get(payload, refProperty)
if val.Exists() {
return val
}
}
return gjson.Get(payload, property)
}

View file

@ -22,11 +22,13 @@ import (
"testing"
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
"github.com/googleapis/gax-go/v2"
"github.com/googleapis/gax-go/v2/apierror"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/utils/pointer"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
fakesm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager/fake"
@ -197,7 +199,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
tests := []struct {
name string
ref esv1beta1.ExternalSecretDataRemoteRef
getSecretMockReturn fakesm.GetSecretMockReturn
getSecretMockReturn fakesm.SecretMockReturn
expectedSecret string
expectedErr string
}{
@ -208,7 +210,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
Property: "annotations.managed-by",
},
getSecretMockReturn: fakesm.GetSecretMockReturn{
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
Annotations: map[string]string{
@ -226,7 +228,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
Property: "labels.managed-by",
},
getSecretMockReturn: fakesm.GetSecretMockReturn{
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
Labels: map[string]string{
@ -244,7 +246,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
Property: "annotations",
},
getSecretMockReturn: fakesm.GetSecretMockReturn{
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
Annotations: map[string]string{
@ -267,7 +269,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
Property: "labels",
},
getSecretMockReturn: fakesm.GetSecretMockReturn{
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
Annotations: map[string]string{
@ -289,7 +291,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
Key: "bar",
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
},
getSecretMockReturn: fakesm.GetSecretMockReturn{
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
Labels: map[string]string{
@ -310,7 +312,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
Property: "annotations.unknown",
},
getSecretMockReturn: fakesm.GetSecretMockReturn{
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
Annotations: map[string]string{
@ -328,7 +330,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
Property: "labels.unknown",
},
getSecretMockReturn: fakesm.GetSecretMockReturn{
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
Labels: map[string]string{
@ -346,7 +348,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
Property: "invalid.managed-by",
},
getSecretMockReturn: fakesm.GetSecretMockReturn{
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
Labels: map[string]string{
@ -414,7 +416,7 @@ func TestDeleteSecret(t *testing.T) {
fakeClient := fakesm.MockSMClient{}
type args struct {
client fakesm.MockSMClient
getSecretOutput fakesm.GetSecretMockReturn
getSecretOutput fakesm.SecretMockReturn
deleteSecretErr error
}
type want struct {
@ -429,7 +431,7 @@ func TestDeleteSecret(t *testing.T) {
"Deletes Successfully": {
args: args{
client: fakeClient,
getSecretOutput: fakesm.GetSecretMockReturn{
getSecretOutput: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
@ -444,7 +446,7 @@ func TestDeleteSecret(t *testing.T) {
"Not Managed by ESO": {
args: args{
client: fakeClient,
getSecretOutput: fakesm.GetSecretMockReturn{
getSecretOutput: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Name: "projects/foo/secret/bar",
@ -457,7 +459,7 @@ func TestDeleteSecret(t *testing.T) {
"Secret Not Found": {
args: args{
client: fakeClient,
getSecretOutput: fakesm.GetSecretMockReturn{
getSecretOutput: fakesm.SecretMockReturn{
Secret: nil,
Err: notFoundError,
},
@ -466,7 +468,7 @@ func TestDeleteSecret(t *testing.T) {
"Random Error": {
args: args{
client: fakeClient,
getSecretOutput: fakesm.GetSecretMockReturn{
getSecretOutput: fakesm.SecretMockReturn{
Secret: nil,
Err: errors.New("This errored out"),
},
@ -478,7 +480,7 @@ func TestDeleteSecret(t *testing.T) {
"Random GError": {
args: args{
client: fakeClient,
getSecretOutput: fakesm.GetSecretMockReturn{
getSecretOutput: fakesm.SecretMockReturn{
Secret: nil,
Err: permissionDeniedError,
},
@ -515,7 +517,7 @@ func TestDeleteSecret(t *testing.T) {
}
}
func TestSetSecret(t *testing.T) {
func TestPushSecret(t *testing.T) {
ref := fakeRef{key: "/baz"}
notFoundError := status.Error(codes.NotFound, "failed")
@ -584,10 +586,10 @@ func TestSetSecret(t *testing.T) {
type args struct {
mock *fakesm.MockSMClient
GetSecretMockReturn fakesm.GetSecretMockReturn
GetSecretMockReturn fakesm.SecretMockReturn
AccessSecretVersionMockReturn fakesm.AccessSecretVersionMockReturn
AddSecretVersionMockReturn fakesm.AddSecretVersionMockReturn
CreateSecretMockReturn fakesm.CreateSecretMockReturn
CreateSecretMockReturn fakesm.SecretMockReturn
}
type want struct {
@ -602,7 +604,7 @@ func TestSetSecret(t *testing.T) {
reason: "SetSecret successfully pushes a secret",
args: args{
mock: smtc.mockClient,
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: &secret, Err: nil},
AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res, Err: nil},
AddSecretVersionMockReturn: fakesm.AddSecretVersionMockReturn{SecretVersion: &secretVersion, Err: nil}},
want: want{
@ -613,7 +615,7 @@ func TestSetSecret(t *testing.T) {
reason: "secret not pushed if AddSecretVersion errors",
args: args{
mock: smtc.mockClient,
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: &secret, Err: nil},
AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res, Err: nil},
AddSecretVersionMockReturn: fakesm.AddSecretVersionMockReturn{SecretVersion: nil, Err: APIerror},
},
@ -625,7 +627,7 @@ func TestSetSecret(t *testing.T) {
reason: "secret not pushed if AccessSecretVersion errors",
args: args{
mock: smtc.mockClient,
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: &secret, Err: nil},
AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: APIerror},
},
want: want{
@ -636,7 +638,7 @@ func TestSetSecret(t *testing.T) {
reason: "secret not pushed if not managed-by external-secrets",
args: args{
mock: smtc.mockClient,
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: &wrongLabelSecret, Err: nil},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: &wrongLabelSecret, Err: nil},
},
want: want{
err: labelError,
@ -647,7 +649,7 @@ func TestSetSecret(t *testing.T) {
args: args{
mock: smtc.mockClient,
AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res2, Err: nil},
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: &secret, Err: nil},
},
want: want{
err: nil,
@ -657,10 +659,10 @@ func TestSetSecret(t *testing.T) {
reason: "secret is created if one doesn't already exist",
args: args{
mock: smtc.mockClient,
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: nil, Err: notFoundError},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: nil, Err: notFoundError},
AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: notFoundError},
AddSecretVersionMockReturn: fakesm.AddSecretVersionMockReturn{SecretVersion: &secretVersion, Err: nil},
CreateSecretMockReturn: fakesm.CreateSecretMockReturn{Secret: &secret, Err: nil},
CreateSecretMockReturn: fakesm.SecretMockReturn{Secret: &secret, Err: nil},
},
want: want{
err: nil,
@ -670,8 +672,8 @@ func TestSetSecret(t *testing.T) {
reason: "secret not created if CreateSecret returns not found error",
args: args{
mock: smtc.mockClient,
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: nil, Err: notFoundError},
CreateSecretMockReturn: fakesm.CreateSecretMockReturn{Secret: &secret, Err: notFoundError},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: nil, Err: notFoundError},
CreateSecretMockReturn: fakesm.SecretMockReturn{Secret: &secret, Err: notFoundError},
},
want: want{
err: notFoundError,
@ -681,7 +683,7 @@ func TestSetSecret(t *testing.T) {
reason: "secret not created if CreateSecret returns error",
args: args{
mock: smtc.mockClient,
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: nil, Err: canceledError},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: nil, Err: canceledError},
},
want: want{
err: canceledError,
@ -691,7 +693,7 @@ func TestSetSecret(t *testing.T) {
reason: "access secret version for an existing secret returns error",
args: args{
mock: smtc.mockClient,
GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
GetSecretMockReturn: fakesm.SecretMockReturn{Secret: &secret, Err: nil},
AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: canceledError},
},
want: want{
@ -728,6 +730,202 @@ func TestSetSecret(t *testing.T) {
}
}
func TestPushSecret_Property(t *testing.T) {
defaultAddSecretVersionMockReturn := func(gotPayload, expectedPayload string) (*secretmanagerpb.SecretVersion, error) {
if gotPayload != expectedPayload {
t.Fatalf("payload does not match: got %s, expected: %s", gotPayload, expectedPayload)
}
return nil, nil
}
tests := []struct {
desc string
payload string
ref esv1beta1.PushRemoteRef
getSecretMockReturn fakesm.SecretMockReturn
createSecretMockReturn fakesm.SecretMockReturn
updateSecretMockReturn fakesm.SecretMockReturn
accessSecretVersionMockReturn fakesm.AccessSecretVersionMockReturn
addSecretVersionMockReturn func(gotPayload, expectedPayload string) (*secretmanagerpb.SecretVersion, error)
expectedPayload string
expectedErr string
}{
{
desc: "Add new key value paris",
payload: "testValue2",
ref: esv1alpha1.PushSecretRemoteRef{
Property: "testKey2",
},
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Labels: map[string]string{
managedByKey: managedByValue,
},
},
},
accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
Res: &secretmanagerpb.AccessSecretVersionResponse{
Payload: &secretmanagerpb.SecretPayload{
Data: []byte(`{"testKey1":"testValue1"}`),
},
},
},
addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
expectedPayload: `{"testKey1":"testValue1","testKey2":"testValue2"}`,
},
{
desc: "Update existing value",
payload: "testValue2",
ref: esv1alpha1.PushSecretRemoteRef{
Property: "testKey1.testKey2",
},
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Labels: map[string]string{
managedByKey: managedByValue,
},
},
},
accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
Res: &secretmanagerpb.AccessSecretVersionResponse{
Payload: &secretmanagerpb.SecretPayload{
Data: []byte(`{"testKey1":{"testKey2":"testValue1"}}`),
},
},
},
addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
expectedPayload: `{"testKey1":{"testKey2":"testValue2"}}`,
},
{
desc: "Secret not found",
payload: "testValue2",
ref: esv1alpha1.PushSecretRemoteRef{
Property: "testKey1.testKey3",
},
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{},
Err: status.Error(codes.NotFound, "failed to find a Secret"),
},
createSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Labels: map[string]string{managedByKey: managedByValue},
},
},
accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
Res: &secretmanagerpb.AccessSecretVersionResponse{
Payload: &secretmanagerpb.SecretPayload{
Data: []byte(`{"testKey1":{"testKey2":"testValue1"}}`),
},
},
},
addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
expectedPayload: `{"testKey1":{"testKey2":"testValue1","testKey3":"testValue2"}}`,
},
{
desc: "Secret version is not found",
payload: "testValue1",
ref: esv1alpha1.PushSecretRemoteRef{
Property: "testKey1",
},
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Labels: map[string]string{managedByKey: managedByValue},
},
},
accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
Err: status.Error(codes.NotFound, "failed to find a Secret Version"),
},
addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
expectedPayload: `{"testKey1":"testValue1"}`,
},
{
desc: "Secret is not managed by the controller",
payload: "testValue1",
ref: esv1alpha1.PushSecretRemoteRef{
Property: "testKey1.testKey2",
},
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{},
},
updateSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Labels: map[string]string{managedByKey: managedByValue},
},
},
accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
Res: &secretmanagerpb.AccessSecretVersionResponse{
Payload: &secretmanagerpb.SecretPayload{
Data: []byte(""),
},
},
},
addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
expectedPayload: `{"testKey1":{"testKey2":"testValue1"}}`,
},
{
desc: "Payload is the same with the existing one",
payload: "testValue1",
ref: esv1alpha1.PushSecretRemoteRef{
Property: "testKey1.testKey2",
},
getSecretMockReturn: fakesm.SecretMockReturn{
Secret: &secretmanagerpb.Secret{
Labels: map[string]string{
managedByKey: managedByValue,
},
},
},
accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
Res: &secretmanagerpb.AccessSecretVersionResponse{
Payload: &secretmanagerpb.SecretPayload{
Data: []byte(`{"testKey1":{"testKey2":"testValue1"}}`),
},
},
},
addSecretVersionMockReturn: func(gotPayload, expectedPayload string) (*secretmanagerpb.SecretVersion, error) {
return nil, errors.New("should not be called")
},
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
smClient := &fakesm.MockSMClient{
AddSecretFn: func(_ context.Context, req *secretmanagerpb.AddSecretVersionRequest, _ ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
return tc.addSecretVersionMockReturn(string(req.Payload.Data), tc.expectedPayload)
},
}
smClient.NewGetSecretFn(tc.getSecretMockReturn)
smClient.NewCreateSecretFn(tc.createSecretMockReturn)
smClient.NewUpdateSecretFn(tc.updateSecretMockReturn)
smClient.NewAccessSecretVersionFn(tc.accessSecretVersionMockReturn)
client := Client{
smClient: smClient,
store: &esv1beta1.GCPSMProvider{},
}
err := client.PushSecret(context.Background(), []byte(tc.payload), tc.ref)
if err != nil {
if tc.expectedErr == "" {
t.Fatalf("PushSecret returns unexpected error: %v", err)
}
if !strings.Contains(err.Error(), tc.expectedErr) {
t.Fatalf("PushSecret returns unexpected error: %q is supposed to contain %q", err, tc.expectedErr)
}
return
}
if tc.expectedErr != "" {
t.Fatal("PushSecret is expected to return error but got nil")
}
})
}
}
func TestGetSecretMap(t *testing.T) {
// good case: default version & deserialization
setDeserialization := func(smtc *secretManagerTestCase) {

View file

@ -28,8 +28,9 @@ import (
type MockSMClient struct {
accessSecretFn func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
ListSecretsFn func(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) *secretmanager.SecretIterator
addSecretFn func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
AddSecretFn func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
createSecretFn func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
updateSecretFn func(ctx context.Context, req *secretmanagerpb.UpdateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
closeFn func() error
GetSecretFn func(ctx context.Context, req *secretmanagerpb.GetSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
DeleteSecretFn func(ctx context.Context, req *secretmanagerpb.DeleteSecretRequest, opts ...gax.CallOption) error
@ -45,12 +46,7 @@ type AddSecretVersionMockReturn struct {
Err error
}
type GetSecretMockReturn struct {
Secret *secretmanagerpb.Secret
Err error
}
type CreateSecretMockReturn struct {
type SecretMockReturn struct {
Secret *secretmanagerpb.Secret
Err error
}
@ -67,7 +63,7 @@ func (mc *MockSMClient) GetSecret(ctx context.Context, req *secretmanagerpb.GetS
return mc.GetSecretFn(ctx, req)
}
func (mc *MockSMClient) NewGetSecretFn(mock GetSecretMockReturn) {
func (mc *MockSMClient) NewGetSecretFn(mock SecretMockReturn) {
mc.GetSecretFn = func(_ context.Context, _ *secretmanagerpb.GetSecretRequest, _ ...gax.CallOption) (*secretmanagerpb.Secret, error) {
return mock.Secret, mock.Err
}
@ -91,11 +87,11 @@ func (mc *MockSMClient) Close() error {
}
func (mc *MockSMClient) AddSecretVersion(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, _ ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
return mc.addSecretFn(ctx, req)
return mc.AddSecretFn(ctx, req)
}
func (mc *MockSMClient) NewAddSecretVersionFn(mock AddSecretVersionMockReturn) {
mc.addSecretFn = func(_ context.Context, _ *secretmanagerpb.AddSecretVersionRequest, _ ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
mc.AddSecretFn = func(_ context.Context, _ *secretmanagerpb.AddSecretVersionRequest, _ ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
return mock.SecretVersion, mock.Err
}
}
@ -104,7 +100,7 @@ func (mc *MockSMClient) CreateSecret(ctx context.Context, req *secretmanagerpb.C
return mc.createSecretFn(ctx, req)
}
func (mc *MockSMClient) NewCreateSecretFn(mock CreateSecretMockReturn) {
func (mc *MockSMClient) NewCreateSecretFn(mock SecretMockReturn) {
mc.createSecretFn = func(_ context.Context, _ *secretmanagerpb.CreateSecretRequest, _ ...gax.CallOption) (*secretmanagerpb.Secret, error) {
return mock.Secret, mock.Err
}
@ -146,7 +142,7 @@ func (mc *MockSMClient) DefaultCreateSecret(wantedSecretID, wantedParent string)
}
func (mc *MockSMClient) DefaultAddSecretVersion(wantedData, wantedParent, versionName string) {
mc.addSecretFn = func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
mc.AddSecretFn = func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
if string(req.Payload.Data) != wantedData {
return nil, fmt.Errorf("add version req wrong data got: %v want %v ", req.Payload.Data, wantedData)
}
@ -177,6 +173,16 @@ func (mc *MockSMClient) AccessSecretVersionWithError(err error) {
}
}
func (mc *MockSMClient) UpdateSecret(ctx context.Context, req *secretmanagerpb.UpdateSecretRequest, _ ...gax.CallOption) (*secretmanagerpb.Secret, error) {
return mc.updateSecretFn(ctx, req)
}
func (mc *MockSMClient) NewUpdateSecretFn(mock SecretMockReturn) {
mc.updateSecretFn = func(_ context.Context, _ *secretmanagerpb.UpdateSecretRequest, _ ...gax.CallOption) (*secretmanagerpb.Secret, error) {
return mock.Secret, mock.Err
}
}
func (mc *MockSMClient) WithValue(_ context.Context, req *secretmanagerpb.AccessSecretVersionRequest, val *secretmanagerpb.AccessSecretVersionResponse, err error) {
if mc != nil {
mc.accessSecretFn = func(paramCtx context.Context, paramReq *secretmanagerpb.AccessSecretVersionRequest, paramOpts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {