Implment a secret store based on Huawei CSMS (#1710)
Signed-off-by: Chen Cong <chock-cong.chen@outlook.com> Co-authored-by: Chen Cong <chock-cong.chen@outlook.com>
This commit is contained in:
		
							parent
							
								
									e0ea27f2c1
								
							
						
					
					
						commit
						b818c2f83e
					
				
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -158,6 +158,7 @@ require ( | |||
| ) | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.87 | ||||
| 	github.com/labd/commercetools-go-sdk v0.3.2 | ||||
| 	github.com/nacos-group/nacos-sdk-go/v2 v2.0.1 | ||||
| 	gopkg.in/couchbase/gocb.v1 v1.6.4 | ||||
|  |  | |||
							
								
								
									
										3
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										3
									
								
								go.sum
								
								
								
								
							|  | @ -756,6 +756,8 @@ github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKEN | |||
| github.com/hazelcast/hazelcast-go-client v0.0.0-20190530123621-6cf767c2f31a h1:j6SSiw7fWemWfrJL801xiQ6xRT7ZImika50xvmPN+tg= | ||||
| github.com/hazelcast/hazelcast-go-client v0.0.0-20190530123621-6cf767c2f31a/go.mod h1:VhwtcZ7sg3xq7REqGzEy7ylSWGKz4jZd05eCJropNzI= | ||||
| github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||||
| github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.87 h1:+7mrZ+YXKTILEBXOXl5fNhXLx9HqBq/OdUEI/603B34= | ||||
| github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.87/go.mod h1:IvF+Pe06JMUivVgN6B4wcsPEoFvVa40IYaOPZyUt5HE= | ||||
| github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= | ||||
| github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= | ||||
| github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||
|  | @ -1429,6 +1431,7 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 | |||
| golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= | ||||
| golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ Currently supported secret stores are: | |||
| * GCP Cloud KMS | ||||
| * GCP Secret Manager | ||||
| * AlibabaCloud OOS Parameter Store | ||||
| * HuaweiCloud CSMS | ||||
| 
 | ||||
| ## Implementing a new Secret Store | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,142 @@ | |||
| /* | ||||
| Copyright 2021 The Dapr Authors | ||||
| 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 csms | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" | ||||
| 	csms "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/csms/v1" | ||||
| 	"github.com/huaweicloud/huaweicloud-sdk-go-v3/services/csms/v1/model" | ||||
| 	csmsRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/csms/v1/region" | ||||
| 
 | ||||
| 	"github.com/dapr/components-contrib/secretstores" | ||||
| 	"github.com/dapr/kit/logger" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	region          string = "region" | ||||
| 	accessKey       string = "accessKey" | ||||
| 	secretAccessKey string = "secretAccessKey" | ||||
| 	pageLimit       string = "100" | ||||
| 	latestVersion   string = "latest" | ||||
| 	versionId       string = "version_id" | ||||
| ) | ||||
| 
 | ||||
| type csmsClient interface { | ||||
| 	ListSecrets(request *model.ListSecretsRequest) (*model.ListSecretsResponse, error) | ||||
| 	ShowSecretVersion(request *model.ShowSecretVersionRequest) (*model.ShowSecretVersionResponse, error) | ||||
| } | ||||
| 
 | ||||
| type csmsSecretStore struct { | ||||
| 	client csmsClient | ||||
| 	logger logger.Logger | ||||
| } | ||||
| 
 | ||||
| // NewHuaweiCsmsSecretStore returns a new Huawei csms secret store.
 | ||||
| func NewHuaweiCsmsSecretStore(logger logger.Logger) secretstores.SecretStore { | ||||
| 	return &csmsSecretStore{logger: logger} | ||||
| } | ||||
| 
 | ||||
| // Init creates a Huawei csms client.
 | ||||
| func (c *csmsSecretStore) Init(metadata secretstores.Metadata) error { | ||||
| 	auth := basic.NewCredentialsBuilder(). | ||||
| 		WithAk(metadata.Properties[accessKey]). | ||||
| 		WithSk(metadata.Properties[secretAccessKey]). | ||||
| 		Build() | ||||
| 
 | ||||
| 	c.client = csms.NewCsmsClient( | ||||
| 		csms.CsmsClientBuilder(). | ||||
| 			WithRegion(csmsRegion.ValueOf(metadata.Properties[region])). | ||||
| 			WithCredential(auth). | ||||
| 			Build()) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GetSecret retrieves a secret using a key and returns a map of decrypted string/string values.
 | ||||
| func (c *csmsSecretStore) GetSecret(req secretstores.GetSecretRequest) (secretstores.GetSecretResponse, error) { | ||||
| 	request := &model.ShowSecretVersionRequest{} | ||||
| 	request.SecretName = req.Name | ||||
| 	if value, ok := req.Metadata[versionId]; ok { | ||||
| 		request.VersionId = value | ||||
| 	} | ||||
| 
 | ||||
| 	response, err := c.client.ShowSecretVersion(request) | ||||
| 	if err != nil { | ||||
| 		return secretstores.GetSecretResponse{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return secretstores.GetSecretResponse{ | ||||
| 		Data: map[string]string{ | ||||
| 			req.Name: *response.Version.SecretString, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // BulkGetSecret retrieves all secrets in the store and returns a map of decrypted string/string values.
 | ||||
| func (c *csmsSecretStore) BulkGetSecret(req secretstores.BulkGetSecretRequest) (secretstores.BulkGetSecretResponse, error) { | ||||
| 	secretNames, err := c.getSecretNames(nil) | ||||
| 	if err != nil { | ||||
| 		return secretstores.BulkGetSecretResponse{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	resp := secretstores.BulkGetSecretResponse{ | ||||
| 		Data: map[string]map[string]string{}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, secretName := range secretNames { | ||||
| 		secret, err := c.GetSecret(secretstores.GetSecretRequest{ | ||||
| 			Name: secretName, | ||||
| 			Metadata: map[string]string{ | ||||
| 				versionId: latestVersion, | ||||
| 			}, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return secretstores.BulkGetSecretResponse{}, err | ||||
| 		} | ||||
| 
 | ||||
| 		resp.Data[secretName] = secret.Data | ||||
| 	} | ||||
| 
 | ||||
| 	return resp, nil | ||||
| } | ||||
| 
 | ||||
| // Get all secret names recursively.
 | ||||
| func (c *csmsSecretStore) getSecretNames(marker *string) ([]string, error) { | ||||
| 	request := &model.ListSecretsRequest{} | ||||
| 	limit := pageLimit | ||||
| 	request.Limit = &limit | ||||
| 	request.Marker = marker | ||||
| 
 | ||||
| 	response, err := c.client.ListSecrets(request) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	resp := make([]string, 0, len(*response.Secrets)) | ||||
| 	for _, secret := range *response.Secrets { | ||||
| 		resp = append(resp, *secret.Name) | ||||
| 	} | ||||
| 
 | ||||
| 	// If the NextMarker has value then continue to retrieve data from next page.
 | ||||
| 	if response.PageInfo.NextMarker != nil { | ||||
| 		nextResp, err := c.getSecretNames(response.PageInfo.NextMarker) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		resp = append(resp, nextResp...) | ||||
| 	} | ||||
| 
 | ||||
| 	return resp, nil | ||||
| } | ||||
|  | @ -0,0 +1,163 @@ | |||
| /* | ||||
| Copyright 2021 The Dapr Authors | ||||
| 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 csms | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/huaweicloud/huaweicloud-sdk-go-v3/services/csms/v1/model" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 
 | ||||
| 	"github.com/dapr/components-contrib/secretstores" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	secretName  = "secret-name" | ||||
| 	secretValue = "secret-value" | ||||
| ) | ||||
| 
 | ||||
| type mockedCsmsSecretStore struct { | ||||
| 	csmsClient | ||||
| } | ||||
| 
 | ||||
| func (m *mockedCsmsSecretStore) ListSecrets(request *model.ListSecretsRequest) (*model.ListSecretsResponse, error) { | ||||
| 	name := secretName | ||||
| 	return &model.ListSecretsResponse{ | ||||
| 		Secrets: &[]model.Secret{ | ||||
| 			{ | ||||
| 				Name: &name, | ||||
| 			}, | ||||
| 		}, | ||||
| 		PageInfo: &model.PageInfo{ | ||||
| 			NextMarker: nil, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (m *mockedCsmsSecretStore) ShowSecretVersion(request *model.ShowSecretVersionRequest) (*model.ShowSecretVersionResponse, error) { | ||||
| 	secretString := secretValue | ||||
| 	return &model.ShowSecretVersionResponse{ | ||||
| 		Version: &model.Version{ | ||||
| 			SecretString: &secretString, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| type mockedCsmsSecretStoreReturnError struct { | ||||
| 	csmsClient | ||||
| } | ||||
| 
 | ||||
| func (m *mockedCsmsSecretStoreReturnError) ListSecrets(request *model.ListSecretsRequest) (*model.ListSecretsResponse, error) { | ||||
| 	name := secretName | ||||
| 	return &model.ListSecretsResponse{ | ||||
| 		Secrets: &[]model.Secret{ | ||||
| 			{ | ||||
| 				Name: &name, | ||||
| 			}, | ||||
| 		}, | ||||
| 		PageInfo: &model.PageInfo{ | ||||
| 			NextMarker: nil, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (m *mockedCsmsSecretStoreReturnError) ShowSecretVersion(request *model.ShowSecretVersionRequest) (*model.ShowSecretVersionResponse, error) { | ||||
| 	return nil, fmt.Errorf("mocked error") | ||||
| } | ||||
| 
 | ||||
| type mockedCsmsSecretStoreBothReturnError struct { | ||||
| 	csmsClient | ||||
| } | ||||
| 
 | ||||
| func (m *mockedCsmsSecretStoreBothReturnError) ListSecrets(request *model.ListSecretsRequest) (*model.ListSecretsResponse, error) { | ||||
| 	return nil, fmt.Errorf("mocked error") | ||||
| } | ||||
| 
 | ||||
| func (m *mockedCsmsSecretStoreBothReturnError) ShowSecretVersion(request *model.ShowSecretVersionRequest) (*model.ShowSecretVersionResponse, error) { | ||||
| 	return nil, fmt.Errorf("mocked error") | ||||
| } | ||||
| 
 | ||||
| func TestGetSecret(t *testing.T) { | ||||
| 	t.Run("successfully get secret", func(t *testing.T) { | ||||
| 		c := csmsSecretStore{ | ||||
| 			client: &mockedCsmsSecretStore{}, | ||||
| 		} | ||||
| 
 | ||||
| 		req := secretstores.GetSecretRequest{ | ||||
| 			Name: secretName, | ||||
| 			Metadata: map[string]string{ | ||||
| 				"version_id": "v1", | ||||
| 			}, | ||||
| 		} | ||||
| 
 | ||||
| 		resp, e := c.GetSecret(req) | ||||
| 		assert.Nil(t, e) | ||||
| 		assert.Equal(t, secretValue, resp.Data[req.Name]) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("unsuccessfully get secret", func(t *testing.T) { | ||||
| 		c := csmsSecretStore{ | ||||
| 			client: &mockedCsmsSecretStoreBothReturnError{}, | ||||
| 		} | ||||
| 
 | ||||
| 		req := secretstores.GetSecretRequest{ | ||||
| 			Name:     secretName, | ||||
| 			Metadata: map[string]string{}, | ||||
| 		} | ||||
| 
 | ||||
| 		_, e := c.GetSecret(req) | ||||
| 		assert.NotNil(t, e) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func TestBulkGetSecret(t *testing.T) { | ||||
| 	t.Run("successfully bulk get secret", func(t *testing.T) { | ||||
| 		c := csmsSecretStore{ | ||||
| 			client: &mockedCsmsSecretStore{}, | ||||
| 		} | ||||
| 
 | ||||
| 		req := secretstores.BulkGetSecretRequest{} | ||||
| 		expectedSecrets := map[string]map[string]string{ | ||||
| 			secretName: { | ||||
| 				secretName: secretValue, | ||||
| 			}, | ||||
| 		} | ||||
| 		resp, e := c.BulkGetSecret(req) | ||||
| 		assert.Nil(t, e) | ||||
| 		assert.Equal(t, expectedSecrets, resp.Data) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("unsuccessfully bulk get secret", func(t *testing.T) { | ||||
| 		t.Run("with failed to retrieve list of secrets", func(t *testing.T) { | ||||
| 			c := csmsSecretStore{ | ||||
| 				client: &mockedCsmsSecretStoreBothReturnError{}, | ||||
| 			} | ||||
| 
 | ||||
| 			req := secretstores.BulkGetSecretRequest{} | ||||
| 			_, e := c.BulkGetSecret(req) | ||||
| 			assert.NotNil(t, e) | ||||
| 		}) | ||||
| 
 | ||||
| 		t.Run("with failed to retrieve the secret", func(t *testing.T) { | ||||
| 			c := csmsSecretStore{ | ||||
| 				client: &mockedCsmsSecretStoreReturnError{}, | ||||
| 			} | ||||
| 
 | ||||
| 			req := secretstores.BulkGetSecretRequest{} | ||||
| 			_, e := c.BulkGetSecret(req) | ||||
| 			assert.NotNil(t, e) | ||||
| 		}) | ||||
| 	}) | ||||
| } | ||||
		Loading…
	
		Reference in New Issue