Browse Source

Merge pull request #360 from ElsaChelala/alibabacloud

Alibaba Provider
paul-the-alien[bot] 4 years ago
parent
commit
913854762a

+ 2 - 0
README.md

@@ -19,6 +19,7 @@ Multiple people and organizations are joining efforts to create a single Externa
 - [IBM Cloud Secrets Manager](https://external-secrets.io/provider-ibm-secrets-manager/)
 - [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/)
 - [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/)
+- [Alibaba Cloud KMS](https://www.alibabacloud.com/product/kms) (Docs still missing, PRs welcomed!)
 
 ## Stability and Support Level
 
@@ -39,6 +40,7 @@ Multiple people and organizations are joining efforts to create a single Externa
 | [IBM SM](https://external-secrets.io/provider-ibm-secrets-manager/) |   alpha   |   @knelasevero @sebagomez @ricardoptcosta  |
 | [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/) |   alpha   |   @AndreyZamyslov @knelasevero          |
 | [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/) |   alpha   |   @Jabray5          |
+| Alibaba Cloud KMS                                                    |   alpha   |            @ElsaChelala                   |
 
 ## Documentation
 

+ 41 - 0
apis/externalsecrets/v1alpha1/secretstore_alibaba_types.go

@@ -0,0 +1,41 @@
+/*
+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"
+)
+
+// AlibabaAuth contains a secretRef for credentials.
+type AlibabaAuth struct {
+	SecretRef AlibabaAuthSecretRef `json:"secretRef"`
+}
+
+// AlibabaAuthSecretRef holds secret references for Alibaba credentials.
+type AlibabaAuthSecretRef struct {
+	// The AccessKeyID is used for authentication
+	AccessKeyID esmeta.SecretKeySelector `json:"accessKeyIDSecretRef"`
+	// The AccessKeySecret is used for authentication
+	AccessKeySecret esmeta.SecretKeySelector `json:"accessKeySecretSecretRef"`
+}
+
+// AlibabaProvider configures a store to sync secrets using the Alibaba Secret Manager provider.
+type AlibabaProvider struct {
+	Auth *AlibabaAuth `json:"auth"`
+	// +optional
+	Endpoint string `json:"endpoint"`
+	// Alibaba Region to be used for the provider
+	RegionID string `json:"regionID"`
+}

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

@@ -61,6 +61,10 @@ type SecretStoreProvider struct {
 	// GItlab configures this store to sync secrets using Gitlab Variables provider
 	// +optional
 	Gitlab *GitlabProvider `json:"gitlab,omitempty"`
+
+	// Alibaba configures this store to sync secrets using Alibaba Cloud provider
+	// +optional
+	Alibaba *AlibabaProvider `json:"alibaba,omitempty"`
 }
 
 type SecretStoreConditionType string

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

@@ -103,6 +103,59 @@ func (in *AWSProvider) DeepCopy() *AWSProvider {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AlibabaAuth) DeepCopyInto(out *AlibabaAuth) {
+	*out = *in
+	in.SecretRef.DeepCopyInto(&out.SecretRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlibabaAuth.
+func (in *AlibabaAuth) DeepCopy() *AlibabaAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(AlibabaAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AlibabaAuthSecretRef) DeepCopyInto(out *AlibabaAuthSecretRef) {
+	*out = *in
+	in.AccessKeyID.DeepCopyInto(&out.AccessKeyID)
+	in.AccessKeySecret.DeepCopyInto(&out.AccessKeySecret)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlibabaAuthSecretRef.
+func (in *AlibabaAuthSecretRef) DeepCopy() *AlibabaAuthSecretRef {
+	if in == nil {
+		return nil
+	}
+	out := new(AlibabaAuthSecretRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AlibabaProvider) DeepCopyInto(out *AlibabaProvider) {
+	*out = *in
+	if in.Auth != nil {
+		in, out := &in.Auth, &out.Auth
+		*out = new(AlibabaAuth)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlibabaProvider.
+func (in *AlibabaProvider) DeepCopy() *AlibabaProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(AlibabaProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AzureKVAuth) DeepCopyInto(out *AzureKVAuth) {
 	*out = *in
 	if in.ClientID != nil {
@@ -702,6 +755,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(GitlabProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Alibaba != nil {
+		in, out := &in.Alibaba, &out.Alibaba
+		*out = new(AlibabaProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.

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

@@ -54,6 +54,73 @@ spec:
                 maxProperties: 1
                 minProperties: 1
                 properties:
+                  alibaba:
+                    description: Alibaba configures this store to sync secrets using
+                      Alibaba Cloud provider
+                    properties:
+                      auth:
+                        description: AlibabaAuth contains a secretRef for credentials.
+                        properties:
+                          secretRef:
+                            description: AlibabaAuthSecretRef holds secret references
+                              for Alibaba credentials.
+                            properties:
+                              accessKeyIDSecretRef:
+                                description: The AccessKeyID 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
+                                type: object
+                              accessKeySecretSecretRef:
+                                description: The AccessKeySecret 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
+                                type: object
+                            required:
+                            - accessKeyIDSecretRef
+                            - accessKeySecretSecretRef
+                            type: object
+                        required:
+                        - secretRef
+                        type: object
+                      endpoint:
+                        type: string
+                      regionID:
+                        description: Alibaba Region to be used for the provider
+                        type: string
+                    required:
+                    - auth
+                    - regionID
+                    type: object
                   aws:
                     description: AWS configures this store to sync secrets using AWS
                       Secret Manager provider

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

@@ -54,6 +54,73 @@ spec:
                 maxProperties: 1
                 minProperties: 1
                 properties:
+                  alibaba:
+                    description: Alibaba configures this store to sync secrets using
+                      Alibaba Cloud provider
+                    properties:
+                      auth:
+                        description: AlibabaAuth contains a secretRef for credentials.
+                        properties:
+                          secretRef:
+                            description: AlibabaAuthSecretRef holds secret references
+                              for Alibaba credentials.
+                            properties:
+                              accessKeyIDSecretRef:
+                                description: The AccessKeyID 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
+                                type: object
+                              accessKeySecretSecretRef:
+                                description: The AccessKeySecret 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
+                                type: object
+                            required:
+                            - accessKeyIDSecretRef
+                            - accessKeySecretSecretRef
+                            type: object
+                        required:
+                        - secretRef
+                        type: object
+                      endpoint:
+                        type: string
+                      regionID:
+                        description: Alibaba Region to be used for the provider
+                        type: string
+                    required:
+                    - auth
+                    - regionID
+                    type: object
                   aws:
                     description: AWS configures this store to sync secrets using AWS
                       Secret Manager provider

BIN
e2e/.DS_Store


+ 47 - 0
e2e/suite/alibaba/alibaba.go

@@ -0,0 +1,47 @@
+/*
+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 alibaba
+
+import (
+	"os"
+
+	// nolint
+	. "github.com/onsi/ginkgo"
+	// nolint
+	. "github.com/onsi/ginkgo/extensions/table"
+
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/suite/common"
+)
+
+var _ = Describe("[alibaba] ", func() {
+	f := framework.New("eso-alibaba")
+	accessKeyID := os.Getenv("ACCESS_KEY_ID")
+	accessKeySecret := os.Getenv("ACCESS_KEY_SECRET")
+	regionID := os.Getenv("REGION_ID")
+	prov := newAlibabaProvider(f, accessKeyID, accessKeySecret, regionID)
+
+	DescribeTable("sync secrets", framework.TableFunc(f, prov),
+		Entry(common.SimpleDataSync(f)),
+		Entry(common.NestedJSONWithGJSON(f)),
+		Entry(common.JSONDataFromSync(f)),
+		Entry(common.JSONDataWithProperty(f)),
+		Entry(common.JSONDataWithTemplate(f)),
+		Entry(common.DockerJSONConfig(f)),
+		Entry(common.DataPropertyDockerconfigJSON(f)),
+		Entry(common.SSHKeySync(f)),
+		Entry(common.SSHKeySyncDataProperty(f)),
+	)
+})

+ 118 - 0
e2e/suite/alibaba/provider.go

@@ -0,0 +1,118 @@
+/*
+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 alibaba
+
+import (
+	"context"
+
+	"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+
+	//nolint
+	. "github.com/onsi/ginkgo"
+
+	//nolint
+	. "github.com/onsi/gomega"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+)
+
+type alibabaProvider struct {
+	accessKeyID     string
+	accessKeySecret string
+	regionID        string
+	framework       *framework.Framework
+}
+
+const (
+	secretName = "secretName"
+)
+
+func newAlibabaProvider(f *framework.Framework, accessKeyID, accessKeySecret, regionID string) *alibabaProvider {
+	prov := &alibabaProvider{
+		accessKeyID:     accessKeyID,
+		accessKeySecret: accessKeySecret,
+		regionID:        regionID,
+		framework:       f,
+	}
+	BeforeEach(prov.BeforeEach)
+	return prov
+}
+
+// CreateSecret creates a secret in both kv v1 and v2 provider.
+func (s *alibabaProvider) CreateSecret(key, val string) {
+	client, err := kms.NewClientWithAccessKey(s.regionID, s.accessKeyID, s.accessKeySecret)
+	Expect(err).ToNot(HaveOccurred())
+	kmssecretrequest := kms.CreateCreateSecretRequest()
+	kmssecretrequest.SecretName = secretName
+	kmssecretrequest.SecretData = "value"
+	_, err = client.CreateSecret(kmssecretrequest)
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func (s *alibabaProvider) DeleteSecret(key string) {
+	client, err := kms.NewClientWithAccessKey(s.regionID, s.accessKeyID, s.accessKeySecret)
+	Expect(err).ToNot(HaveOccurred())
+	kmssecretrequest := kms.CreateDeleteSecretRequest()
+	kmssecretrequest.SecretName = secretName
+	_, err = client.DeleteSecret(kmssecretrequest)
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func (s *alibabaProvider) BeforeEach() {
+	// Creating an Alibaba secret
+	alibabaCreds := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: s.framework.Namespace.Name,
+		},
+		StringData: map[string]string{
+			secretName: "value",
+		},
+	}
+	err := s.framework.CRClient.Create(context.Background(), alibabaCreds)
+	Expect(err).ToNot(HaveOccurred())
+
+	// Creating Alibaba secret store
+	secretStore := &esv1alpha1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      s.framework.Namespace.Name,
+			Namespace: s.framework.Namespace.Name,
+		},
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				Alibaba: &esv1alpha1.AlibabaProvider{
+					Auth: &esv1alpha1.AlibabaAuth{
+						SecretRef: esv1alpha1.AlibabaAuthSecretRef{
+							AccessKeyID: esmeta.SecretKeySelector{
+								Name: "kms-secret",
+								Key:  "keyid",
+							},
+							AccessKeySecret: esmeta.SecretKeySelector{
+								Name: "kms-secret",
+								Key:  "accesskey",
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	err = s.framework.CRClient.Create(context.Background(), secretStore)
+	Expect(err).ToNot(HaveOccurred())
+}

+ 5 - 5
go.mod

@@ -6,7 +6,6 @@ replace (
 	github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1 => ./apis/externalsecrets/v1alpha1
 	github.com/external-secrets/external-secrets/pkg/provider/gitlab => ./pkg/provider/gitlab
 	google.golang.org/grpc => google.golang.org/grpc v1.27.0
-
 	k8s.io/api => k8s.io/api v0.21.2
 	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.21.2
 	k8s.io/apimachinery => k8s.io/apimachinery v0.21.2
@@ -41,6 +40,7 @@ require (
 	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
 	github.com/IBM/go-sdk-core/v5 v5.5.0
 	github.com/IBM/secrets-manager-go-sdk v1.0.23
+	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1192
 	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
@@ -58,7 +58,7 @@ require (
 	github.com/kr/pretty v0.2.1 // indirect
 	github.com/lestrrat-go/jwx v1.2.1
 	github.com/onsi/ginkgo v1.16.4
-	github.com/onsi/gomega v1.13.0
+	github.com/onsi/gomega v1.16.0
 	github.com/pierrec/lz4 v2.5.2+incompatible // indirect
 	github.com/prometheus/client_golang v1.11.0
 	github.com/prometheus/client_model v0.2.0
@@ -78,11 +78,11 @@ require (
 	google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a
 	google.golang.org/grpc v1.31.0
 	honnef.co/go/tools v0.1.4 // indirect
-	k8s.io/api v0.21.2
-	k8s.io/apimachinery v0.21.2
+	k8s.io/api v0.21.3
+	k8s.io/apimachinery v0.21.3
 	k8s.io/client-go v0.21.2
 	k8s.io/utils v0.0.0-20210527160623-6fdb442a123b
-	sigs.k8s.io/controller-runtime v0.9.2
+	sigs.k8s.io/controller-runtime v0.9.3
 	sigs.k8s.io/controller-tools v0.5.0
 	software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
 )

+ 15 - 3
go.sum

@@ -77,6 +77,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.1192 h1:rRuMCkcoxoQ/kWSBN190JmD292PrYnpl7KyRWhYrjnY=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.1192/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -245,6 +247,7 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
@@ -325,6 +328,7 @@ github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3i
 github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
 github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
 github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -406,6 +410,7 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -413,6 +418,7 @@ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMW
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
@@ -536,8 +542,9 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
-github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
 github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
+github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
+github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -600,7 +607,10 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -1055,6 +1065,8 @@ gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+a
 gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
@@ -1120,8 +1132,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
 sigs.k8s.io/controller-runtime v0.8.0/go.mod h1:v9Lbj5oX443uR7GXYY46E0EE2o7k2YxQ58GxVNeXSW4=
-sigs.k8s.io/controller-runtime v0.9.2 h1:MnCAsopQno6+hI9SgJHKddzXpmv2wtouZz6931Eax+Q=
-sigs.k8s.io/controller-runtime v0.9.2/go.mod h1:TxzMCHyEUpaeuOiZx/bIdc2T81vfs/aKdvJt9wuu0zk=
+sigs.k8s.io/controller-runtime v0.9.3 h1:n075bHQ1wb8hpX7C27pNrqsb0fj8mcfCQfNX+oKTbYE=
+sigs.k8s.io/controller-runtime v0.9.3/go.mod h1:TxzMCHyEUpaeuOiZx/bIdc2T81vfs/aKdvJt9wuu0zk=
 sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA=
 sigs.k8s.io/controller-tools v0.5.0 h1:3u2RCwOlp0cjCALAigpOcbAf50pE+kHSdueUosrC/AE=
 sigs.k8s.io/controller-tools v0.5.0/go.mod h1:JTsstrMpxs+9BUj6eGuAaEb6SDSPTeVtUyp0jmnAM/I=

+ 35 - 0
pkg/provider/alibaba/fake/fake.go

@@ -0,0 +1,35 @@
+/*
+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 (
+	kmssdk "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+)
+
+type AlibabaMockClient struct {
+	getSecretValue func(request *kmssdk.GetSecretValueRequest) (response *kmssdk.GetSecretValueResponse, err error)
+}
+
+func (mc *AlibabaMockClient) GetSecretValue(*kmssdk.GetSecretValueRequest) (result *kmssdk.GetSecretValueResponse, err error) {
+	return mc.getSecretValue(&kmssdk.GetSecretValueRequest{})
+}
+
+func (mc *AlibabaMockClient) WithValue(in *kmssdk.GetSecretValueRequest, val *kmssdk.GetSecretValueResponse, err error) {
+	if mc != nil {
+		mc.getSecretValue = func(paramIn *kmssdk.GetSecretValueRequest) (*kmssdk.GetSecretValueResponse, error) {
+			return val, err
+		}
+	}
+}

+ 193 - 0
pkg/provider/alibaba/kms.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 alibaba
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+
+	kmssdk "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+	"github.com/tidwall/gjson"
+	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/aws/util"
+	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+)
+
+const (
+	errAlibabaClient                           = "cannot setup new Alibaba client: %w"
+	errAlibabaCredSecretName                   = "invalid Alibaba SecretStore resource: missing Alibaba APIKey"
+	errUninitalizedAlibabaProvider             = "provider Alibaba is not initialized"
+	errInvalidClusterStoreMissingAKIDNamespace = "invalid ClusterStore, missing  AccessKeyID namespace"
+	errInvalidClusterStoreMissingSKNamespace   = "invalid ClusterStore, missing namespace"
+	errFetchAKIDSecret                         = "could not fetch AccessKeyID secret: %w"
+	errMissingSAK                              = "missing AccessSecretKey"
+	errMissingAKID                             = "missing AccessKeyID"
+)
+
+type Client struct {
+	kube      kclient.Client
+	store     *esv1alpha1.AlibabaProvider
+	namespace string
+	storeKind string
+	regionID  string
+	keyID     []byte
+	accessKey []byte
+}
+
+type KeyManagementService struct {
+	Client SMInterface
+}
+
+type SMInterface interface {
+	GetSecretValue(request *kmssdk.GetSecretValueRequest) (response *kmssdk.GetSecretValueResponse, err error)
+}
+
+// setAuth creates a new Alibaba session based on a store.
+func (c *Client) setAuth(ctx context.Context) error {
+	credentialsSecret := &corev1.Secret{}
+	credentialsSecretName := c.store.Auth.SecretRef.AccessKeyID.Name
+	if credentialsSecretName == "" {
+		return fmt.Errorf(errAlibabaCredSecretName)
+	}
+	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.AccessKeyID.Namespace == nil {
+			return fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
+		}
+		objectKey.Namespace = *c.store.Auth.SecretRef.AccessKeyID.Namespace
+	}
+
+	err := c.kube.Get(ctx, objectKey, credentialsSecret)
+	if err != nil {
+		return fmt.Errorf(errFetchAKIDSecret, err)
+	}
+
+	objectKey = types.NamespacedName{
+		Name:      c.store.Auth.SecretRef.AccessKeySecret.Name,
+		Namespace: c.namespace,
+	}
+	if c.storeKind == esv1alpha1.ClusterSecretStoreKind {
+		if c.store.Auth.SecretRef.AccessKeySecret.Namespace == nil {
+			return fmt.Errorf(errInvalidClusterStoreMissingSKNamespace)
+		}
+		objectKey.Namespace = *c.store.Auth.SecretRef.AccessKeySecret.Namespace
+	}
+	c.keyID = credentialsSecret.Data[c.store.Auth.SecretRef.AccessKeyID.Key]
+	fmt.Println(c.keyID)
+	fmt.Println(c.accessKey)
+	if (c.keyID == nil) || (len(c.keyID) == 0) {
+		return fmt.Errorf(errMissingAKID)
+	}
+	c.accessKey = credentialsSecret.Data[c.store.Auth.SecretRef.AccessKeySecret.Key]
+	if (c.accessKey == nil) || (len(c.accessKey) == 0) {
+		return fmt.Errorf(errMissingSAK)
+	}
+	c.regionID = c.store.RegionID
+	return nil
+}
+
+// GetSecret returns a single secret from the provider.
+func (kms *KeyManagementService) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	if utils.IsNil(kms.Client) {
+		return nil, fmt.Errorf(errUninitalizedAlibabaProvider)
+	}
+	kmsRequest := kmssdk.CreateGetSecretValueRequest()
+	kmsRequest.VersionId = ref.Version
+	kmsRequest.SecretName = ref.Key
+	kmsRequest.SetScheme("https")
+	secretOut, err := kms.Client.GetSecretValue(kmsRequest)
+	if err != nil {
+		return nil, util.SanitizeErr(err)
+	}
+	if ref.Property == "" {
+		if secretOut.SecretData != "" {
+			return []byte(secretOut.SecretData), nil
+		}
+		return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
+	}
+	var payload string
+	if secretOut.SecretData != "" {
+		payload = secretOut.SecretData
+	}
+	val := gjson.Get(payload, ref.Property)
+	if !val.Exists() {
+		return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
+	}
+	return []byte(val.String()), nil
+}
+
+// GetSecretMap returns multiple k/v pairs from the provider.
+func (kms *KeyManagementService) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	data, err := kms.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("unable to unmarshal secret %s: %w", ref.Key, err)
+	}
+	secretData := make(map[string][]byte)
+	for k, v := range kv {
+		secretData[k] = []byte(v)
+	}
+	return secretData, nil
+}
+
+// NewClient constructs a new secrets client based on the provided store.
+func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+	storeSpec := store.GetSpec()
+	alibabaSpec := storeSpec.Provider.Alibaba
+	iStore := &Client{
+		kube:      kube,
+		store:     alibabaSpec,
+		namespace: namespace,
+		storeKind: store.GetObjectKind().GroupVersionKind().Kind,
+	}
+	if err := iStore.setAuth(ctx); err != nil {
+		return nil, err
+	}
+	alibabaRegion := iStore.regionID
+	alibabaKeyID := iStore.keyID
+	alibabaSecretKey := iStore.accessKey
+	keyManagementService, err := kmssdk.NewClientWithAccessKey(alibabaRegion, string(alibabaKeyID), string(alibabaSecretKey))
+	if err != nil {
+		return nil, fmt.Errorf(errAlibabaClient, err)
+	}
+	kms.Client = keyManagementService
+	return kms, nil
+}
+
+func (kms *KeyManagementService) Close(ctx context.Context) error {
+	return nil
+}
+
+func init() {
+	schema.Register(&KeyManagementService{}, &esv1alpha1.SecretStoreProvider{
+		Alibaba: &esv1alpha1.AlibabaProvider{},
+	})
+}

+ 197 - 0
pkg/provider/alibaba/kms_test.go

@@ -0,0 +1,197 @@
+/*
+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 alibaba
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
+	kmssdk "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	fakesm "github.com/external-secrets/external-secrets/pkg/provider/alibaba/fake"
+)
+
+const (
+	secretName  = "test-example"
+	secretValue = "value"
+)
+
+type keyManagementServiceTestCase struct {
+	mockClient     *fakesm.AlibabaMockClient
+	apiInput       *kmssdk.GetSecretValueRequest
+	apiOutput      *kmssdk.GetSecretValueResponse
+	ref            *esv1alpha1.ExternalSecretDataRemoteRef
+	apiErr         error
+	expectError    string
+	expectedSecret string
+	// for testing secretmap
+	expectedData map[string][]byte
+}
+
+func makeValidKMSTestCase() *keyManagementServiceTestCase {
+	kmstc := keyManagementServiceTestCase{
+		mockClient:     &fakesm.AlibabaMockClient{},
+		apiInput:       makeValidAPIInput(),
+		ref:            makeValidRef(),
+		apiOutput:      makeValidAPIOutput(),
+		apiErr:         nil,
+		expectError:    "",
+		expectedSecret: "",
+		expectedData:   make(map[string][]byte),
+	}
+	kmstc.mockClient.WithValue(kmstc.apiInput, kmstc.apiOutput, kmstc.apiErr)
+	return &kmstc
+}
+
+func makeValidRef() *esv1alpha1.ExternalSecretDataRemoteRef {
+	return &esv1alpha1.ExternalSecretDataRemoteRef{
+		Key: secretName,
+	}
+}
+
+func makeValidAPIInput() *kmssdk.GetSecretValueRequest {
+	return &kmssdk.GetSecretValueRequest{
+		SecretName: secretName,
+	}
+}
+
+func makeValidAPIOutput() *kmssdk.GetSecretValueResponse {
+	kmsresponse := &kmssdk.GetSecretValueResponse{
+		BaseResponse:      &responses.BaseResponse{},
+		RequestId:         "",
+		SecretName:        secretName,
+		VersionId:         "",
+		CreateTime:        "",
+		SecretData:        secretValue,
+		SecretDataType:    "",
+		AutomaticRotation: "",
+		RotationInterval:  "",
+		NextRotationDate:  "",
+		ExtendedConfig:    "",
+		LastRotationDate:  "",
+		SecretType:        "",
+		VersionStages:     kmssdk.VersionStagesInGetSecretValue{},
+	}
+	return kmsresponse
+}
+
+func makeValidKMSTestCaseCustom(tweaks ...func(kmstc *keyManagementServiceTestCase)) *keyManagementServiceTestCase {
+	kmstc := makeValidKMSTestCase()
+	for _, fn := range tweaks {
+		fn(kmstc)
+	}
+	kmstc.mockClient.WithValue(kmstc.apiInput, kmstc.apiOutput, kmstc.apiErr)
+	return kmstc
+}
+
+var setAPIErr = func(kmstc *keyManagementServiceTestCase) {
+	kmstc.apiErr = fmt.Errorf("oh no")
+	kmstc.expectError = "oh no"
+}
+
+var setNilMockClient = func(kmstc *keyManagementServiceTestCase) {
+	kmstc.mockClient = nil
+	kmstc.expectError = errUninitalizedAlibabaProvider
+}
+
+func TestAlibabaKMSGetSecret(t *testing.T) {
+	secretData := make(map[string]interface{})
+	secretValue := "changedvalue"
+	secretData["payload"] = secretValue
+
+	// good case: default version is set
+	// key is passed in, output is sent back
+	setSecretString := func(kmstc *keyManagementServiceTestCase) {
+		kmstc.apiOutput.SecretName = secretName
+		kmstc.apiOutput.SecretData = secretValue
+		kmstc.expectedSecret = secretValue
+	}
+
+	// good case: custom version set
+	setCustomKey := func(kmstc *keyManagementServiceTestCase) {
+		kmstc.apiOutput.SecretName = "test-example-other"
+		kmstc.ref.Key = "test-example-other"
+		kmstc.apiOutput.SecretData = secretValue
+		kmstc.expectedSecret = secretValue
+	}
+
+	successCases := []*keyManagementServiceTestCase{
+		makeValidKMSTestCaseCustom(setSecretString),
+		makeValidKMSTestCaseCustom(setCustomKey),
+		makeValidKMSTestCaseCustom(setAPIErr),
+		makeValidKMSTestCaseCustom(setNilMockClient),
+	}
+
+	sm := KeyManagementService{}
+	for k, v := range successCases {
+		sm.Client = 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(kmstc *keyManagementServiceTestCase) {
+		kmstc.apiOutput.SecretName = "foo"
+		kmstc.expectedData["foo"] = []byte("bar")
+		kmstc.apiOutput.SecretData = `{"foo":"bar"}`
+	}
+
+	// bad case: invalid json
+	setInvalidJSON := func(kmstc *keyManagementServiceTestCase) {
+		kmstc.apiOutput.SecretData = "-----------------"
+		kmstc.expectError = "unable to unmarshal secret"
+	}
+
+	successCases := []*keyManagementServiceTestCase{
+		makeValidKMSTestCaseCustom(setDeserialization),
+		makeValidKMSTestCaseCustom(setInvalidJSON),
+		makeValidKMSTestCaseCustom(setNilMockClient),
+		makeValidKMSTestCaseCustom(setAPIErr),
+	}
+
+	sm := KeyManagementService{}
+	for k, v := range successCases {
+		sm.Client = 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 err == nil && !reflect.DeepEqual(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

@@ -17,6 +17,7 @@ package register
 // packages imported here are registered to the controller schema.
 // nolint:golint
 import (
+	_ "github.com/external-secrets/external-secrets/pkg/provider/alibaba"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/aws"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"