Skip to content

IAM Examples

Least-privilege IAM policies for the publisher.

1. Core Minimum (Basic Publish)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListReportsPrefix",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::YOUR_BUCKET",
      "Condition": {
        "StringLike": {
          "s3:prefix": [
            "<PREFIX>/*"
          ]
        }
      }
    },
    {
      "Sid": "RWReports",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::YOUR_BUCKET/<PREFIX>/*"
    }
  ]
}

Note: DeleteObject is required for the two‑phase “latest” promotion (cleaning prior latest/). If you truly never update latest/, you may omit it, but this project defaults to the race‑safe promotion which needs delete.

2. Add Retention & Tagging

Needed for --max-keep-runs and --ttl-days:

{
  "Sid": "RetentionAndTagging",
  "Effect": "Allow",
  "Action": [
    "s3:DeleteObject",
    "s3:PutObjectTagging",
    "s3:GetObjectTagging"
  ],
  "Resource": "arn:aws:s3:::YOUR_BUCKET/<PREFIX>/*"
}

3. KMS (Only If Bucket Uses SSE-KMS)

{
  "Sid": "AllowKmsForReports",
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey",
    "kms:ReEncrypt*",
    "kms:DescribeKey"
  ],
  "Resource": "arn:aws:kms:REGION:ACCOUNT_ID:key/KMS_KEY_ID"
}

4. Optional Hardening

Deny object ACL changes:

{
  "Sid": "DenyObjectAclChanges",
  "Effect": "Deny",
  "Action": ["s3:PutObjectAcl"],
  "Resource": "arn:aws:s3:::YOUR_BUCKET/<PREFIX>/*"
}

5. Summary Matrix

Feature Actions Needed
Basic publish (incl. latest promotion) ListBucket (scoped), GetObject, PutObject, DeleteObject
History pull + GetObject on <PREFIX>/<PROJECT>/<BRANCH>/latest/history/*
Retention pruning + DeleteObject
TTL tagging + PutObjectTagging, GetObjectTagging
SSE-KMS encryption + kms: minimal set (Encrypt/Decrypt/GenerateDataKey/DescribeKey)

6. CloudFront Bucket Policy Snippet

(Attach to the bucket; replace placeholders. This is often created automatically by the console wizard—verify AWS:SourceArn matches your current distribution.)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontRead",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::YOUR_BUCKET/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
        }
      }
    }
  ]
}

7. Best Practices

  • Keep bucket private (Block Public Access ON)
  • Use OAC not legacy Origin Access Identity
  • Scope s3:ListBucket with s3:prefix condition(s)
  • Separate viewer vs publisher roles if needed
  • Rotate IAM access keys or prefer role assumption (OIDC in CI)

8. Extended Security Model

Area Control Implemented By Notes
Origin Access CloudFront OAC + bucket policy SourceArn restriction Your IaC (Terraform/CDK/CLI) Prevents direct public exposure & legacy OAI usage
Encryption Optional CMK + SSE-KMS headers Your bucket/KMS policy + CLI (--sse, --sse-kms-key-id) Add bucket policy Deny block to enforce after validation
Atomic Pointer Two‑phase latest_tmp/latest/ swap Publisher logic Eliminates partial latest during uploads or failures
History Integrity Pre-fetch of latest/history/ before allure generate Publisher Preserves trend continuity between runs
Caching Immutable assets vs index.html no‑cache Upload headers Ensures fast loads yet immediate UI updates
Least Privilege Scoped s3:ListBucket, object CRUD, tagging, optional KMS Publisher role policy No bucket-level destructive permissions
Federation GitHub Actions OIDC trust policy (subject + audience) AWS OIDC (role trust policy) Removes need for static long-lived keys
Observability Manifest + latest.json + trend sheet Publisher uploads Enables dashboards or external automation to discover runs
Retention --max-keep-runs pruning + TTL tagging Publisher features Pair TTL tags with lifecycle rules for cost control

Threat → Mitigation recap (concise):

Threat Mitigation
Leaked static keys OIDC federation / no static keys stored
Public bucket misconfiguration Block Public Access + no website hosting + OAC-only policy
Race producing broken latest Two-phase staging to latest_tmp/ then copy + marker
Stale UI after deploy index.html served with no-cache forcing revalidation
Unauthorized KMS usage CMK policy grants only encrypt/decrypt to publisher role
Excess object sprawl Retention pruning + TTL tags -> lifecycle expiration

Return to the main setup guide: AWS Setup.