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 updatelatest/, 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:ListBucketwiths3:prefixcondition(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.