Browse Source

feat: implement pushing whole k8s secret to Azure Keyvault (#3650)

* feat: implement pushing whole secrets to azure keyvault

Signed-off-by: Christophe Collot <christophe.collot.cloud@gmail.com>

* bump e2e pipeline (#3646)

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@productmadness.com>
Signed-off-by: Christophe Collot <christophe.collot.cloud@gmail.com>

* fix e2e permissions (#3647)

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@productmadness.com>
Signed-off-by: Christophe Collot <christophe.collot.cloud@gmail.com>

* bump docs with e2e commands (#3648)

Signed-off-by: Christophe Collot <christophe.collot.cloud@gmail.com>

* also needs pull-requests (#3649)

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@productmadness.com>
Signed-off-by: Christophe Collot <christophe.collot.cloud@gmail.com>

* style: remove unnecessary line

Signed-off-by: Christophe Collot <christophe.collot.cloud@gmail.com>

* style: remove trailing line

Signed-off-by: Christophe Collot <christophe.collot.cloud@gmail.com>

---------

Signed-off-by: Christophe Collot <christophe.collot.cloud@gmail.com>
Signed-off-by: Gustavo Carvalho <gustavo.carvalho@productmadness.com>
Co-authored-by: Gustavo Fernandes de Carvalho <17139678+gusfcarvalho@users.noreply.github.com>
Christophe Collot 1 year ago
parent
commit
504b5506f4

+ 15 - 2
pkg/provider/azure/keyvault/keyvault.go

@@ -531,12 +531,25 @@ func (a *Azure) setKeyVaultKey(ctx context.Context, secretName string, value []b
 
 
 // PushSecret stores secrets into a Key vault instance.
 // PushSecret stores secrets into a Key vault instance.
 func (a *Azure) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
 func (a *Azure) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
+	var (
+		value []byte
+		err   error
+	)
 	if data.GetSecretKey() == "" {
 	if data.GetSecretKey() == "" {
-		return fmt.Errorf("pushing the whole secret is not yet implemented")
+		// Must convert secret values to string, otherwise data will be sent as base64 to Vault
+		secretStringVal := make(map[string]string)
+		for k, v := range secret.Data {
+			secretStringVal[k] = string(v)
+		}
+		value, err = utils.JSONMarshal(secretStringVal)
+		if err != nil {
+			return fmt.Errorf("failed to serialize secret content as JSON: %w", err)
+		}
+	} else {
+		value = secret.Data[data.GetSecretKey()]
 	}
 	}
 
 
 	objectType, secretName := getObjType(esv1beta1.ExternalSecretDataRemoteRef{Key: data.GetRemoteKey()})
 	objectType, secretName := getObjType(esv1beta1.ExternalSecretDataRemoteRef{Key: data.GetRemoteKey()})
-	value := secret.Data[data.GetSecretKey()]
 	switch objectType {
 	switch objectType {
 	case defaultObjType:
 	case defaultObjType:
 		return a.setKeyVaultSecret(ctx, secretName, value)
 		return a.setKeyVaultSecret(ctx, secretName, value)

+ 39 - 7
pkg/provider/azure/keyvault/keyvault_test.go

@@ -61,9 +61,10 @@ type secretManagerTestCase struct {
 	setValue       []byte
 	setValue       []byte
 	expectedSecret string
 	expectedSecret string
 	// for testing secretmap
 	// for testing secretmap
-	expectedData map[string][]byte
-
+	expectedData      map[string][]byte
 	expectedExistence bool
 	expectedExistence bool
+	// for testing pushing multi-key k8s secrets
+	secret *corev1.Secret
 }
 }
 
 
 func makeValidSecretManagerTestCase() *secretManagerTestCase {
 func makeValidSecretManagerTestCase() *secretManagerTestCase {
@@ -427,6 +428,24 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		}
 		smtc.expectError = errNotManaged
 		smtc.expectError = errNotManaged
 	}
 	}
+	wholeSecretNoKey := func(smtc *secretManagerTestCase) {
+		wholeSecretMap := map[string][]byte{"key1": []byte(`value1`), "key2": []byte(`value2`)}
+		wholeSecretString := `{"key1": "value1", "key2": "value2" }`
+		wholeSecret := &corev1.Secret{Data: wholeSecretMap}
+		smtc.secret = wholeSecret
+		smtc.pushData = testingfake.PushSecretData{
+			RemoteKey: secretName,
+		}
+		smtc.secretOutput = keyvault.SecretBundle{
+			Tags: map[string]*string{
+				"managed-by": pointer.To("external-secrets"),
+			},
+			Value: &wholeSecretString,
+		}
+
+		smtc.expectedData = wholeSecretMap
+	}
+
 	secretNoTags := func(smtc *secretManagerTestCase) {
 	secretNoTags := func(smtc *secretManagerTestCase) {
 		smtc.setValue = []byte(goodSecret)
 		smtc.setValue = []byte(goodSecret)
 		smtc.pushData = testingfake.PushSecretData{
 		smtc.pushData = testingfake.PushSecretData{
@@ -772,6 +791,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(failedNotParseableError),
 		makeValidSecretManagerTestCaseCustom(failedNotParseableError),
 		makeValidSecretManagerTestCaseCustom(failedSetSecret),
 		makeValidSecretManagerTestCaseCustom(failedSetSecret),
 		makeValidSecretManagerTestCaseCustom(typeNotSupported),
 		makeValidSecretManagerTestCaseCustom(typeNotSupported),
+		makeValidSecretManagerTestCaseCustom(wholeSecretNoKey),
 	}
 	}
 
 
 	sm := Azure{
 	sm := Azure{
@@ -779,12 +799,14 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 	}
 	}
 	for k, v := range successCases {
 	for k, v := range successCases {
 		sm.baseClient = v.mockClient
 		sm.baseClient = v.mockClient
-		secret := &corev1.Secret{
-			Data: map[string][]byte{
-				secretKey: v.setValue,
-			},
+		if v.secret == nil {
+			v.secret = &corev1.Secret{
+				Data: map[string][]byte{
+					secretKey: v.setValue,
+				},
+			}
 		}
 		}
-		err := sm.PushSecret(context.Background(), secret, v.pushData)
+		err := sm.PushSecret(context.Background(), v.secret, v.pushData)
 		if !utils.ErrorContains(err, v.expectError) {
 		if !utils.ErrorContains(err, v.expectError) {
 			if err == nil {
 			if err == nil {
 				t.Errorf("[%d] unexpected error: <nil>, expected: '%s'", k, v.expectError)
 				t.Errorf("[%d] unexpected error: <nil>, expected: '%s'", k, v.expectError)
@@ -792,6 +814,16 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 				t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
 				t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
 			}
 			}
 		}
 		}
+		if len(v.expectedData) > 0 {
+			sm.baseClient = v.mockClient
+			out, err := sm.GetSecretMap(context.Background(), *v.ref)
+			if !utils.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)
+			}
+		}
 	}
 	}
 }
 }