feat(core): implement StaticConfig/DynamicConfig split with ArcSwap hot-reload

Split alknet-core configuration into StaticConfig (immutable after startup)
and DynamicConfig (hot-reloadable at runtime via ArcSwap).

- Add StaticConfig struct in config/static_config.rs with all fields per ADR-030
- Add DynamicConfig struct with AuthPolicy, ForwardingPolicy, RateLimitConfig
- Add ForwardingPolicy with allow_all()/deny_all() defaults (ADR-031)
- Add ConfigReloadHandle with reload() method for runtime config updates
- Replace Arc<ServerAuthConfig> with Arc<ArcSwap<DynamicConfig>> in ServerHandler
- Add config_reload_handle() to Server for obtaining reload handles
- Add AuthPolicy with authenticate_publickey/authenticate_certificate methods
- All existing tests pass with the new config structure
- Default DynamicConfig produces identical behavior to current code
This commit is contained in:
2026-06-07 14:03:46 +00:00
parent a7f0dcdeb9
commit ee1b3f3819
36 changed files with 964 additions and 393 deletions

View File

@@ -328,7 +328,12 @@ impl russh::server::Handler for NapiServerHandler {
session: &mut russh::server::Session,
) -> std::result::Result<(), Self::Error> {
tracing::warn!(channel = %channel, "rejected x11 request");
let _ = (single_connection, x11_auth_protocol, x11_auth_cookie, x11_screen_number);
let _ = (
single_connection,
x11_auth_protocol,
x11_auth_cookie,
x11_screen_number,
);
let _ = session.channel_failure(channel);
Ok(())
}
@@ -348,7 +353,11 @@ impl russh::server::Handler for NapiServerHandler {
port: &mut u32,
_session: &mut russh::server::Session,
) -> std::result::Result<bool, Self::Error> {
tracing::warn!(address = address, port = *port, "rejected tcpip-forward request");
tracing::warn!(
address = address,
port = *port,
"rejected tcpip-forward request"
);
Ok(false)
}
@@ -367,7 +376,10 @@ impl russh::server::Handler for NapiServerHandler {
socket_path: &str,
_session: &mut russh::server::Session,
) -> std::result::Result<bool, Self::Error> {
tracing::warn!(socket_path = socket_path, "rejected streamlocal-forward request");
tracing::warn!(
socket_path = socket_path,
"rejected streamlocal-forward request"
);
Ok(false)
}
@@ -542,8 +554,8 @@ pub async fn serve(options: AlknetServeOptions) -> napi::Result<AlknetServer> {
})?,
);
let private_key =
alknet_core::auth::keys::load_private_key(host_key_source.clone()).map_err(|e| {
let private_key = alknet_core::auth::keys::load_private_key(host_key_source.clone())
.map_err(|e| {
napi::Error::new(napi::Status::InvalidArg, format!("host key error: {}", e))
})?;
@@ -635,26 +647,28 @@ pub async fn serve(options: AlknetServeOptions) -> napi::Result<AlknetServer> {
)
})?;
let acceptor = TlsAcceptor::bind(addr, certs, key, None).await.map_err(|e| {
napi::Error::new(
napi::Status::GenericFailure,
format!("tls bind failed: {}", e),
)
})?;
let acceptor = TlsAcceptor::bind(addr, certs, key, None)
.await
.map_err(|e| {
napi::Error::new(
napi::Status::GenericFailure,
format!("tls bind failed: {}", e),
)
})?;
let actual_listen = acceptor.listen_addr().to_string();
let auth_config = Arc::new(
ServerAuthConfig::from_keys_and_ca(authorized_keys_source, cert_authority_source)
.map_err(|e| {
napi::Error::new(
napi::Status::InvalidArg,
format!("auth config error: {}", e),
)
})?,
napi::Error::new(
napi::Status::InvalidArg,
format!("auth config error: {}", e),
)
})?,
);
let private_key =
alknet_core::auth::keys::load_private_key(host_key_source.clone()).map_err(|e| {
let private_key = alknet_core::auth::keys::load_private_key(host_key_source.clone())
.map_err(|e| {
napi::Error::new(napi::Status::InvalidArg, format!("host key error: {}", e))
})?;
@@ -728,11 +742,11 @@ pub async fn serve(options: AlknetServeOptions) -> napi::Result<AlknetServer> {
let auth_config = Arc::new(
ServerAuthConfig::from_keys_and_ca(authorized_keys_source, cert_authority_source)
.map_err(|e| {
napi::Error::new(
napi::Status::InvalidArg,
format!("auth config error: {}", e),
)
})?,
napi::Error::new(
napi::Status::InvalidArg,
format!("auth config error: {}", e),
)
})?,
);
let private_key =