Skip to content

Commit 2694ece

Browse files
authored
fix(db): inline RDS CA bundle to bypass Turbopack ignoring outputFileTracingIncludes
fix(db): inline RDS CA bundle to bypass Turbopack ignoring outputFileTracingIncludes
1 parent 4e8f9b1 commit 2694ece

13 files changed

Lines changed: 130 additions & 102 deletions

File tree

apps/app/next.config.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,6 @@ const config: NextConfig = {
7676
webpackMemoryOptimizations: true,
7777
},
7878
outputFileTracingRoot: workspaceRoot,
79-
outputFileTracingIncludes: {
80-
'/**/*': ['../../packages/db/certs/rds-global-bundle.pem'],
81-
},
8279

8380
// Reduce memory usage during production build
8481
productionBrowserSourceMaps: false,

apps/app/prisma/client.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { PrismaClient } from '@prisma/client';
22
import { PrismaPg } from '@prisma/adapter-pg';
33

4+
import { RDS_CA_BUNDLE } from './rds-ca-bundle';
5+
46
const globalForPrisma = global as unknown as { prisma?: PrismaClient };
57

68
const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);
@@ -24,27 +26,22 @@ function isLocalhostUrl(connectionString: string): boolean {
2426
function createPrismaClient(): PrismaClient {
2527
const rawUrl = process.env.DATABASE_URL!;
2628
const isLocalhost = isLocalhostUrl(rawUrl);
27-
const hasCABundle = !!process.env.NODE_EXTRA_CA_CERTS;
2829
const allowInsecure = process.env.PRISMA_ALLOW_INSECURE_TLS === '1';
2930

3031
let ssl:
3132
| undefined
32-
| { checkServerIdentity: () => undefined }
33+
| { ca: string; checkServerIdentity: () => undefined }
3334
| { rejectUnauthorized: false };
3435
if (isLocalhost) {
3536
ssl = undefined;
36-
} else if (hasCABundle) {
37-
// Verified TLS: rely on Node's TLS context (NODE_EXTRA_CA_CERTS adds the AWS
38-
// RDS CA to the trust store). Skip hostname check because connections may
39-
// traverse an AWS NLB whose hostname isn't in the RDS Proxy cert's SAN list.
40-
// The chain check still rejects forged or wrong-CA certs.
41-
ssl = { checkServerIdentity: () => undefined };
4237
} else if (allowInsecure) {
4338
ssl = { rejectUnauthorized: false };
4439
} else {
45-
throw new Error(
46-
'Refusing to connect to a non-local Postgres without TLS verification. Set NODE_EXTRA_CA_CERTS to a CA bundle, or set PRISMA_ALLOW_INSECURE_TLS=1 if you intentionally want unverified TLS.',
47-
);
40+
// Verified TLS using the inlined AWS RDS CA bundle. Skip hostname check
41+
// because connections may traverse an AWS NLB whose hostname isn't in the
42+
// RDS Proxy cert's SAN list. The chain check still rejects forged or
43+
// wrong-CA certs.
44+
ssl = { ca: RDS_CA_BUNDLE, checkServerIdentity: () => undefined };
4845
}
4946

5047
const url = ssl !== undefined ? stripSslMode(rawUrl) : rawUrl;
@@ -57,10 +54,6 @@ function createPrismaClient(): PrismaClient {
5754
});
5855
}
5956

