Files
alknet/tasks/core/auth-apikey-resources.md
glm-5.2 d149932e2a tasks: decompose review #004 findings into 4 fix tasks + review gate
W1 (call/protocol/abort-cascade-wiring): wire AbortCascade into CallAdapter
handle_stream for EVENT_ABORTED. W2 (core/endpoint-client-fingerprint):
extract TLS client cert fingerprint in dispatch_quinn/dispatch_iroh.
W3 (vault/mnemonic-debug-redaction): replace Mnemonic derive(Debug) with
redacting impl. W4 (core/auth-apikey-resources, level: research): decide
whether ApiKeyEntry should carry resources, then implement or drop from
spec. review-post-impl-fixes gates on all four. Graph: 33 tasks, 12 gens.
2026-06-24 10:02:03 +00:00

117 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: core/auth-apikey-resources
name: Reconcile ApiKeyEntry.resources — add field to type and populate in resolve_api_key, or drop from spec
status: pending
depends_on: []
scope: narrow
risk: low
impact: component
level: research
---
## Description
Three-way mismatch between spec, type, and implementation for
resource-scoped ACLs on API-key-authenticated identities:
- **Spec** (`docs/architecture/crates/core/auth.md:153`):
> "Token: ... return `Identity { id: prefix, scopes: entry.scopes,
> resources: entry.resources }`."
The spec references `entry.resources`.
- **Type** (`crates/alknet-core/src/config.rs:5562`): `ApiKeyEntry` has
fields `prefix, hash, scopes, description, expires_at` — there is no
`resources` field. So `entry.resources` in the spec cannot be
implemented as written.
- **Implementation** (`config.rs:113117`): `resolve_api_key` constructs
the resolved `Identity` with `resources: std::collections::HashMap::new()`
— resources are always empty, regardless of what the API key grants.
The same gap exists in `resolve_identity_from_fingerprint`
(config.rs:6979), which also returns `resources: HashMap::new()`.
### Impact
Latent today: no operation in the workspace uses resource-based ACLs
against a token- or fingerprint-resolved identity. The
`AccessControl::resource_type` / `resource_action` fields exist in
`OperationSpec` (spec.rs:3237) and are tested (spec.rs:284303), but
those tests always hand-construct `Identity.resources` directly —
never via the resolver path. The moment an operation declares a
resource-scoped ACL and a caller authenticates via API key, the ACL
check will fail with "missing resource" even if the key was granted
that resource in config — because `resources` is always empty.
### This is a research/decision task, not an implementation task
The decomposer rule applies: **the architecture is ambiguous** on
whether API keys should grant resource-scoped access. Two valid designs
exist; pick one and document it before implementing. Do not implement
until the decision is made.
**Option A — add `resources` to `ApiKeyEntry`** (matches current spec):
- Add `pub resources: HashMap<String, Vec<String>>` to `ApiKeyEntry`.
- Update `resolve_api_key` to populate `Identity.resources` from
`entry.resources`.
- Update `resolve_identity_from_fingerprint` similarly — either add a
`resources` field to the fingerprint config path, or document that
fingerprint auth grants scopes only (resources empty).
- Update `auth.md`'s token resolution example to match the new field.
- Define the TOML schema for `resources` in `AuthPolicy` (when a TOML
schema is added — currently config is built in code, not parsed).
- Resource-scoped ACLs then work for both auth paths.
**Option B — drop `resources` from the spec for API keys**:
- Remove `entry.resources` from `auth.md:153`.
- Document that API keys grant scopes only; resource-scoped access
requires a different identity source (e.g., a future OAuth/JWT
provider that carries resource claims).
- `Identity.resources` stays in the type (it's used by hand-constructed
identities in tests and by `CompositionAuthority::as_identity` for
internal calls) but token/fingerprint resolvers always return empty.
- Resource-scoped ACLs against token identities return `Forbidden`
this becomes a documented limitation, not a bug.
### Deliverable
Produce a short decision note (a paragraph in `auth.md` under
"Identity Resolution" — or a new ADR if the decision feels
consequential enough) that picks A or B and justifies it. Then either
implement the chosen option in the same task (if small) or split a
follow-up `level: implementation` task gated on this one.
The decision should consider: do any planned operations (in the
upcoming alknet-ssh, alknet-fs, alknet-git crates) need resource-scoped
ACLs on API-key identities? If yes, A. If resource ACLs are only ever
applied to handler-internal composition identities
(`CompositionAuthority`), B is fine and simpler.
## Acceptance Criteria
- [ ] Decision made: Option A or Option B
- [ ] Decision documented in `auth.md` (or a new ADR if consequential)
- [ ] If Option A: `ApiKeyEntry.resources` added, `resolve_api_key` populates `Identity.resources`, `resolve_identity_from_fingerprint` handling decided and documented, `auth.md:153` matches the new shape
- [ ] If Option B: `auth.md:153` corrected to drop `entry.resources`, limitation documented
- [ ] Either way: a test covering the chosen behavior (token resolves with resources, or token resolves with empty resources + documented limitation)
- [ ] `cargo test -p alknet-core` succeeds
- [ ] `cargo clippy -p alknet-core --all-targets` succeeds with no warnings
## References
- docs/reviews/004-post-implementation-sanity-check.md — W4 (full finding)
- docs/architecture/crates/core/auth.md:152153 — spec text referencing `entry.resources`
- crates/alknet-core/src/config.rs:5562 — `ApiKeyEntry` (missing `resources`)
- crates/alknet-core/src/config.rs:69118 — both resolvers returning empty `resources`
- crates/alknet-call/src/registry/spec.rs:77103 — `AccessControl::check` resource path (the consumer that would fail)
- crates/alknet-call/src/registry/context.rs:5865 — `CompositionAuthority::as_identity` (the internal-call path that does populate `resources`)
## Notes
> This is a `level: research` task because the fix is small but the
> decision is not. The decomposer principle: if architecture is
> ambiguous, do not proceed with implementation — escalate. Make the
> decision first, then implement. If the decision is A and the
> implementation is more than ~30 lines, split a follow-up
> `level: implementation` task (`core/auth-apikey-resources-impl`)
> depending on this one.