From 4f1c0b98689b777ff7e2fc329382ffe200a3a94d Mon Sep 17 00:00:00 2001 From: Alastair Lock Date: Thu, 21 May 2026 15:28:52 +0100 Subject: [PATCH] PPHA-925: Convert the container registry to generic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need Azure Container Apps to pull container images from a private GitHub Container Registry (GHCR) repository. At present, GitHub Container Registry does not support authentication using Azure Managed Identity. Because of this, authentication must be handled using GitHub credentials, typically a username and Personal Access Token (PAT). • The PAT would be stored securely as a secret • Terraform would reference the secret during deployment • Azure Container Apps would use the PAT to authenticate with GHCR User Dependency PAT tokens are generally associated with an individual GitHub user account. If that user leaves the organisation, has their account disabled, loses the required permissions, or rotates their credentials, the token may become invalid and could impact deployments or running services. Secret Management Overhead Using PAT tokens also introduces additional operational responsibilities, including: • Secure storage and rotation of secrets • Token and credential lifecycle management • Monitoring token expiry dates • Manual renewal and maintenance processes --- .../modules/container-app-job/README.md | 22 +++++++++++++++++ .../modules/container-app-job/main.tf | 14 +++++------ .../modules/container-app-job/tfdocs.md | 24 +++++++++++++++++++ .../modules/container-app-job/variables.tf | 18 +++++++++----- .../modules/container-app/README.md | 23 ++++++++++++++++++ infrastructure/modules/container-app/main.tf | 14 +++++------ .../modules/container-app/tfdocs.md | 24 +++++++++++++++++++ .../modules/container-app/variables.tf | 18 +++++++++----- 8 files changed, 131 insertions(+), 26 deletions(-) diff --git a/infrastructure/modules/container-app-job/README.md b/infrastructure/modules/container-app-job/README.md index d4567ca6..0ac9075f 100644 --- a/infrastructure/modules/container-app-job/README.md +++ b/infrastructure/modules/container-app-job/README.md @@ -70,3 +70,25 @@ module "job" { fetch_secrets_from_app_key_vault = true } ``` + +## Generic private registry authentication + +The module can authenticate to any private container registry by providing the registry server URL, username and a Key Vault secret URI containing the password. The module will create a container registry credential in the container app referencing the Key Vault secret for secure authentication. + +Example: +```hcl +module "container-app-job" { + + source = "../../../dtos-devops-templates/infrastructure/modules/container-app-job" + + name = "ca-workload-name-${var.environment}" + resource_group_name = var.resource_group_name + location = var.location + container_app_environment_id = module.container-app-environment.id + + container_registry_server = "ghcr.io" + container_registry_username = "github-username" + container_registry_secret_uri = module.app-key-vault.secrets["ghcr-token"].versionless_id + +} +``` diff --git a/infrastructure/modules/container-app-job/main.tf b/infrastructure/modules/container-app-job/main.tf index b8b9a3f4..c8cf9747 100644 --- a/infrastructure/modules/container-app-job/main.tf +++ b/infrastructure/modules/container-app-job/main.tf @@ -40,22 +40,22 @@ resource "azurerm_container_app_job" "this" { } dynamic "secret" { - for_each = var.ghcr_pat_secret_uri != null ? [1] : [] + for_each = var.container_registry_secret_uri != null ? [1] : [] content { - name = "ghcr-token" - key_vault_secret_id = var.ghcr_pat_secret_uri + name = "password" + key_vault_secret_id = var.container_registry_secret_uri identity = module.container_app_identity.id } } dynamic "registry" { - for_each = var.ghcr_pat_secret_uri != null ? [1] : [] + for_each = var.container_registry_secret_uri != null ? [1] : [] content { - server = "ghcr.io" - username = var.ghcr_username - password_secret_name = "ghcr-token" + server = var.container_registry_server + username = var.container_registry_username + password_secret_name = "password" } } diff --git a/infrastructure/modules/container-app-job/tfdocs.md b/infrastructure/modules/container-app-job/tfdocs.md index 09448e46..8b87709b 100644 --- a/infrastructure/modules/container-app-job/tfdocs.md +++ b/infrastructure/modules/container-app-job/tfdocs.md @@ -88,6 +88,30 @@ Type: `list(string)` Default: `null` +### [container\_registry\_secret\_uri](#input\_container\_registry\_secret\_uri) + +Description: Key Vault secret URI containing the registry password or token + +Type: `string` + +Default: `null` + +### [container\_registry\_server](#input\_container\_registry\_server) + +Description: Container registry hostname (for example ghcr.io) + +Type: `string` + +Default: `null` + +### [container\_registry\_username](#input\_container\_registry\_username) + +Description: Username used to authenticate to the container registry + +Type: `string` + +Default: `null` + ### [cron\_expression](#input\_cron\_expression) Description: Cron formatted repeating schedule of a Cron Job eg. '0 5 * * *'. Optional. diff --git a/infrastructure/modules/container-app-job/variables.tf b/infrastructure/modules/container-app-job/variables.tf index 33ef2939..3b51b7c4 100644 --- a/infrastructure/modules/container-app-job/variables.tf +++ b/infrastructure/modules/container-app-job/variables.tf @@ -161,16 +161,22 @@ variable "time_window" { default = 30 } -variable "ghcr_username" { +variable "container_registry_server" { type = string - description = "" - default = null + description = "Container registry hostname (for example ghcr.io)" + default = null +} + +variable "container_registry_username" { + type = string + description = "Username used to authenticate to the container registry" + default = null } -variable "ghcr_pat_secret_uri" { +variable "container_registry_secret_uri" { type = string - description = "URI of the GitHub Container Registry Personal Access Token stored in Key Vault. This is used to authenticate to GHCR if var.docker_image is hosted there. The secret must be in the format 'username:token'." - default = null + description = "Key Vault secret URI containing the registry password or token" + default = null } locals { diff --git a/infrastructure/modules/container-app/README.md b/infrastructure/modules/container-app/README.md index 553dafb3..bad6ce08 100644 --- a/infrastructure/modules/container-app/README.md +++ b/infrastructure/modules/container-app/README.md @@ -153,3 +153,26 @@ We will allow using the previously pinned "4.34.0" or newer, as defined in the c New version definition is `version = ">= 4.34.0"` More on the provider version constraints in terraform modules can be found [here](https://developer.hashicorp.com/terraform/language/modules/develop/providers#provider-version-constraints-in-modules). + + +## Generic private registry authentication + +The module can authenticate to any private container registry by providing the registry server URL, username and a Key Vault secret URI containing the password. The module will create a container registry credential in the container app referencing the Key Vault secret for secure authentication. + +Example: +```hcl +module "container-app" { + + source = "../../../dtos-devops-templates/infrastructure/modules/container-app" + + name = "ca-workload-name-${var.environment}" + resource_group_name = var.resource_group_name + location = var.location + container_app_environment_id = module.container-app-environment.id + + container_registry_server = "ghcr.io" + container_registry_username = "github-username" + container_registry_secret_uri = module.app-key-vault.secrets["ghcr-token"].versionless_id + +} +``` diff --git a/infrastructure/modules/container-app/main.tf b/infrastructure/modules/container-app/main.tf index 5e07d9df..131f8324 100644 --- a/infrastructure/modules/container-app/main.tf +++ b/infrastructure/modules/container-app/main.tf @@ -62,22 +62,22 @@ resource "azurerm_container_app" "main" { } dynamic "secret" { - for_each = var.ghcr_pat_secret_uri != null ? [1] : [] + for_each = var.container_registry_secret_uri != null ? [1] : [] content { - name = "ghcr-token" - key_vault_secret_id = var.ghcr_pat_secret_uri + name = "password" + key_vault_secret_id = var.container_registry_secret_uri identity = module.container_app_identity.id } } dynamic "registry" { - for_each = var.ghcr_pat_secret_uri != null ? [1] : [] + for_each = var.container_registry_secret_uri != null ? [1] : [] content { - server = "ghcr.io" - username = var.ghcr_username - password_secret_name = "ghcr-token" + server = var.container_registry_server + username = var.container_registry_username + password_secret_name = "password" } } diff --git a/infrastructure/modules/container-app/tfdocs.md b/infrastructure/modules/container-app/tfdocs.md index 8584ea66..47c75ee6 100644 --- a/infrastructure/modules/container-app/tfdocs.md +++ b/infrastructure/modules/container-app/tfdocs.md @@ -96,6 +96,30 @@ Type: `list(string)` Default: `[]` +### [container\_registry\_secret\_uri](#input\_container\_registry\_secret\_uri) + +Description: Key Vault secret URI containing the registry password or token + +Type: `string` + +Default: `null` + +### [container\_registry\_server](#input\_container\_registry\_server) + +Description: Container registry hostname (for example ghcr.io) + +Type: `string` + +Default: `null` + +### [container\_registry\_username](#input\_container\_registry\_username) + +Description: Username used to authenticate to the container registry + +Type: `string` + +Default: `null` + ### [enable\_alerting](#input\_enable\_alerting) Description: Whether monitoring and alerting is enabled for the PostgreSQL Flexible Server. diff --git a/infrastructure/modules/container-app/variables.tf b/infrastructure/modules/container-app/variables.tf index 340ddf20..cd5a7411 100644 --- a/infrastructure/modules/container-app/variables.tf +++ b/infrastructure/modules/container-app/variables.tf @@ -202,16 +202,22 @@ variable "probe_path" { default = null } -variable "ghcr_username" { +variable "container_registry_server" { type = string - description = "" - default = null + description = "Container registry hostname (for example ghcr.io)" + default = null +} + +variable "container_registry_username" { + type = string + description = "Username used to authenticate to the container registry" + default = null } -variable "ghcr_pat_secret_uri" { +variable "container_registry_secret_uri" { type = string - description = "URI of the GitHub Container Registry Personal Access Token stored in Key Vault. This is used to authenticate to GHCR if var.docker_image is hosted there. The secret must be in the format 'username:token'." - default = null + description = "Key Vault secret URI containing the registry password or token" + default = null } locals {