Skip to content

Commit

Permalink
feat: add CROSS_ACCOUNT_ROLE for Vault-based cross-account credential…
Browse files Browse the repository at this point in the history
… flow

- buildspec-executor.yml / buildspec.yml: default CROSS_ACCOUNT_ROLE=r-inf-terraform;
  replace hardcoded role name with ${CROSS_ACCOUNT_ROLE} in sts:AssumeRole block
  (interim scaffolding — will be replaced by vault read in CSC-1345)
- deploy/codebuild.tf: add CROSS_ACCOUNT_ROLE env var to executor project
- deploy/iam.tf: StsAssumeRoleCrossAccount allows r-inf-terraform, r-inf-terraform-eks,
  sc-automation-codebuild-role (backwards compat)
- lambda/app.py: add TfRunRequest.cross_account_role field (default: r-inf-terraform);
  pass CROSS_ACCOUNT_ROLE in CodeBuild env overrides for apply action
- docs/decisions/001-webhook-auto-apply.md: add cross_account_role to schema table
- design-docs/CHECKPOINT.md: update with Vault pivot and CSC-1344 blocked status

Jira: CSC-1344 (Blocked on CSC-1345)
  • Loading branch information
Dave Arnold committed Jun 2, 2026
1 parent 66838cf commit 3834d9e
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 16 deletions.
12 changes: 8 additions & 4 deletions buildspec-executor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ version: 0.2
# GITHUB_TOKEN - GHE PAT (PLAINTEXT, value from Secrets Manager)
#
# Optional env-var overrides:
# TARGET_ACCOUNT_ID - AWS account ID to assume sc-automation-codebuild-role in
# TARGET_ACCOUNT_ID - AWS account ID to assume the cross-account role in
# (default: empty = run with CodeBuild role, csvd-dev only)
# CROSS_ACCOUNT_ROLE - IAM role name to assume in TARGET_ACCOUNT_ID
# (default: r-inf-terraform)
# TF_RUN_START_TAG - tf-run.data TAG label to start from (default: empty = from top)
# DRY_RUN - "true" = tf-run plan only, no apply (default: "false")
# ---------------------------------------------------------------------------
Expand All @@ -32,6 +34,7 @@ env:
NO_PROXY: "github.e.it.census.gov,169.254.169.254,169.254.170.2"
# Per-build defaults (overridden via environmentVariablesOverride in Lambda)
TARGET_ACCOUNT_ID: ""
CROSS_ACCOUNT_ROLE: "r-inf-terraform"
TF_RUN_START_TAG: ""
DRY_RUN: "false"

Expand Down Expand Up @@ -99,12 +102,13 @@ phases:
- echo "Applying from $(git rev-parse --short HEAD) on main"

