Skip to content

Latest commit

 

History

History
253 lines (193 loc) · 7.92 KB

File metadata and controls

253 lines (193 loc) · 7.92 KB

Containerisation & Productionisation

This document provides a complete, single-host container deployment for Purple Sentinel SOC Studio using Docker Compose.

It includes:

  • a production-style build for the backend services (Node/Express) and Web UI (Vite build served by nginx)
  • an isolated local Docker network
  • persisted storage for PostgreSQL data and server key material
  • operational recommendations and hardening notes

Overview

Goal: serve the UI and API from the same origin to avoid CORS complexity.

Topology:

  • web (nginx): serves static UI + reverse proxies:
    • /api/auth/*, /api/profile/*, /api/audit/*, /api/settings/*auth-service
    • /api/iocs/*, /api/ioc-enrichment/*ioc
    • /api/tools/*tools
    • everything else under /api/*core-api
  • core-api (node): runs incidents/health API routes on port 4000
  • auth-service (node): runs auth/profile/audit/settings API routes on port 4001
  • ioc (node): runs IOC and IOC enrichment API routes on port 4005
  • tools (node): runs analyst tool APIs on port 4010
  • postgres (PostgreSQL): persists platform state
  • pss-pgdata volume: persists PostgreSQL data files
  • pss-data volume: persists key material fallback (only used when secrets are not provided via env)
flowchart LR
  B["Browser"] -->|"https://host:8443 (TLS 1.3, self-signed)"| W["web (nginx)"]
  W -->|"/api/auth/*, /api/profile/*, /api/audit/*, /api/settings/*"| AS["auth-service (node) :4001"]
  W -->|"/api/iocs/*, /api/ioc-enrichment/*"| IS["ioc (node) :4005"]
  W -->|"/api/tools/*"| TS["tools (node) :4010"]
  W -->|"/api/* (other paths)"| CA["core-api (node) :4000"]
  AS -->|SQL| P["postgres :5432"]
  IS -->|SQL| P
  TS -->|SQL| P
  CA -->|SQL| P
  P --> PG["pss-pgdata volume (PostgreSQL data)"]
  AS --> D["pss-data volume (key material fallback)"]
  IS --> D
  TS --> D
  CA --> D
Loading

Files Included

IaC files (in repo root):

  • docker-compose.yml
  • .dockerignore
  • .env.example
  • server/Dockerfile
  • client/Dockerfile
  • deploy/nginx.conf

Quick Start (Local “Production-Style”)

Prereqs:

  • Docker Desktop (macOS/Windows) or Docker Engine + Compose (Linux)

Steps:

  1. Create environment file:
cd /Users/mark/Documents/dev/PurpleSentinelStudio
cp .env.example .env
  1. Generate strong secrets (examples):
# JWT signing secret (HS256)
export JWT_SECRET="$(openssl rand -hex 64)"

# 32-byte key for integration key encryption (AES-256-GCM)
export PSS_MASTER_KEY="$(openssl rand -base64 32)"

echo "JWT_SECRET=\"$JWT_SECRET\"" >> .env
echo "PSS_MASTER_KEY=\"$PSS_MASTER_KEY\"" >> .env
  1. Set an initial admin password (optional but recommended):
echo "DEFAULT_ADMIN_PASSWORD=\"change-this-to-a-strong-password\"" >> .env
  1. Build + run:
docker compose up -d --build
  1. Verify health:
curl -sk https://localhost:8443/api/health
  1. Open UI:
  • https://localhost:8443 (self-signed; your browser will warn)
  • http://localhost:8080 (redirects to HTTPS)

If you did not set DEFAULT_ADMIN_PASSWORD, the server will generate one and print it in logs:

docker compose logs -f auth-service

Persistence Model (Important)

The platform stores durable state in two places:

  • PostgreSQL data files under /var/lib/postgresql/data (inside the postgres container)
  • Optional key material under server/data/ (inside the backend containers at /app/server/data)

The API data directory is only used when you do not set:

  • JWT_SECRET (fallback .jwt-secret is created)
  • PSS_MASTER_KEY (fallback .settings-key is created)

In Compose, these live in the named volumes:

  • pss-pgdata (PostgreSQL data)
  • pss-data (API key material fallback)

Backup

Recommended: use pg_dump for a consistent DB backup:

docker compose exec -T postgres sh -lc 'PGPASSWORD="$POSTGRES_PASSWORD" pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > pss-db-backup.sql

Alternative (volume snapshot; stop containers first for consistency):

docker compose down
docker run --rm -v pss-pgdata:/data -v "$PWD":/backup alpine \
  sh -c "cd /data && tar -czf /backup/pss-pgdata-backup.tgz ."
docker run --rm -v pss-data:/data -v "$PWD":/backup alpine \
  sh -c "cd /data && tar -czf /backup/pss-data-backup.tgz ."

Restore

# Restore pg_dump (recommended)
docker compose up -d postgres
cat pss-db-backup.sql | docker compose exec -T postgres sh -lc 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'

# (Optional) restore key material volume
docker run --rm -v pss-data:/data -v "$PWD":/backup alpine \
  sh -c "rm -rf /data/* && tar -xzf /backup/pss-data-backup.tgz -C /data"

docker compose up -d --build

Network Model

Compose creates a dedicated bridge network pss-net (explicitly named, so it is stable).

Exposure:

  • web publishes:
    • 8080 to the host (HTTP redirect → HTTPS)
    • 8443 to the host (HTTPS, TLS 1.3)
  • core-api is not published to the host (reachable only via web on pss-net)
  • auth-service is not published to the host (reachable only via web on pss-net)
  • ioc is not published to the host (reachable only via web on pss-net)
  • tools is not published to the host (reachable only via web on pss-net)

This reduces the external attack surface: the API is not directly reachable from outside the Docker host.

nginx Reverse Proxy

deploy/nginx.conf:

  • proxies:
    • /api/auth/*, /api/profile/*, /api/audit/*, /api/settings/*http://auth-service:4001
    • /api/iocs/*, /api/ioc-enrichment/*http://ioc:4005
    • /api/tools/*http://tools:4010
    • everything else under /api/*http://core-api:4000
  • serves the SPA and supports client-side routing via try_files ... /index.html
  • adds basic response headers for static UI responses
  • caches hashed build assets for performance

Production Hardening Recommendations

This repo ships as a developer-focused tool; for real environments, do these:

  1. TLS
  • Terminate TLS in front of web (preferred: load balancer / ingress / Caddy/Traefik).
  • Enable HSTS at the TLS termination layer.
  1. Secrets
  • Always set:
    • JWT_SECRET
    • PSS_MASTER_KEY
  • Store them in a secrets manager (not in .env committed to git).
  1. Reverse proxy trust
  • When running behind a proxy/load balancer, enable proxy trust so audit ip_address is meaningful.
  • This compose file sets TRUST_PROXY=1.
  1. Outbound HTTP tooling (SSRF surface)
  • The HTTP Request Generator (POST /api/tools/http-requests) can be abused if exposed to untrusted users.
  • For production:
    • restrict it by role/permission, and/or
    • add SSRF protections (block private IP ranges + DNS rebinding), and/or
    • use network policy/egress controls to prevent access to internal networks/metadata.
  1. Scaling constraints
  • PostgreSQL enables multi-replica API deployments.
  • If you scale core-api, auth-service, ioc, and/or tools, ensure JWT_SECRET and PSS_MASTER_KEY are identical across replicas (do not rely on per-container fallback files).
  1. Observability
  • Add request IDs and structured logs for production.
  • Ship logs from web, core-api, auth-service, ioc, and tools to your log pipeline (ELK, Loki, CloudWatch, etc.).

Common Operations

Rebuild after updates:

docker compose up -d --build

Stop:

docker compose down

Wipe everything (danger: destroys DB):

docker compose down -v

Troubleshooting

  1. “Permission denied” writing to /app/server/data
  • If the volume was created with unexpected ownership, fix it:
docker run --rm -v pss-data:/data alpine sh -c "chown -R 10001:10001 /data || true"
  1. UI loads but API calls fail
  • Check:
docker compose ps
docker compose logs --tail=200 core-api
docker compose logs --tail=200 auth-service
docker compose logs --tail=200 ioc
docker compose logs --tail=200 tools
curl -i http://localhost:8080/api/health
curl -ik https://localhost:8443/api/health