From fae7e606ffb9abb53626d8b515c1a753946d50e9 Mon Sep 17 00:00:00 2001 From: WhiteRow33 Date: Tue, 26 May 2026 23:16:56 +0300 Subject: [PATCH] Fix Plane S3 upload redirects --- .github/workflows/terraform-prod.yml | 6 +++++- README.md | 21 ++++++++++++++++++--- helm/plane/values/dev.yaml | 2 +- terraform/environments/prod/main.tf | 12 ++++++++++++ terraform/environments/prod/variables.tf | 5 +++++ 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.github/workflows/terraform-prod.yml b/.github/workflows/terraform-prod.yml index 0aa806f..036da62 100644 --- a/.github/workflows/terraform-prod.yml +++ b/.github/workflows/terraform-prod.yml @@ -452,14 +452,18 @@ jobs: echo "runtime_values_file=/tmp/plane-runtime-values.yaml" >> "$GITHUB_OUTPUT" - name: Helm Upgrade/Install + env: + PLANE_S3_ENDPOINT_URL: ${{ secrets.PLANE_S3_ENDPOINT_URL }} run: | + S3_ENDPOINT_URL="${PLANE_S3_ENDPOINT_URL:-https://s3.${{ env.AWS_REGION }}.amazonaws.com}" + helm upgrade --install "${{ env.HELM_RELEASE_NAME }}" "${{ env.HELM_CHART_DIR }}" \ --namespace "${{ env.K8S_NAMESPACE }}" \ --values "${{ env.HELM_VALUES_FILE }}" \ --values "${{ steps.plane_runtime_values.outputs.runtime_values_file }}" \ --set-string plane-ce.env.docstore_bucket="${{ steps.tf_outputs.outputs.plane_docstore_bucket_name }}" \ --set-string plane-ce.env.aws_region="${{ env.AWS_REGION }}" \ - --set-string plane-ce.env.aws_s3_endpoint_url="${{ secrets.PLANE_S3_ENDPOINT_URL }}" \ + --set-string plane-ce.env.aws_s3_endpoint_url="${S3_ENDPOINT_URL}" \ --rollback-on-failure \ --wait \ --wait-for-jobs \ diff --git a/README.md b/README.md index d141dc6..68437ec 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,19 @@ We intentionally use one NAT Gateway per AZ to avoid cross-AZ egress dependencie If a single AZ has issues, workloads in other AZs keep outbound internet access via their local NAT Gateway. +## Availability and isolation + +The production design is intentionally isolated and highly available at the platform layer: + +- Plane runs on its own dedicated EKS cluster: `plane-prod-eks` +- EKS control plane and worker nodes use private subnets across 3 Availability Zones +- Public exposure is handled through ALB ingress; application workloads stay inside the cluster/VPC boundary +- One NAT Gateway per AZ avoids a single shared egress dependency for private workloads +- Cluster Autoscaler is installed with IRSA so the managed node group can scale between configured minimum and maximum capacity +- HPA rules are defined for core Plane workloads (`web`, `api`, `worker`, and `beatworker`) and use CPU utilization as the scaling signal + +Current bootstrap sizing is intentionally conservative for cost control. Production replica counts, HPA max replicas, and node group limits should be raised based on observed metrics and expected traffic. + ## Deployment approach Terraform manages the AWS infrastructure baseline. @@ -86,6 +99,8 @@ The wrapper chart is located at `helm/plane` and environment overrides start wit - MinIO is disabled in `helm/plane/values/dev.yaml`; Plane document storage is configured for direct S3 usage. - Plane workloads use IRSA (IAM Role for Service Account) for S3 access; no static S3 access key is required. - Wrapper chart creates Kubernetes Secret `plane-dev-doc-store-secrets` during `helm upgrade/install`, so app deploy is self-contained. +- The S3 doc-store bucket has CORS enabled for browser-based presigned uploads. +- CI defaults `AWS_S3_ENDPOINT_URL` to the regional S3 endpoint (`https://s3.eu-west-1.amazonaws.com`) to avoid global S3 endpoint redirects during uploads. ## GitHub CI/CD (OIDC) @@ -105,7 +120,7 @@ Behavior: Required GitHub secrets: - `AWS_GITHUB_OIDC_ROLE_ARN` -- `PLANE_S3_ENDPOINT_URL` (optional for AWS S3; can be empty) +- `PLANE_S3_ENDPOINT_URL` (optional; when empty, CI uses `https://s3..amazonaws.com` for AWS S3) ## Unified Terraform + Helm Workflow @@ -146,7 +161,7 @@ helm upgrade --install plane-dev helm/plane \ --values helm/plane/values/dev.runtime.local.yaml \ --set-string plane-ce.env.docstore_bucket="" \ --set-string plane-ce.env.aws_region="eu-west-1" \ - --set-string plane-ce.env.aws_s3_endpoint_url="" + --set-string plane-ce.env.aws_s3_endpoint_url="https://s3.eu-west-1.amazonaws.com" ``` Without the runtime override file, manual upgrades can fail due to empty managed-service URLs. @@ -321,7 +336,7 @@ Use this checklist to bring up the project from scratch in a new AWS account. 1. Create GitHub repository secrets - Add `AWS_GITHUB_OIDC_ROLE_ARN` in repository secrets. - - Add optional `PLANE_S3_ENDPOINT_URL` (leave empty for AWS S3). + - Add optional `PLANE_S3_ENDPOINT_URL` only for custom S3-compatible endpoints; leave empty for AWS S3. 2. Configure AWS IAM/OIDC once (manual) - Create GitHub OIDC identity provider (`token.actions.githubusercontent.com`) if missing. diff --git a/helm/plane/values/dev.yaml b/helm/plane/values/dev.yaml index 3dee32b..fdf1781 100644 --- a/helm/plane/values/dev.yaml +++ b/helm/plane/values/dev.yaml @@ -42,7 +42,7 @@ plane-ce: remote_redis_url: "" docstore_bucket: "plane-dev-uploads" aws_region: "eu-west-1" - aws_s3_endpoint_url: "" + aws_s3_endpoint_url: "https://s3.eu-west-1.amazonaws.com" ingress: enabled: true diff --git a/terraform/environments/prod/main.tf b/terraform/environments/prod/main.tf index f5ef40f..8089e54 100644 --- a/terraform/environments/prod/main.tf +++ b/terraform/environments/prod/main.tf @@ -163,6 +163,18 @@ resource "aws_s3_bucket_public_access_block" "plane_docstore" { restrict_public_buckets = true } +resource "aws_s3_bucket_cors_configuration" "plane_docstore" { + bucket = aws_s3_bucket.plane_docstore.id + + cors_rule { + allowed_headers = ["*"] + allowed_methods = ["GET", "HEAD", "POST", "PUT"] + allowed_origins = var.plane_docstore_cors_allowed_origins + expose_headers = ["ETag"] + max_age_seconds = 3000 + } +} + resource "aws_iam_role" "plane_irsa" { name = "${var.project_name}-${var.environment}-plane-irsa-role" diff --git a/terraform/environments/prod/variables.tf b/terraform/environments/prod/variables.tf index b2fff3b..53055f1 100644 --- a/terraform/environments/prod/variables.tf +++ b/terraform/environments/prod/variables.tf @@ -363,3 +363,8 @@ variable "plane_docstore_bucket_name" { default = "" } +variable "plane_docstore_cors_allowed_origins" { + description = "Allowed browser origins for Plane direct S3 uploads. Replace the bootstrap wildcard with explicit app domains once DNS is stable." + type = list(string) + default = ["*"] +}