218 lines
9.8 KiB
Markdown
218 lines
9.8 KiB
Markdown
---
|
|
id: FLEX-WP-0012
|
|
type: workplan
|
|
title: "Authorize railiance-platform credential-grant requests"
|
|
domain: infotech
|
|
repo: flex-auth
|
|
status: proposed
|
|
owner: codex
|
|
topic_slug: netkingdom
|
|
planning_priority: P3
|
|
planning_order: 120
|
|
related_workplans:
|
|
- RAILIANCE-WP-0005
|
|
created: "2026-08-11"
|
|
updated: "2026-08-11"
|
|
state_hub_workstream_id: "89ecdb1f-ceb0-4a50-b72d-c3dfd5fa7c73"
|
|
---
|
|
|
|
# FLEX-WP-0012 - Authorize railiance-platform credential-grant requests
|
|
|
|
`railiance-platform`'s credential broker (`scripts/credential.py`) can call
|
|
flex-auth as a preflight before issuing a credential lease, posting non-secret
|
|
grant metadata and expecting an allow/deny back. It has been able to do this
|
|
since 2026-06-27. flex-auth has never answered, because nothing in this repo
|
|
serves the endpoint the helper posts to or understands the payload it sends.
|
|
|
|
Opened from State Hub capability request `893ff109`, filed 2026-07-02 and
|
|
unanswered since. **Read the two corrections below before planning any work:
|
|
the request as filed rests on a premise that is not true, and the urgency it
|
|
describes has since evaporated.**
|
|
|
|
## Correction 1: this is not blocking anyone
|
|
|
|
The capability request says `RAILIANCE-WP-0005-T07` is blocked. It is not.
|
|
Later the same day, railiance-platform re-scoped and closed T07 on its own
|
|
scope, and `RAILIANCE-WP-0005` is now `finished`. Their closure note records
|
|
that the flex-auth call is optional by T07's own exit criteria, and that
|
|
**the grant catalog already enforces TTL, actor-type, purpose, and
|
|
delivery-mode bounds locally**.
|
|
|
|
So this workplan buys *defence in depth and central, auditable policy* — a
|
|
second, independently-owned opinion on credential issuance, with decision
|
|
records — not the unblocking of stalled work. Priority is set accordingly.
|
|
If it is never done, railiance-platform keeps enforcing the same bounds
|
|
locally; what is lost is central policy and the decision log, not safety.
|
|
|
|
Their closure note also says: "Decision taken autonomously (operator away);
|
|
revert to `wait` if Bernd prefers to hold WP-0005 open on flex-auth." That
|
|
choice is still open and should be confirmed rather than assumed.
|
|
|
|
## Correction 2: the stated technical blocker is wrong
|
|
|
|
The request says the CARING `/v1/check` schema "cannot express TTL bounds,
|
|
purpose, or delivery-mode checks." That is not correct, and the
|
|
counter-example already ships in this repo.
|
|
|
|
`examples/ops-warden/policy_package.md` denies `ttl_out_of_bounds` today by
|
|
reading `input.context.ttl_hours` and comparing it against
|
|
`input.resource.attributes.max_ttl_hours`. `CheckRequest` carries open
|
|
`context` and `resource.attributes` maps; Rego reads whatever is in them.
|
|
The CARING profile constrains *descriptor* vocabulary — canonical roles,
|
|
planes, capabilities — not the request payload a policy may inspect. A
|
|
credential-grant preflight is the same shape as the ops-warden SSH signing
|
|
gate that has been in production since `FLEX-WP-0007`: bounded TTL, an actor
|
|
class, and a purpose.
|
|
|
|
What is *actually* missing is narrower and worth stating precisely:
|
|
|
|
1. No credential-grant policy package or protected-system manifest exists for
|
|
`railiance-platform`.
|
|
2. The helper posts to `/credential-grants/authorize`, which flex-auth does
|
|
not serve. **This is not the obstacle it appears to be** — the path is
|
|
configurable via `--flex-auth-path` / `FLEX_AUTH_PATH`, so it can be
|
|
pointed at `/v1/check` without a code change on their side.
|
|
3. The payload and response shapes genuinely do differ, and this is the one
|
|
real gap. The helper posts a flat object — `grant_id`, `actor`,
|
|
`actor_type`, `subject`, `purpose`, `requested_ttl`, `delivery_mode`,
|
|
`audience`, `issuer`, `credential_type` — and reads `allowed` / `decision`
|
|
/ `status` plus optional `decision_id` and `reason`. `/v1/check` expects a
|
|
`CheckRequest` (`subject` / `action` / `resource` / `context`) and returns
|
|
`effect`, `reason`, `id`. Something has to translate.
|
|
|
|
## T01 - Decide where the translation lives
|
|
|
|
```task
|
|
id: FLEX-WP-0012-T01
|
|
status: todo
|
|
priority: medium
|
|
state_hub_task_id: "a1c9ba0c-3413-4823-9f9e-ccee09cf5d74"
|
|
```
|
|
|
|
Two honest options, and the choice is a charter question rather than a
|
|
technical one:
|
|
|
|
**A. railiance-platform emits a `CheckRequest`.** No new flex-auth API
|
|
surface. `FLEX_AUTH_PATH=/v1/check`, the helper maps its own fields into
|
|
`subject`/`action`/`resource`/`context` and reads `effect`. Keeps flex-auth's
|
|
decision surface singular, which is what `SCOPE.md` describes. Costs a change
|
|
in a repo whose workplan is already `finished`, and pushes mapping knowledge
|
|
into the consumer — the thing `FLEX-WP-0008-T01` established should be
|
|
*coordinated*, not independently invented.
|
|
|
|
**B. flex-auth serves a thin `/v1/credential-grants/authorize` adapter.** It
|
|
translates the flat payload into a `CheckRequest`, evaluates the
|
|
credential-grant package, and answers in the helper's vocabulary. No consumer
|
|
change beyond setting `FLEX_AUTH_URL`. Costs a second decision surface, and
|
|
risks the pattern `SCOPE.md` warns about — consumer-specific assumptions
|
|
leaking into the generic model — if the adapter grows policy logic instead of
|
|
staying a pure shape translation.
|
|
|
|
Recommendation to argue with, not to accept by default: **B, on the strict
|
|
condition that the adapter contains no authorization logic whatsoever** — it
|
|
maps fields, delegates to the same engine, and returns the same decision id.
|
|
The ops-warden gate is the precedent for the *policy*; the adapter is only
|
|
about a consumer's wire format, and confining it to that is what keeps the
|
|
generic model clean.
|
|
|
|
Record the decision and its reasoning in the policy package, in the style of
|
|
`FLEX-WP-0010-T02`.
|
|
|
|
Done when the decision is recorded, and railiance-platform has agreed to it —
|
|
this one cannot be decided unilaterally, because either option changes
|
|
something they own.
|
|
|
|
## T02 - Credential-grant policy package and fixtures
|
|
|
|
```task
|
|
id: FLEX-WP-0012-T02
|
|
status: todo
|
|
priority: medium
|
|
state_hub_task_id: "7e9c4e59-e59c-4617-a87f-99142952fe78"
|
|
```
|
|
|
|
Add `examples/railiance-platform/` with a protected-system manifest, subject
|
|
manifest, registry snapshot, policy package, and fixtures. Model it on
|
|
`examples/ops-warden/`, which already encodes the hard part — bounded TTL
|
|
against a per-resource maximum.
|
|
|
|
The four denials the capability request asks for, and their shapes:
|
|
|
|
| Denial | Reads |
|
|
|---|---|
|
|
| overbroad TTL | requested TTL vs the grant's max, as ops-warden does |
|
|
| wrong actor type | `actor_type` against the grant's allowed classes (`human-operator`, `approved-agent`, `ci-runner`) |
|
|
| wrong purpose | purpose required and matching the grant's permitted purposes |
|
|
| disallowed delivery mode | `delivery_mode` against the grant's allowed modes |
|
|
|
|
Note the TTL unit mismatch: the helper sends `requested_ttl` as a duration
|
|
string (`15m`), while the ops-warden package compares a numeric
|
|
`ttl_hours`. Decide where normalisation happens and make it explicit — a
|
|
policy that silently mis-parses `15m` as 15 would be a fail-*open* bug in the
|
|
one check most worth getting right. Prove the parse with a fixture, not an
|
|
assumption.
|
|
|
|
Fixtures must cover allow plus one deny per row above, and an unknown grant
|
|
id. Verify against the real binary: `test-policy`, `load-registry`, and
|
|
`check` against standalone request files.
|
|
|
|
Done when all Rego tests and fixtures pass and default-deny is demonstrated
|
|
for an unregistered grant.
|
|
|
|
## T03 - Implement the decided integration and prove it end to end
|
|
|
|
```task
|
|
id: FLEX-WP-0012-T03
|
|
status: todo
|
|
priority: medium
|
|
state_hub_task_id: "3335b2b7-cf1a-411d-95b3-03c4b4c35659"
|
|
```
|
|
|
|
Implement whichever shape T01 chose. If B, the adapter must reuse the
|
|
existing engine and emit the same decision id and provenance as `/v1/check` —
|
|
no parallel evaluation path, no second decision log.
|
|
|
|
Then close the loop against the real helper, the way `FLEX-WP-0010-T03` did
|
|
rather than in isolation: run `flex-auth serve` with this registry, point
|
|
`scripts/credential.py` at it via `FLEX_AUTH_URL`, and exercise an allow plus
|
|
each of the four denials with `--require-flex-auth` set, so a deny actually
|
|
stops issuance rather than falling through to local preauthorization.
|
|
|
|
Also verify the degraded path deliberately: with `--require-flex-auth`
|
|
**unset** and flex-auth unreachable, the helper is documented to continue by
|
|
local preauthorization. Confirm that is still what happens, and record it —
|
|
it means flex-auth is an *additional* gate here, not the deciding one, which
|
|
is a materially different security posture from the tenant-engine and
|
|
ops-warden gates and should not be quietly assumed to match them.
|
|
|
|
**No secret material is involved anywhere in this flow.** The helper posts
|
|
request metadata only. Nothing in this workplan should read, log, or persist a
|
|
credential value; if a task appears to require one, that is a signal the
|
|
design has drifted, not a reason to handle secrets here.
|
|
|
|
Done when allow and all four denials are demonstrated against the real helper
|
|
over real HTTP, `go test ./...` is green, and `gofmt`/`go vet` are clean.
|
|
|
|
## T04 - Closure and handoff
|
|
|
|
```task
|
|
id: FLEX-WP-0012-T04
|
|
status: todo
|
|
priority: low
|
|
state_hub_task_id: "40015a87-040c-4d48-b360-fd5566dbc552"
|
|
```
|
|
|
|
Reply to capability request `893ff109` — it has been open since 2026-07-02
|
|
and deserves an answer either way, including if the answer is "declined, use
|
|
`/v1/check` directly."
|
|
|
|
If the work lands, ship an image (`.forgejo/workflows/image.yaml` builds it)
|
|
and state plainly whether the credential-grant policy is *deployed* or only
|
|
*in source* — the distinction `FLEX-WP-0010` learned the hard way. Note that
|
|
the helper's flex-auth URL points at a local instance (`127.0.0.1:18090`),
|
|
not the cluster Services, so deployment here may mean something different
|
|
from the tenant-engine and user-engine rollouts; establish which runtime is
|
|
actually meant before promising an endpoint.
|
|
|
|
Update `docs/` with a credential-grant action vocabulary, matching the
|
|
existing per-consumer vocabulary docs.
|