Browse Source

Merge pull request #1281 from external-secrets/azure-keyvault-e2e-test

merging for testing the Azure keyvault e2e test for AKS
Rodrigo Martinez 4 years ago
parent
commit
0ed419cdba

+ 71 - 0
e2e/suites/provider/cases/azure/azure_managed.go

@@ -0,0 +1,71 @@
+/*
+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.
+limitations under the License.
+*/
+package azure
+
+import (
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	// nolint
+	// . "github.com/onsi/gomega"
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/framework/addon"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
+)
+
+const (
+	withPodID = "sync secrets with workload identity"
+)
+
+// Deploys eso to the default namespace
+// that uses the service account provisioned by terraform
+// to test workload-identity authentication.
+var _ = Describe("[azuremanaged] with pod identity", Label("azure", "keyvault", "managed", "workload-identity"), func() {
+	f := framework.New("eso-azuremanaged")
+	prov := newFromEnv(f)
+
+	// each test case gets its own ESO instance
+	BeforeEach(func() {
+		f.Install(addon.NewESO(
+			addon.WithControllerClass(f.BaseName),
+			addon.WithServiceAccount(prov.clientID),
+			addon.WithReleaseName(f.Namespace.Name),
+			addon.WithNamespace("default"),
+			addon.WithoutWebhook(),
+			addon.WithoutCertController(),
+		))
+	})
+
+	DescribeTable("sync secrets",
+		framework.TableFunc(f,
+			prov),
+		// uses pod id
+		framework.Compose(withPodID, f, common.SimpleDataSync, usePodIDESReference),
+		framework.Compose(withPodID, f, common.JSONDataWithProperty, usePodIDESReference),
+		framework.Compose(withPodID, f, common.JSONDataFromSync, usePodIDESReference),
+		framework.Compose(withPodID, f, common.NestedJSONWithGJSON, usePodIDESReference),
+		framework.Compose(withPodID, f, common.JSONDataWithTemplate, usePodIDESReference),
+		framework.Compose(withPodID, f, common.DockerJSONConfig, usePodIDESReference),
+		framework.Compose(withPodID, f, common.DataPropertyDockerconfigJSON, usePodIDESReference),
+		framework.Compose(withPodID, f, common.SSHKeySync, usePodIDESReference),
+		framework.Compose(withPodID, f, common.SSHKeySyncDataProperty, usePodIDESReference),
+		framework.Compose(withPodID, f, common.SyncWithoutTargetName, usePodIDESReference),
+		framework.Compose(withPodID, f, common.JSONDataWithoutTargetName, usePodIDESReference),
+	)
+})
+
+func usePodIDESReference(tc *framework.TestCase) {
+	tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1beta1.ClusterSecretStoreKind
+}

+ 26 - 1
e2e/suites/provider/cases/azure/provider.go

@@ -68,6 +68,7 @@ func newazureProvider(f *framework.Framework, clientID, clientSecret, tenantID,
 			}
 			prov.client.Authorizer = authorizer
 		})
+		prov.CreateSecretStoreWithWI()
 		prov.CreateSecretStore()
 	})
 
@@ -195,7 +196,6 @@ func (s *azureProvider) CreateSecretStore() {
 	}
 	err := s.framework.CRClient.Create(context.Background(), azureCreds)
 	Expect(err).ToNot(HaveOccurred())
-
 	secretStore := &esv1beta1.SecretStore{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      s.framework.Namespace.Name,
@@ -223,3 +223,28 @@ func (s *azureProvider) CreateSecretStore() {
 	err = s.framework.CRClient.Create(context.Background(), secretStore)
 	Expect(err).ToNot(HaveOccurred())
 }
+
+func (s *azureProvider) CreateSecretStoreWithWI() {
+	authType := esv1beta1.AzureWorkloadIdentity
+	namespace := "external-secrets-operator"
+	ClusterSecretStore := &esv1beta1.ClusterSecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: s.framework.Namespace.Name,
+		},
+		Spec: esv1beta1.SecretStoreSpec{
+			Provider: &esv1beta1.SecretStoreProvider{
+				AzureKV: &esv1beta1.AzureKVProvider{
+					TenantID: &s.tenantID,
+					VaultURL: &s.vaultURL,
+					AuthType: &authType,
+					ServiceAccountRef: &esmeta.ServiceAccountSelector{
+						Name:      "external-secrets-operator",
+						Namespace: &namespace,
+					},
+				},
+			},
+		},
+	}
+	err := s.framework.CRClient.Create(context.Background(), ClusterSecretStore)
+	Expect(err).ToNot(HaveOccurred())
+}

+ 23 - 0
terraform/azure/aks/main.tf

