feat: public CDN for MinIO assets bucket #132

Merged
forgejo_admin merged 1 commit from 126-minio-public-cdn into main 2026-03-21 17:14:50 +00:00

Summary

  • Add Tailscale funnel annotation to MinIO API ingress for public internet access
  • Add public-read bucket policy for assets bucket (anonymous GET only)
  • Other buckets (postgres_wal, tf_state_backups) remain private (default deny)

Changes

  • terraform/main.tf: Added tailscale.com/funnel = "true" annotation to kubernetes_ingress_v1.minio_api_funnel ingress resource
  • terraform/main.tf: Added minio_s3_bucket_policy.assets_public_read resource — allows anonymous s3:GetObject on arn:aws:s3:::assets/*

Test Plan

  • tofu fmt && tofu validate passes
  • tofu plan -lock=false shows 2 to add, 1 to change, 0 to destroy
  • tofu apply clean — 2 added, 1 changed
  • Local curl to assets/westside/jerseys/IMG_4164.jpeg returns 200
  • Local curl to postgres_wal/ returns 400 (denied)
  • WebFetch from public internet returns 200 + 242KB JPEG
  • kubectl get ingress -n minio minio-api-funnel confirms annotation {"tailscale.com/funnel":"true"}
  • No regressions in other MinIO buckets or funnel ingresses

Review Checklist

  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • tofu fmt applied
  • tofu validate passes
  • Closes #126
  • plan-wkq — Phase 11 (Girls Tryout) blocker: email images not rendering in Gmail
## Summary - Add Tailscale funnel annotation to MinIO API ingress for public internet access - Add public-read bucket policy for `assets` bucket (anonymous GET only) - Other buckets (`postgres_wal`, `tf_state_backups`) remain private (default deny) ## Changes - `terraform/main.tf`: Added `tailscale.com/funnel = "true"` annotation to `kubernetes_ingress_v1.minio_api_funnel` ingress resource - `terraform/main.tf`: Added `minio_s3_bucket_policy.assets_public_read` resource — allows anonymous `s3:GetObject` on `arn:aws:s3:::assets/*` ## Test Plan - [x] `tofu fmt && tofu validate` passes - [x] `tofu plan -lock=false` shows 2 to add, 1 to change, 0 to destroy - [x] `tofu apply` clean — 2 added, 1 changed - [x] Local curl to `assets/westside/jerseys/IMG_4164.jpeg` returns 200 - [x] Local curl to `postgres_wal/` returns 400 (denied) - [x] WebFetch from public internet returns 200 + 242KB JPEG - [x] `kubectl get ingress -n minio minio-api-funnel` confirms annotation `{"tailscale.com/funnel":"true"}` - [ ] No regressions in other MinIO buckets or funnel ingresses ## Review Checklist - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive - [x] `tofu fmt` applied - [x] `tofu validate` passes ## Related - Closes #126 - `plan-wkq` — Phase 11 (Girls Tryout) blocker: email images not rendering in Gmail
feat: public CDN for MinIO assets bucket (#126)
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline failed
027c239bd3
Enable public internet access to the assets bucket so email images
render in Gmail. Adds Tailscale funnel annotation to the MinIO API
ingress and a public-read bucket policy for the assets bucket only.
Other buckets (postgres_wal, tf_state_backups) remain private.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

PR #132 Review

DOMAIN REVIEW

Tech stack: OpenTofu / Kubernetes / MinIO S3 / Tailscale funnel ingress

This PR makes two changes to terraform/main.tf:

  1. Funnel annotation on minio_api_funnel ingress -- adds tailscale.com/funnel = "true" to expose the MinIO S3 API (port 9000) to the public internet. This follows the exact same pattern used by every other funnel ingress in this file (grafana, alertmanager, forgejo, woodpecker, harbor, minio console, keycloak -- all at lines 543, 574, 671, 801, 978, 1076, 2067).

  2. Public-read bucket policy for assets bucket -- grants anonymous s3:GetObject on arn:aws:s3:::assets/*. This is a standard S3 CDN pattern.

Security analysis:

  • The policy grants only s3:GetObject (read-only). No s3:ListBucket (directory listing not possible), no s3:PutObject or s3:DeleteObject (no anonymous writes).
  • The Principal: { AWS: ["*"] } scope is intentional for a public CDN use case.
  • Other buckets (postgres-wal, tf-state-backups) use IAM user policies with specific principals and are unaffected by this change.
  • MinIO admin API endpoints on port 9000 require root credentials -- anonymous requests can only exercise bucket-policy-permitted operations.
  • The MinIO console (port 9001, minio_funnel) was already public-funneled with root password auth. Pre-existing, not in scope.

Terraform quality:

  • jsonencode() for the policy block matches the existing pattern used for CNPG and TF backup IAM policies (lines 1418, 2143).
  • Resource naming (assets_public_read) is clear and descriptive.
  • No hardcoded values -- bucket name references minio_s3_bucket.assets.bucket.
  • Comment on line 1100 updated to reflect the new intent ("public funnel for CDN image access").

BLOCKERS

None.

This is a Terraform-only infrastructure change (no application code, no tests to write). The bucket policy is correctly scoped. No secrets committed. No unvalidated user input.

NITS

  1. Minor: The bucket policy does not include a Sid field in the Statement. While optional in S3 policy syntax, adding Sid = "PublicReadAssets" would make the policy self-documenting when inspected via mc admin policy or the MinIO console. Non-blocking.

  2. Minor: The assets bucket has force_destroy = true (line 1135). This is pre-existing and not introduced by this PR, but worth noting -- a tofu destroy or bucket resource recreation would delete all CDN assets. Consider whether this is still appropriate now that the bucket serves as a public CDN backing store. Non-blocking, discovered scope.

SOP COMPLIANCE

  • Branch named after issue (126-minio-public-cdn references #126)
  • PR body has Summary, Changes, Test Plan, Related sections
  • Related references plan slug (plan-wkq)
  • No secrets committed
  • No unnecessary file changes (1 file, +17/-1, all on-topic)
  • Commit messages are descriptive
  • tofu fmt applied per test plan
  • tofu validate passes per test plan

PROCESS OBSERVATIONS

  • Deployment frequency: Clean apply already completed (2 added, 1 changed per test plan). This is a low-risk infra change with immediate value -- unblocks email image rendering for the Girls Tryout phase.
  • Change failure risk: Low. The change is additive (new annotation, new resource). No resources destroyed. The policy is narrowly scoped.
  • Test plan quality: Thorough -- includes both positive (curl returns 200 + correct size) and negative (postgres_wal returns 400) verification, plus public internet validation via WebFetch. The one unchecked item ("No regressions in other MinIO buckets or funnel ingresses") is an honest gap acknowledgment.

VERDICT: APPROVED

## PR #132 Review ### DOMAIN REVIEW **Tech stack**: OpenTofu / Kubernetes / MinIO S3 / Tailscale funnel ingress This PR makes two changes to `terraform/main.tf`: 1. **Funnel annotation on `minio_api_funnel` ingress** -- adds `tailscale.com/funnel = "true"` to expose the MinIO S3 API (port 9000) to the public internet. This follows the exact same pattern used by every other funnel ingress in this file (grafana, alertmanager, forgejo, woodpecker, harbor, minio console, keycloak -- all at lines 543, 574, 671, 801, 978, 1076, 2067). 2. **Public-read bucket policy for `assets` bucket** -- grants anonymous `s3:GetObject` on `arn:aws:s3:::assets/*`. This is a standard S3 CDN pattern. **Security analysis**: - The policy grants only `s3:GetObject` (read-only). No `s3:ListBucket` (directory listing not possible), no `s3:PutObject` or `s3:DeleteObject` (no anonymous writes). - The `Principal: { AWS: ["*"] }` scope is intentional for a public CDN use case. - Other buckets (`postgres-wal`, `tf-state-backups`) use IAM user policies with specific principals and are unaffected by this change. - MinIO admin API endpoints on port 9000 require root credentials -- anonymous requests can only exercise bucket-policy-permitted operations. - The MinIO console (port 9001, `minio_funnel`) was already public-funneled with root password auth. Pre-existing, not in scope. **Terraform quality**: - `jsonencode()` for the policy block matches the existing pattern used for CNPG and TF backup IAM policies (lines 1418, 2143). - Resource naming (`assets_public_read`) is clear and descriptive. - No hardcoded values -- bucket name references `minio_s3_bucket.assets.bucket`. - Comment on line 1100 updated to reflect the new intent ("public funnel for CDN image access"). ### BLOCKERS None. This is a Terraform-only infrastructure change (no application code, no tests to write). The bucket policy is correctly scoped. No secrets committed. No unvalidated user input. ### NITS 1. **Minor**: The bucket policy does not include a `Sid` field in the Statement. While optional in S3 policy syntax, adding `Sid = "PublicReadAssets"` would make the policy self-documenting when inspected via `mc admin policy` or the MinIO console. Non-blocking. 2. **Minor**: The `assets` bucket has `force_destroy = true` (line 1135). This is pre-existing and not introduced by this PR, but worth noting -- a `tofu destroy` or bucket resource recreation would delete all CDN assets. Consider whether this is still appropriate now that the bucket serves as a public CDN backing store. Non-blocking, discovered scope. ### SOP COMPLIANCE - [x] Branch named after issue (`126-minio-public-cdn` references #126) - [x] PR body has Summary, Changes, Test Plan, Related sections - [x] Related references plan slug (`plan-wkq`) - [x] No secrets committed - [x] No unnecessary file changes (1 file, +17/-1, all on-topic) - [x] Commit messages are descriptive - [x] `tofu fmt` applied per test plan - [x] `tofu validate` passes per test plan ### PROCESS OBSERVATIONS - **Deployment frequency**: Clean apply already completed (2 added, 1 changed per test plan). This is a low-risk infra change with immediate value -- unblocks email image rendering for the Girls Tryout phase. - **Change failure risk**: Low. The change is additive (new annotation, new resource). No resources destroyed. The policy is narrowly scoped. - **Test plan quality**: Thorough -- includes both positive (curl returns 200 + correct size) and negative (postgres_wal returns 400) verification, plus public internet validation via WebFetch. The one unchecked item ("No regressions in other MinIO buckets or funnel ingresses") is an honest gap acknowledgment. ### VERDICT: APPROVED
forgejo_admin deleted branch 126-minio-public-cdn 2026-03-21 17:14:50 +00:00
Sign in to join this conversation.
No description provided.