diff --git a/.changeset/sparkly-bananas-arrive.md b/.changeset/sparkly-bananas-arrive.md new file mode 100644 index 0000000000..58bdb6a036 --- /dev/null +++ b/.changeset/sparkly-bananas-arrive.md @@ -0,0 +1,5 @@ +--- +"github_selfhosted_runner_on_container_app_jobs": minor +--- + +Switch to GitHub App-based authentication replacing PAT-based. This approach is generally more secure and scalable. diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/README.md b/infra/modules/github_selfhosted_runner_on_container_app_jobs/README.md index 7d2c4db207..9a307a289b 100644 --- a/infra/modules/github_selfhosted_runner_on_container_app_jobs/README.md +++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/README.md @@ -8,12 +8,20 @@ This module creates a Container App Job to be used as a GitHub self-hosted runne - **Container App Job**: Deploys a Container App Job in the specified Azure Container App Environment. - **Managed Identity**: Provides System Assigned Managed Identity for secure authentication with Azure resources. -- **Key Vault Access**: Grant access to the KeyVault instance with GitHub credentials (PAT token) +- **Key Vault Access**: Grant access to the KeyVault instance with GitHub credentials. - **Auto GitHub Registration**: Automatically scale and register as self-hosted runner in the target repository. +## Authentication Methods + +This module supports two authentication methods: + +1. **GitHub App Authentication (Recommended)**: Use `app_key_secret_name`, `app_id_secret_name`, and `installation_id_secret_name` in the `key_vault` variable. +2. **PAT-based Authentication (Legacy)**: Use `secret_name` in the `key_vault` variable for backward compatibility. + ## Usage Example A usage example can be found in the [examples](https://github.com/pagopa-dx/terraform-azurerm-azure-container-app/tree/main/examples/basic) directory. + ## Requirements @@ -42,7 +50,7 @@ No modules. |------|-------------|------|---------|:--------:| | [container\_app\_environment](#input\_container\_app\_environment) | Configuration for the Container App Environment. |
object({
id = string
location = string
replica_timeout_in_seconds = optional(number, 1800)
polling_interval_in_seconds = optional(number, 30)
min_instances = optional(number, 0)
max_instances = optional(number, 30)
use_labels = optional(bool, false)
override_labels = optional(list(string), [])
cpu = optional(number, 1.5)
memory = optional(string, "3Gi")
image = optional(string, "ghcr.io/pagopa/github-self-hosted-runner-azure:latest")
env_vars = optional(map(string), {})
secrets = optional(map(string), {})
}) | n/a | yes |
| [environment](#input\_environment) | Values which are used to generate resource names and location short names. They are all mandatory except for domain, which should not be used only in the case of a resource used by multiple domains. | object({
prefix = string
env_short = string
location = string
instance_number = string
}) | n/a | yes |
-| [key\_vault](#input\_key\_vault) | Details of the Key Vault used to store secrets for the Container App Job. | object({
name = string
resource_group_name = string
use_rbac = optional(bool, false)
secret_name = optional(string, "github-runner-pat")
}) | n/a | yes |
+| [key\_vault](#input\_key\_vault) | "Details of the Key Vault used to store GitHub credentials. Use either:object({
name = string
resource_group_name = string
use_rbac = optional(bool, false)
secret_name = optional(string, "github-runner-pat")
app_key_secret_name = optional(string, null)
app_id_secret_name = optional(string, null)
installation_id_secret_name = optional(string, null)
}) | n/a | yes |
| [repository](#input\_repository) | Details of the GitHub repository, including the owner and repository name. | object({
owner = optional(string, "pagopa")
name = string
}) | n/a | yes |
| [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group where the Container App Job will be deployed. Defaults to null. | `string` | `null` | no |
| [tags](#input\_tags) | A map of tags to assign to the resources. | `map(any)` | n/a | yes |
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/container_app_job.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/container_app_job.tf
index c238ef535d..7b3381fc33 100644
--- a/infra/modules/github_selfhosted_runner_on_container_app_jobs/container_app_job.tf
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/container_app_job.tf
@@ -24,29 +24,64 @@ resource "azurerm_container_app_job" "github_runner" {
name = "github-runner-rule"
custom_rule_type = "github-runner"
- # https://keda.sh/docs/2.17/scalers/github-runner/
+ # https://keda.sh/docs/2.19/scalers/github-runner/
metadata = merge({
+ githubApiURL = "https://api.github.com"
owner = var.repository.owner
runnerScope = "repo"
repos = var.repository.name
- targetWorkflowQueueLength = "1"
- github-runner = "https://api.github.com"
enableEtags = "true"
- }, var.container_app_environment.use_labels ? { labels = local.labels } : {})
+ targetWorkflowQueueLength = "1"
+ },
+ local.use_github_app ? {
+ applicationIDFromEnv = "GITHUB_APP_ID"
+ installationIDFromEnv = "GITHUB_APP_INSTALLATION_ID"
+ } : {},
+ var.container_app_environment.use_labels ? { labels = local.labels } : {}
+ )
authentication {
- secret_name = var.key_vault.secret_name
- trigger_parameter = "personalAccessToken"
+ secret_name = local.use_github_app ? var.key_vault.app_key_secret_name : var.key_vault.secret_name
+ trigger_parameter = local.use_github_app ? "appKey" : "personalAccessToken"
}
}
}
}
- secret {
- key_vault_secret_id = "${local.key_vault_uri}secrets/${var.key_vault.secret_name}" # no versioning
+ dynamic "secret" {
+ for_each = local.use_github_app ? [1] : []
+ content {
+ key_vault_secret_id = "${local.key_vault_uri}secrets/${var.key_vault.app_key_secret_name}"
+ identity = "System"
+ name = var.key_vault.app_key_secret_name
+ }
+ }
- identity = "System"
- name = var.key_vault.secret_name
+ dynamic "secret" {
+ for_each = local.use_github_app ? [1] : []
+ content {
+ key_vault_secret_id = "${local.key_vault_uri}secrets/${var.key_vault.app_id_secret_name}"
+ identity = "System"
+ name = var.key_vault.app_id_secret_name
+ }
+ }
+
+ dynamic "secret" {
+ for_each = local.use_github_app ? [1] : []
+ content {
+ key_vault_secret_id = "${local.key_vault_uri}secrets/${var.key_vault.installation_id_secret_name}"
+ identity = "System"
+ name = var.key_vault.installation_id_secret_name
+ }
+ }
+
+ dynamic "secret" {
+ for_each = local.use_github_app ? [] : [1]
+ content {
+ key_vault_secret_id = "${local.key_vault_uri}secrets/${var.key_vault.secret_name}"
+ identity = "System"
+ name = var.key_vault.secret_name
+ }
}
template {
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/README.md b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/README.md
index 3b170e1687..4df77b723c 100644
--- a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/README.md
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/README.md
@@ -4,7 +4,7 @@ This directory contains examples of how to use the GitHub SelfHosted Runner on A
## Basic Example
-The [basic example](./basic) demonstrates a simple implementation of the GitHub SelfHosted Runner on Azure Container App Job module.
+The [app-based example](./app-based) demonstrates a simple implementation of the GitHub SelfHosted Runner on Azure Container App Job module using GitHub App for authentication.
### Usage
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/README.md b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/README.md
new file mode 100644
index 0000000000..bfb44683c6
--- /dev/null
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/README.md
@@ -0,0 +1,28 @@
+# app_based
+
+
+## Requirements
+
+No requirements.
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [container\_app\_job\_selfhosted\_runner](#module\_container\_app\_job\_selfhosted\_runner) | pagopa-dx/github-selfhosted-runner-on-container-app-jobs/azurerm | ~> 1.4 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [azurerm_container_app_environment.gh_runner](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/container_app_environment) | data source |
+| [azurerm_resource_group.gh_runner](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source |
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+No outputs.
+
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/data.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/data.tf
similarity index 100%
rename from infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/data.tf
rename to infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/data.tf
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/locals.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/locals.tf
similarity index 90%
rename from infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/locals.tf
rename to infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/locals.tf
index 3525c1c3e6..0f6bd4d209 100644
--- a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/locals.tf
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/locals.tf
@@ -19,6 +19,6 @@ locals {
Environment = "Dev"
BusinessUnit = "DevEx"
ManagementTeam = "Developer Experience"
- Source = "https://github.com/pagopa/dx/modules/github_selfhosted_runner_container_app_job/examples/basic"
+ Source = "https://github.com/pagopa/dx/modules/github_selfhosted_runner_container_app_job/examples/app_based"
}
}
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/main.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/main.tf
new file mode 100644
index 0000000000..f4683005c8
--- /dev/null
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/app_based/main.tf
@@ -0,0 +1,28 @@
+module "container_app_job_selfhosted_runner" {
+ source = "pagopa-dx/github-selfhosted-runner-on-container-app-jobs/azurerm"
+ version = "~> 1.4"
+
+ environment = local.environment
+
+ resource_group_name = data.azurerm_resource_group.gh_runner.name
+
+ repository = {
+ name = local.repo_name
+ }
+
+ container_app_environment = {
+ id = data.azurerm_container_app_environment.gh_runner.id
+ location = local.environment.location
+ }
+
+ key_vault = {
+ name = local.key_vault.name
+ resource_group_name = local.key_vault.resource_group_name
+ use_rbac = true
+ app_key_secret_name = "github-runner-app-key"
+ app_id_secret_name = "github-runner-app-id"
+ installation_id_secret_name = "github-runner-app-installation-id"
+ }
+
+ tags = local.tags
+}
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/README.md b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/README.md
similarity index 95%
rename from infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/README.md
rename to infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/README.md
index 9c939c49b3..784a68fe21 100644
--- a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/README.md
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/README.md
@@ -11,7 +11,7 @@
| Name | Source | Version |
|------|--------|---------|
-| [container\_app\_job\_selfhosted\_runner](#module\_container\_app\_job\_selfhosted\_runner) | pagopa-dx/github-selfhosted-runner-on-container-app-jobs/azurerm | ~> 1.2 |
+| [container\_app\_job\_selfhosted\_runner](#module\_container\_app\_job\_selfhosted\_runner) | pagopa-dx/github-selfhosted-runner-on-container-app-jobs/azurerm | ~> 1.3 |
## Resources
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/data.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/data.tf
new file mode 100644
index 0000000000..afc59b3ba0
--- /dev/null
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/data.tf
@@ -0,0 +1,8 @@
+data "azurerm_resource_group" "gh_runner" {
+ name = "dx-d-itn-github-runner-rg-01"
+}
+
+data "azurerm_container_app_environment" "gh_runner" {
+ name = "dx-d-itn-github-runner-cae-01"
+ resource_group_name = data.azurerm_resource_group.gh_runner.name
+}
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/locals.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/locals.tf
new file mode 100644
index 0000000000..5e15b34b76
--- /dev/null
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/locals.tf
@@ -0,0 +1,24 @@
+locals {
+ environment = {
+ prefix = "dx"
+ env_short = "d"
+ location = "italynorth"
+ instance_number = "01"
+ }
+
+ repo_name = "dx"
+
+ key_vault = {
+ name = "dx-d-itn-common-kv-01"
+ resource_group_name = "dx-d-itn-common-rg-01"
+ }
+
+ tags = {
+ CostCenter = "TS000 - Tecnologia e Servizi"
+ CreatedBy = "Terraform"
+ Environment = "Dev"
+ BusinessUnit = "DevEx"
+ ManagementTeam = "Developer Experience"
+ Source = "https://github.com/pagopa/dx/modules/github_selfhosted_runner_container_app_job/examples/pat_based"
+ }
+}
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/main.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/main.tf
similarity index 96%
rename from infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/main.tf
rename to infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/main.tf
index 15cbacefc0..c48f4394e1 100644
--- a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/main.tf
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/main.tf
@@ -1,6 +1,6 @@
module "container_app_job_selfhosted_runner" {
source = "pagopa-dx/github-selfhosted-runner-on-container-app-jobs/azurerm"
- version = "~> 1.2"
+ version = "~> 1.3"
environment = local.environment
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/versions.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/versions.tf
similarity index 100%
rename from infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/basic/versions.tf
rename to infra/modules/github_selfhosted_runner_on_container_app_jobs/examples/pat_based/versions.tf
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/locals.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/locals.tf
index 463c957761..dadfcc86be 100644
--- a/infra/modules/github_selfhosted_runner_on_container_app_jobs/locals.tf
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/locals.tf
@@ -21,17 +21,24 @@ locals {
})) : var.resource_group_name
}
+ use_github_app = var.key_vault.app_key_secret_name != null
+
runner_env_vars = merge(var.container_app_environment.env_vars, {
REPO_URL = "https://github.com/${var.repository.owner}/${var.repository.name}"
REGISTRATION_TOKEN_API_URL = "https://api.github.com/repos/${var.repository.owner}/${var.repository.name}/actions/runners/registration-token"
})
- runner_secrets = merge(var.container_app_environment.secrets, {
- GITHUB_PAT = var.key_vault.secret_name
+ runner_secrets = merge(var.container_app_environment.secrets,
+ local.use_github_app ? {
+ GITHUB_APP_KEY = var.key_vault.app_key_secret_name
+ GITHUB_APP_ID = var.key_vault.app_id_secret_name
+ GITHUB_APP_INSTALLATION_ID = var.key_vault.installation_id_secret_name
+ } : {
+ GITHUB_PAT = var.key_vault.secret_name
})
labels = join(",", coalescelist(var.container_app_environment.override_labels, [local.env[var.environment.env_short]]))
- key_vault_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${var.key_vault.resource_group_name}/providers/Microsoft.KeyVault/vaults/${var.key_vault.name}"
+ key_vault_id = provider::azurerm::normalise_resource_id("/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${var.key_vault.resource_group_name}/providers/Microsoft.KeyVault/vaults/${var.key_vault.name}")
key_vault_uri = "https://${var.key_vault.name}.vault.azure.net/"
}
diff --git a/infra/modules/github_selfhosted_runner_on_container_app_jobs/variables.tf b/infra/modules/github_selfhosted_runner_on_container_app_jobs/variables.tf
index a33fe95673..df1c2654b2 100644
--- a/infra/modules/github_selfhosted_runner_on_container_app_jobs/variables.tf
+++ b/infra/modules/github_selfhosted_runner_on_container_app_jobs/variables.tf
@@ -51,11 +51,26 @@ variable "container_app_environment" {
variable "key_vault" {
type = object({
- name = string
- resource_group_name = string
- use_rbac = optional(bool, false)
- secret_name = optional(string, "github-runner-pat")
+ name = string
+ resource_group_name = string
+ use_rbac = optional(bool, false)
+ secret_name = optional(string, "github-runner-pat")
+ app_key_secret_name = optional(string, null)
+ app_id_secret_name = optional(string, null)
+ installation_id_secret_name = optional(string, null)
})
- description = "Details of the Key Vault used to store secrets for the Container App Job."
+ description = <<-EOT
+ "Details of the Key Vault used to store GitHub credentials. Use either:
+ - 'app_key_secret_name', 'app_id_secret_name', and 'installation_id_secret_name' for GitHub App authentication"
+ - (Legacy) 'secret_name' for PAT-based authentication
+ EOT
+
+ validation {
+ condition = (
+ (var.key_vault.secret_name != null && var.key_vault.app_key_secret_name == null && var.key_vault.app_id_secret_name == null && var.key_vault.installation_id_secret_name == null) ||
+ (var.key_vault.secret_name == null && var.key_vault.app_key_secret_name != null && var.key_vault.app_id_secret_name != null && var.key_vault.installation_id_secret_name != null)
+ )
+ error_message = "Either provide 'secret_name' for PAT-based authentication OR all three GitHub App credentials ('app_key_secret_name', 'app_id_secret_name', 'installation_id_secret_name'), but not both."
+ }
}