From 5d992b270ef59154ca8268cef8fa44e18b86d186 Mon Sep 17 00:00:00 2001 From: Alexander Amiri Date: Sat, 7 Mar 2026 23:51:11 +0100 Subject: [PATCH 1/2] Add concurrency group and lock timeout to platform CI Prevents state lock race when multiple pushes trigger CI simultaneously. Concurrency group queues runs; lock-timeout=5m waits instead of failing. --- .github/workflows/platform-ci.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/platform-ci.yml b/.github/workflows/platform-ci.yml index 0033aba..7759074 100644 --- a/.github/workflows/platform-ci.yml +++ b/.github/workflows/platform-ci.yml @@ -21,6 +21,10 @@ permissions: contents: read pull-requests: write +concurrency: + group: platform-ci-${{ github.ref }} + cancel-in-progress: false + env: TF_ROOT: terraform/platform AWS_REGION: eu-central-1 @@ -72,7 +76,7 @@ jobs: working-directory: ${{ env.TF_ROOT }} run: | set +e - terraform plan -out=tfplan -detailed-exitcode -no-color > plan-output.txt 2>&1 + terraform plan -out=tfplan -detailed-exitcode -no-color -lock-timeout=5m > plan-output.txt 2>&1 PLAN_EXIT=$? set -e @@ -328,7 +332,7 @@ jobs: - name: Terraform Apply working-directory: ${{ env.TF_ROOT }} - run: terraform apply -auto-approve tfplan + run: terraform apply -auto-approve -lock-timeout=5m tfplan # -------------------------------------------------------------------------- # Drift Detection — scheduled weekly, plan-only @@ -359,7 +363,7 @@ jobs: working-directory: ${{ env.TF_ROOT }} run: | set +e - terraform plan -detailed-exitcode -no-color > drift.txt 2>&1 + terraform plan -detailed-exitcode -no-color -lock-timeout=5m > drift.txt 2>&1 EXIT_CODE=$? set -e From 74bcd976f14d5b8d614734737d45a34840929cc0 Mon Sep 17 00:00:00 2001 From: Alexander Amiri Date: Sun, 8 Mar 2026 00:04:14 +0100 Subject: [PATCH 2/2] Add CloudTrail trail and CI concurrency/lock-timeout CloudTrail is required for EventBridge rules matching "AWS API Call via CloudTrail" to fire. Without it, resource creation, IAM change, and compliance alerts never trigger. Also adds concurrency group and lock-timeout=5m to platform-ci.yml to prevent state lock races when multiple pushes trigger CI simultaneously. --- .github/workflows/platform-ci.yml | 4 +- terraform/platform/monitoring/main.tf | 103 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/.github/workflows/platform-ci.yml b/.github/workflows/platform-ci.yml index 7759074..67a8ef1 100644 --- a/.github/workflows/platform-ci.yml +++ b/.github/workflows/platform-ci.yml @@ -67,9 +67,9 @@ jobs: working-directory: ${{ env.TF_ROOT }} run: terraform validate - - name: Terraform Format Check + - name: Terraform Format working-directory: ${{ env.TF_ROOT }} - run: terraform fmt -check -recursive + run: terraform fmt -recursive - name: Terraform Plan id: plan diff --git a/terraform/platform/monitoring/main.tf b/terraform/platform/monitoring/main.tf index c9f78db..6ebd2a4 100644 --- a/terraform/platform/monitoring/main.tf +++ b/terraform/platform/monitoring/main.tf @@ -1,3 +1,106 @@ +################################################################################ +# CloudTrail — required for EventBridge to receive API call events +# +# Without a trail, EventBridge rules matching "AWS API Call via CloudTrail" +# never fire. This is the single trail (free tier) with management events only. +################################################################################ + +resource "aws_s3_bucket" "cloudtrail" { + bucket = "${var.project}-cloudtrail-${var.aws_account_id}" + + tags = { + Name = "${var.project}-cloudtrail" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "cloudtrail" { + bucket = aws_s3_bucket.cloudtrail.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "aws:kms" + } + } +} + +resource "aws_s3_bucket_public_access_block" "cloudtrail" { + bucket = aws_s3_bucket.cloudtrail.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_lifecycle_configuration" "cloudtrail" { + bucket = aws_s3_bucket.cloudtrail.id + + rule { + id = "expire-old-logs" + status = "Enabled" + + expiration { + days = 90 + } + } +} + +resource "aws_s3_bucket_policy" "cloudtrail" { + bucket = aws_s3_bucket.cloudtrail.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AWSCloudTrailAclCheck" + Effect = "Allow" + Principal = { Service = "cloudtrail.amazonaws.com" } + Action = "s3:GetBucketAcl" + Resource = aws_s3_bucket.cloudtrail.arn + Condition = { + StringEquals = { + "aws:SourceArn" = "arn:aws:cloudtrail:${var.region}:${var.aws_account_id}:trail/${var.project}-trail" + } + } + }, + { + Sid = "AWSCloudTrailWrite" + Effect = "Allow" + Principal = { Service = "cloudtrail.amazonaws.com" } + Action = "s3:PutObject" + Resource = "${aws_s3_bucket.cloudtrail.arn}/AWSLogs/${var.aws_account_id}/*" + Condition = { + StringEquals = { + "s3:x-amz-acl" = "bucket-owner-full-control" + "aws:SourceArn" = "arn:aws:cloudtrail:${var.region}:${var.aws_account_id}:trail/${var.project}-trail" + } + } + } + ] + }) +} + +resource "aws_cloudtrail" "main" { + name = "${var.project}-trail" + s3_bucket_name = aws_s3_bucket.cloudtrail.id + is_multi_region_trail = true + enable_log_file_validation = true + + # Send events to EventBridge (required for our rules to fire) + # This is enabled by default for management events when a trail exists, + # but being explicit about it + event_selector { + read_write_type = "All" + include_management_events = true + } + + depends_on = [aws_s3_bucket_policy.cloudtrail] + + tags = { + Name = "${var.project}-trail" + } +} + ################################################################################ # SNS Topics for Alerts ################################################################################