| 1 | import { execFile } from "node:child_process"; |
| 2 | import { copyFile, readFile } from "node:fs/promises"; |
| 3 | import tls from "node:tls"; |
| 4 | import { promisify } from "node:util"; |
| 5 | |
| 6 | /** Where Cloudflare puts the CA it intercepts a container's HTTPS with, once the container is up. */ |
| 7 | export const EGRESS_CA = "/etc/cloudflare/certs/cloudflare-containers-ca.crt"; |
| 8 | |
| 9 | /** |
| 10 | * Under web-only egress (DIFFINERY_EGRESS=web, apps/diffinery/src/lib/egress.ts) HTTPS leaves the |
| 11 | * container through Cloudflare's interception, signed by EGRESS_CA. The agent trusts it for its own |
| 12 | * requests and adds it to the system store (git, curl, Docker, Python's ssl) and to the variables |
| 13 | * Node.js and Python's requests read, so the steps and commands it starts trust it too. |
| 14 | */ |
| 15 | export async function trustEgressCa(env: NodeJS.ProcessEnv = process.env): Promise<void> { |
| 16 | if (env.DIFFINERY_EGRESS !== "web") return; |
| 17 | let pem = ""; |
| 18 | for (let i = 0; i < 100 && !pem; i++) { |
| 19 | pem = await readFile(EGRESS_CA, "utf8").catch(() => ""); |
| 20 | if (!pem) await new Promise((r) => setTimeout(r, 100)); |
| 21 | } |
| 22 | if (!pem) { |
| 23 | process.stderr.write(`[diffinery-runner] web-only egress, but ${EGRESS_CA} did not appear: HTTPS will fail\n`); |
| 24 | return; |
| 25 | } |
| 26 | tls.setDefaultCACertificates([...tls.getCACertificates("default"), pem]); |
| 27 | try { |
| 28 | await copyFile(EGRESS_CA, "/usr/local/share/ca-certificates/cloudflare-containers-ca.crt"); |
| 29 | await promisify(execFile)("update-ca-certificates"); |
| 30 | } catch (e) { |
| 31 | process.stderr.write(`[diffinery-runner] could not add the egress CA to the system store: ${(e as Error).message}\n`); |
| 32 | } |
| 33 | env.NODE_EXTRA_CA_CERTS ??= EGRESS_CA; |
| 34 | env.REQUESTS_CA_BUNDLE ??= "/etc/ssl/certs/ca-certificates.crt"; |
| 35 | } |