@@ -0,0 +1,23 @@
+
+resource "azurerm_kubernetes_cluster" "current" {
+  name                = var.cluster_name
+  resource_group_name = var.resource_group_name
+  location            = var.resource_group_location
+  dns_prefix          = var.dns_prefix
+
+  oidc_issuer_enabled               = var.oidc_issuer_enabled
+  role_based_access_control_enabled = true
+
+  default_node_pool {
+    name       = var.default_node_pool_name
+    node_count = var.default_node_pool_node_count
+    vm_size    = var.default_node_pool_vm_size
+  }
+
+  identity {
+    type = "SystemAssigned"
+  }
+
+  tags = var.cluster_tags
+
+}

+ 10 - 0
terraform/azure/aks/output.tf

@@ -0,0 +1,10 @@
+
+output "cluster_issuer_url" {
+  value = azurerm_kubernetes_cluster.current.oidc_issuer_url
+}
+
+output "kube_config" {
+  value = azurerm_kubernetes_cluster.current.kube_config_raw
+
+  sensitive = true
+}

+ 13 - 0
terraform/azure/aks/providers.tf

@@ -0,0 +1,13 @@
+terraform {
+  required_providers {
+    azurerm = {
+      source = "hashicorp/azurerm"
+    }
+  }
+}
+
+provider "azurerm" {
+  features {}
+}
+
+

+ 48 - 0
terraform/azure/aks/variables.tf

@@ -0,0 +1,48 @@
+variable "cluster_name" {
+  type        = string
+  description = "The name of the Managed Kubernetes Cluster to create"
+}
+
+variable "resource_group_name" {
+  type        = string
+  description = "The Name which should be used for this Resource Group"
+}
+
+variable "resource_group_location" {
+  type        = string
+  description = "The Azure Region where the Resource Group should exist"
+}
+
+variable "dns_prefix" {
+  type        = string
+  description = "DNS prefix specified when creating the managed cluster"
+  default     = "api"
+}
+
+variable "oidc_issuer_enabled" {
+  type        = bool
+  description = "Enable or Disable the OIDC issuer URL"
+  default     = true
+}
+
+variable "default_node_pool_name" {
+  type        = string
+  description = " The name of the Default Node Pool which should be created within the Kubernetes Cluster"
+  default     = "default"
+}
+
+variable "default_node_pool_node_count" {
+  type        = number
+  description = " The initial number of nodes which should exist within this Node Pool"
+}
+
+variable "default_node_pool_vm_size" {
+  type        = string
+  description = " The SKU which should be used for the Virtual Machines used in this Node Pool"
+
+}
+
+variable "cluster_tags" {
+  type        = map(string)
+  description = "A mapping of tags to assign to the cluster"
+}

+ 42 - 0
terraform/azure/key-vault/main.tf

@@ -0,0 +1,42 @@
+
+resource "azurerm_key_vault" "current" {
+  name                        = var.key_vault_display_name
+  location                    = var.resource_group_location
+  resource_group_name         = var.resource_group_name
+  enabled_for_disk_encryption = true
+  tenant_id                   = var.tenant_id
+  soft_delete_retention_days  = 7
+  purge_protection_enabled    = false
+
+  sku_name = "standard"
+
+  access_policy {
+    tenant_id = var.tenant_id
+    object_id = var.client_object_id
+
+    key_permissions = [
+      "Get",
+    ]
+
+    secret_permissions = [
+      "Set",
+      "Get",
+      "Delete",
+      "Purge",
+      "Recover"
+    ]
+
+    storage_permissions = [
+      "Get",
+    ]
+  }
+  access_policy {
+    tenant_id = var.tenant_id
+    object_id = var.eso_sp_object_id
+
+    secret_permissions = [
+      "Get",
+    ]
+
+  }
+}

+ 3 - 0
terraform/azure/key-vault/output.tf

@@ -0,0 +1,3 @@
+output "key_vault_id" {
+  value = azurerm_key_vault.current.id
+}

+ 11 - 0
terraform/azure/key-vault/providers.tf

@@ -0,0 +1,11 @@
+terraform {
+  required_providers {
+    azurerm = {
+      source = "hashicorp/azurerm"
+    }
+  }
+}
+
+provider "azurerm" {
+  features {}
+}

+ 24 - 0
terraform/azure/key-vault/variables.tf

