Read the repo inbox after grading, which is the wrong order and is now recorded as such. flex-auth had answered the NetworkPolicy question on 2026-08-18 (narrow ingress, not default-deny — L3 becomes L2, critical becomes high) and reported RISK-F-0001 fixed at 12:35 today with live 401 probes. F-0001 closes fixed and public; its escalation is withdrawn before it was ever sent. RISK-F-0002's ordering constraint lifts with it and its trigger-6 escalation is withdrawn. audit-core had routed the erasure-versus-audit legal question here on 2026-08-18 asking for an owner. RISK-N-0002 was wrong to call it a note: the remedy is not retrofittable, so the decision can only be taken early. Promoted to RISK-F-0008, owned by this repo as regulatory intake, escalated on trigger 2. Accepted rapp-postgres's record format and ops-warden's typed-act escalation vocabulary. Reading the inbox is now question zero of every review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
234 lines
11 KiB
Markdown
234 lines
11 KiB
Markdown
---
|
||
id: RISK-F-0002
|
||
type: finding
|
||
title: "ops-warden signs SSH certificates with no authorization decision, and its unblock is now unsafe"
|
||
status: open
|
||
reported_by: ops-warden
|
||
reported_via: ops-warden
|
||
routed_by: ops-warden
|
||
date_reported: "2026-08-18"
|
||
system: ops-warden
|
||
environment: production
|
||
fix_owner: ops-warden
|
||
fix_tracking: WARDEN-WP-0007 (gate shipped, disabled) / FLEX-WP-0007 (runtime deploy)
|
||
related: [RISK-F-0001]
|
||
# Graded by risk-nexus 2026-08-19 — docs/rulings/2026-08-19-first-grading.md
|
||
severity: medium
|
||
severity_at_production: medium
|
||
impact: I3
|
||
likelihood: L2
|
||
fidelity_modifier: false
|
||
production_rescore: false
|
||
constraint_on: RISK-F-0001
|
||
constraint_severity: lifted
|
||
constraint: "LIFTED 2026-08-19 — flex-auth /v1/check now authenticates callers (RISK-F-0001 fixed). Enabling policy.enabled is now an availability question for ops-warden, no longer an attestation hazard."
|
||
disclosure: embargoed
|
||
embargo_condition: "FLEX-WP-0015-T02 shipped and ops-warden policy.enabled true in production"
|
||
embargo_since: "2026-08-19"
|
||
embargo_review: "2026-11-17"
|
||
escalation: withdrawn
|
||
escalation_trigger: 6
|
||
escalation_status: withdrawn-hazard-window-closed
|
||
last_reviewed: "2026-08-19"
|
||
review_by: "2026-11-17"
|
||
graded_by: risk-nexus
|
||
ruling: RISK-RULING-2026-08-19
|
||
---
|
||
|
||
# RISK-F-0002 — the SSH signing gate is off, and turning it on is now the more dangerous move
|
||
|
||
## What is true
|
||
|
||
`ops-warden` issues short-lived SSH certificates for `adm`/`agt`/`atm` actors
|
||
against production OpenBao. It ships a pre-sign authorization gate that asks
|
||
`flex-auth` whether a given actor may sign. **That gate is disabled in
|
||
production and has been since it shipped.**
|
||
|
||
Verifiable in this estate's own files:
|
||
|
||
- `ops-warden/examples/warden.production.example.yaml:22` — `policy.enabled: false`
|
||
- `ops-warden/src/warden/policy.py:33` — `if not cfg.enabled: return None`
|
||
|
||
Every production `warden sign` therefore proceeds with no authorization
|
||
decision. What still constrains it: the actor must exist in `inventory.yaml`,
|
||
TTL is enforced per actor type (`adm` 48h, `agt` 24h, `atm` 8h), the caller must
|
||
hold a scoped `VAULT_TOKEN`, and every issuance is logged. What does not
|
||
constrain it: any per-request judgement about whether this actor should be
|
||
getting this certificate right now. Possession of the signing token is the
|
||
whole authorization model.
|
||
|
||
This is a known, deliberate state, not a discovery. It is filed because
|
||
"deliberate and known" is exactly the condition that stops being tracked once
|
||
the person who decided it stops looking — and because the reason it is still
|
||
true has just changed.
|
||
|
||
## Why this is filed now rather than in June
|
||
|
||
The gate's stated blocker has always been `FLEX-WP-0007`: `flex-auth` is not
|
||
deployed at a reachable URL, so enabling a `fail_closed: true` gate would break
|
||
all signing. That is a availability blocker, and it is boring.
|
||
|
||
`RISK-F-0001` changes the shape of it. The production config points the gate at:
|
||
|
||
```
|
||
flex_auth_url: http://flex-auth.flex-auth.svc.cluster.local:8080
|
||
```
|
||
|
||
That is precisely the ClusterIP surface `RISK-F-0001` reports as authenticating
|
||
no caller. So the sequencing is no longer "enable it once flex-auth is up". It
|
||
is:
|
||
|
||
> **Enabling the gate before `RISK-F-0001` is fixed would make things worse, not
|
||
> better.**
|
||
|
||
Today, an unauthorized signing attempt is unauthorized and unrecorded as such.
|
||
With the gate on and the oracle forgeable, an attacker who can reach the
|
||
ClusterIP can obtain a genuine `allow` and the signature log will carry a
|
||
`policy_decision_id` attesting that the issuance was authorized. The gate would
|
||
convert an absent control into a **false attestation** — and the audit trail,
|
||
which currently makes no claim, would start making one that is wrong.
|
||
|
||
An authorization check that can be forged is worse than no authorization check,
|
||
because only one of the two lies in the record afterwards.
|
||
|
||
## What ops-warden is doing about it
|
||
|
||
The mechanism recommendation for `RISK-F-0001` was answered on 2026-08-17
|
||
(`ops-warden/wiki/NetKingdomSecurityMap.md`, "Service-to-service caller
|
||
authentication"): projected, audience-scoped Kubernetes ServiceAccount tokens
|
||
verified by TokenReview, with the caller's asserted `system` bound to the
|
||
authenticated ServiceAccount. `ops-warden` implements the calling side and has
|
||
committed to a warn-only rollout on `flex-auth`'s schedule.
|
||
|
||
The ordering constraint is recorded there and restated here because it is a
|
||
risk statement, not an architecture one:
|
||
|
||
```
|
||
flex-auth warn-only -> ops-warden gate presents its SA token -> logs clean
|
||
-> flex-auth fail-closed -> ops-warden policy.enabled: true
|
||
```
|
||
|
||
`policy.enabled` must not flip anywhere while `/v1/check` answers
|
||
unauthenticated callers. Nothing else is asked of `flex-auth` by this record.
|
||
|
||
## What this repo is asked to decide
|
||
|
||
1. **Severity.** Note the two states are not equally bad and the register should
|
||
probably say which it is scoring: the gate being *off* (a missing control,
|
||
honestly represented) versus the gate being *turned on prematurely* (a
|
||
present control that lies). The second is the one worth a severity.
|
||
2. **Whether this is one finding or a dependency on `RISK-F-0001`.** It is
|
||
filed separately because the fix owner differs and because the "off" state
|
||
has its own standing regardless of how `RISK-F-0001` resolves. If this repo
|
||
would rather carry it as a consequence of `RISK-F-0001` than as a peer,
|
||
that is a reasonable call and `ops-warden` will not re-file it.
|
||
3. **Escalation.** `ops-warden` does not think this needs the operator: it is
|
||
known, owned, and its dangerous failure mode is a *sequencing* error that is
|
||
now written down in both repos. Recorded so the judgement is this repo's and
|
||
not assumed.
|
||
|
||
## The general point, offered for the register's design
|
||
|
||
`RISK-F-0001` observed that four live defects were found by repos reading their
|
||
own code against a ladder, none by monitoring. This finding is a fifth, found
|
||
the same way — by reading an answer we had just given and noticing it changed
|
||
the risk of a decision we had already made and filed away as merely blocked.
|
||
|
||
The estate's habit of recording a blocker once and not revisiting it is the
|
||
thing to watch. A blocker is a claim about the world at a date. `RISK-F-0001`
|
||
invalidated this one in a day, and nothing would have re-checked it.
|
||
|
||
## Register ruling — 2026-08-19
|
||
|
||
All three questions this finding put are answered.
|
||
|
||
**1. Severity — `medium` today, with a `high` constraint.** The headline
|
||
scores the state of the world now: the gate is off, a missing control honestly
|
||
represented. `I3` (SSH certificates into production hosts cross a trust
|
||
boundary) × `L2` (scoped `VAULT_TOKEN`, actor in `inventory.yaml`, TTLs, every
|
||
issuance logged — thin, but not nothing).
|
||
|
||
The argument that the two states are not equally bad is **accepted in full**,
|
||
and it is now written into the scale as the fidelity modifier: a control that
|
||
lies is one impact band worse than the same control absent
|
||
(`docs/method/severity.md`). It is recorded as a constraint rather than the
|
||
headline because the register describes the estate as it is, and the dangerous
|
||
state does not exist yet:
|
||
|
||
> **Constraint, severity `high`.** Enabling `policy.enabled` while `/v1/check`
|
||
> answers unauthenticated callers converts an absent control into a false
|
||
> attestation — a genuine `allow` obtained by anyone with ClusterIP reach, and
|
||
> a `policy_decision_id` in the signature log asserting the issuance was
|
||
> authorized. `I3 + fidelity → I4`, `L2` → `high`.
|
||
|
||
The constraint is attached to `RISK-F-0001`'s remediation and recorded on both
|
||
findings. A reader must not take away `medium` and miss it.
|
||
|
||
**2. Peer, not consequence.** Filed as `ops-warden` filed it. The fix owner
|
||
differs and the "off" state has standing regardless of how `RISK-F-0001`
|
||
resolves — if that finding were withdrawn tomorrow, production signing would
|
||
still carry no per-request judgement. What is not independent is the ordering,
|
||
and that travels as a constraint rather than by collapsing the two records.
|
||
|
||
**3. Escalation — the register disagrees, narrowly.** On triggers 1-5 it
|
||
agrees with `ops-warden`: no real tenant data, no obligation, no spend, no
|
||
ownership dispute, no stall. But "it is written down in both repos" is the one
|
||
argument the register cannot accept here, because this finding is itself the
|
||
evidence against it: its own blocker was written down, filed, and invalidated
|
||
in a day with nothing re-checking it.
|
||
|
||
So trigger 6 — ordering hazard producing a false attestation — fires **once**.
|
||
One acknowledgement that the operator holds the ordering, then the register
|
||
carries it. Not a standing supervision request. That trigger exists in
|
||
`docs/method/escalation.md` because of this finding.
|
||
|
||
**The general point is adopted.** "A blocker is a claim about the world at a
|
||
date" is now question 2 of every review in `docs/method/review.md`, and this
|
||
finding is cited there as the case that bought it.
|
||
|
||
Reasoning: `docs/rulings/2026-08-19-first-grading.md`.
|
||
|
||
## Reviews
|
||
|
||
- **2026-08-19** — graded. Next review 2026-11-17 (`medium` → 90 days).
|
||
Open at review: has `FLEX-WP-0007` or `WARDEN-WP-0007` moved; is the stated
|
||
blocker still true; does the ordering constraint still hold.
|
||
|
||
## Constraint lifted, escalation withdrawn — 2026-08-19 (same day)
|
||
|
||
`RISK-F-0001` was fixed hours after this register graded it: both `flex-auth`
|
||
production Deployments now enforce ADR-0004 TokenReview, with live
|
||
unbound-request probes returning 401.
|
||
|
||
**The attestation hazard is gone.** The constraint recorded this morning —
|
||
that enabling `policy.enabled` against a forgeable oracle would write a
|
||
`policy_decision_id` asserting an authorization nobody made — required
|
||
`/v1/check` to answer unauthenticated callers. It no longer does. Enabling the
|
||
gate is now an ordinary availability question for `ops-warden`: does the
|
||
calling side present its ServiceAccount token, and does signing survive a
|
||
`fail_closed: true`. That is `ops-warden`'s sequencing to run, not a risk
|
||
this register holds.
|
||
|
||
**The trigger-6 escalation is withdrawn.** The hazard window closed before the
|
||
operator was asked to hold the ordering. `ops-warden`'s own judgement that this
|
||
did not need the operator was, in the end, right — and the register still holds
|
||
that it was right for the wrong reason. The disagreement was never about the
|
||
severity of the hazard; it was about whether "written down in both repos"
|
||
keeps a thing held. It did not: the window closed by `flex-auth` shipping, not
|
||
by anyone re-reading the note.
|
||
|
||
**The finding stays open, unchanged at `medium`.** The gate is still off, and
|
||
`warden sign` still proceeds with no per-request judgement. Nothing about this
|
||
morning's news changes that.
|
||
|
||
**The general point earns another instance.** This finding argued that a
|
||
blocker is a claim about the world at a date, and that its own blocker had been
|
||
invalidated in a day with nothing re-checking it. That has now happened twice
|
||
to the same finding, in the same week, in the same direction. The second time,
|
||
the thing that had gone stale was this register's own grading, four hours old.
|
||
|
||
## Reviews
|
||
|
||
- **2026-08-19** — constraint lifted, escalation withdrawn, severity unchanged.
|
||
Open at review: has `ops-warden` presented SA tokens and enabled the gate;
|
||
is `FLEX-WP-0007` still the stated blocker or has it too gone stale.
|