Same treatment for the prose the compiler carries: doc and line comments across src/ and tests/, test assertion and error-message strings (prose surfaces: the tunnel-map registration/establish errors, the key-source decode payload, assertion messages), scripts/matrix.sh comment lines, the fuzz sidecar's comments and README (corpus seeds untouched), and the Cargo.toml comment headers plus the crates.io description. The AGENTS.md no-em-dash rule (convention 14) now holds for the whole tracked tree. Verification: repo-wide git grep is clean (single exempt hit: the verification command's own search pattern), cargo test (18 suites, zero failures, default and per-feature builds via scripts/matrix.sh), clippy --all-targets -D warnings and clippy all-features, fmt --check, doc --no-deps, and the fuzz corpus gate (fuzz/shared, 11 tests) all green.
812 lines
32 KiB
Rust
812 lines
32 KiB
Rust
//! The wave-2 dispatch/decline-core contract tests
|
|
//! (`w2-dispatch-decline-core`): the skeleton rows of the decline
|
|
//! matrix (`mapping.md` §Decline matrix), driven per feature build
|
|
//! through the loopback harness. This task tests the rows it lands;
|
|
//! the full per-build matrix suite is wave-5's.
|
|
//!
|
|
//! Feature builds are compile-time, so the rows are feature-gated to
|
|
//! the build whose matrix row they pin: the shared rows
|
|
//! (never-feature `x11`, the `tcpip-forward` listen decline) run in
|
|
//! every build; the capability rows run in the builds that open their
|
|
//! subject (a session channel needs `session` or `git`); the
|
|
//! per-build matrix rows are pinned to exactly one feature combination
|
|
//! (`not(...)` guards) so `--all-features` runs neither a
|
|
//! git-only row nor a session-only row by accident. Every feature
|
|
//! build is exercised by its own verification run
|
|
//! (`cargo test [--features …]`), not by cfg tricks.
|
|
|
|
#![allow(dead_code)]
|
|
|
|
mod common;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use common::harness::{
|
|
LoopbackSession, adapter, authenticate_publickey, await_close, cancel_streamlocal_forward,
|
|
cancel_tcpip_forward, drive, next_message, open_session_channel, open_x11_channel,
|
|
streamlocal_forward, tcpip_forward,
|
|
};
|
|
use common::{MemHostKeyPolicy, MemIdentityProvider, fixture, identity};
|
|
use russh::client::Msg;
|
|
use russh::{Channel, ChannelOpenFailure, Error};
|
|
|
|
use alkssh::Limits;
|
|
|
|
/// Mounts an authenticated loopback session over the doubles, the
|
|
/// dispatch rows' driver: fixture `alpha` resolves through
|
|
/// `MemIdentityProvider`, host keys `bravo`, the permissive host-key
|
|
/// posture (the explicit test choice).
|
|
async fn authenticated() -> LoopbackSession {
|
|
let provider = Arc::new(MemIdentityProvider::new());
|
|
provider.register(
|
|
fixture("alpha").fingerprint,
|
|
identity("dispatch-core", &["ssh"]),
|
|
);
|
|
let adapter = adapter(provider, &[fixture("bravo")]).await;
|
|
|
|
let policy = Arc::new(MemHostKeyPolicy::with_default(true));
|
|
let mut session = drive(adapter, Arc::clone(&policy), Limits::default())
|
|
.await
|
|
.expect("the loopback mounts");
|
|
|
|
let result =
|
|
authenticate_publickey(&mut session.client, "dispatch-core", fixture("alpha")).await;
|
|
assert!(
|
|
result.success(),
|
|
"the fixture identity authenticates before every dispatch row"
|
|
);
|
|
session
|
|
}
|
|
|
|
/// Matches an open failure's SSH reason, the wire decline reason the
|
|
/// open-gate rows assert.
|
|
fn open_failure(err: Error, expected: ChannelOpenFailure, context: &str) {
|
|
assert!(
|
|
matches!(
|
|
&err,
|
|
Error::ChannelOpenFailure(reason) if *reason == expected
|
|
),
|
|
"{context}: expected open decline with {expected:?}, got {err:?}"
|
|
);
|
|
}
|
|
|
|
/// The request-level decline observation: the channel answers Failure.
|
|
/// A capability decline's channel stays open afterwards, the POC-1 §5
|
|
/// session-survives-declines posture the channel-alive rows lean on.
|
|
async fn expect_failure(channel: &mut Channel<Msg>, context: &str) {
|
|
match next_message(channel).await {
|
|
Some(russh::ChannelMsg::Failure) => {}
|
|
other => panic!("{context}: expected a channel failure, got {other:?}"),
|
|
}
|
|
}
|
|
|
|
/// The serving-path (family-seam stub) decline observation: Failure
|
|
/// then Close, the vanilla-clean shape for a row whose serving path
|
|
/// would own the channel.
|
|
async fn expect_failure_then_close(channel: &mut Channel<Msg>, context: &str) {
|
|
expect_failure(channel, context).await;
|
|
assert!(
|
|
await_close(channel).await,
|
|
"{context}: the seam stub closes the channel after the failure"
|
|
);
|
|
}
|
|
|
|
/// Proves a declined channel is still usable: a follow-up request
|
|
/// reaches the dispatch again instead of the channel being dead.
|
|
async fn expect_channel_alive(channel: &mut Channel<Msg>) {
|
|
common::harness::request_subsystem(channel, "sftp")
|
|
.await
|
|
.expect("the still-open channel carries another request");
|
|
expect_failure(channel, "the surviving channel still dispatches").await;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Shared rows; run in every feature build.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// - *Contract stamp:* ADR-003 §5 (x11 is a never-feature, declined
|
|
/// in every build, never feature-gated).
|
|
#[tokio::test]
|
|
async fn x11_channel_opens_decline_in_every_build() {
|
|
let session = authenticated().await;
|
|
let err = open_x11_channel(&session.client, "127.0.0.1", 0)
|
|
.await
|
|
.expect_err("x11 opens decline");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"the never-feature x11 open",
|
|
);
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Global-request rows (the `-R`
|
|
/// listen flow is a `tunnel` row declined when the flow cannot
|
|
/// serve; this wave: every build).
|
|
#[tokio::test]
|
|
async fn tcpip_forward_listen_requests_decline_in_every_build() {
|
|
let session = authenticated().await;
|
|
let err = tcpip_forward(&session.client, "127.0.0.1", 8080)
|
|
.await
|
|
.expect_err("the listen request declines");
|
|
assert!(
|
|
matches!(err, Error::RequestDenied),
|
|
"expected the declined listen's RequestDenied, got {err:?}"
|
|
);
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Global-request rows (the
|
|
/// `streamlocal-forward` unix listen form is the `tunnel` row's
|
|
/// other half: unfeatured builds decline before any fabric call,
|
|
/// a `tunnel` build declines on the unset listen seam; the same
|
|
/// refused-global-request wire shape both ways).
|
|
#[tokio::test]
|
|
async fn streamlocal_forward_listen_requests_decline_in_every_build() {
|
|
let session = authenticated().await;
|
|
let err = streamlocal_forward(&session.client, "/run/alkssh-matrix.sock")
|
|
.await
|
|
.expect_err("the unix listen request declines");
|
|
assert!(
|
|
matches!(err, Error::RequestDenied),
|
|
"expected the declined unix listen's RequestDenied, got {err:?}"
|
|
);
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Global-request rows (the cancel
|
|
/// forms: `false` for an unknown pair (the sshd not-found parity);
|
|
/// unfeatured builds decline their gate before reaching the
|
|
/// listener map, a `tunnel` build's empty map misses the pair; the
|
|
/// same refused-cancel wire shape both ways).
|
|
#[tokio::test]
|
|
async fn global_request_cancels_refuse_unknown_pairs_in_every_build() {
|
|
let session = authenticated().await;
|
|
let denied = cancel_tcpip_forward(&session.client, "127.0.0.1", 8080)
|
|
.await
|
|
.expect_err("the tcpip cancel of a no-listen pair refuses");
|
|
assert!(
|
|
matches!(denied, Error::RequestDenied),
|
|
"expected the refused cancel's RequestDenied, got {denied:?}"
|
|
);
|
|
let denied = cancel_streamlocal_forward(&session.client, "/run/alkssh-matrix.sock")
|
|
.await
|
|
.expect_err("the streamlocal cancel of a no-listen pair refuses");
|
|
assert!(
|
|
matches!(denied, Error::RequestDenied),
|
|
"expected the refused cancel's RequestDenied, got {denied:?}"
|
|
);
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Decline matrix (git-build row,
|
|
/// request side: `x11` / `auth-agent` requests decline; ADR-003 §5).
|
|
#[cfg(any(feature = "git", feature = "session"))]
|
|
#[tokio::test]
|
|
async fn x11_and_agent_requests_decline_wherever_sessions_open() {
|
|
use common::harness::{request_agent_forward, request_x11};
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens where a session-serving feature exists");
|
|
|
|
request_x11(&channel)
|
|
.await
|
|
.expect("the x11-req rides the wire");
|
|
expect_failure(&mut channel, "the never-feature x11 request declines").await;
|
|
|
|
request_agent_forward(&channel)
|
|
.await
|
|
.expect("the agent-forward request rides the wire");
|
|
expect_failure(&mut channel, "the never-feature agent request declines").await;
|
|
|
|
expect_channel_alive(&mut channel).await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Default build (no features): everything declines (mapping.md
|
|
// §Decline matrix, "none (default): declines everything").
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(not(any(feature = "session", feature = "git", feature = "tunnel")))]
|
|
mod default_build {
|
|
use super::*;
|
|
use common::harness::{open_direct_streamlocal, open_direct_tcpip};
|
|
|
|
/// - *Contract stamp:* mapping.md §Decline matrix (none-build
|
|
/// row: every channel type declines at open (session channels
|
|
/// as unfeatured, tunnel rows as unfeatured) plus the
|
|
/// never-feature x11).
|
|
#[tokio::test]
|
|
async fn every_channel_open_declines_in_a_default_build() {
|
|
let session = authenticated().await;
|
|
|
|
let err = open_session_channel(&session.client)
|
|
.await
|
|
.expect_err("session channels decline unfeatured");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"the unfeatured session open",
|
|
);
|
|
|
|
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
|
|
.await
|
|
.expect_err("direct-tcpip declines unfeatured");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"the unfeatured direct-tcpip open",
|
|
);
|
|
|
|
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
|
|
.await
|
|
.expect_err("direct-streamlocal declines unfeatured");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"the unfeatured direct-streamlocal open",
|
|
);
|
|
|
|
session.unmount().await;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// git-only build: routes git-parseable exec to the git row; declines
|
|
// pty/shell, plain exec, all other opens (mapping.md §Decline matrix,
|
|
// git row). The harness builds no seam, so the git row's decline is
|
|
// the config-driven one (`w3-git-mapping`: seam-unconfigured, the
|
|
// unset-declines contract, serving-path shape); the serving rows ride
|
|
// `tests/git_mapping.rs`.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(all(feature = "git", not(feature = "session"), not(feature = "tunnel")))]
|
|
mod git_build {
|
|
use super::*;
|
|
use common::harness::{
|
|
open_direct_streamlocal, open_direct_tcpip, request_env, request_exec, request_pty,
|
|
request_shell, request_subsystem,
|
|
};
|
|
|
|
/// - *Contract stamp:* the task's channel-open gate (session
|
|
/// channels open where a session-serving feature exists; `git`
|
|
/// qualifies, the parse path lives inside session channels).
|
|
#[tokio::test]
|
|
async fn session_channels_open_in_a_git_build() {
|
|
let session = authenticated().await;
|
|
open_session_channel(&session.client)
|
|
.await
|
|
.expect("session channels open under the git feature");
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* ADR-006 point 3 (parse-only: a
|
|
/// git-parseable exec routes to the git row; this build
|
|
/// configures no seam, so the row declines at its config gate,
|
|
/// the serving-path shape: failure then close). The row's
|
|
/// serving surface (seam configured, real dispatch) is
|
|
/// `tests/git_mapping.rs`'s.
|
|
#[tokio::test]
|
|
async fn git_parseable_exec_routes_to_the_git_row_stub() {
|
|
let session = authenticated().await;
|
|
for command in [
|
|
"git-upload-pack '/owner/demo'",
|
|
"git-receive-pack '/owner/demo'",
|
|
] {
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_exec(&channel, command)
|
|
.await
|
|
.expect("the exec request rides the wire");
|
|
expect_failure_then_close(
|
|
&mut channel,
|
|
&format!("the git row serves its seam stub for {command}"),
|
|
)
|
|
.await;
|
|
}
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Decline matrix (git-build row:
|
|
/// plain exec declines; the parse-then-fail shape of POC-3
|
|
/// stage A; the channel survives the decline).
|
|
#[tokio::test]
|
|
async fn plain_exec_declines_and_keeps_the_channel_in_a_git_build() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_exec(&channel, "uptime")
|
|
.await
|
|
.expect("the exec request rides the wire");
|
|
expect_failure(&mut channel, "the unfeatured pipe row declines").await;
|
|
expect_channel_alive(&mut channel).await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* POC-3 §2 (unknown git services take the
|
|
/// agreed declined surface (`w3-git-mapping`'s decided class,
|
|
/// off the pipe row: `ExecRoute::Declined`, build-independent;
|
|
/// the reply vocabulary is shared with parse-then-fail: failure,
|
|
/// channel alive).
|
|
#[tokio::test]
|
|
async fn unknown_git_services_take_the_declined_surface() {
|
|
let session = authenticated().await;
|
|
for command in ["git-send-pack '/r'", "git-upload-archive '/r'"] {
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_exec(&channel, command)
|
|
.await
|
|
.expect("the exec request rides the wire");
|
|
expect_failure(
|
|
&mut channel,
|
|
&format!("an unknown service does not reach the git row: {command}"),
|
|
)
|
|
.await;
|
|
expect_channel_alive(&mut channel).await;
|
|
}
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Decline matrix (git-build row:
|
|
/// pty/shell and all session requests decline; the pty row
|
|
/// declines unfeatured (`channel_failure`, the channel alive)
|
|
/// exactly as the shell row does).
|
|
#[tokio::test]
|
|
async fn pty_shell_and_capability_requests_decline_in_a_git_build() {
|
|
let session = authenticated().await;
|
|
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_pty(&channel)
|
|
.await
|
|
.expect("the pty request rides the wire");
|
|
expect_failure(&mut channel, "the pty row declines unfeatured").await;
|
|
expect_channel_alive(&mut channel).await;
|
|
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_shell(&channel)
|
|
.await
|
|
.expect("the shell request rides the wire");
|
|
expect_failure(&mut channel, "the shell row declines unfeatured").await;
|
|
expect_channel_alive(&mut channel).await;
|
|
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Request-level rows (subsystem:
|
|
/// `sftp` is the OQ-13 placeholder decline; git service names
|
|
/// carry no repository id; ADR-006 decision 2).
|
|
#[tokio::test]
|
|
async fn subsystems_decline_with_their_named_reasons_in_a_git_build() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
for name in ["sftp", "git-upload-pack"] {
|
|
request_subsystem(&channel, name)
|
|
.await
|
|
.expect("the subsystem request rides the wire");
|
|
expect_failure(
|
|
&mut channel,
|
|
&format!("the subsystem {name} declines with reason"),
|
|
)
|
|
.await;
|
|
}
|
|
expect_channel_alive(&mut channel).await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* OQ-09 (mapping.md §Request-level rows:
|
|
/// `env_request` declines unconditionally; no allowlist ships).
|
|
#[tokio::test]
|
|
async fn env_requests_decline_in_a_git_build() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_env(&channel, "GIT_DIR", "/srv/git")
|
|
.await
|
|
.expect("the env request rides the wire");
|
|
expect_failure(&mut channel, "the env request declines").await;
|
|
expect_channel_alive(&mut channel).await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Decline matrix (git-build
|
|
/// row: all other channel types decline, tunnel unfeatured).
|
|
#[tokio::test]
|
|
async fn tunnel_opens_decline_unfeatured_in_a_git_build() {
|
|
let session = authenticated().await;
|
|
|
|
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
|
|
.await
|
|
.expect_err("direct-tcpip declines unfeatured");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"direct-tcpip",
|
|
);
|
|
|
|
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
|
|
.await
|
|
.expect_err("direct-streamlocal declines unfeatured");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"direct-streamlocal",
|
|
);
|
|
|
|
session.unmount().await;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// session-only build: declines git-parseable exec (the git parse is
|
|
// its gate); with no seam/config this build's session rows decline at
|
|
// their config gates (the unset-declines contract, serving-path
|
|
// shape: `w3-tty-session`); declines tunnel opens (mapping.md
|
|
// §Decline matrix, session row).
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(all(feature = "session", not(feature = "git"), not(feature = "tunnel")))]
|
|
mod session_build {
|
|
use super::*;
|
|
use common::harness::{
|
|
open_direct_streamlocal, open_direct_tcpip, request_env, request_exec, request_pty,
|
|
request_shell, request_subsystem,
|
|
};
|
|
|
|
/// - *Contract stamp:* the task's channel-open gate (session
|
|
/// channels open where a session-serving feature exists).
|
|
#[tokio::test]
|
|
async fn session_channels_open_in_a_session_build() {
|
|
let session = authenticated().await;
|
|
open_session_channel(&session.client)
|
|
.await
|
|
.expect("session channels open under the session feature");
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Decline matrix (session row:
|
|
/// git-parseable exec declines; the `git` parse is its gate;
|
|
/// the channel survives the decline, POC-1 §5).
|
|
#[tokio::test]
|
|
async fn git_parseable_exec_declines_in_a_session_build() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_exec(&channel, "git-upload-pack '/owner/demo'")
|
|
.await
|
|
.expect("the exec request rides the wire");
|
|
expect_failure(
|
|
&mut channel,
|
|
"the git parse gates the row off in a session build",
|
|
)
|
|
.await;
|
|
expect_channel_alive(&mut channel).await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* OQ-08 + the recorded wire shape
|
|
/// (`w3-tty-session`: `exec_wrapper_argv` unset → the pipe row
|
|
/// declines (the serving-path shape, failure then close, never
|
|
/// a fallback). The row's serving surface (everything
|
|
/// configured) is `tests/tty_mapping.rs`'s.
|
|
#[tokio::test]
|
|
async fn plain_exec_declines_wrapper_unconfigured_in_a_session_build() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_exec(&channel, "uptime")
|
|
.await
|
|
.expect("the exec request rides the wire");
|
|
expect_failure_then_close(&mut channel, "the unset-wrapper pipe row").await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Channel-type rows + OQ-08 (the
|
|
/// `session` feature alone configures nothing: `session_seam`
|
|
/// unset → the pty/shell rows decline `seam-unconfigured`, the
|
|
/// serving-path shape this build pins).
|
|
#[tokio::test]
|
|
async fn pty_and_shell_decline_seam_unconfigured_in_a_session_build() {
|
|
let session = authenticated().await;
|
|
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_pty(&channel)
|
|
.await
|
|
.expect("the pty request rides the wire");
|
|
expect_failure_then_close(&mut channel, "the unset-seam pty row").await;
|
|
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_shell(&channel)
|
|
.await
|
|
.expect("the shell request rides the wire");
|
|
expect_failure_then_close(&mut channel, "the unset-seam shell row").await;
|
|
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Request-level rows (subsystem:
|
|
/// the OQ-13 placeholder and git service names decline-with-
|
|
/// reason in every session-serving build).
|
|
#[tokio::test]
|
|
async fn subsystems_decline_with_their_named_reasons_in_a_session_build() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
for name in ["sftp", "git-upload-pack"] {
|
|
request_subsystem(&channel, name)
|
|
.await
|
|
.expect("the subsystem request rides the wire");
|
|
expect_failure(
|
|
&mut channel,
|
|
&format!("the subsystem {name} declines with reason"),
|
|
)
|
|
.await;
|
|
}
|
|
expect_channel_alive(&mut channel).await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* OQ-09 (mapping.md §Request-level rows:
|
|
/// `env_request` declines unconditionally).
|
|
#[tokio::test]
|
|
async fn env_requests_decline_in_a_session_build() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_env(&channel, "GIT_DIR", "/srv/git")
|
|
.await
|
|
.expect("the env request rides the wire");
|
|
expect_failure(&mut channel, "the env request declines").await;
|
|
expect_channel_alive(&mut channel).await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Decline matrix (session row:
|
|
/// other channel types decline, tunnel unfeatured).
|
|
#[tokio::test]
|
|
async fn tunnel_opens_decline_unfeatured_in_a_session_build() {
|
|
let session = authenticated().await;
|
|
|
|
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
|
|
.await
|
|
.expect_err("direct-tcpip declines unfeatured");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"direct-tcpip",
|
|
);
|
|
|
|
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
|
|
.await
|
|
.expect_err("direct-streamlocal declines unfeatured");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"direct-streamlocal",
|
|
);
|
|
|
|
session.unmount().await;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// tunnel-only build: session channels decline (auto-reject); the
|
|
// tunnel direct rows decline on the **absent handle** even with the
|
|
// feature on (the route-1 bridge is `w3-tunnel-direct`: the pre-admit
|
|
// unset-handle class, the unset-declines posture; the fabric double's
|
|
// serve/cap/fabric-failure rows ride tests/tunnel_mapping.rs) per
|
|
// mapping.md §Decline matrix (tunnel row).
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(all(feature = "tunnel", not(feature = "git"), not(feature = "session")))]
|
|
mod tunnel_build {
|
|
use super::*;
|
|
use common::harness::{open_direct_streamlocal, open_direct_tcpip};
|
|
|
|
/// - *Contract stamp:* ADR-006 point 3 as amended (parse-only
|
|
/// dispatch: a request-less/deserving-less session channel
|
|
/// declines as unfeatured; a tunnel-only build has no
|
|
/// session-serving feature, so the open declines).
|
|
#[tokio::test]
|
|
async fn session_channels_decline_in_a_tunnel_build() {
|
|
let session = authenticated().await;
|
|
let err = open_session_channel(&session.client)
|
|
.await
|
|
.expect_err("session channels decline unfeatured");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::AdministrativelyProhibited,
|
|
"the unfeatured session open in a tunnel build",
|
|
);
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Channel-type rows + the
|
|
/// unset-declines posture (`w3-seam-config`, the feature alone
|
|
/// configures nothing): the direct rows decline on the **absent
|
|
/// fabric handle** even with `tunnel` on:
|
|
/// [`connect failed`], the cannot-bridge class ADR-006 decision
|
|
/// 1 pins (`w3-tunnel-direct`'s recorded wire classes: policy
|
|
/// refusals are administratively-prohibited, cannot-bridge is
|
|
/// connect-failed).
|
|
#[tokio::test]
|
|
async fn tunnel_direct_rows_decline_seam_unconfigured_without_a_handle() {
|
|
let session = authenticated().await;
|
|
|
|
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
|
|
.await
|
|
.expect_err("the direct-tcpip row declines on the absent handle");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::ConnectFailed,
|
|
"the direct-tcpip unset-handle decline",
|
|
);
|
|
|
|
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
|
|
.await
|
|
.expect_err("the direct-streamlocal row declines on the absent handle");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::ConnectFailed,
|
|
"the direct-streamlocal unset-handle decline",
|
|
);
|
|
|
|
session.unmount().await;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// All features: the routing decisions hold with the full graph on; the
|
|
// git row is still its config-driven decline in this binary (the
|
|
// serving rows ride tests/git_mapping.rs), and the session rows are
|
|
// still their config-driven declines (the serving rows ride
|
|
// tests/tty_mapping.rs).
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(all(feature = "git", feature = "session", feature = "tunnel"))]
|
|
mod all_features {
|
|
use super::*;
|
|
use common::harness::{
|
|
open_direct_streamlocal, open_direct_tcpip, request_env, request_exec, request_pty,
|
|
request_shell, request_subsystem,
|
|
};
|
|
|
|
/// - *Contract stamp:* mapping.md §Session-channel dispatch (the
|
|
/// git-parseable exec routes to the git row in the full build).
|
|
#[tokio::test]
|
|
async fn git_exec_routes_to_the_git_row_stub_with_all_features() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_exec(&channel, "git-upload-pack '/owner/demo'")
|
|
.await
|
|
.expect("the exec request rides the wire");
|
|
expect_failure_then_close(&mut channel, "the git row serves its seam stub").await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* OQ-08 + the recorded wire shape (the
|
|
/// unset-wrapper pipe row declines, the serving-path shape;
|
|
/// `w3-tty-session`). The serving surface rides
|
|
/// `tests/tty_mapping.rs`.
|
|
#[tokio::test]
|
|
async fn plain_exec_declines_wrapper_unconfigured_with_all_features() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_exec(&channel, "uptime")
|
|
.await
|
|
.expect("the exec request rides the wire");
|
|
expect_failure_then_close(&mut channel, "the unset-wrapper pipe row").await;
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Channel-type rows + OQ-08 (the
|
|
/// unset-seam pty/shell declines with the full graph on, since the
|
|
/// feature alone configures nothing; the serving surface rides
|
|
/// `tests/tty_mapping.rs`).
|
|
#[tokio::test]
|
|
async fn pty_and_shell_decline_seam_unconfigured_with_all_features() {
|
|
let session = authenticated().await;
|
|
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_pty(&channel)
|
|
.await
|
|
.expect("the pty request rides the wire");
|
|
expect_failure_then_close(&mut channel, "the unset-seam pty row").await;
|
|
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
request_shell(&channel)
|
|
.await
|
|
.expect("the shell request rides the wire");
|
|
expect_failure_then_close(&mut channel, "the unset-seam shell row").await;
|
|
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* the unset-declines posture (`w3-seam-config`,
|
|
/// carried through `w3-tunnel-direct`: the route-1 bridge's
|
|
/// pre-admit unset-handle class): the direct rows decline on
|
|
/// the **absent fabric handle** even with the full feature graph
|
|
/// on (`ConnectFailed`, the cannot-bridge class); the served
|
|
/// surface rides tests/tunnel_mapping.rs's fabric double.
|
|
#[tokio::test]
|
|
async fn tunnel_direct_rows_decline_seam_unconfigured_without_a_handle_with_all_features() {
|
|
let session = authenticated().await;
|
|
|
|
let err = open_direct_tcpip(&session.client, "internal.example", 5432)
|
|
.await
|
|
.expect_err("the full-featured direct-tcpip row declines on the absent handle");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::ConnectFailed,
|
|
"the direct-tcpip unset-handle decline",
|
|
);
|
|
|
|
let err = open_direct_streamlocal(&session.client, "/run/alkssh.sock")
|
|
.await
|
|
.expect_err("the full-featured direct-streamlocal row declines on the absent handle");
|
|
open_failure(
|
|
err,
|
|
ChannelOpenFailure::ConnectFailed,
|
|
"the direct-streamlocal unset-handle decline",
|
|
);
|
|
|
|
session.unmount().await;
|
|
}
|
|
|
|
/// - *Contract stamp:* mapping.md §Request-level rows (the
|
|
/// capability declines (sftp placeholder, git subsystem
|
|
/// names, env) hold with everything on).
|
|
#[tokio::test]
|
|
async fn capability_declines_hold_with_all_features() {
|
|
let session = authenticated().await;
|
|
let mut channel = open_session_channel(&session.client)
|
|
.await
|
|
.expect("the session channel opens");
|
|
|
|
request_subsystem(&channel, "sftp")
|
|
.await
|
|
.expect("the subsystem request rides the wire");
|
|
expect_failure(&mut channel, "the sftp placeholder declines").await;
|
|
|
|
request_subsystem(&channel, "git-upload-pack")
|
|
.await
|
|
.expect("the subsystem request rides the wire");
|
|
expect_failure(&mut channel, "the git subsystem declines").await;
|
|
|
|
request_env(&channel, "GIT_DIR", "/srv/git")
|
|
.await
|
|
.expect("the env request rides the wire");
|
|
expect_failure(&mut channel, "the env request declines").await;
|
|
|
|
expect_channel_alive(&mut channel).await;
|
|
session.unmount().await;
|
|
}
|
|
}
|