60-
// Lazy initialization. Importing this module does NOT construct a Prisma client
61-
// — that only happens on first property access on `db`. Critical so that
62-
// Next.js `next build` (which imports every route handler to analyze it) does
63-
// not trigger the strict TLS check at build time when no actual queries run.
6457
function getClient(): PrismaClient {
6558
if (!globalForPrisma.prisma) {
6659
globalForPrisma.prisma = createPrismaClient();

apps/app/prisma/rds-ca-bundle.ts

Lines changed: 7 additions & 0 deletions
Large diffs are not rendered by default.

apps/framework-editor/prisma/client.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { PrismaClient } from '@prisma/client';
22
import { PrismaPg } from '@prisma/adapter-pg';
33

4+
import { RDS_CA_BUNDLE } from './rds-ca-bundle';
5+
46
const globalForPrisma = global as unknown as { prisma?: PrismaClient };
57

68
const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);
@@ -24,27 +26,18 @@ function isLocalhostUrl(connectionString: string): boolean {
2426
function createPrismaClient(): PrismaClient {
2527
const rawUrl = process.env.DATABASE_URL!;
2628
const isLocalhost = isLocalhostUrl(rawUrl);
27-
const hasCABundle = !!process.env.NODE_EXTRA_CA_CERTS;
2829
const allowInsecure = process.env.PRISMA_ALLOW_INSECURE_TLS === '1';
2930

3031
let ssl:
3132
| undefined
32-
| { checkServerIdentity: () => undefined }
33+
| { ca: string; checkServerIdentity: () => undefined }
3334
| { rejectUnauthorized: false };
3435
if (isLocalhost) {
3536
ssl = undefined;
36-
} else if (hasCABundle) {
37-
// Verified TLS: rely on Node's TLS context (NODE_EXTRA_CA_CERTS adds the AWS
38-
// RDS CA to the trust store). Skip hostname check because connections may
39-
// traverse an AWS NLB whose hostname isn't in the RDS Proxy cert's SAN list.
40-
// The chain check still rejects forged or wrong-CA certs.
41-
ssl = { checkServerIdentity: () => undefined };
4237
} else if (allowInsecure) {
4338
ssl = { rejectUnauthorized: false };
4439
} else {
45-
throw new Error(
46-
'Refusing to connect to a non-local Postgres without TLS verification. Set NODE_EXTRA_CA_CERTS to a CA bundle, or set PRISMA_ALLOW_INSECURE_TLS=1 if you intentionally want unverified TLS.',
47-
);
40+
ssl = { ca: RDS_CA_BUNDLE, checkServerIdentity: () => undefined };
4841
}
4942

5043
const url = ssl !== undefined ? stripSslMode(rawUrl) : rawUrl;
@@ -57,10 +50,6 @@ function createPrismaClient(): PrismaClient {
5750
});
5851
}
5952

60-
// Lazy initialization. Importing this module does NOT construct a Prisma client
61-
// — that only happens on first property access on `db`. Critical so that
62-
// Next.js `next build` (which imports every route handler to analyze it) does
63-
// not trigger the strict TLS check at build time when no actual queries run.
6453
function getClient(): PrismaClient {
6554
if (!globalForPrisma.prisma) {
6655
globalForPrisma.prisma = createPrismaClient();

apps/framework-editor/prisma/rds-ca-bundle.ts

Lines changed: 7 additions & 0 deletions
Large diffs are not rendered by default.

apps/portal/next.config.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,6 @@ const config = {
6565
},
6666
skipTrailingSlashRedirect: true,
6767
outputFileTracingRoot: path.join(__dirname, '../../'),
68-
outputFileTracingIncludes: {
69-
'/**/*': ['../../packages/db/certs/rds-global-bundle.pem'],
70-
},
7168
...(isStandalone
7269
? {
7370
output: 'standalone' as const,

apps/portal/prisma/client.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { PrismaClient } from '../src/generated/prisma/client';
22
import { PrismaPg } from '@prisma/adapter-pg';
33

4+
import { RDS_CA_BUNDLE } from './rds-ca-bundle';
5+
46
const globalForPrisma = global as unknown as { prisma?: PrismaClient };
57

68
const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);
@@ -24,27 +26,18 @@ function isLocalhostUrl(connectionString: string): boolean {
2426
function createPrismaClient(): PrismaClient {
2527
const rawUrl = process.env.DATABASE_URL!;
2628
const isLocalhost = isLocalhostUrl(rawUrl);
27-
const hasCABundle = !!process.env.NODE_EXTRA_CA_CERTS;
2829
const allowInsecure = process.env.PRISMA_ALLOW_INSECURE_TLS === '1';
2930

3031
let ssl:
3132
| undefined
32-
| { checkServerIdentity: () => undefined }
33+
| { ca: string; checkServerIdentity: () => undefined }
3334
| { rejectUnauthorized: false };
3435
if (isLocalhost) {
3536
ssl = undefined;
36-
} else if (hasCABundle) {
37-
// Verified TLS: rely on Node's TLS context (NODE_EXTRA_CA_CERTS adds the AWS
38-
// RDS CA to the trust store). Skip hostname check because connections may
39-
// traverse an AWS NLB whose hostname isn't in the RDS Proxy cert's SAN list.
40-
// The chain check still rejects forged or wrong-CA certs.
41-
ssl = { checkServerIdentity: () => undefined };
4237
} else if (allowInsecure) {
4338
ssl = { rejectUnauthorized: false };
4439
} else {
45-
throw new Error(
46-
'Refusing to connect to a non-local Postgres without TLS verification. Set NODE_EXTRA_CA_CERTS to a CA bundle, or set PRISMA_ALLOW_INSECURE_TLS=1 if you intentionally want unverified TLS.',
47-
);
40+
ssl = { ca: RDS_CA_BUNDLE, checkServerIdentity: () => undefined };
4841
}
4942

5043
const url = ssl !== undefined ? stripSslMode(rawUrl) : rawUrl;
@@ -57,10 +50,6 @@ function createPrismaClient(): PrismaClient {
5750
});
5851
}
5952

60-
// Lazy initialization. Importing this module does NOT construct a Prisma client
61-
// — that only happens on first property access on `db`. Critical so that
62-
// Next.js `next build` (which imports every route handler to analyze it) does
63-
// not trigger the strict TLS check at build time when no actual queries run.
6453
function getClient(): PrismaClient {
6554
if (!globalForPrisma.prisma) {
6655
globalForPrisma.prisma = createPrismaClient();

apps/portal/prisma/rds-ca-bundle.ts

Lines changed: 7 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,32 @@
11
# Secure RDS TLS — Deploy Checklist
22

3-
After merging the secure-rds-tls PR, the following env vars must be set per environment.
4-
53
## Vercel (apps/app and apps/portal)
64

7-
Set on each Vercel project, all environments (Production + Preview + Development):
8-
9-
```
10-
NODE_EXTRA_CA_CERTS=/var/task/packages/db/certs/rds-global-bundle.pem
11-
```
12-
13-
Verified on staging (apps/app): `process.cwd()` is `/var/task/apps/app`, the cert is traced
14-
into the deploy at `/var/task/packages/db/certs/rds-global-bundle.pem` (165408 bytes), and
15-
`/api/health` succeeds end-to-end. The cert is bundled via `outputFileTracingIncludes` in
16-
each app's `next.config.ts`.
5+
**No env var or `outputFileTracingIncludes` config required.** The AWS RDS CA
6+
bundle is inlined as a TypeScript constant (`RDS_CA_BUNDLE`) and passed
7+
directly to the Postgres adapter via `ssl.ca`. This works under both Webpack
8+
and Turbopack since it's just a string the bundler always emits.
179

18-
## Downstream consumers (comp-private/apps/enterprise-api, etc.)
10+
Background: `outputFileTracingIncludes` is silently no-op'd under Turbopack
11+
(`next/dist/build/index.js` line ~1537 gates `collectBuildTraces` on
12+
`bundler !== Bundler.Turbopack`). All current Vercel deployments use Turbopack
13+
(metadata `bundler: "turbopack"`), which is why the file-based approach from
14+
PR #2761 failed in production for app-router page routes.
1915

20-
The CA bundle now ships with the published `@trycompai/db` package (added to the `files` array
21-
in this PR). After the next `@trycompai/db` publish, downstream consumers can ship the cert with
22-
their own Vercel/Docker/Trigger.dev builds without committing a copy.
23-
24-
For Vercel-deployed apps that install `@trycompai/db` from npm:
25-
26-
1. Bump the dependency to the version that includes `certs/`.
27-
2. Add `outputFileTracingIncludes` to `next.config.{ts,mjs}`:
28-
```ts
29-
outputFileTracingIncludes: {
30-
'/**/*': ['./node_modules/@trycompai/db/certs/rds-global-bundle.pem'],
31-
},
32-
```
33-
3. Set the Vercel env var:
34-
```
35-
NODE_EXTRA_CA_CERTS=/var/task/node_modules/@trycompai/db/certs/rds-global-bundle.pem
36-
```
37-
4. Apply the same strict-TLS Prisma client logic (or import a shared helper from `@trycompai/db`).
16+
If `NODE_EXTRA_CA_CERTS` is still set as a shared/team Vercel env var,
17+
**unset it** — when the path doesn't exist on the function, Node logs a
18+
`Warning: Ignoring extra certs from … load failed: error:80000002:system
19+
library` for every cold start.
3820

3921
## Trigger.dev (api and app projects, staging + prod)
4022

41-
After deploying with the `caBundleExtension` (already wired in `trigger.config.ts`), remove the
42-
legacy opt-in that bypasses TLS verification:
23+
The `caBundleExtension` and `NODE_EXTRA_CA_CERTS` setup in `trigger.config.ts`
24+
remain as-is — Trigger.dev images bake the cert into
25+
`/app/certs/rds-global-bundle.pem`. The shared `@trycompai/db` client falls
26+
through to verified TLS via the inline bundle either way.
27+
28+
If `PRISMA_ALLOW_INSECURE_TLS` is still set as a leftover from earlier
29+
debugging, remove it:
4330

4431
```bash
4532
bunx trigger.dev@4.4.3 envvars remove PRISMA_ALLOW_INSECURE_TLS --env staging
@@ -48,5 +35,26 @@ bunx trigger.dev@4.4.3 envvars remove PRISMA_ALLOW_INSECURE_TLS --env prod
4835

4936
## API Docker (apps/api)
5037

51-
No action — `apps/api/Dockerfile.multistage` already installs the RDS CA bundle and sets
52-
`NODE_EXTRA_CA_CERTS` at the system level.
38+
No action — `apps/api/Dockerfile.multistage` already installs the RDS CA bundle
39+
and sets `NODE_EXTRA_CA_CERTS` at the system level. `apps/api/prisma/client.ts`
40+
still consults the env var, which is the correct path for that runtime.
41+
42+
## Downstream consumers (comp-private/apps/enterprise-api, etc.)
43+
44+
After bumping `@trycompai/db` to a version that includes the inline bundle,
45+
consumers that import `resolveSslConfig` from `@trycompai/db/ssl-config`
46+
automatically get verified TLS via the inline bundle — no env var required.
47+
They can drop their own `NODE_EXTRA_CA_CERTS` and `outputFileTracingIncludes`
48+
on the new version.
49+
50+
## Regenerating the inlined CA bundle
51+
52+
When AWS rotates the RDS CA, replace the PEM and regenerate:
53+
54+
```bash
55+
# overwrite packages/db/certs/rds-global-bundle.pem with the new bundle
56+
node packages/db/scripts/generate-ca-bundle-ts.mjs
57+
```
58+
59+
This rewrites the inlined `rds-ca-bundle.ts` in `packages/db/src` and in each
60+
app's `prisma/` directory.

packages/db/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@trycompai/db",
33
"description": "Database package with Prisma client and schema for Comp AI",
4-
"version": "2.1.1",
4+
"version": "2.2.0",
55
"dependencies": {
66
"@prisma/adapter-pg": "7.6.0",
77
"@prisma/client": "7.6.0",

0 commit comments

Comments
 (0)