@@ -0,0 +1,24 @@
+variable "key_vault_display_name" {
+  type        = string
+  description = "Metadata name to use."
+}
+variable "resource_group_name" {
+  type        = string
+  description = "The Name which should be used for this Resource Group"
+}
+variable "resource_group_location" {
+  type        = string
+  description = "The Azure Region where the Resource Group should exist"
+}
+variable "tenant_id" {
+  type        = string
+  description = "Azure Tenant ID"
+}
+variable "client_object_id" {
+  type        = string
+  description = "The object ID of a user, service principal or security group in the Azure Active Directory tenant for the vault"
+}
+variable "eso_sp_object_id" {
+  type        = string
+  description = "The object ID of the ESO service account"
+}

+ 70 - 0
terraform/azure/main.tf

@@ -0,0 +1,70 @@
+data "azurerm_client_config" "current" {}
+
+data "azurerm_subscription" "primary" {}
+
+module "test_resource_group" {
+  source = "./resource-group"
+
+  resource_group_name     = var.resource_group_name
+  resource_group_location = var.resource_group_location
+}
+
+module "test_sp" {
+  source = "./service-principal"
+
+  application_display_name = var.application_display_name
+  application_owners       = [data.azurerm_client_config.current.object_id]
+  issuer                   = module.test_aks.cluster_issuer_url
+  subject                  = "system:serviceaccount:${var.sa_namespace}:${var.sa_name}"
+}
+
+module "test_key_vault" {
+  source = "./key-vault"
+
+  key_vault_display_name  = var.key_vault_display_name
+  resource_group_location = var.resource_group_location
+  resource_group_name     = var.resource_group_name
+  tenant_id               = data.azurerm_client_config.current.tenant_id
+  client_object_id        = data.azurerm_client_config.current.object_id
+  eso_sp_object_id        = module.test_sp.sp_object_id
+}
+
+module "test_workload_identity" {
+  source = "./workload-identity"
+
+  tenant_id = data.azurerm_client_config.current.tenant_id
+  tags      = var.cluster_tags
+
+}
+
+module "test_aks" {
+  source = "./aks"
+
+  cluster_name                 = var.cluster_name
+  resource_group_name          = var.resource_group_name
+  resource_group_location      = var.resource_group_location
+  default_node_pool_node_count = var.default_node_pool_node_count
+  default_node_pool_vm_size    = var.default_node_pool_vm_size
+  cluster_tags                 = var.cluster_tags
+}
+
+resource "azurerm_role_assignment" "current" {
+  scope                = data.azurerm_subscription.primary.id
+  role_definition_name = "Reader"
+  principal_id         = module.test_sp.sp_id
+}
+
+resource "kubernetes_service_account" "current" {
+  metadata {
+    name      = "external-secrets-operator"
+    namespace = "external-secrets-operator"
+    annotations = {
+      "azure.workload.identity/client-id" = module.test_sp.application_id
+      "azure.workload.identity/tenant-id" = data.azurerm_client_config.current.tenant_id
+    }
+    labels = {
+      "azure.workload.identity/use" = "true"
+    }
+  }
+
+}

+ 21 - 0
terraform/azure/providers.tf

@@ -0,0 +1,21 @@
+terraform {
+  required_providers {
+    azuread = {
+      source = "hashicorp/azuread"
+    }
+  }
+}
+
+provider "azurerm" {
+  features {}
+}
+
+provider "helm" {
+  kubernetes {
+    config_path = "~/.kube/config"
+  }
+}
+provider "kubernetes" {
+  config_path = "~/.kube/config"
+}
+

+ 4 - 0
terraform/azure/resource-group/main.tf

@@ -0,0 +1,4 @@
+resource "azurerm_resource_group" "current" {
+  name     = var.resource_group_name
+  location = var.resource_group_location
+}

+ 11 - 0
terraform/azure/resource-group/providers.tf

@@ -0,0 +1,11 @@
+terraform {
+  required_providers {
+    azurerm = {
+      source = "hashicorp/azurerm"
+    }
+  }
+}
+
+provider "azurerm" {
+  features {}
+}

+ 9 - 0
terraform/azure/resource-group/variables.tf

@@ -0,0 +1,9 @@
+variable "resource_group_name" {
+  type        = string
+  description = "The Name which should be used for this Resource Group"
+}
+
+variable "resource_group_location" {
+  type        = string
+  description = "The Azure Region where the Resource Group should exist"
+}

+ 31 - 0
terraform/azure/service-principal/main.tf

@@ -0,0 +1,31 @@
+resource "azuread_application" "current" {
+
+  display_name = var.application_display_name
+  owners       = var.application_owners
+}
+
+resource "azuread_service_principal" "current" {
+
+  application_id               = azuread_application.current.application_id
+  app_role_assignment_required = false
+  owners                       = var.application_owners
+  feature_tags {
+    enterprise = true
+    gallery    = true
+  }
+}
+
+resource "azuread_service_principal_password" "current" {
+
+  service_principal_id = azuread_service_principal.current.id
+}
+
+
+
+resource "azuread_application_federated_identity_credential" "example" {
+  application_object_id = azuread_application.current.object_id
+  display_name          = var.application_display_name
+  audiences             = var.audiences
+  issuer                = var.issuer
+  subject               = var.subject
+}

