gcb-host 0.0.0 → 0.0.1
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/CHANGELOG.md +9 -0
- package/bin/gcb.js +5 -0
- package/lib/audit.d.ts +25 -0
- package/lib/audit.d.ts.map +1 -0
- package/lib/audit.js +73 -0
- package/lib/audit.js.map +1 -0
- package/lib/broker.d.ts +61 -0
- package/lib/broker.d.ts.map +1 -0
- package/lib/broker.js +512 -0
- package/lib/broker.js.map +1 -0
- package/lib/commands/authorize.d.ts +38 -0
- package/lib/commands/authorize.d.ts.map +1 -0
- package/lib/commands/authorize.js +134 -0
- package/lib/commands/authorize.js.map +1 -0
- package/lib/commands/compose.d.ts +42 -0
- package/lib/commands/compose.d.ts.map +1 -0
- package/lib/commands/compose.js +208 -0
- package/lib/commands/compose.js.map +1 -0
- package/lib/commands/deployment.d.ts +49 -0
- package/lib/commands/deployment.d.ts.map +1 -0
- package/lib/commands/deployment.js +72 -0
- package/lib/commands/deployment.js.map +1 -0
- package/lib/commands/diagnose.d.ts +4 -0
- package/lib/commands/diagnose.d.ts.map +1 -0
- package/lib/commands/diagnose.js +213 -0
- package/lib/commands/diagnose.js.map +1 -0
- package/lib/commands/host-opener.d.ts +23 -0
- package/lib/commands/host-opener.d.ts.map +1 -0
- package/lib/commands/host-opener.js +244 -0
- package/lib/commands/host-opener.js.map +1 -0
- package/lib/commands/init.d.ts +153 -0
- package/lib/commands/init.d.ts.map +1 -0
- package/lib/commands/init.js +510 -0
- package/lib/commands/init.js.map +1 -0
- package/lib/commands/stage.d.ts +44 -0
- package/lib/commands/stage.d.ts.map +1 -0
- package/lib/commands/stage.js +165 -0
- package/lib/commands/stage.js.map +1 -0
- package/lib/config.d.ts +110 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +408 -0
- package/lib/config.js.map +1 -0
- package/lib/daemon.d.ts +45 -0
- package/lib/daemon.d.ts.map +1 -0
- package/lib/daemon.js +123 -0
- package/lib/daemon.js.map +1 -0
- package/lib/device-flow.d.ts +80 -0
- package/lib/device-flow.d.ts.map +1 -0
- package/lib/device-flow.js +148 -0
- package/lib/device-flow.js.map +1 -0
- package/lib/gh.d.ts +37 -0
- package/lib/gh.d.ts.map +1 -0
- package/lib/gh.js +181 -0
- package/lib/gh.js.map +1 -0
- package/lib/http.d.ts +20 -0
- package/lib/http.d.ts.map +1 -0
- package/lib/http.js +133 -0
- package/lib/http.js.map +1 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +52 -0
- package/lib/index.js.map +1 -0
- package/lib/policy.d.ts +60 -0
- package/lib/policy.d.ts.map +1 -0
- package/lib/policy.js +118 -0
- package/lib/policy.js.map +1 -0
- package/lib/providers/github-app.d.ts +56 -0
- package/lib/providers/github-app.d.ts.map +1 -0
- package/lib/providers/github-app.js +901 -0
- package/lib/providers/github-app.js.map +1 -0
- package/lib/providers/index.d.ts +29 -0
- package/lib/providers/index.d.ts.map +1 -0
- package/lib/providers/index.js +32 -0
- package/lib/providers/index.js.map +1 -0
- package/lib/providers/static.d.ts +18 -0
- package/lib/providers/static.d.ts.map +1 -0
- package/lib/providers/static.js +41 -0
- package/lib/providers/static.js.map +1 -0
- package/lib/proxy.d.ts +23 -0
- package/lib/proxy.d.ts.map +1 -0
- package/lib/proxy.js +57 -0
- package/lib/proxy.js.map +1 -0
- package/package.json +36 -2
- package/README.md +0 -1
package/lib/broker.js
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The broker itself: a unix-socket server that turns a (host, repository) request into one
|
|
3
|
+
* short-lived credential.
|
|
4
|
+
*
|
|
5
|
+
* Why a unix socket and not the LAN HTTP API the original design proposed: the container
|
|
6
|
+
* runs with `http_proxy`/`https_proxy`/`all_proxy` pointing at another machine and with
|
|
7
|
+
* `NODE_USE_ENV_PROXY=1`, so a plain-HTTP broker hop would route the shared key and the
|
|
8
|
+
* minted token through that proxy, in cleartext. A socket on the filesystem is reachable
|
|
9
|
+
* only from this host, ignores proxy environment variables entirely, and needs no TLS,
|
|
10
|
+
* nonce or replay window.
|
|
11
|
+
*
|
|
12
|
+
* Security properties enforced here:
|
|
13
|
+
* - default deny: unknown host, missing path, unparsable path and unlisted repository are
|
|
14
|
+
* all refusals, each recorded in the audit log;
|
|
15
|
+
* - the credential is scoped to exactly one repository and to narrowed permissions;
|
|
16
|
+
* - a refusal never explains internals to the caller (details go to the host-side log);
|
|
17
|
+
* - the socket path is created by this process and refuses to replace anything that is not
|
|
18
|
+
* already a socket, and refuses to follow a symlink.
|
|
19
|
+
*/
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import { parseProxy, withProxy } from './http.js';
|
|
22
|
+
import { buildGhArgv, PACKAGE_VERSION, getGhAction, MAX_BODY_CHARS, MAX_MESSAGE_BYTES, MAX_TITLE_CHARS, ProviderConfigError, ActionError, BRANCH, createLineReader, encodeMessage, parseMessage, socketPathRefusal, tokenFingerprint, } from 'gcb-core';
|
|
23
|
+
import net from 'node:net';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { hostConfig, normalizeRepoPath, repoAllowed } from './policy.js';
|
|
26
|
+
/** Reported by the `ping` operation so the container side can prove what it reached. */
|
|
27
|
+
export const BROKER_VERSION = PACKAGE_VERSION;
|
|
28
|
+
/** How long one socket connection may stay idle before it is dropped. */
|
|
29
|
+
// How long a connection may stay silent before it is dropped. It has to outlast the longest wait a client
|
|
30
|
+
// makes on it: `pr` waits PR_TIMEOUT_MS (60s) for an answer that arrives only when the handler finishes, and
|
|
31
|
+
// the handler is silent on the socket the whole time. At 30s the timer won every long action, and the client
|
|
32
|
+
// reported "unreachable" for a merge or a close that had already happened.
|
|
33
|
+
const REQUEST_TIMEOUT_MS = 120_000;
|
|
34
|
+
/** Response codes returned to the helper. */
|
|
35
|
+
export const CODES = {
|
|
36
|
+
BAD_REQUEST: 'bad-request',
|
|
37
|
+
PROTOCOL_NOT_HTTPS: 'protocol-not-https',
|
|
38
|
+
HOST_NOT_ALLOWED: 'host-not-allowed',
|
|
39
|
+
PATH_REQUIRED: 'path-required',
|
|
40
|
+
REPO_NOT_ALLOWED: 'repo-not-allowed',
|
|
41
|
+
PROVIDER_ERROR: 'provider-error',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Make the socket path safe to bind.
|
|
45
|
+
*
|
|
46
|
+
* A stale socket left by a crashed broker is removed. Anything else at that path — a
|
|
47
|
+
* symlink, a regular file, a directory — is refused rather than overwritten, because the path
|
|
48
|
+
* may sit in a directory the agent container can write to. The containing directory is checked
|
|
49
|
+
* for the same reason: a symlinked directory would let the container choose where the socket
|
|
50
|
+
* gets bound.
|
|
51
|
+
*
|
|
52
|
+
* @param socketPath - the configured socket path.
|
|
53
|
+
* @throws {Error} when the path exists and is not a stale socket, or the directory is a symlink.
|
|
54
|
+
*/
|
|
55
|
+
export function prepareSocketPath(socketPath) {
|
|
56
|
+
// Before creating anything: a Windows-backed mount inside WSL is 9p/drvfs, which will not carry a
|
|
57
|
+
// unix socket. Failing here names the reason; failing at bind time does not.
|
|
58
|
+
const refusal = socketPathRefusal(socketPath);
|
|
59
|
+
if (refusal !== null) {
|
|
60
|
+
throw new Error(`refusing to bind at ${socketPath}: ${refusal}`);
|
|
61
|
+
}
|
|
62
|
+
const directory = path.dirname(socketPath);
|
|
63
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
64
|
+
// Explicitly, not only in the mkdir call: on an ACL-based share (Synology's `synoacl`, which is
|
|
65
|
+
// where this was measured) the mode given to a create call is advisory and the directory comes
|
|
66
|
+
// out world-accessible. These permissions are the only thing stopping another local user from
|
|
67
|
+
// connecting to the socket and asking the broker for credentials.
|
|
68
|
+
fs.chmodSync(directory, 0o700);
|
|
69
|
+
if (fs.lstatSync(directory).isSymbolicLink()) {
|
|
70
|
+
throw new Error(`refusing to bind inside a symlinked directory: ${directory}`);
|
|
71
|
+
}
|
|
72
|
+
let stats;
|
|
73
|
+
try {
|
|
74
|
+
stats = fs.lstatSync(socketPath);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.code === 'ENOENT')
|
|
78
|
+
return;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
if (stats.isSymbolicLink()) {
|
|
82
|
+
throw new Error(`refusing to use socket path that is a symlink: ${socketPath}`);
|
|
83
|
+
}
|
|
84
|
+
if (!stats.isSocket()) {
|
|
85
|
+
throw new Error(`refusing to replace ${socketPath}: something that is not a socket is in the way. ` +
|
|
86
|
+
'If it is a leftover — an interrupted start, or a stray redirect onto the path — remove it and start again.');
|
|
87
|
+
}
|
|
88
|
+
fs.unlinkSync(socketPath);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build the request handler. Kept separate from the server so it can be unit-tested without
|
|
92
|
+
* a socket.
|
|
93
|
+
*
|
|
94
|
+
* @param options - handler options.
|
|
95
|
+
* @returns the handler.
|
|
96
|
+
*/
|
|
97
|
+
export function createRequestHandler(options) {
|
|
98
|
+
const { config, audit, providers, log = () => { } } = options;
|
|
99
|
+
/**
|
|
100
|
+
* Record a refusal and produce the caller-visible response.
|
|
101
|
+
*
|
|
102
|
+
* @param context - host/repo/session context.
|
|
103
|
+
* @param code - the response code.
|
|
104
|
+
* @param reason - the detailed reason; host-side log and audit only for provider errors.
|
|
105
|
+
* @param message - what the caller is told.
|
|
106
|
+
* @returns the refusal.
|
|
107
|
+
*/
|
|
108
|
+
function deny(context, code, reason, message,
|
|
109
|
+
// Detail for this side only: a provider's response body, which the caller must never receive. It
|
|
110
|
+
// reaches the audit record and the log line and nowhere else, because this function is what decides
|
|
111
|
+
// what the caller is told — a property on an error cannot make that decision.
|
|
112
|
+
hostDetail, event = 'credential') {
|
|
113
|
+
const detail = hostDetail === undefined || hostDetail === '' ? '' : ` | ${hostDetail}`;
|
|
114
|
+
audit.record({ event, decision: 'deny', ...context, code, reason: reason + detail });
|
|
115
|
+
log(`deny ${code} host=${context.host ?? '-'} repo=${context.repo ?? '-'} reason=${reason}${detail}`);
|
|
116
|
+
return { ok: false, code, reason: message };
|
|
117
|
+
}
|
|
118
|
+
async function handleRequest(message, proxy) {
|
|
119
|
+
const auditEvent = message['op'] === 'action' ? 'action' : 'credential';
|
|
120
|
+
const context = {
|
|
121
|
+
host: typeof message['host'] === 'string' ? message['host'] : null,
|
|
122
|
+
repo: null,
|
|
123
|
+
session: typeof message['session'] === 'string' ? message['session'].slice(0, 128) : null,
|
|
124
|
+
helper_pid: typeof message['pid'] === 'number' && Number.isInteger(message['pid']) ? message['pid'] : null,
|
|
125
|
+
};
|
|
126
|
+
if (message['op'] === 'ping') {
|
|
127
|
+
return { ok: true, version: BROKER_VERSION, hosts: [...providers.keys()] };
|
|
128
|
+
}
|
|
129
|
+
if (message['op'] !== 'credential' &&
|
|
130
|
+
message['op'] !== 'logs' &&
|
|
131
|
+
message['op'] !== 'pull-request' &&
|
|
132
|
+
message['op'] !== 'action') {
|
|
133
|
+
return deny(context, CODES.BAD_REQUEST, `unknown op ${JSON.stringify(message['op'])}`, 'unsupported operation');
|
|
134
|
+
}
|
|
135
|
+
// Host first: whether plaintext is tolerated is a property of the *host's* configuration,
|
|
136
|
+
// not of the request, so the protocol can only be judged once the host is known.
|
|
137
|
+
const block = hostConfig(config, message['host']);
|
|
138
|
+
if (!block) {
|
|
139
|
+
return deny(context, CODES.HOST_NOT_ALLOWED, 'no configuration for this host', 'host is not configured', undefined, auditEvent);
|
|
140
|
+
}
|
|
141
|
+
context.host = block.host;
|
|
142
|
+
const protocol = message['protocol'];
|
|
143
|
+
const insecureHttpAllowed = block.allowInsecureHttp === true;
|
|
144
|
+
if (message['op'] === 'credential' &&
|
|
145
|
+
protocol !== 'https' &&
|
|
146
|
+
!(protocol === 'http' && insecureHttpAllowed)) {
|
|
147
|
+
return deny(context, CODES.PROTOCOL_NOT_HTTPS, `protocol=${JSON.stringify(protocol)} (allowInsecureHttp=${String(insecureHttpAllowed)})`, 'only https is served for this host (a plaintext request would expose the credential on the wire)');
|
|
148
|
+
}
|
|
149
|
+
const repo = normalizeRepoPath(message['path']);
|
|
150
|
+
if (!repo) {
|
|
151
|
+
const reason = typeof message['path'] === 'string' && message['path']
|
|
152
|
+
? `unparsable path ${JSON.stringify(message['path'])}`
|
|
153
|
+
: 'no path sent: git omits it unless credential.useHttpPath is enabled';
|
|
154
|
+
return deny(context, CODES.PATH_REQUIRED, reason, 'the request carries no usable repository path, so a single-repository credential cannot be issued; enable `git config --global credential.useHttpPath true`', undefined, auditEvent);
|
|
155
|
+
}
|
|
156
|
+
context.repo = repo.full;
|
|
157
|
+
if (!repoAllowed(block.allow, repo.full)) {
|
|
158
|
+
return deny(context, CODES.REPO_NOT_ALLOWED, 'repository not in allowlist', 'repository is not allowlisted', undefined, auditEvent);
|
|
159
|
+
}
|
|
160
|
+
const provider = providers.get(block.host);
|
|
161
|
+
if (!provider) {
|
|
162
|
+
return deny(context, CODES.PROVIDER_ERROR, 'no provider instance for this host', 'provider unavailable', undefined, auditEvent);
|
|
163
|
+
}
|
|
164
|
+
if (message['op'] === 'action') {
|
|
165
|
+
const ghAction = message['ghAction'];
|
|
166
|
+
const values = message['values'];
|
|
167
|
+
const denyAction = (code, reason, message, hostDetail) => deny(context, code, reason, message, hostDetail, 'action');
|
|
168
|
+
if (typeof ghAction !== 'string' || values === null || typeof values !== 'object' || Array.isArray(values)) {
|
|
169
|
+
return denyAction(CODES.BAD_REQUEST, 'bad action request', 'the request carries no usable action');
|
|
170
|
+
}
|
|
171
|
+
const actionValues = values;
|
|
172
|
+
let spec;
|
|
173
|
+
try {
|
|
174
|
+
spec = getGhAction(ghAction);
|
|
175
|
+
buildGhArgv(ghAction, actionValues, repo.full);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof ActionError) {
|
|
179
|
+
return denyAction(CODES.BAD_REQUEST, error.message, error.message);
|
|
180
|
+
}
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
if (typeof provider.runAction !== 'function') {
|
|
184
|
+
return denyAction(CODES.PROVIDER_ERROR, `provider ${provider.name} cannot run actions`, "this host's provider cannot run GitHub actions");
|
|
185
|
+
}
|
|
186
|
+
const startedAt = Date.now();
|
|
187
|
+
try {
|
|
188
|
+
const result = await provider.runAction({
|
|
189
|
+
host: block.host,
|
|
190
|
+
owner: repo.owner,
|
|
191
|
+
repo: repo.repo,
|
|
192
|
+
full: repo.full,
|
|
193
|
+
action: ghAction,
|
|
194
|
+
values: actionValues,
|
|
195
|
+
proxy,
|
|
196
|
+
});
|
|
197
|
+
if (result.error !== undefined) {
|
|
198
|
+
const refusal = denyAction(CODES.PROVIDER_ERROR, result.error, 'the host could not start gh');
|
|
199
|
+
return result.warning === undefined ? refusal : { ...refusal, warning: result.warning };
|
|
200
|
+
}
|
|
201
|
+
audit.record({
|
|
202
|
+
event: 'action',
|
|
203
|
+
decision: 'allow',
|
|
204
|
+
...context,
|
|
205
|
+
action: ghAction,
|
|
206
|
+
command: spec.command.join(' '),
|
|
207
|
+
permissions: spec.permissions,
|
|
208
|
+
duration_ms: Date.now() - startedAt,
|
|
209
|
+
token_fingerprint: result.tokenFingerprint,
|
|
210
|
+
exit_code: result.code,
|
|
211
|
+
truncated: result.truncated === true,
|
|
212
|
+
timed_out: result.timedOut === true,
|
|
213
|
+
bytes: Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr),
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
ok: true,
|
|
217
|
+
stdout: result.stdout,
|
|
218
|
+
stderr: result.stderr,
|
|
219
|
+
exitCode: result.code,
|
|
220
|
+
truncated: result.truncated,
|
|
221
|
+
timedOut: result.timedOut,
|
|
222
|
+
...(result.warning === undefined ? {} : { warning: result.warning }),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
const reason = String(error.message ?? error).slice(0, 300);
|
|
227
|
+
const hostDetail = error.hostDetail;
|
|
228
|
+
const callerMessage = error instanceof ProviderConfigError ? reason : 'the broker could not run that action; see the broker log';
|
|
229
|
+
return denyAction(CODES.PROVIDER_ERROR, reason, callerMessage, hostDetail);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (message['op'] === 'logs') {
|
|
233
|
+
const jobId = message['jobId'];
|
|
234
|
+
if (typeof jobId !== 'number' || !Number.isInteger(jobId) || jobId <= 0) {
|
|
235
|
+
return deny(context, CODES.BAD_REQUEST, `bad jobId ${JSON.stringify(jobId)}`, 'the request carries no usable workflow job id');
|
|
236
|
+
}
|
|
237
|
+
if (typeof provider.getJobLog !== 'function') {
|
|
238
|
+
return deny(context, CODES.PROVIDER_ERROR, `provider ${provider.name} cannot read logs`, "this host's provider cannot read workflow logs");
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const jobLog = await provider.getJobLog({
|
|
242
|
+
host: block.host,
|
|
243
|
+
owner: repo.owner,
|
|
244
|
+
repo: repo.repo,
|
|
245
|
+
jobId,
|
|
246
|
+
proxy,
|
|
247
|
+
});
|
|
248
|
+
audit.record({
|
|
249
|
+
event: 'logs',
|
|
250
|
+
decision: 'allow',
|
|
251
|
+
...context,
|
|
252
|
+
job_id: jobId,
|
|
253
|
+
bytes: Buffer.byteLength(jobLog.text, 'utf8'),
|
|
254
|
+
truncated: jobLog.truncated === true,
|
|
255
|
+
});
|
|
256
|
+
return { ok: true, log: jobLog.text, truncated: jobLog.truncated === true };
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
// Same rule as a credential: a provider response can embed anything, so only our own
|
|
260
|
+
// configuration errors are shown to the caller.
|
|
261
|
+
const reason = String(error.message ?? error).slice(0, 300);
|
|
262
|
+
const hostDetail = error.hostDetail;
|
|
263
|
+
const callerMessage = error instanceof ProviderConfigError
|
|
264
|
+
? reason
|
|
265
|
+
: 'the broker could not read that log; see the broker log';
|
|
266
|
+
return deny(context, CODES.PROVIDER_ERROR, reason, callerMessage, hostDetail);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (message['op'] === 'pull-request') {
|
|
270
|
+
const action = message['action'] ?? 'open';
|
|
271
|
+
if (action !== 'open' &&
|
|
272
|
+
action !== 'status' &&
|
|
273
|
+
action !== 'comment' &&
|
|
274
|
+
action !== 'threads' &&
|
|
275
|
+
action !== 'reply' &&
|
|
276
|
+
action !== 'edit' &&
|
|
277
|
+
action !== 'resolve') {
|
|
278
|
+
return deny(context, CODES.BAD_REQUEST, `bad action ${JSON.stringify(action)}`, 'unknown pull request action');
|
|
279
|
+
}
|
|
280
|
+
const number = message['number'];
|
|
281
|
+
const commentId = message['commentId'];
|
|
282
|
+
const threadId = message['threadId'];
|
|
283
|
+
const head = message['head'];
|
|
284
|
+
const base = message['base'];
|
|
285
|
+
const title = message['title'];
|
|
286
|
+
const body = message['body'];
|
|
287
|
+
const filePath = message['filePath'];
|
|
288
|
+
const line = message['line'];
|
|
289
|
+
const side = message['side'] ?? 'right';
|
|
290
|
+
const branch = BRANCH;
|
|
291
|
+
const usable = (value) => typeof value === 'string' && branch.test(value) && !value.includes('..');
|
|
292
|
+
if (action === 'open' && !usable(head)) {
|
|
293
|
+
return deny(context, CODES.BAD_REQUEST, `bad head ${JSON.stringify(head)}`, 'the head branch name is not usable');
|
|
294
|
+
}
|
|
295
|
+
if (action === 'open' && base !== undefined && !usable(base)) {
|
|
296
|
+
return deny(context, CODES.BAD_REQUEST, `bad base ${JSON.stringify(base)}`, 'the base branch name is not usable');
|
|
297
|
+
}
|
|
298
|
+
if (action === 'open' && (typeof title !== 'string' || title.trim() === '' || title.length > MAX_TITLE_CHARS)) {
|
|
299
|
+
return deny(context, CODES.BAD_REQUEST, 'bad title', `a pull request needs a title of at most ${MAX_TITLE_CHARS} characters`);
|
|
300
|
+
}
|
|
301
|
+
if (body !== undefined && (typeof body !== 'string' || body.length > MAX_BODY_CHARS)) {
|
|
302
|
+
return deny(context, CODES.BAD_REQUEST, 'bad body', `the body must be text of at most ${MAX_BODY_CHARS} characters`);
|
|
303
|
+
}
|
|
304
|
+
// An anchored comment: both halves or neither, only on a comment, and never outside the repository.
|
|
305
|
+
const anchored = filePath !== undefined || line !== undefined;
|
|
306
|
+
if (anchored && action !== 'comment') {
|
|
307
|
+
return deny(context, CODES.BAD_REQUEST, `anchor fields with ${action}`, 'only a comment can be anchored to a file and a line');
|
|
308
|
+
}
|
|
309
|
+
if (anchored && (filePath === undefined || line === undefined)) {
|
|
310
|
+
return deny(context, CODES.BAD_REQUEST, 'half an anchor', 'an anchored comment needs a file and a line, or neither');
|
|
311
|
+
}
|
|
312
|
+
if (filePath !== undefined) {
|
|
313
|
+
if (typeof filePath !== 'string' ||
|
|
314
|
+
filePath === '' ||
|
|
315
|
+
filePath.startsWith('/') ||
|
|
316
|
+
filePath.length > 1024 ||
|
|
317
|
+
filePath.split('/').includes('..')) {
|
|
318
|
+
return deny(context, CODES.BAD_REQUEST, `bad file path ${JSON.stringify(filePath)}`, 'the file path is not usable');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (line !== undefined && (!Number.isInteger(line) || Number(line) <= 0)) {
|
|
322
|
+
return deny(context, CODES.BAD_REQUEST, `bad line ${JSON.stringify(line)}`, 'the line has to be a positive number');
|
|
323
|
+
}
|
|
324
|
+
if (side !== 'left' && side !== 'right') {
|
|
325
|
+
return deny(context, CODES.BAD_REQUEST, `bad side ${JSON.stringify(side)}`, 'the side has to be left or right');
|
|
326
|
+
}
|
|
327
|
+
if ((action === 'comment' || action === 'reply' || action === 'edit') && (typeof body !== 'string' || body.trim() === '')) {
|
|
328
|
+
return deny(context, CODES.BAD_REQUEST, 'empty comment', 'a comment has to say something');
|
|
329
|
+
}
|
|
330
|
+
if ((action === 'reply' || action === 'edit') && (typeof commentId !== 'number' || !Number.isInteger(commentId) || commentId <= 0)) {
|
|
331
|
+
return deny(context, CODES.BAD_REQUEST, 'bad commentId', action === 'reply' ? 'a reply needs the id of the comment it answers' : 'an edit needs the id of the review comment it changes');
|
|
332
|
+
}
|
|
333
|
+
if (action === 'resolve' && (typeof threadId !== 'string' || threadId === '')) {
|
|
334
|
+
return deny(context, CODES.BAD_REQUEST, 'bad threadId', 'resolving needs the id of the thread, which --threads prints');
|
|
335
|
+
}
|
|
336
|
+
if (action !== 'open' && action !== 'resolve' && action !== 'edit' && (typeof number !== 'number' || !Number.isInteger(number) || number <= 0)) {
|
|
337
|
+
return deny(context, CODES.BAD_REQUEST, `bad number ${JSON.stringify(number)}`, 'this action needs a pull request number');
|
|
338
|
+
}
|
|
339
|
+
if (typeof provider.pullRequest !== 'function') {
|
|
340
|
+
return deny(context, CODES.PROVIDER_ERROR, `provider ${provider.name} cannot manage pull requests`, "this host's provider cannot manage pull requests");
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const result = await provider.pullRequest({
|
|
344
|
+
host: block.host,
|
|
345
|
+
owner: repo.owner,
|
|
346
|
+
repo: repo.repo,
|
|
347
|
+
action,
|
|
348
|
+
proxy,
|
|
349
|
+
...(typeof number === 'number' ? { number } : {}),
|
|
350
|
+
...(typeof commentId === 'number' ? { commentId } : {}),
|
|
351
|
+
...(typeof threadId === 'string' ? { threadId } : {}),
|
|
352
|
+
...(typeof filePath === 'string' ? { filePath } : {}),
|
|
353
|
+
...(typeof line === 'number' ? { line } : {}),
|
|
354
|
+
...(typeof filePath === 'string' && typeof line === 'number' ? { side } : {}),
|
|
355
|
+
...(typeof head === 'string' ? { head } : {}),
|
|
356
|
+
...(typeof base === 'string' ? { base } : {}),
|
|
357
|
+
...(typeof title === 'string' ? { title } : {}),
|
|
358
|
+
...(typeof body === 'string' ? { body } : {}),
|
|
359
|
+
draft: message['draft'] === true,
|
|
360
|
+
});
|
|
361
|
+
audit.record({
|
|
362
|
+
event: 'pull-request',
|
|
363
|
+
decision: 'allow',
|
|
364
|
+
...context,
|
|
365
|
+
action,
|
|
366
|
+
...(typeof number === 'number' ? { number } : {}),
|
|
367
|
+
// The comment an edit changes is the one thing the entry would otherwise not say, and the
|
|
368
|
+
// number alone does not name it. Recorded for a reply too, which also carries an id.
|
|
369
|
+
...(typeof commentId === 'number' ? { comment_id: commentId } : {}),
|
|
370
|
+
...(typeof head === 'string' ? { head } : {}),
|
|
371
|
+
...(typeof base === 'string' ? { base } : {}),
|
|
372
|
+
pr_number: result.number,
|
|
373
|
+
pr_url: result.url,
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
ok: true,
|
|
377
|
+
prNumber: result.number,
|
|
378
|
+
prUrl: result.url,
|
|
379
|
+
prState: result.state,
|
|
380
|
+
prStatus: result.status,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
const reason = String(error.message ?? error).slice(0, 300);
|
|
385
|
+
const hostDetail = error.hostDetail;
|
|
386
|
+
const callerMessage = error instanceof ProviderConfigError
|
|
387
|
+
? reason
|
|
388
|
+
: 'the broker could not manage that pull request; see the broker log';
|
|
389
|
+
return deny(context, CODES.PROVIDER_ERROR, reason, callerMessage, hostDetail);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
const credential = await provider.getCredential({
|
|
394
|
+
host: block.host,
|
|
395
|
+
owner: repo.owner,
|
|
396
|
+
repo: repo.repo,
|
|
397
|
+
full: repo.full,
|
|
398
|
+
proxy,
|
|
399
|
+
});
|
|
400
|
+
audit.record({
|
|
401
|
+
event: 'credential',
|
|
402
|
+
decision: 'allow',
|
|
403
|
+
...context,
|
|
404
|
+
provider: provider.name,
|
|
405
|
+
token_fingerprint: tokenFingerprint(credential.password),
|
|
406
|
+
expires_at: new Date(credential.expiresAt).toISOString(),
|
|
407
|
+
cached: credential.cached === true,
|
|
408
|
+
});
|
|
409
|
+
return {
|
|
410
|
+
ok: true,
|
|
411
|
+
username: credential.username,
|
|
412
|
+
password: credential.password,
|
|
413
|
+
expires_at: new Date(credential.expiresAt).toISOString(),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
// The caller normally gets a generic message, because provider errors can embed API
|
|
418
|
+
// responses. A ProviderConfigError is ours — a permission name and the configuration — and
|
|
419
|
+
// passing it on is the difference between "see the broker log" and knowing what is missing.
|
|
420
|
+
const reason = String(error.message ?? error).slice(0, 300);
|
|
421
|
+
const hostDetail = error.hostDetail;
|
|
422
|
+
const message = error instanceof ProviderConfigError
|
|
423
|
+
? reason
|
|
424
|
+
: 'the broker could not mint a credential; see the broker log';
|
|
425
|
+
return deny(context, CODES.PROVIDER_ERROR, reason, message, hostDetail);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
;
|
|
429
|
+
return async function handle(message) {
|
|
430
|
+
const proxy = parseProxy(message['proxy']);
|
|
431
|
+
if (proxy === false) {
|
|
432
|
+
return deny({ host: null, repo: null, session: null, helper_pid: null }, CODES.BAD_REQUEST, 'invalid proxy', 'invalid proxy');
|
|
433
|
+
}
|
|
434
|
+
return withProxy(proxy, () => handleRequest(message, proxy));
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Start the broker.
|
|
439
|
+
*
|
|
440
|
+
* @param options - server options.
|
|
441
|
+
* @returns the running broker.
|
|
442
|
+
*/
|
|
443
|
+
export async function startBroker(options) {
|
|
444
|
+
const { config, audit, providers, log = () => { } } = options;
|
|
445
|
+
prepareSocketPath(config.socketPath);
|
|
446
|
+
const handle = createRequestHandler({ config, audit, providers, log });
|
|
447
|
+
const server = net.createServer({ allowHalfOpen: false }, (socket) => {
|
|
448
|
+
socket.setTimeout(REQUEST_TIMEOUT_MS);
|
|
449
|
+
const reader = createLineReader({
|
|
450
|
+
onLine: (line) => {
|
|
451
|
+
const message = parseMessage(line);
|
|
452
|
+
if (message?.['op'] === 'action' && typeof message['ghAction'] === 'string') {
|
|
453
|
+
try {
|
|
454
|
+
socket.setTimeout(getGhAction(message['ghAction']).timeoutSeconds * 1000 + 30_000);
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
// The action branch returns the named refusal.
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const respond = (response) => {
|
|
461
|
+
if (!socket.destroyed)
|
|
462
|
+
socket.write(encodeMessage(response));
|
|
463
|
+
};
|
|
464
|
+
if (!message) {
|
|
465
|
+
respond({ ok: false, code: CODES.BAD_REQUEST, reason: 'malformed request' });
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
handle(message).then(respond, (error) => {
|
|
469
|
+
respond({ ok: false, code: CODES.PROVIDER_ERROR, reason: 'internal broker error' });
|
|
470
|
+
log(`internal error: ${String(error.message ?? error)}`);
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
onOverflow: () => {
|
|
474
|
+
log(`request exceeded ${MAX_MESSAGE_BYTES} bytes`);
|
|
475
|
+
if (!socket.writableEnded && !socket.destroyed) {
|
|
476
|
+
socket.end(encodeMessage({
|
|
477
|
+
ok: false,
|
|
478
|
+
code: CODES.BAD_REQUEST,
|
|
479
|
+
reason: `request exceeds ${MAX_MESSAGE_BYTES} bytes`,
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
socket.on('data', reader);
|
|
485
|
+
socket.on('timeout', () => socket.destroy());
|
|
486
|
+
socket.on('error', () => { });
|
|
487
|
+
});
|
|
488
|
+
await new Promise((resolve, reject) => {
|
|
489
|
+
const onError = (error) => reject(error);
|
|
490
|
+
server.once('error', onError);
|
|
491
|
+
server.listen(config.socketPath, () => {
|
|
492
|
+
server.off('error', onError);
|
|
493
|
+
resolve();
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
if (config.socketMode)
|
|
497
|
+
fs.chmodSync(config.socketPath, config.socketMode);
|
|
498
|
+
log(`listening on ${config.socketPath} (mode ${config.socketMode.toString(8)})`);
|
|
499
|
+
return {
|
|
500
|
+
socketPath: config.socketPath,
|
|
501
|
+
async close() {
|
|
502
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
503
|
+
try {
|
|
504
|
+
fs.unlinkSync(config.socketPath);
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// Already gone: fine.
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
//# sourceMappingURL=broker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker.js","sourceRoot":"","sources":["../src/broker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `authorize` — get a user token for this app, once, from whoever runs it.
|
|
3
|
+
*
|
|
4
|
+
* Run on the host, beside the configuration. It prints a code, waits for that person to type it at
|
|
5
|
+
* GitHub on whatever device has a browser, and stores the refresh token next to the configuration and
|
|
6
|
+
* its key — outside every mount, never in the repository. From then on the broker renews the token by
|
|
7
|
+
* itself and nothing here has to be run again.
|
|
8
|
+
*
|
|
9
|
+
* The token is what makes a pull request authored by a person rather than by the app. Only a person
|
|
10
|
+
* can be the author of one that reviewers will not skip, and only the device flow reaches a person
|
|
11
|
+
* whose host has no browser and no callback server.
|
|
12
|
+
*
|
|
13
|
+
* Exit codes: 0 stored, 1 refused.
|
|
14
|
+
*/
|
|
15
|
+
/** Usage text for `authorize`. */
|
|
16
|
+
export declare const AUTHORIZE_USAGE = "Usage: gcb authorize [options]\n\nGet a user token for this app, by authorizing it once from a browser somewhere else. Run it on the host,\nbeside the configuration. It stores a refresh token and prints where; nothing else needs running after\nthat, because the broker renews the token itself.\n\n --config <path> The configuration (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)\n --host <host> The host block to authorize for (default: github.com)\n --to <path> Where to write the refresh token (default: beside the configuration)\n --proxy <url> Use this proxy for this command\n --no-proxy Reach GitHub directly for this command\n -h, --help Show this help\n\nThe app needs its client id configured (the App ID will not do), and \"Enable Device Flow\" selected in\nits settings. GitHub returns a refresh token only while \"Expire user authorization tokens\" is selected,\nwhich is its default and what this wants.\n";
|
|
17
|
+
/**
|
|
18
|
+
* Where this command writes the refresh token.
|
|
19
|
+
*
|
|
20
|
+
* Beside the configuration, because the configuration is the path this process was *given*: the paths
|
|
21
|
+
* inside it belong to whichever process runs the broker, and for a sidecar those are container paths
|
|
22
|
+
* that do not exist out here. In the usual sidecar layout the configuration and the key sit in one
|
|
23
|
+
* directory, so this lands in the same file the broker reads; where they differ, the difference is
|
|
24
|
+
* printed rather than discovered later.
|
|
25
|
+
*
|
|
26
|
+
* @param configPath - the resolved configuration path.
|
|
27
|
+
* @returns the path to write the refresh token to.
|
|
28
|
+
*/
|
|
29
|
+
export declare function refreshTokenPath(configPath: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Where the broker will look for it, which is beside the key — from the key's own point of view.
|
|
32
|
+
*
|
|
33
|
+
* @param privateKeyPath - the path as the configuration records it.
|
|
34
|
+
* @returns the path the broker reads, or `undefined` when the key is not a file.
|
|
35
|
+
*/
|
|
36
|
+
export declare function brokerReadsPath(privateKeyPath: string | undefined): string | undefined;
|
|
37
|
+
export declare function runAuthorize(argv: readonly string[]): Promise<number>;
|
|
38
|
+
//# sourceMappingURL=authorize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/commands/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAUH,kCAAkC;AAClC,eAAO,MAAM,eAAe,w8BAgB3B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEtF;AAUD,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAqE3E"}
|