Allure Hosting Architecture on AWS¶
This document describes a secure, reusable way to host Allure reports on AWS without public S3 website hosting. It also outlines a pytest plugin flow that generates the static Allure site and publishes it to a private S3 bucket behind CloudFront.
High level¶
%%{init: {'theme': 'neutral'}}%%
flowchart LR
subgraph UserLayer["User Layer"]
User[User]
end
subgraph CDNLayer["CDN Layer - optional"]
CF[CloudFront\nHTTPS and caching]
WAF[WAF - allow list or auth]
end
subgraph StorageLayer["Storage Layer"]
S3[S3 Bucket\nPrivate objects]
end
User -->|view report URL| CF
CF -->|signed request via OAC| S3
WAF -. policy .- CF
Why not S3 website hosting: website endpoints are public only and do not use IAM. Privacy is achieved by keeping S3 private and letting CloudFront read via Origin Access Control. Viewer access is controlled at the CDN edge using IP allow list, signed cookies, or SSO.
Components¶
- S3 bucket: private, versioning on, lifecycle rules. Folder layout:
s3://<BUCKET>/<PREFIX>/<project>/<branch>/<run_id>/s3://<BUCKET>/<PREFIX>/<project>/<branch>/latest/- CloudFront: origin is the S3 bucket (not the website endpoint). Default root object
index.html. Error responses map403and404to/index.htmlso Allure widget routes resolve. - Origin Access Control: grants CloudFront read access to the bucket. Bucket policy restricts
s3:GetObjectto the CloudFront distribution ARN. - Auth options at the edge:
- WAF IP allow list for company egress ranges
- Signed URLs or cookies for link scoped access
- SSO via Cognito or federated IdP with Lambda at Edge
- CI runner: Jenkins, GitHub Actions, or similar invokes tests and uploads.
- pytest plugin: builds the static site and publishes it with proper cache headers.
Request and publish flow with pytest plugin¶
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
autonumber
participant Dev as Developer
participant CI as CI runner
participant PL as Pytest plugin
participant AL as Allure CLI
participant S3 as S3 private bucket
participant CF as CloudFront
participant U as User
Dev->>CI: push code and tests
CI->>PL: run pytest with allure results
PL->>AL: generate static site into allure-report
Note right of AL: assets css js icons\nindex html and widgets
PL->>S3: upload <PREFIX>/<project>/<branch>/<run_id>/ with immutable cache
PL->>S3: upload index.html with no cache
PL->>S3: copy to <PREFIX>/<project>/<branch>/latest/
U->>CF: open report URL
CF->>S3: fetch objects via OAC
CF-->>U: serve cached content
The pytest plugin:
- Reads options such as bucket, base prefix, project, branch, run id source, CloudFront domain.
- Downloads
latest/historyto enrich the nextallure-results. - Calls
allure generate ... --clean -o allure-report. - Uploads to
<PREFIX>/<project>/<branch>/<run_id>/and tolatest/with cache headers. - Writes a small manifest
runs/index.jsonfor navigation. - Prints final URLs in the test summary.
Caching strategy¶
- All files upload with
Cache-Control: public, max-age=31536000, immutable. - Override
index.htmlwithCache-Control: no-cacheto allow quick refresh of the landing page. - Optional: set
no-cachefor thewidgetsfolder to refresh summary data faster.
URL scheme¶
- Latest per branch:
https://<cloudfront-domain>/<PREFIX>/<project>/<branch>/latest/ - Immutable run:
https://<cloudfront-domain>/<PREFIX>/<project>/<branch>/<run_id>/
Security model¶
- S3 stays private, block public access on.
- CloudFront reads with Origin Access Control (OAC). Bucket policy restricts
s3:GetObjectto CloudFront distribution ARN. - Viewer controls at the CDN:
- WAF allow list for corporate IPs
- Signed cookies for time-bound sharing
- SSO at the edge
Example bucket policy snippet:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontReadOnly",
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::<BUCKET>/<PREFIX>/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<ACCOUNT_ID>:distribution/<DISTRIBUTION_ID>"
}
}
}
]
}
Quickstart (secondary AWS profile)¶
aws configure --profile allure-test
export AWS_PROFILE=allure-test
export AWS_REGION=eu-west-1
# Produce results (example)
pytest -q --alluredir=allure-results
# Dry run with validation
publish-allure \
--bucket <BUCKET> \
--prefix <PREFIX> \
--project demo \
--branch main \
--cloudfront https://<cf-domain> \
--check --dry-run
# Real publish
publish-allure \
--bucket <BUCKET> \
--prefix <PREFIX> \
--project demo \
--branch main \
--cloudfront https://<cf-domain> \
--ttl-days 30 \
--max-keep-runs 5
Notes on CI usage¶
Detailed CI pipeline examples such as Jenkins or GitHub Actions belong in docs/ci.md.
See CI examples for Jenkins and GitHub Actions usage.
CloudFront configuration (SPA routing)¶
Set the distribution with:
- Origin: S3 bucket (NOT the website endpoint)
- Default root object:
index.html - Custom error responses (to support Allure's client-side routes):
- 403 -> Response page path
/index.html, HTTP response code200 - 404 -> Response page path
/index.html, HTTP response code200 - Set Error Caching Minimum TTL to
0to avoid stale SPA shell during rollouts
Recommended distribution tunings:
- Allowed methods: GET, HEAD (others unnecessary)
- Enable compression: GZIP + Brotli (saves 60–80% on JS/CSS)
- Cache policy: long-lived for immutable assets, but
index.htmlshould have no caching (already controlled by upload headers—no invalidations typically required) - Invalidation generally NOT required because new runs use unique prefixes; only invalidate if you change shared root assets unexpectedly. Example (rare):
aws cloudfront create-invalidation --distribution-id DXXXXXXXX --paths "/<PREFIX>/demo/main/latest/index.html"
S3 security checklist¶
| Item | State |
|---|---|
| Block Public Access (all 4 settings) | ENABLED |
| Bucket ACLs | Not used (bucket-owner enforced) |
| Public bucket policy | None (no Principal: *) |
| S3 Static website hosting | DISABLED |
| Access path | CloudFront OAC only |
Least privilege IAM (writer role)¶
Attach to the CI role that publishes reports. Replace placeholders.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBranchPrefix",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::<BUCKET>",
"Condition": {
"StringLike": { "s3:prefix": ["<PREFIX>/<PROJECT>/<BRANCH>/*"] }
}
},
{
"Sid": "RWReports",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:PutObjectTagging",
"s3:CopyObject"
],
"Resource": "arn:aws:s3:::<BUCKET>/<PREFIX>/<PROJECT>/<BRANCH>/*"
}
]
}
Optional additions if you enable encryption with a KMS key: add kms:Encrypt, kms:Decrypt, kms:GenerateDataKey* on that key.
Two-phase latest promotion (race-safe)¶
When multiple pipelines might publish concurrently, avoid partially updated latest/ contents.
- Upload run to immutable prefix:
<project>/<branch>/<run_id>/... - Sync report to a temporary prefix:
<project>/<branch>/latest_tmp/ - Write readiness marker:
latest_tmp/LATEST_READY - Delete existing
latest/(best effort) - Copy / sync
latest_tmp/->latest/ - Delete
latest_tmp/
Consumers (if they need strict consistency) can optionally poll for the absence of latest_tmp/ or presence of latest/LATEST_READY (if you copy the marker) before reading.
Implementation note The two-phase
latest_tmp → latestsynchronization is fully implemented in thepublish-allureCLI. It performs copy, readiness marker creation, index verification, and cleanup to ensure atomic promotion even under concurrent runs. Trend and history assets are revalidated withCache-Control: no-cacheto ensure freshness across CDN and browsers.
Cache headers summary¶
| Path / Pattern | Cache-Control |
|---|---|
**/* (default) |
public, max-age=31536000, immutable |
index.html |
no-cache |
widgets/** (optional) |
no-cache |
Alternative without CDN¶
If CloudFront is not allowed, serve the static site behind an internal ALB:
%%{init: {'theme': 'neutral'}}%%
flowchart LR
subgraph VPC[Private network]
ALB[Internal ALB]
ECS[ECS Fargate task\nNginx read only]
S3[S3 Bucket\nVPC endpoint]
end
User[User over VPN] --> ALB
ALB --> ECS
ECS --> S3
- Nginx container serves files from a mounted sync dir. A small sidecar pulls from S3 on demand or on a schedule. Access is VPN only.
Gotchas¶
- S3 website endpoints are public only. Use CloudFront or an internal server.
- SPA routing for Allure needs 403 and 404 to map to
index.htmlat the CDN. - Make sure
Content-Typeis set by the client or inferred correctly by S3. - Large reports benefit from gzip or brotli at the CDN.
- Region mismatch (HTTP 301 redirect from S3) can break history pull — verify bucket region with
aws s3api get-bucket-location.
Future ideas¶
- Index page listing latest runs per project and branch from the manifest files.
- Slack or Teams notification with direct links after CI publish.
- Signed link generator for time bound external sharing.