@andrewheberle/serverless-ssh-ca-testing 0.0.0-stage → 0.20.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/README.md +84 -2
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +50 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +45 -0
- package/dist/config.js.map +1 -0
- package/dist/format.d.ts +27 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +31 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +54 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +88 -0
- package/dist/log.js.map +1 -0
- package/dist/node.d.ts +17 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +98 -0
- package/dist/node.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -0
- package/dist/worker.d.ts +8 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +25 -0
- package/dist/worker.js.map +1 -0
- package/dist/workerd.d.ts +24 -0
- package/dist/workerd.d.ts.map +1 -0
- package/dist/workerd.js +91 -0
- package/dist/workerd.js.map +1 -0
- package/package.json +72 -5
- package/src/cli.ts +57 -0
- package/src/config.ts +59 -0
- package/src/format.ts +33 -0
- package/src/index.ts +74 -0
- package/src/log.ts +98 -0
- package/src/node.ts +112 -0
- package/src/server.ts +9 -0
- package/src/worker.ts +30 -0
- package/src/workerd.ts +111 -0
package/dist/node.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { createSshCa } from "@andrewheberle/serverless-ssh-ca";
|
|
4
|
+
import { bindingsFromEnv, fromNodeSqlite, secretFromString } from "@andrewheberle/serverless-ssh-ca/node";
|
|
5
|
+
import { formatArgs } from "./format.js";
|
|
6
|
+
const consoleMethods = ["debug", "info", "log", "warn", "error"];
|
|
7
|
+
// convert a request received by the HTTP server into a Fetch API request
|
|
8
|
+
const toRequest = async (req) => {
|
|
9
|
+
const chunks = [];
|
|
10
|
+
for await (const chunk of req) {
|
|
11
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
12
|
+
}
|
|
13
|
+
const headers = new Headers();
|
|
14
|
+
for (const [name, values] of Object.entries(req.headersDistinct)) {
|
|
15
|
+
for (const value of values ?? []) {
|
|
16
|
+
headers.append(name, value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const method = req.method ?? "GET";
|
|
20
|
+
return new Request(`http://${req.headers.host ?? "localhost"}${req.url ?? "/"}`, {
|
|
21
|
+
method,
|
|
22
|
+
headers,
|
|
23
|
+
body: method === "GET" || method === "HEAD" ? undefined : Buffer.concat(chunks),
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Run the CA on Node.js, created with the package's Node.js helpers and an
|
|
28
|
+
* in-memory `node:sqlite` database, behind an HTTP server on a random port.
|
|
29
|
+
*
|
|
30
|
+
* Output the CA writes with `console` is written to `log` until the server is
|
|
31
|
+
* closed.
|
|
32
|
+
*
|
|
33
|
+
* @param config The test server configuration
|
|
34
|
+
* @param log The log file for the CA and harness
|
|
35
|
+
* @returns The running server
|
|
36
|
+
* @throws InvalidConfigurationError if `config.bindings` is not a valid CA configuration
|
|
37
|
+
*/
|
|
38
|
+
export const startNode = async (config, log) => {
|
|
39
|
+
const db = new DatabaseSync(":memory:");
|
|
40
|
+
// bindingsFromEnv validates the configuration so a mistake fails at start up
|
|
41
|
+
const ca = createSshCa(bindingsFromEnv({
|
|
42
|
+
DB: fromNodeSqlite(db),
|
|
43
|
+
PRIVATE_KEY: secretFromString(config.private_key),
|
|
44
|
+
}, config.bindings));
|
|
45
|
+
// the CA logs JSON lines via console, so send them to the log file
|
|
46
|
+
const original = consoleMethods.map(level => [level, console[level]]);
|
|
47
|
+
for (const level of consoleMethods) {
|
|
48
|
+
console[level] = (...args) => log.write(formatArgs(args));
|
|
49
|
+
}
|
|
50
|
+
const restoreConsole = () => {
|
|
51
|
+
for (const [level, method] of original) {
|
|
52
|
+
console[level] = method;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const server = createServer(async (req, res) => {
|
|
56
|
+
try {
|
|
57
|
+
const request = await toRequest(req);
|
|
58
|
+
const response = await ca.fetch(request);
|
|
59
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
60
|
+
log.request(request.method, req.url ?? "/", response.status, body.toString());
|
|
61
|
+
res.writeHead(response.status, Object.fromEntries(response.headers));
|
|
62
|
+
res.end(body);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
log.fault("request failed", err);
|
|
66
|
+
res.writeHead(502);
|
|
67
|
+
res.end();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
try {
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
server.once("error", reject);
|
|
73
|
+
server.listen(0, "127.0.0.1", () => resolve());
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
restoreConsole();
|
|
78
|
+
db.close();
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
const address = server.address();
|
|
82
|
+
if (address === null || typeof address === "string") {
|
|
83
|
+
restoreConsole();
|
|
84
|
+
server.close();
|
|
85
|
+
db.close();
|
|
86
|
+
throw new Error(`unexpected server address: ${String(address)}`);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
port: address.port,
|
|
90
|
+
close: async () => {
|
|
91
|
+
server.closeAllConnections();
|
|
92
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
93
|
+
restoreConsole();
|
|
94
|
+
db.close();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A running test server, returned by {@link startTestServer}.
|
|
3
|
+
*/
|
|
4
|
+
export type TestServer = {
|
|
5
|
+
/** The port the CA is listening on at 127.0.0.1 */
|
|
6
|
+
port: number;
|
|
7
|
+
/** Stop the CA and release its resources */
|
|
8
|
+
close(): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACxB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB,CAAA"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import ca from "@andrewheberle/serverless-ssh-ca";
|
|
2
|
+
type FetchParameters = Parameters<typeof ca.fetch>;
|
|
3
|
+
declare const _default: {
|
|
4
|
+
scheduled(controller: unknown, env: import("@andrewheberle/serverless-ssh-ca").SshCaBindings, ctx: import("hono").ExecutionContext): Promise<void>;
|
|
5
|
+
fetch(request: FetchParameters[0], env: FetchParameters[1], ctx: FetchParameters[2]): Promise<Response>;
|
|
6
|
+
};
|
|
7
|
+
export default _default;
|
|
8
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,kCAAkC,CAAA;AAGjD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;;;mBAW5B,eAAe,CAAC,CAAC,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC"}
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Worker entry point used by startWorkerd, bundled by Wrangler when the test
|
|
2
|
+
// server starts.
|
|
3
|
+
//
|
|
4
|
+
// The CA's default export is used as on Cloudflare Workers, wrapped only to
|
|
5
|
+
// log each request and to write console output as JSON lines, as the Node.js
|
|
6
|
+
// runtime does.
|
|
7
|
+
import ca from "@andrewheberle/serverless-ssh-ca";
|
|
8
|
+
import { formatArgs, formatRequest } from "./format.js";
|
|
9
|
+
// workerd formats objects passed to console for display rather than as JSON,
|
|
10
|
+
// so format them here
|
|
11
|
+
for (const level of ["debug", "info", "log", "warn", "error"]) {
|
|
12
|
+
const write = console[level].bind(console);
|
|
13
|
+
console[level] = (...args) => write(formatArgs(args));
|
|
14
|
+
}
|
|
15
|
+
export default {
|
|
16
|
+
...ca,
|
|
17
|
+
async fetch(request, env, ctx) {
|
|
18
|
+
const response = await ca.fetch(request, env, ctx);
|
|
19
|
+
const url = new URL(request.url);
|
|
20
|
+
const body = response.status >= 400 ? await response.clone().text() : "";
|
|
21
|
+
console.log(formatRequest(request.method, url.pathname + url.search, response.status, body));
|
|
22
|
+
return response;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,iBAAiB;AACjB,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,gBAAgB;AAChB,OAAO,EAAE,MAAM,kCAAkC,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIvD,6EAA6E;AAC7E,sBAAsB;AACtB,KAAK,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAU,EAAE,CAAC;IACxE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC1C,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAe,EAAQ,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AACvE,CAAC;AAED,eAAe;IACd,GAAG,EAAE;IACL,KAAK,CAAC,KAAK,CAAC,OAA2B,EAAE,GAAuB,EAAE,GAAuB;QACxF,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;QAClD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAExE,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;QAE5F,OAAO,QAAQ,CAAA;IAChB,CAAC;CACD,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TestServerConfig } from "./config.js";
|
|
2
|
+
import type { LogFile } from "./log.js";
|
|
3
|
+
import type { TestServer } from "./server.js";
|
|
4
|
+
/**
|
|
5
|
+
* The Workers compatibility date the CA is run with, which matches the
|
|
6
|
+
* configuration the CA is tested with on Workers.
|
|
7
|
+
*/
|
|
8
|
+
export declare const COMPATIBILITY_DATE = "2026-03-13";
|
|
9
|
+
/**
|
|
10
|
+
* Run the CA under workerd, the Cloudflare Workers runtime, using the Wrangler
|
|
11
|
+
* test harness as "wrangler dev" would, with a local D1 database and Secrets
|
|
12
|
+
* Store that are not persisted.
|
|
13
|
+
*
|
|
14
|
+
* The CA's output is copied to `log` every 100ms and when the server is
|
|
15
|
+
* closed. Errors from workerd itself, such as an uncaught exception, are
|
|
16
|
+
* logged as harness faults.
|
|
17
|
+
*
|
|
18
|
+
* @param config The test server configuration
|
|
19
|
+
* @param log The log file for the CA and harness
|
|
20
|
+
* @param root The directory Wrangler's working files are written to
|
|
21
|
+
* @returns The running server
|
|
22
|
+
*/
|
|
23
|
+
export declare const startWorkerd: (config: TestServerConfig, log: LogFile, root: string) => Promise<TestServer>;
|
|
24
|
+
//# sourceMappingURL=workerd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workerd.d.ts","sourceRoot":"","sources":["../src/workerd.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAc7C;;;GAGG;AACH,eAAO,MAAM,kBAAkB,eAAe,CAAA;AAE9C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,WAAkB,gBAAgB,OAAO,OAAO,QAAQ,MAAM,KAAG,OAAO,CAAC,UAAU,CAwE3G,CAAA"}
|
package/dist/workerd.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { createTestHarness } from "wrangler";
|
|
3
|
+
/**
|
|
4
|
+
* The Workers compatibility date the CA is run with, which matches the
|
|
5
|
+
* configuration the CA is tested with on Workers.
|
|
6
|
+
*/
|
|
7
|
+
export const COMPATIBILITY_DATE = "2026-03-13";
|
|
8
|
+
/**
|
|
9
|
+
* Run the CA under workerd, the Cloudflare Workers runtime, using the Wrangler
|
|
10
|
+
* test harness as "wrangler dev" would, with a local D1 database and Secrets
|
|
11
|
+
* Store that are not persisted.
|
|
12
|
+
*
|
|
13
|
+
* The CA's output is copied to `log` every 100ms and when the server is
|
|
14
|
+
* closed. Errors from workerd itself, such as an uncaught exception, are
|
|
15
|
+
* logged as harness faults.
|
|
16
|
+
*
|
|
17
|
+
* @param config The test server configuration
|
|
18
|
+
* @param log The log file for the CA and harness
|
|
19
|
+
* @param root The directory Wrangler's working files are written to
|
|
20
|
+
* @returns The running server
|
|
21
|
+
*/
|
|
22
|
+
export const startWorkerd = async (config, log, root) => {
|
|
23
|
+
// use placeholder request.cf data rather than downloading it from
|
|
24
|
+
// Cloudflare and caching it in the working directory
|
|
25
|
+
process.env.CLOUDFLARE_CF_FETCH_ENABLED = "false";
|
|
26
|
+
const server = createTestHarness({
|
|
27
|
+
root,
|
|
28
|
+
workers: [{
|
|
29
|
+
config: {
|
|
30
|
+
name: "serverless-ssh-ca",
|
|
31
|
+
main: fileURLToPath(new URL("./worker.js", import.meta.url)),
|
|
32
|
+
compatibility_date: COMPATIBILITY_DATE,
|
|
33
|
+
compatibility_flags: ["nodejs_compat"],
|
|
34
|
+
vars: config.bindings,
|
|
35
|
+
d1_databases: [{
|
|
36
|
+
binding: "DB",
|
|
37
|
+
database_name: "ssh-ca-database",
|
|
38
|
+
database_id: "00000000-0000-0000-0000-000000000000",
|
|
39
|
+
}],
|
|
40
|
+
secrets_store_secrets: [{
|
|
41
|
+
binding: "PRIVATE_KEY",
|
|
42
|
+
store_id: "test",
|
|
43
|
+
secret_name: "ssh-ca-private-key",
|
|
44
|
+
}],
|
|
45
|
+
},
|
|
46
|
+
}],
|
|
47
|
+
});
|
|
48
|
+
// copy the runtime logs to the log file. Errors that are not JSON lines
|
|
49
|
+
// from the CA are from workerd, such as an uncaught exception, so are
|
|
50
|
+
// logged as a harness fault.
|
|
51
|
+
const flushLogs = () => {
|
|
52
|
+
for (const entry of server.getLogs()) {
|
|
53
|
+
let json = true;
|
|
54
|
+
try {
|
|
55
|
+
JSON.parse(entry.message);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
json = false;
|
|
59
|
+
}
|
|
60
|
+
if (json || entry.level !== "error") {
|
|
61
|
+
log.write(entry.message);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
log.fault("runtime error", entry.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
server.clearLogs();
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
const { url } = await server.listen();
|
|
71
|
+
// the secret must exist before the port is reported and requests are made
|
|
72
|
+
const env = await server.getWorker().getEnv();
|
|
73
|
+
const secrets = await env.PRIVATE_KEY["SecretsStoreSecret::admin_api"]();
|
|
74
|
+
await secrets.create(config.private_key);
|
|
75
|
+
const timer = setInterval(flushLogs, 100);
|
|
76
|
+
return {
|
|
77
|
+
port: Number(url.port),
|
|
78
|
+
close: async () => {
|
|
79
|
+
clearInterval(timer);
|
|
80
|
+
flushLogs();
|
|
81
|
+
await server.close();
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
flushLogs();
|
|
87
|
+
await server.close();
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=workerd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workerd.js","sourceRoot":"","sources":["../src/workerd.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,73 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@andrewheberle/serverless-ssh-ca-testing",
|
|
3
|
+
"version": "0.20.2",
|
|
4
|
+
"description": "Runs the Serverless SSH CA under Node.js or workerd for end-to-end tests of clients",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"serverless-ssh-ca-test-server": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"lint": "eslint",
|
|
24
|
+
"release": "release-it",
|
|
25
|
+
"release:ci": "npm run release:patch",
|
|
26
|
+
"release:ci:major": "release-it major --ci",
|
|
27
|
+
"release:ci:minor": "release-it minor --ci",
|
|
28
|
+
"release:ci:patch": "release-it patch --ci",
|
|
29
|
+
"release:dryrun": "release-it --dry-run",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:workers": "vitest run --config vitest.integration.config.mts"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"wrangler": "^4.95.0",
|
|
35
|
+
"zod": "^4.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@andrewheberle/serverless-ssh-ca": ">=0.20.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@andrewheberle/serverless-ssh-ca": "*",
|
|
42
|
+
"@eslint/js": "^10.0.1",
|
|
43
|
+
"@types/node": "^24.19.1",
|
|
44
|
+
"eslint": "^10.4.1",
|
|
45
|
+
"typescript": "^7.0.0",
|
|
46
|
+
"typescript-eslint": "^8.61.0",
|
|
47
|
+
"vitest": "^4.1.7"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=24.0.0"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/andrewheberle/serverless-ssh-ca.git",
|
|
58
|
+
"directory": "packages/testing"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/andrewheberle/serverless-ssh-ca/tree/main/packages/testing#readme",
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/andrewheberle/serverless-ssh-ca/issues"
|
|
63
|
+
},
|
|
64
|
+
"author": "Andrew Heberle",
|
|
65
|
+
"license": "Apache-2.0",
|
|
66
|
+
"release-it": {
|
|
67
|
+
"git": false,
|
|
68
|
+
"npm": {
|
|
69
|
+
"publish": true,
|
|
70
|
+
"skipChecks": true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs the Serverless SSH CA for end-to-end tests of clients.
|
|
3
|
+
//
|
|
4
|
+
// Usage: serverless-ssh-ca-test-server [--runtime node|workerd] <config.json>
|
|
5
|
+
//
|
|
6
|
+
// The process exits when its stdin is closed, so it does not outlive the test
|
|
7
|
+
// that started it.
|
|
8
|
+
import { parseArgs } from "node:util"
|
|
9
|
+
import z from "zod"
|
|
10
|
+
import { readConfig, RuntimeSchema } from "./config.js"
|
|
11
|
+
import { startTestServer } from "./index.js"
|
|
12
|
+
import { LogFile } from "./log.js"
|
|
13
|
+
|
|
14
|
+
const usage = "usage: serverless-ssh-ca-test-server [--runtime node|workerd] <config.json>"
|
|
15
|
+
|
|
16
|
+
const main = async (): Promise<void> => {
|
|
17
|
+
const { values, positionals } = parseArgs({
|
|
18
|
+
options: {
|
|
19
|
+
runtime: { type: "string", default: "node" },
|
|
20
|
+
},
|
|
21
|
+
allowPositionals: true,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const runtime = RuntimeSchema.safeParse(values.runtime)
|
|
25
|
+
if (!runtime.success) {
|
|
26
|
+
throw new Error(`--runtime ${z.prettifyError(runtime.error)}\n${usage}`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const [configFile, ...extra] = positionals
|
|
30
|
+
if (configFile === undefined || extra.length > 0) {
|
|
31
|
+
throw new Error(usage)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// faults after start up are written to the log file, where the test
|
|
35
|
+
// reading it will find them
|
|
36
|
+
const config = await readConfig(configFile)
|
|
37
|
+
const log = new LogFile(config.log_file)
|
|
38
|
+
process.on("uncaughtException", (err) => {
|
|
39
|
+
log.fault("uncaught exception", err)
|
|
40
|
+
process.exit(1)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const server = await startTestServer(runtime.data, configFile)
|
|
44
|
+
|
|
45
|
+
process.stdin.on("end", () => {
|
|
46
|
+
server.close().then(() => process.exit(0), (err: unknown) => {
|
|
47
|
+
log.fault("close failed", err)
|
|
48
|
+
process.exit(1)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
process.stdin.resume()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main().catch((err: unknown) => {
|
|
55
|
+
console.error(err instanceof Error ? err.message : err)
|
|
56
|
+
process.exit(1)
|
|
57
|
+
})
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises"
|
|
2
|
+
import z from "zod"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The runtimes the CA can be run under by {@link startTestServer}: `node` uses
|
|
6
|
+
* the package's Node.js helpers with an in-memory `node:sqlite` database, and
|
|
7
|
+
* `workerd` uses the Cloudflare Workers runtime with a local D1 database and
|
|
8
|
+
* Secrets Store.
|
|
9
|
+
*/
|
|
10
|
+
export const RuntimeSchema = z.enum(["node", "workerd"])
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A runtime the CA can be run under, see {@link RuntimeSchema}.
|
|
14
|
+
*/
|
|
15
|
+
export type Runtime = z.infer<typeof RuntimeSchema>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The configuration file read by {@link readConfig}.
|
|
19
|
+
*/
|
|
20
|
+
export const ConfigSchema = z.object({
|
|
21
|
+
/** The CA private key in OpenSSH format */
|
|
22
|
+
private_key: z.string().min(1),
|
|
23
|
+
/** The CA configuration, as the Workers vars (environment variables) */
|
|
24
|
+
bindings: z.record(z.string(), z.string()),
|
|
25
|
+
/** Path the listening port is written to once the CA is ready */
|
|
26
|
+
port_file: z.string().min(1),
|
|
27
|
+
/** Path that log output from the CA and harness is appended to */
|
|
28
|
+
log_file: z.string().min(1),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The configuration of a test server, see {@link ConfigSchema}.
|
|
33
|
+
*/
|
|
34
|
+
export type TestServerConfig = z.infer<typeof ConfigSchema>
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read and validate a test server configuration file.
|
|
38
|
+
*
|
|
39
|
+
* @param path The path of a JSON file matching {@link ConfigSchema}
|
|
40
|
+
* @returns The validated configuration
|
|
41
|
+
* @throws Error if the file cannot be read, is not JSON or is not a valid configuration
|
|
42
|
+
*/
|
|
43
|
+
export const readConfig = async (path: string): Promise<TestServerConfig> => {
|
|
44
|
+
const text = await readFile(path, { encoding: "utf-8" })
|
|
45
|
+
|
|
46
|
+
let json: unknown
|
|
47
|
+
try {
|
|
48
|
+
json = JSON.parse(text)
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new Error(`parsing ${path}: ${err instanceof Error ? err.message : String(err)}`, { cause: err })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = ConfigSchema.safeParse(json)
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
throw new Error(`invalid configuration in ${path}:\n${z.prettifyError(result.error)}`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result.data
|
|
59
|
+
}
|
package/src/format.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a value logged by the CA as text, with objects as JSON so log lines
|
|
3
|
+
* from the CA's logger are written as JSON lines.
|
|
4
|
+
*
|
|
5
|
+
* @param value A value passed to a `console` method
|
|
6
|
+
* @returns The value as a string
|
|
7
|
+
*/
|
|
8
|
+
export const formatValue = (value: unknown): string => typeof value === "string"
|
|
9
|
+
? value
|
|
10
|
+
: JSON.stringify(value, (_, x: unknown) => x instanceof Error
|
|
11
|
+
? { name: x.name, message: x.message, stack: x.stack }
|
|
12
|
+
: typeof x === "bigint" ? x.toString() : x)
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format the arguments to a `console` method as a single line.
|
|
16
|
+
*
|
|
17
|
+
* @param args The arguments passed to a `console` method
|
|
18
|
+
* @returns The arguments formatted with {@link formatValue}, separated by spaces
|
|
19
|
+
*/
|
|
20
|
+
export const formatArgs = (args: readonly unknown[]): string => args.map(formatValue).join(" ")
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format a request handled by the CA as a log line, including the response
|
|
24
|
+
* body when the request was rejected.
|
|
25
|
+
*
|
|
26
|
+
* @param method The request method
|
|
27
|
+
* @param url The path and query of the request
|
|
28
|
+
* @param status The response status code
|
|
29
|
+
* @param body The response body
|
|
30
|
+
* @returns A JSON line with the message "harness request"
|
|
31
|
+
*/
|
|
32
|
+
export const formatRequest = (method: string, url: string, status: number, body: string): string =>
|
|
33
|
+
formatValue({ level: "info", message: "harness request", method, url, status, ...(status >= 400 ? { body } : {}) })
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs the Serverless SSH CA for end-to-end tests of clients, under Node.js or
|
|
3
|
+
* workerd, configured from a file and reporting what it does through a log
|
|
4
|
+
* file of JSON lines (see {@link LogFile}).
|
|
5
|
+
*
|
|
6
|
+
* Most clients run the `serverless-ssh-ca-test-server` command rather than
|
|
7
|
+
* using this module directly.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { startTestServer } from "@andrewheberle/serverless-ssh-ca-testing"
|
|
12
|
+
*
|
|
13
|
+
* const server = await startTestServer("node", "config.json")
|
|
14
|
+
* // make requests to http://127.0.0.1:${server.port}
|
|
15
|
+
* await server.close()
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
import { writeFile } from "node:fs/promises"
|
|
21
|
+
import { dirname, resolve } from "node:path"
|
|
22
|
+
import { readConfig, type Runtime } from "./config.js"
|
|
23
|
+
import { LogFile } from "./log.js"
|
|
24
|
+
import { startNode } from "./node.js"
|
|
25
|
+
import type { TestServer } from "./server.js"
|
|
26
|
+
|
|
27
|
+
export { ConfigSchema, readConfig, RuntimeSchema } from "./config.js"
|
|
28
|
+
export type { Runtime, TestServerConfig } from "./config.js"
|
|
29
|
+
export { FAULT_MESSAGE, FAULT_MESSAGES, isFault, LogFile } from "./log.js"
|
|
30
|
+
export type { TestServer } from "./server.js"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Start the CA under `runtime` using the configuration in `configFile` (see
|
|
34
|
+
* {@link ConfigSchema}), then write the port it is listening on to the
|
|
35
|
+
* configuration's `port_file`.
|
|
36
|
+
*
|
|
37
|
+
* Output from the CA and harness is appended to the configuration's
|
|
38
|
+
* `log_file`, see {@link LogFile}. Under workerd, Wrangler's working files are
|
|
39
|
+
* written to the directory containing `configFile`.
|
|
40
|
+
*
|
|
41
|
+
* @param runtime The runtime to run the CA under, see {@link Runtime}
|
|
42
|
+
* @param configFile The path of the configuration file
|
|
43
|
+
* @returns The running server, see {@link TestServer}
|
|
44
|
+
*/
|
|
45
|
+
export const startTestServer = async (runtime: Runtime, configFile: string): Promise<TestServer> => {
|
|
46
|
+
const config = await readConfig(configFile)
|
|
47
|
+
const log = new LogFile(config.log_file)
|
|
48
|
+
|
|
49
|
+
let server: TestServer
|
|
50
|
+
switch (runtime) {
|
|
51
|
+
case "node":
|
|
52
|
+
server = await startNode(config, log)
|
|
53
|
+
break
|
|
54
|
+
case "workerd": {
|
|
55
|
+
// loaded on demand as importing Wrangler is slow
|
|
56
|
+
const { startWorkerd } = await import("./workerd.js")
|
|
57
|
+
server = await startWorkerd(config, log, dirname(resolve(configFile)))
|
|
58
|
+
break
|
|
59
|
+
}
|
|
60
|
+
default: {
|
|
61
|
+
const unknown: never = runtime
|
|
62
|
+
throw new Error(`unknown runtime: ${String(unknown)}`)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await writeFile(config.port_file, String(server.port))
|
|
68
|
+
} catch (err) {
|
|
69
|
+
await server.close()
|
|
70
|
+
throw err
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return server
|
|
74
|
+
}
|