From fe00fd3cb1d793d5ece3439a198c762d11bb0cb1 Mon Sep 17 00:00:00 2001 From: Marat Soltobaev Date: Thu, 6 Nov 2025 12:35:53 -0600 Subject: [PATCH 1/5] feat(runner-role): Enable using separate iam role for runners --- main.tf | 1 + modules/runners/logging.tf | 4 +-- modules/runners/main.tf | 2 +- modules/runners/policies-runner.tf | 41 +++++++++++++++++------------- modules/runners/pool.tf | 2 +- modules/runners/scale-up.tf | 2 +- modules/runners/variables.tf | 19 +++++++++++++- variables.tf | 17 +++++++++++++ 8 files changed, 64 insertions(+), 24 deletions(-) diff --git a/main.tf b/main.tf index 9c72614808..f776f70ee1 100644 --- a/main.tf +++ b/main.tf @@ -157,6 +157,7 @@ module "runners" { subnet_ids = var.subnet_ids prefix = var.prefix tags = local.tags + iam_overrides = var.iam_overrides ssm_paths = { root = local.ssm_root_path diff --git a/modules/runners/logging.tf b/modules/runners/logging.tf index 1b61f16f7b..84823bb432 100644 --- a/modules/runners/logging.tf +++ b/modules/runners/logging.tf @@ -59,9 +59,9 @@ resource "aws_cloudwatch_log_group" "gh_runners" { } resource "aws_iam_role_policy" "cloudwatch" { - count = var.enable_cloudwatch_agent ? 1 : 0 + count = var.iam_overrides["override_runner_role"] ? 0 : (var.enable_cloudwatch_agent ? 1 : 0) name = "CloudWatchLogginAndMetrics" - role = aws_iam_role.runner.name + role = aws_iam_role.runner[0].name policy = templatefile("${path.module}/policies/instance-cloudwatch-policy.json", { ssm_parameter_arn = aws_ssm_parameter.cloudwatch_agent_config_runner[0].arn diff --git a/modules/runners/main.tf b/modules/runners/main.tf index 7184b08ee0..2966d2522b 100644 --- a/modules/runners/main.tf +++ b/modules/runners/main.tf @@ -173,7 +173,7 @@ resource "aws_launch_template" "runner" { } iam_instance_profile { - name = aws_iam_instance_profile.runner.name + name = var.iam_overrides["override_instance_profile"] ? var.iam_overrides["instance_profile_name"] : aws_iam_instance_profile.runner[0].name } instance_initiated_shutdown_behavior = "terminate" diff --git a/modules/runners/policies-runner.tf b/modules/runners/policies-runner.tf index 2b7d894619..405245b0aa 100644 --- a/modules/runners/policies-runner.tf +++ b/modules/runners/policies-runner.tf @@ -1,6 +1,7 @@ data "aws_caller_identity" "current" {} resource "aws_iam_role" "runner" { + count = var.iam_overrides["override_runner_role"] ? 0 : 1 name = "${substr("${var.prefix}-runner", 0, 54)}-${substr(md5("${var.prefix}-runner"), 0, 8)}" assume_role_policy = templatefile("${path.module}/policies/instance-role-trust-policy.json", {}) path = local.role_path @@ -9,22 +10,24 @@ resource "aws_iam_role" "runner" { } resource "aws_iam_instance_profile" "runner" { - name = "${var.prefix}-runner-profile" - role = aws_iam_role.runner.name - path = local.instance_profile_path - tags = local.tags + count = var.iam_overrides["override_instance_profile"] ? 0 : 1 + name = "${var.prefix}-runner-profile" + role = aws_iam_role.runner[0].name + path = local.instance_profile_path + tags = local.tags } resource "aws_iam_role_policy" "runner_session_manager_aws_managed" { + count = var.iam_overrides["override_runner_role"] ? 0 : (var.enable_ssm_on_runners ? 1 : 0) name = "runner-ssm-session" - count = var.enable_ssm_on_runners ? 1 : 0 - role = aws_iam_role.runner.name + role = aws_iam_role.runner[0].name policy = templatefile("${path.module}/policies/instance-ssm-policy.json", {}) } resource "aws_iam_role_policy" "ssm_parameters" { - name = "runner-ssm-parameters" - role = aws_iam_role.runner.name + count = var.iam_overrides["override_runner_role"] ? 0 : 1 + name = "runner-ssm-parameters" + role = aws_iam_role.runner[0].name policy = templatefile("${path.module}/policies/instance-ssm-parameters-policy.json", { arn_ssm_parameters_path_tokens = "arn:${var.aws_partition}:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter${var.ssm_paths.root}/${var.ssm_paths.tokens}" @@ -34,10 +37,10 @@ resource "aws_iam_role_policy" "ssm_parameters" { } resource "aws_iam_role_policy" "dist_bucket" { - count = var.enable_runner_binaries_syncer ? 1 : 0 + count = var.iam_overrides["override_runner_role"] ? 0 : (var.enable_runner_binaries_syncer ? 1 : 0) name = "distribution-bucket" - role = aws_iam_role.runner.name + role = aws_iam_role.runner[0].name policy = templatefile("${path.module}/policies/instance-s3-policy.json", { s3_arn = "${var.s3_runner_binaries.arn}/${var.s3_runner_binaries.key}" @@ -46,33 +49,35 @@ resource "aws_iam_role_policy" "dist_bucket" { } resource "aws_iam_role_policy_attachment" "xray_tracing" { - count = var.tracing_config.mode != null ? 1 : 0 - role = aws_iam_role.runner.name + count = var.iam_overrides["override_runner_role"] ? 0 : (var.tracing_config.mode != null ? 1 : 0) + role = aws_iam_role.runner[0].name policy_arn = "arn:${var.aws_partition}:iam::aws:policy/AWSXRayDaemonWriteAccess" } resource "aws_iam_role_policy" "describe_tags" { + count = var.iam_overrides["override_runner_role"] ? 0 : 1 name = "runner-describe-tags" - role = aws_iam_role.runner.name + role = aws_iam_role.runner[0].name policy = file("${path.module}/policies/instance-describe-tags-policy.json") } resource "aws_iam_role_policy" "create_tag" { + count = var.iam_overrides["override_runner_role"] ? 0 : 1 name = "runner-create-tags" - role = aws_iam_role.runner.name + role = aws_iam_role.runner[0].name policy = templatefile("${path.module}/policies/instance-create-tags-policy.json", {}) } resource "aws_iam_role_policy_attachment" "managed_policies" { - count = length(var.runner_iam_role_managed_policy_arns) - role = aws_iam_role.runner.name + count = var.iam_overrides["override_runner_role"] ? 0 : length(var.runner_iam_role_managed_policy_arns) + role = aws_iam_role.runner[0].name policy_arn = element(var.runner_iam_role_managed_policy_arns, count.index) } - resource "aws_iam_role_policy" "ec2" { + count = var.iam_overrides["override_runner_role"] ? 0 : 1 name = "ec2" - role = aws_iam_role.runner.name + role = aws_iam_role.runner[0].name policy = templatefile("${path.module}/policies/instance-ec2.json", {}) } diff --git a/modules/runners/pool.tf b/modules/runners/pool.tf index 2762008ebf..8489b495dd 100644 --- a/modules/runners/pool.tf +++ b/modules/runners/pool.tf @@ -48,7 +48,7 @@ module "pool" { group_name = var.runner_group_name name_prefix = var.runner_name_prefix pool_owner = var.pool_runner_owner - role = aws_iam_role.runner + role = var.iam_overrides["override_runner_role"] ? var.iam_overrides["runner_role_arn"] : aws_iam_role.runner[0].name } subnet_ids = var.subnet_ids ssm_token_path = "${var.ssm_paths.root}/${var.ssm_paths.tokens}" diff --git a/modules/runners/scale-up.tf b/modules/runners/scale-up.tf index 9230267c07..01f5468728 100644 --- a/modules/runners/scale-up.tf +++ b/modules/runners/scale-up.tf @@ -112,7 +112,7 @@ resource "aws_iam_role_policy" "scale_up" { name = "scale-up-policy" role = aws_iam_role.scale_up.name policy = templatefile("${path.module}/policies/lambda-scale-up.json", { - arn_runner_instance_role = aws_iam_role.runner.arn + arn_runner_instance_role = var.iam_overrides["override_runner_role"] ? var.iam_overrides["runner_role_arn"] : aws_iam_role.runner[0].arn sqs_arn = var.sqs_build_queue.arn github_app_id_arn = var.github_app_parameters.id.arn github_app_key_base64_arn = var.github_app_parameters.key_base64.arn diff --git a/modules/runners/variables.tf b/modules/runners/variables.tf index a78231e7da..b1427fde2d 100644 --- a/modules/runners/variables.tf +++ b/modules/runners/variables.tf @@ -36,7 +36,7 @@ variable "subnet_ids" { } variable "overrides" { - description = "This map provides the possibility to override some defaults. The following attributes are supported: `name_sg` overrides the `Name` tag for all security groups created by this module. `name_runner_agent_instance` overrides the `Name` tag for the ec2 instance defined in the auto launch configuration. `name_docker_machine_runners` overrides the `Name` tag spot instances created by the runner agent." + description = "This map provides the possibility to override some defaults. The following attributes are supported: `name_sg` overrides the `Name` tag for all security groups created by this module. `name_runner` overrides the `Name` tag for the ec2 instance defined in the auto launch configuration. `instance_profile_name` overrides the instance profile name used in the launch template." type = map(string) default = { @@ -45,6 +45,23 @@ variable "overrides" { } } +variable "iam_overrides" { + description = "This map provides the possibility to override some IAM defaults. The following attributes are supported: `instance_profile_name` overrides the instance profile name used in the launch template. `runner_role_arn` overrides the IAM role ARN used for the runner instances." + type = object({ + override_instance_profile = optional(bool, null) + instance_profile_name = optional(string, null) + override_runner_role = optional(bool, null) + runner_role_arn = optional(string, null) + }) + + default = { + override_instance_profile = false + instance_profile_name = null + override_runner_role = false + runner_role_arn = null + } +} + variable "tags" { description = "Map of tags that will be added to created resources. By default resources will be tagged with name." type = map(string) diff --git a/variables.tf b/variables.tf index f412d2a486..dcafc8f849 100644 --- a/variables.tf +++ b/variables.tf @@ -108,6 +108,23 @@ variable "runner_group_name" { default = "Default" } +variable "iam_overrides" { + description = "This map provides the possibility to override some defaults. The following attributes are supported: `name_sg` overrides the `Name` tag for all security groups created by this module. `name_runner` overrides the `Name` tag for the ec2 instance defined in the auto launch configuration. `instance_profile_name` overrides the instance profile name used in the launch template." + type = object({ + override_instance_profile = optional(bool, null) + instance_profile_name = optional(string, null) + override_runner_role = optional(bool, null) + runner_role_arn = optional(string, null) + }) + + default = { + override_instance_profile = false + instance_profile_name = null + override_runner_role = false + runner_role_arn = null + } +} + variable "scale_up_reserved_concurrent_executions" { description = "Amount of reserved concurrent executions for the scale-up lambda function. A value of 0 disables lambda from being triggered and -1 removes any concurrency limitations." type = number From b09301d61ccb07443cf1d0407420c92bee29ad34 Mon Sep 17 00:00:00 2001 From: Marat Soltobaev Date: Thu, 6 Nov 2025 12:50:58 -0600 Subject: [PATCH 2/5] Update variable description --- variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variables.tf b/variables.tf index dcafc8f849..8e10c91172 100644 --- a/variables.tf +++ b/variables.tf @@ -109,7 +109,7 @@ variable "runner_group_name" { } variable "iam_overrides" { - description = "This map provides the possibility to override some defaults. The following attributes are supported: `name_sg` overrides the `Name` tag for all security groups created by this module. `name_runner` overrides the `Name` tag for the ec2 instance defined in the auto launch configuration. `instance_profile_name` overrides the instance profile name used in the launch template." + description = "This map provides the possibility to override some IAM defaults. Note that when using this variable, you are responsible for ensuring the role has necessary permissions to access required resources; `override_instance_profile`: When set to true, the instance profile name provided in `instance_profile_name` will be used for the runners. `override_runner_role`: When set to true, the role ARN provided in `runner_role_arn` will be used for the runners." type = object({ override_instance_profile = optional(bool, null) instance_profile_name = optional(string, null) From 703763ffe224e8a204f4a561609788e511878e23 Mon Sep 17 00:00:00 2001 From: Marat Soltobaev Date: Thu, 6 Nov 2025 15:03:31 -0600 Subject: [PATCH 3/5] Add iam_overrides var to multi-runner module --- modules/multi-runner/variables.tf | 17 +++++++++++++++++ modules/runners/variables.tf | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/multi-runner/variables.tf b/modules/multi-runner/variables.tf index be35ad09f8..88e1951009 100644 --- a/modules/multi-runner/variables.tf +++ b/modules/multi-runner/variables.tf @@ -718,3 +718,20 @@ variable "user_agent" { type = string default = "github-aws-runners" } + +variable "iam_overrides" { + description = "This map provides the possibility to override some IAM defaults. The following attributes are supported: `instance_profile_name` overrides the instance profile name used in the launch template. `runner_role_arn` overrides the IAM role ARN used for the runner instances." + type = object({ + override_instance_profile = optional(bool, null) + instance_profile_name = optional(string, null) + override_runner_role = optional(bool, null) + runner_role_arn = optional(string, null) + }) + + default = { + override_instance_profile = false + instance_profile_name = null + override_runner_role = false + runner_role_arn = null + } +} diff --git a/modules/runners/variables.tf b/modules/runners/variables.tf index 79bc36f1fa..d324b749af 100644 --- a/modules/runners/variables.tf +++ b/modules/runners/variables.tf @@ -36,7 +36,7 @@ variable "subnet_ids" { } variable "overrides" { - description = "This map provides the possibility to override some defaults. The following attributes are supported: `name_sg` overrides the `Name` tag for all security groups created by this module. `name_runner` overrides the `Name` tag for the ec2 instance defined in the auto launch configuration. `instance_profile_name` overrides the instance profile name used in the launch template." + description = "This map provides the possibility to override some defaults. The following attributes are supported: `name_sg` overrides the `Name` tag for all security groups created by this module. `name_runner_agent_instance` overrides the `Name` tag for the ec2 instance defined in the auto launch configuration. `name_docker_machine_runners` overrides the `Name` tag spot instances created by the runner agent." type = map(string) default = { From 8cdfa6aed3eb1dcd2db8741d173e154d7851b220 Mon Sep 17 00:00:00 2001 From: Marat Soltobaev Date: Thu, 20 Nov 2025 17:03:09 -0600 Subject: [PATCH 4/5] Define iam_overrides var in multi_runners module --- modules/multi-runner/runners.tf | 1 + modules/multi-runner/variables.tf | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/modules/multi-runner/runners.tf b/modules/multi-runner/runners.tf index 811ab36260..3115be3f84 100644 --- a/modules/multi-runner/runners.tf +++ b/modules/multi-runner/runners.tf @@ -100,6 +100,7 @@ module "runners" { create_service_linked_role_spot = each.value.runner_config.create_service_linked_role_spot runner_iam_role_managed_policy_arns = each.value.runner_config.runner_iam_role_managed_policy_arns + iam_overrides = each.value.runner_config.iam_overrides ghes_url = var.ghes_url ghes_ssl_verify = var.ghes_ssl_verify diff --git a/modules/multi-runner/variables.tf b/modules/multi-runner/variables.tf index 88e1951009..ec28d02ed9 100644 --- a/modules/multi-runner/variables.tf +++ b/modules/multi-runner/variables.tf @@ -162,6 +162,17 @@ variable "multi_runner_config" { lambda_timeout = optional(number, 30) max_attempts = optional(number, 1) }), {}) + iam_overrides = optional(object({ + override_instance_profile = optional(bool, null) + instance_profile_name = optional(string, null) + override_runner_role = optional(bool, null) + runner_role_arn = optional(string, null) + }), { + override_instance_profile = false + instance_profile_name = null + override_runner_role = false + runner_role_arn = null + }) }) matcherConfig = object({ labelMatchers = list(list(string)) @@ -233,6 +244,7 @@ variable "multi_runner_config" { block_device_mappings: "The EC2 instance block device configuration. Takes the following keys: `device_name`, `delete_on_termination`, `volume_type`, `volume_size`, `encrypted`, `iops`, `throughput`, `kms_key_id`, `snapshot_id`." job_retry: "Experimental! Can be removed / changed without trigger a major release. Configure job retries. The configuration enables job retries (for ephemeral runners). After creating the instances a message will be published to a job retry queue. The job retry check lambda is checking after a delay if the job is queued. If not the message will be published again on the scale-up (build queue). Using this feature can impact the rate limit of the GitHub app." pool_config: "The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for week days to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1. Use `schedule_expression_timezone` to override the schedule time zone (defaults to UTC)." + iam_overrides: "Allows to (optionally) override the instance profile and runner role created by the module. Set `override_instance_profile` to true and provide the `instance_profile_name` to use an existing instance profile. Set `override_runner_role` to true and provide the `runner_role_arn` to use an existing role for the runner instances." } matcherConfig: { labelMatchers: "The list of list of labels supported by the runner configuration. `[[self-hosted, linux, x64, example]]`" From e0fb3e6068bba5f7aab706c701af5776fe1a31e1 Mon Sep 17 00:00:00 2001 From: Marat Soltobaev Date: Thu, 20 Nov 2025 17:06:26 -0600 Subject: [PATCH 5/5] tf fmt --- modules/multi-runner/runners.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/multi-runner/runners.tf b/modules/multi-runner/runners.tf index 3115be3f84..ac06bd54ae 100644 --- a/modules/multi-runner/runners.tf +++ b/modules/multi-runner/runners.tf @@ -100,7 +100,7 @@ module "runners" { create_service_linked_role_spot = each.value.runner_config.create_service_linked_role_spot runner_iam_role_managed_policy_arns = each.value.runner_config.runner_iam_role_managed_policy_arns - iam_overrides = each.value.runner_config.iam_overrides + iam_overrides = each.value.runner_config.iam_overrides ghes_url = var.ghes_url ghes_ssl_verify = var.ghes_ssl_verify