diff --git a/plane-app/docker-compose.yaml b/plane-app/docker-compose.yaml index 4309eca..c68e859 100644 --- a/plane-app/docker-compose.yaml +++ b/plane-app/docker-compose.yaml @@ -30,6 +30,7 @@ x-proxy-env: &proxy-env CERT_EMAIL: ${CERT_EMAIL:-} CERT_ACME_CA: ${CERT_ACME_CA:-https://acme-v02.api.letsencrypt.org/directory} CERT_ACME_DNS: ${CERT_ACME_DNS:-} + TRUSTED_PROXIES: ${TRUSTED_PROXIES:-0.0.0.0/0} LISTEN_HTTP_PORT: ${LISTEN_HTTP_PORT:-8090} LISTEN_HTTPS_PORT: ${LISTEN_HTTPS_PORT:-8443} BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads} diff --git a/plane-app/plane.env.staging.example b/plane-app/plane.env.staging.example new file mode 100644 index 0000000..188e83a --- /dev/null +++ b/plane-app/plane.env.staging.example @@ -0,0 +1,97 @@ +APP_DOMAIN=task.staging.nodedc.example +APP_RELEASE=v1.3.0 + +WEB_REPLICAS=1 +SPACE_REPLICAS=1 +ADMIN_REPLICAS=1 +API_REPLICAS=1 +WORKER_REPLICAS=1 +BEAT_WORKER_REPLICAS=1 +LIVE_REPLICAS=1 + +LISTEN_HTTP_PORT=8090 +LISTEN_HTTPS_PORT=8443 + +WEB_URL=https://task.staging.nodedc.example +DEBUG=0 +CORS_ALLOWED_ORIGINS=https://task.staging.nodedc.example,https://launcher.staging.nodedc.example +API_BASE_URL=http://api:8000 +COOKIE_DOMAIN=.staging.nodedc.example + +PGHOST=plane-db +PGDATABASE=plane +POSTGRES_USER=plane +POSTGRES_PASSWORD=replace-with-random-staging-secret +POSTGRES_DB=plane +POSTGRES_PORT=5432 +PGDATA=/var/lib/postgresql/data +DATABASE_URL= + +REDIS_HOST=plane-redis +REDIS_PORT=6379 +REDIS_URL= + +RABBITMQ_HOST=plane-mq +RABBITMQ_PORT=5672 +RABBITMQ_USER=plane +RABBITMQ_PASSWORD=replace-with-random-staging-secret +RABBITMQ_VHOST=plane +AMQP_URL= + +CERT_ACME_CA=https://acme-v02.api.letsencrypt.org/directory +TRUSTED_PROXIES=replace-with-platform-edge-proxy-cidr +SITE_ADDRESS=:80 +CERT_EMAIL=admin@nodedc.example +CERT_ACME_DNS= + +SECRET_KEY=replace-with-random-staging-secret + +USE_MINIO=1 +AWS_REGION= +AWS_ACCESS_KEY_ID=replace-with-random-staging-secret +AWS_SECRET_ACCESS_KEY=replace-with-random-staging-secret +AWS_S3_ENDPOINT_URL=http://plane-minio:9000 +AWS_S3_BUCKET_NAME=uploads +FILE_SIZE_LIMIT=5242880 +PROXY_BODY_SIZE_LIMIT=1073741824 +POSTHOG_API_KEY= +POSTHOG_HOST= +INSTANCE_CHANGELOG_URL= +IS_INTERCOM_ENABLED=0 +INTERCOM_APP_ID= + +GUNICORN_WORKERS=1 +MINIO_ENDPOINT_SSL=0 +API_KEY_RATE_LIMIT=60/minute +LIVE_SERVER_SECRET_KEY=replace-with-random-staging-secret +DOCKERHUB_USER=makeplane +PULL_POLICY=if_not_present +CUSTOM_BUILD=false + +ENABLE_SIGNUP=0 +ENABLE_EMAIL_PASSWORD=0 +ENABLE_MAGIC_LINK_LOGIN=0 +PLANE_OIDC_ISSUER=https://auth.staging.nodedc.example/application/o/task-manager/ +PLANE_OIDC_CLIENT_ID=nodedc-task-manager +PLANE_OIDC_CLIENT_SECRET=replace-with-random-staging-secret +PLANE_OIDC_REDIRECT_URI=https://task.staging.nodedc.example/auth/oidc/callback +PLANE_OIDC_SCOPE="openid email profile groups" +PLANE_OIDC_REQUIRED_GROUPS=nodedc:superadmin,nodedc:taskmanager:admin,nodedc:taskmanager:user +PLANE_OIDC_AUTO_LINK_EMAIL=1 +PLANE_OIDC_AUTO_CREATE_USER=1 +PLANE_NODEDC_SKIP_PROFILE_ONBOARDING=1 +PLANE_NODEDC_ACCESS_ENFORCEMENT=1 +PLANE_NODEDC_ACCESS_CHECK_URL=https://launcher.staging.nodedc.example/api/internal/access/check +PLANE_NODEDC_ACCESS_TOKEN=replace-with-same-value-as-NODEDC_INTERNAL_ACCESS_TOKEN +PLANE_NODEDC_ACCESS_SERVICE_SLUG=task-manager +PLANE_NODEDC_ACCESS_TIMEOUT_SECONDS=3 +PLANE_NODEDC_ACCESS_CACHE_SECONDS=0 +PLANE_NODEDC_ACCESS_ENFORCE_UNLINKED=1 +PLANE_NODEDC_ACCESS_DENIED_REDIRECT_URL=https://launcher.staging.nodedc.example/ +PLANE_NODEDC_GLOBAL_LOGOUT_URL="https://launcher.staging.nodedc.example/auth/logout?global=1&returnTo=/" +PLANE_NODEDC_LAUNCHER_PUBLIC_URL=https://launcher.staging.nodedc.example +PLANE_NODEDC_HANDOFF_URL=https://launcher.staging.nodedc.example/api/internal/handoff/consume +PLANE_NODEDC_HANDOFF_TIMEOUT_SECONDS=3 +PLANE_NODEDC_WORKSPACE_POLICY_URL=https://launcher.staging.nodedc.example/api/internal/access/check +PLANE_NODEDC_WORKSPACE_POLICY_TIMEOUT_SECONDS=3 +PLANE_NODEDC_WORKSPACE_CREATION_MODE=any_authorized_user diff --git a/scripts/check-tasker-staging-env.sh b/scripts/check-tasker-staging-env.sh new file mode 100755 index 0000000..bb1d4fb --- /dev/null +++ b/scripts/check-tasker-staging-env.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${1:-"$ROOT_DIR/plane-app/plane.env.staging"}" + +if [[ ! -f "$ENV_FILE" ]]; then + echo "Missing Tasker staging env file: $ENV_FILE" >&2 + exit 1 +fi + +set -a +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +failures=0 + +fail() { + echo "FAIL: $*" >&2 + failures=$((failures + 1)) +} + +require_value() { + local name="$1" + local value="${!name:-}" + + if [[ -z "$value" ]]; then + fail "$name is required" + fi +} + +require_secret() { + local name="$1" + local value="${!name:-}" + + require_value "$name" + + if [[ "$value" =~ change-me|local-dev|replace-with|example|plane|secret-key|access-key ]]; then + fail "$name uses a placeholder/dev value" + fi + + if [[ ${#value} -lt 32 ]]; then + fail "$name must be at least 32 characters" + fi +} + +require_https_url() { + local name="$1" + local value="${!name:-}" + + require_value "$name" + + if [[ "$value" != https://* ]]; then + fail "$name must use https://" + fi +} + +require_staging_domain() { + local name="$1" + local value="${!name:-}" + + require_value "$name" + + if [[ "$value" == *.local.nodedc || "$value" == "localhost" || "$value" == 127.* ]]; then + fail "$name must not use local/dev domain" + fi +} + +require_staging_domain APP_DOMAIN +require_https_url WEB_URL +require_https_url PLANE_OIDC_ISSUER +require_https_url PLANE_OIDC_REDIRECT_URI +require_https_url PLANE_NODEDC_ACCESS_CHECK_URL +require_https_url PLANE_NODEDC_ACCESS_DENIED_REDIRECT_URL +require_https_url PLANE_NODEDC_GLOBAL_LOGOUT_URL +require_https_url PLANE_NODEDC_LAUNCHER_PUBLIC_URL +require_https_url PLANE_NODEDC_HANDOFF_URL +require_https_url PLANE_NODEDC_WORKSPACE_POLICY_URL + +require_value CORS_ALLOWED_ORIGINS +IFS=',' read -ra cors_origins <<< "$CORS_ALLOWED_ORIGINS" +for origin in "${cors_origins[@]}"; do + origin="${origin//[[:space:]]/}" + if [[ "$origin" != https://* ]]; then + fail "CORS_ALLOWED_ORIGINS contains non-HTTPS origin: $origin" + fi +done + +require_secret POSTGRES_PASSWORD +require_secret RABBITMQ_PASSWORD +require_secret SECRET_KEY +require_secret AWS_ACCESS_KEY_ID +require_secret AWS_SECRET_ACCESS_KEY +require_secret LIVE_SERVER_SECRET_KEY +require_secret PLANE_OIDC_CLIENT_SECRET +require_secret PLANE_NODEDC_ACCESS_TOKEN + +if [[ "${COOKIE_DOMAIN:-}" == *.local.nodedc || "${COOKIE_DOMAIN:-}" == "localhost" ]]; then + fail "COOKIE_DOMAIN must not use local/dev domain" +fi + +if [[ "${ENABLE_SIGNUP:-}" != "0" ]]; then + fail "ENABLE_SIGNUP must be 0" +fi + +if [[ "${ENABLE_EMAIL_PASSWORD:-}" != "0" ]]; then + fail "ENABLE_EMAIL_PASSWORD must be 0 for staging OIDC-only access" +fi + +if [[ "${ENABLE_MAGIC_LINK_LOGIN:-}" != "0" ]]; then + fail "ENABLE_MAGIC_LINK_LOGIN must be 0" +fi + +if [[ "${PLANE_NODEDC_ACCESS_ENFORCEMENT:-}" != "1" ]]; then + fail "PLANE_NODEDC_ACCESS_ENFORCEMENT must be 1" +fi + +if [[ "${PLANE_NODEDC_ACCESS_ENFORCE_UNLINKED:-}" != "1" ]]; then + fail "PLANE_NODEDC_ACCESS_ENFORCE_UNLINKED must be 1" +fi + +if [[ "${TRUSTED_PROXIES:-}" =~ change-me|local-dev|replace-with|example ]]; then + fail "TRUSTED_PROXIES uses a placeholder/dev value" +fi + +case "${TRUSTED_PROXIES:-}" in + *"0.0.0.0/0"*|*"::/0"*|*"10.0.0.0/8"*|*"172.16.0.0/12"*|*"192.168.0.0/16"*|*"127.0.0.0/8"*) + fail "TRUSTED_PROXIES must be limited to the actual platform edge proxy or ingress subnet" + ;; +esac + +if [[ $failures -gt 0 ]]; then + echo "Tasker staging env check failed with $failures issue(s)." >&2 + exit 1 +fi + +echo "Tasker staging env check passed: $ENV_FILE"