Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions infrastructure/commons/cert_manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,12 @@ module "cert_manager" {
| <a name="input_aws_region"></a> [aws\_region](#input\_aws\_region) | The AWS region. | `string` | `""` | no |
| <a name="input_aws_sa_arn"></a> [aws\_sa\_arn](#input\_aws\_sa\_arn) | The AWS IAM role ARN for cert-manager. | `string` | `""` | no |
| <a name="input_azure_client_id"></a> [azure\_client\_id](#input\_azure\_client\_id) | The Azure client ID for cert-manager. | `string` | `""` | no |
| <a name="input_azure_client_secret"></a> [azure\_client\_secret](#input\_azure\_client\_secret) | The Azure client secret for Service Principal authentication. | `string` | `""` | no |
| <a name="input_azure_hosted_zone_name"></a> [azure\_hosted\_zone\_name](#input\_azure\_hosted\_zone\_name) | The hosted zone name in Azure DNS. | `string` | `""` | no |
| <a name="input_azure_resource_group_name"></a> [azure\_resource\_group\_name](#input\_azure\_resource\_group\_name) | The name of the Azure resource group that contains the DNS zone. | `string` | `""` | no |
| <a name="input_azure_subscription_id"></a> [azure\_subscription\_id](#input\_azure\_subscription\_id) | The Azure subscription ID. | `string` | `""` | no |
| <a name="input_azure_tenant_id"></a> [azure\_tenant\_id](#input\_azure\_tenant\_id) | The Azure tenant ID. | `string` | `""` | no |
| <a name="input_azure_use_workload_identity"></a> [azure\_use\_workload\_identity](#input\_azure\_use\_workload\_identity) | Whether to use Azure Workload Identity (true) or Service Principal (false). | `bool` | `false` | no |
| <a name="input_cert_manager_config_version"></a> [cert\_manager\_config\_version](#input\_cert\_manager\_config\_version) | The version of the cert-manager configuration Helm chart | `string` | `"2.29.2"` | no |
| <a name="input_cert_manager_namespace"></a> [cert\_manager\_namespace](#input\_cert\_manager\_namespace) | The Kubernetes namespace where cert-manager will be deployed | `string` | `"cert-manager"` | no |
| <a name="input_cert_manager_version"></a> [cert\_manager\_version](#input\_cert\_manager\_version) | The version of cert-manager Helm chart to deploy | `string` | `"1.18.2"` | no |
Expand Down
1 change: 1 addition & 0 deletions infrastructure/commons/cert_manager/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ locals {
client_id = var.azure_client_id
tenant_id = var.azure_tenant_id
hosted_zone_name = var.azure_hosted_zone_name
client_secret = var.azure_client_secret
} : {},

var.cloud_provider == "aws" ? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ azure:
clientID: "${client_id}"
tenantID: "${tenant_id}"
hostedZoneName: "${hosted_zone_name}"
%{ if client_secret != "" ~}
clientSecret: "${client_secret}"
%{ endif ~}
13 changes: 13 additions & 0 deletions infrastructure/commons/cert_manager/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,19 @@ variable "azure_hosted_zone_name" {
}
}

variable "azure_client_secret" {
description = "The Azure client secret for Service Principal authentication."
type = string
sensitive = true
default = ""
}

variable "azure_use_workload_identity" {
description = "Whether to use Azure Workload Identity (true) or Service Principal (false)."
type = bool
default = false
}

###############################################################################
# CLOUDFLARE CONFIGURATION
###############################################################################
Expand Down
42 changes: 41 additions & 1 deletion infrastructure/commons/external_dns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Module: external_dns

This OpenTofu module installs **ExternalDNS** using a Helm chart, enabling dynamic DNS record management through
either **AWS Route53** or **Cloudflare** as your DNS provider.
**AWS Route53**, **Cloudflare**, **OCI DNS**, or **Azure DNS** as your DNS provider.


## Usage
Expand Down Expand Up @@ -35,6 +35,39 @@ module "external_dns" {
}
```

### Azure DNS example (with Workload Identity - recommended for AKS)

```hcl
module "external_dns" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/commons/external_dns?ref=v1.0.0"

dns_provider_name = "azure"
azure_resource_group = var.azure_resource_group
azure_tenant_id = var.azure_tenant_id
azure_subscription_id = var.azure_subscription_id
azure_client_id = var.azure_managed_identity_client_id
azure_use_workload_identity = true
domain_filters = var.domain_filters
}
```

### Azure DNS example (with Service Principal)

```hcl
module "external_dns" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/commons/external_dns?ref=v1.0.0"

dns_provider_name = "azure"
azure_resource_group = var.azure_resource_group
azure_tenant_id = var.azure_tenant_id
azure_subscription_id = var.azure_subscription_id
azure_client_id = var.azure_client_id
azure_client_secret = var.azure_client_secret
azure_use_workload_identity = false
domain_filters = var.domain_filters
}
```

<!-- BEGIN_TF_DOCS -->
## Requirements

Expand All @@ -55,6 +88,7 @@ module "external_dns" {
|------|------|
| [helm_release.external_dns](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource |
| [kubernetes_namespace_v1.external_dns](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace_v1) | resource |
| [kubernetes_secret_v1.external_dns_azure](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes_secret_v1.external_dns_cloudflare](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes_secret_v1.external_dns_oci_config](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |

Expand All @@ -64,6 +98,12 @@ module "external_dns" {
|------|-------------|------|---------|:--------:|
| <a name="input_aws_iam_role_arn"></a> [aws\_iam\_role\_arn](#input\_aws\_iam\_role\_arn) | The IAM role ARN for ExternalDNS to assume for Route53 access (required when dns\_provider\_name is 'aws') | `string` | `null` | no |
| <a name="input_aws_region"></a> [aws\_region](#input\_aws\_region) | The AWS region where the Route53 hosted zones are located | `string` | `null` | no |
| <a name="input_azure_client_id"></a> [azure\_client\_id](#input\_azure\_client\_id) | The Azure client ID (application ID) for service principal authentication. Required when azure\_use\_workload\_identity is false. | `string` | `null` | no |
| <a name="input_azure_client_secret"></a> [azure\_client\_secret](#input\_azure\_client\_secret) | The Azure client secret for service principal authentication. Required when azure\_use\_workload\_identity is false. | `string` | `null` | no |
| <a name="input_azure_resource_group"></a> [azure\_resource\_group](#input\_azure\_resource\_group) | The Azure resource group containing the DNS zone (required when dns\_provider\_name is 'azure') | `string` | `null` | no |
| <a name="input_azure_subscription_id"></a> [azure\_subscription\_id](#input\_azure\_subscription\_id) | The Azure subscription ID containing the DNS zone (required when dns\_provider\_name is 'azure') | `string` | `null` | no |
| <a name="input_azure_tenant_id"></a> [azure\_tenant\_id](#input\_azure\_tenant\_id) | The Azure tenant ID for authentication (required when dns\_provider\_name is 'azure') | `string` | `null` | no |
| <a name="input_azure_use_workload_identity"></a> [azure\_use\_workload\_identity](#input\_azure\_use\_workload\_identity) | Whether to use Azure Workload Identity for authentication (recommended for AKS) | `bool` | `true` | no |
| <a name="input_cloudflare_token"></a> [cloudflare\_token](#input\_cloudflare\_token) | The Cloudflare API token for DNS management (required when dns\_provider\_name is 'cloudflare') | `string` | `null` | no |
| <a name="input_dns_provider_name"></a> [dns\_provider\_name](#input\_dns\_provider\_name) | The DNS provider to use with ExternalDNS | `string` | n/a | yes |
| <a name="input_domain_filters"></a> [domain\_filters](#input\_domain\_filters) | The domain filter to limit ExternalDNS to manage DNS records only for specific domains | `string` | n/a | yes |
Expand Down
65 changes: 61 additions & 4 deletions infrastructure/commons/external_dns/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ locals {
provider = { name = "aws" }
env = [{
name = "AWS_DEFAULT_REGION"
value = var.aws_region
value = var.aws_region != null ? var.aws_region : ""
}]
serviceAccount = {
create = true
annotations = {
"eks.amazonaws.com/role-arn" = var.aws_iam_role_arn
"eks.amazonaws.com/role-arn" = var.aws_iam_role_arn != null ? var.aws_iam_role_arn : ""
}
}
rbac = {
Expand Down Expand Up @@ -62,7 +62,7 @@ locals {
}
]
extraArgs = [
"--oci-compartment-ocid=${var.oci_compartment_ocid}",
"--oci-compartment-ocid=${var.oci_compartment_ocid != null ? var.oci_compartment_ocid : ""}",
"--oci-zone-scope=${var.oci_zone_scope}",
"--oci-zones-cache-duration=${var.oci_zones_cache_duration}"
]
Expand All @@ -83,11 +83,68 @@ locals {
]
}

azure_workload_identity_config = {
provider = { name = "azure" }
serviceAccount = {
create = true
labels = {
"azure.workload.identity/use" = "true"
}
annotations = {
"azure.workload.identity/client-id" = var.azure_client_id != null ? var.azure_client_id : ""
}
}
podLabels = {
"azure.workload.identity/use" = "true"
}
env = [
{
name = "AZURE_TENANT_ID"
value = var.azure_tenant_id != null ? var.azure_tenant_id : ""
},
{
name = "AZURE_SUBSCRIPTION_ID"
value = var.azure_subscription_id != null ? var.azure_subscription_id : ""
}
]
extraArgs = [
"--azure-resource-group=${var.azure_resource_group != null ? var.azure_resource_group : ""}",
"--azure-config-file="
]
}

azure_service_principal_config = {
provider = { name = "azure" }
extraArgs = [
"--azure-resource-group=${var.azure_resource_group != null ? var.azure_resource_group : ""}"
]
extraVolumes = [
{
name = "azure-config"
secret = {
secretName = "external-dns-azure"
}
}
]
extraVolumeMounts = [
{
name = "azure-config"
mountPath = "/etc/kubernetes/"
readOnly = true
}
]
}

provider_configs = {
cloudflare = local.cloudflare_config
aws = local.route53_config
oci = local.oci_config
azure = local.azure_workload_identity_config
azure-sp = local.azure_service_principal_config
}

external_dns_values = merge(local.base_config, local.provider_configs[var.dns_provider_name])
# Determine the actual provider key to use
effective_provider_key = var.dns_provider_name == "azure" && !var.azure_use_workload_identity ? "azure-sp" : var.dns_provider_name

external_dns_values = merge(local.base_config, local.provider_configs[local.effective_provider_key])
}
3 changes: 2 additions & 1 deletion infrastructure/commons/external_dns/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ resource "helm_release" "external_dns" {

depends_on = [
kubernetes_secret_v1.external_dns_cloudflare,
kubernetes_secret_v1.external_dns_oci_config
kubernetes_secret_v1.external_dns_oci_config,
kubernetes_secret_v1.external_dns_azure
]
}
25 changes: 25 additions & 0 deletions infrastructure/commons/external_dns/secret.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ resource "kubernetes_secret_v1" "external_dns_cloudflare" {
data = {
"api-token" = var.cloudflare_token
}

depends_on = [kubernetes_namespace_v1.external_dns]
}

resource "kubernetes_secret_v1" "external_dns_azure" {
count = var.dns_provider_name == "azure" && !var.azure_use_workload_identity ? 1 : 0

metadata {
name = "external-dns-azure"
namespace = var.external_dns_namespace
}

type = "Opaque"
data = {
"azure.json" = jsonencode({
tenantId = var.azure_tenant_id
subscriptionId = var.azure_subscription_id
resourceGroup = var.azure_resource_group
aadClientId = var.azure_client_id
aadClientSecret = var.azure_client_secret
useManagedIdentityExtension = false
})
}

depends_on = [kubernetes_namespace_v1.external_dns]
}

resource "kubernetes_secret_v1" "external_dns_oci_config" {
Expand Down
61 changes: 59 additions & 2 deletions infrastructure/commons/external_dns/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,63 @@ variable "zone_type" {
}
}

###############################################################################
# AZURE CONFIGURATION
###############################################################################

variable "azure_resource_group" {
description = "The Azure resource group containing the DNS zone (required when dns_provider_name is 'azure')"
type = string
default = null
validation {
condition = var.dns_provider_name != "azure" || var.azure_resource_group != null
error_message = "azure_resource_group is required when dns_provider_name is 'azure'."
}
}

variable "azure_tenant_id" {
description = "The Azure tenant ID for authentication (required when dns_provider_name is 'azure')"
type = string
default = null
validation {
condition = var.dns_provider_name != "azure" || var.azure_tenant_id != null
error_message = "azure_tenant_id is required when dns_provider_name is 'azure'."
}
}

variable "azure_subscription_id" {
description = "The Azure subscription ID containing the DNS zone (required when dns_provider_name is 'azure')"
type = string
default = null
validation {
condition = var.dns_provider_name != "azure" || var.azure_subscription_id != null
error_message = "azure_subscription_id is required when dns_provider_name is 'azure'."
}
}

variable "azure_use_workload_identity" {
description = "Whether to use Azure Workload Identity for authentication (recommended for AKS)"
type = bool
default = true
}

variable "azure_client_id" {
description = "The Azure client ID (application ID) for service principal authentication. Required when azure_use_workload_identity is false."
type = string
default = null
}

variable "azure_client_secret" {
description = "The Azure client secret for service principal authentication. Required when azure_use_workload_identity is false."
type = string
sensitive = true
default = null
validation {
condition = var.dns_provider_name != "azure" || var.azure_use_workload_identity == true || (var.azure_client_id != null && var.azure_client_secret != null)
error_message = "azure_client_id and azure_client_secret are required when dns_provider_name is 'azure' and azure_use_workload_identity is false."
}
}

###############################################################################
# OCI CONFIGURATION
###############################################################################
Expand Down Expand Up @@ -166,7 +223,7 @@ variable "dns_provider_name" {
type = string
description = "The DNS provider to use with ExternalDNS "
validation {
condition = contains(["cloudflare", "aws", "oci"], var.dns_provider_name)
error_message = "dns_provider_name must be either 'cloudflare', 'aws', or 'oci'."
condition = contains(["cloudflare", "aws", "oci", "azure"], var.dns_provider_name)
error_message = "dns_provider_name must be either 'cloudflare', 'aws', 'oci', or 'azure'."
}
}