Files
alknet/docs/architecture/crates/core/config.md
glm-5.1 90d5f4eaf9 docs(architecture): spec alknet-core with per-crate subdocs, ADR-010/011
Add alknet-core architecture specs in docs/architecture/crates/core/ with
focused subdocuments for core types, endpoint, auth, and config. Write
ADR-010 (ALPN Router and Endpoint) defining AlknetEndpoint, HandlerRegistry,
accept loop, and graceful shutdown. Write ADR-011 (AuthContext Structure)
defining AuthContext fields, immutability in handle(), and IdentityProvider
injection pattern. Resolve OQ-04 (static registration), OQ-12 (file paths
only for v1). Add OQ-11 (auth observability). Fix remaining alknet-secret
references to alknet-vault across ADRs 003/004/005/009.
2026-06-16 12:07:17 +00:00

8.0 KiB

status, last_updated
status last_updated
draft 2026-06-16

Configuration

StaticConfig, DynamicConfig, ArcSwap, and ConfigReloadHandle.

StaticConfig

Immutable configuration resolved at startup. Cannot be changed without restarting the endpoint.

pub struct StaticConfig {
    /// Bind address for the QUIC endpoint (e.g., "0.0.0.0:4433").
    pub listen_addr: SocketAddr,

    /// Path to TLS certificate file (PEM).
    /// Required for QUIC+TLS. The endpoint will not start without TLS configuration.
    pub tls_cert: Option<PathBuf>,

    /// Path to TLS private key file (PEM).
    /// Required alongside tls_cert.
    pub tls_key: Option<PathBuf>,

    /// Drain timeout for graceful shutdown (default: 2 seconds).
    pub drain_timeout: Duration,
}

Key differences from reference implementation

The reference StaticConfig (in alknet-main/crates/alknet-core/src/config/static_config.rs) is SSH-centric: it holds host_key, host_key_algorithm, proxy_config, stealth, transport_mode, and listeners. The new model removes all of these:

  • No host_key/host_key_algorithm: SSH host keys are managed by the SSH handler, not by core config. The endpoint uses TLS certs, not SSH host keys.
  • No proxy_config: Outbound proxy is an SSH-specific concern (SOCKS5/HTTP CONNECT forwarding). Not in core config.
  • No stealth: ALPN eliminates the need for stealth/byte-peeking. See ADR-001.
  • No transport_mode/listeners: The old ServeTransportMode and ListenerConfig enum are replaced by a single listen_addr. QUIC+TLS+ALPN replaces multiple listener types. See ADR-010.
  • No iroh_relay: iroh transport is deferred (OQ-05). The v1 endpoint uses quinn directly.

Construction

StaticConfig is constructed by the CLI binary from CLI arguments or a config file. The exact shape of StartupOptions (or whatever the CLI uses) is a CLI concern, not a core concern. alknet-core provides StaticConfig as a data structure; the CLI is responsible for populating it.

// The CLI binary constructs StaticConfig from its own options/config.
// StartupOptions is NOT a core type — it belongs to the alknet CLI binary.
// alknet-core receives a fully populated StaticConfig.
let static_config = StaticConfig {
    listen_addr: "0.0.0.0:4433".parse()?,
    tls_cert: Some("/path/to/cert.pem".into()),
    tls_key: Some("/path/to/key.pem".into()),
    drain_timeout: Duration::from_secs(2),
};

DynamicConfig

Runtime-reloadable configuration. Hot-reloaded via ArcSwap without restarting the endpoint.

#[derive(Debug, Clone)]
pub struct DynamicConfig {
    pub auth: AuthPolicy,
    pub rate_limits: RateLimitConfig,
}

AuthPolicy

Authorization policy derived from authorized keys, certificate authorities, and API keys.

pub struct AuthPolicy {
    /// SHA-256 fingerprints of authorized keys (SSH keys, TLS client certs).
    /// Stored as strings to avoid russh dependency in core.
    pub authorized_fingerprints: HashSet<String>,

    /// Certificate authorities for certificate-based auth.
    /// The exact structure is TBD — it will be defined when alknet-ssh
    /// is implemented. For now, this is a placeholder that reserves
    /// the field. alknet-ssh will define `CertAuthorityEntry` with
    /// the necessary fields (public key, principals, options).
    pub cert_authorities: Vec<CertAuthorityEntry>,