# --- Assume cross-account role (if TARGET_ACCOUNT_ID is set) ---
# The role sc-automation-codebuild-role must exist in the target account and
# trust the CodeBuild IAM role from the central account (csvd-dev).
# The role (default: r-inf-terraform) must exist in the target account and
# trust arn:...:iam::229685449397:role/tf-run-executor-codebuild.
# Override CROSS_ACCOUNT_ROLE per-build to use a different role name.
- |
if [ -n "${TARGET_ACCOUNT_ID}" ]; then
PARTITION=$(aws sts get-caller-identity --query Arn --output text | cut -d: -f2)
ROLE_ARN="arn:${PARTITION}:iam::${TARGET_ACCOUNT_ID}:role/sc-automation-codebuild-role"
ROLE_ARN="arn:${PARTITION}:iam::${TARGET_ACCOUNT_ID}:role/${CROSS_ACCOUNT_ROLE}"
echo "Assuming cross-account role: ${ROLE_ARN}"
CREDS=$(aws sts assume-role \
--role-arn "${ROLE_ARN}" \
Expand Down
11 changes: 7 additions & 4 deletions buildspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ version: 0.2
# TARGET_ACCOUNT_ID - AWS account ID to assume role in before running tf-run
# (default: empty = run with CodeBuild's own credentials,
# i.e. csvd-dev. Set this when targeting a different account.)
# CROSS_ACCOUNT_ROLE - IAM role name to assume in TARGET_ACCOUNT_ID
# (default: r-inf-terraform)
# ---------------------------------------------------------------------------

env:
Expand All @@ -41,6 +43,7 @@ env:
TEMPLATE_VARS: "{}"
EXTRA_FILES: "{}"
TARGET_ACCOUNT_ID: ""
CROSS_ACCOUNT_ROLE: "r-inf-terraform"

phases:
install:
Expand Down Expand Up @@ -167,13 +170,13 @@ phases:

# --- Assume cross-account role (if TARGET_ACCOUNT_ID is set) ---
# CodeBuild runs in csvd-dev by default. To run tf-run apply against resources
# in a different AWS account, set TARGET_ACCOUNT_ID. The role
# sc-automation-codebuild-role must exist in that account and trust the
# CodeBuild IAM role from csvd-dev.
# in a different AWS account, set TARGET_ACCOUNT_ID. The role (default:
# r-inf-terraform) must exist in that account and trust the CodeBuild IAM
# role from csvd-dev. Override CROSS_ACCOUNT_ROLE per-build if needed.
- |
if [ -n "${TARGET_ACCOUNT_ID}" ]; then
PARTITION=$(aws sts get-caller-identity --query Arn --output text | cut -d: -f2)
ROLE_ARN="arn:${PARTITION}:iam::${TARGET_ACCOUNT_ID}:role/sc-automation-codebuild-role"
ROLE_ARN="arn:${PARTITION}:iam::${TARGET_ACCOUNT_ID}:role/${CROSS_ACCOUNT_ROLE}"
echo "Assuming cross-account role: ${ROLE_ARN}"
CREDS=$(aws sts assume-role \
--role-arn "${ROLE_ARN}" \
Expand Down
4 changes: 4 additions & 0 deletions deploy/codebuild.tf
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ resource "aws_codebuild_project" "tf_run_executor" {
name = "TARGET_ACCOUNT_ID"
value = ""
}
environment_variable {
name = "CROSS_ACCOUNT_ROLE"
value = "r-inf-terraform"
}
environment_variable {
name = "TF_RUN_START_TAG"
value = ""
Expand Down
11 changes: 8 additions & 3 deletions deploy/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@ data "aws_iam_policy_document" "codebuild_exec" {
]
}

# Secrets Manager: read the GHE PAT at runtime (GITHUB_TOKEN env var)
# Note: CodeBuild uses PARAMETER_STORE for the token; this covers the SM read
# used during Terraform apply of source credentials (aws_codebuild_source_credential).
# Secrets Manager: read the GHE PAT at runtime.
# Both CodeBuild projects define GITHUB_TOKEN as type=SECRETS_MANAGER pointing to this
# secret. CodeBuild fetches the current value fresh at each build start using this
# permission, so the token never appears in StartBuild CloudTrail logs or BatchGetBuilds
# responses. This also covers the SM read in aws_codebuild_source_credential.
statement {
sid = "SecretsManagerReadGheToken"
effect = "Allow"
Expand Down Expand Up @@ -163,11 +165,14 @@ data "aws_iam_policy_document" "codebuild_exec" {

# STS: allow executor to assume a cross-account role in target accounts
# Only the executor needs this; proposer only needs GHE access.
# Default role is r-inf-terraform; can be overridden per-build via CROSS_ACCOUNT_ROLE.
statement {
sid = "StsAssumeRoleCrossAccount"
effect = "Allow"
actions = ["sts:AssumeRole"]
resources = [
"arn:${data.aws_partition.current.partition}:iam::*:role/r-inf-terraform",
"arn:${data.aws_partition.current.partition}:iam::*:role/r-inf-terraform-eks",
"arn:${data.aws_partition.current.partition}:iam::*:role/sc-automation-codebuild-role",
]
}
Expand Down
12 changes: 12 additions & 0 deletions design-docs/CHECKPOINT.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@

Parent: **[CSC-1341](https://jira.it.census.gov/browse/CSC-1341)**[sc-lambda-ghactions] Design & implement next-gen SC automation system

**Completed work (In Review — GHE PR #1 open):**

| Key | Summary | Priority | Status | ADR |
|-----|---------|----------|--------|-----|
| [CSC-1351](https://jira.it.census.gov/browse/CSC-1351) | Phase 1: CodeBuild runner + buildspec | High | In Review ||
| [CSC-1352](https://jira.it.census.gov/browse/CSC-1352) | Phase 2: Lambda CFN Custom Resource handler | High | In Review ||
| [CSC-1353](https://jira.it.census.gov/browse/CSC-1353) | Phase 3: Service Catalog product registration | High | In Review ||
| [CSC-1354](https://jira.it.census.gov/browse/CSC-1354) | Architecture design, .sc-automation.yml schema, and deploy Terraform | High | In Review ||
| [CSC-1355](https://jira.it.census.gov/browse/CSC-1355) | ADR-001: Webhook auto-apply on merge accepted | High | In Review | [ADR-001](../docs/decisions/001-webhook-auto-apply.md) |

**Open / remaining work:**

| Key | Summary | Priority | Status | ADR |
|-----|---------|----------|--------|-----|
| [CSC-1342](https://jira.it.census.gov/browse/CSC-1342) | Build and push Lambda container image to ECR (via packer-pipeline) | High | To Do ||
Expand Down
1 change: 1 addition & 0 deletions docs/decisions/001-webhook-auto-apply.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Fields per entry:
| `layer` | yes | `common`, `infrastructure`, or `vpc` |
| `region_dir` | yes | `east`, `west`, or `global` |
| `target_account_id` | no | 12-digit AWS account ID; omit to run in csvd-dev |
| `cross_account_role` | no | IAM role name to assume in `target_account_id` (default: `r-inf-terraform`) |
| `tf_run_start_tag` | no | tf-run TAG label to start from |
| `dry_run` | no | `true` to plan only (default: `false`) |

Expand Down
16 changes: 11 additions & 5 deletions lambda/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ class TfRunRequest(BaseModel):
git_branch: str = Field(default="propose/sc-automation", description="Branch to commit and open PR from (propose only)")

# --- Executor fields (action=apply only) ---
target_account_id: str = Field(default="", description="AWS account ID to assume sc-automation-codebuild-role in before running tf-run; empty = run with CodeBuild role (csvd-dev)")
target_account_id: str = Field(default="", description="AWS account ID to assume cross_account_role in before running tf-run; empty = run with CodeBuild role (csvd-dev)")
cross_account_role: str = Field(default="r-inf-terraform", description="IAM role name to assume in target_account_id (default: r-inf-terraform)")
tf_run_start_tag: str = Field(default="", description="tf-run.data TAG label to start from; empty = from beginning (apply only)")
dry_run: bool = Field(default=False, description="true = tf-run plan only, no apply (apply action only)")

Expand Down Expand Up @@ -139,11 +140,17 @@ def send_cfn_response(

def start_codebuild_build(
tf_req: TfRunRequest,
github_token: str,
request_id: str,
) -> str:
"""Start the proposer or executor CodeBuild project with per-build env-var overrides.
GITHUB_TOKEN is intentionally omitted here — both CodeBuild projects define it
as type=SECRETS_MANAGER at the project level. The CodeBuild service role has
secretsmanager:GetSecretValue for that secret, so CodeBuild fetches the current
value fresh at each build start without the token ever appearing in CloudTrail
(StartBuild) or BatchGetBuilds API responses. Passing it as PLAINTEXT here would
override that project-level definition and expose the token in both.
Returns the CodeBuild build ID.
"""
if tf_req.action == "propose":
Expand All @@ -156,7 +163,6 @@ def start_codebuild_build(
{"name": "TEMPLATE_REPO", "value": tf_req.template_repo, "type": "PLAINTEXT"},
{"name": "TEMPLATE_VARS", "value": json.dumps(tf_req.template_vars), "type": "PLAINTEXT"},
{"name": "EXTRA_FILES", "value": json.dumps(tf_req.extra_files), "type": "PLAINTEXT"},
{"name": "GITHUB_TOKEN", "value": github_token, "type": "PLAINTEXT"},
]
else: # apply
project_name = os.environ.get("EXECUTOR_PROJECT_NAME", "tf-run-executor")
Expand All @@ -165,9 +171,9 @@ def start_codebuild_build(
{"name": "LAYER", "value": tf_req.layer, "type": "PLAINTEXT"},
{"name": "REGION_DIR", "value": tf_req.region_dir, "type": "PLAINTEXT"},
{"name": "TARGET_ACCOUNT_ID", "value": tf_req.target_account_id, "type": "PLAINTEXT"},
{"name": "CROSS_ACCOUNT_ROLE", "value": tf_req.cross_account_role, "type": "PLAINTEXT"},
{"name": "TF_RUN_START_TAG", "value": tf_req.tf_run_start_tag, "type": "PLAINTEXT"},
{"name": "DRY_RUN", "value": str(tf_req.dry_run).lower(), "type": "PLAINTEXT"},
{"name": "GITHUB_TOKEN", "value": github_token, "type": "PLAINTEXT"},
]

region = os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-gov-west-1"))
Expand Down Expand Up @@ -320,7 +326,7 @@ def lambda_handler(event: dict, context) -> dict:
logger.info(f"[{request_id}] Fetching GitHub token from secret: {github_token_secret}")
github_token = get_secret(github_token_secret)

build_id = start_codebuild_build(tf_req, github_token, request_id)
build_id = start_codebuild_build(tf_req, request_id)

# Poll — leave 60s buffer before Lambda timeout for cfn-response PUT
lambda_timeout_s = context.get_remaining_time_in_millis() / 1000
Expand Down

0 comments on commit 3834d9e

Please sign in to comment.