Publish and roll back¶
Changes go live in small, checked steps. Nothing invalid is ever saved, and every publish can be undone.
flowchart LR
Draft["Edit the draft"] --> Save["Save<br/>validated"]
Save --> Tests["Config tests<br/>must pass"]
Tests --> Publish["Publish<br/>new version"]
Publish --> Canary["Canary<br/>optional"]
Canary --> Full["All traffic"]
Publish --> Full
Full --> Guard{"Guard watches<br/>first 10 minutes"}
Canary --> Guard
Guard -->|"degraded"| Back["Automatic rollback<br/>or canary abort"]
Guard -->|"healthy"| Done["Done"]
Draft¶
There is one shared draft. Saving it validates the whole config; an invalid draft is refused with the exact field and reason. Saves are guarded by revision, so two editors can't overwrite each other: if the active version moved on, Rebase the draft onto it.
Publish¶
Publish turns the saved draft into a new, numbered, immutable version with a message. Publish is refused when:
- the message is empty;
- a config test fails;
- a canary is in progress (promote or abort it first);
- the active version changed since the draft started (Rebase first);
- a live edge can't read the config's schema version (upgrade edges first);
- a live edge runs with different deployment settings than admin (
CLIENT_IP_SOURCE,VIEWER_HEADERS,INGRESS_SECRET_HEADERorALLOW_REGEX), because that edge would reject the config and keep its old one. Admin answers 409 naming the edge; make the settings match. Reverts and guard rollbacks skip this check; - a rule requires auth from an issuer whose keys (JWKS) admin has never fetched. A publisher can override this one with a reason, for example "IdP is down, publishing anyway".
Edges poll admin every 5 seconds and switch to the new version atomically. Fleet shows which edges run which version.
Canary¶
Enter a canary percent when publishing to send only part of the traffic to the new version. Clients are chosen by a hash of their IP, so each client consistently sees one version (IPv6 per /64).
| Action | Effect |
|---|---|
| Step up | Raise the percentage, for example 1 → 10 → 50. Each step must run at least 5 minutes and see 1000 requests |
| Promote to all traffic | Send all traffic to the new version |
| Abort | Send all traffic back, with a reason |
If admin goes away during a canary, edges drop the canary on their own within 10 minutes.
Rollback guard¶
For the first 10 minutes after a publish, or during a canary, the guard compares the new version with the old one. A canary is compared with the active version over the same minutes; a full publish is compared with the 10 minutes before it. Both sides need 1000 requests before it decides anything.
| Signal | Default | Trips when |
|---|---|---|
fleet_adoption |
rollback | Under 90% of edges run the version after 2 minutes |
edge_5xx_ratio |
rollback | Over 2× the baseline and over 1% |
upstream_5xx_ratio |
rollback | Over 2× the baseline and over 2% |
changed_rule_limited_ratio |
rollback | A changed rate limit limits over 5% of what it evaluates, or over 3× the rate it would have limited while it was log-only. A changed config rule blocks over 3× the larger of the share it logged and the share it blocked in the active version, or over 5% if it did neither |
unchanged_rule_limited_ratio |
alert | Over 3× the baseline |
p99_latency |
alert | Over 1.5× the baseline and over 50 ms |
auth_failure_ratio |
alert | Over 2× the baseline and over 2% |
The two rule signals cover rate limits and config rules with a block setting; a config rule evaluates every request, so its share is of all requests the version served. A config rule whose block was log in the active version is judged only against its logged share × max_vs_shadow, so a rule that blocks what it logged doesn't roll back however large that share is. An edited block rule is judged against the larger of its logged share and its blocked share in the active version, times max_vs_shadow. With neither a log run nor blocks in the active version, max_ratio (5%) applies, which is why the bot recipes log first. Rollback signals are ones only a config change causes. Signals an attack can also cause only alert. For rate limits, clients hammering one rule (each holding at least 5% of its limited requests) are left out of the counts, so a single attacker can't trip it. Config rules count every block, since stats keep no per-client config counts, so log a new block rule first.
A rollback publishes the previous version again as a new version. By default it happens at most once per publish (max_auto_rollbacks_per_publish), and never for a revert or a rollback, which only alert. Every decision is stored on the version; alerts, rollbacks and canary aborts are also written to the audit log, and admin logs guard tripped.
Settings → Rollback guard changes the defaults. Every signal must be present with its thresholds; set a signal's mode to off to disable it. A publisher can change the guard for one publish, with a required reason, for example "this rule limits on purpose".
Publishing a rule that targets an ongoing attack
Attackers spread over many IPs can push changed_rule_limited_ratio over its limit and get the rule rolled back. Publish such a rule with a guard override and a reason.
Revert¶
Versions → Revert publishes an old version's own rules as a new version (optionally through a canary), with the current autopilot rules, or without them when together they are refused (Autopilot rules). History is never rewritten. Copy to draft starts a new draft from any version instead, without its autopilot rules.
Autopilot rules¶
Autopilot rules are rate-limit rules whose ids start ap-. They run after your rules, each until its expires_at (Rules that expire). Admin refuses (422) an autopilot rule whose id lacks the ap- prefix, that has no expires_at, or whose expires_at is more than 24 hours ahead, and leaves out one whose expires_at has already passed. A rule's content is fixed per id: republishing a live ap- id with anything changed but its action and expires_at is refused (422), so a changed rule needs a new id. Republishing it with action: block makes it enforce, and with a later expires_at renews it. An ap- id adopted into your draft and no longer live is refused too (422): a new autopilot rule needs a new id. They are published as their own versions, with source autopilot in Versions and config.overlay_publish in Audit, through every publish check, except that one which only takes autopilot rules out skips the deployment and schema checks, like a revert. They wait for a canary like any publish.
- The draft holds your rules only. Rules lists the autopilot rules apart, read-only, with their expiry. A save counts them, so a draft that would take the version past 200 rate-limit rules is refused at save.
- They don't make you rebase. An autopilot publish keeps your draft as it is and moves it onto the new version. It publishes over issuers whose keys admin never fetched, as the live version did, and records its own fixed reason in
override_reason. - Config tests ignore them in
rate_limit_rules. A test's list is compared with your rules only. - Every publish keeps them while they run: yours, an import and a revert. Every publish, revert, rollback and import drops the ones already expired. An import ignores the
ap-rate-limit rules in its file and is refused when a config rule or request header rule there has anap-id; an export includes them, so an edge started from the bootstrap file keeps them until they expire. - Reverts and rollbacks keep today's autopilot rules. An
ap-rule of the restored version comes back too while it has not expired, if you adopted it after that version: restoring removes your copy. The guard judges the version a person published, not an autopilot publish, and leaves autopilot rules out of its rule signals: each autopilot rule has its own guard. If a revert or a guard rollback is refused with the autopilot rules (validation, the schema the edges can load, or the config tests) but the restored rules alone pass validation and the config tests, admin publishes them without any autopilot rules (an edge too old to read them keeps its policy), so autopilot rules never block either, and the version's audit event hasoverlay_dropped: true. - Adopt copies an autopilot rule into the draft as your own rule, without the
ap-prefix and without an expiry. It needsconfig.writeand a draft with no unsaved changes, and is refused when the draft already has a rule with that id in any phase, since rule ids are unique across phases. The rule shows as adopted until your next publish, which removes the autopilot copy. ap-is reserved. A rule of yours with such an id, in any phase, is refused at save. Admin won't upgrade while the active version, a canary or the draft has one:admin migratefails naming the rule. Rename it with the previous release, then runadmin migrateagain before rolling admin. An older version with such a rule, in any phase, loses it when restored.
Audit¶
Every change is in Audit: who, what, when and a diff. The log is append-only, HMAC-chained with a key kept outside Postgres, and checked every 5 minutes and in full daily. The page shows the result of the last recorded verification and has a Verify chain button.