Skip to content

Instantly share code, notes, and snippets.

@portellaa
Forked from fdelu/managed_certificate.md
Last active September 17, 2024 15:49
Show Gist options
  • Save portellaa/b50e717395c8df32f2e460e6b190526a to your computer and use it in GitHub Desktop.
Save portellaa/b50e717395c8df32f2e460e6b190526a to your computer and use it in GitHub Desktop.
Managed certificate creation with terraform - Azure Container Apps

variables.tf

variable "env" {
  description = "Environment"
  default     = "dev"
}

variable "app_name" {
  description = "Application name"
}

variable "subdomain" {
  description = "Subdomain for the DNS record"
}

variable "dns_zone" {
  description = "DNS Zone name"
}

variable "dns_resource_group" {
  description = "DNS resource group"
}

variable "location" {
  description = "Location"
  default     = "Brazil South"
}

main.tf

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.80.0"
    }
    azapi = {
      source = "azure/azapi"
    }
  }
  backend "remote" {}
}

provider "azurerm" {
  features {}
}

provider "azapi" {}

resource "azurerm_resource_group" "rg" {
  name     = "${upper(var.env)}-${upper(var.app_name)}-RG"
  location = var.location
}

resource "azurerm_log_analytics_workspace" "logs" {
  name                = "${upper(var.env)}-${upper(var.app_name)}-logs"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_container_app_environment" "app_env" {
  name                       = "${upper(var.env)}-${upper(var.app_name)}-CAE"
  location                   = azurerm_resource_group.rg.location
  resource_group_name        = azurerm_resource_group.rg.name
  log_analytics_workspace_id = azurerm_log_analytics_workspace.logs.id
}

resource "azurerm_container_app" "app" {
  name                         = "${lower(var.env)}-${lower(var.app_name)}-ca"
  container_app_environment_id = azurerm_container_app_environment.app_env.id
  resource_group_name          = azurerm_resource_group.rg.name
  revision_mode                = "Single"

  template {
    container {
      name   = "${var.env}-${var.app_name}-container"
      image  = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"
      cpu    = 0.25
      memory = "0.5Gi"
    }
  }

  ingress {
    transport                  = "http"
    target_port                = 80
    external_enabled           = true
    allow_insecure_connections = false
    traffic_weight {
      percentage      = 100
      latest_revision = true
    }
  }

  lifecycle {
    ignore_changes = [ingress.0.custom_domain] // Required to not delete the custom domain created in dns.tf
  }
}

output "default_app_url" {
  value = azurerm_container_app.app.ingress[0].fqdn
}

output "app_url" {
  value = time_sleep.dns_propagation.triggers["url"]
}

dns.tf

data "azurerm_dns_zone" "dns" {
  name                = var.dns_zone
  resource_group_name = var.dns_resource_group
}

resource "azurerm_dns_cname_record" "cname_record" {
  name                = var.subdomain
  zone_name           = data.azurerm_dns_zone.dns.name
  resource_group_name = data.azurerm_dns_zone.dns.resource_group_name
  ttl                 = 3600
  record              = azurerm_container_app.app.ingress[0].fqdn
}

data "azapi_resource" "app_verification_id" {
  resource_id = azurerm_container_app_environment.app_env.id
  type        = "Microsoft.App/managedEnvironments@2023-05-01"

  response_export_values = ["properties.customDomainConfiguration.customDomainVerificationId"]
}

locals {
  verificationId = jsondecode(data.azapi_resource.app_verification_id.output).properties.customDomainConfiguration.customDomainVerificationId
}

resource "azurerm_dns_txt_record" "txt_record" {
  name                = "asuid.${var.subdomain}"
  zone_name           = data.azurerm_dns_zone.dns.name
  resource_group_name = data.azurerm_dns_zone.dns.resource_group_name
  ttl                 = 3600
  record {
    value = local.verificationId
  }
}


resource "time_sleep" "dns_propagation" {
  create_duration = "60s"

  depends_on = [azurerm_dns_txt_record.txt_record, azurerm_dns_cname_record.cname_record]

  triggers = {
    url            = "${azurerm_dns_cname_record.cname_record.name}.${data.azurerm_dns_zone.dns.name}",
    verificationId = local.verificationId,
    record         = azurerm_dns_cname_record.cname_record.record,
  }
}

// azurerm can't create a managed TLS certificate - see https://github.com/hashicorp/terraform-provider-azurerm/issues/21866
// The following resources are the workaround

resource "azapi_update_resource" "custom_domain" {
  type        = "Microsoft.App/containerApps@2023-05-01"
  resource_id = azurerm_container_app.app.id

  body = jsonencode({
    properties = {
      configuration = {
        ingress = {
          customDomains = [
          {
            bindingType = "Disabled",
            name        = time_sleep.dns_propagation.triggers["url"],
          }
        ]
        }
      }
    }
  })
}

resource "azapi_resource" "managed_certificate" {
  depends_on = [time_sleep.dns_propagation, azapi_update_resource.custom_domain]
  type       = "Microsoft.App/ManagedEnvironments/managedCertificates@2023-05-01"
  name       = "${lower(var.env)}-${lower(var.app_name)}-cert"
  parent_id  = azurerm_container_app_environment.app_env.id
  location   = azurerm_resource_group.rg.location

  body = jsonencode({
    properties = {
      subjectName             = time_sleep.dns_propagation.triggers["url"]
      domainControlValidation = "CNAME"
    }
  })

  response_export_values = ["*"]
}

resource "azapi_update_resource" "custom_domain_binding" {
  type        = "Microsoft.App/containerApps@2023-05-01"
  resource_id = azurerm_container_app.app.id

  body = jsonencode({
    properties = {
      configuration = {
        ingress = {
          customDomains = [
            {
              bindingType   = "SniEnabled",
              name          = time_sleep.dns_propagation.triggers["url"],
              certificateId = jsondecode(azapi_resource.managed_certificate.output).id
            }
          ]
        }
      }
    }
  })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment