Bläddra i källkod

Implement GetAllSecrets for Azure Key Vault

Sebastián Gómez 4 år sedan
förälder
incheckning
02a8878707

+ 8 - 0
pkg/provider/azure/keyvault/fake/fake.go

@@ -65,3 +65,11 @@ func (mc *AzureMockClient) WithCertificate(serviceURL, secretName, secretVersion
 		}
 		}
 	}
 	}
 }
 }
+
+func (mc *AzureMockClient) WithList(serviceURL string, apiOutput keyvault.SecretListResultIterator, err error) {
+	if mc != nil {
+		mc.getSecretsComplete = func(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error) {
+			return apiOutput, err
+		}
+	}
+}

+ 69 - 3
pkg/provider/azure/keyvault/keyvault.go

@@ -19,6 +19,8 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
+	"path"
+	"regexp"
 	"strings"
 	"strings"
 
 
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
@@ -115,10 +117,41 @@ func getProvider(store esv1beta1.GenericStore) (*esv1beta1.AzureKVProvider, erro
 	return spc.Provider.AzureKV, nil
 	return spc.Provider.AzureKV, nil
 }
 }
 
 
-// Empty GetAllSecrets.
+// Implements store.Client.GetAllSecrets Interface.
+// Retrieves a map[string][]byte with the secret names as key and the secret itself as the calue.
 func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
-	// TO be implemented
-	return nil, fmt.Errorf("GetAllSecrets not implemented")
+	basicClient := a.baseClient
+	secretsMap := make(map[string][]byte)
+	checkTags := len(ref.Tags) > 0
+	checkName := ref.Name != nil && len(ref.Name.RegExp) > 0
+
+	secretListIter, err := basicClient.GetSecretsComplete(context.Background(), *a.provider.VaultURL, nil)
+
+	if err != nil {
+		return nil, err
+	}
+	for secretListIter.NotDone() {
+		secretList := secretListIter.Response().Value
+		for _, secret := range *secretList {
+			ok, secretName := isValidSecret(checkTags, checkName, ref, secret)
+			if !ok {
+				continue
+			}
+
+			secretResp, err := basicClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, "")
+			secretValue := *secretResp.Value
+
+			if err != nil {
+				return nil, err
+			}
+			secretsMap[secretName] = []byte(secretValue)
+		}
+		err = secretListIter.Next()
+		if err != nil {
+			return nil, err
+		}
+	}
+	return secretsMap, nil
 }
 }
 
 
 // Implements store.Client.GetSecret Interface.
 // Implements store.Client.GetSecret Interface.
@@ -306,3 +339,36 @@ func getObjType(ref esv1beta1.ExternalSecretDataRemoteRef) (string, string) {
 	}
 	}
 	return objectType, secretName
 	return objectType, secretName
 }
 }
+
+func isValidSecret(checkTags, checkName bool, ref esv1beta1.ExternalSecretFind, secret keyvault.SecretItem) (bool, string) {
+	if secret.ID == nil || !*secret.Attributes.Enabled {
+		return false, ""
+	}
+
+	if checkTags && !okByTags(ref, secret) {
+		return false, ""
+	}
+
+	secretName := path.Base(*secret.ID)
+	if checkName && !okByName(ref, secretName) {
+		return false, ""
+	}
+
+	return true, secretName
+}
+
+func okByName(ref esv1beta1.ExternalSecretFind, secretName string) bool {
+	matches, _ := regexp.MatchString(ref.Name.RegExp, secretName)
+	return matches
+}
+
+func okByTags(ref esv1beta1.ExternalSecretFind, secret keyvault.SecretItem) bool {
+	tagsFound := true
+	for k, v := range ref.Tags {
+		if val, ok := secret.Tags[k]; !ok || *val != v {
+			tagsFound = false
+			break
+		}
+	}
+	return tagsFound
+}

+ 181 - 6
pkg/provider/azure/keyvault/keyvault_test.go

