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
5 changes: 5 additions & 0 deletions .changeset/sparkly-bananas-arrive.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<!-- markdownlint-disable -->
<!-- BEGIN_TF_DOCS -->
## Requirements
Expand Down Expand Up @@ -42,7 +50,7 @@ No modules.
|------|-------------|------|---------|:--------:|
| <a name="input_container_app_environment"></a> [container\_app\_environment](#input\_container\_app\_environment) | Configuration for the Container App Environment. | <pre>object({<br/> id = string<br/> location = string<br/> replica_timeout_in_seconds = optional(number, 1800)<br/> polling_interval_in_seconds = optional(number, 30)<br/> min_instances = optional(number, 0)<br/> max_instances = optional(number, 30)<br/> use_labels = optional(bool, false)<br/> override_labels = optional(list(string), [])<br/> cpu = optional(number, 1.5)<br/> memory = optional(string, "3Gi")<br/> image = optional(string, "ghcr.io/pagopa/github-self-hosted-runner-azure:latest")<br/> env_vars = optional(map(string), {})<br/> secrets = optional(map(string), {})<br/> })</pre> | n/a | yes |
| <a name="input_environment"></a> [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. | <pre>object({<br/> prefix = string<br/> env_short = string<br/> location = string<br/> instance_number = string<br/> })</pre> | n/a | yes |
| <a name="input_key_vault"></a> [key\_vault](#input\_key\_vault) | Details of the Key Vault used to store secrets for the Container App Job. | <pre>object({<br/> name = string<br/> resource_group_name = string<br/> use_rbac = optional(bool, false)<br/> secret_name = optional(string, "github-runner-pat")<br/> })</pre> | n/a | yes |
| <a name="input_key_vault"></a> [key\_vault](#input\_key\_vault) | "Details of the Key Vault used to store GitHub credentials. Use either:<br/> - 'app\_key\_secret\_name', 'app\_id\_secret\_name', and 'installation\_id\_secret\_name' for GitHub App authentication"<br/> - (Legacy) 'secret\_name' for PAT-based authentication | <pre>object({<br/> name = string<br/> resource_group_name = string<br/> use_rbac = optional(bool, false)<br/> secret_name = optional(string, "github-runner-pat")<br/> app_key_secret_name = optional(string, null)<br/> app_id_secret_name = optional(string, null)<br/> installation_id_secret_name = optional(string, null)<br/> })</pre> | n/a | yes |
| <a name="input_repository"></a> [repository](#input\_repository) | Details of the GitHub repository, including the owner and repository name. | <pre>object({<br/> owner = optional(string, "pagopa")<br/> name = string<br/> })</pre> | n/a | yes |
| <a name="input_resource_group_name"></a> [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 |
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to assign to the resources. | `map(any)` | n/a | yes |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# app_based

<!-- BEGIN_TF_DOCS -->
## Requirements

No requirements.

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_container_app_job_selfhosted_runner"></a> [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.
<!-- END_TF_DOCS -->
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

| Name | Source | Version |
|------|--------|---------|
| <a name="module_container_app_job_selfhosted_runner"></a> [container\_app\_job\_selfhosted\_runner](#module\_container\_app\_job\_selfhosted\_runner) | pagopa-dx/github-selfhosted-runner-on-container-app-jobs/azurerm | ~> 1.2 |
| <a name="module_container_app_job_selfhosted_runner"></a> [container\_app\_job\_selfhosted\_runner](#module\_container\_app\_job\_selfhosted\_runner) | pagopa-dx/github-selfhosted-runner-on-container-app-jobs/azurerm | ~> 1.3 |

## Resources

Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}