Files
alknet/docs/architecture/decisions/008-secret-service-integration.md
glm-5.2 7dda6eec68 docs(architecture): add ADR-025 — vault local-only dispatch, drop irpc
Drops irpc from alknet-vault entirely. The vault's dispatch is now direct
method calls on VaultServiceHandle — no VaultProtocol enum, no
VaultMessage, no VaultServiceActor, no mpsc channel, no Service trait, no
RemoteService trait, no postcard serialization. The vault is local-only by
construction.

The core security argument: irpc made the vault remote-capable by default
(RemoteService generated unless no_rpc is passed). The IrohProtocol handler
forwards all messages without auth. The docs framed 'register an ALPN' as a
server-setup change. This is the default-insecure anti-pattern — security
should be opt-in, not opt-out. ADR-025 inverts the default: local-only is
the only mode, and remote access requires building a separate vault-server
crate (a visible architectural act, not a flag flip).

The actor path was already dead code — service.md said 'prefer
VaultServiceHandle directly — no channel, no serialization.' The actor
existed only to make irpc's Service trait work, which existed only to make
RemoteService work, which was the footgun. VaultServiceHandle's
Arc<RwLock> provides concurrent reads and exclusive writes — better
throughput than the actor's sequential processing.

DerivedKey serialization simplifies: always redact on serialize (for
logging safety), reject '[REDACTED]' on deserialize with an error. No
'postcard preserves bytes' path. This resolves review #002 W8 (silent
corruption on JSON-deserialized DerivedKey).

Resolves:
- OQ-21: remote vault access — resolved (not deferred). Not a vault crate
  feature; if needed, a separate vault-server crate with its own ADR.
- C7: vault-server-crate question decided — not created now, not precluded.
- C8: operation access policy table dissolved — all operations local-only
  by default; if a vault-server crate exposes some remotely, that crate
  defines the policy.
- W8: DerivedKey JSON deserialization — resolved (reject redacted payloads).

Amends ADR-005 (irpc remains for alknet-call, not for alknet-vault),
ADR-018 (vault is even more standalone — zero RPC framework deps),
ADR-019 (vault is the only layer, not just the only direct-caller layer),
ADR-008 (vault integration point unchanged, but now local-only by
construction).
2026-06-22 14:53:52 +00:00

72 lines
5.7 KiB
Markdown

# ADR-008: Vault Integration Point
## Status
Accepted
## Context
alknet-vault (formerly alknet-secret) is a standalone crate with zero alknet crate dependencies and zero RPC framework dependencies (ADR-025). It provides BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption, and a direct-method-call API (`VaultServiceHandle`). It is already implemented and stable, pending the ADR-025 refactor to drop irpc.
The question (OQ-08) was: how does the rest of the alknet system access alknet-vault's capabilities? The options were:
1. **Call protocol exposure**: Other services call vault operations through the call protocol.
2. **ALPN handler on `alknet/secret`**: alknet-vault implements ProtocolHandler and gets its own ALPN.
3. **Direct library dependency**: alknet-core or handler crates depend on alknet-vault directly, breaking its independence.
4. **CLI-embedded with call protocol exposure**: The CLI binary instantiates VaultServiceHandle locally and registers vault operations in the call protocol's registry.
This is a one-way door because if alknet-vault gets pulled into alknet-core as a dependency, its independence is permanently lost. The standalone property is valuable — alknet-vault has no QUIC, no tokio runtime requirement (the handle works without it), and no alknet crate dependencies. It can be used in contexts where QUIC networking doesn't exist (CLI tools, test harnesses, WASM key derivation).
Beyond the integration point, there's a question of access patterns. The vault holds the master seed and can derive keys and encrypt/decrypt arbitrary data. This is used for:
- Identity key derivation (SSH host keys, node identity)
- Provider API key storage (Vast.ai, LLM providers) — encrypted at rest, decrypted on demand
- Credential encryption for storage
- Multi-tenant key derivation (different derivation paths per tenant)
The vault is a capability source, not a service endpoint. Operations that need provider keys don't hold a reference to the vault — they receive the derived/decrypted material through their operation context. The vault is unlocked at startup by the CLI, and the CLI injects material into operation contexts as needed.
## Decision
**Option 4: CLI-embedded, assembly-layer only.**
The CLI binary (the `alknet` crate) is the integration point. It:
1. Instantiates `VaultServiceHandle` locally at startup (or on-demand with Unlock/Lock lifecycle).
2. Derives and decrypts the credentials each handler needs.
3. Injects those credentials into handler capabilities at construction time.
4. Other handlers access vault-derived material through their `OperationContext.capabilities` — they don't import alknet-vault directly and don't call vault operations over the wire.
**alknet-vault does NOT get its own ALPN.** Key derivation is a local operation — the master seed never crosses the network. If a remote node needs derived public keys (e.g., for identity verification), they're shared through the call protocol, not through direct vault access.
**The vault is accessed at the assembly layer, not by individual handlers.** The CLI (or a configuration middleware it sets up) is the only component that talks to the vault directly. Derived keys and decrypted credentials are injected into operation contexts — handlers receive the material they need, not a vault reference.
**No vault operations are registered in the call protocol.** The vault is not exposed over the wire. This is the mechanism this ADR described in prose ("derived keys and decrypted credentials are injected into operation contexts at the assembly layer"); ADR-014 specifies it as a one-way door with explicit constraints. See ADR-014.
This is analogous to the reverse-proxy admin key pattern (ADR-028 in the reverse-proxy project): the proxy reads the key file once at startup, hashes it, and individual handlers never see the file. Here, the CLI unlocks the vault once at startup, and individual handlers receive the results of vault operations through their contexts.
## Consequences
**Positive:**
- alknet-vault remains fully standalone — no QUIC dependency, no tokio runtime requirement for the handle
- Key derivation and encryption are local-only by default — the master seed never leaves the node
- Remote access to public key material (not secrets) flows through the existing call protocol — no separate ALPN needed
- The CLI binary is the single integration point — clean dependency graph, no circular dependencies
- The `VaultServiceHandle` is used in-process with zero serialization overhead — direct method calls, not irpc messages
- Test harnesses can use `VaultServiceHandle` directly without any QUIC infrastructure
- Access pattern is clear: vault → CLI → operation contexts, not vault ← handlers
**Negative:**
- Handlers that need keys must receive them through their operation context — this requires the CLI or call protocol to mediate
- The CLI binary has a larger dependency tree since it imports both alknet-call and alknet-vault (expected: the CLI assembles everything)
- If the call protocol is not yet running when a handler needs a key, the handler must wait for initialization (mitigated: the CLI starts VaultServiceHandle before accepting connections)
## References
- ADR-003: Crate decomposition (alknet-vault is standalone)
- ADR-005: irpc as call protocol foundation (for alknet-call; the vault no longer uses irpc — see ADR-025)
- ADR-009: One-way door decision framework
- ADR-014: Secret material flow and capability injection (specifies the mechanism this ADR described in prose)
- ADR-025: Vault local-only dispatch (dropped irpc from the vault; direct method calls only)
- OQ-08: Secret service integration point (resolved by this ADR, refined by ADR-014)
- alknet-vault implementation: `crates/alknet-vault/`