makeplane / makeplane/developer-docs

Docs: Kubernetes self-host guide should document bucket CORS for external S3 backends

Aperta Adatta ai principianti
#269 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Lingua principale
JavaScript
Stelle
16
Fork
23
Merge medio
46m
PR unite (30g)
1

Descrizione

Summary

The Kubernetes self-host docs document the env vars needed to point Plane at an external S3-compatible backend (AWS_S3_ENDPOINT_URL, aws_access_key, etc.) but don't mention that the operator must also configure bucket-level CORS on the target bucket allowing the Plane web UI's origin. Without it, browser-side attachment uploads fail with net::ERR_FAILED (CORS preflight blocked) even though all server-side configuration is correct.

This silently bites every external-S3 deployment because the bundled-MinIO path "just works" for an unrelated reason (see below), so there is no signal to the operator that bucket CORS is part of the contract.

Verification

I traced the upload flow end-to-end before filing:

  • API (apps/api/plane/app/views/issue/attachment.py:136, mirrored in app/views/asset/v2.py and space/views/asset.py) calls S3Storage.generate_presigned_post(...) and returns {upload_data: {url, fields}, ...} to the client. The url points at AWS_S3_ENDPOINT_URL, not the Plane API.
  • Frontend (apps/web/core/services/issue/issue_attachment.service.ts:50-67, with the same pattern in core/services/file.service.ts and packages/services/src/file/sites-file.service.ts) calls fileUploadService.uploadFile(signedURLResponse.upload_data.url, FormData) — a direct browser POST to the S3 endpoint, with FormData built from upload_data.fields in packages/services/src/file/helper.ts:56-62. This is cross-origin whenever the endpoint differs from the web UI host.
  • Preflight repro against an external S3 backend (Versity Gateway, with no permissive server default) and the bucket freshly created without CORS rules:
    $ curl -X OPTIONS -i \
        -H "Origin: https://plane.example.com" \
        -H "Access-Control-Request-Method: POST" \
        -H "Access-Control-Request-Headers: content-type" \
        http://<endpoint>/uploads
    HTTP/1.1 403 Forbidden
    <Error><Code>AccessForbidden</Code><Message>CORSResponse: CORS is not enabled for this bucket.</Message>...
    
    After applying the CORS policy proposed below via put-bucket-cors, the same preflight returns 200 OK with the expected Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Expose-Headers: ETag, etc. — the browser then proceeds with the upload.

Reproduction

  1. Deploy plane-enterprise (tested 2.3.2, app v2.5.3) or plane-ce with:
    services:
      minio:
        local_setup: false
    external_secrets:
      doc_store_existingSecret: plane-doc-store   # provides AWS_* envs
    
    Backend can be any S3-compatible service hosted on a different origin from the Plane web UI (Versity Gateway, MinIO standalone, AWS S3, Garage, Backblaze B2, etc.).
  2. Set AWS_S3_ENDPOINT_URL to a publicly-reachable URL (browser must resolve it; in-cluster Service URLs fail).
  3. Open the Plane web UI on a public hostname and try to attach a file to an issue.
Actual
api.service-*.js: POST https://<your-s3-endpoint>/<bucket>  net::ERR_FAILED
store-context-*.js: Error in uploading issue attachment: undefined

The OPTIONS preflight is blocked because the S3 endpoint returns no Access-Control-Allow-Origin matching the request origin.

Expected

Upload completes — matching the bundled-MinIO behaviour or, at minimum, the docs warn that bucket CORS configuration is required and provide a reference policy.

Why bundled-MinIO masks this

Plane's frontend uses S3 POST policy uploads — the API generates a presigned form, the browser submits the file directly to the S3 endpoint. When that endpoint is on a different origin than the Plane web UI, the browser issues a CORS preflight, which the bucket must answer.

