| 1 | import * as Context from "effect/Context"; |
| 2 | import * as Layer from "effect/Layer"; |
| 3 | import * as NodeFs from "node:fs"; |
| 4 | import * as NodeTls from "node:tls"; |
| 5 | import type * as Config from "../workerd/Config.ts"; |
| 6 | |
| 7 | export class Internet extends Context.Service<Internet, Config.Service>()( |
| 8 | "cloudflare-runtime/Internet", |
| 9 | ) {} |
| 10 | |
| 11 | /** |
| 12 | * Extra trusted CAs from `NODE_EXTRA_CA_CERTS`, read FRESH on each call. |
| 13 | * |
| 14 | * This deliberately does NOT match Node's read-once-at-startup semantics: |
| 15 | * this layer is built once per long-lived sidecar process, but the file the |
| 16 | * env var points at may not exist yet when the sidecar starts — `alchemy |
| 17 | * dev` points it at the local emulator's self-signed CA |
| 18 | * (`~/.floci/ca.pem`), which is minted while the FIRST apply is already |
| 19 | * deploying workers in parallel. Reading at every workerd config build |
| 20 | * means the first worker (re)start after the CA lands trusts it, instead |
| 21 | * of the whole sidecar being stuck with an empty store for its lifetime. |
| 22 | */ |
| 23 | const extraTrustedCertificates = (): string[] => { |
| 24 | const bundlePath = process.env.NODE_EXTRA_CA_CERTS; |
| 25 | if (bundlePath === undefined) return []; |
| 26 | let pem: string; |
| 27 | try { |
| 28 | pem = NodeFs.readFileSync(bundlePath, "utf8"); |
| 29 | } catch { |
| 30 | return []; |
| 31 | } |
| 32 | // Split the bundle into individual certificates and add each individually: |
| 33 | // https://github.com/cloudflare/miniflare/pull/587/files#r1271579671 |
| 34 | return ( |
| 35 | pem.match( |
| 36 | /-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g, |
| 37 | ) ?? [] |
| 38 | ); |
| 39 | }; |
| 40 | |
| 41 | export const InternetLive = Layer.sync(Internet, () => { |
| 42 | // `workerd`'s `trustBrowserCas` should probably be named `trustSystemCas`. |
| 43 | // Rather than using a bundled CA store like Node, it uses |
| 44 | // `SSL_CTX_set_default_verify_paths()` to use the system CA store: |
| 45 | // https://github.com/capnproto/capnproto/blob/6e26d260d1d91e0465ca12bbb5230a1dfa28f00d/c%2B%2B/src/kj/compat/tls.c%2B%2B#L745 |
| 46 | // Unfortunately, this doesn't work on Windows. Luckily, Node exposes its own |
| 47 | // bundled CA store's certificates, so we just use those. |
| 48 | const baseCertificates = |
| 49 | process.platform === "win32" ? Array.from(NodeTls.rootCertificates) : []; |
| 50 | return { |
| 51 | name: "internet", |
| 52 | get network() { |
| 53 | return { |
| 54 | // Allow access to private/public addresses: |
| 55 | // https://github.com/cloudflare/miniflare/issues/412 |
| 56 | allow: ["public", "private", "240.0.0.0/4"], |
| 57 | deny: [], |
| 58 | tlsOptions: { |
| 59 | trustBrowserCas: true, |
| 60 | trustedCertificates: [ |
| 61 | ...baseCertificates, |
| 62 | ...extraTrustedCertificates(), |
| 63 | ], |
| 64 | }, |
| 65 | }; |
| 66 | }, |
| 67 | }; |
| 68 | }); |