You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2023/10/05 12:04:32 UTC

[camel-k] 01/01: Kamelet - Inject secret in Vaults - Azure Key Vault

This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch 4743-azure-key-vault
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 23f1fc2fcdf06464ad1994c7e5c105bd8242dde3
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Thu Oct 5 14:04:10 2023 +0200

    Kamelet - Inject secret in Vaults - Azure Key Vault
    
    Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
 addons/vault/azure/azure_key_vault.go      |  36 ++++++++--
 addons/vault/azure/azure_key_vault_test.go | 110 ++++++++++++++++++++++++++++-
 2 files changed, 140 insertions(+), 6 deletions(-)

diff --git a/addons/vault/azure/azure_key_vault.go b/addons/vault/azure/azure_key_vault.go
index 76ecee985..5ab1ab0f6 100644
--- a/addons/vault/azure/azure_key_vault.go
+++ b/addons/vault/azure/azure_key_vault.go
@@ -18,8 +18,11 @@ limitations under the License.
 package azure
 
 import (
+	"regexp"
 	"strconv"
 
+	"github.com/apache/camel-k/v2/pkg/util/kubernetes"
+
 	v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
 	traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait"
 	"github.com/apache/camel-k/v2/pkg/trait"
@@ -50,7 +53,9 @@ type Trait struct {
 	TenantID string `property:"tenant-id" json:"tenantId,omitempty"`
 	// The Azure Client Id for accessing Key Vault
 	ClientID string `property:"client-id" json:"clientId,omitempty"`
-	// The Azure Client Secret for accessing Key Vault
+	// The Azure Client Secret for accessing Key Vault. This could be a plain text or a configmap/secret.
+	// The content of the azure key vault client secret is expected to be a text containing a valid Client Secret.
+	// Syntax: [configmap|secret]:name[/key], where name represents the resource name, key optionally represents the resource key to be filtered (default key value = azure-key-vault-client-secret).
 	ClientSecret string `property:"client-secret" json:"clientSecret,omitempty"`
 	// The Azure Vault Name for accessing Key Vault
 	VaultName string `property:"vault-name" json:"vaultName,omitempty"`
@@ -66,7 +71,9 @@ type Trait struct {
 	EventhubConnectionString string `property:"eventhub-connection-string" json:"eventhubConnectionString,omitempty"`
 	// If Refresh is enabled, the account name for Azure Storage Blob service used to save checkpoint while consuming from Eventhub
 	BlobAccountName string `property:"blob-account-name" json:"blobAccountName,omitempty"`
-	// If Refresh is enabled, the access key for Azure Storage Blob service used to save checkpoint while consuming from Eventhub
+	// If Refresh is enabled, the access key for Azure Storage Blob service used to save checkpoint while consuming from Eventhub. This could be a plain text or a configmap/secret.
+	// The content of the azure key vault blob access key is expected to be a text containing a valid Access Key for Azure Storage Blob.
+	// Syntax: [configmap|secret]:name[/key], where name represents the resource name, key optionally represents the resource key to be filtered (default key value = azure-storage-blob-access-key).
 	BlobAccessKey string `property:"blob-access-key" json:"blobAccessKey,omitempty"`
 	// If Refresh is enabled, the container name for Azure Storage Blob service used to save checkpoint while consuming from Eventhub
 	BlobContainerName string `property:"blob-container-name" json:"blobContainerName,omitempty"`
@@ -104,6 +111,7 @@ func (t *azureKeyVaultTrait) Configure(environment *trait.Environment) (bool, er
 }
 
 func (t *azureKeyVaultTrait) Apply(environment *trait.Environment) error {
+	rex := regexp.MustCompile(`^(configmap|secret):([a-zA-Z0-9][a-zA-Z0-9-]*)(/([a-zA-Z0-9].*))?$`)
 	if environment.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
 		util.StringSliceUniqueAdd(&environment.Integration.Status.Capabilities, v1.CapabilityAzureKeyVault)
 		// Deprecated
@@ -112,9 +120,30 @@ func (t *azureKeyVaultTrait) Apply(environment *trait.Environment) error {
 	}
 
 	if environment.IntegrationInRunningPhases() {
+		hits := rex.FindAllStringSubmatch(t.ClientSecret, -1)
+		if len(hits) >= 1 {
+			var res, _ = v1.DecodeValueSource(t.ClientSecret, "azure-key-vault-client-secret", "The Azure Key Vault Client Secret provided is not valid")
+			if secretValue, err := kubernetes.ResolveValueSource(environment.Ctx, environment.Client, environment.Platform.Namespace, &res); err != nil {
+				return err
+			} else if secretValue != "" {
+				environment.ApplicationProperties["camel.vault.azure.clientSecret"] = string([]byte(secretValue))
+			}
+		} else {
+			environment.ApplicationProperties["camel.vault.azure.clientSecret"] = t.ClientSecret
+		}
+		hits = rex.FindAllStringSubmatch(t.BlobAccessKey, -1)
+		if len(hits) >= 1 {
+			var res, _ = v1.DecodeValueSource(t.BlobAccessKey, "azure-storage-blob-access-key", "The Azure Storage Blob Access Key provided is not valid")
+			if secretValue, err := kubernetes.ResolveValueSource(environment.Ctx, environment.Client, environment.Platform.Namespace, &res); err != nil {
+				return err
+			} else if secretValue != "" {
+				environment.ApplicationProperties["camel.vault.azure.blobAccessKey"] = string([]byte(secretValue))
+			}
+		} else {
+			environment.ApplicationProperties["camel.vault.azure.blobAccessKey"] = t.BlobAccessKey
+		}
 		environment.ApplicationProperties["camel.vault.azure.tenantId"] = t.TenantID
 		environment.ApplicationProperties["camel.vault.azure.clientId"] = t.ClientID
-		environment.ApplicationProperties["camel.vault.azure.clientSecret"] = t.ClientSecret
 		environment.ApplicationProperties["camel.vault.azure.vaultName"] = t.VaultName
 		environment.ApplicationProperties["camel.vault.azure.refreshEnabled"] = strconv.FormatBool(*t.RefreshEnabled)
 		environment.ApplicationProperties["camel.main.context-reload-enabled"] = strconv.FormatBool(*t.ContextReloadEnabled)
@@ -125,7 +154,6 @@ func (t *azureKeyVaultTrait) Apply(environment *trait.Environment) error {
 		environment.ApplicationProperties["camel.vault.azure.eventhubConnectionString"] = t.EventhubConnectionString
 		environment.ApplicationProperties["camel.vault.azure.blobAccountName"] = t.BlobAccountName
 		environment.ApplicationProperties["camel.vault.azure.blobContainerName"] = t.BlobContainerName
-		environment.ApplicationProperties["camel.vault.azure.blobAccessKey"] = t.BlobAccessKey
 	}
 
 	return nil
diff --git a/addons/vault/azure/azure_key_vault_test.go b/addons/vault/azure/azure_key_vault_test.go
index 6de912212..388643605 100644
--- a/addons/vault/azure/azure_key_vault_test.go
+++ b/addons/vault/azure/azure_key_vault_test.go
@@ -20,6 +20,9 @@ package azure
 import (
 	"testing"
 
+	"github.com/apache/camel-k/v2/pkg/util/test"
+	corev1 "k8s.io/api/core/v1"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
 
@@ -28,6 +31,7 @@ import (
 	"github.com/apache/camel-k/v2/pkg/util/camel"
 
 	"github.com/stretchr/testify/assert"
+	"k8s.io/apimachinery/pkg/runtime"
 )
 
 func TestAzureKeyVaultTraitApply(t *testing.T) {
@@ -52,25 +56,127 @@ func TestAzureKeyVaultTraitApply(t *testing.T) {
 	assert.Equal(t, "my-vault", e.ApplicationProperties["camel.vault.azure.vaultName"])
 }
 
-func createEnvironment(t *testing.T, catalogGen func() (*camel.RuntimeCatalog, error)) *trait.Environment {
+func TestAzureKeyVaultTraitApplyWithConfigmapAndRefresh(t *testing.T) {
+	e := createEnvironment(t, camel.QuarkusCatalog, &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "my-configmap1",
+		},
+		Data: map[string]string{
+			"azure-client-secret": "my-secret-key",
+		},
+	}, &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "my-configmap2",
+		},
+		Data: map[string]string{
+			"azure-storage-blob-key": "my-access-key",
+		},
+	})
+	azure := NewAzureKeyVaultTrait()
+	secrets, _ := azure.(*azureKeyVaultTrait)
+	secrets.Enabled = pointer.Bool(true)
+	secrets.TenantID = "tenant-id"
+	secrets.ClientID = "client-id"
+	secrets.ClientSecret = "configmap:my-configmap1/azure-client-secret"
+	secrets.VaultName = "my-vault"
+	secrets.RefreshEnabled = pointer.Bool(true)
+	secrets.BlobAccessKey = "configmap:my-configmap2/azure-storage-blob-key"
+	secrets.BlobAccountName = "camel-k"
+	secrets.BlobContainerName = "camel-k-container"
+	ok, err := secrets.Configure(e)
+	assert.Nil(t, err)
+	assert.True(t, ok)
+
+	err = secrets.Apply(e)
+	assert.Nil(t, err)
+
+	assert.Equal(t, "client-id", e.ApplicationProperties["camel.vault.azure.clientId"])
+	assert.Equal(t, "my-secret-key", e.ApplicationProperties["camel.vault.azure.clientSecret"])
+	assert.Equal(t, "tenant-id", e.ApplicationProperties["camel.vault.azure.tenantId"])
+	assert.Equal(t, "my-vault", e.ApplicationProperties["camel.vault.azure.vaultName"])
+	assert.Equal(t, "camel-k", e.ApplicationProperties["camel.vault.azure.blobAccountName"])
+	assert.Equal(t, "camel-k-container", e.ApplicationProperties["camel.vault.azure.blobContainerName"])
+	assert.Equal(t, "my-access-key", e.ApplicationProperties["camel.vault.azure.blobAccessKey"])
+	assert.True(t, true, e.ApplicationProperties["camel.vault.azure.refreshEnabled"])
+}
+
+func TestAzureKeyVaultTraitApplyWithSecretAndRefresh(t *testing.T) {
+	e := createEnvironment(t, camel.QuarkusCatalog, &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "my-secret1",
+		},
+		Data: map[string][]byte{
+			"azure-client-secret": []byte("my-secret-key"),
+		},
+	}, &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "my-secret2",
+		},
+		Data: map[string][]byte{
+			"azure-storage-blob-key": []byte("my-access-key"),
+		},
+	})
+	azure := NewAzureKeyVaultTrait()
+	secrets, _ := azure.(*azureKeyVaultTrait)
+	secrets.Enabled = pointer.Bool(true)
+	secrets.TenantID = "tenant-id"
+	secrets.ClientID = "client-id"
+	secrets.ClientSecret = "secret:my-secret1/azure-client-secret"
+	secrets.VaultName = "my-vault"
+	secrets.RefreshEnabled = pointer.Bool(true)
+	secrets.BlobAccessKey = "secret:my-secret2/azure-storage-blob-key"
+	secrets.BlobAccountName = "camel-k"
+	secrets.BlobContainerName = "camel-k-container"
+	ok, err := secrets.Configure(e)
+	assert.Nil(t, err)
+	assert.True(t, ok)
+
+	err = secrets.Apply(e)
+	assert.Nil(t, err)
+
+	assert.Equal(t, "client-id", e.ApplicationProperties["camel.vault.azure.clientId"])
+	assert.Equal(t, "my-secret-key", e.ApplicationProperties["camel.vault.azure.clientSecret"])
+	assert.Equal(t, "tenant-id", e.ApplicationProperties["camel.vault.azure.tenantId"])
+	assert.Equal(t, "my-vault", e.ApplicationProperties["camel.vault.azure.vaultName"])
+	assert.Equal(t, "camel-k", e.ApplicationProperties["camel.vault.azure.blobAccountName"])
+	assert.Equal(t, "camel-k-container", e.ApplicationProperties["camel.vault.azure.blobContainerName"])
+	assert.Equal(t, "my-access-key", e.ApplicationProperties["camel.vault.azure.blobAccessKey"])
+	assert.True(t, true, e.ApplicationProperties["camel.vault.azure.refreshEnabled"])
+}
+
+func createEnvironment(t *testing.T, catalogGen func() (*camel.RuntimeCatalog, error), objects ...runtime.Object) *trait.Environment {
 	t.Helper()
 
 	catalog, err := catalogGen()
+	client, _ := test.NewFakeClient(objects...)
 	assert.Nil(t, err)
 
 	e := trait.Environment{
 		CamelCatalog:          catalog,
 		ApplicationProperties: make(map[string]string),
+		Client:                client,
 	}
 
 	it := v1.Integration{
 		ObjectMeta: metav1.ObjectMeta{
-			Name: "test",
+			Namespace: "test",
+			Name:      "test",
 		},
 		Status: v1.IntegrationStatus{
 			Phase: v1.IntegrationPhaseDeploying,
 		},
 	}
+	platform := v1.IntegrationPlatform{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "test",
+		},
+	}
 	e.Integration = &it
+	e.Platform = &platform
 	return &e
 }