# Mac-free, one-command demo stack for evaluating RawTrail. # # docker compose -f docker-compose.demo.yml up # # then open https://localhost:8088/ui/ and sign in: demo@rawtrail.local / demo # # It boots the real server + embedded UI against MySQL, generates a self-signed # localhost TLS cert, bundles a dex IdP for SSO, and runs a one-shot seeder that # replays a curated attack + noise corpus through the real ingest + detection # pipeline so the UI shows a process graph, fired ATT&CK alerts, and an # application-control block. No Mac, MDM, or Apple entitlement required. # # Images default to the `latest` released tag so the demo always shows the # newest published product; pin a specific release with EDR_VERSION=vX.Y.Z. To # run from a local source build instead (no published tag needed), layer the # build override: # # docker compose -f docker-compose.demo.yml -f docker-compose.demo.build.yml up --build # # This stack is for evaluation only: empty MySQL password, self-signed cert, # checked-in dev secrets. Do not expose it or use it in production. name: rawtrail-demo services: mysql: image: mysql:8.4.9@sha256:c36050afdca850f23cef85703f84c7531a5ae155a11b5ee1c60acb09937c4084 environment: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" MYSQL_DATABASE: edr # No named volume: data lives in an anonymous volume that persists across `up`/`down` # but is discarded by `docker compose ... down -v`. Re-runs are safe regardless: the # seeder is idempotent and skips replay when the demo data is already present. healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s timeout: 5s retries: 20 start_period: 10s # ClickHouse event store (ADR-0015): the server stores events here and does not start without it. Anonymous volume like mysql: # persists across up/down, discarded by `down -v`. clickhouse: image: clickhouse/clickhouse-server:24.8@sha256:1ffa82edee000a42c09313bd9f1293d94c570aee74babc1b3ca9983a35fa597b environment: CLICKHOUSE_DB: edr CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: "1" ulimits: nofile: soft: 262144 hard: 262144 healthcheck: test: ["CMD", "clickhouse-client", "--query", "SELECT 1"] interval: 5s timeout: 5s retries: 20 start_period: 10s dex: image: ghcr.io/dexidp/dex:v2.41.1 command: ["dex", "serve", "/etc/dex/config.yaml"] volumes: - ./config/dex/demo-config.yaml:/etc/dex/config.yaml:ro - dex-data:/var/dex networks: default: # The server reaches the issuer host (dex.demo.localhost) through this # alias; the browser reaches the same host via *.localhost -> 127.0.0.1 # and the published port below. See config/dex/demo-config.yaml. aliases: - dex.demo.localhost ports: - "127.0.0.1:5556:5556" healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:5556/dex/.well-known/openid-configuration"] interval: 5s timeout: 5s retries: 20 cert-init: # Generate a self-signed localhost cert into the shared tls volume before the # server boots (the distroless server image has no openssl). SAN covers # `localhost` (the browser-facing URL) and `server` (the in-network hostname # the seeder verifies). Runs once and exits; idempotent across restarts. image: alpine:3.21 command: - sh - -c - | set -e if [ -s /tls/dev.crt ] && [ -s /tls/dev.key ]; then echo "cert already present, skipping" exit 0 fi apk add --no-cache openssl >/dev/null openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ -keyout /tls/dev.key -out /tls/dev.crt \ -subj "/CN=localhost" \ -addext "subjectAltName=DNS:localhost,DNS:server,IP:127.0.0.1,IP:::1" chmod 0644 /tls/dev.crt /tls/dev.key echo "generated /tls/dev.crt" volumes: - tls:/tls server: image: ghcr.io/rawtrail/rawtrail-server:${EDR_VERSION:-latest} restart: unless-stopped # Longer than every stage of a graceful stop put together: the drain (EDR_SHUTDOWN_DRAIN, # 30s), the in-flight deadline (15s), the two loop joins (10s + 5s) and the telemetry # flush (5s): 65s in all. Docker's default is 10s, which cuts the drain the load # balancer is watching and severs in-flight requests (#1127). test/arch keeps this # in step with those five constants. Raise it too if you raise EDR_SHUTDOWN_DRAIN. stop_grace_period: 90s depends_on: mysql: condition: service_healthy clickhouse: condition: service_healthy dex: condition: service_healthy cert-init: condition: service_completed_successfully environment: EDR_DSN: "root@tcp(mysql:3306)/edr?parseTime=true&tls=false" # Required by the source-built server (ADR-0015); ignored by the released `latest` image. EDR_CLICKHOUSE_DSN: "clickhouse://default:@clickhouse:9000/edr" EDR_ENROLL_SECRET: demo-enroll-secret EDR_TLS_CERT_FILE: /tls/dev.crt EDR_TLS_KEY_FILE: /tls/dev.key EDR_LOG_FORMAT: json EDR_SECRET_KEY: demo-only-secret-key-do-not-use-in-prod-0123456789 # SSO against the bundled dex is configured by the seeder below, which writes the dex connection into the durable oidc_config # store (issue #512). The server reads no EDR_OIDC_* and picks up the seeded config at login. # Break-glass is available as an alternative login (a redemption URL prints # to the server logs on first boot). See README "Notes for reviewers". EDR_BREAKGLASS_RP_ID: localhost EDR_BREAKGLASS_RP_ORIGINS: https://localhost:8088 ports: - "127.0.0.1:8088:8088" volumes: - tls:/tls:ro seeder: image: ghcr.io/rawtrail/rawtrail-demo-seed:${EDR_VERSION:-latest} # One-shot: enrolls hosts, replays the corpus, verifies, seeds the dex SSO config, then exits 0. restart: "no" depends_on: mysql: condition: service_healthy clickhouse: condition: service_healthy cert-init: condition: service_completed_successfully server: # The server image is distroless (no shell for a container healthcheck), # so the seeder polls /readyz itself rather than gating on health. condition: service_started environment: EDR_DEMO_SERVER_URL: https://server:8088 EDR_ENROLL_SECRET: demo-enroll-secret EDR_DSN: "root@tcp(mysql:3306)/edr?parseTime=true&tls=false" # Lets the source-built seeder slide archived-event timestamps on restart (ADR-0015); ignored by the released `latest` image. EDR_CLICKHOUSE_DSN: "clickhouse://default:@clickhouse:9000/edr" EDR_DEMO_CA_CERT: /tls/dev.crt # SSO seed (issue #512): the seeder writes the dex connection config into the durable oidc_config store; the server reads no # EDR_OIDC_* and picks it up at login. EDR_SECRET_KEY MUST match the server's so the sealed client secret decrypts at login. # EDR_DEMO_OIDC_EXTERNAL_URL is the browser-facing base; the redirect is /api/auth/callback. JIT lands dex users at analyst. EDR_SECRET_KEY: demo-only-secret-key-do-not-use-in-prod-0123456789 EDR_DEMO_OIDC_ISSUER: http://dex.demo.localhost:5556/dex EDR_DEMO_OIDC_CLIENT_ID: edr-demo EDR_DEMO_OIDC_CLIENT_SECRET: edr-demo-client-secret-do-not-use-in-prod EDR_DEMO_OIDC_EXTERNAL_URL: https://localhost:8088 EDR_DEMO_OIDC_DEFAULT_ROLE: analyst # Pre-provision the demo SSO account so first dex login lands as `admin` instead of the JIT default analyst: the seeder binds the # dex-issued OIDC subject below to an `admin` user, and login matches that (provider, subject) identity and reuses it rather than # JIT-provisioning. This is the only sound way to seed a privileged demo user: the JIT default role is clamped to analyst/auditor # by design (identity/bootstrap clampJITRole), so an SSO claim can never auto-grant admin. EDR_DEMO_OIDC_SUBJECT is the dex # local-connector subject for the demo@rawtrail.local static user (userID de300000-...-0001, connID `local`), stable for the # checked-in dex config. Break-glass (admin@rawtrail.local) stays the super_admin path. EDR_DEMO_OIDC_SUBJECT: "CiRkZTMwMDAwMC0wMDAwLTQwMDAtODAwMC0wMDAwMDAwMDAwMDESBWxvY2Fs" EDR_DEMO_ROLE: admin volumes: - tls:/tls:ro volumes: dex-data: tls: