From 3b9b9fb936500c5cbfe0cfc1e5f89c0d55d51c97 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Mon, 10 Aug 2026 15:42:12 -0400 Subject: [PATCH] feat(iam): add prod weekly-menu GitHub OIDC role --- terraform/iam_github_weekly_menu.tf | 115 ++++++++++++++++++++++++++++ terraform/outputs.tf | 5 ++ 2 files changed, 120 insertions(+) create mode 100644 terraform/iam_github_weekly_menu.tf diff --git a/terraform/iam_github_weekly_menu.tf b/terraform/iam_github_weekly_menu.tf new file mode 100644 index 0000000..dd2cefc --- /dev/null +++ b/terraform/iam_github_weekly_menu.tf @@ -0,0 +1,115 @@ +# GitHub Actions OIDC role for .github/workflows/weekly-menu.yml. +# +# Trust is pinned three ways (aud, sub to main, job_workflow_ref to the +# weekly-menu workflow at main) so no other workflow in the repo can assume it. +# Permissions mirror the mgmt github-oidc-deploy-roles weekly-menu role, retargeted +# to prod resources and without form-api-key (SigV4 publish path). + +data "aws_iam_openid_connect_provider" "github" { + url = "https://token.actions.githubusercontent.com" +} + +data "aws_iam_policy_document" "weekly_menu_assume" { + statement { + effect = "Allow" + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + identifiers = [data.aws_iam_openid_connect_provider.github.arn] + } + + condition { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:aud" + values = ["sts.amazonaws.com"] + } + + condition { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:sub" + values = ["repo:Sea-Haven-Industries/meal-order-manager:ref:refs/heads/main"] + } + + condition { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:job_workflow_ref" + values = ["Sea-Haven-Industries/meal-order-manager/.github/workflows/weekly-menu.yml@refs/heads/main"] + } + } +} + +resource "aws_iam_role" "weekly_menu" { + name = "githubdeploy-meal-order-manager-weekly-menu" + path = "/tf-managed/" + description = "GitHub Actions weekly-menu scrape/publish for meal-order-manager" + assume_role_policy = data.aws_iam_policy_document.weekly_menu_assume.json + permissions_boundary = local.boundary_arn + max_session_duration = 3600 +} + +data "aws_iam_policy_document" "weekly_menu" { + statement { + sid = "SlackBotSecret" + effect = "Allow" + actions = [ + "secretsmanager:GetSecretValue", + ] + resources = [var.slack_bot_secret_arn] + } + + statement { + sid = "DeployAndAppParams" + effect = "Allow" + actions = [ + "ssm:GetParameter", + ] + resources = [ + "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.ssm_prefix}/deploy/api-url", + "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.ssm_prefix}/deploy/form-bucket", + "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.ssm_prefix}/deploy/distribution-id", + "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.ssm_prefix}/deploy/form-url", + "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.google_client_id_param}", + "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.slack_channel_param}", + ] + } + + statement { + sid = "PublishApi" + effect = "Allow" + actions = [ + "execute-api:Invoke", + ] + resources = [ + "arn:aws:execute-api:${var.aws_region}:${local.account_id}:${aws_apigatewayv2_api.order_api.id}/*/GET/api/publish/settings", + "arn:aws:execute-api:${var.aws_region}:${local.account_id}:${aws_apigatewayv2_api.order_api.id}/*/POST/api/publish/menu", + ] + } + + statement { + sid = "FormObjects" + effect = "Allow" + actions = [ + "s3:PutObject", + ] + resources = [ + "${aws_s3_bucket.form.arn}/index.html", + "${aws_s3_bucket.form.arn}/archive/*.html", + ] + } + + statement { + sid = "InvalidateForm" + effect = "Allow" + actions = [ + "cloudfront:CreateInvalidation", + ] + resources = [aws_cloudfront_distribution.form.arn] + } +} + +resource "aws_iam_role_policy" "weekly_menu" { + name = "weekly-menu-publish" + role = aws_iam_role.weekly_menu.id + policy = data.aws_iam_policy_document.weekly_menu.json +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf index 78ea0d1..b73fa21 100644 --- a/terraform/outputs.tf +++ b/terraform/outputs.tf @@ -38,6 +38,11 @@ output "orders_table_name" { value = aws_dynamodb_table.orders.name } +output "weekly_menu_role_arn" { + description = "OIDC role ARN for .github/workflows/weekly-menu.yml (repo secret AWS_WEEKLY_MENU_ROLE_ARN)." + value = aws_iam_role.weekly_menu.arn +} + output "shared_layer_arn" { description = "Version ARN of the shared Lambda layer." value = aws_lambda_layer_version.shared.arn