diff --git a/.changeset/extra-ca-certs-deploy.md b/.changeset/extra-ca-certs-deploy.md new file mode 100644 index 00000000000..f86854b078a --- /dev/null +++ b/.changeset/extra-ca-certs-deploy.md @@ -0,0 +1,5 @@ +--- +"trigger.dev": patch +--- + +Fix `extraCACerts` in `trigger.config.ts` having no effect on deployed tasks. The configured CA file is now copied into the deployment image via the `additionalFiles` build extension, and the deploy command passes its image path to the `NODE_EXTRA_CA_CERTS` build argument, so self-signed or corporate certificates are trusted at runtime. diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 02f1a0bfcc4..987c042e564 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -50,7 +50,7 @@ import { } from "../cli/common.js"; import { loadConfig } from "../config.js"; import { authenticateForDeploy, userIdForDeploy } from "../deploy/auth.js"; -import { buildImage } from "../deploy/buildImage.js"; +import { buildImage, resolveExtraCACertsImagePath } from "../deploy/buildImage.js"; import { type DeployEnvironment, resolveDeployEnvironment } from "../deploy/environment.js"; import { checkLogsForErrors, @@ -776,6 +776,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { externalBuildProjectId: deployment.externalBuildData?.projectId, projectId: projectClient.id, projectRef: resolvedConfig.project, + extraCACerts: resolveExtraCACertsImagePath( + resolvedConfig.extraCACerts, + resolvedConfig.workingDir + ), apiUrl: projectClient.client.apiURL, apiKey: projectClient.client.accessToken!, apiClient: projectClient.client, diff --git a/packages/cli-v3/src/config.ts b/packages/cli-v3/src/config.ts index a34138cd4c1..eb098e84043 100644 --- a/packages/cli-v3/src/config.ts +++ b/packages/cli-v3/src/config.ts @@ -301,6 +301,14 @@ async function autoDetectDirs(workingDir: string): Promise { } function validateConfig(config: TriggerConfig, warn = true) { + if (config.extraCACerts) { + // The CA file must be inside the build output so the image can point + // NODE_EXTRA_CA_CERTS at it + config.build ??= {}; + config.build.extensions ??= []; + config.build.extensions.push(additionalFiles({ files: [config.extraCACerts] })); + } + if (config.additionalFiles && config.additionalFiles.length > 0) { warn && prettyWarning( diff --git a/packages/cli-v3/src/deploy/buildImage.test.ts b/packages/cli-v3/src/deploy/buildImage.test.ts index a136c4c95a7..7c57a9e2b38 100644 --- a/packages/cli-v3/src/deploy/buildImage.test.ts +++ b/packages/cli-v3/src/deploy/buildImage.test.ts @@ -12,6 +12,7 @@ import { TOOLCHAIN_PACKAGES, formatCommandForDiagnostics, generateContainerfile, + resolveExtraCACertsImagePath, } from "./buildImage.js"; import { EMSIGN_COMPROMISE_REJECTED_INTERMEDIATES } from "./emsignRejectedIntermediates.js"; @@ -56,6 +57,26 @@ const images: Array<[BuildRuntime, string, string]> = [ ], ]; +describe("resolveExtraCACertsImagePath", () => { + it("returns undefined when no CA file is configured", () => { + expect(resolveExtraCACertsImagePath(undefined, "/project")).toBeUndefined(); + }); + + it("maps a project-relative path into /app", () => { + expect(resolveExtraCACertsImagePath("./certs/ca.crt", "/project")).toBe("/app/certs/ca.crt"); + }); + + it("strips leading parent segments the same way additionalFiles does", () => { + expect(resolveExtraCACertsImagePath("../shared/ca.crt", "/project")).toBe("/app/shared/ca.crt"); + }); + + it("maps an absolute path inside the project into /app", () => { + expect(resolveExtraCACertsImagePath("/project/certs/ca.crt", "/project")).toBe( + "/app/certs/ca.crt" + ); + }); +}); + describe("generateContainerfile", () => { it.each(images)("uses the pinned published base images for %s", async (runtime, base, build) => { const containerfile = await generateContainerfile({ diff --git a/packages/cli-v3/src/deploy/buildImage.ts b/packages/cli-v3/src/deploy/buildImage.ts index c7f300b5060..061f535c91e 100644 --- a/packages/cli-v3/src/deploy/buildImage.ts +++ b/packages/cli-v3/src/deploy/buildImage.ts @@ -3,7 +3,7 @@ import { depot } from "@depot/cli"; import { x } from "tinyexec"; import type { BuildManifest, BuildRuntime } from "@trigger.dev/core/v3/schemas"; import { networkInterfaces } from "os"; -import { join } from "path"; +import { isAbsolute, join, relative } from "path"; import { safeReadJSONFile } from "../utilities/fileSystem.js"; import { readFileSync } from "fs"; @@ -1305,3 +1305,25 @@ function getOutputOptions({ return outputOptions; } + +// The extraCACerts config option is project-relative, but NODE_EXTRA_CA_CERTS +// must point at the file inside the deployed image. additionalFiles copies it +// into the build output root, which becomes /app in the image. +export function resolveExtraCACertsImagePath( + extraCACerts: string | undefined, + workingDir: string +): string | undefined { + if (!extraCACerts) { + return undefined; + } + + const projectRelativePath = isAbsolute(extraCACerts) + ? relative(workingDir, extraCACerts) + : extraCACerts; + + const segments = projectRelativePath + .split(/[\\/]+/) + .filter((segment) => segment && segment !== "." && segment !== ".."); + + return `/app/${segments.join("/")}`; +}