The bundled-MinIO path avoids the problem by being same-origin, not by configuring CORS:

  • helm-charts: charts/plane-enterprise/templates/ingress.yaml (and the plane-ce equivalent) routes the bucket path under the same appHost ingress when services.minio.local_setup: true and env.docstore_bucket is set:
    - backend:
        service:
          port: { number: 9000 }
          name: {{ .Release.Name }}-minio
      path: /{{ .Values.env.docstore_bucket }}
      pathType: Prefix
    
    The browser uploads to https://<appHost>/<bucket>, same origin as the UI — no preflight is issued.
  • The chart's bucket-init Job (templates/workloads/minio.stateful.yaml) only runs mc config host add, mc mb, and mc anonymous set download. It does not apply a CORS policy.

When services.minio.local_setup: false the chart can't route the bucket through appHost (the backend isn't in-cluster), so the upload always becomes cross-origin — and the operator must configure bucket CORS themselves.

(Note: operators who keep local_setup: true but set ingress.minioHost to a separate subdomain are also cross-origin and would hit a similar problem; bundled MinIO papers over it because the MinIO server itself returns permissive defaults. Out of scope for this issue but worth a follow-up.)

Some S3 gateways/proxies will also mask this by returning a permissive Access-Control-Allow-Origin server-side regardless of bucket configuration (e.g. versitygw's --cors-allow-origin "*", MinIO standalone's defaults). The docs should still recommend explicit bucket CORS — it's the portable fix and what AWS S3 itself requires.

Proposed fix (docs)

Add a "Bucket CORS configuration" subsection to the S3-Compatible Object Storage (Non-MinIO) block in both:

  • docs/self-hosting/methods/kubernetes.md
  • docs/self-hosting/methods/install-methods-commercial/kubernetes.md

Suggested copy:

Bucket CORS configuration (required for browser uploads)

Plane's web UI uploads files directly to your S3 endpoint via presigned POST. When the S3 endpoint is on a different hostname from the Plane web UI, the browser's same-origin policy requires you to configure CORS on the bucket. The bundled MinIO setup avoids this by routing the bucket through the same ingress as the web UI; external backends are always cross-origin and need explicit CORS configuration.

Apply the following CORS configuration to your uploads bucket, replacing https://plane.example.com with your Plane web URL:

{
  "CORSRules": [
    {
      "AllowedOrigins": ["https://plane.example.com"],
      "AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 3000
    }
  ]
}
aws --endpoint-url https://your-s3-endpoint s3api put-bucket-cors \
  --bucket uploads \
  --cors-configuration file://cors.json

Optionally, mention env.use_storage_proxy: true as an alternative for downloads only — it routes GETs through the Plane API, avoiding cross-origin downloads — with the explicit caveat that it does not affect uploads, so bucket CORS is still required.

Workaround in the meantime

cat > cors.json <<'EOF2'
{
  "CORSRules": [
    {
      "AllowedOrigins": ["https://plane.example.com"],
      "AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 3000
    }
  ]
}
EOF2

aws --endpoint-url https://your-s3-endpoint s3api put-bucket-cors \
  --bucket uploads \
  --cors-configuration file://cors.json

Environment

  • Plane chart: plane-enterprise 2.3.2 (also reproducible on plane-ce)
  • Plane app: v2.5.3
  • Object store: Versity Gateway (S3-compatible); reproducible against any external S3 backend
  • Browser: Chrome 147, Firefox 132 (any modern browser enforcing CORS)

A docs PR adding the section above is on the way.


Investigation assisted by Claude (Anthropic).

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Leggi le sezioni S3-Compatible Object Storage (Non-MinIO) in docs/self-hosting/methods/kubernetes.md e docs/self-hosting/methods/install-methods-commercial/kubernetes.md. Aggiungi a entrambi i file la sottosezione CORS del bucket, l’esempio JSON e il comando aws s3api descritti nell’issue. Il lavoro è completo quando gli operatori S3 esterni vengono avvisati dei caricamenti dal browser e viene mostrato loro come configurare CORS.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
aws, kubernetes
Ambito
cloud, documentation
Tipo di issue
Documentazione
Difficoltà
2/5
Tempo stimato
1-3 ore
Stato di attività
Tranquilla
Chiarezza
Specificata chiaramente
Idoneità per principianti
78/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.