+ 9 - 0
terraform/azure/service-principal/output.tf

@@ -0,0 +1,9 @@
+output "application_id" {
+  value = azuread_application.current.application_id
+}
+output "sp_id" {
+  value = azuread_service_principal.current.id
+}
+output "sp_object_id" {
+  value = azuread_service_principal.current.object_id
+}

+ 11 - 0
terraform/azure/service-principal/providers.tf

@@ -0,0 +1,11 @@
+terraform {
+  required_providers {
+    azuread = {
+      source = "hashicorp/azuread"
+    }
+  }
+}
+
+provider "azurerm" {
+  features {}
+}

+ 17 - 0
terraform/azure/service-principal/variables.tf

@@ -0,0 +1,17 @@
+variable "application_display_name" {
+  type        = string
+  description = "Metadata name to use."
+}
+variable "application_owners" {
+  type = list(string)
+}
+variable "issuer" {
+  type = string
+}
+variable "audiences" {
+  type    = list(string)
+  default = ["api://AzureADTokenExchange"]
+}
+variable "subject" {
+  type = string
+}

+ 66 - 0
terraform/azure/variables.tf

@@ -0,0 +1,66 @@
+variable "cluster_name" {
+  type        = string
+  description = "The name of the Managed Kubernetes Cluster to create"
+  default     = "eso-cluster"
+}
+
+variable "resource_group_name" {
+  type        = string
+  description = "The Name which should be used for this Resource Group"
+  default     = "external-secrets-operator"
+}
+
+variable "resource_group_location" {
+  type        = string
+  description = "The Azure Region where the Resource Group should exist"
+  default     = "westeurope"
+}
+variable "application_display_name" {
+  type        = string
+  description = "Metadata name to use."
+  default     = "external-secrets-operator"
+}
+
+variable "dns_prefix" {
+  type        = string
+  description = "DNS prefix specified when creating the managed cluster"
+  default     = "eso"
+}
+
+variable "key_vault_display_name" {
+  type        = string
+  description = "The name of the Key Vault to create"
+  default     = "eso-testing"
+}
+
+variable "default_node_pool_name" {
+  type        = string
+  description = " The name of the Default Node Pool which should be created within the Kubernetes Cluster"
+  default     = "default"
+}
+
+variable "default_node_pool_node_count" {
+  type        = number
+  description = " The initial number of nodes which should exist within this Node Pool"
+  default     = 1
+}
+
+variable "default_node_pool_vm_size" {
+  type        = string
+  description = " The SKU which should be used for the Virtual Machines used in this Node Pool"
+  default     = "Standard_B2ms"
+}
+variable "sa_name" {
+  type    = string
+  default = "external-secrets-operator"
+}
+variable "sa_namespace" {
+  type        = string
+  description = "The namespace where the service account will be created"
+  default     = "external-secrets-operator"
+}
+variable "cluster_tags" {
+  type        = map(string)
+  description = "A mapping of tags to assign to the cluster"
+  default     = { cluster_name = "external-secrets-operator" }
+}

+ 23 - 0
terraform/azure/workload-identity/main.tf

@@ -0,0 +1,23 @@
+resource "kubernetes_namespace" "azure-workload-identity-system" {
+  metadata {
+    annotations = {
+      name = "azure-workload-identity-system"
+    }
+    name   = "azure-workload-identity-system"
+    labels = var.tags
+  }
+}
+
+resource "helm_release" "azure-workload-identity-system" {
+  name       = "workload-identity-webhook"
+  namespace  = "azure-workload-identity-system"
+  chart      = "workload-identity-webhook"
+  repository = "https://azure.github.io/azure-workload-identity/charts"
+  wait       = false
+  depends_on = [kubernetes_namespace.azure-workload-identity-system]
+
+  set {
+    name  = "azureTenantID"
+    value = var.tenant_id
+  }
+}

+ 5 - 0
terraform/azure/workload-identity/provider.tf

@@ -0,0 +1,5 @@
+provider "helm" {
+  kubernetes {
+    config_path = "~/.kube/config"
+  }
+}

+ 7 - 0
terraform/azure/workload-identity/variables.tf

@@ -0,0 +1,7 @@
+variable "tags" {
+  type = map(string)
+}
+variable "tenant_id" {
+  type        = string
+  description = "Azure Tenant ID"
+}