Add http feature flag with axum, hyper, hyper-util, tower, and http-body-util
dependencies. Create http module with auth middleware (extracts Bearer token,
calls IdentityProvider::resolve_from_token, attaches Identity to extensions)
and router scaffold (default 404 fallback, no operational routes yet). Replace
send_fake_nginx_404 with axum router handoff when http feature is enabled;
fake 404 behavior preserved when http is disabled. Wire HttpInterface with
build_router() method and pass IdentityProvider through Server to handle_connection.
Implement the SSH session to call protocol bridge:
- Add FrameFramedReader/FrameFramedWriter for async I/O of length-prefixed EventEnvelope frames
- SshSession::recv() reads InterfaceEvent frames from the alknet-control:0 channel via mpsc
- SshSession::send() writes EventEnvelope frames to the alknet-control:0 channel via mpsc
- Add ControlChannelBridge implementing ControlChannelHandler for routing channel data
- SshHandler::channel_open_direct_tcpip routes alknet-control:0 to the bridge task
- Session Identity attached to every InterfaceEvent produced by recv()
- ControlChannelRouter gains take_handler() for non-control alknet-* channel routing
Implement RawFramingSession with tokio::io::split for read/write halves,
BufReader/BufWriter for buffered I/O, and decode_with_remainder for
partial frame reassembly. Add first-frame authentication via
IdentityProvider::resolve_from_token(). Add RawFramingConfig.auth
field for IdentityProvider reference. Update SshInterface test for
new RawFramingConfig shape.
Define the outbound authentication abstraction in alknet_core::credentials:
- CredentialProvider trait with get_credentials and refresh_credentials
- CredentialSet enum with ApiKey, Basic, Bearer, S3AccessKey, OidcToken, Custom variants
- ConfigCredentialProvider reads credentials from DynamicConfig.credentials
- SecretStoreCredentialProvider stub returns None for all lookups (Phase 3)
- Wire CredentialProvider into OperationEnv via credentials() method
- Add credentials HashMap field to DynamicConfig
Per ADR-035: split Interface trait into StreamInterface (stream-based, SSH/RawFraming)
and MessageInterface (request/response, HTTP/DNS). Remove TransportKind::Dns (DNS is
a MessageInterface). Change WebTransport { host } to { server_name: Option<String> }.
Restructure ListenerConfig from flat struct to enum with Stream/Http/Dns variants.
Task 1 (stream-interface-message-interface-split):
- Document that TransportKind::Dns EXISTS and must be removed (was incorrectly described as 'never added')
- Document that TransportKind::WebTransport has { host: String } and must be changed to { server_name: Option<String> }
- Document that ListenerConfig is a flat struct, not an enum, and must be restructured per ADR-035
- Move ListenerConfig restructuring, InterfaceConfig rename, and TransportKind cleanup into task 1 to avoid overlap with task 2
- Add HttpListenerConfig/DnsListenerConfig/StreamInterfaceKind/MessageInterfaceKind to task 1 scope
Task 2 (listenconfig-http-dns-stubs):
- Remove work now covered by task 1 (InterfaceConfig rename, TransportKind changes, ListenerConfig enum creation)
- Focus on wiring the new enum form into Server/ServeOptions/StaticConfig, adding constructors, validation, and accept loop stubs
Phase 2 completes the interface-to-protocol bridge and adds core types
that external crates depend on. The 8 tasks are organized into 5
generations with clear dependencies:
- Gen 1: StreamInterface/MessageInterface trait split (must go first)
- Gen 2: SshSession bridge, RawFraming impl, CredentialProvider (parallel)
- Gen 3: API keys in DynamicConfig (depends on CredentialProvider)
- Gen 4: ListenerConfig HTTP/DNS stubs + axum scaffold
- Gen 5: Review gate before Phase 3
Key design decisions:
- 2.4a/2.4b split: SecretStoreCredentialProvider deferred to Phase 3
- API keys (2.6) must land before axum scaffold (2.7)
- ListenerConfig (2.5) must land before axum scaffold (2.7)
- Gen 2 tasks are parallelizable (separate modules)
- 2.1: Add prerequisites note (verify call::frame module, ControlChannelRouter
wiring) before decomposition
- 2.2: Add raw framing auth design decision (first-frame auth event pattern
instead of per-frame auth) — simpler, more secure, matches InterfaceEvent model
- 2.3: Add InterfaceConfig restructuring note, TransportKind::WebTransport
tag addition (missed in Phase 1), note that TransportKind::Dns removal
is a no-op (never added). Add scheduling note: do 2.3 early since
subsequent tasks reference new trait names. Update ADR reference to 035.
- 2.4: Split into 2.4a (trait+enum+ConfigCredentialProvider) and 2.4b
(SecretStoreCredentialProvider, Phase 3). Clarify that the Phase 2 impl
is config-backed, not secret-backed.
- 2.5: Mark TransportKind::Dns removal as no-op since it was never added.
- 4.5: Note that doc sync round 1 is already done (commit cfc4400).
Second sync needed after implementation to capture any deviations.
- Open questions: Mark OQ-IF-01 and OQ-IF-02 as resolved with ADR-035
and ADR-031 references. Update OQ-P2-01 through P2-04 with ADR-036
and resolution status.
The axum router scaffold now only includes auth middleware and stealth
handoff — no operational routes or path conventions. External HTTP path
routing (from_openapi inverse, custom S3/git/OpenAI paths) is deferred
to Phase 5 since it depends on the spec-generation work.
- rustfs-events-select.md: deep dive into rustfs S3 event notification
system (9 target types, 30+ event types, rule engine, queue store)
and S3 Select (DataFusion-based SQL, CSV/JSON/Parquet input)
- honker-reference.md: deep dive into honker SQLite extension for
pub/sub, queue, and notification — core primitives, SQL API,
wake mechanism, single-machine design, and mapping to alknet
storage patterns
- definitions.md: formal term disambiguation for overloaded concepts
(service, interface, token, identity, domain) with cross-domain mapping
tables (alknet ↔ Keystone, distributed git, rustfs) and 8 open questions
- references/rustfs/: research on rustfs S3 store, Keystone/OIDC integration,
and credential mapping to CredentialSet
- references/gitserver/: research on gitserver library architecture and
integration paths as HTTP MessageInterface and SSH adapter
- references/openstack-keystone/: research on Keystone identity concepts
(tokens, scoping, service catalog, RBAC, trust delegation, federation)
and what alknet should adopt vs skip
- references/distributed-identity/: research on decentralized git, smart
contract ACL, on-chain identity, and Radicle comparison
Three research documents for Phase 2 planning:
- credential-provider.md: Outbound auth (CredentialProvider trait, CredentialSet enum),
account model as storage-layer concern (Identity.id as account UUID), SecretStoreCredentialProvider,
ManagedCredentialProvider, self-hosted service auth analysis (rustfs S3/OIDC, gitea OAuth2),
implementation phases A-D.
- interface-model.md: StreamInterface vs MessageInterface trait design, HTTP interface
as axum handler, DNS as MessageInterface, unified auth across all interfaces
(AuthToken + API keys via resolve_from_token), removal of TransportKind::Dns.
- tls-transport.md: Unified multi-interface architecture on port 443. Byte-peek protocol
detection (existing stealth mode) routes SSH vs axum. Axum multiplexes REST, WebSocket,
SSE, gRPC. QUIC/UDP with ALPN routing for WebTransport and iroh P2P. Single AuthToken
mechanism for all non-SSH interfaces. Four primitive operations (call/batch/schema/subscribe)
map to HTTP, MCP, and DNS.
Add doc comments and TODO markers to SshSession::recv() and send()
explicitly marking them as Phase 1 stubs. Notes that call protocol
event bridging from SSH channels is planned for Phase 2/3.
New Phase 1 modules should follow the existing pattern of referencing
ADR numbers in module-level doc comments for discoverability, matching
the style in transport/mod.rs.
ForwardingAction, TargetPattern, ForwardingRule, OperationType,
InterfaceConfig, InterfaceKind, DynamicConfig, and CallError are all
likely to gain variants/fields in future phases. Marking them
#[non_exhaustive] now prevents downstream breakage when new
variants/fields are added. Added constructor methods for types that
are constructed from other crates.
parse_proxy_config was using expect()/unwrap()/panic!() which would
crash the process on malformed proxy config strings instead of
returning a descriptive error. Now returns ConfigError::ProxyConfigInvalid
with the specific issue (bad scheme, bad address). Added tests for
invalid scheme, invalid address, and end-to-end from_serve_options.
NapiServerHandler was bypassing IdentityProvider, calling
config.auth.authenticate_publickey() directly, which meant no Identity
was stored on the session and per-identity forwarding rules could not
match. It also skipped ForwardingPolicy::check() entirely, defeating
forwarding access control for NAPI-served tunnels. Both are now
consistent with ServerHandler and SshHandler behavior.
Review found Phase 1 approved with minor issues. Created cleanup tasks:
- napi-identity-provider-wiring (bug: NAPI bypasses IdentityProvider)
- panic-free-static-config (code smell: panic/unwrap in production path)
- non-exhaustive-public-api (future-proofing public API types)
- adr-doc-comments (doc convention: ADR references in new modules)
- ssh-session-recv-stub-doc (documentation: mark stubs as planned)
- SshInterface implements Interface trait with accept() method
- SshSession implements InterfaceSession trait (stub for call protocol events)
- RawFramingInterface is type-only stub (Phase 4+ for DNS, WebTransport)
- TransportKind consolidated into transport module with Display, PartialEq, Eq
- ListenerConfig gains interface_kind field for (Transport, Interface) pairs
- SshInterface wraps existing russh handler logic (SshHandler)
- Auth delegation through IdentityProvider (not embedded in SshInterface)
- Channel routing through session to Layer 3 (forwarding policy)
- Server accept loop uses (Transport, Interface) pairs
Per ADR-026: SSH is Layer 2, not Layer 1. This is the highest-risk Phase 1
task, implementing the Interface trait to separate transport from interface.
- Add reloadAuth(), reloadForwarding(), reloadAll() methods to AlknetServer
- Add NAPI type definitions: AuthConfigNapi, ForwardingPolicyConfig, ForwardingRuleConfig
- Refactor NapiServerHandler to use ArcSwap<DynamicConfig> for atomic config swaps
- Add ConfigReloadHandle::dynamic_arc() accessor for sharing ArcSwap between NAPI and accept loop
- Add ipnetwork dependency to alknet-napi for TargetPattern CIDR parsing
- Add builder functions for AuthPolicy and ForwardingPolicy from NAPI config types
- All swaps are atomic via ArcSwap per ADR-030
Add Layer 2 interface abstraction per ADR-026:
- Interface trait with accept() and associated Session type
- InterfaceConfig enum with Ssh and RawFraming variants
- SshInterfaceConfig with auth, forwarding, host_key fields
- RawFramingConfig (minimal, no SSH-specific config)
- InterfaceSession trait with recv()/send() producing InterfaceEvent frames
- InterfaceEvent wraps EventEnvelope with optional Identity
- Resolves OQ-IF-01: every session produces EventEnvelope frames
via InterfaceSession, making Layer 3 interface-agnostic
- Valid (Transport, Interface) pair enumeration with
TransportKindBase and is_valid_pair validation function
- Module re-exported from lib.rs
- Change ServerHandler to hold Arc<dyn IdentityProvider> instead of Box<dyn IdentityProvider>
- Refactor Server::new() to use StaticConfig::from_serve_options() producing (StaticConfig, DynamicConfig)
- Remove duplicate parse_proxy_config from serve.rs (now in static_config.rs)
- Add with_identity_provider() accepting Arc<dyn IdentityProvider>
- Add integration tests for DynamicConfig reload and ForwardingPolicy deny
- Add test for custom IdentityProvider injection via with_identity_provider
- Move parse_proxy_config tests to static_config.rs module
Add local dispatch for OperationEnv with invoke() method, EventEnvelope
wire format struct, 4-byte BE length-prefixed frame encoding/decoding,
PendingRequestMap for call/subscribe correlation, call protocol event type
constants, and default /services/list and /services/schema operations.
Add ForwardingPolicy, ForwardingAction, ForwardingRule, and TargetPattern
types in config/forwarding.rs. Implement policy evaluation with first-match
wins semantics, principal and transport matching, CIDR and glob patterns.
Modify ServerHandler to check ForwardingPolicy before proxying in
channel_open_direct_tcpip. Reserved alknet-* destinations bypass policy.
Preserve existing behavior with default allow_all() policy.
Add AuthProtocol enum (VerifyPubkey, VerifyToken, ReloadKeys, CheckAccess),
AuthResult enum (Ok(Identity), Denied(String)), and AuthServiceImpl
wrapping ConfigIdentityProvider via ArcSwap<DynamicConfig>. All gated
behind the irpc feature flag per ADR-028.
- Add ListenerConfig struct with transport_kind, listen_addr, per-transport config
- Add Dns and WebTransport variants to TransportKind (tags only, no behavior)
- Add .listeners() builder method to ServeOptions for multi-listener config
- Keep .transport_mode() backwards compatible (creates single-element listeners vec)
- Update Server::run() to use listeners from Server struct (first listener)
- Add Server::listeners() accessor for multi-transport listener configs
- Update StaticConfig to support listeners field, converted from ServeOptions
- All listeners share Arc<ArcSwap<DynamicConfig>>, ConnectionRateLimiter, and IdentityProvider
- Graceful shutdown terminates accept loop via existing shutdown signal
- TOML [[listeners]] array-of-tables syntax supported via ListenerConfig in StaticConfig
- Add comprehensive tests for ListenerConfig, multi-listener ServeOptions, Server creation