@intentius/chant 0.124.30 → 0.124.31

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.
Files changed (72) hide show
  1. package/dist/cli/main.d.ts.map +1 -1
  2. package/dist/cli/main.js +38 -4
  3. package/dist/cli/parse-args.d.ts.map +1 -1
  4. package/dist/cli/parse-args.js +50 -0
  5. package/dist/cli/registry.d.ts +18 -0
  6. package/dist/cli/registry.d.ts.map +1 -1
  7. package/dist/op/local-executor.d.ts +12 -0
  8. package/dist/op/local-executor.d.ts.map +1 -1
  9. package/dist/op/local-executor.js +22 -3
  10. package/dist/op/redact.d.ts +34 -0
  11. package/dist/op/redact.d.ts.map +1 -0
  12. package/dist/op/redact.js +94 -0
  13. package/dist/op/step-console.d.ts +2 -0
  14. package/dist/op/step-console.d.ts.map +1 -1
  15. package/dist/op/step-console.js +5 -2
  16. package/dist/pr-forge.d.ts +17 -0
  17. package/dist/pr-forge.d.ts.map +1 -1
  18. package/dist/pr-forge.js +12 -6
  19. package/dist/pr-report-url.d.ts +8 -0
  20. package/dist/pr-report-url.d.ts.map +1 -1
  21. package/dist/pr-report-url.js +47 -1
  22. package/dist/wave-runtime.mjs +159 -159
  23. package/dist/workspace/ci-run-index.d.ts +96 -0
  24. package/dist/workspace/ci-run-index.d.ts.map +1 -0
  25. package/dist/workspace/ci-run-index.js +162 -0
  26. package/dist/workspace/ci-run-index.schema.json +50 -0
  27. package/dist/workspace/ci-run.d.ts +84 -7
  28. package/dist/workspace/ci-run.d.ts.map +1 -1
  29. package/dist/workspace/ci-run.js +241 -14
  30. package/dist/workspace/ci-runner-key.d.ts +120 -0
  31. package/dist/workspace/ci-runner-key.d.ts.map +1 -0
  32. package/dist/workspace/ci-runner-key.js +217 -0
  33. package/dist/workspace/ci-secrets.d.ts +65 -0
  34. package/dist/workspace/ci-secrets.d.ts.map +1 -0
  35. package/dist/workspace/ci-secrets.js +211 -0
  36. package/dist/workspace/ci-serve.d.ts +13 -0
  37. package/dist/workspace/ci-serve.d.ts.map +1 -1
  38. package/dist/workspace/ci-serve.js +69 -13
  39. package/dist/workspace/reason-codes.d.ts +6 -2
  40. package/dist/workspace/reason-codes.d.ts.map +1 -1
  41. package/dist/workspace/reason-codes.js +7 -2
  42. package/dist/workspace/trust/trust-write.d.ts +109 -0
  43. package/dist/workspace/trust/trust-write.d.ts.map +1 -0
  44. package/dist/workspace/trust/trust-write.js +320 -0
  45. package/dist/workspace/trust-write.schema.json +265 -0
  46. package/package.json +1 -1
  47. package/src/cli/main.test.ts +1 -0
  48. package/src/cli/main.ts +38 -4
  49. package/src/cli/parse-args.ts +36 -0
  50. package/src/cli/registry.ts +18 -0
  51. package/src/op/local-executor.ts +31 -3
  52. package/src/op/redact.ts +93 -0
  53. package/src/op/step-console.ts +7 -2
  54. package/src/pr-forge.test.ts +29 -0
  55. package/src/pr-forge.ts +30 -6
  56. package/src/pr-report-url.ts +53 -1
  57. package/src/workspace/ci-run-index.schema.json +50 -0
  58. package/src/workspace/ci-run-index.test.ts +353 -0
  59. package/src/workspace/ci-run-index.ts +192 -0
  60. package/src/workspace/ci-run.test.ts +337 -37
  61. package/src/workspace/ci-run.ts +277 -15
  62. package/src/workspace/ci-runner-key.test.ts +166 -0
  63. package/src/workspace/ci-runner-key.ts +250 -0
  64. package/src/workspace/ci-secrets.test.ts +106 -0
  65. package/src/workspace/ci-secrets.ts +213 -0
  66. package/src/workspace/ci-serve.test.ts +136 -37
  67. package/src/workspace/ci-serve.ts +66 -13
  68. package/src/workspace/reason-codes.test.ts +2 -0
  69. package/src/workspace/reason-codes.ts +7 -2
  70. package/src/workspace/trust/trust-write.test.ts +179 -0
  71. package/src/workspace/trust/trust-write.ts +377 -0
  72. package/src/workspace/trust-write.schema.json +265 -0
