Skip to main content

Runtime administration

The optional runtime-administration profile lets an operator inspect and change deployment policy without editing a file or restarting Control or Egress. It uses a file-backed NATS JetStream KV bucket for current configuration and history. The default profile remains unchanged: static JSON configuration and Core NATS are enough unless you enable this feature.

One deployment is still one trust boundary. There are no accounts, tenants, billing controls, or per-user roles.

Start the local example​

make dev-admin

This combines deploy/local/docker-compose.yml with deploy/local/docker-compose.runtime-admin.yml, enables JetStream persistence, and uses local-admin as the development-only admin token. Open http://localhost:8080/admin/, enter local-admin, and choose Connect. Set STRAW_ADMIN_TOKEN before the command to use a different token.

To prove worker behavior changes while requests continue, start a slow request in one terminal, then drain the worker in another:

curl -sS -H 'Content-Type: application/json' \
-d '{"method":"GET","url":"https://httpbingo.org/delay/5"}' \
http://localhost:8080/api/v1/requests &

revision="$(curl -sS -D - -o /dev/null \
-H 'Authorization: Bearer local-admin' \
http://localhost:8080/api/v1/admin/config |
awk -F '"' 'tolower($1) == "etag: " {print $2}')"

curl -sS -X POST \
-H 'Authorization: Bearer local-admin' \
-H "If-Match: $revision" \
http://localhost:8080/api/v1/admin/workers/egress-1/drain

The running request finishes. New requests receive route_unavailable until the worker is undrained. Repeat the configuration read to obtain the new ETag, then call the undrain action.

Authenticate administrative actions​

