#![cfg(windows)]
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use chrono::{DateTime, Utc};
use runner_manager_domain::model::StartMode;
use runner_manager_domain::path::LocalAbsolutePath;
use runner_manager_platform::lock::{HostLock, LockKind};
use runner_manager_platform::paths::AppPaths;
use runner_manager_platform::runner_root::default_runner_root;
use runner_manager_platform::runner_root_access::{
Reversal, RootAccessChange, RootAccessError, RootAccessReport, RootAdmission,
ensure_default_root, grants_broad_write, is_protected, report,
};
use runner_manager_platform::service::{
BinaryPath, HostControls, InstallRecord, InstallRequest, Installed, RestartPolicy,
ServiceError, ServiceIdentity, ServiceOperations, WINDOWS_SCM_HOST_ARGUMENT,
};
use windows_service::service::{ServiceAccess, ServiceExitCode};
use windows_service::service_manager::{ServiceManager, ServiceManagerAccess};
const FIXTURE_PREFIX: &str = "runner-manager-selftest-";
struct Fixture {
identity: ServiceIdentity,
operations: ServiceOperations,
paths: AppPaths,
binary: PathBuf,
heartbeat: PathBuf,
runner_root: PathBuf,
_root: tempfile::TempDir,
}
impl Fixture {
fn new(tag: &str) -> Self {
let identity = ServiceIdentity::fixture(&format!(
"{tag}-{}-{}",
std::process::id(),
Utc::now().timestamp_subsec_nanos()
));
assert!(
identity.is_fixture() && identity.name().starts_with(FIXTURE_PREFIX),
"a test must never be able to name a real registration, got {}",
identity.name()
);
let root = tempfile::tempdir().expect("a temporary directory");
let paths = AppPaths::rooted_at(root.path());
paths.create_all().expect("the four directories");
let binary = root.path().join("runner-manager-selftest.exe");
std::fs::copy(fixture_service_host(), &binary).expect("a copy of the fixture host");
let runner_root = root.path().join("runner-root");
let operations = ServiceOperations::with_controls(
paths.clone(),
identity.clone(),
std::sync::Arc::new(HostControls),
)
.with_runner_root(
LocalAbsolutePath::new(runner_root.to_str().expect("a unicode temporary path"))
.expect("a local absolute path"),
);
let existing = operations
.status()
.expect("the service manager can be asked");
assert!(
!existing.is_installed(),
"{} already exists on this machine; refusing to touch it",
identity.name()
);
Self {
heartbeat: root.path().join("starts.tsv"),
identity,
operations,
paths,
binary,
runner_root,
_root: root,
}
}
fn install(&self, mode: StartMode, restart: RestartPolicy) -> Installed {
self.operations
.install(&self.request(mode, restart))
.expect("the fixture registers")
}
fn request(&self, mode: StartMode, restart: RestartPolicy) -> InstallRequest {
self.request_exercising(mode, restart, None)
}
fn request_exercising(
&self,
mode: StartMode,
restart: RestartPolicy,
exercise: Option<&Path>,
) -> InstallRequest {
let mut arguments = vec![self.heartbeat.as_os_str().to_owned()];
if let Some(root) = exercise {
arguments.push(root.as_os_str().to_owned());
}
if mode == StartMode::Boot {
arguments.push(std::ffi::OsString::from(WINDOWS_SCM_HOST_ARGUMENT));
}
InstallRequest::new(mode)
.for_binary(&self.binary)
.with_arguments(arguments)
.with_restart(restart)
.started_on_demand()
}
fn workspace_outcome(&self) -> Option<String> {
let mut name = self.heartbeat.as_os_str().to_owned();
name.push(".workspace");
std::fs::read_to_string(PathBuf::from(name)).ok()
}
fn wait_for_workspace_outcome(&self, timeout: Duration) -> String {
let deadline = Instant::now() + timeout;
loop {
if let Some(outcome) = self.workspace_outcome() {
return outcome;
}
assert!(
Instant::now() < deadline,
"{} never reported what it could do below the runner root in {timeout:?}",
self.identity.name()
);
std::thread::sleep(Duration::from_millis(200));
}
}
fn starts(&self) -> Vec<(DateTime<Utc>, u32)> {
let Ok(text) = std::fs::read_to_string(&self.heartbeat) else {
return Vec::new();
};
text.lines()
.filter_map(|line| {
let (at, pid) = line.split_once('\t')?;
Some((
DateTime::parse_from_rfc3339(at).ok()?.with_timezone(&Utc),
pid.trim().parse().ok()?,
))
})
.collect()
}
fn wait_for_starts(&self, count: usize, timeout: Duration) -> Vec<(DateTime<Utc>, u32)> {
let deadline = Instant::now() + timeout;
loop {
let starts = self.starts();
if starts.len() >= count {
return starts;
}
assert!(
Instant::now() < deadline,
"the fixture host started {} time(s) in {timeout:?}, expected {count}",
starts.len()
);
std::thread::sleep(Duration::from_millis(200));
}
}
}
impl Drop for Fixture {
fn drop(&mut self) {
if !self.identity.is_fixture() {
return;
}
let _ = self.operations.stop();
let _ = self.operations.uninstall();
sweep(self.identity.name());
}
}
fn sweep(name: &str) {
assert!(
name.starts_with(FIXTURE_PREFIX),
"sweep refuses to touch {name}: it is not a self-test fixture"
);
let _ = std::process::Command::new("sc.exe")
.args(["delete", name])
.output();
let _ = std::process::Command::new("schtasks.exe")
.args(["/Delete", "/TN", name, "/F"])
.output();
}
fn fixture_service_host() -> PathBuf {
let test_binary = std::env::current_exe().expect("this test binary has a path");
let candidate = test_binary
.parent()
.and_then(Path::parent)
.expect("target/debug/deps has two ancestors")
.join("examples")
.join("service_host_fixture.exe");
assert!(
candidate.is_file(),
"{} is missing, so there is nothing for the service manager to start. Build it first:\n\
\n cargo build -p runner-manager-platform --example service_host_fixture\n\n\
`cargo test --test privileged_service_installer` does NOT build it; only a whole-crate \
`cargo test` or the explicit command above does.",
candidate.display()
);
candidate
}
fn runner_manager_binary() -> PathBuf {
let test_binary = std::env::current_exe().expect("this test binary has a path");
let candidate = test_binary
.parent()
.and_then(Path::parent)
.expect("target/debug/deps has two ancestors")
.join("runner-manager.exe");
assert!(
candidate.is_file(),
"{} is missing. Build the production service host first:\n\
\n cargo build -p runner-manager\n",
candidate.display()
);
candidate
}
fn production_daemon_arguments(paths: &AppPaths, windows_scm: bool) -> Vec<std::ffi::OsString> {
let mut arguments: Vec<_> = [
std::ffi::OsString::from("daemon"),
std::ffi::OsString::from("run"),
std::ffi::OsString::from("--service-config-dir"),
paths.config_dir().as_os_str().to_owned(),
std::ffi::OsString::from("--service-state-dir"),
paths.state_dir().as_os_str().to_owned(),
std::ffi::OsString::from("--service-runtime-dir"),
paths.runtime_dir().as_os_str().to_owned(),
std::ffi::OsString::from("--service-logs-dir"),
paths.logs_dir().as_os_str().to_owned(),
]
.into_iter()
.collect();
if windows_scm {
arguments.push(std::ffi::OsString::from(WINDOWS_SCM_HOST_ARGUMENT));
}
arguments
}
fn wait_for_running(fixture: &Fixture, running: bool, timeout: Duration) {
let deadline = Instant::now() + timeout;
loop {
let observed = fixture
.operations
.status()
.expect("SCM can report status")
.registration()
.is_some_and(|registration| registration.running);
if observed == running {
return;
}
assert!(
Instant::now() < deadline,
"{} did not become {} within {timeout:?}",
fixture.identity.name(),
if running { "RUNNING" } else { "STOPPED" }
);
std::thread::sleep(Duration::from_millis(200));
}
}
fn remove_file_after_process_exit(path: &Path, timeout: Duration) {
let deadline = Instant::now() + timeout;
loop {
match std::fs::remove_file(path) {
Ok(()) => return,
Err(error)
if error.kind() == std::io::ErrorKind::PermissionDenied
&& Instant::now() < deadline =>
{
std::thread::sleep(Duration::from_millis(100));
}
Err(error) => panic!("{} did not become removable: {error}", path.display()),
}
}
}
fn scm_exit_code(fixture: &Fixture) -> ServiceExitCode {
let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)
.expect("the local SCM opens");
manager
.open_service(fixture.identity.name(), ServiceAccess::QUERY_STATUS)
.expect("the fixture remains registered until its exit is inspected")
.query_status()
.expect("SCM reports the final service status")
.exit_code
}
fn require_elevation(error: &ServiceError) -> ! {
panic!(
"these tests register a real service and need administrative rights: {error}\n\
Run them from an elevated prompt:\n\
cargo test -p runner-manager-platform --test privileged_service_installer -- \
--ignored --test-threads=1"
);
}
#[test]
#[ignore = "registers a real Windows service; run explicitly"]
fn install_status_and_uninstall_round_trip_against_the_real_service_manager() {
let fixture = Fixture::new("round-trip");
let installed = match fixture
.operations
.install(&fixture.request(StartMode::Boot, RestartPolicy::default()))
{
Ok(installed) => installed,
Err(error @ ServiceError::NeedsElevation { .. }) => require_elevation(&error),
Err(error) => panic!("{error}"),
};
assert_eq!(installed.record.binary, fixture.binary);
assert!(installed.record.binary.is_absolute());
assert!(
installed.review.is_least_privilege(),
"{}",
installed.review
);
let status = fixture.operations.status().expect("a status");
assert!(status.is_installed());
assert_eq!(status.start_mode(), Some(StartMode::Boot));
assert_eq!(
status.log_file(),
fixture.paths.logs_dir().join("runner-manager.service.log")
);
assert!(
matches!(status.binary(), Some(BinaryPath::Current { .. })),
"{status}"
);
let registration = status.registration().expect("the manager knows it");
assert_eq!(
registration.binary().as_deref(),
Some(fixture.binary.as_path()),
"the manager's own command line must name the recorded binary: {}",
registration.command_line
);
assert_eq!(
registration.restart_delay,
Some(RestartPolicy::default().delay()),
"the manager must report back the bounded delay that was configured"
);
let account = registration.account.as_deref().unwrap_or_default();
assert!(
account.eq_ignore_ascii_case("LocalSystem")
|| account.eq_ignore_ascii_case("NT AUTHORITY\\SYSTEM"),
"the account must be the one the machine-scoped store's DACL admits, got {account}"
);
let problems: Vec<&str> = status
.problems()
.iter()
.map(|problem| problem.subject)
.collect();
assert_eq!(problems, vec!["start mode"], "{status}");
let uninstalled = fixture.operations.uninstall().expect("an uninstall");
assert!(uninstalled.removed_registration);
assert!(uninstalled.removed_record);
let after = fixture.operations.status().expect("a status");
assert!(!after.is_installed(), "{after}");
}
#[test]
#[ignore = "installs and starts the production runner-manager binary as a real Windows service"]
fn production_daemon_entrypoint_reaches_running_and_handles_scm_stop() {
let fixture = Fixture::new("production-entrypoint");
std::fs::copy(runner_manager_binary(), &fixture.binary)
.expect("the fixture owns a copy of runner-manager.exe");
let request = InstallRequest::new(StartMode::Boot)
.for_binary(&fixture.binary)
.with_arguments(production_daemon_arguments(&fixture.paths, true))
.started_on_demand();
match fixture.operations.install(&request) {
Ok(_) => {}
Err(error @ ServiceError::NeedsElevation { .. }) => require_elevation(&error),
Err(error) => panic!("{error}"),
}
wait_for_running(&fixture, true, Duration::from_secs(30));
std::thread::sleep(Duration::from_secs(32));
assert!(
fixture
.operations
.status()
.expect("SCM can report the stable service")
.registration()
.is_some_and(|registration| registration.running),
"the production service did not remain RUNNING past SCM's dispatcher timeout"
);
assert!(
fixture
.operations
.stop()
.expect("SCM delivers SERVICE_CONTROL_STOP"),
"the service was expected to be running"
);
wait_for_running(&fixture, false, Duration::from_secs(30));
assert_eq!(
scm_exit_code(&fixture),
ServiceExitCode::Win32(0),
"the production daemon must report a clean exit after graceful drain"
);
fixture
.operations
.uninstall()
.expect("the fixture registration is removed");
assert!(
fixture
.operations
.status()
.expect("SCM can prove cleanup")
.registration()
.is_none(),
"the production-entrypoint fixture leaked a service registration"
);
}
#[test]
#[ignore = "installs and starts the production login command as a real scheduled task"]
fn production_login_entrypoint_runs_without_scm_and_stops_through_task_scheduler() {
let fixture = Fixture::new("production-login-entrypoint");
std::fs::copy(runner_manager_binary(), &fixture.binary)
.expect("the fixture owns a copy of runner-manager.exe");
let arguments = production_daemon_arguments(&fixture.paths, false);
assert!(
arguments
.iter()
.all(|argument| argument != WINDOWS_SCM_HOST_ARGUMENT),
"the login command must not carry the SCM discriminator"
);
let request = InstallRequest::new(StartMode::Login)
.for_binary(&fixture.binary)
.with_arguments(arguments)
.started_on_demand();
match fixture.operations.install(&request) {
Ok(_) => {}
Err(error @ ServiceError::NeedsElevation { .. }) => require_elevation(&error),
Err(error) => panic!("{error}"),
}
fixture
.operations
.start()
.expect("Task Scheduler starts the production login entrypoint");
wait_for_running(&fixture, true, Duration::from_secs(30));
std::thread::sleep(Duration::from_secs(3));
assert!(
fixture
.operations
.status()
.expect("Task Scheduler can report the live daemon")
.registration()
.is_some_and(|registration| registration.running),
"the login daemon exited as it would if it had incorrectly attempted to connect to SCM"
);
assert!(
fixture
.operations
.stop()
.expect("Task Scheduler ends its running task"),
"the scheduled task was expected to be running"
);
wait_for_running(&fixture, false, Duration::from_secs(30));
fixture
.operations
.uninstall()
.expect("the scheduled-task fixture is removed");
assert!(
fixture
.operations
.status()
.expect("Task Scheduler can prove cleanup")
.registration()
.is_none(),
"the production-login fixture leaked a scheduled task"
);
}
#[test]
#[ignore = "registers a real Windows service; run explicitly"]
fn uninstall_leaves_configuration_sqlite_secrets_and_cache_byte_for_byte() {
let fixture = Fixture::new("preserve");
fixture.install(StartMode::Boot, RestartPolicy::default());
let config = fixture.paths.config_dir();
std::fs::write(config.join("runner-manager.db"), b"sqlite fixture").expect("writable");
std::fs::create_dir_all(fixture.paths.state_dir().join("packages/2.330.0")).expect("writable");
std::fs::write(
fixture
.paths
.state_dir()
.join("packages/2.330.0/runner.tar.gz"),
b"cached runner package",
)
.expect("writable");
std::fs::create_dir_all(fixture.paths.state_dir().join("secrets")).expect("writable");
std::fs::write(
fixture.paths.state_dir().join("secrets/user-access-token"),
b"a stand-in for the stored credential",
)
.expect("writable");
std::fs::write(
fixture
.paths
.logs_dir()
.join("runner-manager.log.2026-08-22"),
b"diagnostics",
)
.expect("writable");
let roots: Vec<PathBuf> = fixture
.paths
.all()
.iter()
.map(|(_, path)| (*path).to_path_buf())
.collect();
let before = tree(&roots);
assert!(
before.len() >= 5,
"the fixture must hold the files this test is about: {before:#?}"
);
let record = InstallRecord::path(&fixture.paths);
assert!(before.iter().any(|(path, _)| path == &record));
fixture.operations.uninstall().expect("an uninstall");
let after = tree(&roots);
let expected: Vec<_> = before
.iter()
.filter(|(path, _)| path != &record)
.cloned()
.collect();
assert_eq!(
after, expected,
"uninstall deleted more than its own record"
);
assert!(!record.exists(), "or it deleted nothing at all");
}
fn tree(roots: &[PathBuf]) -> Vec<(PathBuf, Vec<u8>)> {
fn walk(directory: &Path, out: &mut Vec<(PathBuf, Vec<u8>)>) {
let Ok(entries) = std::fs::read_dir(directory) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk(&path, out);
} else if let Ok(bytes) = std::fs::read(&path) {
out.push((path, bytes));
}
}
}
let mut out = Vec::new();
for root in roots {
walk(root, &mut out);
}
out.sort();
out
}
#[test]
#[ignore = "registers a real Windows service; run explicitly"]
fn a_binary_that_moves_after_install_is_reported_as_stale() {
let fixture = Fixture::new("stale");
fixture.install(StartMode::Boot, RestartPolicy::default());
let healthy = fixture.operations.status().expect("a status");
assert!(
!healthy
.problems()
.iter()
.any(|problem| problem.subject == "binary"),
"{healthy}"
);
assert!(
fixture
.operations
.stop()
.expect("the installed fixture stops before its binary moves"),
"install must have started the fixture"
);
wait_for_running(&fixture, false, Duration::from_secs(30));
remove_file_after_process_exit(&fixture.binary, Duration::from_secs(30));
let stale = fixture.operations.status().expect("a status");
assert!(
matches!(stale.binary(), Some(BinaryPath::Missing { .. })),
"{stale}"
);
assert!(
stale
.problems()
.iter()
.any(|problem| problem.subject == "binary"),
"{stale}"
);
assert!(!stale.is_healthy(), "{stale}");
assert!(stale.registration().is_some(), "{stale}");
}
#[test]
#[ignore = "registers a real Windows service; run explicitly"]
fn installing_while_the_single_instance_lock_is_held_registers_nothing() {
let fixture = Fixture::new("lock");
let held = HostLock::try_acquire(&fixture.paths, LockKind::SingleInstance)
.expect("this process takes the lock first");
let error = fixture
.operations
.install(&fixture.request(StartMode::Boot, RestartPolicy::default()))
.expect_err("a second agent must not be registered while one is running");
assert!(matches!(error, ServiceError::LockHeld { .. }), "{error}");
assert!(
error.to_string().contains("already running"),
"the refusal must be actionable: {error}"
);
let status = fixture.operations.status().expect("a status");
assert!(status.registration().is_none(), "{status}");
assert!(!status.is_installed(), "{status}");
drop(held);
fixture.install(StartMode::Boot, RestartPolicy::default());
assert!(
fixture
.operations
.status()
.expect("a status")
.registration()
.is_some()
);
}
#[test]
#[ignore = "registers a real Windows service and a real scheduled task; run explicitly"]
fn switching_start_mode_moves_the_registration_between_the_two_windows_facilities() {
let fixture = Fixture::new("switch");
fixture.install(StartMode::Boot, RestartPolicy::default());
let boot = fixture.operations.status().expect("a status");
assert_eq!(
boot.registration().map(|found| found.manager.manager()),
Some("the Windows Service Control Manager"),
"{boot}"
);
let change = fixture
.operations
.set_start_mode(StartMode::Login)
.expect("Windows has no service that starts at logon, so this moves facility");
assert!(change.changed);
let login = fixture.operations.status().expect("a status");
assert_eq!(
login.registration().map(|found| found.manager.manager()),
Some("Windows Task Scheduler"),
"{login}"
);
assert_eq!(login.start_mode(), Some(StartMode::Login));
assert!(
!login
.registration()
.expect("Task Scheduler owns the login registration")
.command_line
.contains(WINDOWS_SCM_HOST_ARGUMENT),
"switching to login must remove the SCM-only marker"
);
assert_eq!(
login
.registration()
.and_then(|found| found.binary())
.as_deref(),
Some(fixture.binary.as_path()),
"the switch must carry the recorded path across, not re-resolve one"
);
fixture
.operations
.set_start_mode(StartMode::Boot)
.expect("switching back recreates the service without reinstalling the product");
let boot_again = fixture.operations.status().expect("a status");
assert!(
boot_again
.registration()
.expect("SCM owns the boot registration")
.command_line
.contains(WINDOWS_SCM_HOST_ARGUMENT),
"switching back to boot must restore the durable SCM marker"
);
}
const MEASURED_DELAY: Duration = Duration::from_secs(10);
#[test]
#[ignore = "registers, starts, and kills a real Windows service; run explicitly"]
fn a_killed_service_comes_back_and_no_sooner_than_the_bounded_delay() {
let fixture = Fixture::new("restart");
let restart = RestartPolicy::new(MEASURED_DELAY, Duration::from_secs(600))
.expect("ten seconds is inside the supported range");
fixture.install(StartMode::Boot, restart);
let first = fixture.wait_for_starts(1, Duration::from_secs(30));
let (_, pid) = first[0];
let killed = std::process::Command::new("taskkill.exe")
.args(["/F", "/PID", &pid.to_string()])
.output()
.expect("taskkill runs");
assert!(
killed.status.success(),
"could not kill the fixture host: {}",
String::from_utf8_lossy(&killed.stderr)
);
let killed_at = Utc::now();
let starts = fixture.wait_for_starts(2, Duration::from_secs(90));
let restarted_at = starts[1].0;
assert_ne!(starts[1].1, pid, "the second start must be a new process");
let measured = (restarted_at - killed_at)
.to_std()
.expect("the restart is after the kill");
assert!(
measured + Duration::from_millis(250) >= MEASURED_DELAY,
"the service came back after {measured:?}, sooner than the {MEASURED_DELAY:?} bound: \
the manager is not honouring the restart delay"
);
assert!(
measured <= MEASURED_DELAY + Duration::from_secs(30),
"the service took {measured:?} to come back, well past the {MEASURED_DELAY:?} bound"
);
eprintln!("measured restart interval: {measured:?} against a {MEASURED_DELAY:?} bound");
}
struct RestoresTheRealRunnerRoot(Option<RootAccessChange>);
impl RestoresTheRealRunnerRoot {
fn revert(&mut self) -> Reversal {
self.0
.take()
.map_or(Reversal::NothingToUndo, |change| change.revert())
}
}
impl Drop for RestoresTheRealRunnerRoot {
fn drop(&mut self) {
let _ = self.revert();
}
}
#[test]
#[ignore = "creates and re-permissions the real %SystemDrive%\\rman and registers a real service"]
fn a_boot_service_creates_materializes_and_cleans_a_child_below_the_real_default_root() {
let fixture = Fixture::new("root-boot");
let paths = fixture.paths.clone();
let root = default_runner_root(&paths).expect("this host resolves a default runner root");
let rendered = root.as_str();
let mut characters = rendered.chars();
assert!(
characters
.next()
.is_some_and(|drive| drive.is_ascii_uppercase())
&& characters.as_str().eq_ignore_ascii_case(":\\rman")
&& rendered.len() == 7,
"the privileged evidence must be about `<system drive>:\\rman`, got {rendered:?}"
);
let mut prepared = match ensure_default_root(&paths, &RootAdmission::LocalSystem) {
Ok(prepared) => RestoresTheRealRunnerRoot(Some(prepared)),
Err(error @ RootAccessError::BroadExistingAccess { .. }) => panic!(
"this host already has a runner root that ordinary local users can write, and the \
product refuses such a directory rather than adopting it -- which is the behaviour \
this test exists to preserve. Remove or empty it and run this again.\n{error}"
),
Err(error) => panic!("the default runner root could not be prepared: {error}"),
};
match report(root.as_path()) {
RootAccessReport::Present {
dacl,
protected,
broad_write,
} => {
assert!(!broad_write, "the whole point of this feature: {dacl}");
assert!(
protected,
"an unprotected root inherits whatever the volume grants, which is exactly the \
Authenticated Users write grant this severs: {dacl}"
);
assert!(
!dacl.contains("S-1-5-21-1") && !dacl.contains("S-1-5-21-2"),
"the reported descriptor must be redacted: {dacl}"
);
}
other => panic!("the root this account just prepared must be readable, got {other:?}"),
}
match fixture.operations.install(&fixture.request_exercising(
StartMode::Boot,
RestartPolicy::default(),
Some(root.as_path()),
)) {
Ok(_) => {}
Err(error @ ServiceError::NeedsElevation { .. }) => require_elevation(&error),
Err(error) => panic!("{error}"),
}
fixture.wait_for_starts(1, Duration::from_secs(30));
let outcome = fixture.wait_for_workspace_outcome(Duration::from_secs(30));
let _ = fixture.operations.stop();
let reversal = prepared.revert();
assert_eq!(
outcome.trim(),
"ok",
"a boot service running as LocalSystem must be able to create a child below {}, write \
inside it, and remove it again",
root.as_path().display()
);
assert!(
!matches!(reversal, Reversal::Retained { .. }),
"this test must leave the host's runner root as it found it: {reversal}"
);
}
#[test]
#[ignore = "registers a real Windows service and a real scheduled task; run explicitly"]
fn a_login_task_uses_the_runner_root_as_the_invoking_user_after_a_mode_transition() {
let fixture = Fixture::new("root-login");
let exercised = fixture.runner_root.clone();
match fixture.operations.install(&fixture.request_exercising(
StartMode::Boot,
RestartPolicy::default(),
Some(&exercised),
)) {
Ok(installed) => assert_eq!(
installed.runner_root.path(),
Some(exercised.as_path()),
"install prepares the root the registration will run jobs under"
),
Err(error @ ServiceError::NeedsElevation { .. }) => require_elevation(&error),
Err(error) => panic!("{error}"),
}
let change = fixture
.operations
.set_start_mode(StartMode::Login)
.expect("the registration moves to Task Scheduler");
assert!(change.changed);
assert_eq!(change.runner_root.path(), Some(exercised.as_path()));
let dacl = match report(&exercised) {
RootAccessReport::Present {
dacl, broad_write, ..
} => {
assert!(!broad_write, "{dacl}");
dacl
}
other => panic!("the reconciled root must be readable, got {other:?}"),
};
fixture
.operations
.start()
.expect("Task Scheduler starts the fixture as the invoking user");
fixture.wait_for_starts(1, Duration::from_secs(60));
let outcome = fixture.wait_for_workspace_outcome(Duration::from_secs(60));
let _ = fixture.operations.stop();
assert_eq!(
outcome.trim(),
"ok",
"a login task running as the invoking user must be able to create a child below {}, \
write inside it, and remove it again -- the task runs under a *filtered* token, in \
which Administrators is deny-only, so this passes only if the root admits the account \
by name. Its access control is {dacl}",
exercised.display()
);
}
#[test]
#[ignore = "drives the real installer against a real directory with a real ACL"]
fn an_existing_broad_root_fails_the_preflight_and_registers_nothing() {
let fixture = Fixture::new("root-broad");
std::fs::create_dir_all(&fixture.runner_root).expect("a directory to open up");
grant_everyone_full_control(&fixture.runner_root);
assert!(
matches!(
report(&fixture.runner_root),
RootAccessReport::Present {
broad_write: true,
..
}
),
"the case under test was not actually set up"
);
let error = fixture
.operations
.install(&fixture.request(StartMode::Boot, RestartPolicy::default()))
.expect_err("a runner root ordinary local users can write must refuse the install");
assert!(matches!(error, ServiceError::RunnerRoot { .. }), "{error}");
let message = error.to_string();
assert!(message.contains("nothing was registered"), "{message}");
assert!(message.contains("host set-runtime-root"), "{message}");
assert!(
fixture
.operations
.status()
.expect("the managers can be asked")
.registration()
.is_none(),
"the refusal has to come before anything is registered"
);
}
#[test]
#[ignore = "writes a real ACL to a temporary directory; run with the rest of this file"]
fn a_custom_root_is_reported_and_never_rewritten() {
let fixture = Fixture::new("root-custom");
let custom = fixture.runner_root.join("an-operators-own-directory");
std::fs::create_dir_all(&custom).expect("an operator's own directory");
grant_everyone_full_control(&custom);
let before = report(&custom);
assert!(
matches!(
before,
RootAccessReport::Present {
broad_write: true,
..
}
),
"{before:?}"
);
for _ in 0..3 {
assert_eq!(report(&custom), before);
}
let rendered = icacls(&custom, &[]);
assert!(
rendered.contains("Everyone"),
"the operator's own grant must survive being reported on: {rendered}"
);
}
#[test]
#[ignore = "reads real descriptors from real directories"]
fn the_predicates_agree_with_what_windows_actually_writes() {
let fixture = Fixture::new("root-predicates");
let narrow = fixture.runner_root.join("narrow");
let open = fixture.runner_root.join("open");
std::fs::create_dir_all(&narrow).expect("a directory");
std::fs::create_dir_all(&open).expect("a directory");
icacls(
&narrow,
&["/inheritance:r", "/grant", "*S-1-5-18:(OI)(CI)F"],
);
let RootAccessReport::Present {
dacl: narrow_dacl, ..
} = report(&narrow)
else {
panic!("the narrow directory must be readable")
};
assert!(is_protected(&narrow_dacl), "{narrow_dacl}");
assert!(!grants_broad_write(&narrow_dacl), "{narrow_dacl}");
grant_everyone_full_control(&open);
let RootAccessReport::Present {
dacl: open_dacl, ..
} = report(&open)
else {
panic!("the open directory must be readable")
};
assert!(
grants_broad_write(&open_dacl),
"a descriptor Windows itself wrote for Everyone must read as broadly writable: {open_dacl}"
);
}
fn icacls(path: &Path, arguments: &[&str]) -> String {
let output = std::process::Command::new("icacls.exe")
.arg(path)
.args(arguments)
.output()
.expect("icacls.exe is present on every Windows host");
assert!(
output.status.success(),
"icacls {arguments:?} failed on {}: {}",
path.display(),
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8_lossy(&output.stdout).into_owned()
}
fn grant_everyone_full_control(path: &Path) {
icacls(path, &["/grant", "*S-1-1-0:(OI)(CI)F"]);
}