@@ -17,6 +17,7 @@ package keyvault
 import (
 import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"fmt"
 	"reflect"
 	"reflect"
 	"testing"
 	"testing"
@@ -40,10 +41,12 @@ type secretManagerTestCase struct {
 	secretVersion  string
 	secretVersion  string
 	serviceURL     string
 	serviceURL     string
 	ref            *esv1beta1.ExternalSecretDataRemoteRef
 	ref            *esv1beta1.ExternalSecretDataRemoteRef
+	refFind        *esv1beta1.ExternalSecretFind
 	apiErr         error
 	apiErr         error
 	secretOutput   keyvault.SecretBundle
 	secretOutput   keyvault.SecretBundle
 	keyOutput      keyvault.KeyBundle
 	keyOutput      keyvault.KeyBundle
 	certOutput     keyvault.CertificateBundle
 	certOutput     keyvault.CertificateBundle
+	listOutput     keyvault.SecretListResultIterator
 	expectError    string
 	expectError    string
 	expectedSecret string
 	expectedSecret string
 	// for testing secretmap
 	// for testing secretmap
@@ -57,6 +60,7 @@ func makeValidSecretManagerTestCase() *secretManagerTestCase {
 		secretName:     "MySecret",
 		secretName:     "MySecret",
 		secretVersion:  "",
 		secretVersion:  "",
 		ref:            makeValidRef(),
 		ref:            makeValidRef(),
+		refFind:        makeValidFind(),
 		secretOutput:   keyvault.SecretBundle{Value: &secretString},
 		secretOutput:   keyvault.SecretBundle{Value: &secretString},
 		serviceURL:     "",
 		serviceURL:     "",
 		apiErr:         nil,
 		apiErr:         nil,
@@ -79,6 +83,7 @@ func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTest
 	smtc.mockClient.WithValue(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.secretOutput, smtc.apiErr)
 	smtc.mockClient.WithValue(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.secretOutput, smtc.apiErr)
 	smtc.mockClient.WithKey(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.keyOutput, smtc.apiErr)
 	smtc.mockClient.WithKey(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.keyOutput, smtc.apiErr)
 	smtc.mockClient.WithCertificate(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.certOutput, smtc.apiErr)
 	smtc.mockClient.WithCertificate(smtc.serviceURL, smtc.secretName, smtc.secretVersion, smtc.certOutput, smtc.apiErr)
+	smtc.mockClient.WithList(smtc.serviceURL, smtc.listOutput, smtc.apiErr)
 
 
 	return smtc
 	return smtc
 }
 }
@@ -160,6 +165,11 @@ const (
 	jsonSingleTestString = `{"Name": "External", "LastName": "Secret" }`
 	jsonSingleTestString = `{"Name": "External", "LastName": "Secret" }`
 	keyName              = "key/keyname"
 	keyName              = "key/keyname"
 	certName             = "cert/certname"
 	certName             = "cert/certname"
+	secretString         = "changedvalue"
+	unexpectedError      = "[%d] unexpected error: %s, expected: '%s'"
+	unexpectedSecretData = "[%d] unexpected secret data: expected %#v, got %#v"
+	secretName           = "example-1"
+	fakeURL              = "noop"
 )
 )
 
 
 func newKVJWK(b []byte) *keyvault.JSONWebKey {
 func newKVJWK(b []byte) *keyvault.JSONWebKey {
@@ -211,7 +221,7 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 		}
 		}
 		smtc.ref.Property = "Age"
 		smtc.ref.Property = "Age"
 		smtc.expectError = fmt.Sprintf("property %s does not exist in key %s", smtc.ref.Property, smtc.ref.Key)
 		smtc.expectError = fmt.Sprintf("property %s does not exist in key %s", smtc.ref.Property, smtc.ref.Key)
-		smtc.apiErr = fmt.Errorf(smtc.expectError)
+		smtc.apiErr = errors.New(smtc.expectError)
 	}
 	}
 
 
 	// // good case: key set
 	// // good case: key set
@@ -265,7 +275,7 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 	}
 	}
 
 
 	sm := Azure{
 	sm := Azure{
-		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.StringPtr("noop")},
+		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.StringPtr(fakeURL)},
 	}
 	}
 	for k, v := range successCases {
 	for k, v := range successCases {
 		sm.baseClient = v.mockClient
 		sm.baseClient = v.mockClient
@@ -319,7 +329,7 @@ func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 		}
 		}
 		smtc.ref.Property = "Age"
 		smtc.ref.Property = "Age"
 		smtc.expectError = fmt.Sprintf("property %s does not exist in key %s", smtc.ref.Property, smtc.ref.Key)
 		smtc.expectError = fmt.Sprintf("property %s does not exist in key %s", smtc.ref.Property, smtc.ref.Key)
