Skip to content

Rust #325: published Compose runs the native server - #920

Merged
rmcdaniel merged 2 commits into
mainfrom
rust-325-compose-helm-native
Nov 1, 2026
Merged

rmcdaniel merged 2 commits into
mainfrom
rust-325-compose-helm-native

Conversation

@rmcdaniel

Copy link
Copy Markdown
Member

Operator decision (November 1, #325): the published Docker Compose file and Helm chart follow the native server in 3.0.0. This PR switches Compose. The Helm chart follows in a separate PR.

Native published Compose

  • docker-compose.published.yml (the file the README tells users to download from main) now runs the native server plus MySQL.
    • Native creates and checks its schema at startup and runs its maintenance loop in-process, so there is no bootstrap job, queue worker, scheduler or Redis.
    • The default image is the native release candidate by digest (ghcr.io/durable-workflow/server-native@sha256:f54e89d7…) until 3.0.0. The release replaces that one line with durableworkflow/server:3.0.0. DW_SERVER_IMAGE overrides it.
    • DW_RUST_EXPERIMENTAL=1 is set with a comment, because release candidates require it.
  • The healthcheck reads /api/ready with bash's /dev/tcp, because the native image has no curl.
  • server_storage persists /app/storage (native local payload storage).

PHP 2.x stays available

  • The old file moves to docker-compose.php.yml. It is the same stack, with a header explaining the split.
  • PHP releases still synchronize its fallback tag (sync-source-release.mjs), and the PHP release's source-free Compose smoke uses it (DW_PUBLISHED_COMPOSE_FILE).
  • The PHP published-artifact conformance runners, the regression and soak scripts, the dedicated-matching override and their contract tests now point at it.
  • Historical evidence docs are unchanged.

Native auth differences (recorded)

  • Native refuses DW_AUTH_BACKWARD_COMPATIBLE=false and an empty DW_AUTH_TOKEN. Role-only authentication is therefore unavailable on native: the shared token is required and accepted for every role.
  • The native file omits that variable and documents that production must set a strong DW_AUTH_TOKEN.
  • The smoke's production profile now sets a strong shared token and checks that dev-token gets 401.

CI

Docs: README quickstart, the server reference's Compose sections and the port doc inventory row describe native. The PHP-only procedures that run artisan (backup and restore, terminal summary recovery) now name docker-compose.php.yml.

Checks run locally

  • Native file against rc.2 with 1 GiB memory caps:
    • docker compose config passes, and up --wait reaches healthy (healthcheck works);
    • local profile: /api/health, /api/ready, authenticated /api/cluster/info (version 3.0.0-rc.2), worker registration, and a clean SIGTERM stop (exit 0);
    • production profile: admin and worker role tokens work, a wrong token and dev-token get 401.
  • sync-source-release.mjs --check passes. test-source-release.mjs passes 2/3; the third needs php, which this host lacks.
  • Docker workflow isolation check and its 74 unit tests pass. composer_trust_test.py passes.
  • The two PHP contract tests pass (9 tests) in a scratch PHPUnit 11 project.
  • The touched Node regression tests pass.

🤖 Generated with Claude Code

Operator decision (November 1): the published Compose file follows native
in 3.0.0. docker-compose.published.yml now runs the native server with
MySQL only. Native creates its schema and runs its own maintenance, so
there is no bootstrap job, queue worker, scheduler or Redis. Until 3.0.0 is
published its default image is the current native release candidate by
digest.

The PHP 2.x stack moves to docker-compose.php.yml. PHP releases still
synchronize it, the PHP release's Compose smoke uses it, and the PHP
published-artifact runners and their contract tests now point at it.

Native refuses DW_AUTH_BACKWARD_COMPATIBLE=false and an empty
DW_AUTH_TOKEN, so the native file omits the former and documents that
production needs a strong shared token. The Compose smoke now runs on pull
requests that change the file, and its production profile checks that
dev-token is refused. NativePublishedComposeContractTest and #907's
PublishedServerAutoSelectionTest join the Repository contracts PHPUnit
list; the latter had not been run by CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security correction required — published Compose starts with a known full-access token on every host interface

Reviewed exact head 27c893ba7e3fbe09ce1fae1033e523c638c08cf1.

Rendered with no operator environment, docker-compose.published.yml currently has both of these properties:

  • DW_AUTH_TOKEN resolves to the public literal dev-token;
  • ${SERVER_PORT:-8080}:8080 has no host address, so Docker publishes it on all host interfaces.

That combination makes a direct docker compose up expose the native API with a known administrator-compatible shared credential. The README labels dev-token as development-only and the production section asks for a strong value, but the public Compose file itself still starts successfully in the unsafe state. Docker's own documentation says host-IP omission publishes on all interfaces and calls port publishing insecure by default: https://docs.docker.com/engine/network/port-publishing/

This matters at merge, not only at 3.0.0: the file is downloaded directly from main and this PR changes that existing published deployment surface to the native runtime.

Smallest fail-closed correction:

  1. Remove the implicit token value from the Compose file. Require an explicit value, for example DW_AUTH_TOKEN: ${DW_AUTH_TOKEN:?Set DW_AUTH_TOKEN}. The documented local quickstart already exports dev-token, so it remains one command; an unconfigured direct start refuses instead of creating a known credential.
  2. Bind the host port to loopback by default, for example ${SERVER_BIND_ADDRESS:-127.0.0.1}:${SERVER_PORT:-8080}:8080. Document that remote access must select an intended protected interface/private network and terminate HTTPS at the documented reverse proxy.
  3. Add controls proving an unset token refuses Compose rendering/startup, the explicit local quickstart still works on loopback, and the production profile uses the strong shared token while rejecting dev-token. Preserve the current no-host-port MySQL boundary.

Evidence from a clean rendered config at this head:

{"known_default_token":true,"server_port_has_explicit_host_ip":false,"mysql_host_published":false,"privileged_services":0}

bash -n scripts/smoke-published-compose.sh and git diff --check pass. No container, listener, credential, database, registry, or production service was touched. This finding is limited to the public Compose default; it does not reopen PHP-only topology or the separate #919 corrections.

Security correction on #920: with no environment the native published file
started with the public dev-token on every host interface. It now has no
default shared token (Compose refuses to render until DW_AUTH_TOKEN is set)
and publishes the API on 127.0.0.1 unless SERVER_BIND_ADDRESS selects an
interface. The smoke checks that an unset token refuses to render and that
the default binding is loopback, for the native file only. The contract test
pins both, and that MySQL publishes no host port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security correction applied at a805d098, matching all three requested items:

  1. No implicit token. DW_AUTH_TOKEN: ${DW_AUTH_TOKEN:?Set DW_AUTH_TOKEN, ...}. With no environment, docker compose config exits 1 with "required variable DW_AUTH_TOKEN is missing a value". The README quickstart already exports dev-token, so it's still one command.
  2. Loopback by default. ${SERVER_BIND_ADDRESS:-127.0.0.1}:${SERVER_PORT:-8080}:8080; rendered config shows host_ip: 127.0.0.1. The file, README and server reference say remote access needs SERVER_BIND_ADDRESS on a protected private interface, with HTTPS at the reverse proxy. MySQL still publishes no host port.
  3. Checks.
    • smoke-published-compose.sh now fails if the native file renders without DW_AUTH_TOKEN and if the default published binding isn't 127.0.0.1. The PR-triggered Compose smoke runs both checks, plus the production profile's dev-token 401. The PHP release's smoke on docker-compose.php.yml is unaffected.
    • NativePublishedComposeContractTest pins the :? token, the loopback port and the MySQL no-port boundary.

Local checks run: unset-token render refused; contract tests 10/10 in PHPUnit 11; the native stack on rc.2 is healthy under the production profile (admin and worker tokens 200, a wrong token and dev-token 401). CI is running on the new head. I'll enqueue only after your re-review.

@sonarqubecloud

sonarqubecloud Bot commented Nov 1, 2026

Copy link
Copy Markdown

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security accepts the corrected public-Compose boundary at exact head a805d098db83f3ab7d05f713817cf5c6f489235e.

The file now refuses interpolation without an explicit DW_AUTH_TOKEN, publishes the API on 127.0.0.1 by default, keeps MySQL unpublised, and documents the protected-interface/HTTPS requirement for deliberate remote exposure. The focused contract and smoke controls pin the unset-token refusal, loopback binding, and production-profile rejection of dev-token.

Independent source/render controls here: bash -n and git diff --check passed; an unset-token docker compose config -q refused; a synthetic explicit token rendered exactly one loopback server port and no MySQL host port. PHP and actionlint were unavailable locally and are not claimed. The exact-head amd64/arm64 local and production Compose smokes, repository contracts, native image builds, public-boundary checks, Actions/JS/Python CodeQL and Sonar are green; the broader Rust/backend matrix is still running and remains normal owner qualification.

The prior Security hold is cleared for these boundaries. No container, listener, database, registry, credential, or production surface was changed by this rereview.

@rmcdaniel
rmcdaniel added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit bbed2be Nov 1, 2026
69 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant