Break down the three initial crates (alknet-vault, alknet-core, alknet-call) into dependency-ordered task files for implementation agents. Structure: - tasks/vault/ (10 tasks) — drift fixes from ADR-025/026 refactor, review, spec sync. Vault is independent and can run fully in parallel with core/call. - tasks/core/ (6 tasks) — crate init, core types, config, auth, endpoint, review. Core is foundational; call depends on it. - tasks/call/ (12 tasks) — split into registry/ and protocol/ topic subdirs reflecting the two subsystems. CallAdapter is the merge point. Key decisions: - Drifts 3+9+10 grouped as one task (key-versioning-rotation) — the complete ADR-021 rotation feature that doesn't compile in pieces - Reviews injected at end of each crate phase (vault, core, call) - Vault spec-sync task removes the drift table and bumps doc status to stable - ACME deferred in core/endpoint (noted as TODO; X509 manual certs for now) - OperationEnv kept as a trait (load-bearing for ADR-024 layering) Validated: 28 tasks, no cycles, 11 generations of parallel work. Critical path runs through call (11 tasks). Vault completes by generation 4. 6 high-risk tasks identified (21%): irpc-removal, endpoint, operation-context, operation-env, call-adapter, abort-cascade.
6.0 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| core/auth | Implement AuthContext, Identity, AuthToken, IdentityProvider trait, and ConfigIdentityProvider | pending |
|
moderate | medium | component | implementation |
Description
Implement the authentication types in src/auth.rs. Auth is hybrid: the
endpoint resolves what it can (TLS-level), handlers resolve what they need
(protocol-level). AuthContext may be partial — handlers complete auth inside
handle().
AuthContext
#[derive(Clone)]
pub struct AuthContext {
pub identity: Option<Identity>,
pub alpn: Vec<u8>,
pub remote_addr: Option<SocketAddr>,
pub tls_client_fingerprint: Option<String>,
}
Created by the endpoint for each incoming connection. Passed to
ProtocolHandler::handle() as an immutable reference.
identity: peer's authenticated identity, if resolved by the endpoint. None means the endpoint has no identity info for this connection.alpn: negotiated ALPN — always present after TLS handshake.remote_addr: peer's address, if available (may be None for iroh).tls_client_fingerprint: SHA-256 fingerprint of TLS client cert, if presented.
AuthContext is Clone (handlers clone for per-stream contexts) and immutable
in handle() (handlers create local variables for resolved identity, they
don't mutate the shared context).
Identity
#[derive(Debug, Clone, PartialEq)]
pub struct Identity {
pub id: String,
pub scopes: Vec<String>,
pub resources: HashMap<String, Vec<String>>,
}
The authenticated peer identity. id is ALPN-agnostic:
- SSH key auth:
"SHA256:abc123..."(key fingerprint) - API key auth:
"alk_test"(key prefix) - Certificate auth:
"username"(principal name)
AuthToken
#[derive(Debug, Clone)]
pub struct AuthToken {
pub raw: Vec<u8>,
}
Opaque authentication token carried in protocol frames. The handler that extracted it knows its encoding.
IdentityProvider trait
pub trait IdentityProvider: Send + Sync + 'static {
fn resolve_from_fingerprint(&self, fingerprint: &str) -> Option<Identity>;
fn resolve_from_token(&self, token: &AuthToken) -> Option<Identity>;
}
resolve_from_fingerprint(): used by endpoint (TLS client cert) and SSH (key fingerprint)resolve_from_token(): used by call protocol (AuthToken in first frame) and HTTP (Bearer header)- Both return
Option<Identity>— None means credential not recognized
ConfigIdentityProvider
pub struct ConfigIdentityProvider {
dynamic: Arc<ArcSwap<DynamicConfig>>,
}
The default implementation. Resolves identities from DynamicConfig (reads
from ArcSwap on every call — hot-reloadable).
Resolution logic:
- Fingerprint: look up in
DynamicConfig::auth::authorized_fingerprints. If found, returnIdentity { id: fingerprint, scopes: ["relay:connect"], resources: {} }. - Token: parse as UTF-8. If starts with
alk_, look up inDynamicConfig::auth::api_keysby prefix match + SHA-256 hash. If found and not expired, returnIdentity { id: prefix, scopes: entry.scopes, resources: entry.resources }.
Changes to DynamicConfig via ConfigReloadHandle are reflected immediately.
Two Identity Scopes
There are two distinct identity scopes that must not be conflated:
| Scope | Where set | Where stored | Represents | Used for |
|---|---|---|---|---|
| Connection-level | Handler in handle() |
Connection (via set_identity) |
Who opened the QUIC connection | Observability, logging |
| Per-request | CallAdapter per call.requested |
OperationContext.identity |
Who makes this specific call | ACL (ADR-015) |
The connection-level identity is stable (set once). The per-request identity is dynamic (resolved per call, potentially different across requests). The per-request identity takes precedence for ACL.
Security constraints
- Token entropy: generated
alk_tokens must have ≥128 bits of entropy. The prefix (first 8 chars) is for O(1) lookup and is not secret — it appears in logs by design. SHA-256 of the full token allows offline verification; this is safe only if the full token is high-entropy. - Config reload must be authenticated: a reload that adds an authorized fingerprint or API key grants access immediately. The reload trigger must be local-only or admin-scoped.
- Connection-level identity is for observability only: per-request identity takes precedence for ACL.
Acceptance Criteria
AuthContextstruct with all 4 fields, derivesCloneIdentitystruct withid,scopes,resources, derivesClone,PartialEqAuthTokenstruct withrawfield, derivesCloneIdentityProvidertrait with both methodsConfigIdentityProviderstruct holdingArc<ArcSwap<DynamicConfig>>ConfigIdentityProvider::resolve_from_fingerprintlooks up in authorized_fingerprintsConfigIdentityProvider::resolve_from_tokenparsesalk_prefix, matches by hash, checks expiry- ConfigIdentityProvider reads from ArcSwap on every call (hot-reloadable)
- Unit test: fingerprint resolution (known fingerprint → Some, unknown → None)
- Unit test: token resolution (valid non-expired → Some, expired → None, unknown → None)
- Unit test: config reload changes resolution results immediately
cargo test -p alknet-coresucceedscargo clippy -p alknet-coresucceeds with no warnings
References
- docs/architecture/crates/core/auth.md — all type definitions, resolution flow
- docs/architecture/decisions/004-auth-as-shared-core.md — ADR-004
- docs/architecture/decisions/011-authcontext-structure.md — ADR-011
Notes
Auth is hybrid: endpoint resolves TLS-level, handler resolves protocol-level. AuthContext may be partial (identity = None). The two identity scopes (connection-level for observability, per-request for ACL) must not be conflated. ConfigIdentityProvider reads from ArcSwap on every call so config reloads take effect immediately.
Summary
To be filled on completion