-		smtc.apiErr = fmt.Errorf(smtc.expectError)
+		smtc.apiErr = errors.New(smtc.expectError)
 	}
 	}
 
 
 	badPubRSAKey := func(smtc *secretManagerTestCase) {
 	badPubRSAKey := func(smtc *secretManagerTestCase) {
@@ -361,7 +371,7 @@ func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 	}
 	}
 
 
 	sm := Azure{
 	sm := Azure{
-		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.StringPtr("noop")},
+		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.StringPtr(fakeURL)},
 	}
 	}
 	for k, v := range successCases {
 	for k, v := range successCases {
 		sm.baseClient = v.mockClient
 		sm.baseClient = v.mockClient
@@ -375,9 +385,174 @@ func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestAzureKeyVaultSecretManagerGetAllSecrets(t *testing.T) {
+	secretString := secretString
+	secretName := secretName
+	wrongName := "not-valid"
+	environment := "dev"
+	author := "seb"
+	enabled := true
+
+	getNextPage := func(ctx context.Context, list keyvault.SecretListResult) (result keyvault.SecretListResult, err error) {
+		return keyvault.SecretListResult{
+			Value:    nil,
+			NextLink: nil,
+		}, nil
+	}
+
+	setOneSecretByName := func(smtc *secretManagerTestCase) {
+		enabledAtt := keyvault.SecretAttributes{
+			Enabled: &enabled,
+		}
+		secretItem := keyvault.SecretItem{
+			ID:         &secretName,
+			Attributes: &enabledAtt,
+		}
+
+		secretList := make([]keyvault.SecretItem, 0)
+		secretList = append(secretList, secretItem)
+
+		list := keyvault.SecretListResult{
+			Value: &secretList,
+		}
+
+		resultPage := keyvault.NewSecretListResultPage(list, getNextPage)
+		smtc.listOutput = keyvault.NewSecretListResultIterator(resultPage)
+
+		smtc.expectedSecret = secretString
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString,
+		}
+
+		smtc.expectedData[secretName] = []byte(secretString)
+	}
+
+	setTwoSecretsByName := func(smtc *secretManagerTestCase) {
+		enabledAtt := keyvault.SecretAttributes{
+			Enabled: &enabled,
+		}
+		secretItemOne := keyvault.SecretItem{
+			ID:         &secretName,
+			Attributes: &enabledAtt,
+		}
+
+		secretItemTwo := keyvault.SecretItem{
+			ID:         &wrongName,
+			Attributes: &enabledAtt,
+		}
+
+		secretList := make([]keyvault.SecretItem, 1)
+		secretList = append(secretList, secretItemOne, secretItemTwo)
+
+		list := keyvault.SecretListResult{
+			Value: &secretList,
+		}
+
+		resultPage := keyvault.NewSecretListResultPage(list, getNextPage)
+		smtc.listOutput = keyvault.NewSecretListResultIterator(resultPage)
+
+		smtc.expectedSecret = secretString
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString,
+		}
+
+		smtc.expectedData[secretName] = []byte(secretString)
+	}
+
+	setOneSecretByTag := func(smtc *secretManagerTestCase) {
+		enabledAtt := keyvault.SecretAttributes{
+			Enabled: &enabled,
+		}
+		secretItem := keyvault.SecretItem{
+			ID:         &secretName,
+			Attributes: &enabledAtt,
+			Tags:       map[string]*string{"environment": &environment},
+		}
+
+		secretList := make([]keyvault.SecretItem, 0)
+		secretList = append(secretList, secretItem)
+
+		list := keyvault.SecretListResult{
+			Value: &secretList,
+		}
+
+		resultPage := keyvault.NewSecretListResultPage(list, getNextPage)
+		smtc.listOutput = keyvault.NewSecretListResultIterator(resultPage)
+
+		smtc.expectedSecret = secretString
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString,
+		}
+		smtc.refFind.Tags = map[string]string{"environment": environment}
+
+		smtc.expectedData[secretName] = []byte(secretString)
+	}
+
+	setTwoSecretsByTag := func(smtc *secretManagerTestCase) {
+		enabled := true
+		enabledAtt := keyvault.SecretAttributes{
+			Enabled: &enabled,
+		}
+		secretItem := keyvault.SecretItem{
+			ID:         &secretName,
+			Attributes: &enabledAtt,
+			Tags:       map[string]*string{"environment": &environment, "author": &author},
+		}
+
+		secretList := make([]keyvault.SecretItem, 0)
+		secretList = append(secretList, secretItem)
+
+		list := keyvault.SecretListResult{
+			Value: &secretList,
+		}
+
+		resultPage := keyvault.NewSecretListResultPage(list, getNextPage)
+		smtc.listOutput = keyvault.NewSecretListResultIterator(resultPage)
+
+		smtc.expectedSecret = secretString
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString,
+		}
+		smtc.refFind.Tags = map[string]string{"environment": environment, "author": author}
+
+		smtc.expectedData[secretName] = []byte(secretString)
+	}
+
+	successCases := []*secretManagerTestCase{
+		makeValidSecretManagerTestCaseCustom(setOneSecretByName),
+		makeValidSecretManagerTestCaseCustom(setTwoSecretsByName),
+		makeValidSecretManagerTestCaseCustom(setOneSecretByTag),
+		makeValidSecretManagerTestCaseCustom(setTwoSecretsByTag),
+	}
+
+	sm := Azure{
+		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.StringPtr(fakeURL)},
+	}
+	for k, v := range successCases {
+		sm.baseClient = v.mockClient
+		out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
+		if !utils.ErrorContains(err, v.expectError) {
+			t.Errorf(unexpectedError, k, err.Error(), v.expectError)
+		}
+		if err == nil && !reflect.DeepEqual(out, v.expectedData) {
+			t.Errorf(unexpectedSecretData, k, v.expectedData, out)
+		}
+	}
+}
+
 func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
 func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
 	return &esv1beta1.ExternalSecretDataRemoteRef{
 	return &esv1beta1.ExternalSecretDataRemoteRef{
-		Key:     "test-secret",
-		Version: "default",
+		Key:      "test-secret",
+		Version:  "default",
+		Property: "",
+	}
+}
+
+func makeValidFind() *esv1beta1.ExternalSecretFind {
+	return &esv1beta1.ExternalSecretFind{
+		Name: &esv1beta1.FindName{
+			RegExp: "^example",
+		},
+		Tags: map[string]string{},
 	}
 	}
 }
 }