Browse Source

Merge pull request #144 from gabibeyer/add-gcpsm-provider

Add gcpsm provider
paul-the-alien[bot] 4 years ago
parent
commit
576f8aed39

+ 1 - 1
README.md

@@ -14,8 +14,8 @@ Multiple people and organizations are joining efforts to create a single Externa
 - [AWS Secrets Manager](https://external-secrets.io/provider-aws-secrets-manager/)
 - [AWS Parameter Store](https://external-secrets.io/provider-aws-parameter-store/)
 - [Hashicorp Vault](https://www.vaultproject.io/)
+- [Google Cloud Secrets Manager](https://external-secrets.io/provider-google-secrets-manager/)
 - [Azure Key Vault](https://external-secrets.io/provider-azure-key-vault/) (being implemented)
-- [Google Cloud Secrets Manager](https://external-secrets.io/provider-google-secrets-manager/) (being implemented)
 
 ## ESO installation with an AWS example
 

+ 38 - 0
apis/externalsecrets/v1alpha1/secretstore_gcpsm_types.go

@@ -0,0 +1,38 @@
+/*
+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 v1alpha1
+
+import (
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+type GCPSMAuth struct {
+	SecretRef GCPSMAuthSecretRef `json:"secretRef"`
+}
+
+type GCPSMAuthSecretRef struct {
+	// The SecretAccessKey is used for authentication
+	// +optional
+	SecretAccessKey esmeta.SecretKeySelector `json:"secretAccessKeySecretRef,omitempty"`
+}
+
+// GCPSMProvider Configures a store to sync secrets using the GCP Secret Manager provider.
+type GCPSMProvider struct {
+	// Auth defines the information necessary to authenticate against GCP
+	Auth GCPSMAuth `json:"auth"`
+
+	// ProjectID project where secret is located
+	ProjectID string `json:"projectID,omitempty"`
+}

+ 4 - 0
apis/externalsecrets/v1alpha1/secretstore_types.go

@@ -41,6 +41,10 @@ type SecretStoreProvider struct {
 	// Vault configures this store to sync secrets using Hashi provider
 	// +optional
 	Vault *VaultProvider `json:"vault,omitempty"`
+
+	// GCPSM configures this store to sync secrets using Google Cloud Platform Secret Manager provider
+	// +optional
+	GCPSM *GCPSMProvider `json:"gcpsm,omitempty"`
 }
 
 type SecretStoreConditionType string

+ 53 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -369,6 +369,54 @@ func (in *ExternalSecretTemplateMetadata) DeepCopy() *ExternalSecretTemplateMeta
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPSMAuth) DeepCopyInto(out *GCPSMAuth) {
+	*out = *in
+	in.SecretRef.DeepCopyInto(&out.SecretRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPSMAuth.
+func (in *GCPSMAuth) DeepCopy() *GCPSMAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPSMAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPSMAuthSecretRef) DeepCopyInto(out *GCPSMAuthSecretRef) {
+	*out = *in
+	in.SecretAccessKey.DeepCopyInto(&out.SecretAccessKey)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPSMAuthSecretRef.
+func (in *GCPSMAuthSecretRef) DeepCopy() *GCPSMAuthSecretRef {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPSMAuthSecretRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPSMProvider) DeepCopyInto(out *GCPSMProvider) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPSMProvider.
+func (in *GCPSMProvider) DeepCopy() *GCPSMProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPSMProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SecretStore) DeepCopyInto(out *SecretStore) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
@@ -440,6 +488,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(VaultProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.GCPSM != nil {
+		in, out := &in.GCPSM, &out.GCPSM
+		*out = new(GCPSMProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.

+ 42 - 0
deploy/crds/external-secrets.io_clustersecretstores.yaml

@@ -135,6 +135,48 @@ spec:
                     - region
                     - service
                     type: object
+                  gcpsm:
+                    description: GCPSM configures this store to sync secrets using
+                      Google Cloud Platform Secret Manager provider
+                    properties:
+                      auth:
+                        description: Auth defines the information necessary to authenticate
+                          against GCP
+                        properties:
+                          secretRef:
+                            properties:
+                              secretAccessKeySecretRef:
+                                description: The SecretAccessKey is used for authentication
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                            type: object
+                        required:
+                        - secretRef
+                        type: object
+                      projectID:
+                        description: ProjectID project where secret is located
+                        type: string
+                    required:
+                    - auth
+                    type: object
                   vault:
                     description: Vault configures this store to sync secrets using
                       Hashi provider

+ 42 - 0
deploy/crds/external-secrets.io_secretstores.yaml

@@ -135,6 +135,48 @@ spec:
                     - region
                     - service
                     type: object
+                  gcpsm:
+                    description: GCPSM configures this store to sync secrets using
+                      Google Cloud Platform Secret Manager provider
+                    properties:
+                      auth:
+                        description: Auth defines the information necessary to authenticate
+                          against GCP
+                        properties:
+                          secretRef:
+                            properties:
+                              secretAccessKeySecretRef:
+                                description: The SecretAccessKey is used for authentication
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                            type: object
+                        required:
+                        - secretRef
+                        type: object
+                      projectID:
+                        description: ProjectID project where secret is located
+                        type: string
+                    required:
+                    - auth
+                    type: object
                   vault:
                     description: Vault configures this store to sync secrets using
                       Hashi provider

+ 34 - 4
docs/provider-google-secrets-manager.md

@@ -1,5 +1,35 @@
+## Google Cloud Secret Manager
 
-!!! bug "Not implemented"
-    This is currently **not yet** implemented. Feel free to contribute.
-    Please see [issue#33](https://github.com/external-secrets/external-secrets/issues/33)
-    for futher information.
+External Secrets Operator integrates with [GCP Secret Manager](https://cloud.google.com/secret-manager) for secret management.
+
+### Authentication
+
+At the moment, we only support [service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) authentication.
+
+#### Service account key authentication
+
+A service account key is created and the JSON keyfile is stored in a `Kind=Secret`. The `project_id` and `private_key` should be configured for the project.
+
+```yaml
+{% include 'gcpsm-credentials-secret.yaml' %}
+```
+
+### Update secret store
+Be sure the `gcpsm` provider is listed in the `Kind=SecretStore`
+
+```yaml
+{% include 'gcpsm-secret-store.yaml' %}
+```
+
+### Creating external secret
+
+To create a kubernetes secret from the GCP Secret Manager secret a `Kind=ExternalSecret` is needed.
+
+```yaml
+{% include 'gcpsm-external-secret.yaml' %}
+```
+
+The operator will fetch the GCP Secret Manager secret and inject it as a `Kind=Secret`
+```
+kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath='{.data.example-externalsecret-key}' | base64 -d
+```

+ 10 - 0
docs/snippets/full-secret-store.yaml

@@ -75,6 +75,16 @@ spec:
             namespace: "secret-admin"
             key: "vault"
 
+    # (2): GCP Secret Manager
+    gcpsm:
+      # Auth defines the information necessary to authenticate against GCP by getting
+      # the credentials from an already created Kubernetes Secret.
+      auth:
+        secretRef:
+          secretAccessKeySecretRef:
+            name: gcpsm-secret
+            key: secret-access-credentials
+      projectID: myproject
     # (TODO): add more provider examples here
 
 status:

+ 21 - 0
docs/snippets/gcpsm-credentials-secret.yaml

@@ -0,0 +1,21 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: gcpsm-secret
+  labels:
+    type: gcpsm
+type: Opaque
+stringData:
+  secret-access-credentials: |-
+    {
+      "type": "service_account",
+      "project_id": "external-secrets-operator",
+      "private_key_id": "",
+      "private_key": "-----BEGIN PRIVATE KEY-----\nA key\n-----END PRIVATE KEY-----\n",
+      "client_email": "test-service-account@external-secrets-operator.iam.gserviceaccount.com",
+      "client_id": "client ID",
+      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+      "token_uri": "https://oauth2.googleapis.com/token",
+      "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+      "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-service-account%40external-secrets-operator.iam.gserviceaccount.com"
+    }

+ 16 - 0
docs/snippets/gcpsm-external-secret.yaml

@@ -0,0 +1,16 @@
+apiVersion: external-secrets.io/v1alpha1
+kind: ExternalSecret
+metadata:
+  name: example
+spec:
+  refreshInterval: 1h           # rate SecretManager pulls GCPSM
+  secretStoreRef:
+    kind: SecretStore
+    name: example               # name of the SecretStore (or kind specified)
+  target:
+    name: secret-to-be-created  # name of the k8s Secret to be created
+    creationPolicy: Owner
+  data:
+  - secretKey: dev-secret-test  # name of the GCPSM secret key
+    remoteRef:
+      key: dev-secret-test

+ 13 - 0
docs/snippets/gcpsm-secret-store.yaml

@@ -0,0 +1,13 @@
+apiVersion: external-secrets.io/v1alpha1
+kind: SecretStore
+metadata:
+  name: example
+spec:
+  provider:
+      gcpsm:                                  # gcpsm provider
+        auth:
+          secretRef:
+            secretAccessKeySecretRef:
+              name: gcpsm-secret              # secret name containing SA key
+              key: secret-access-credentials  # key name containing SA key
+        projectID: myproject                  # name of Google Cloud project

+ 117 - 0
docs/spec.md

@@ -888,6 +888,109 @@ map[string]string
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1alpha1.GCPSMAuth">GCPSMAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.GCPSMProvider">GCPSMProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>secretRef</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.GCPSMAuthSecretRef">
+GCPSMAuthSecretRef
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1alpha1.GCPSMAuthSecretRef">GCPSMAuthSecretRef
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.GCPSMAuth">GCPSMAuth</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>secretAccessKeySecretRef</code></br>
+<em>
+github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>The SecretAccessKey is used for authentication</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1alpha1.GCPSMProvider">GCPSMProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>GCPSMProvider Configures a store to sync secrets using the GCP Secret Manager provider.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.GCPSMAuth">
+GCPSMAuth
+</a>
+</em>
+</td>
+<td>
+<p>Auth defines the information necessary to authenticate against GCP</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>projectID</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>ProjectID project where secret is located</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1alpha1.GenericStore">GenericStore
 </h3>
 <p>
@@ -1041,6 +1144,20 @@ VaultProvider
 <p>Vault configures this store to sync secrets using Hashi provider</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>gcpsm</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.GCPSMProvider">
+GCPSMProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>GCPSM configures this store to sync secrets using Google Cloud Platform Secret Manager provider</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1alpha1.SecretStoreRef">SecretStoreRef

+ 5 - 1
go.mod

@@ -32,6 +32,7 @@ replace (
 )
 
 require (
+	cloud.google.com/go v0.65.0
 	github.com/aws/aws-sdk-go v1.38.6
 	github.com/crossplane/crossplane-runtime v0.13.0
 	github.com/fatih/color v1.10.0 // indirect
@@ -41,6 +42,7 @@ require (
 	github.com/google/go-cmp v0.5.4
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/uuid v1.2.0 // indirect
+	github.com/googleapis/gax-go v1.0.3
 	github.com/googleapis/gnostic v0.5.4 // indirect
 	github.com/hashicorp/go-hclog v0.14.1 // indirect
 	github.com/hashicorp/go-retryablehttp v0.6.7 // indirect
@@ -61,11 +63,13 @@ require (
 	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 	golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
-	golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c // indirect
+	golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c
 	golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
 	golang.org/x/text v0.3.5 // indirect
 	golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
+	google.golang.org/api v0.30.0
 	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a
 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 	k8s.io/api v0.21.0

+ 13 - 0
go.sum

@@ -194,6 +194,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -257,7 +258,11 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go v1.0.3 h1:9dMLqhaibYONnDRcnHdUs9P8Mw64jLlZTYlDe3leBtQ=
+github.com/googleapis/gax-go v1.0.3/go.mod h1:QyXYajJFdARxGzjwUfbDFIse7Spkw81SJ4LrBJXtlQ8=
+github.com/googleapis/gax-go/v2 v2.0.2/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
 github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
@@ -608,6 +613,7 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -643,6 +649,7 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190221220918-438050ddec5e/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
@@ -651,9 +658,11 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -811,6 +820,7 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -886,6 +896,7 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
 google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
 google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -926,7 +937,9 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=
 google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=

+ 54 - 0
pkg/provider/gcp/secretmanager/fake/fake.go

@@ -0,0 +1,54 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package fake
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	grpc "github.com/googleapis/gax-go"
+	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
+)
+
+type MockSMClient struct {
+	accessSecretFn func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...grpc.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
+	closeFn        func() error
+}
+
+func (mc *MockSMClient) AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...grpc.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
+	return mc.accessSecretFn(ctx, req)
+}
+
+func (mc *MockSMClient) Close() error {
+	return mc.closeFn()
+}
+
+func (mc *MockSMClient) NilClose() {
+	mc.closeFn = func() error {
+		return nil
+	}
+}
+
+func (mc *MockSMClient) WithValue(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, val *secretmanagerpb.AccessSecretVersionResponse, err error) {
+	mc.accessSecretFn = func(paramCtx context.Context, paramReq *secretmanagerpb.AccessSecretVersionRequest, paramOpts ...grpc.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
+		// type secretmanagerpb.AccessSecretVersionRequest contains unexported fields
+		// use cmpopts.IgnoreUnexported to ignore all the unexported fields in the cmp.
+		if !cmp.Equal(paramReq, req, cmpopts.IgnoreUnexported(secretmanagerpb.AccessSecretVersionRequest{})) {
+			return nil, fmt.Errorf("unexpected test argument")
+		}
+		return val, err
+	}
+}

+ 193 - 0
pkg/provider/gcp/secretmanager/secretsmanager.go

@@ -0,0 +1,193 @@
+/*
+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 (
+	"context"
+	"encoding/json"
+	"fmt"
+
+	secretmanager "cloud.google.com/go/secretmanager/apiv1"
+	"github.com/googleapis/gax-go"
+	"golang.org/x/oauth2/google"
+	"google.golang.org/api/option"
+	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	"github.com/external-secrets/external-secrets/pkg/provider"
+	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+)
+
+const (
+	cloudPlatformRole = "https://www.googleapis.com/auth/cloud-platform"
+	defaultVersion    = "latest"
+
+	errGCPSMStore                             = "received invalid GCPSM SecretStore resource"
+	errGCPSMCredSecretName                    = "invalid GCPSM SecretStore resource: missing GCP Secret Access Key"
+	errInvalidClusterStoreMissingSAKNamespace = "invalid ClusterSecretStore: missing GCP SecretAccessKey Namespace"
+	errFetchSAKSecret                         = "could not fetch SecretAccessKey secret: %w"
+	errMissingSAK                             = "missing SecretAccessKey"
+	errUnableProcessJSONCredentials           = "failed to process the provided JSON credentials: %w"
+	errUnableCreateGCPSMClient                = "failed to create GCP secretmanager client: %w"
+	errUninitalizedGCPProvider                = "provider GCP is not initialized"
+	errClientGetSecretAccess                  = "unable to access Secret from SecretManager Client: %w"
+	errClientClose                            = "unable to close SecretManager client: %w"
+	errJSONSecretUnmarshal                    = "unable to unmarshal secret: %w"
+)
+
+type GoogleSecretManagerClient interface {
+	AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
+	Close() error
+}
+
+// ProviderGCP is a provider for GCP Secret Manager.
+type ProviderGCP struct {
+	projectID           string
+	SecretManagerClient GoogleSecretManagerClient
+}
+
+type gClient struct {
+	kube        kclient.Client
+	store       *esv1alpha1.GCPSMProvider
+	namespace   string
+	storeKind   string
+	credentials []byte
+}
+
+func (c *gClient) setAuth(ctx context.Context) error {
+	credentialsSecret := &corev1.Secret{}
+	credentialsSecretName := c.store.Auth.SecretRef.SecretAccessKey.Name
+	if credentialsSecretName == "" {
+		return fmt.Errorf(errGCPSMCredSecretName)
+	}
+	objectKey := types.NamespacedName{
+		Name:      credentialsSecretName,
+		Namespace: c.namespace,
+	}
+
+	// only ClusterStore is allowed to set namespace (and then it's required)
+	if c.storeKind == esv1alpha1.ClusterSecretStoreKind {
+		if c.store.Auth.SecretRef.SecretAccessKey.Namespace == nil {
+			return fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
+		}
+		objectKey.Namespace = *c.store.Auth.SecretRef.SecretAccessKey.Namespace
+	}
+
+	err := c.kube.Get(ctx, objectKey, credentialsSecret)
+	if err != nil {
+		return fmt.Errorf(errFetchSAKSecret, err)
+	}
+
+	c.credentials = credentialsSecret.Data[c.store.Auth.SecretRef.SecretAccessKey.Key]
+	if (c.credentials == nil) || (len(c.credentials) == 0) {
+		return fmt.Errorf(errMissingSAK)
+	}
+	return nil
+}
+
+// NewClient constructs a GCP Provider.
+func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+	storeSpec := store.GetSpec()
+	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
+		return nil, fmt.Errorf(errGCPSMStore)
+	}
+	storeSpecGCPSM := storeSpec.Provider.GCPSM
+
+	cliStore := gClient{
+		kube:      kube,
+		store:     storeSpecGCPSM,
+		namespace: namespace,
+		storeKind: store.GetObjectKind().GroupVersionKind().Kind,
+	}
+
+	if err := cliStore.setAuth(ctx); err != nil {
+		return nil, err
+	}
+
+	sm.projectID = cliStore.store.ProjectID
+
+	config, err := google.JWTConfigFromJSON(cliStore.credentials, cloudPlatformRole)
+	if err != nil {
+		return nil, fmt.Errorf(errUnableProcessJSONCredentials, err)
+	}
+	ts := config.TokenSource(ctx)
+
+	clientGCPSM, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
+	if err != nil {
+		return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
+	}
+	sm.SecretManagerClient = clientGCPSM
+	return sm, nil
+}
+
+// GetSecret returns a single secret from the provider.
+func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	if sm.SecretManagerClient == nil || sm.projectID == "" {
+		return nil, fmt.Errorf(errUninitalizedGCPProvider)
+	}
+
+	version := ref.Version
+	if version == "" {
+		version = defaultVersion
+	}
+
+	req := &secretmanagerpb.AccessSecretVersionRequest{
+		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", sm.projectID, ref.Key, version),
+	}
+	result, err := sm.SecretManagerClient.AccessSecretVersion(ctx, req)
+	if err != nil {
+		return nil, fmt.Errorf(errClientGetSecretAccess, err)
+	}
+
+	err = sm.SecretManagerClient.Close()
+	if err != nil {
+		return nil, fmt.Errorf(errClientClose, err)
+	}
+
+	return result.Payload.Data, nil
+}
+
+// GetSecretMap returns multiple k/v pairs from the provider.
+func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	if sm.SecretManagerClient == nil || sm.projectID == "" {
+		return nil, fmt.Errorf(errUninitalizedGCPProvider)
+	}
+
+	data, err := sm.GetSecret(ctx, ref)
+	if err != nil {
+		return nil, err
+	}
+
+	kv := make(map[string]string)
+	err = json.Unmarshal(data, &kv)
+	if err != nil {
+		return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+
+	secretData := make(map[string][]byte)
+	for k, v := range kv {
+		secretData[k] = []byte(v)
+	}
+
+	return secretData, nil
+}
+
+func init() {
+	schema.Register(&ProviderGCP{}, &esv1alpha1.SecretStoreProvider{
+		GCPSM: &esv1alpha1.GCPSMProvider{},
+	})
+}

+ 176 - 0
pkg/provider/gcp/secretmanager/secretsmanager_test.go

@@ -0,0 +1,176 @@
+/*
+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 (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	fakesm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager/fake"
+)
+
+type secretManagerTestCase struct {
+	mockClient     *fakesm.MockSMClient
+	apiInput       *secretmanagerpb.AccessSecretVersionRequest
+	apiOutput      *secretmanagerpb.AccessSecretVersionResponse
+	ref            *esv1alpha1.ExternalSecretDataRemoteRef
+	projectID      string
+	apiErr         error
+	expectError    string
+	expectedSecret string
+	// for testing secretmap
+	expectedData map[string]string
+}
+
+func makeValidSecretManagerTestCase() *secretManagerTestCase {
+	smtc := secretManagerTestCase{
+		mockClient:     &fakesm.MockSMClient{},
+		apiInput:       makeValidAPIInput(),
+		ref:            makeValidRef(),
+		apiOutput:      makeValidAPIOutput(),
+		projectID:      "default",
+		apiErr:         nil,
+		expectError:    "",
+		expectedSecret: "",
+		expectedData:   map[string]string{},
+	}
+	smtc.mockClient.NilClose()
+	smtc.mockClient.WithValue(context.Background(), smtc.apiInput, smtc.apiOutput, smtc.apiErr)
+	return &smtc
+}
+
+func makeValidRef() *esv1alpha1.ExternalSecretDataRemoteRef {
+	return &esv1alpha1.ExternalSecretDataRemoteRef{
+		Key:     "/baz",
+		Version: "default",
+	}
+}
+
+func makeValidAPIInput() *secretmanagerpb.AccessSecretVersionRequest {
+	return &secretmanagerpb.AccessSecretVersionRequest{
+		Name: "projects/default/secrets//baz/versions/default",
+	}
+}
+
+func makeValidAPIOutput() *secretmanagerpb.AccessSecretVersionResponse {
+	return &secretmanagerpb.AccessSecretVersionResponse{
+		Payload: &secretmanagerpb.SecretPayload{
+			Data: []byte{},
+		},
+	}
+}
+
+func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
+	smtc := makeValidSecretManagerTestCase()
+	for _, fn := range tweaks {
+		fn(smtc)
+	}
+	smtc.mockClient.WithValue(context.Background(), smtc.apiInput, smtc.apiOutput, smtc.apiErr)
+	return smtc
+}
+
+// This case can be shared by both GetSecret and GetSecretMap tests.
+// bad case: set apiErr.
+var setAPIErr = func(smtc *secretManagerTestCase) {
+	smtc.apiErr = fmt.Errorf("oh no")
+	smtc.expectError = "oh no"
+}
+
+// test the sm<->gcp interface
+// make sure correct values are passed and errors are handled accordingly.
+func TestSecretManagerGetSecret(t *testing.T) {
+	// good case: default version is set
+	// key is passed in, output is sent back
+	setSecretString := func(smtc *secretManagerTestCase) {
+		smtc.apiOutput.Payload.Data = []byte("testtesttest")
+		smtc.expectedSecret = "testtesttest"
+	}
+
+	// good case: custom version set
+	setCustomVersion := func(smtc *secretManagerTestCase) {
+		smtc.ref.Version = "1234"
+		smtc.apiInput.Name = "projects/default/secrets//baz/versions/1234"
+		smtc.apiOutput.Payload.Data = []byte("FOOBA!")
+		smtc.expectedSecret = "FOOBA!"
+	}
+
+	successCases := []*secretManagerTestCase{
+		makeValidSecretManagerTestCase(),
+		makeValidSecretManagerTestCaseCustom(setSecretString),
+		makeValidSecretManagerTestCaseCustom(setCustomVersion),
+		makeValidSecretManagerTestCaseCustom(setAPIErr),
+	}
+
+	sm := ProviderGCP{}
+	for k, v := range successCases {
+		sm.projectID = v.projectID
+		sm.SecretManagerClient = v.mockClient
+		out, err := sm.GetSecret(context.Background(), *v.ref)
+		if !ErrorContains(err, v.expectError) {
+			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
+		}
+		if string(out) != v.expectedSecret {
+			t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
+		}
+	}
+}
+
+func TestGetSecretMap(t *testing.T) {
+	// good case: default version & deserialization
+	setDeserialization := func(smtc *secretManagerTestCase) {
+		smtc.apiOutput.Payload.Data = []byte(`{"foo":"bar"}`)
+		smtc.expectedData["foo"] = "bar"
+	}
+
+	// bad case: invalid json
+	setInvalidJSON := func(smtc *secretManagerTestCase) {
+		smtc.apiOutput.Payload.Data = []byte(`-----------------`)
+		smtc.expectError = "unable to unmarshal secret"
+	}
+
+	successCases := []*secretManagerTestCase{
+		makeValidSecretManagerTestCaseCustom(setDeserialization),
+		makeValidSecretManagerTestCaseCustom(setAPIErr),
+		makeValidSecretManagerTestCaseCustom(setInvalidJSON),
+	}
+
+	sm := ProviderGCP{}
+	for k, v := range successCases {
+		sm.projectID = v.projectID
+		sm.SecretManagerClient = v.mockClient
+		out, err := sm.GetSecretMap(context.Background(), *v.ref)
+		if !ErrorContains(err, v.expectError) {
+			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
+		}
+		if cmp.Equal(out, v.expectedData) {
+			t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
+		}
+	}
+}
+
+func ErrorContains(out error, want string) bool {
+	if out == nil {
+		return want == ""
+	}
+	if want == "" {
+		return false
+	}
+	return strings.Contains(out.Error(), want)
+}

+ 1 - 0
pkg/provider/register/register.go

@@ -18,5 +18,6 @@ package register
 // nolint:golint
 import (
 	_ "github.com/external-secrets/external-secrets/pkg/provider/aws"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/vault"
 )

+ 24 - 0
pkg/provider/schema/schema_test.go

@@ -141,3 +141,27 @@ func TestForceRegister(t *testing.T) {
 	assert.Nil(t, err)
 	assert.Equal(t, testProvider, p2)
 }
+
+func TestRegisterGCP(t *testing.T) {
+	p, ok := GetProviderByName("gcpsm")
+	assert.Nil(t, p)
+	assert.False(t, ok, "provider should not be registered")
+
+	testProvider := &PP{}
+	secretStore := &esv1alpha1.SecretStore{
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				GCPSM: &esv1alpha1.GCPSMProvider{},
+			},
+		},
+	}
+
+	ForceRegister(testProvider, secretStore.Spec.Provider)
+	p1, ok := GetProviderByName("gcpsm")
+	assert.True(t, ok, "provider should be registered")
+	assert.Equal(t, testProvider, p1)
+
+	p2, err := GetProvider(secretStore)
+	assert.Nil(t, err)
+	assert.Equal(t, testProvider, p2)
+}