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
8 changes: 8 additions & 0 deletions src/dns-service.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export function serviceUnit({
port,
registryBase = null,
upstreams = [],
proxy = null,
user = process.env.USER || process.env.LOGNAME,
} = {}) {
if (!entry) throw new Error("serviceUnit needs the entry script to run");
Expand All @@ -80,6 +81,13 @@ export function serviceUnit({
// registry. Recorded here, while a working resolver is still around to be
// asked, rather than rediscovered at boot when it cannot be.
if (upstreams.length) args.push("--upstream", upstreams.join(","));
// Without this the supervised bridge answers every name with its origin, and
// the origin serves a certificate no CA signed — so `https://` fails on a
// machine where the pinned-TLS proxy is installed, trusted and running.
// `dns enable` has always probed for the proxy and passed it through;
// `dns service` did not, which made a service-managed bridge the one way to
// run Moshpit where HTTPS could never work.
if (proxy) args.push("--proxy", proxy);
const exec = [execPath, ...args].map((part) => (/\s/.test(part) ? JSON.stringify(part) : part)).join(" ");

const lines = [
Expand Down
28 changes: 27 additions & 1 deletion src/dns.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2788,7 +2788,33 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
const upstreams = upstreamsFromArgs(rest).servers.length
? upstreamsFromArgs(rest).servers
: await discoverUpstreams();
const unit = serviceUnit({ system, entry: cliEntry(), port, registryBase, upstreams });
// Probed the same way `enable` probes it, and for the same reason: a name
// that resolves but cannot complete a TLS handshake reads as broken to the
// person who typed the URL. Asked now, while this command can ask; the unit
// it writes runs at boot and has no way to find out later.
let proxy = null;
if (!rest.includes("--no-proxy")) {
const claimed = await fetchTlds({ registryBase }).catch(() => []);
const probeName = claimed[0] ? `a.${claimed[0]}` : null;
if (probeName) {
const local = await findLocalProxyImpl(probeName).catch(() => ({ found: false }));
if (local.found) proxy = local.address.v4 || local.address.v6;
}
}

const unit = serviceUnit({ system, entry: cliEntry(), port, registryBase, upstreams, proxy });

if (proxy) {
out(`ok pinned-TLS proxy on ${proxy}:${PROXY_PORT} — names will answer there, so https:// verifies`);
out("");
} else if (!rest.includes("--no-proxy")) {
out("! no pinned-TLS proxy found on this machine");
out(" names will answer their origin, and a stock client cannot verify those —");
out(" https:// will fail even though the name resolves. Install it with:");
out(" curl -fsSL https://raw.githubusercontent.com/profullstack/moshpit-proxy/main/install.sh | sh");
out(" then re-run this command so the unit points names at it.");
out("");
}

if (!upstreams.length) {
out("! no upstream nameservers found, and none given");
Expand Down
29 changes: 29 additions & 0 deletions test/dns-service.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,32 @@ test("remove takes the unit away even when it was never enabled", async () => {
assert.equal(result.ok, true);
assert.equal(existsSync(join(home, ".config/systemd/user", UNIT_NAME)), false);
});

/* ------------------------------------------------- pointing at the proxy */

// A supervised bridge that answers with the origin gives every name a
// certificate no CA signed, so `https://` fails on a machine where the
// pinned-TLS proxy is installed, trusted and running. `dns enable` has always
// probed for the proxy and passed it to the daemon; `dns service` did not,
// which made a service-managed bridge the one way to run Moshpit where HTTPS
// could not work at all.

test("the unit points names at the local proxy when there is one", () => {
assert.match(unit({ proxy: "127.0.0.1" }), /ExecStart=.* --proxy 127\.0\.0\.1$/m);
});

test("a v6 proxy address survives into the unit", () => {
assert.match(unit({ proxy: "::1" }), /--proxy ::1$/m);
});

test("no proxy means no flag, not an empty one", () => {
// `--proxy` with nothing after it would make the daemon read the next token
// as an address, and there is no next token.
assert.doesNotMatch(unit({ proxy: null }), /--proxy/);
assert.doesNotMatch(unit(), /--proxy/);
});

test("upstreams and proxy coexist without eating each other's values", () => {
const text = unit({ upstreams: ["1.1.1.1", "1.0.0.1"], proxy: "127.0.0.1" });
assert.match(text, /--upstream 1\.1\.1\.1,1\.0\.0\.1 --proxy 127\.0\.0\.1$/m);
});
Loading