diff --git a/terraform/lambda-src/slack_alert/handler.py b/terraform/lambda-src/slack_alert/handler.py index 53fb0c9..f36c305 100644 --- a/terraform/lambda-src/slack_alert/handler.py +++ b/terraform/lambda-src/slack_alert/handler.py @@ -271,6 +271,11 @@ def service_display_name(event_source): # --------------------------------------------------------------------------- # Identity / resource extraction — GitHub OIDC attribution # --------------------------------------------------------------------------- +def _strip_github_ids(repo): + """Turn an immutable-subject owner/repo ('javaBin@95047/x@123') into 'javaBin/x'.""" + return re.sub(r"@\d+", "", repo) + + def parse_identity(detail): """Extract human-readable identity from CloudTrail userIdentity. @@ -283,7 +288,8 @@ def parse_identity(detail): - GitHubSHA: the commit SHA - GitHubRepo: org/repo (e.g. javaBin/moresleep) - GitHubRunID: the workflow run ID - The OIDC sub claim format: repo:javaBin/{name}:ref:refs/heads/{branch} + The OIDC sub claim format: repo:javaBin/{name}:ref:refs/heads/{branch}, or for repos + with immutable subjects repo:javaBin@{org_id}/{name}@{repo_id}:ref:refs/heads/{branch} """ identity = detail.get("userIdentity", {}) arn = identity.get("arn", "unknown") @@ -319,7 +325,7 @@ def parse_identity(detail): r"repo:([^:]+):ref:refs/(?:heads|tags)/(.+)", sub_claim ) if sub_match: - ci_ctx["repo"] = sub_match.group(1) + ci_ctx["repo"] = _strip_github_ids(sub_match.group(1)) ci_ctx["ref"] = sub_match.group(2) # Session tags from OIDC (if present in CloudTrail) @@ -336,7 +342,7 @@ def parse_identity(detail): r"repo:([^:]+):ref:refs/(?:heads|tags)/(.+)", session ) if sub_match: - ci_ctx["repo"] = sub_match.group(1) + ci_ctx["repo"] = _strip_github_ids(sub_match.group(1)) ci_ctx["ref"] = sub_match.group(2) # Parse enriched session name: {actor}-{sha8}-{run_id} diff --git a/terraform/lambda-src/tests/test_slack_alert_identity.py b/terraform/lambda-src/tests/test_slack_alert_identity.py new file mode 100644 index 0000000..166ac59 --- /dev/null +++ b/terraform/lambda-src/tests/test_slack_alert_identity.py @@ -0,0 +1,37 @@ +"""Tests for slack_alert GitHub OIDC attribution — run: python3 -m unittest discover -s terraform/lambda-src/tests""" + +import unittest + +from test_slack_alert_ecs import FakeTable, load + + +def ci_detail(sub): + return { + "userIdentity": { + "type": "AssumedRole", + "arn": "arn:aws:sts::111111111111:assumed-role/javabin-ci-app-broker/someone-deadbeef-42", + "sessionContext": {"webIdFederationData": {"attributes": {"sub": sub}}}, + } + } + + +class CiIdentityTests(unittest.TestCase): + def setUp(self): + self.mod = load(FakeTable()) + + def ci_context(self, sub): + _, is_ci, ctx = self.mod.parse_identity(ci_detail(sub)) + self.assertTrue(is_ci) + return ctx + + def test_legacy_subject(self): + ctx = self.ci_context("repo:javaBin/jz-watch:ref:refs/heads/main") + self.assertEqual((ctx["repo"], ctx["ref"]), ("javaBin/jz-watch", "main")) + + def test_immutable_subject_strips_ids(self): + ctx = self.ci_context("repo:javaBin@95047/2027.javazone.no@1433986972:ref:refs/heads/main") + self.assertEqual((ctx["repo"], ctx["ref"]), ("javaBin/2027.javazone.no", "main")) + + +if __name__ == "__main__": + unittest.main() diff --git a/terraform/platform/iam/main.tf b/terraform/platform/iam/main.tf index 273c313..7ddfd45 100644 --- a/terraform/platform/iam/main.tf +++ b/terraform/platform/iam/main.tf @@ -295,7 +295,10 @@ resource "aws_iam_role" "ci_app_broker" { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" } StringLike = { - "token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/*:*" + "token.actions.githubusercontent.com:sub" = [ + "repo:${var.github_org}/*:*", + "repo:${var.github_org}@${var.github_org_id}/*:*", + ] "token.actions.githubusercontent.com:job_workflow_ref" = [ "${var.github_org}/platform/.github/workflows/tf-plan.yml@refs/heads/main", "${var.github_org}/platform/.github/workflows/docker-build.yml@refs/heads/main", @@ -773,7 +776,10 @@ resource "aws_iam_role" "ci_override_approver" { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" } StringLike = { - "token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/*:*" + "token.actions.githubusercontent.com:sub" = [ + "repo:${var.github_org}/*:*", + "repo:${var.github_org}@${var.github_org_id}/*:*", + ] # Pin to the approve-override workflow on main branch "token.actions.githubusercontent.com:job_workflow_ref" = "${var.github_org}/platform/.github/workflows/approve-override.yml@refs/heads/main" } @@ -892,7 +898,10 @@ resource "aws_iam_role" "ci_apply_gate" { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" } StringLike = { - "token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/*:*" + "token.actions.githubusercontent.com:sub" = [ + "repo:${var.github_org}/*:*", + "repo:${var.github_org}@${var.github_org_id}/*:*", + ] "token.actions.githubusercontent.com:job_workflow_ref" = "${var.github_org}/platform/.github/workflows/tf-apply.yml@refs/heads/main" } } diff --git a/terraform/platform/iam/variables.tf b/terraform/platform/iam/variables.tf index 3e07fa3..0dc2c7c 100644 --- a/terraform/platform/iam/variables.tf +++ b/terraform/platform/iam/variables.tf @@ -25,6 +25,12 @@ variable "github_org" { default = "javaBin" } +variable "github_org_id" { + description = "GitHub organization ID, used in immutable OIDC subjects (repo:@/@:...)" + type = string + default = "95047" +} + variable "apply_gate_role_arn" { description = "ARN of the apply-gate Lambda role (for team role trust policy)" type = string