chore(security): remove committed secrets/dumps, .env-based config, hardened deploy webhook
- Delete backups/*.dump and all real credentials from README/docs
(prod MinIO creds, Casdoor passwords, server IP, admin password)
- All stack secrets now come from git-ignored .env (.env.example added);
docker-compose.prod.yml refuses to boot without them (${VAR:?})
- seed_casdoor.py: bcrypt password hashing, generated console/client secrets
- Harden infra/deploy-webhook: HMAC-verified Gitea webhook receiver
+ systemd unit + README (replaces unauthenticated deploy endpoint)
- docs/SECRET-MANAGEMENT.md: secret inventory + rotation checklist
This commit is contained in:
+54
-27
@@ -1,32 +1,72 @@
|
||||
# ============================================================================
|
||||
# Production overlay — real domains + HTTPS (Let's Encrypt) + HTTP→HTTPS
|
||||
# redirect. Usage:
|
||||
# redirect + security middlewares + required secrets.
|
||||
#
|
||||
# APP_HOST=app.example.com API_HOST=api.example.com AUTH_HOST=auth.example.com \
|
||||
# S3_HOST=s3.example.com STORAGE_HOST=storage.example.com \
|
||||
# TRAEFIK_ACME_EMAIL=admin@example.com \
|
||||
# All credentials come from the git-ignored .env NEXT TO THE COMPOSE FILES
|
||||
# (compose loads it automatically — keep it on the VPS, never in git):
|
||||
#
|
||||
# cp .env.example .env # then fill in STRONG values
|
||||
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
||||
#
|
||||
# Requirements: DNS records for every host pointing at the server, ports
|
||||
# 80 + 443 open (ACME http-challenge uses port 80), and the api's
|
||||
# PROCUREMENT_CASDOOR_ISSUER must be updated to the public auth host
|
||||
# (see docs/casdoor-auth.md).
|
||||
# 80 + 443 open (ACME http-challenge uses port 80). The api's issuer is
|
||||
# derived here from AUTH_HOST (see docs/casdoor-auth.md).
|
||||
#
|
||||
# Any `${VAR:?}` below refuses to boot when the variable is missing/empty —
|
||||
# that is deliberate: production must not run on defaults.
|
||||
# ============================================================================
|
||||
|
||||
services:
|
||||
traefik:
|
||||
environment:
|
||||
# Certificates persist across restarts (acme.json in the traefik_certs volume).
|
||||
TRAEFIK_ACME_EMAIL: ${TRAEFIK_ACME_EMAIL:?set TRAEFIK_ACME_EMAIL for production}
|
||||
TRAEFIK_ACME_EMAIL: ${TRAEFIK_ACME_EMAIL:?set TRAEFIK_ACME_EMAIL in .env}
|
||||
|
||||
postgres:
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
||||
|
||||
minio:
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER in .env}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD in .env}
|
||||
labels:
|
||||
- traefik.http.routers.minio-api.rule=Host(`${S3_HOST:?set S3_HOST}`)
|
||||
- traefik.http.routers.minio-api.entrypoints=websecure
|
||||
- traefik.http.routers.minio-api.tls=true
|
||||
- traefik.http.routers.minio-api.tls.certresolver=letsencrypt
|
||||
- traefik.http.routers.minio-api.middlewares=secure-headers@file
|
||||
- traefik.http.routers.minio-api-redir.rule=Host(`${S3_HOST:?set S3_HOST}`)
|
||||
- traefik.http.routers.minio-api-redir.entrypoints=web
|
||||
- traefik.http.routers.minio-api-redir.middlewares=redirect-https@file
|
||||
- traefik.http.routers.minio-console.rule=Host(`${STORAGE_HOST:?set STORAGE_HOST}`)
|
||||
- traefik.http.routers.minio-console.entrypoints=websecure
|
||||
- traefik.http.routers.minio-console.tls=true
|
||||
- traefik.http.routers.minio-console.tls.certresolver=letsencrypt
|
||||
- traefik.http.routers.minio-console.middlewares=secure-headers@file
|
||||
- traefik.http.routers.minio-console-redir.rule=Host(`${STORAGE_HOST:?set STORAGE_HOST}`)
|
||||
- traefik.http.routers.minio-console-redir.entrypoints=web
|
||||
- traefik.http.routers.minio-console-redir.middlewares=redirect-https@file
|
||||
|
||||
api:
|
||||
ports: [] # production: only reachable through Traefik
|
||||
environment:
|
||||
PROCUREMENT_DATABASE_URL: postgresql+asyncpg://postgres:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/procurement
|
||||
PROCUREMENT_MINIO_ACCESS_KEY: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER in .env}
|
||||
PROCUREMENT_MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD in .env}
|
||||
PROCUREMENT_CASDOOR_CLIENT_SECRET: ${CASDOOR_CLIENT_SECRET:?set CASDOOR_CLIENT_SECRET in .env}
|
||||
PROCUREMENT_CASDOOR_ADMIN_PASSWORD: ${CASDOOR_ADMIN_PASSWORD:?set CASDOOR_ADMIN_PASSWORD in .env}
|
||||
# Issuer must equal the browser-facing Casdoor origin (its discovery doc).
|
||||
PROCUREMENT_CASDOOR_ISSUER: https://${AUTH_HOST:?set AUTH_HOST}
|
||||
PROCUREMENT_CORS_ORIGINS: ${CORS_ORIGINS:?set CORS_ORIGINS in .env}
|
||||
PROCUREMENT_ENVIRONMENT: prod
|
||||
PROCUREMENT_DEV_LOGIN_ENABLED: "false"
|
||||
labels:
|
||||
- traefik.http.routers.api.rule=Host(`${API_HOST:?set API_HOST}`)
|
||||
- traefik.http.routers.api.entrypoints=websecure
|
||||
- traefik.http.routers.api.tls=true
|
||||
- traefik.http.routers.api.tls.certresolver=letsencrypt
|
||||
# HTTP → HTTPS
|
||||
- traefik.http.routers.api.middlewares=secure-headers@file,api-ratelimit@file,api-bodylimit@file
|
||||
- traefik.http.routers.api-redir.rule=Host(`${API_HOST:?set API_HOST}`)
|
||||
- traefik.http.routers.api-redir.entrypoints=web
|
||||
- traefik.http.routers.api-redir.middlewares=redirect-https@file
|
||||
@@ -41,34 +81,21 @@ services:
|
||||
- traefik.http.routers.casdoor.entrypoints=websecure
|
||||
- traefik.http.routers.casdoor.tls=true
|
||||
- traefik.http.routers.casdoor.tls.certresolver=letsencrypt
|
||||
- traefik.http.routers.casdoor.middlewares=secure-headers@file
|
||||
- traefik.http.routers.casdoor-redir.rule=Host(`${AUTH_HOST:?set AUTH_HOST}`)
|
||||
- traefik.http.routers.casdoor-redir.entrypoints=web
|
||||
- traefik.http.routers.casdoor-redir.middlewares=redirect-https@file
|
||||
|
||||
frontend:
|
||||
build:
|
||||
args:
|
||||
DEV_LOGIN_ENABLED: "false"
|
||||
labels:
|
||||
- traefik.http.routers.frontend.rule=Host(`${APP_HOST:?set APP_HOST}`)
|
||||
- traefik.http.routers.frontend.entrypoints=websecure
|
||||
- traefik.http.routers.frontend.tls=true
|
||||
- traefik.http.routers.frontend.tls.certresolver=letsencrypt
|
||||
- traefik.http.routers.frontend.middlewares=secure-headers@file
|
||||
- traefik.http.routers.frontend-redir.rule=Host(`${APP_HOST:?set APP_HOST}`)
|
||||
- traefik.http.routers.frontend-redir.entrypoints=web
|
||||
- traefik.http.routers.frontend-redir.middlewares=redirect-https@file
|
||||
|
||||
minio:
|
||||
ports: [] # production: only reachable through Traefik
|
||||
labels:
|
||||
- traefik.http.routers.minio-api.rule=Host(`${S3_HOST:?set S3_HOST}`)
|
||||
- traefik.http.routers.minio-api.entrypoints=websecure
|
||||
- traefik.http.routers.minio-api.tls=true
|
||||
- traefik.http.routers.minio-api.tls.certresolver=letsencrypt
|
||||
- traefik.http.routers.minio-api-redir.rule=Host(`${S3_HOST:?set S3_HOST}`)
|
||||
- traefik.http.routers.minio-api-redir.entrypoints=web
|
||||
- traefik.http.routers.minio-api-redir.middlewares=redirect-https@file
|
||||
- traefik.http.routers.minio-console.rule=Host(`${STORAGE_HOST:?set STORAGE_HOST}`)
|
||||
- traefik.http.routers.minio-console.entrypoints=websecure
|
||||
- traefik.http.routers.minio-console.tls=true
|
||||
- traefik.http.routers.minio-console.tls.certresolver=letsencrypt
|
||||
- traefik.http.routers.minio-console-redir.rule=Host(`${STORAGE_HOST:?set STORAGE_HOST}`)
|
||||
- traefik.http.routers.minio-console-redir.entrypoints=web
|
||||
- traefik.http.routers.minio-console-redir.middlewares=redirect-https@file
|
||||
|
||||
Reference in New Issue
Block a user