@specific.dev/spectest 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/daemon.js
CHANGED
|
@@ -933,8 +933,15 @@ async function ensureCaTrustedImage(serviceName, tag) {
|
|
|
933
933
|
const ctxDir = path.join(WORKSPACE, ".spectest", "ca-trust", serviceName);
|
|
934
934
|
await fs.mkdir(ctxDir, { recursive: true });
|
|
935
935
|
await fs.copyFile(CA_PATH, path.join(ctxDir, "spectest-ca.crt"));
|
|
936
|
+
// An image that declares a non-root `USER` runs this step as that user,
|
|
937
|
+
// and writing the trust store then fails ("cannot create
|
|
938
|
+
// /etc/ssl/certs/ca-certificates.crt.new: Permission denied") — the CA
|
|
939
|
+
// silently never lands. Take root for the one command, then hand the
|
|
940
|
+
// image back its own user so containers still run as it.
|
|
941
|
+
const declaredUser = (await docker(["image", "inspect", "--format", "{{.Config.User}}", tag], 60_000)).stdout.trim();
|
|
942
|
+
const needsRoot = declaredUser !== "" && declaredUser !== "root" && declaredUser !== "0";
|
|
936
943
|
const dockerfile = `FROM ${tag}
|
|
937
|
-
COPY spectest-ca.crt /usr/local/share/ca-certificates/spectest-ca.crt
|
|
944
|
+
${needsRoot ? "USER root\n" : ""}COPY spectest-ca.crt /usr/local/share/ca-certificates/spectest-ca.crt
|
|
938
945
|
RUN if command -v update-ca-certificates >/dev/null 2>&1; then \\
|
|
939
946
|
update-ca-certificates; \\
|
|
940
947
|
elif command -v update-ca-trust >/dev/null 2>&1; then \\
|
|
@@ -942,7 +949,7 @@ RUN if command -v update-ca-certificates >/dev/null 2>&1; then \\
|
|
|
942
949
|
else \\
|
|
943
950
|
echo "[spectest] no system CA trust tool in image; env-var trust only"; \\
|
|
944
951
|
fi
|
|
945
|
-
`;
|
|
952
|
+
${needsRoot ? `USER ${declaredUser}\n` : ""}`;
|
|
946
953
|
await fs.writeFile(path.join(ctxDir, "Dockerfile"), dockerfile);
|
|
947
954
|
const build = await docker(["build", "-t", tag, ctxDir], 300_000);
|
|
948
955
|
if (build.code !== 0) {
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
* showed the problem, which is why it went unnoticed: only the appending
|
|
30
30
|
* variable was in play.
|
|
31
31
|
*
|
|
32
|
+
* **Deno is a third mechanism.** It reads none of those variables and
|
|
33
|
+
* trusts only its bundled Mozilla roots, so an edge function calling a
|
|
34
|
+
* fake fails with `UnknownIssuer` however the image is set up.
|
|
35
|
+
* `DENO_TLS_CA_STORE=mozilla,system` is what adds the platform store,
|
|
36
|
+
* and the platform store is `SSL_CERT_FILE` — the combined bundle.
|
|
37
|
+
*
|
|
32
38
|
* **`command` and `args` are different overrides.** `command` replaces the
|
|
33
39
|
* entrypoint and runs through `sh -c`; `args` keeps the entrypoint and
|
|
34
40
|
* overrides CMD, which is what init-wrapped images like postgres need to
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
* showed the problem, which is why it went unnoticed: only the appending
|
|
30
30
|
* variable was in play.
|
|
31
31
|
*
|
|
32
|
+
* **Deno is a third mechanism.** It reads none of those variables and
|
|
33
|
+
* trusts only its bundled Mozilla roots, so an edge function calling a
|
|
34
|
+
* fake fails with `UnknownIssuer` however the image is set up.
|
|
35
|
+
* `DENO_TLS_CA_STORE=mozilla,system` is what adds the platform store,
|
|
36
|
+
* and the platform store is `SSL_CERT_FILE` — the combined bundle.
|
|
37
|
+
*
|
|
32
38
|
* **`command` and `args` are different overrides.** `command` replaces the
|
|
33
39
|
* entrypoint and runs through `sh -c`; `args` keeps the entrypoint and
|
|
34
40
|
* overrides CMD, which is what init-wrapped images like postgres need to
|
|
@@ -87,6 +93,13 @@ export function runContainerArgs(input) {
|
|
|
87
93
|
// replacing ones take the combined bundle.
|
|
88
94
|
args.push(`--volume=${input.caPath}:${input.caPath}:ro`);
|
|
89
95
|
args.push("-e", `NODE_EXTRA_CA_CERTS=${input.caPath}`);
|
|
96
|
+
// Deno reads none of the above, and its default store is the bundled
|
|
97
|
+
// Mozilla roots alone — so a Deno runtime (a Supabase edge function is
|
|
98
|
+
// the usual one) rejects every certificate this environment mints, with
|
|
99
|
+
// or without the CA layered into the image. `system` adds the platform
|
|
100
|
+
// store, which resolves through `SSL_CERT_FILE`: the bundle below.
|
|
101
|
+
// `mozilla` stays first so the public roots survive.
|
|
102
|
+
args.push("-e", "DENO_TLS_CA_STORE=mozilla,system");
|
|
90
103
|
const bundle = input.caBundle;
|
|
91
104
|
if (bundle) {
|
|
92
105
|
if (bundle !== input.caPath)
|
package/package.json
CHANGED
package/src/daemon.ts
CHANGED
|
@@ -1221,8 +1221,17 @@ async function ensureCaTrustedImage(serviceName: string, tag: string): Promise<v
|
|
|
1221
1221
|
const ctxDir = path.join(WORKSPACE, ".spectest", "ca-trust", serviceName);
|
|
1222
1222
|
await fs.mkdir(ctxDir, { recursive: true });
|
|
1223
1223
|
await fs.copyFile(CA_PATH, path.join(ctxDir, "spectest-ca.crt"));
|
|
1224
|
+
// An image that declares a non-root `USER` runs this step as that user,
|
|
1225
|
+
// and writing the trust store then fails ("cannot create
|
|
1226
|
+
// /etc/ssl/certs/ca-certificates.crt.new: Permission denied") — the CA
|
|
1227
|
+
// silently never lands. Take root for the one command, then hand the
|
|
1228
|
+
// image back its own user so containers still run as it.
|
|
1229
|
+
const declaredUser = (
|
|
1230
|
+
await docker(["image", "inspect", "--format", "{{.Config.User}}", tag], 60_000)
|
|
1231
|
+
).stdout.trim();
|
|
1232
|
+
const needsRoot = declaredUser !== "" && declaredUser !== "root" && declaredUser !== "0";
|
|
1224
1233
|
const dockerfile = `FROM ${tag}
|
|
1225
|
-
COPY spectest-ca.crt /usr/local/share/ca-certificates/spectest-ca.crt
|
|
1234
|
+
${needsRoot ? "USER root\n" : ""}COPY spectest-ca.crt /usr/local/share/ca-certificates/spectest-ca.crt
|
|
1226
1235
|
RUN if command -v update-ca-certificates >/dev/null 2>&1; then \\
|
|
1227
1236
|
update-ca-certificates; \\
|
|
1228
1237
|
elif command -v update-ca-trust >/dev/null 2>&1; then \\
|
|
@@ -1230,7 +1239,7 @@ RUN if command -v update-ca-certificates >/dev/null 2>&1; then \\
|
|
|
1230
1239
|
else \\
|
|
1231
1240
|
echo "[spectest] no system CA trust tool in image; env-var trust only"; \\
|
|
1232
1241
|
fi
|
|
1233
|
-
`;
|
|
1242
|
+
${needsRoot ? `USER ${declaredUser}\n` : ""}`;
|
|
1234
1243
|
await fs.writeFile(path.join(ctxDir, "Dockerfile"), dockerfile);
|
|
1235
1244
|
const build = await docker(["build", "-t", tag, ctxDir], 300_000);
|
|
1236
1245
|
if (build.code !== 0) {
|
|
@@ -101,6 +101,14 @@ describe("certificate trust", () => {
|
|
|
101
101
|
);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
/** Deno ignores every variable above and ships its own root store, so
|
|
105
|
+
* without this an edge function calling a fake gets `UnknownIssuer`
|
|
106
|
+
* no matter what the image's trust store says. */
|
|
107
|
+
test("points Deno at the platform store as well as its own", () => {
|
|
108
|
+
const args = runContainerArgs(base({ caBundle: "/etc/spectest/ca-bundle.crt" }));
|
|
109
|
+
expect(envOf(args, "DENO_TLS_CA_STORE")).toBe("mozilla,system");
|
|
110
|
+
});
|
|
111
|
+
|
|
104
112
|
test("with no bundle, the replacing variables are left unset", () => {
|
|
105
113
|
const args = runContainerArgs(base({ caBundle: null }));
|
|
106
114
|
expect(envOf(args, "SSL_CERT_FILE")).toBeUndefined();
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
* showed the problem, which is why it went unnoticed: only the appending
|
|
30
30
|
* variable was in play.
|
|
31
31
|
*
|
|
32
|
+
* **Deno is a third mechanism.** It reads none of those variables and
|
|
33
|
+
* trusts only its bundled Mozilla roots, so an edge function calling a
|
|
34
|
+
* fake fails with `UnknownIssuer` however the image is set up.
|
|
35
|
+
* `DENO_TLS_CA_STORE=mozilla,system` is what adds the platform store,
|
|
36
|
+
* and the platform store is `SSL_CERT_FILE` — the combined bundle.
|
|
37
|
+
*
|
|
32
38
|
* **`command` and `args` are different overrides.** `command` replaces the
|
|
33
39
|
* entrypoint and runs through `sh -c`; `args` keeps the entrypoint and
|
|
34
40
|
* overrides CMD, which is what init-wrapped images like postgres need to
|
|
@@ -132,6 +138,13 @@ export function runContainerArgs(input: ContainerRunInput): string[] {
|
|
|
132
138
|
// replacing ones take the combined bundle.
|
|
133
139
|
args.push(`--volume=${input.caPath}:${input.caPath}:ro`);
|
|
134
140
|
args.push("-e", `NODE_EXTRA_CA_CERTS=${input.caPath}`);
|
|
141
|
+
// Deno reads none of the above, and its default store is the bundled
|
|
142
|
+
// Mozilla roots alone — so a Deno runtime (a Supabase edge function is
|
|
143
|
+
// the usual one) rejects every certificate this environment mints, with
|
|
144
|
+
// or without the CA layered into the image. `system` adds the platform
|
|
145
|
+
// store, which resolves through `SSL_CERT_FILE`: the bundle below.
|
|
146
|
+
// `mozilla` stays first so the public roots survive.
|
|
147
|
+
args.push("-e", "DENO_TLS_CA_STORE=mozilla,system");
|
|
135
148
|
const bundle = input.caBundle;
|
|
136
149
|
if (bundle) {
|
|
137
150
|
if (bundle !== input.caPath) args.push(`--volume=${bundle}:${bundle}:ro`);
|