    /// API keys for token-based auth.
    pub api_keys: Vec<ApiKeyEntry>,
}

CertAuthorityEntry is a placeholder type. Its fields will be defined when alknet-ssh is implemented and the certificate authority validation requirements are clear. For v1, cert_authorities will be an empty vector.

This replaces the reference implementation's AuthPolicy which depended on russh::keys::PublicKey. The new version stores fingerprints as strings, not russh types. This removes the russh dependency from alknet-core.

ApiKeyEntry

pub struct ApiKeyEntry {
    /// Key prefix (first 8 chars of the key). Used for O(1) lookup.
    pub prefix: String,

    /// SHA-256 hash of the full key. Used for verification.
    pub hash: String,

    /// Authorization scopes granted by this key.
    pub scopes: Vec<String>,

    /// Human-readable description.
    pub description: String,

    /// Unix timestamp when the key expires. None = never expires.
    pub expires_at: Option<u64>,
}

Carries forward from the reference implementation with no changes.

RateLimitConfig

pub struct RateLimitConfig {
    pub max_connections_per_ip: usize,
    pub max_auth_attempts: usize,
}

Carries forward from the reference implementation. Note: max_connections_per_ip and max_auth_attempts appear in both StaticConfig and RateLimitConfig. The relationship is:

  • StaticConfig does NOT contain rate limit fields. Rate limits are entirely dynamic.
  • RateLimitConfig in DynamicConfig is the authoritative source at runtime.
  • The CLI binary sets initial RateLimitConfig values when creating the initial DynamicConfig.
  • Hot-reloading DynamicConfig via ConfigReloadHandle replaces rate limits immediately — no restart needed.

ArcSwap Pattern

DynamicConfig is wrapped in Arc<ArcSwap<DynamicConfig>> for lock-free reads and atomic swaps.

let dynamic = Arc::new(ArcSwap::new(Arc::new(DynamicConfig::default())));
  • Reads: dynamic.load() returns Arc<DynamicConfig>. Multiple readers can hold references simultaneously without blocking.
  • Writes: dynamic.store(Arc::new(new_config)) atomically replaces the config. All subsequent reads see the new config.
  • No locks: ArcSwap uses atomic operations. No reader is ever blocked by a writer.

This pattern carries forward directly from the reference implementation (alknet-main/crates/alknet-core/src/config/dynamic_config.rs).

ConfigReloadHandle

pub struct ConfigReloadHandle {
    dynamic: Arc<ArcSwap<DynamicConfig>>,
}

impl ConfigReloadHandle {
    pub fn reload(&self, new_config: DynamicConfig);
    pub fn dynamic(&self) -> Arc<DynamicConfig>;
}
  • reload(): Atomically replaces the dynamic config. All subsequent reads (including in-flight IdentityProvider calls) see the new config.
  • dynamic(): Returns the current config as Arc<DynamicConfig>.

The CLI binary creates a ConfigReloadHandle and passes it to a config watcher (file watcher, SIGHUP handler, or call protocol operation) that calls reload() when config changes are detected.

ConfigError

pub enum ConfigError {
    InvalidFlag { name: String },
    KeyFileNotFound { path: String },
    BindFailed(io::Error),
    TlsConfig(io::Error),
    IncompatibleOptions,
}

Simplified from the reference implementation. Removes proxy-specific errors (now an SSH concern) and listener validation errors (no more ListenerConfig enum).

Key Differences from Reference Implementation

Aspect Reference New Model
StaticConfig fields SSH host key, stealth, transport_mode, listeners, proxy listen_addr, TLS cert/key, drain_timeout, rate limits
DynamicConfig.auth HashSet<PublicKey> (russh types) HashSet<String> (fingerprint strings)
ListenerConfig Enum with Stream/Http/Dns variants Eliminated — single endpoint, ALPN dispatch
TransportMode Tcp/Tls/Iroh Eliminated — always QUIC+TLS
Stealth mode Byte-peeking HTTP/SSH detection Eliminated — ALPN handles protocol detection
ForwardingPolicy In DynamicConfig Moved to handler-specific config (SSH)

Design Decisions

Decision ADR Summary
No russh dependency in core ADR-003 Core is ALPN-agnostic; russh is an alknet-ssh dependency
ArcSwap for dynamic config Carry-forward from reference Lock-free reads, atomic swaps
No ListenerConfig ADR-001 Single endpoint, ALPN replaces multiple listener types