diff --git a/infrastructure/commons/cert_manager/locals.tf b/infrastructure/commons/cert_manager/locals.tf index 5a4ad10b..8fcbd0c8 100644 --- a/infrastructure/commons/cert_manager/locals.tf +++ b/infrastructure/commons/cert_manager/locals.tf @@ -19,12 +19,14 @@ locals { } : {}, var.cloud_provider == "azure" ? { - enabled = true - subscription_id = var.azure_subscription_id - resource_group_name = var.azure_resource_group_name - client_id = var.azure_client_id - tenant_id = var.azure_tenant_id - hosted_zone_name = var.azure_hosted_zone_name + enabled = true + subscription_id = var.azure_subscription_id + resource_group_name = var.azure_resource_group_name + client_id = var.azure_client_id + client_secret = var.azure_client_secret + tenant_id = var.azure_tenant_id + hosted_zone_name = var.azure_hosted_zone_name + use_workload_identity = var.azure_workload_identity_enabled } : {}, var.cloud_provider == "aws" ? { @@ -67,9 +69,9 @@ locals { "eks.amazonaws.com/role-arn" = var.aws_sa_arn } - azure = { + azure = var.azure_workload_identity_enabled ? { "azure.workload.identity/client-id" = var.azure_client_id - } + } : {} oci = { "oci.oraclecloud.com/workload-identity-principal" = var.oci_sa_ocid @@ -87,7 +89,7 @@ locals { lookup(local.annotations_by_provider, var.cloud_provider, {}) ) } - podLabels = var.cloud_provider == "azure" ? { + podLabels = var.cloud_provider == "azure" && var.azure_workload_identity_enabled ? { "azure.workload.identity/use" = "true" } : {} dns01RecursiveNameservers = "8.8.8.8:53,1.1.1.1:53" diff --git a/infrastructure/commons/cert_manager/templates/cert_manager_azure_values.tmpl.yaml b/infrastructure/commons/cert_manager/templates/cert_manager_azure_values.tmpl.yaml index 9cd81552..6ee80671 100644 --- a/infrastructure/commons/cert_manager/templates/cert_manager_azure_values.tmpl.yaml +++ b/infrastructure/commons/cert_manager/templates/cert_manager_azure_values.tmpl.yaml @@ -1,7 +1,11 @@ azure: subscriptionID: "${subscription_id}" resourceGroupName: "${resource_group_name}" - clientID: "${client_id}" tenantID: "${tenant_id}" hostedZoneName: "${hosted_zone_name}" + clientID: "${client_id}" +%{ if use_workload_identity ~} useWorkloadIdentity: true +%{ else ~} + clientSecret: "${client_secret}" +%{ endif ~} diff --git a/infrastructure/commons/cert_manager/tests/cert_manager_azure.tftest.hcl b/infrastructure/commons/cert_manager/tests/cert_manager_azure.tftest.hcl index d59de64d..595e2f0c 100644 --- a/infrastructure/commons/cert_manager/tests/cert_manager_azure.tftest.hcl +++ b/infrastructure/commons/cert_manager/tests/cert_manager_azure.tftest.hcl @@ -5,11 +5,12 @@ variables { hosted_zone_name = "myorg.nullimplementation.com" account_slug = "myorg" private_domain_name = "myorg.nullimplementation.com" - azure_client_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - azure_subscription_id = "00000000-0000-0000-0000-000000000000" - azure_resource_group_name = "rg-test" - azure_tenant_id = "11111111-2222-3333-4444-555555555555" - azure_hosted_zone_name = "myorg.nullimplementation.com" + azure_client_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + azure_federated_credential_id = "/subscriptions/00000000/resourceGroups/rg-test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cert-manager/federatedIdentityCredentials/cert-manager-federated" + azure_subscription_id = "00000000-0000-0000-0000-000000000000" + azure_resource_group_name = "rg-test" + azure_tenant_id = "11111111-2222-3333-4444-555555555555" + azure_hosted_zone_name = "myorg.nullimplementation.com" } # Validates Azure provider config plans successfully diff --git a/infrastructure/commons/cert_manager/tests/cert_manager_cross_provider.tftest.hcl b/infrastructure/commons/cert_manager/tests/cert_manager_cross_provider.tftest.hcl index 452ab66e..e34b0ae7 100644 --- a/infrastructure/commons/cert_manager/tests/cert_manager_cross_provider.tftest.hcl +++ b/infrastructure/commons/cert_manager/tests/cert_manager_cross_provider.tftest.hcl @@ -23,11 +23,12 @@ run "gcp_vars_not_required_for_azure" { hosted_zone_name = "myorg.example.com" account_slug = "myorg" private_domain_name = "myorg.example.com" - azure_client_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - azure_subscription_id = "00000000-0000-0000-0000-000000000000" - azure_resource_group_name = "rg-test" - azure_tenant_id = "11111111-2222-3333-4444-555555555555" - azure_hosted_zone_name = "myorg.example.com" + azure_client_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + azure_federated_credential_id = "/subscriptions/00000000/resourceGroups/rg-test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cert-manager/federatedIdentityCredentials/cert-manager-federated" + azure_subscription_id = "00000000-0000-0000-0000-000000000000" + azure_resource_group_name = "rg-test" + azure_tenant_id = "11111111-2222-3333-4444-555555555555" + azure_hosted_zone_name = "myorg.example.com" # gcp_sa_email and project_id intentionally left empty } @@ -126,11 +127,12 @@ run "oci_webhook_not_deployed_for_azure" { hosted_zone_name = "myorg.example.com" account_slug = "myorg" private_domain_name = "myorg.example.com" - azure_client_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - azure_subscription_id = "00000000-0000-0000-0000-000000000000" - azure_resource_group_name = "rg-test" - azure_tenant_id = "11111111-2222-3333-4444-555555555555" - azure_hosted_zone_name = "myorg.example.com" + azure_client_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + azure_federated_credential_id = "/subscriptions/00000000/resourceGroups/rg-test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cert-manager/federatedIdentityCredentials/cert-manager-federated" + azure_subscription_id = "00000000-0000-0000-0000-000000000000" + azure_resource_group_name = "rg-test" + azure_tenant_id = "11111111-2222-3333-4444-555555555555" + azure_hosted_zone_name = "myorg.example.com" } assert { diff --git a/infrastructure/commons/cert_manager/validation.tf b/infrastructure/commons/cert_manager/validation.tf index ed657ff9..a17eebfe 100644 --- a/infrastructure/commons/cert_manager/validation.tf +++ b/infrastructure/commons/cert_manager/validation.tf @@ -20,6 +20,14 @@ resource "terraform_data" "provider_validation" { condition = var.cloud_provider != "azure" || length(var.azure_client_id) > 0 error_message = "azure_client_id is required when cloud_provider is 'azure'." } + precondition { + condition = var.cloud_provider != "azure" || !var.azure_workload_identity_enabled || length(var.azure_federated_credential_id) > 0 + error_message = "azure_federated_credential_id is required when cloud_provider is 'azure' and azure_workload_identity_enabled is true. Use module.iam to create the federated identity credential and pass its id output." + } + precondition { + condition = var.cloud_provider != "azure" || var.azure_workload_identity_enabled || length(var.azure_client_secret) > 0 + error_message = "azure_client_secret is required when cloud_provider is 'azure' and azure_workload_identity_enabled is false." + } precondition { condition = var.cloud_provider != "azure" || length(var.azure_subscription_id) > 0 error_message = "azure_subscription_id is required when cloud_provider is 'azure'." diff --git a/infrastructure/commons/cert_manager/variables.tf b/infrastructure/commons/cert_manager/variables.tf index 4cb92446..fa185408 100644 --- a/infrastructure/commons/cert_manager/variables.tf +++ b/infrastructure/commons/cert_manager/variables.tf @@ -34,6 +34,24 @@ variable "azure_client_id" { default = "" } +variable "azure_workload_identity_enabled" { + description = "Enable Workload Identity for Azure DNS solver. When false, Service Principal auth is used and azure_client_secret is required." + type = bool + default = true +} + +variable "azure_federated_credential_id" { + description = "Resource ID of the Azure federated identity credential for cert-manager (required when cloud_provider is 'azure' and azure_workload_identity_enabled is true). Pass module.iam_cert_manager.id to enforce dependency ordering." + type = string + default = "" +} + +variable "azure_client_secret" { + description = "Azure AD client secret for Service Principal auth (required when cloud_provider is 'azure' and azure_workload_identity_enabled is false)." + type = string + sensitive = true + default = "" +} variable "private_domain_name" { description = "The private domain name for internal certificate issuance" diff --git a/infrastructure/commons/external_dns/locals.tf b/infrastructure/commons/external_dns/locals.tf index f646eab4..3081d79b 100644 --- a/infrastructure/commons/external_dns/locals.tf +++ b/infrastructure/commons/external_dns/locals.tf @@ -92,13 +92,13 @@ locals { provider = { name = "azure" } serviceAccount = { create = true - annotations = { + annotations = var.azure_workload_identity_enabled ? { "azure.workload.identity/client-id" = var.azure_client_id - } + } : {} } - podLabels = { + podLabels = var.azure_workload_identity_enabled ? { "azure.workload.identity/use" = "true" - } + } : {} extraVolumes = [ { name = "azure-config" diff --git a/infrastructure/commons/external_dns/secret.tf b/infrastructure/commons/external_dns/secret.tf index 7f1a4d64..934437e3 100644 --- a/infrastructure/commons/external_dns/secret.tf +++ b/infrastructure/commons/external_dns/secret.tf @@ -19,12 +19,18 @@ resource "kubernetes_secret_v1" "external_dns_azure_config" { } data = { - "azure.json" = jsonencode({ - tenantId = var.azure_tenant_id - subscriptionId = var.azure_subscription_id - resourceGroup = var.azure_resource_group - useWorkloadIdentityExtension = true - }) + "azure.json" = jsonencode(merge( + { + tenantId = var.azure_tenant_id + subscriptionId = var.azure_subscription_id + resourceGroup = var.azure_resource_group + useWorkloadIdentityExtension = var.azure_workload_identity_enabled + }, + var.azure_workload_identity_enabled ? {} : { + clientId = var.azure_client_id + clientSecret = var.azure_client_secret + } + )) } depends_on = [kubernetes_namespace_v1.external_dns] diff --git a/infrastructure/commons/external_dns/validation.tf b/infrastructure/commons/external_dns/validation.tf index 6b531498..d34cf990 100644 --- a/infrastructure/commons/external_dns/validation.tf +++ b/infrastructure/commons/external_dns/validation.tf @@ -32,6 +32,14 @@ resource "terraform_data" "provider_validation" { condition = var.dns_provider_name != "azure" || length(var.azure_client_id) > 0 error_message = "azure_client_id is required when dns_provider_name is 'azure'." } + precondition { + condition = var.dns_provider_name != "azure" || !var.azure_workload_identity_enabled || length(var.azure_federated_credential_id) > 0 + error_message = "azure_federated_credential_id is required when dns_provider_name is 'azure' and azure_workload_identity_enabled is true. Use module.iam to create the federated identity credential and pass its id output." + } + precondition { + condition = var.dns_provider_name != "azure" || var.azure_workload_identity_enabled || length(var.azure_client_secret) > 0 + error_message = "azure_client_secret is required when dns_provider_name is 'azure' and azure_workload_identity_enabled is false." + } precondition { condition = var.dns_provider_name != "azure" || length(var.azure_subscription_id) > 0 error_message = "azure_subscription_id is required when dns_provider_name is 'azure'." diff --git a/infrastructure/commons/external_dns/variables.tf b/infrastructure/commons/external_dns/variables.tf index 3b81fe10..f89f113c 100644 --- a/infrastructure/commons/external_dns/variables.tf +++ b/infrastructure/commons/external_dns/variables.tf @@ -152,8 +152,27 @@ variable "dns_provider_name" { # AZURE CONFIGURATION ############################################################################### +variable "azure_workload_identity_enabled" { + description = "Enable Workload Identity for Azure DNS provider. When false, Service Principal auth is used and azure_client_secret is required." + type = bool + default = true +} + +variable "azure_federated_credential_id" { + description = "Resource ID of the Azure federated identity credential for external-dns (required when dns_provider_name is 'azure' and azure_workload_identity_enabled is true). Pass module.iam_external_dns.id to enforce dependency ordering." + type = string + default = "" +} + +variable "azure_client_secret" { + description = "Azure AD client secret for Service Principal auth (required when dns_provider_name is 'azure' and azure_workload_identity_enabled is false)." + type = string + sensitive = true + default = "" +} + variable "azure_client_id" { - description = "Client ID of the Azure Managed Identity for Workload Identity (required when dns_provider_name is 'azure')" + description = "Client ID of the Azure Managed Identity for Workload Identity (required when dns_provider_name is 'azure' and azure_workload_identity_enabled is true)" type = string default = "" } diff --git a/infrastructure/commons/istio/variables.tf b/infrastructure/commons/istio/variables.tf index ca6a3257..cc29466b 100644 --- a/infrastructure/commons/istio/variables.tf +++ b/infrastructure/commons/istio/variables.tf @@ -21,9 +21,9 @@ variable "istiod_version" { } variable "istiod_replicas" { - description = "Number of istiod replicas. Default is 1 to preserve the previous behavior of this module for existing consumers; set to 2 (recommended) to let the pilot deployment tolerate node drains — the istiod chart installs a PodDisruptionBudget with minAvailable=1, and a single-replica istiod therefore blocks node rolling updates (e.g. EKS AMI bumps). This value is applied to both pilot.replicaCount and pilot.autoscaleMin; without the autoscaleMin override, the HPA (enabled by default with autoscaleMin=1) would scale back to 1 replica shortly after install." + description = "Number of istiod replicas. Set to 2+ to avoid PDB blocking node drains. Applied to both pilot.replicaCount and pilot.autoscaleMin to prevent the HPA from scaling back to 1." type = number - default = 1 + default = 2 validation { condition = var.istiod_replicas >= 1