Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .github/workflows/platform-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -63,16 +67,16 @@ 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
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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
104 changes: 104 additions & 0 deletions terraform/org/cloudtrail.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
################################################################################
# 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.
#
# Human-applied alongside Identity Center and Organizations because:
# - CI role has explicit deny on cloudtrail:* in the permission boundary
# - Audit trail should not be modifiable by automated pipelines
################################################################################

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"
filter {}

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

event_selector {
read_write_type = "All"
include_management_events = true
}

depends_on = [aws_s3_bucket_policy.cloudtrail]

tags = {
Name = "${var.project}-trail"
}
}