diff --git a/.env.load.example b/.env.load.example index b1cb2be..5422034 100644 --- a/.env.load.example +++ b/.env.load.example @@ -51,3 +51,7 @@ LOAD_DURATION=30s LOAD_WS_HOLD_MS=5000 LOAD_WS_CONNECT_TIMEOUT_MS=3000 LOAD_HTTP_THINK_SECONDS=0.1 +LOAD_AUTH_VUS=8 +LOAD_AUTH_WS_TIMEOUT_MS=5000 +LOAD_AUTH_THINK_SECONDS=0.1 +LOAD_FIXTURE_PASSWORD=GENERATE_LOAD_FIXTURE_PASSWORD diff --git a/Dockerfile b/Dockerfile index 10b3ffd..424eb9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -165,6 +165,11 @@ RUN mix dialyzer --plt CMD ["mix", "dialyzer"] +FROM builder AS load_tools + +CMD ["mix", "wnh.load_fixtures"] + # Keep the production release as the default build result while exposing the -# dedicated `test` and `quality` targets to local verification scripts. +# dedicated `test`, `quality`, and isolated `load_tools` targets to local +# verification scripts. FROM final AS release diff --git a/compose.load.yaml b/compose.load.yaml index 13eaabe..04e23d5 100644 --- a/compose.load.yaml +++ b/compose.load.yaml @@ -14,6 +14,11 @@ services: labels: - traefik.http.routers.${TRAEFIK_APP_NAME}.middlewares=${TRAEFIK_APP_NAME}-forwarded - traefik.http.middlewares.${TRAEFIK_APP_NAME}-forwarded.headers.customrequestheaders.X-Forwarded-Proto=https + - traefik.http.routers.${TRAEFIK_APP_NAME}-tls.rule=${TRAEFIK_ROUTER_RULE} + - traefik.http.routers.${TRAEFIK_APP_NAME}-tls.entrypoints=websecure + - traefik.http.routers.${TRAEFIK_APP_NAME}-tls.service=${TRAEFIK_APP_NAME} + - traefik.http.routers.${TRAEFIK_APP_NAME}-tls.middlewares=${TRAEFIK_APP_NAME}-forwarded + - traefik.http.routers.${TRAEFIK_APP_NAME}-tls.tls=true worker: image: who-need-help:load diff --git a/compose.yaml b/compose.yaml index 587c493..fc786bf 100644 --- a/compose.yaml +++ b/compose.yaml @@ -37,6 +37,7 @@ services: - --providers.docker.exposedbydefault=false - --providers.docker.constraints=Label(`com.docker.compose.project`,`${TRAEFIK_PROJECT_CONSTRAINT:-who_need_help}`) - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 - --entrypoints.web.forwardedheaders.trustedips=${TRAEFIK_TRUSTED_IPS:-127.0.0.1/32} ports: - "${HTTP_PORT:-4010}:80" diff --git a/docs/local-hardening-plan.md b/docs/local-hardening-plan.md index 697e6c0..54ac7f9 100644 --- a/docs/local-hardening-plan.md +++ b/docs/local-hardening-plan.md @@ -10,13 +10,13 @@ item below unless the evidence column explicitly describes a local mock. | Browser E2E | Manual headed-Chrome scenarios exist; no committed browser suite | A fresh uniquely named Compose project runs two-user urgent help, Activity, moderation, privacy, and error paths; traces are retained on failure; its exact volume is removed | | Android UI | Two JVM unit-test files; no `androidTest` source set | Emulator instrumentation covers deep links, permissions, foreground tracking, notification Stop, lifecycle, and network failure | | CI and quality | No tracked CI workflow or static/security analysis dependencies | The same containerized gates pass locally and are represented in a validated CI workflow | -| Localization and accessibility | Completed locally: product copy and custom validation messages are extracted; EN/UK/RU catalogs and localized category descriptions/structured values are implemented | 508 default and 40 error messages are current; RU/UK have no empty/fuzzy entries; 153 backend tests and all 8 browser specs pass, including locale persistence, keyboard, axe, themes, responsive widths, and reconnect | +| Localization and accessibility | Completed locally: product copy and custom validation messages are extracted; EN/UK/RU catalogs and localized category descriptions/structured values are implemented | 508 default and 40 error messages are current; RU/UK have no empty/fuzzy entries; 161 backend tests and all 8 browser specs pass, including locale persistence, keyboard, axe, themes, responsive widths, and reconnect | | Database scale | Core discovery/chat/moderation lists call unbounded `Repo.all()` | Cursor-bounded queries pass behavior tests and measured `EXPLAIN ANALYZE` checks on an isolated generated dataset | | Load and resilience | Public/readiness/heartbeat k6 profile exists | Authenticated writes, chat, tracking, reconnect, rolling replacement, and worker retry profiles pass without touching staging data | | Observability | Protected Prometheus text endpoint exists | Local Prometheus/Grafana/Alertmanager profile scrapes every replica and an induced isolated failure exercises alert delivery | | Backup | Validated local custom-format dump and restore drill exist | An encrypted artifact is uploaded to local S3-compatible MinIO and restored into a fresh database; corruption and interrupted-upload checks fail closed | | External boundaries | Mailpit and a fake GitHub strategy cover parts of SMTP/OAuth | Local protocol-level SMTP/OAuth mocks and the applicable push adapter boundary cover success, rejection, retry, replay, and timeout | -| Final regression | 153 Phoenix tests plus reproducible browser and Android device suites | Browser, Android, API, DB, WebSocket, backup, monitoring, failure, cleanup, docs, and clean Git are verified from the final commits | +| Final regression | 161 Phoenix tests plus reproducible browser and Android device suites | Browser, Android, API, DB, WebSocket, backup, monitoring, failure, cleanup, docs, and clean Git are verified from the final commits | The goal remains open while any row lacks reproducible local evidence. @@ -55,7 +55,7 @@ The goal remains open while any row lacks reproducible local evidence. - The containerized `scripts/quality.sh` gate passes ShellCheck, Hadolint, actionlint, all Compose renders, Helm lint, a Trivy scan of tracked source and the rendered Kubernetes manifest, compiler/xref/Credo/Sobelow/Dialyzer/Hex - checks, 153 Phoenix tests, both npm audits, and a Trivy scan of the production + checks, 161 Phoenix tests, both npm audits, and a Trivy scan of the production release image. It creates random one-run database credentials and removes its exact volume, networks, images, and source snapshot. - The checked-in GitHub Actions workflow runs the same isolated backend/security @@ -71,5 +71,12 @@ The goal remains open while any row lacks reproducible local evidence. authenticated LiveViews; category descriptions and structured values follow that browser locale. The localization browser specs run in the same isolated two-web/two-worker gate as the accessibility checks. +- The isolated three-web/two-worker load profile now uses real password login, + authenticated LiveView joins, tracking start, HTTP position writes, private + chat writes, and explicit tracking stop. A measured 30-second run completed + all 1,880 authenticated chains and 240 heartbeat sockets with no functional + failure, retained no current positions after stop, passed cross-node PubSub + and readiness, and restored every tracked table count after exact fixture + cleanup. - The remaining rows above are still pending; this document is not a completion claim for the entire hardening goal. diff --git a/docs/performance.md b/docs/performance.md index a29ecc5..0078e4d 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -13,11 +13,14 @@ the load profile cannot join the staging router. It exercises: - dynamic public home and Safety pages; - database-backed readiness queries; - Phoenix WebSocket upgrades and protocol heartbeats; +- real password login and authenticated LiveView joins; +- consent-driven tracking start, authenticated location writes, private chat + writes, and explicit tracking stop/position deletion; - the configured number of web and worker replicas; - cross-node Phoenix PubSub after each run. -It does not authenticate users, create requests, send chat messages, update -locations, render a real browser, or reproduce an unknown future production +It does not render a real browser, retain a browser tracking session for an +unknown real-world trip duration, or reproduce an unknown future production traffic mix. It also does not sample the k6 load-generator container itself. Its results only describe the recorded host, container versions, replica counts, database state, and experiment inputs. @@ -50,10 +53,29 @@ Each run writes ignored evidence below `output/performance/