Phase 0a — ADRs (9 new): - ADR-026: Transport/interface separation (three-layer model) - ADR-027: Crate decomposition (core, secret, storage, flowgraph, napi, CLI) - ADR-028: Auth as irpc service (AuthProtocol behind feature flag) - ADR-029: Identity as core type (Identity + IdentityProvider in alknet-core) - ADR-030: Static/dynamic config split (ArcSwap, ConfigReloadHandle) - ADR-031: Forwarding policy (rule-based allow/deny, TransportKind-aware) - ADR-032: Event boundary discipline (domain, irpc, call protocol boundaries) - ADR-033: OperationEnv universal composition (three dispatch paths) - ADR-034: Head/worker terminology (replace hub/spoke) Phase 0b — New spec documents (7): - identity.md, services.md, interface.md, configuration.md, storage.md, flowgraph.md, secret-service.md Updated existing docs: - auth.md: reference identity.md for canonical definitions, add AuthProtocol - open-questions.md: resolve OQ-12, OQ-16, OQ-18, OQ-22, OQ-23-25 - README.md: add all new docs, ADRs 026-034 Marked 19 architecture tasks as completed.
6.0 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-06-07 |
Configuration
What
Alknet's configuration is split into StaticConfig (immutable after startup) and
DynamicConfig (hot-reloadable at runtime), with ArcSwap providing lock-free
reads on the hot path. ConfigService wraps reloads behind an irpc protocol
for production deployments.
Why
Three specific failures motivated the split (ADR-030):
- No hot reload of authentication credentials — adding a key requires a restart.
- No port forwarding access control — any authenticated client has unrestricted access (ADR-031).
- No structured configuration beyond CLI flags — operators need config files and the NAPI layer needs programmatic reload.
The split is clean: anything that affects SSH handshake or socket binding is static; anything checked per-connection or per-channel is dynamic.
Architecture
StaticConfig
Immutable after startup. Constructed from ServeOptions (the builder pattern
is preserved per ADR-011). Contains:
- Transport mode, listen address
- TLS config (cert, key)
- iroh config (relay URL)
- Stealth mode flag
- Host key, host key algorithm
- Max auth attempts, max connections per IP
- Proxy config
Changing any of these requires a restart.
DynamicConfig
Hot-reloadable at runtime via ArcSwap<DynamicConfig>. Contains:
AuthPolicy— authorized keys, certificate authorities, token configForwardingPolicy— allow/deny rules for channel targets (ADR-031)RateLimitConfig— rate limiting parameters
ArcSwap provides lock-free reads. Every auth_publickey() and
channel_open_direct_tcpip() call does a single Arc dereference — zero cost
compared to the current approach. Writes are atomic: store() swaps the
pointer.
ConfigReloadHandle
pub struct ConfigReloadHandle {
dynamic: Arc<ArcSwap<DynamicConfig>>,
}
impl ConfigReloadHandle {
pub fn reload(&self, new_config: DynamicConfig) { ... }
}
Obtained from Server::run(). Passed to NAPI or CLI for explicit reload.
ConfigService irpc Service
enum ConfigProtocol {
GetForwardingPolicy,
GetRateLimits,
ReloadForwarding { policy: ForwardingPolicy },
ReloadRateLimits { limits: RateLimitConfig },
}
Behind the irpc feature flag. For production deployments that use the service
layer. For minimal deployments, direct ConfigReloadHandle::reload() is
sufficient.
ForwardingPolicy
Part of DynamicConfig (ADR-031). Evaluated per-channel-open, matched against
the authenticated Identity. Rules are evaluated in order; first match wins.
Default determines fallback.
pub struct ForwardingPolicy {
pub default: ForwardingAction,
pub rules: Vec<ForwardingRule>,
}
TOML Config File
Optional convenience input format (amends ADR-011, does not replace programmatic API). Covers static config plus initial auth/forwarding paths.
[server]
transport = "tls"
listen = "0.0.0.0:443"
[auth]
host_key = "/etc/alknet/ssh/host_key"
[forwarding]
default = "deny"
[[forwarding.rules]]
target = "localhost:*"
action = "allow"
NAPI Reload API
interface AlknetServer {
reloadAuth(auth: { authorizedKeys?: Buffer, certAuthority?: Buffer }): void;
reloadForwarding(policy: ForwardingPolicyConfig): void;
reloadAll(config: DynamicConfig): void;
}
Multi-Transport Listeners
A head node may accept connections on multiple transports simultaneously. The
architecture supports Vec<ListenerConfig> instead of a single
ServeTransportMode. Server::run() spawns one accept loop per listener,
sharing DynamicConfig, ConnectionRateLimiter, sessions, and shutdown signal.
[[listeners]]
transport = "tls"
listen = "0.0.0.0:443"
stealth = true
[[listeners]]
transport = "tcp"
listen = "0.0.0.0:22"
[[listeners]]
transport = "iroh"
iroh_relay = "https://relay.alk.dev"
CLI vs Programmatic Behavior
| Interface | Static config | Dynamic config | Reload mechanism |
|---|---|---|---|
| CLI | Flags + optional --config file |
Loaded at startup from --authorized-keys |
None (restart to change) |
| Core Rust | StaticConfig struct |
AuthService (irpc) or ArcSwap<DynamicConfig> (minimal) |
ConfigService::reload() or ConfigReloadHandle::reload() |
| NAPI | serve() options |
Same | server.reloadAuth(), server.reloadForwarding() |
Constraints
StaticConfigcannot be changed after startup. Changing transport mode, listen address, TLS config, or host key requires a restart.DynamicConfigis reloaded atomically viaArcSwap. Existing connections continue with their current config; new connections get the new config.- Config file is optional.
ServeOptionsbuilder pattern remains the primary API (amends ADR-011, does not supersede it). - No file watching (OQ-13 resolved: potential attack vector, unnecessary complexity).
- Client configuration stays as
ConnectOptions— noArcSwapneeded.
Open Questions
- None. All configuration-related questions are resolved per ADR-030, ADR-031, and the resolved OQs in open-questions.md.
Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| 030 | Static/dynamic config split | Immutable transport vs. reloadable auth/forwarding |
| 011 | Programmatic-first API | Amended, not superseded — TOML is convenience layer |
| 031 | Forwarding policy | Rule-based allow/deny, TransportKind-aware |
| 029 | Identity as core type | DynamicConfig.auth consumed by IdentityProvider |
| 028 | Auth as irpc service | ConfigService wraps DynamicConfig reloads |
References
- research/configuration.md — Full analysis and proposed solution
- identity.md — IdentityProvider trait, DynamicConfig.auth
- ADR-013 — Rate limiting parameters