fix: resolve review #004 findings W1-W4 + close review gate

W1 (call/protocol/abort-cascade-wiring): wire AbortCascade into
CallAdapter handle_stream for EVENT_ABORTED. Cascades with
AbortPolicy::AbortDependents, aborts root, no descendant frames on
wire (ADR-016 Decision 2). Two integration tests added.

W2 (core/endpoint-client-fingerprint): extract TLS client cert
fingerprint in dispatch_quinn (SHA256:<hex> of leaf cert DER via
peer_identity) and dispatch_iroh (ed25519:<hex> of peer NodeId).
Fingerprint format documented in auth.md. Server config change
(with_no_client_auth → request-but-don't-require) deferred to new
follow-up task core/endpoint-request-client-cert.

W3 (vault/mnemonic-debug-redaction): replace Mnemonic derive(Debug)
with manual redacting impl (phrase: "[REDACTED]"). Seed confirmed
no Debug impl. Redaction test added.

W4 (core/auth-apikey-resources): Option B — drop entry.resources from
spec. External identities (token/fingerprint) grant scopes only;
resource-scoped ACLs are composition-internal (ADR-015/022). auth.md
corrected + limitation documented. Two tests confirm empty resources.

review-post-impl-fixes: all 4 verified, workspace green (326 tests,
0 failures, 0 clippy warnings). Review #004 status → resolved.

Graph: 34 tasks, 12 gens.
This commit is contained in:
2026-06-24 11:00:54 +00:00
parent d149932e2a
commit 97216764ea
12 changed files with 492 additions and 32 deletions

View File

@@ -150,10 +150,41 @@ The "Config" prefix indicates that identities are resolved from configuration (a
How it resolves:
- **Fingerprint**: Look up in `DynamicConfig::auth::authorized_keys_fingerprints`. If found, return `Identity { id: fingerprint, scopes: ["relay:connect"], resources: {} }`.
- **Token**: Parse as UTF-8. If it starts with `alk_`, look up in `DynamicConfig::auth::api_keys` by prefix match + SHA-256 hash. If found and not expired, return `Identity { id: prefix, scopes: entry.scopes, resources: entry.resources }`.
- **Token**: Parse as UTF-8. If it starts with `alk_`, look up in `DynamicConfig::auth::api_keys` by prefix match + SHA-256 hash. If found and not expired, return `Identity { id: prefix, scopes: entry.scopes, resources: {} }`.
> **Resource-scoped ACLs and external identities.** `Identity.resources` is
> populated only by the composition path (`CompositionAuthority::as_identity`,
> ADR-015/022) — never by token or fingerprint resolvers. API keys and
> fingerprints grant **scopes only**; resource-scoped access is an
> internal-composition concern. An `OperationSpec` that declares
> `resource_type`/`resource_action` will return `FORBIDDEN` when the caller
> authenticated via token or fingerprint, because `Identity.resources` is
> empty. This is a documented limitation, not a bug: if a future crate needs
> per-key resource binding, it must earn a dedicated ADR that adds a
> `resources` field to `ApiKeyEntry` and the fingerprint config path, rather
> than silently widening the external-auth contract.
Changes to `DynamicConfig` via `ConfigReloadHandle` are reflected immediately — `ConfigIdentityProvider` reads from `ArcSwap` on every call.
### Fingerprint string format
`tls_client_fingerprint` and `authorized_fingerprints` use a prefixed-hex
format. The prefix identifies the key type; the body is the hex-encoded
hash or raw key bytes. `AuthPolicy::resolve_identity_from_fingerprint`
does a literal `HashSet::contains()` — no normalization — so the extractor
and the operator config must use the same format.
| Transport | Source | Format |
|-----------|--------|--------|
| quinn (X.509) | leaf client cert DER | `SHA256:<hex of SHA-256(cert_der)>` |
| iroh (raw Ed25519) | peer `NodeId` | `ed25519:<lowercase hex of 32-byte pub key>` |
When no client cert is presented (the current default — server uses
`with_no_client_auth()`), the fingerprint is `None` and identity remains
unresolved at the endpoint layer. A follow-up task will switch the server
config to request-but-not-require client certs so fingerprints flow for
peers that present them.
## Resolution Flow
### Endpoint-level (before `handle()`)