@crewhaus/crewhaus-cloud 0.1.1 → 0.1.2
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/package.json +8 -13
- package/src/index.test.ts +84 -3
- package/src/index.ts +9 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/crewhaus-cloud",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Managed-as-a-service composite recipe: target-managed + helm-chart Kustomize overlay + Terraform module for GKE/EKS/AKS provisioning (Section 32)",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
"test": "bun test src"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@crewhaus/docker-images": "0.1.
|
|
16
|
-
"@crewhaus/errors": "0.1.
|
|
17
|
-
"@crewhaus/helm-chart": "0.1.
|
|
15
|
+
"@crewhaus/docker-images": "0.1.2",
|
|
16
|
+
"@crewhaus/errors": "0.1.2",
|
|
17
|
+
"@crewhaus/helm-chart": "0.1.2"
|
|
18
18
|
},
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"author": {
|
|
21
21
|
"name": "Max Meier",
|
|
22
|
-
"email": "max@
|
|
23
|
-
"url": "https://
|
|
22
|
+
"email": "max@crewhaus.ai",
|
|
23
|
+
"url": "https://crewhaus.ai"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
@@ -32,12 +32,7 @@
|
|
|
32
32
|
"url": "https://github.com/crewhaus/factory/issues"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
|
-
"access": "
|
|
35
|
+
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"files": [
|
|
38
|
-
"src",
|
|
39
|
-
"README.md",
|
|
40
|
-
"LICENSE",
|
|
41
|
-
"NOTICE"
|
|
42
|
-
]
|
|
37
|
+
"files": ["src", "README.md", "LICENSE", "NOTICE"]
|
|
43
38
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import * as nodeCrypto from "node:crypto";
|
|
2
3
|
import { mkdtempSync, readFileSync } from "node:fs";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
5
|
import { join } from "node:path";
|
|
@@ -64,8 +65,8 @@ describe("renderKustomizeOverlay", () => {
|
|
|
64
65
|
const overlay = renderKustomizeOverlay(config);
|
|
65
66
|
expect(overlay.kustomization).toContain("kind: Kustomization");
|
|
66
67
|
expect(overlay.kustomization).toContain(`namespace: ${config.clusterName}`);
|
|
67
|
-
expect(overlay.kustomization).toContain("crewhaus.
|
|
68
|
-
expect(overlay.kustomization).toContain("crewhaus.
|
|
68
|
+
expect(overlay.kustomization).toContain("crewhaus.ai/provider: aws");
|
|
69
|
+
expect(overlay.kustomization).toContain("crewhaus.ai/region: us-east-1");
|
|
69
70
|
expect(Object.keys(overlay.manifests).length).toBeGreaterThan(0);
|
|
70
71
|
expect(Object.keys(overlay.manifests).some((k) => k.startsWith("managed-"))).toBe(true);
|
|
71
72
|
});
|
|
@@ -190,6 +191,86 @@ describe("deployCloud (T2 dry-run with fake runner)", () => {
|
|
|
190
191
|
});
|
|
191
192
|
});
|
|
192
193
|
|
|
194
|
+
describe("randomPassword (CWE-338 regression — CSPRNG, not Math.random)", () => {
|
|
195
|
+
function passwordFromCalls(calls: string[][]): string | undefined {
|
|
196
|
+
for (const argv of calls) {
|
|
197
|
+
const varArg = argv.find((a) => a.startsWith("spec_registry_password="));
|
|
198
|
+
if (varArg) return varArg.slice("spec_registry_password=".length);
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
test("deploy injects a 24-char hex password sourced from node:crypto.randomBytes", async () => {
|
|
204
|
+
const randomBytesSpy = spyOn(nodeCrypto, "randomBytes");
|
|
205
|
+
try {
|
|
206
|
+
const dir = mkdtempSync(join(tmpdir(), "crewhaus-cloud-pw-"));
|
|
207
|
+
const calls: string[][] = [];
|
|
208
|
+
const runner: CloudRunner = async (argv) => {
|
|
209
|
+
calls.push([...argv]);
|
|
210
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
211
|
+
};
|
|
212
|
+
await deployCloud({
|
|
213
|
+
config: defaultCloudConfig("aws", "us-east-1"),
|
|
214
|
+
workingDir: dir,
|
|
215
|
+
runner,
|
|
216
|
+
});
|
|
217
|
+
const pw = passwordFromCalls(calls);
|
|
218
|
+
expect(pw).toBeDefined();
|
|
219
|
+
// Same shape as before the fix: exactly 24 lowercase-hex characters.
|
|
220
|
+
expect(pw).toMatch(/^[0-9a-f]{24}$/);
|
|
221
|
+
// Proves the placeholder is drawn from the CSPRNG, not Math.random().
|
|
222
|
+
expect(randomBytesSpy).toHaveBeenCalled();
|
|
223
|
+
} finally {
|
|
224
|
+
randomBytesSpy.mockRestore();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("two separate deploys produce different passwords (not a constant)", async () => {
|
|
229
|
+
const collect = async (): Promise<string | undefined> => {
|
|
230
|
+
const dir = mkdtempSync(join(tmpdir(), "crewhaus-cloud-pw-"));
|
|
231
|
+
const calls: string[][] = [];
|
|
232
|
+
const runner: CloudRunner = async (argv) => {
|
|
233
|
+
calls.push([...argv]);
|
|
234
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
235
|
+
};
|
|
236
|
+
await deployCloud({
|
|
237
|
+
config: defaultCloudConfig("gcp", "us-central1"),
|
|
238
|
+
workingDir: dir,
|
|
239
|
+
runner,
|
|
240
|
+
});
|
|
241
|
+
return passwordFromCalls(calls);
|
|
242
|
+
};
|
|
243
|
+
const [a, b] = [await collect(), await collect()];
|
|
244
|
+
expect(a).toMatch(/^[0-9a-f]{24}$/);
|
|
245
|
+
expect(b).toMatch(/^[0-9a-f]{24}$/);
|
|
246
|
+
expect(a).not.toBe(b);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("teardown also injects a CSPRNG-sourced 24-char hex password", async () => {
|
|
250
|
+
const randomBytesSpy = spyOn(nodeCrypto, "randomBytes");
|
|
251
|
+
try {
|
|
252
|
+
const dir = mkdtempSync(join(tmpdir(), "crewhaus-cloud-pw-td-"));
|
|
253
|
+
await deployCloud({ config: defaultCloudConfig("aws", "us-east-1"), workingDir: dir });
|
|
254
|
+
randomBytesSpy.mockClear();
|
|
255
|
+
const calls: string[][] = [];
|
|
256
|
+
const runner: CloudRunner = async (argv) => {
|
|
257
|
+
calls.push([...argv]);
|
|
258
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
259
|
+
};
|
|
260
|
+
await teardownCloud({
|
|
261
|
+
config: defaultCloudConfig("aws", "us-east-1"),
|
|
262
|
+
workingDir: dir,
|
|
263
|
+
runner,
|
|
264
|
+
});
|
|
265
|
+
const pw = passwordFromCalls(calls);
|
|
266
|
+
expect(pw).toMatch(/^[0-9a-f]{24}$/);
|
|
267
|
+
expect(randomBytesSpy).toHaveBeenCalled();
|
|
268
|
+
} finally {
|
|
269
|
+
randomBytesSpy.mockRestore();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
193
274
|
describe("teardownCloud", () => {
|
|
194
275
|
test("refuses if working dir does not exist", async () => {
|
|
195
276
|
await expect(
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { dirname, join, resolve } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -129,10 +130,10 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|
|
129
130
|
kind: Kustomization
|
|
130
131
|
namespace: ${config.clusterName}
|
|
131
132
|
commonLabels:
|
|
132
|
-
crewhaus.
|
|
133
|
-
crewhaus.
|
|
134
|
-
crewhaus.
|
|
135
|
-
crewhaus.
|
|
133
|
+
crewhaus.ai/cluster: ${config.clusterName}
|
|
134
|
+
crewhaus.ai/provider: ${config.provider}
|
|
135
|
+
crewhaus.ai/region: ${config.region}
|
|
136
|
+
crewhaus.ai/tier: ${config.tier}
|
|
136
137
|
resources:
|
|
137
138
|
${resourceFiles.map((f) => ` - ${f}`).join("\n")}
|
|
138
139
|
`;
|
|
@@ -476,8 +477,10 @@ export async function teardownCloud(opts: DeployCloudOptions): Promise<void> {
|
|
|
476
477
|
|
|
477
478
|
function randomPassword(): string {
|
|
478
479
|
// 24 hex chars — only used as a placeholder so the Terraform variable is set;
|
|
479
|
-
// production users override via -var-file.
|
|
480
|
-
|
|
480
|
+
// production users override via -var-file. Sourced from a CSPRNG so that, if
|
|
481
|
+
// an operator does not override it, the provisioned DB admin credential is not
|
|
482
|
+
// derived from a predictable Math.random() stream (CWE-338).
|
|
483
|
+
return randomBytes(12).toString("hex");
|
|
481
484
|
}
|
|
482
485
|
|
|
483
486
|
/** Used by the CLI subcommand to enumerate provider choices. */
|