From 315b09599c1f9462efd990aec66767960590bdab Mon Sep 17 00:00:00 2001 From: safouak Date: Tue, 12 May 2026 10:56:14 +0200 Subject: [PATCH] feat: support centralised webhook proxy in multi-account deployments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When deploying runners with a centralised webhook proxy in a separate AWS account (e.g. an Ops account that validates and authorises webhook events before routing them to target accounts), three gaps force consumers to work around the module: 1. The module exclusively owns the SQS queue policy (DenyInsecureTransport only). Adding cross-account sqs:SendMessage requires overriding the whole policy, duplicating DenyInsecureTransport and causing permanent Terraform drift on every plan. 2. aws_sqs_queue_policy requires the queue URL, not the ARN. The queues output only exposed the ARN, forcing consumers to reconstruct the URL by string-splitting. 3. The webhook Lambda and API Gateway are always deployed even when the module's built-in webhook is not used (the external proxy receives GitHub events directly and publishes to SQS, bypassing the module's endpoint). Changes (root module + modules/multi-runner): - sqs_build_queue_extra_policy_json (default: null): optional policy JSON merged into the build queue policy via source_policy_documents. No-op when unset — no behaviour change for existing deployments. - build_queue_url added to the queues output (root); queues output added to modules/multi-runner exposing ARN + URL per runner key. - create_webhook_module (default: true): set to false to skip the webhook Lambda and API Gateway. The webhook output becomes null when disabled. --- main.tf | 10 +++++++++- modules/multi-runner/outputs.tf | 30 ++++++++++++++++++++---------- modules/multi-runner/queues.tf | 11 ++++++++++- modules/multi-runner/variables.tf | 12 ++++++++++++ modules/multi-runner/webhook.tf | 1 + outputs.tf | 21 +++++++++++---------- variables.tf | 12 ++++++++++++ 7 files changed, 75 insertions(+), 22 deletions(-) diff --git a/main.tf b/main.tf index a9a79c87a3..2a49e33a0f 100644 --- a/main.tf +++ b/main.tf @@ -48,9 +48,16 @@ data "aws_iam_policy_document" "deny_insecure_transport" { } } +data "aws_iam_policy_document" "build_queue_policy" { + source_policy_documents = compact([ + data.aws_iam_policy_document.deny_insecure_transport.json, + var.sqs_build_queue_extra_policy_json, + ]) +} + resource "aws_sqs_queue_policy" "build_queue_policy" { queue_url = aws_sqs_queue.queued_builds.id - policy = data.aws_iam_policy_document.deny_insecure_transport.json + policy = data.aws_iam_policy_document.build_queue_policy.json } resource "aws_sqs_queue" "queued_builds" { @@ -97,6 +104,7 @@ module "ssm" { module "webhook" { source = "./modules/webhook" + count = var.create_webhook_module ? 1 : 0 ssm_paths = { root = local.ssm_root_path diff --git a/modules/multi-runner/outputs.tf b/modules/multi-runner/outputs.tf index 7ce7171faf..65c1f9d810 100644 --- a/modules/multi-runner/outputs.tf +++ b/modules/multi-runner/outputs.tf @@ -32,16 +32,16 @@ output "binaries_syncer_map" { } output "webhook" { - value = { - gateway = module.webhook.gateway - lambda = module.webhook.lambda - lambda_log_group = module.webhook.lambda_log_group - lambda_role = module.webhook.role - endpoint = "${module.webhook.gateway.api_endpoint}/${module.webhook.endpoint_relative_path}" - webhook = module.webhook.webhook - dispatcher = var.eventbridge.enable ? module.webhook.dispatcher : null - eventbridge = var.eventbridge.enable ? module.webhook.eventbridge : null - } + value = var.create_webhook_module ? { + gateway = module.webhook[0].gateway + lambda = module.webhook[0].lambda + lambda_log_group = module.webhook[0].lambda_log_group + lambda_role = module.webhook[0].role + endpoint = "${module.webhook[0].gateway.api_endpoint}/${module.webhook[0].endpoint_relative_path}" + webhook = module.webhook[0].webhook + dispatcher = var.eventbridge.enable ? module.webhook[0].dispatcher : null + eventbridge = var.eventbridge.enable ? module.webhook[0].eventbridge : null + } : null } output "ssm_parameters" { @@ -67,3 +67,13 @@ output "instance_termination_handler" { lambda_role = module.instance_termination_watcher[0].spot_termination_handler.lambda_role } : null } + +output "queues" { + description = "SQS build queues per runner type." + value = { + for key in keys(var.multi_runner_config) : key => { + build_queue_arn = aws_sqs_queue.queued_builds[key].arn + build_queue_url = aws_sqs_queue.queued_builds[key].url + } + } +} diff --git a/modules/multi-runner/queues.tf b/modules/multi-runner/queues.tf index bcc75f99cc..fa7f18bf12 100644 --- a/modules/multi-runner/queues.tf +++ b/modules/multi-runner/queues.tf @@ -45,10 +45,19 @@ resource "aws_sqs_queue" "queued_builds" { tags = var.tags } +data "aws_iam_policy_document" "build_queue_policy" { + for_each = var.multi_runner_config + + source_policy_documents = compact([ + data.aws_iam_policy_document.deny_insecure_transport.json, + var.sqs_build_queue_extra_policy_json, + ]) +} + resource "aws_sqs_queue_policy" "build_queue_policy" { for_each = var.multi_runner_config queue_url = aws_sqs_queue.queued_builds[each.key].id - policy = data.aws_iam_policy_document.deny_insecure_transport.json + policy = data.aws_iam_policy_document.build_queue_policy[each.key].json } resource "aws_sqs_queue" "queued_builds_dlq" { diff --git a/modules/multi-runner/variables.tf b/modules/multi-runner/variables.tf index 613cf8b2ce..2bbee577f0 100644 --- a/modules/multi-runner/variables.tf +++ b/modules/multi-runner/variables.tf @@ -770,3 +770,15 @@ variable "parameter_store_tags" { type = map(string) default = {} } + +variable "sqs_build_queue_extra_policy_json" { + description = "Optional additional SQS policy statements (JSON) merged into the build queue policy for all runner types. Useful for cross-account access, e.g. allowing an SNS topic from another account to send messages." + type = string + default = null +} + +variable "create_webhook_module" { + description = "Set to false to skip deploying the webhook Lambda and API Gateway. Use when webhook delivery is handled externally (e.g. a centralised proxy in another account)." + type = bool + default = true +} diff --git a/modules/multi-runner/webhook.tf b/modules/multi-runner/webhook.tf index 900040c609..284356d024 100644 --- a/modules/multi-runner/webhook.tf +++ b/modules/multi-runner/webhook.tf @@ -1,5 +1,6 @@ module "webhook" { source = "../webhook" + count = var.create_webhook_module ? 1 : 0 prefix = var.prefix tags = local.tags kms_key_arn = var.kms_key_arn diff --git a/outputs.tf b/outputs.tf index fdf4a37801..42a68cb40c 100644 --- a/outputs.tf +++ b/outputs.tf @@ -31,16 +31,16 @@ output "binaries_syncer" { } output "webhook" { - value = { - gateway = module.webhook.gateway - lambda = module.webhook.lambda - lambda_log_group = module.webhook.lambda_log_group - lambda_role = module.webhook.role - endpoint = "${module.webhook.gateway.api_endpoint}/${module.webhook.endpoint_relative_path}" - webhook = module.webhook.webhook - dispatcher = var.eventbridge.enable ? module.webhook.dispatcher : null - eventbridge = var.eventbridge.enable ? module.webhook.eventbridge : null - } + value = var.create_webhook_module ? { + gateway = module.webhook[0].gateway + lambda = module.webhook[0].lambda + lambda_log_group = module.webhook[0].lambda_log_group + lambda_role = module.webhook[0].role + endpoint = "${module.webhook[0].gateway.api_endpoint}/${module.webhook[0].endpoint_relative_path}" + webhook = module.webhook[0].webhook + dispatcher = var.eventbridge.enable ? module.webhook[0].dispatcher : null + eventbridge = var.eventbridge.enable ? module.webhook[0].eventbridge : null + } : null } output "ssm_parameters" { @@ -56,6 +56,7 @@ output "queues" { description = "SQS queues." value = { build_queue_arn = aws_sqs_queue.queued_builds.arn + build_queue_url = aws_sqs_queue.queued_builds.url build_queue_dlq_arn = var.redrive_build_queue.enabled ? aws_sqs_queue.queued_builds_dlq[0].arn : null } } diff --git a/variables.tf b/variables.tf index d739e916fb..cd9e50aebb 100644 --- a/variables.tf +++ b/variables.tf @@ -3,6 +3,18 @@ variable "aws_region" { type = string } +variable "sqs_build_queue_extra_policy_json" { + description = "Optional additional SQS policy statements (JSON) merged into the build queue policy. Useful for cross-account access, e.g. allowing an SNS topic from another account to send messages." + type = string + default = null +} + +variable "create_webhook_module" { + description = "Set to false to skip deploying the webhook Lambda and API Gateway. Use when webhook delivery is handled externally (e.g. a centralised proxy in another account)." + type = bool + default = true +} + variable "vpc_id" { description = "The VPC for security groups of the action runners." type = string