moshcode 0.84.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/dns-service.mjs +12 -7
- package/src/trust.mjs +87 -10
package/package.json
CHANGED
package/src/dns-service.mjs
CHANGED
|
@@ -33,6 +33,7 @@ import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
|
33
33
|
import { existsSync } from "node:fs";
|
|
34
34
|
import { homedir } from "node:os";
|
|
35
35
|
import { dirname, join } from "node:path";
|
|
36
|
+
import { operatorHome } from "./trust.mjs";
|
|
36
37
|
|
|
37
38
|
export const UNIT_NAME = "moshcode-dns.service";
|
|
38
39
|
|
|
@@ -217,7 +218,7 @@ export function proxyServicePaths() {
|
|
|
217
218
|
export function proxyServiceUnit({
|
|
218
219
|
wrapper,
|
|
219
220
|
nodeDir,
|
|
220
|
-
home =
|
|
221
|
+
home = operatorHome(),
|
|
221
222
|
user = process.env.SUDO_USER || process.env.USER || process.env.LOGNAME,
|
|
222
223
|
port = 443,
|
|
223
224
|
tlds = [],
|
|
@@ -249,10 +250,14 @@ export function proxyServiceUnit({
|
|
|
249
250
|
`Environment=MOSHPIT_PROXY_PORT=${port}`,
|
|
250
251
|
`Environment=MOSHPIT_PROXY_DIR=${dir}`,
|
|
251
252
|
];
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
|
|
253
|
+
// Deliberately not written any more.
|
|
254
|
+
//
|
|
255
|
+
// It used to be set from the endings the registry had sold — 18224 of them, a
|
|
256
|
+
// ~150 KB environment variable in a unit file, for a list stale the next time
|
|
257
|
+
// one is sold. moshpit-proxy now reads an unset value as "every Moshpit
|
|
258
|
+
// ending", defining the namespace by excluding the real internet rather than
|
|
259
|
+
// by enumerating what Moshpit owns, so there is nothing left to pass. Setting
|
|
260
|
+
// it there still narrows, which is a deployment's choice and not this unit's.
|
|
256
261
|
|
|
257
262
|
lines.push(
|
|
258
263
|
`ExecStart=${wrapper}`,
|
|
@@ -273,7 +278,7 @@ export function proxyServiceUnit({
|
|
|
273
278
|
}
|
|
274
279
|
|
|
275
280
|
/** Where moshpit-proxy's installer puts its wrapper, if it ran. */
|
|
276
|
-
export function proxyWrapperPath({ home =
|
|
281
|
+
export function proxyWrapperPath({ home = operatorHome(), exists = existsSync } = {}) {
|
|
277
282
|
const candidate = join(home, ".local/bin/moshpit-proxy");
|
|
278
283
|
return exists(candidate) ? candidate : null;
|
|
279
284
|
}
|
|
@@ -293,7 +298,7 @@ export function proxyWrapperPath({ home = homedir(), exists = existsSync } = {})
|
|
|
293
298
|
* answer, so the wait is generous.
|
|
294
299
|
*/
|
|
295
300
|
export async function ensureProxyService({
|
|
296
|
-
home =
|
|
301
|
+
home = operatorHome(),
|
|
297
302
|
user = process.env.SUDO_USER || process.env.USER || process.env.LOGNAME,
|
|
298
303
|
nodeDir = dirname(process.execPath),
|
|
299
304
|
tlds = [],
|
package/src/trust.mjs
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import os from "node:os";
|
|
22
22
|
import { execFileSync } from "node:child_process";
|
|
23
|
+
import { IANA_TLDS } from "./iana-tlds.mjs";
|
|
23
24
|
|
|
24
25
|
/** Where moshpit-proxy generates its root on first run. */
|
|
25
26
|
export function caPath({ home = os.homedir(), dir = null } = {}) {
|
|
@@ -139,20 +140,74 @@ export function requireNameConstraints(text, { tlds = [] } = {}) {
|
|
|
139
140
|
// `excluded;DNS:.hacker` alone is an unconstrained root wearing the word
|
|
140
141
|
// "constraints". Requiring a permitted DNS subtree is what makes the rest of
|
|
141
142
|
// this check mean anything.
|
|
143
|
+
const bare = (entry) => entry.replace(/^\./, "");
|
|
144
|
+
|
|
145
|
+
// Two shapes are acceptable, and they make the same promise a different way.
|
|
146
|
+
//
|
|
147
|
+
// The old one permits a list of Moshpit endings and is bounded by what it
|
|
148
|
+
// names. It cannot scale: there are 18224 endings, a permitted subtree each
|
|
149
|
+
// is roughly 214 KB of constraints on every handshake, and the list is stale
|
|
150
|
+
// the next time the registry sells one — which is why a machine could reach
|
|
151
|
+
// `.2600` over HTTPS and not `.hacker`.
|
|
152
|
+
//
|
|
153
|
+
// The new one names nothing and excludes the real internet instead: all 1438
|
|
154
|
+
// delegated top-level domains, about 15 KB, covering every Moshpit ending
|
|
155
|
+
// that exists or ever will. RFC 5280 4.2.1.10 leaves a name type unrestricted
|
|
156
|
+
// when no permitted subtree names it, which is what makes that work — and is
|
|
157
|
+
// also exactly what an unconstrained root looks like, so the difference has
|
|
158
|
+
// to be established rather than assumed.
|
|
159
|
+
//
|
|
160
|
+
// It is established against the same IANA list this tool refuses to sell
|
|
161
|
+
// endings from. A root that excludes the internet cannot forge your bank,
|
|
162
|
+
// which is the whole property the old shape bought by enumeration.
|
|
142
163
|
if (!constraints.permitted.length) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
164
|
+
const excluded = new Set(constraints.excluded.map(bare));
|
|
165
|
+
const covered = [...IANA_TLDS].filter((tld) => excluded.has(tld));
|
|
166
|
+
const uncovered = [...IANA_TLDS].filter((tld) => !excluded.has(tld));
|
|
167
|
+
|
|
168
|
+
// A handful of exclusions is not this shape; it is an unconstrained root
|
|
169
|
+
// with a few names crossed out, which is the thing this gate exists to
|
|
170
|
+
// refuse. The threshold sits far below the real count on purpose: the
|
|
171
|
+
// question is "is the internet excluded", not "is this list current".
|
|
172
|
+
if (covered.length < 1000) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
kind: "unconstrained",
|
|
176
|
+
why: covered.length
|
|
177
|
+
? `the root excludes only ${covered.length} real top-level domains — it could still vouch for the rest of the internet`
|
|
178
|
+
: "the root permits no DNS subtree and excludes no real domains, so it could vouch for any name",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// A root minted before a TLD was delegated does not exclude it. A real gap,
|
|
183
|
+
// and a small one, so it is reported rather than made fatal: refusing here
|
|
184
|
+
// would mean refusing every root the day IANA adds a name.
|
|
185
|
+
return uncovered.length
|
|
186
|
+
? { ok: true, why: `excludes ${covered.length} real top-level domains`, uncovered }
|
|
187
|
+
: { ok: true, why: `excludes all ${covered.length} real top-level domains` };
|
|
148
188
|
}
|
|
149
189
|
|
|
150
|
-
const bare = (entry) => entry.replace(/^\./, "");
|
|
151
190
|
const permits = (tld) => constraints.permitted.some((entry) => bare(entry) === String(tld).toLowerCase());
|
|
152
191
|
|
|
153
192
|
const missing = tlds.filter((tld) => !permits(tld));
|
|
154
|
-
|
|
155
|
-
|
|
193
|
+
// Not a refusal, and this was the single most damaging line in the file.
|
|
194
|
+
//
|
|
195
|
+
// The root is minted by moshpit-proxy for the endings it was configured to
|
|
196
|
+
// serve — a handful. `tlds` is every ending the registry has sold, which is
|
|
197
|
+
// 18224 and climbing. So this could never pass on a real machine: it refused
|
|
198
|
+
// to install a perfectly good root because it did not also cover 18000
|
|
199
|
+
// endings nobody on that machine was trying to reach, and printed all of them
|
|
200
|
+
// as evidence. The advice it gave — regenerate the root — cannot help,
|
|
201
|
+
// because the new root is scoped to the same handful.
|
|
202
|
+
//
|
|
203
|
+
// A root that covers some of what you resolve is worth exactly what it
|
|
204
|
+
// covers. So it goes in, and the shortfall is a count rather than a wall.
|
|
205
|
+
if (missing.length === tlds.length) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
kind: "out-of-step",
|
|
209
|
+
why: `the root permits none of the endings claimed (${summarise(missing)}) — it is for a different namespace`,
|
|
210
|
+
};
|
|
156
211
|
}
|
|
157
212
|
// And nothing beyond them. One `DNS:.com` in the permitted subtree is the
|
|
158
213
|
// whole hole this gate exists to close, and it would otherwise sail through
|
|
@@ -163,10 +218,32 @@ export function requireNameConstraints(text, { tlds = [] } = {}) {
|
|
|
163
218
|
return {
|
|
164
219
|
ok: false,
|
|
165
220
|
kind: "out-of-step",
|
|
166
|
-
why: `the root also permits ${foreign
|
|
221
|
+
why: `the root also permits ${summarise(foreign)}, which is not an ending we resolve — it reaches past Moshpit`,
|
|
167
222
|
};
|
|
168
223
|
}
|
|
169
|
-
|
|
224
|
+
// Carried, not printed here: the caller decides how loud a partial root is,
|
|
225
|
+
// and it is the only place that knows whether the person asked about a name
|
|
226
|
+
// the root actually covers.
|
|
227
|
+
return missing.length
|
|
228
|
+
? {
|
|
229
|
+
ok: true,
|
|
230
|
+
why: `constrained to Moshpit endings — covers ${tlds.length - missing.length} of ${tlds.length}`,
|
|
231
|
+
missing,
|
|
232
|
+
}
|
|
233
|
+
: { ok: true, why: "constrained to Moshpit endings" };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* A list a person can read, rather than one that fills the terminal.
|
|
238
|
+
*
|
|
239
|
+
* A registry with 18224 endings turns any "these are wrong" message into
|
|
240
|
+
* several screens of text, which is not a diagnostic — it is the reason nobody
|
|
241
|
+
* reads the line above it.
|
|
242
|
+
*/
|
|
243
|
+
export function summarise(items, show = 8) {
|
|
244
|
+
const list = [...items];
|
|
245
|
+
if (list.length <= show) return list.join(", ");
|
|
246
|
+
return `${list.slice(0, show).join(", ")} and ${list.length - show} more`;
|
|
170
247
|
}
|
|
171
248
|
|
|
172
249
|
/**
|