@@ -0,0 +1,217 @@
1
+ /**
2
+ * The runner key on a `chant ci serve` or `chant ci run` host (#4099, epic
3
+ * #4101).
4
+ *
5
+ * The host keeps its runner key in the serve's state directory, by default
6
+ * `<git common dir>/chant/ci-serve/runner-key.pem`: an Ed25519 private key in
7
+ * PEM, mode 0600, made by `--setup-key` when there is none. Its public half
8
+ * is the `ssh-ed25519` line `.chant/trust.json` lists under `runners`, and
9
+ * `--setup-key --trust` proposes that entry through `chant workspace trust
10
+ * runner add` (#3510) on a branch of its own, pushed for review. It never
11
+ * commits to the target branch: changing the trust config is a protected
12
+ * write, and the change is reviewed and merged like any other.
13
+ *
14
+ * `ci serve` refuses to start when its key is not listed, with the runner
15
+ * role, in `.chant/trust.json` at the target branch's head, since every run
16
+ * it signed would then be ignored by `ci tick --source runs`. `--unsigned`
17
+ * starts it without a key.
18
+ */
19
+ import { execFileSync } from "node:child_process";
20
+ import { generateKeyPairSync } from "node:crypto";
21
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
22
+ import { hostname, tmpdir } from "node:os";
23
+ import { join } from "node:path";
24
+ import { ciTagSigner } from "./ci-green.js";
25
+ import { CiRunError } from "./ci-run.js";
26
+ import { loadRunnerKey } from "./trust/dsse.js";
27
+ import { trustWrite } from "./trust/trust-write.js";
28
+ /** The runner key's file in the state directory. */
29
+ export const RUNNER_KEY_FILE = "runner-key.pem";
30
+ /** The principal `--setup-key --trust` lists the key for when `--principal` names none. */
31
+ export function defaultRunnerPrincipal() {
32
+ return `chant-runner:${hostname().split(".")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-") || "host"}`;
33
+ }
34
+ const message = (err) => (err instanceof Error ? err.message : String(err));
35
+ function git(cwd, args) {
36
+ return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
37
+ }
38
+ function tryGit(cwd, args) {
39
+ try {
40
+ return git(cwd, args);
41
+ }
42
+ catch {
43
+ return undefined;
44
+ }
45
+ }
46
+ /** Read the key at `path`, refusing one others can read or one that is not an Ed25519 private key in PEM. */
47
+ function readHostKey(path) {
48
+ const mode = statSync(path).mode & 0o777;
49
+ if (mode & 0o077)
50
+ throw new CiRunError(`${path} can be read by other users (mode ${mode.toString(8)}); chmod 600 it, since it signs for this runner`);
51
+ const pem = readFileSync(path, "utf-8");
52
+ try {
53
+ return { path, pem, publicKey: loadRunnerKey(pem).publicKey, created: false };
54
+ }
55
+ catch (err) {
56
+ throw new CiRunError(`${path} is not an Ed25519 private key in PEM: ${message(err)}`);
57
+ }
58
+ }
59
+ /** The host's runner key in `stateDir`, or undefined when there is none. */
60
+ export function hostRunnerKey(stateDir) {
61
+ const path = join(stateDir, RUNNER_KEY_FILE);
62
+ return existsSync(path) ? readHostKey(path) : undefined;
63
+ }
64
+ /** The host's runner key in `stateDir`, made when there is none: Ed25519, PEM, mode 0600 in a directory of mode 0700. */
65
+ export function ensureHostRunnerKey(stateDir) {
66
+ const found = hostRunnerKey(stateDir);
67
+ if (found)
68
+ return found;
69
+ mkdirSync(stateDir, { recursive: true, mode: 0o700 });
70
+ const path = join(stateDir, RUNNER_KEY_FILE);
71
+ const { privateKey } = generateKeyPairSync("ed25519");
72
+ const pem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
73
+ // wx: never replace a key another process made in the meantime.
74
+ writeFileSync(path, pem, { mode: 0o600, flag: "wx" });
75
+ chmodSync(path, 0o600);
76
+ return { path, pem, publicKey: loadRunnerKey(pem).publicKey, created: true };
77
+ }
78
+ /**
79
+ * The target branch's head, as the readers of runners take it: the remote's
80
+ * branch after a fetch (best effort), else the local branch. Undefined when
81
+ * neither exists.
82
+ */
83
+ export function targetHead(top, branch, remote = "origin") {
84
+ if (tryGit(top, ["remote", "get-url", remote]) !== undefined)
85
+ tryGit(top, ["fetch", "-q", remote, `+refs/heads/${branch}:refs/remotes/${remote}/${branch}`]);
86
+ for (const ref of [`refs/remotes/${remote}/${branch}`, `refs/heads/${branch}`]) {
87
+ const commit = tryGit(top, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
88
+ if (commit)
89
+ return { commit, ref };
90
+ }
91
+ return undefined;
92
+ }
93
+ /** The exact command that lists `publicKey` for `principal`. */
94
+ export function trustCommand(principal, publicKey) {
95
+ return `chant workspace trust runner add ${principal} --key "${publicKey}"`;
96
+ }
97
+ /** Whether `.chant/trust.json` at `commit` lists the key as a runner holding the runner role: its principal, or why not. */
98
+ export function runnerKeyListed(top, commit, pem) {
99
+ try {
100
+ return { ok: true, principal: ciTagSigner(top, commit, pem, "the runs it signs").principal };
101
+ }
102
+ catch (err) {
103
+ return { ok: false, reason: message(err) };
104
+ }
105
+ }
106
+ /**
107
+ * Propose the runner entry for review (#4099): in a temporary worktree at
108
+ * the target's head, run `trust runner add` (#3510), commit the change on
109
+ * `chant/trust-runner/<principal>`, and push that branch to `remote` when
110
+ * the clone has it. The target branch is never written. The commit is made
111
+ * with the host's git identity and signing settings; when the policy at base
112
+ * has signers, the change needs a signature by a policy writer before it
113
+ * merges.
114
+ */
115
+ export function proposeRunnerTrust(req) {
116
+ const remote = req.remote ?? "origin";
117
+ const head = targetHead(req.top, req.branch, remote);
118
+ if (!head)
119
+ throw new CiRunError(`there is no branch ${req.branch} here or on ${remote}, so there is no trust config to add the runner to`);
120
+ const branch = `chant/trust-runner/${req.principal.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "")}`;
121
+ if (tryGit(req.top, ["rev-parse", "--verify", "-q", `refs/heads/${branch}`])) {
122
+ throw new CiRunError(`the branch ${branch} exists already; review or delete it, then run --setup-key --trust again`);
123
+ }
124
+ const dir = mkdtempSync(join(tmpdir(), "chant-trust-runner-"));
125
+ try {
126
+ git(req.top, ["worktree", "add", "-q", "-b", branch, dir, head.commit]);
127
+ const doc = trustWrite({ cwd: dir, action: "runner add", name: req.principal, key: req.publicKey, ...(req.by ? { by: req.by } : {}) });
128
+ if ("error" in doc)
129
+ throw new CiRunError(`trust runner add refused (${doc.error.code}): ${doc.error.message}`);
130
+ if (!doc.changed) {
131
+ git(req.top, ["worktree", "remove", "--force", dir]);
132
+ git(req.top, ["branch", "-q", "-D", branch]);
133
+ return { branch: null, commit: null, pushed: false, digest: doc.digest };
134
+ }
135
+ git(dir, ["add", "--", ".chant/trust.json"]);
136
+ git(dir, ["commit", "-q", "-m", `trust the ${req.principal} runner key\n\nchant ci --setup-key --trust: list this host's runner key in .chant/trust.json (#4099).`]);
137
+ const commit = git(dir, ["rev-parse", "HEAD"]);
138
+ let pushed = false;
139
+ if (tryGit(req.top, ["remote", "get-url", remote]) !== undefined) {
140
+ try {
141
+ git(dir, ["push", "-q", remote, `refs/heads/${branch}:refs/heads/${branch}`]);
142
+ pushed = true;
143
+ }
144
+ catch (err) {
145
+ throw new CiRunError(`committed ${branch} here, and pushing it to ${remote} failed: ${message(err).split("\n")[0]}`);
146
+ }
147
+ }
148
+ return { branch, commit, pushed, digest: doc.digest };
149
+ }
150
+ finally {
151
+ tryGit(req.top, ["worktree", "remove", "--force", dir]);
152
+ rmSync(dir, { recursive: true, force: true });
153
+ }
154
+ }
155
+ /** `--setup-key [--trust]`: make or read the key, see whether the target's head lists it, and with `trust` propose the entry. */
156
+ export function setupRunnerKey(req) {
157
+ const key = ensureHostRunnerKey(req.stateDir);
158
+ const head = targetHead(req.top, req.branch);
159
+ const listed = head ? runnerKeyListed(req.top, head.commit, key.pem) : undefined;
160
+ const principal = req.principal ?? defaultRunnerPrincipal();
161
+ const out = { key, head: head?.commit ?? null, listedAs: listed?.ok ? listed.principal : null, principal, command: trustCommand(principal, key.publicKey) };
162
+ if (req.trust && !out.listedAs)
163
+ out.proposal = proposeRunnerTrust({ top: req.top, branch: req.branch, principal, publicKey: key.publicKey, ...(req.by ? { by: req.by } : {}) });
164
+ return out;
165
+ }
166
+ /** What `--setup-key` prints on stderr; the public key alone goes to stdout. */
167
+ export function formatRunnerKeySetup(s, branch) {
168
+ const lines = [`runner key ${s.key.path} (${s.key.created ? "made now, mode 0600" : "already here"})`];
169
+ if (s.listedAs) {
170
+ lines.push(`.chant/trust.json at ${branch} ${s.head.slice(0, 12)} lists it for ${s.listedAs}, with the runner role`);
171
+ }
172
+ else if (s.proposal?.branch) {
173
+ lines.push(`committed the runner entry for ${s.principal} on ${s.proposal.branch} (${s.proposal.commit.slice(0, 12)})${s.proposal.pushed ? " and pushed it" : ""}; open a pull request into ${branch}, and the key counts once it merges`);
174
+ lines.push("changing .chant/trust.json is a protected write: when the trust policy has signers, an admin signs the commit before it merges");
175
+ }
176
+ else if (s.proposal) {
177
+ lines.push(`the runner entry for ${s.principal} is already at ${branch}'s head`);
178
+ }
179
+ else {
180
+ lines.push(`.chant/trust.json at ${branch}${s.head ? ` ${s.head.slice(0, 12)}` : ""} does not list it yet. In a checkout of ${branch}, run`, ` ${s.command}`, "commit the change through review, or run this again with --trust to push it on a branch");
181
+ }
182
+ return lines.join("\n");
183
+ }
184
+ /**
185
+ * The key `ci serve` signs with, checked before its first poll (#4099): the
186
+ * `--key` file, else `CHANT_RUNNER_KEY`, else the host's key in the state
187
+ * directory. Throws, with the command that lists it, when there is none or
188
+ * `.chant/trust.json` at the target's head does not list it with the runner
189
+ * role. With `unsigned` there is no key and nothing is checked.
190
+ */
191
+ export function ciServeRunnerKey(req) {
192
+ if (req.unsigned)
193
+ return { fromFlag: false, source: "none (--unsigned)" };
194
+ const host = req.keyFile === undefined && !req.envKey ? hostRunnerKey(req.stateDir) : undefined;
195
+ const pem = req.keyFile !== undefined ? readFileSync(req.keyFile, "utf-8") : req.envKey || host?.pem;
196
+ const source = req.keyFile !== undefined ? req.keyFile : req.envKey ? "CHANT_RUNNER_KEY" : (host?.path ?? join(req.stateDir, RUNNER_KEY_FILE));
197
+ if (pem === undefined) {
198
+ throw new CiRunError(`there is no runner key: no --key, no CHANT_RUNNER_KEY and no ${source}. Make one with chant ci serve --setup-key, list it with --trust, or start with --unsigned`);
199
+ }
200
+ let publicKey;
201
+ try {
202
+ publicKey = loadRunnerKey(pem).publicKey;
203
+ }
204
+ catch (err) {
205
+ throw new CiRunError(`the runner key from ${source} is not an Ed25519 private key in PEM: ${message(err)}`);
206
+ }
207
+ const head = targetHead(req.top, req.branch);
208
+ if (!head)
209
+ throw new CiRunError(`there is no branch ${req.branch} here or on origin, so the runner key can't be checked against its .chant/trust.json; fetch it, or start with --unsigned`);
210
+ const listed = runnerKeyListed(req.top, head.commit, pem);
211
+ if (!listed.ok) {
212
+ throw new CiRunError(`the runner key from ${source} is not trusted at ${branchAt(req.branch, head.commit)}: ${listed.reason}\n` +
213
+ ` List it on a branch for review with chant ci serve --setup-key --trust, or run ${trustCommand(defaultRunnerPrincipal(), publicKey)} in a checkout of ${req.branch} and merge the change. Start with --unsigned to serve without signing.`);
214
+ }
215
+ return { pem, fromFlag: req.keyFile !== undefined, source };
216
+ }
217
+ const branchAt = (branch, commit) => `${branch} ${commit.slice(0, 12)}`;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The chant runner's secrets file (#4096, epic #4101): the values a job gets
3
+ * from the host, such as cloud credentials and the endpoint, which a forge
4
+ * job would get from the forge's secrets.
5
+ *
6
+ * `chant ci run` and `chant ci serve` read it from `--secrets <file>`, or
7
+ * the file `CHANT_CI_SECRETS` names. It is a dotenv file of `NAME=value`
8
+ * lines, or a JSON object:
9
+ *
10
+ * - a map of name to value, `{ "AWS_SECRET_ACCESS_KEY": "..." }`;
11
+ * - or `{ "secrets": { ... }, "jobs": { "plan": [...], "apply": [...] } }`,
12
+ * where `jobs` lists, for a job, the names it gets. A job `jobs` doesn't
13
+ * name gets every secret.
14
+ *
15
+ * The allowlist lives in this file on the host, not in the repository, so a
16
+ * pull request can't widen what its plan gets.
17
+ *
18
+ * The file must belong to the user reading it and give group and others no
19
+ * permission at all, as ssh asks of a private key (`chmod 600`). A file
20
+ * that does is refused before any job runs.
21
+ *
22
+ * Every value in the file, and every value of the job's environment whose
23
+ * name looks secret ({@link SECRET_NAME}), is redacted from what the run
24
+ * writes about itself (../op/redact.ts).
25
+ */
26
+ import { type Redact } from "../op/redact.js";
27
+ import type { CiJobName } from "./ci-run.js";
28
+ /** The variable that names the secrets file when `--secrets` is not given. */
29
+ export declare const CI_SECRETS_ENV = "CHANT_CI_SECRETS";
30
+ /** A variable whose name matches this is taken as secret, and its value redacted. */
31
+ export declare const SECRET_NAME: RegExp;
32
+ /** The secrets file, read. */
33
+ export interface CiSecrets {
34
+ /** Where it was read from. */
35
+ file: string;
36
+ /** Every secret, by name. */
37
+ values: Record<string, string>;
38
+ /** For a job the file names, the secrets it gets. A job missing here gets every one. */
39
+ jobs: Partial<Record<CiJobName, string[]>>;
40
+ }
41
+ export declare class CiSecretsError extends Error {
42
+ constructor(message: string);
43
+ }
44
+ /**
45
+ * Refuse a secrets file anyone but its owner can read or write, or that
46
+ * belongs to another user, as ssh refuses a loose key. Not checked on
47
+ * Windows, whose files carry no such mode.
48
+ */
49
+ export declare function checkCiSecretsMode(file: string): void;
50
+ /** A dotenv text's `NAME=value` pairs. Errors name the line, never a value. */
51
+ export declare function parseDotenv(text: string, file: string): Record<string, string>;
52
+ /** The secrets in a file's text: JSON when it starts with `{`, else dotenv. */
53
+ export declare function parseCiSecrets(text: string, file: string): Omit<CiSecrets, "file">;
54
+ /** Read the secrets file, refusing one others can read. */
55
+ export declare function readCiSecrets(file: string): CiSecrets;
56
+ /** The secrets `job` gets: those `jobs` lists for it, else every one. */
57
+ export declare function ciSecretsFor(secrets: CiSecrets | undefined, job: CiJobName): Record<string, string>;
58
+ /**
59
+ * The redactor for a job: every value in the secrets file, whether the job
60
+ * gets it or not, and the value of each variable in the job's environment
61
+ * whose name looks secret, such as `GITHUB_TOKEN` or a `--pass-env
62
+ * AWS_SECRET_ACCESS_KEY`. Undefined when there is nothing to redact.
63
+ */
64
+ export declare function ciRedactor(jobEnv: Record<string, string>, secrets?: CiSecrets): Redact | undefined;
65
+ //# sourceMappingURL=ci-secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-secrets.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAEjD,qFAAqF;AACrF,eAAO,MAAM,WAAW,QAA+B,CAAC;AAMxD,8BAA8B;AAC9B,MAAM,WAAW,SAAS;IACxB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,wFAAwF;IACxF,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CAC5C;AAED,qBAAa,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAoBrD;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0D9E;AAOD,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAmClF;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAGrD;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,EAAE,GAAG,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAInG;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAIlG"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The chant runner's secrets file (#4096, epic #4101): the values a job gets
3
+ * from the host, such as cloud credentials and the endpoint, which a forge
4
+ * job would get from the forge's secrets.
5
+ *
6
+ * `chant ci run` and `chant ci serve` read it from `--secrets <file>`, or
7
+ * the file `CHANT_CI_SECRETS` names. It is a dotenv file of `NAME=value`
8
+ * lines, or a JSON object:
9
+ *
10
+ * - a map of name to value, `{ "AWS_SECRET_ACCESS_KEY": "..." }`;
11
+ * - or `{ "secrets": { ... }, "jobs": { "plan": [...], "apply": [...] } }`,
12
+ * where `jobs` lists, for a job, the names it gets. A job `jobs` doesn't
13
+ * name gets every secret.
14
+ *
15
+ * The allowlist lives in this file on the host, not in the repository, so a
16
+ * pull request can't widen what its plan gets.
17
+ *
18
+ * The file must belong to the user reading it and give group and others no
19
+ * permission at all, as ssh asks of a private key (`chmod 600`). A file
20
+ * that does is refused before any job runs.
21
+ *
22
+ * Every value in the file, and every value of the job's environment whose
23
+ * name looks secret ({@link SECRET_NAME}), is redacted from what the run
24
+ * writes about itself (../op/redact.ts).
25
+ */
26
+ import { readFileSync, statSync } from "node:fs";
27
+ import { redactorOf } from "../op/redact.js";
28
+ import { RUNNER_KEY_ENV } from "./ci-green.js";
29
+ /** The variable that names the secrets file when `--secrets` is not given. */
30
+ export const CI_SECRETS_ENV = "CHANT_CI_SECRETS";
31
+ /** A variable whose name matches this is taken as secret, and its value redacted. */
32
+ export const SECRET_NAME = /TOKEN|SECRET|KEY|PASSWORD/i;
33
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
34
+ const JOBS = ["plan", "apply"];
35
+ export class CiSecretsError extends Error {
36
+ constructor(message) {
37
+ super(message);
38
+ this.name = "CiSecretsError";
39
+ }
40
+ }
41
+ /**
42
+ * Refuse a secrets file anyone but its owner can read or write, or that
43
+ * belongs to another user, as ssh refuses a loose key. Not checked on
44
+ * Windows, whose files carry no such mode.
45
+ */
46
+ export function checkCiSecretsMode(file) {
47
+ let st;
48
+ try {
49
+ st = statSync(file);
50
+ }
51
+ catch (err) {
52
+ throw new CiSecretsError(`the secrets file ${file} can't be read: ${err.code ?? String(err)}`);
53
+ }
54
+ if (!st.isFile())
55
+ throw new CiSecretsError(`the secrets file ${file} is not a file`);
56
+ if (process.platform === "win32")
57
+ return;
58
+ const loose = Number(st.mode) & 0o077;
59
+ if (loose !== 0) {
60
+ const mode = (Number(st.mode) & 0o777).toString(8).padStart(4, "0");
61
+ throw new CiSecretsError(`the secrets file ${file} has mode ${mode}, so its group or other users can ${loose & 0o044 ? "read" : "change"} it; a secrets file must be readable only by its owner, as ssh asks of a key: chmod 600 ${file}`);
62
+ }
63
+ const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
64
+ if (uid !== undefined && Number(st.uid) !== uid) {
65
+ throw new CiSecretsError(`the secrets file ${file} belongs to uid ${st.uid}, not to this user (uid ${uid}); a secrets file must be its reader's own`);
66
+ }
67
+ }
68
+ /** A dotenv text's `NAME=value` pairs. Errors name the line, never a value. */
69
+ export function parseDotenv(text, file) {
70
+ const out = {};
71
+ let i = 0;
72
+ let line = 1;
73
+ const fail = (why) => {
74
+ throw new CiSecretsError(`${file}:${line}: ${why}`);
75
+ };
76
+ while (i < text.length) {
77
+ // One line: blank, a comment, or NAME=value.
78
+ const eol = text.indexOf("\n", i);
79
+ const rest = text.slice(i, eol === -1 ? text.length : eol);
80
+ const trimmed = rest.trim();
81
+ if (trimmed === "" || trimmed.startsWith("#")) {
82
+ i = eol === -1 ? text.length : eol + 1;
83
+ line++;
84
+ continue;
85
+ }
86
+ const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*/.exec(rest);
87
+ if (!m)
88
+ fail("expected NAME=value");
89
+ const name = m[1];
90
+ let j = i + m[0].length;
91
+ const startLine = line;
92
+ let value = "";
93
+ const q = text[j];
94
+ if (q === '"' || q === "'") {
95
+ j++;
96
+ for (;;) {
97
+ if (j >= text.length) {
98
+ line = startLine;
99
+ fail(`the value of ${name} has no closing ${q}`);
100
+ }
101
+ const c = text[j];
102
+ if (c === q)
103
+ break;
104
+ if (c === "\n")
105
+ line++;
106
+ if (q === '"' && c === "\\" && j + 1 < text.length) {
107
+ const n = text[j + 1];
108
+ value += n === "n" ? "\n" : n === "r" ? "\r" : n === "t" ? "\t" : n;
109
+ j += 2;
110
+ continue;
111
+ }
112
+ value += c;
113
+ j++;
114
+ }
115
+ j++;
116
+ const end = text.indexOf("\n", j);
117
+ const after = text.slice(j, end === -1 ? text.length : end).trim();
118
+ if (after !== "" && !after.startsWith("#"))
119
+ fail(`unexpected text after the quoted value of ${name}`);
120
+ i = end === -1 ? text.length : end + 1;
121
+ }
122
+ else {
123
+ const end = text.indexOf("\n", j);
124
+ value = text.slice(j, end === -1 ? text.length : end).replace(/\s+#.*$/, "").trim();
125
+ i = end === -1 ? text.length : end + 1;
126
+ }
127
+ if (Object.prototype.hasOwnProperty.call(out, name))
128
+ fail(`${name} is set twice`);
129
+ out[name] = value;
130
+ line++;
131
+ }
132
+ return out;
133
+ }
134
+ function checkName(name, file) {
135
+ if (!ENV_NAME.test(name))
136
+ throw new CiSecretsError(`${file} names a secret ${JSON.stringify(name)}, which is not a variable name`);
137
+ if (name === RUNNER_KEY_ENV)
138
+ throw new CiSecretsError(`${file} sets ${RUNNER_KEY_ENV}, and the runner key is never passed to a job; give it to ci run or ci serve with --key or ${RUNNER_KEY_ENV}`);
139
+ }
140
+ /** The secrets in a file's text: JSON when it starts with `{`, else dotenv. */
141
+ export function parseCiSecrets(text, file) {
142
+ if (!text.trimStart().startsWith("{")) {
143
+ const values = parseDotenv(text, file);
144
+ for (const name of Object.keys(values))
145
+ checkName(name, file);
146
+ return { values, jobs: {} };
147
+ }
148
+ let raw;
149
+ try {
150
+ raw = JSON.parse(text);
151
+ }
152
+ catch {
153
+ // The parser's message quotes the text near the error, which may be a secret.
154
+ throw new CiSecretsError(`${file} starts with {, and is not valid JSON`);
155
+ }
156
+ const doc = raw;
157
+ const structured = doc.secrets !== null && typeof doc.secrets === "object" && !Array.isArray(doc.secrets);
158
+ const map = (structured ? doc.secrets : doc);
159
+ const values = {};
160
+ for (const [name, value] of Object.entries(map)) {
161
+ checkName(name, file);
162
+ if (typeof value !== "string")
163
+ throw new CiSecretsError(`${file}: the secret ${name} must be a string`);
164
+ values[name] = value;
165
+ }
166
+ const jobs = {};
167
+ if (structured) {
168
+ for (const key of Object.keys(doc))
169
+ if (key !== "secrets" && key !== "jobs")
170
+ throw new CiSecretsError(`${file} has ${JSON.stringify(key)} beside secrets and jobs`);
171
+ const given = doc.jobs ?? {};
172
+ if (given === null || typeof given !== "object" || Array.isArray(given))
173
+ throw new CiSecretsError(`${file}: jobs must map plan and apply to lists of secret names`);
174
+ for (const [job, names] of Object.entries(given)) {
175
+ if (!JOBS.includes(job))
176
+ throw new CiSecretsError(`${file}: jobs names ${JSON.stringify(job)}, and the jobs are plan and apply`);
177
+ if (!Array.isArray(names) || names.some((n) => typeof n !== "string"))
178
+ throw new CiSecretsError(`${file}: jobs.${job} must be a list of secret names`);
179
+ for (const n of names)
180
+ if (!Object.prototype.hasOwnProperty.call(values, n))
181
+ throw new CiSecretsError(`${file}: jobs.${job} names ${n}, which the file does not set`);
182
+ jobs[job] = [...names];
183
+ }
184
+ }
185
+ return { values, jobs };
186
+ }
187
+ /** Read the secrets file, refusing one others can read. */
188
+ export function readCiSecrets(file) {
189
+ checkCiSecretsMode(file);
190
+ return { file, ...parseCiSecrets(readFileSync(file, "utf-8"), file) };
191
+ }
192
+ /** The secrets `job` gets: those `jobs` lists for it, else every one. */
193
+ export function ciSecretsFor(secrets, job) {
194
+ if (!secrets)
195
+ return {};
196
+ const names = secrets.jobs[job] ?? Object.keys(secrets.values);
197
+ return Object.fromEntries(names.map((n) => [n, secrets.values[n]]));
198
+ }
199
+ /**
200
+ * The redactor for a job: every value in the secrets file, whether the job
201
+ * gets it or not, and the value of each variable in the job's environment
202
+ * whose name looks secret, such as `GITHUB_TOKEN` or a `--pass-env
203
+ * AWS_SECRET_ACCESS_KEY`. Undefined when there is nothing to redact.
204
+ */
205
+ export function ciRedactor(jobEnv, secrets) {
206
+ const values = Object.values(secrets?.values ?? {});
207
+ for (const [name, value] of Object.entries(jobEnv))
208
+ if (SECRET_NAME.test(name))
209
+ values.push(value);
210
+ return redactorOf(values);
211
+ }
@@ -15,6 +15,10 @@
15
15
  * the one `chant operator` holds) named for `prApplyGroup`, renews it while
16
16
  * it runs and releases it after. An apply that finds it held waits for a
17
17
  * later poll, as a forge's concurrency group queues it.
18
+ * - Merged pull request. An apply asks the forge for the pull request whose
19
+ * merge commit the new head is (`pullRequestFor`, as pr-apply does) and
20
+ * passes it as `--pr`, so its commit status link and run index use
21
+ * `<pr>/<commit>`. With none found it runs without `--pr`.
18
22
  * - Resume records. Each apply gets `<state>/resume/<prApplyRecordKey>.json`
19
23
  * as its `--resume` record, so a re-run of a failed apply at the same
20
24
  * commit finishes under the approval it has. This replaces the forge's CI
@@ -40,6 +44,8 @@ export type CiServeForgeKind = "github" | "forgejo";
40
44
  export interface CiServeForge {
41
45
  openPullRequests(base: string): Promise<OpenPullRequest[]>;
42
46
  branchHead(branch: string): Promise<string | undefined>;
47
+ /** The merged pull request that produced a commit, as pr-apply finds it (`pullRequestFor`). Without it an apply gets no `--pr`. */
48
+ pullRequestFor?(sha: string): Promise<number | null>;
43
49
  }
44
50
  /** A job the forge's state calls for: the plan of a pull request's head, or the apply of a target-branch commit. */
45
51
  export type CiServeTrigger = {
@@ -207,6 +213,13 @@ export declare function ciServeLoop(opts: CiServeOptions & {
207
213
  }): Promise<void>;
208
214
  /** One line for an event, as `ci serve` prints it. */
209
215
  export declare function formatCiServeEvent(e: CiServeEvent): string;
216
+ /**
217
+ * `chant ci serve|run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]` (#4099):
218
+ * make the host's runner key when there is none, print its public half on
219
+ * stdout, and say whether the target's head lists it. With --trust, commit
220
+ * the runner entry on a branch of its own and push it for review.
221
+ */
222
+ export declare function runnerKeySetupCommand(ctx: CommandContext): number;
210
223
  /** `--interval`: seconds, or a duration such as `30s` or `2m`. Default 30 seconds. */
211
224
  export declare function ciServeInterval(value: string | undefined): number;
212
225
  /** `chant ci serve` (#4095). */
@@ -1 +1 @@
1
- {"version":3,"file":"ci-serve.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-serve.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ci-serve.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-serve.ts"],"names":[],"mappings":"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"}