When runtime_admin.enabled is true, Control refuses to start unless the environment variable named by runtime_admin.token_env contains a non-empty token. Every /api/v1/admin/* request requires Authorization: Bearer <admin-token>. The request token in STRAW_AUTH_TOKEN does not grant administration access.

The dashboard is a static shell and does not contain the token. It sends the token entered in its password field to the same documented REST endpoints. Put both the dashboard and API behind TLS and network access controls.

Use the Config and Admin API​

All successful JSON responses use Content-Type: application/json. Mutations require If-Match with the numeric ETag revision returned by GET /api/v1/admin/config. A stale revision returns 409; omitting it returns 428. This prevents two operators from silently overwriting one another.

CapabilityREST endpointDashboard
Inspect and replace the full deployment snapshotGET, PUT /api/v1/admin/configJSON editor
Inspect audit historyGET /api/v1/admin/config/historyAudit cards
Roll back by configuration versionPOST /api/v1/admin/config/rollbackRollback button
Inspect rolloutGET /api/v1/admin/rolloutsRollout panel
Inspect workersGET /api/v1/admin/workersWorker cards
Drain, undrain, disable, or enablePOST /api/v1/admin/workers/{worker_id}/{action}Worker buttons
Inspect active request IDsGET /api/v1/admin/requestsRequest cards
Safely cancel a requestDELETE /api/v1/admin/requests/{request_id}Cancel button

Set X-Straw-Actor to a stable operator or automation identity. It is recorded in history for attribution; bearer authentication remains the authorization decision.

How an accepted change reaches the fleet:

Replace configuration​

Read the record and ETag, edit its snapshot, and send that snapshot as the PUT body:

curl -sS -D headers.txt -o record.json \
-H 'Authorization: Bearer local-admin' \
http://localhost:8080/api/v1/admin/config

revision="$(awk -F '"' 'tolower($1) == "etag: " {print $2}' headers.txt)"
jq '.snapshot.max_timeout_ms = 90000 | .snapshot' record.json > snapshot.json

curl -sS -X PUT \
-H 'Authorization: Bearer local-admin' \
-H 'X-Straw-Actor: local-example' \
-H "If-Match: $revision" \
-H 'Content-Type: application/json' \
--data-binary @snapshot.json \
http://localhost:8080/api/v1/admin/config

Control validates and prepares the complete snapshot before the durable compare-and-swap. Validation covers routing conditions and sticky TTL/fallback coherence, executor-pool eligibility semantics, destination-policy rules, header injection operations, fingerprint-profile metadata, and worker settings—not just IDs and references. Invalid input returns 422; nothing is persisted, activated, or published. Accepted changes receive the next config_version, are atomically applied to new requests, and are repeatedly published to workers. Requests already running keep the immutable snapshot with which they started.

Destination rules accept raw_pattern as their authoritative input. Control deterministically writes the corresponding normalized_host, normalized_cidr, normalized_ip, or normalized_name and clears the other derived fields. Host and CNAME patterns are lowercased and IDNA-normalized; CIDRs are masked to their canonical network; IPs are canonicalized. The rule type must match the representation: host, host_suffix (optionally *.example.com), cidr, ip, cname_suffix, private_range, or a recognized metadata_ip. Malformed hosts, suffixes, CIDRs, IPs, private ranges, metadata addresses, actions, injection operations, or non-executable fingerprint records are rejected before persistence. An empty raw_pattern is accepted only for legacy snapshots that already contain exactly one valid normalized field; when a raw pattern is supplied, stale normalized values are never retained.

Roll back​

Rollback copies a retained snapshot into a new current version; history is never rewritten:

curl -sS -X POST \
-H 'Authorization: Bearer local-admin' \
-H "If-Match: $revision" \
-H 'Content-Type: application/json' \
-d '{"config_version":1}' \
http://localhost:8080/api/v1/admin/config/rollback

Rollback goes through the same complete preparation and validation boundary. It creates a new version only when the retained snapshot is still executable, while preserving compare-and-swap and the original history record.

Understand lifecycle behavior​

  • Drain and disable stop new assignments. Existing requests continue.
  • Undrain and enable make an otherwise healthy worker eligible again.
  • Lifecycle choices are stored in the deployment snapshot, so they survive Control and worker restarts.
  • Safe cancellation cancels the Control request context and uses the existing ordered cancel frame to stop Egress.
  • Rollout status is pending until an official worker acknowledges the current version. Custom workers that do not implement runtime snapshot acknowledgement remain pending, while Control still enforces routing policy.

Back up and recover​

The default runtime-admin bucket is STRAW_RUNTIME_CONFIG; it stores the current record at the deployment key and the bounded history in the same file-backed JetStream KV bucket. The checked-in Compose overlays use the straw-runtime-data volume. Back up the NATS account or JetStream topology that owns this bucket—not just the application container filesystem.

For an owned local verification, use the disposable backup drill:

make state-backup-smoke PROFILE=admin

For an operator-managed NATS account, the NATS CLI account backup includes all streams in that account. Use a dedicated account or an operator-approved backup scope, keep the output encrypted and access-controlled, and adapt the credentials to your deployment:

export NATS_URL=nats://nats.example.internal:4222
export NATS_USER=straw-backup
export NATS_PASSWORD=replace-me
backup_dir="/secure/backups/straw-runtime-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$backup_dir"

nats --server "$NATS_URL" --user "$NATS_USER" --password "$NATS_PASSWORD" \
account backup "$backup_dir"

Restore into a replacement or clean JetStream topology according to the NATS JetStream disaster recovery procedure; do not overwrite a live data directory while NATS is running:

nats --server "$NATS_URL" --user "$NATS_USER" --password "$NATS_PASSWORD" \
account restore "$backup_dir"

The exact NATS account, operator policy, storage class, and restore target are deployment decisions. Do not copy live storage files directly; use the NATS-supported account/stream procedure or stop the server cleanly first.

If the bucket is unavailable or its current record is invalid, Control fails startup instead of silently falling back to file defaults. Restore the NATS data or start the static profile with a reviewed JSON configuration. If the bucket is intentionally deleted, the next runtime-profile start initializes version 1 from the built-in deployment policy. Retained history is bounded by history_limit (maximum 64), so external backups are required for longer retention.