Files
diegosouzapw--omniroute/tests/unit/mitm-sudo-graceful-degrade.test.ts
2026-07-13 13:39:12 +08:00

102 lines
3.6 KiB
TypeScript

/**
* Regression test for MITM cert install in Docker `USER node` (non-root, no sudo).
*
* OmniRoute's runtime Docker image (`Dockerfile`) runs as `USER node` (UID 1000)
* and the slim base (`node:24-trixie-slim`) does NOT ship `sudo`. The previous
* `execFileWithPassword("sudo", ["-S", ...])` would spawn `sudo` unconditionally
* when not root, producing `spawn sudo ENOENT` and breaking `installCert` /
* `addDNSEntries` for any MITM operation triggered from inside the container.
*
* The fix:
* - `isSudoAvailable()` probes `PATH` for `sudo`.
* - `execFileWithPassword` when invoked with `command === "sudo"` and sudo is
* NOT available and the process is NOT root, strips the leading `sudo -S`
* flags and runs the underlying command directly (same user, no elevation).
* - Callers that absolutely require elevation (e.g. installing a CA to the
* system trust store) can pre-check with `isSudoAvailable()` and skip with
* a clear log instead of throwing.
*
* This mirrors upstream behavior (decolua/9router) extended to OmniRoute's TS
* fork and OmniRoute's existing root-detection (`isRoot()`).
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import {
execFileWithPassword,
isRoot,
isSudoAvailable,
} from "../../src/mitm/systemCommands.ts";
test("isSudoAvailable: returns a boolean (probe shape)", () => {
const result = isSudoAvailable();
assert.equal(typeof result, "boolean");
});
test(
"execFileWithPassword: when command is 'sudo' but sudo is missing, falls back to direct exec (no ENOENT)",
async () => {
if (isRoot()) {
// Root path is already handled by an earlier branch — skip.
return;
}
if (isSudoAvailable()) {
// We cannot exercise the no-sudo path on a host that has sudo on PATH.
// The dedicated unit test below covers it by isolating PATH.
return;
}
// Use a no-op binary that succeeds without requiring privileges.
// After stripping `sudo -S`, the underlying command must be `true` (or any
// executable in PATH that exits 0).
const result = await execFileWithPassword(
"sudo",
["-S", "true"],
"fake-password"
);
assert.equal(typeof result, "string");
}
);
test(
"execFileWithPassword: with empty PATH so sudo is undiscoverable, falls back gracefully",
async () => {
if (isRoot()) {
// Root branch strips sudo upstream — covered separately.
return;
}
// Force the discovery probe to find no sudo by clearing PATH temporarily.
// We do this only around the probe call inside `execFileWithPassword`,
// by stubbing the env. Restore on test exit.
const originalPath = process.env.PATH;
process.env.PATH = "/__no_such_dir__";
try {
// After fallback, the bare command must be a valid executable. We use
// the current Node binary with `process.exit(0)` so it is portable
// regardless of which utilities (`true`, `:`) exist in the path.
const result = await execFileWithPassword(
"sudo",
["-S", process.execPath, "-e", "process.exit(0)"],
"fake-password"
);
assert.equal(typeof result, "string");
} finally {
process.env.PATH = originalPath;
}
}
);
test(
"execFileWithPassword: a non-sudo command is unaffected by the fallback path",
async () => {
// Direct invocation of a known-good binary must still work end-to-end.
const result = await execFileWithPassword(
process.execPath,
["-e", "process.exit(0)"],
"" // no password needed
);
assert.equal(typeof result, "string");
}
);