History

Trust bundled root certificates in workerd outbound TLS

workerd's built-in internet service cannot read the Windows system trust store, so npm installs inside the publish worker failed TLS verification. Declare the internet service explicitly with the runtime's bundled Mozilla roots, surface workerd stderr in startup failures, and fail the bundle when a dependency install warning is present instead of publishing a broken bundle.

Rhys Sullivan committed 4 months ago

Commit bb54fa91 parent 4be58bc2 files · +23 −2Browse files

Unified · Split

2 files
  1. packages/kernel/runtime-workerd-subprocess/src/index.ts +19 −2
  2. packages/plugins/apps/src/pipeline/worker-bundler.ts +4 −0
packages/kernel/runtime-workerd-subprocess/src/index.ts+19−2
@@ -8,6 +8,7 @@ import { createServer as createNetServer, type Server as NetServer } from "node: · 7 unmodified lines
88 import type { Readable } from "node:stream";
99 import { tmpdir } from "node:os";
1010 import { dirname, join } from "node:path";
11+import { rootCertificates } from "node:tls";
1112 import { fileURLToPath } from "node:url";
1213
1314 import {
@@ -303,9 +304,19 @@ const buildConfig = (input: { · 289 unmodified lines
303304 return `( name = ${capnpString(name)}, ${field} = embed ${capnpString(name)} )`;
304305 })
305306 .join(",\n ");
307+ // workerd's built-in `internet` service relies on the platform TLS trust
308+ // store, which kj/tls cannot read on Windows ("unable to get local issuer
309+ // certificate"). Declaring our own internet service with the runtime's
310+ // bundled Mozilla roots gives every platform the same trust behavior.
311+ const trustedCertificates = rootCertificates.map(capnpString).join(",\n ");
306312 const services = [
307313 '( name = "main", worker = .mainWorker )',
308314 '( name = "blocked", worker = .blockedWorker )',
315+ ...(input.globalOutbound === "internet"
316+ ? [
317+ `( name = "internet", network = ( allow = ["public"], tlsOptions = ( trustedCertificates = [\n ${trustedCertificates}\n ] ) ) )`,
318+ ]
319+ : []),
309320 ...(input.hostPort === null
310321 ? []
311322 : [`( name = "host", external = ( address = "127.0.0.1:${input.hostPort}", http = () ) )`]),
@@ -504,7 +515,10 @@ export const createWorkerdModuleRunner = ( · 192 unmodified lines
504515 });
505516 proc.stderr.setEncoding("utf8");
506517 proc.stdout.resume();
507− proc.stderr.resume();
518+ let stderrTail = "";
519+ proc.stderr.on("data", (chunk: string) => {
520+ stderrTail = (stderrTail + chunk).slice(-4000);
521+ });
508522 proc.on("exit", (code, signal) => {
509523 lastExitAt = Date.now();
510524 if (processState?.proc === proc) {
@@ -522,7 +536,10 @@ export const createWorkerdModuleRunner = ( · 11 unmodified lines
522536 await waitReady(listenPort, token, startupTimeoutMs);
523537 } catch (cause) {
524538 await disposeState(true);
525− throw cause;
539+ const detail = stderrTail.trim();
540+ throw detail.length === 0
541+ ? cause
542+ : new Error(`${normalizeError(cause).message}\nworkerd stderr:\n${detail}`);
526543 }
527544 })();
528545
packages/plugins/apps/src/pipeline/worker-bundler.ts+4−0
@@ -81,6 +81,10 @@ export default { · 80 unmodified lines
8181 if (code === null) {
8282 return json({ ok: false, message: "worker-bundler did not return JavaScript for " + result.mainModule });
8383 }
84+ const installFailure = (result.warnings ?? []).find((warning) => /failed to install/i.test(String(warning)));
85+ if (installFailure) {
86+ return json({ ok: false, message: String(installFailure) });
87+ }
8488 for (const [path, module] of Object.entries(result.modules)) {
8589 const source = moduleCode(module) ?? "";
8690 if (/\\.node(?:$|[?#])|node-gyp|prebuild-install|node-pre-gyp/.test(path) || /\\.node(?:$|[?#])|node-gyp|prebuild-install|node-pre-gyp/.test(source)) {