Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/dns.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3294,6 +3294,7 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
// Moshpit names and cannot verify them, which is bad; a machine whose DNS
// was refused because an optional component would not start is worse.
let proxyUnitPath = null;
let ours = false;
if (!rest.includes("--no-proxy") && platform === "linux") {
if (!proxyWrapper()) {
out(" -- no pinned-TLS proxy installed — https:// on a name will not verify");
Expand All @@ -3306,6 +3307,10 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
out(` ${step.ok ? "ok " : "-- "} ${step.step}${step.error ? ` — ${step.error}` : ""}`);
}
if (!ensured.ok) out(` -- the proxy is not serving (${ensured.reason}) — https:// will not verify`);
// Started by this run, and confirmed holding the port. That is the
// strongest evidence available that the proxy on 443 is ours, and it is
// strictly better than the handshake below.
ours = ensured.ok;
}
}

Expand Down Expand Up @@ -3363,6 +3368,26 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
// proxy and retracting it two lines later is worse than not looking.
out(" -- the bridge already running was not started by this run, so it keeps its own");
out(" mode — to pick up proxy mode: moshcode dns disable && moshcode dns enable");
} else if (ours) {
// No handshake needed, and none that would work.
//
// The probe asks the proxy for a certificate under some name. The proxy
// will not present one for a name with no key published in the registry —
// deliberately, so a browser gets a TLS failure it can explain rather than
// a reset mid-request — and this command has no way to invent a name that
// someone has registered. There is no endpoint that lists names, only
// endings, so `a.<ending>` is the best it could ever do and that is
// exactly the name the proxy refuses.
//
// Which made the check impossible to pass on a correctly configured
// machine: `proxy holds 127.0.0.1:443` and `no pinned-TLS proxy on this
// machine`, three lines apart, both true as written.
//
// So when this run installed the unit, started it, and watched it take the
// port, that is the answer. The probe below stays for a proxy this run did
// not start, where the question is genuinely open.
proxyAddress = { v4: DEFAULT_HOST, v6: null };
out(` ok pinned-TLS proxy on ${DEFAULT_HOST}:${PROXY_PORT} — started by this run, so every live name will answer there`);
} else if (!rest.includes("--no-proxy")) {
const probeName = moshpitProbe || "";
if (!probeName) {
Expand Down
66 changes: 66 additions & 0 deletions test/dns-enable-rollback.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,69 @@ function noSystem() {
uid: 0,
};
}

/* --------------------------- a proxy this run started is a proxy it can trust */

// Detection used to ask the proxy for a certificate under `a.<ending>`. The
// proxy will not present one for a name with no key published in the registry —
// deliberately, so a browser gets a TLS failure it can explain — and nothing
// here can invent a name someone has registered, because the registry lists
// endings and not names.
//
// So on a correctly configured machine the check could not pass. Three lines
// apart, both true as written:
//
// ok proxy holds 127.0.0.1:443
// -- no pinned-TLS proxy on this machine
//
// and the bridge was then started without proxy mode, leaving every name to
// answer its origin with a certificate no CA had signed.

test("a proxy started by this run turns proxy mode on without a handshake", async () => {
const lines = [];
let startedWith = null;
const code = await dnsCommand(["enable"], (l) => lines.push(String(l)), {
...noSystem(),
proxyWrapper: () => "/home/x/.local/bin/moshpit-proxy",
ensureProxy: async () => ({ ok: true, steps: [{ step: "proxy holds 127.0.0.1:443", ok: true }] }),
applyWith: async () => ({ saved: { ok: true }, applied: { ok: true, results: [] }, verified: { ok: true, checks: [] }, rolledBack: null, backups: [] }),
// If this is consulted at all the fix has not worked: the whole point is
// that no name is invented and no handshake is attempted.
findLocalProxyImpl: async () => { throw new Error("the probe must not run for a proxy we started"); },
startBridge: async (opts) => { startedWith = opts; return { started: true, pid: 1, alreadyRunning: false }; },
});

assert.equal(code, 0);
assert.match(lines.join("\n"), /started by this run/);
assert.equal(startedWith?.proxy, "127.0.0.1", "the bridge has to be told, or names still answer their origin");
});

test("a proxy this run did not start is still probed", async () => {
// The question is genuinely open there, so the handshake stays.
const lines = [];
let probed = false;
await dnsCommand(["enable"], (l) => lines.push(String(l)), {
...noSystem(),
proxyWrapper: () => "/home/x/.local/bin/moshpit-proxy",
ensureProxy: async () => ({ ok: false, reason: "not-listening", steps: [] }),
applyWith: async () => ({ saved: { ok: true }, applied: { ok: true, results: [] }, verified: { ok: true, checks: [] }, rolledBack: null, backups: [] }),
findLocalProxyImpl: async () => { probed = true; return { found: false, why: null, address: { v4: null, v6: null } }; },
});

assert.equal(probed, true);
});

test("with no proxy installed, nothing is claimed and DNS still comes up", async () => {
const lines = [];
let startedWith = null;
const code = await dnsCommand(["enable"], (l) => lines.push(String(l)), {
...noSystem(),
proxyWrapper: () => null,
applyWith: async () => ({ saved: { ok: true }, applied: { ok: true, results: [] }, verified: { ok: true, checks: [] }, rolledBack: null, backups: [] }),
startBridge: async (opts) => { startedWith = opts; return { started: true, pid: 1, alreadyRunning: false }; },
});

assert.equal(code, 0, "a missing optional component must never refuse the machine its DNS");
assert.equal(startedWith?.proxy, null);
assert.match(lines.join("\n"), /no pinned-TLS proxy installed/);
});
Loading