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/config.js
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Broker configuration: loaded from a YAML file that lives on the host, outside every
|
|
3
|
+
* mount the agent container can see, and validated eagerly so a broken or over-broad
|
|
4
|
+
* configuration stops the daemon at startup instead of silently denying (or silently
|
|
5
|
+
* allowing) at push time.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import path, { posix } from 'node:path';
|
|
9
|
+
import { parseDocument } from 'yaml';
|
|
10
|
+
import { parseAllowEntry } from './policy.js';
|
|
11
|
+
/** The file every command looks for when no configuration path is given. */
|
|
12
|
+
export const DEFAULT_CONFIG_FILE = 'broker.config.yml';
|
|
13
|
+
/**
|
|
14
|
+
* Where the configuration lives when nothing says otherwise.
|
|
15
|
+
*
|
|
16
|
+
* This convention is what makes the commands usable without repeating paths: run them from the
|
|
17
|
+
* deployment directory and `./broker.config.yml` is found, or export `GIT_BROKER_CONFIG` once.
|
|
18
|
+
* An explicit `--config` always wins.
|
|
19
|
+
*
|
|
20
|
+
* @param explicit - the `--config` argument, if any.
|
|
21
|
+
* @param env - the environment to read.
|
|
22
|
+
* @returns an absolute path.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveConfigPath(explicit, env = process.env) {
|
|
25
|
+
return path.resolve(explicit ?? env['GIT_BROKER_CONFIG'] ?? DEFAULT_CONFIG_FILE);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The deployment directory implied by a configuration path.
|
|
29
|
+
*
|
|
30
|
+
* The key, the configuration and the log directory are siblings, so the file's own location says
|
|
31
|
+
* where the deployment is.
|
|
32
|
+
*
|
|
33
|
+
* @param configPath - the configuration path.
|
|
34
|
+
* @returns the absolute directory.
|
|
35
|
+
*/
|
|
36
|
+
export function deploymentDir(configPath) {
|
|
37
|
+
return path.dirname(path.resolve(configPath));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Fixed paths inside the sidecar.
|
|
41
|
+
*
|
|
42
|
+
* A contract with the compose file `compose` prints: it and `init --mode sidecar` both read these
|
|
43
|
+
* from here, because a mismatch is a broker that starts and then cannot find its own key.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* The file name the authorized person's refresh token is kept under.
|
|
47
|
+
*
|
|
48
|
+
* It sits beside the private key, because the broker reads the key and can find the token without being
|
|
49
|
+
* told anything else — one rule, used by the writer, the reader and the mount, so they cannot drift.
|
|
50
|
+
*/
|
|
51
|
+
export const USER_TOKEN_FILE = 'user.refresh';
|
|
52
|
+
/**
|
|
53
|
+
* Where the authorized person's refresh token is, given the key it sits beside.
|
|
54
|
+
*
|
|
55
|
+
* posix, not path: these are paths inside the deployment, and the process reading them need not be the
|
|
56
|
+
* platform they belong to.
|
|
57
|
+
*
|
|
58
|
+
* @param privateKeyPath - the key, as the process that reads it sees the path.
|
|
59
|
+
* @returns the refresh token path.
|
|
60
|
+
*/
|
|
61
|
+
export function userTokenPath(privateKeyPath) {
|
|
62
|
+
return posix.join(posix.dirname(privateKeyPath), USER_TOKEN_FILE);
|
|
63
|
+
}
|
|
64
|
+
export const SIDECAR = {
|
|
65
|
+
configPath: '/etc/git-cred-broker/broker.config.yml',
|
|
66
|
+
keyPath: '/etc/git-cred-broker/app.pem',
|
|
67
|
+
// Beside the key, because that is how the provider derives it without being told.
|
|
68
|
+
// Derived from the key rather than written again: the mount target and the reader's path at once.
|
|
69
|
+
userTokenPath: posix.join(posix.dirname('/etc/git-cred-broker/app.pem'), USER_TOKEN_FILE),
|
|
70
|
+
auditDir: '/var/log/git-cred-broker',
|
|
71
|
+
auditPath: '/var/log/git-cred-broker/audit.jsonl',
|
|
72
|
+
socketDir: '/run/git-broker',
|
|
73
|
+
socketPath: '/run/git-broker/broker.sock',
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Which deployment a configuration was written for.
|
|
77
|
+
*
|
|
78
|
+
* Derived from the paths rather than stored, because the paths are what decide whether the broker
|
|
79
|
+
* can find its key. This lives here, not with the CLI, so the daemon can use it to explain a
|
|
80
|
+
* mismatch instead of reporting a bare ENOENT about a path that only exists in the other layout.
|
|
81
|
+
*
|
|
82
|
+
* @param config - a validated configuration.
|
|
83
|
+
* @returns the deployment it suits.
|
|
84
|
+
*/
|
|
85
|
+
export function inferMode(config) {
|
|
86
|
+
const block = config.hosts['github.com'];
|
|
87
|
+
const keyPath = block?.provider === 'github-app' ? block.privateKeyPath : undefined;
|
|
88
|
+
return keyPath === SIDECAR.keyPath && config.socketPath === SIDECAR.socketPath ? 'sidecar' : 'host';
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Refuse to start when a person's token is named but not there.
|
|
92
|
+
*
|
|
93
|
+
* Same reasoning as the key: a path that does not resolve is a deployment mistake, and the process
|
|
94
|
+
* that runs the broker is the one whose paths count.
|
|
95
|
+
*
|
|
96
|
+
* @param config - the loaded configuration.
|
|
97
|
+
*/
|
|
98
|
+
export function assertUserTokensReadable(config) {
|
|
99
|
+
for (const [host, block] of Object.entries(config.hosts)) {
|
|
100
|
+
if (block.provider !== 'github-app')
|
|
101
|
+
continue;
|
|
102
|
+
for (const [owner, tokenPath] of Object.entries(block.userTokens ?? {})) {
|
|
103
|
+
if (existsSync(tokenPath))
|
|
104
|
+
continue;
|
|
105
|
+
throw new Error(`cannot read the token for ${host}/${owner}: ${tokenPath} does not exist.\n` +
|
|
106
|
+
` Create a fine-grained token with pull requests: read and write, contents: read and\n` +
|
|
107
|
+
` metadata: read, for the repositories the allowlist names, and put it there — or drop\n` +
|
|
108
|
+
` userTokens to keep opening pull requests as the app.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Check that each recorded private key is readable, before the caller tries to read it.
|
|
114
|
+
*
|
|
115
|
+
* Both the daemon and `diagnose` need this, because both read the key, and a bare
|
|
116
|
+
* `ENOENT ... /etc/git-cred-broker/app.pem` says nothing: that path exists only inside the sidecar,
|
|
117
|
+
* so the mistake is where the command was run, not that the key is missing. The layout is
|
|
118
|
+
* recoverable from the configuration itself, so name it and give the way out.
|
|
119
|
+
*
|
|
120
|
+
* @param config - the validated configuration.
|
|
121
|
+
* @throws {Error} when a key file is missing, with the remedy in the message.
|
|
122
|
+
*/
|
|
123
|
+
export function assertKeysReadable(config) {
|
|
124
|
+
const mode = inferMode(config);
|
|
125
|
+
for (const [host, block] of Object.entries(config.hosts)) {
|
|
126
|
+
if (block.provider !== 'github-app' || block.privateKeyPath === undefined)
|
|
127
|
+
continue;
|
|
128
|
+
if (existsSync(block.privateKeyPath))
|
|
129
|
+
continue;
|
|
130
|
+
const remedy = mode === 'sidecar'
|
|
131
|
+
? `${block.privateKeyPath} exists only inside the sidecar container, so this has to run there —\n` +
|
|
132
|
+
' or against a configuration that records host paths instead:\n' +
|
|
133
|
+
' gcb init --mode host --socket-path <the path the host process binds>'
|
|
134
|
+
: `${block.privateKeyPath} is the host's path. Install the key there (init does), or if this\n` +
|
|
135
|
+
' deployment is a sidecar, re-run init with --mode sidecar so the configuration records\n' +
|
|
136
|
+
' the paths the sidecar actually mounts.';
|
|
137
|
+
throw new Error(`cannot read the private key for ${host}: ${remedy}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Providers this build knows how to construct. */
|
|
141
|
+
export const KNOWN_PROVIDERS = ['github-app', 'static'];
|
|
142
|
+
/**
|
|
143
|
+
* Whether a value is a plain object.
|
|
144
|
+
*
|
|
145
|
+
* @param value - the value to test.
|
|
146
|
+
* @returns true for a non-null, non-array object.
|
|
147
|
+
*/
|
|
148
|
+
function isRecord(value) {
|
|
149
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Read an optional string field, recording a type error instead of coercing.
|
|
153
|
+
*
|
|
154
|
+
* @param record - the object to read from.
|
|
155
|
+
* @param key - the field name.
|
|
156
|
+
* @param errors - the error accumulator.
|
|
157
|
+
* @param at - the human-readable location, for messages.
|
|
158
|
+
* @returns the string, or undefined when absent or invalid.
|
|
159
|
+
*/
|
|
160
|
+
function stringField(record, key, errors, at) {
|
|
161
|
+
const value = record[key];
|
|
162
|
+
if (value === undefined || value === null)
|
|
163
|
+
return undefined;
|
|
164
|
+
if (typeof value !== 'string') {
|
|
165
|
+
errors.push(`${at}.${key} must be a string`);
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Read an optional numeric field.
|
|
172
|
+
*
|
|
173
|
+
* @param record - the object to read from.
|
|
174
|
+
* @param key - the field name.
|
|
175
|
+
* @param errors - the error accumulator.
|
|
176
|
+
* @param at - the human-readable location, for messages.
|
|
177
|
+
* @returns the number, or undefined when absent or invalid.
|
|
178
|
+
*/
|
|
179
|
+
function numberField(record, key, errors, at) {
|
|
180
|
+
const value = record[key];
|
|
181
|
+
if (value === undefined || value === null)
|
|
182
|
+
return undefined;
|
|
183
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
184
|
+
errors.push(`${at}.${key} must be a finite number`);
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Read an optional boolean field.
|
|
191
|
+
*
|
|
192
|
+
* @param record - the object to read from.
|
|
193
|
+
* @param key - the field name.
|
|
194
|
+
* @param errors - the error accumulator.
|
|
195
|
+
* @param at - the human-readable location, for messages.
|
|
196
|
+
* @returns the boolean, or undefined when absent or invalid.
|
|
197
|
+
*/
|
|
198
|
+
function booleanField(record, key, errors, at) {
|
|
199
|
+
const value = record[key];
|
|
200
|
+
if (value === undefined || value === null)
|
|
201
|
+
return undefined;
|
|
202
|
+
if (typeof value !== 'boolean') {
|
|
203
|
+
errors.push(`${at}.${key} must be a boolean`);
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Read an optional string-or-number field (the app ID may be written either way).
|
|
210
|
+
*
|
|
211
|
+
* @param record - the object to read from.
|
|
212
|
+
* @param key - the field name.
|
|
213
|
+
* @param errors - the error accumulator.
|
|
214
|
+
* @param at - the human-readable location, for messages.
|
|
215
|
+
* @returns the value, or undefined when absent or invalid.
|
|
216
|
+
*/
|
|
217
|
+
function stringOrNumberField(record, key, errors, at) {
|
|
218
|
+
const value = record[key];
|
|
219
|
+
if (value === undefined || value === null)
|
|
220
|
+
return undefined;
|
|
221
|
+
if (typeof value === 'string' || typeof value === 'number')
|
|
222
|
+
return value;
|
|
223
|
+
errors.push(`${at}.${key} must be a string or a number`);
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The same map with its keys lower-cased.
|
|
228
|
+
*
|
|
229
|
+
* These keys are owners, and owners are case-insensitive on GitHub while every comparison in this
|
|
230
|
+
* codebase is made against a lower-cased one. Normalising at the edge is what keeps a configuration that
|
|
231
|
+
* looks right from silently doing nothing.
|
|
232
|
+
*
|
|
233
|
+
* @param value - the map, as written, or nothing when the field is absent.
|
|
234
|
+
* @returns the map with lower-cased keys, or nothing.
|
|
235
|
+
*/
|
|
236
|
+
function lowerCasedKeys(value) {
|
|
237
|
+
if (value === undefined)
|
|
238
|
+
return undefined;
|
|
239
|
+
const out = {};
|
|
240
|
+
for (const [key, entry] of Object.entries(value))
|
|
241
|
+
out[key.toLowerCase()] = entry;
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Read an optional map of strings, for REST permission names.
|
|
246
|
+
*
|
|
247
|
+
* @param value - the raw value.
|
|
248
|
+
* @param errors - the error accumulator.
|
|
249
|
+
* @param at - the human-readable location, for messages.
|
|
250
|
+
* @returns the map, or undefined when absent or invalid.
|
|
251
|
+
*/
|
|
252
|
+
function stringMapField(value, errors, at, field = 'permissions') {
|
|
253
|
+
if (value === undefined || value === null)
|
|
254
|
+
return undefined;
|
|
255
|
+
if (!isRecord(value)) {
|
|
256
|
+
errors.push(`${at}.${field} must be an object of names to values`);
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
const out = {};
|
|
260
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
261
|
+
if (typeof entry !== 'string') {
|
|
262
|
+
errors.push(`${at}.${field}["${key}"] must be a string`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
out[key] = entry;
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Validate a parsed configuration object.
|
|
271
|
+
*
|
|
272
|
+
* @param raw - the parsed JSON.
|
|
273
|
+
* @param source - where it came from, for error messages.
|
|
274
|
+
* @returns the validated configuration with defaults applied.
|
|
275
|
+
* @throws {Error} when any field is missing or malformed.
|
|
276
|
+
*/
|
|
277
|
+
export function validateConfig(raw, source = '<config>') {
|
|
278
|
+
if (!isRecord(raw)) {
|
|
279
|
+
throw new Error(`invalid config (${source}): top level must be an object`);
|
|
280
|
+
}
|
|
281
|
+
const errors = [];
|
|
282
|
+
const socketPathRaw = raw['socketPath'];
|
|
283
|
+
if (typeof socketPathRaw !== 'string' || !socketPathRaw) {
|
|
284
|
+
errors.push('socketPath is required (absolute path of the broker unix socket)');
|
|
285
|
+
}
|
|
286
|
+
const auditRaw = raw['auditPath'];
|
|
287
|
+
if (auditRaw !== undefined && auditRaw !== null && typeof auditRaw !== 'string') {
|
|
288
|
+
errors.push('auditPath must be a string when present');
|
|
289
|
+
}
|
|
290
|
+
const socketModeRaw = raw['socketMode'];
|
|
291
|
+
if (socketModeRaw !== undefined && !Number.isInteger(socketModeRaw)) {
|
|
292
|
+
errors.push('socketMode must be an integer (octal literal, e.g. 432 for 0660)');
|
|
293
|
+
}
|
|
294
|
+
const skewRaw = raw['tokenCacheSkewSeconds'];
|
|
295
|
+
if (skewRaw !== undefined && (typeof skewRaw !== 'number' || !Number.isFinite(skewRaw))) {
|
|
296
|
+
errors.push('tokenCacheSkewSeconds must be a number');
|
|
297
|
+
}
|
|
298
|
+
const hostsRaw = raw['hosts'];
|
|
299
|
+
const hosts = {};
|
|
300
|
+
if (!isRecord(hostsRaw) || Object.keys(hostsRaw).length === 0) {
|
|
301
|
+
errors.push('hosts must be a non-empty object keyed by host as git reports it (e.g. "github.com")');
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
for (const [host, blockRaw] of Object.entries(hostsRaw)) {
|
|
305
|
+
const at = `hosts["${host}"]`;
|
|
306
|
+
if (!isRecord(blockRaw)) {
|
|
307
|
+
errors.push(`${at} must be an object`);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const provider = blockRaw['provider'];
|
|
311
|
+
if (provider !== 'static' && provider !== 'github-app') {
|
|
312
|
+
errors.push(`${at}.provider must be one of: ${KNOWN_PROVIDERS.join(', ')}`);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const allowRaw = blockRaw['allow'];
|
|
316
|
+
if (!Array.isArray(allowRaw) || allowRaw.length === 0) {
|
|
317
|
+
errors.push(`${at}.allow must be a non-empty array; the broker is default deny, so an empty allowlist is almost certainly a mistake`);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const allow = [];
|
|
321
|
+
for (const entry of allowRaw) {
|
|
322
|
+
if (typeof entry !== 'string') {
|
|
323
|
+
errors.push(`${at}.allow must contain only strings`);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
// A malformed entry would match nothing and therefore deny, which is safe but silent:
|
|
327
|
+
// the operator would believe they had allowed a repository. Reject it loudly instead.
|
|
328
|
+
if (!parseAllowEntry(entry)) {
|
|
329
|
+
errors.push(`${at}.allow entry ${JSON.stringify(entry)} is not a valid "owner/repo" pattern ` +
|
|
330
|
+
'(a whole segment may be "*", but a partial wildcard like "wid*" is not)');
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
allow.push(entry);
|
|
334
|
+
}
|
|
335
|
+
if (provider === 'static') {
|
|
336
|
+
const block = {
|
|
337
|
+
provider: 'static',
|
|
338
|
+
allow,
|
|
339
|
+
allowInsecureHttp: booleanField(blockRaw, 'allowInsecureHttp', errors, at),
|
|
340
|
+
username: stringField(blockRaw, 'username', errors, at),
|
|
341
|
+
password: stringField(blockRaw, 'password', errors, at),
|
|
342
|
+
passwordPath: stringField(blockRaw, 'passwordPath', errors, at),
|
|
343
|
+
passwordEnv: stringField(blockRaw, 'passwordEnv', errors, at),
|
|
344
|
+
expiresInSeconds: numberField(blockRaw, 'expiresInSeconds', errors, at),
|
|
345
|
+
};
|
|
346
|
+
hosts[host] = block;
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
const block = {
|
|
350
|
+
provider: 'github-app',
|
|
351
|
+
allow,
|
|
352
|
+
allowInsecureHttp: booleanField(blockRaw, 'allowInsecureHttp', errors, at),
|
|
353
|
+
clientId: stringField(blockRaw, 'clientId', errors, at),
|
|
354
|
+
appId: stringOrNumberField(blockRaw, 'appId', errors, at),
|
|
355
|
+
privateKeyPath: stringField(blockRaw, 'privateKeyPath', errors, at),
|
|
356
|
+
// Keys are owners, and every other owner in this codebase is compared lower-cased. A key spelled
|
|
357
|
+
// `An-Org` used to validate and then never match, which meant the pull request was created as the app
|
|
358
|
+
// while the operator believed a person would author it.
|
|
359
|
+
userTokens: lowerCasedKeys(stringMapField(blockRaw['userTokens'], errors, at, 'userTokens')),
|
|
360
|
+
privateKeyPem: stringField(blockRaw, 'privateKeyPem', errors, at),
|
|
361
|
+
permissions: stringMapField(blockRaw['permissions'], errors, at),
|
|
362
|
+
apiBaseUrl: stringField(blockRaw, 'apiBaseUrl', errors, at),
|
|
363
|
+
apiVersion: stringField(blockRaw, 'apiVersion', errors, at),
|
|
364
|
+
installationCacheSeconds: numberField(blockRaw, 'installationCacheSeconds', errors, at),
|
|
365
|
+
verifyAppPermissions: booleanField(blockRaw, 'verifyAppPermissions', errors, at),
|
|
366
|
+
};
|
|
367
|
+
hosts[host] = block;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (errors.length > 0) {
|
|
372
|
+
const error = new Error(`invalid config (${source}):\n - ${errors.join('\n - ')}`);
|
|
373
|
+
error.name = 'ConfigError';
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
socketPath: typeof socketPathRaw === 'string' ? socketPathRaw : '',
|
|
378
|
+
socketMode: typeof socketModeRaw === 'number' ? socketModeRaw : 0o660,
|
|
379
|
+
auditPath: typeof auditRaw === 'string' ? auditRaw : null,
|
|
380
|
+
tokenCacheSkewSeconds: typeof skewRaw === 'number' ? skewRaw : 300,
|
|
381
|
+
hosts,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Read and validate a configuration file.
|
|
386
|
+
*
|
|
387
|
+
* @param path - path to the YAML config.
|
|
388
|
+
* @returns the validated configuration.
|
|
389
|
+
*/
|
|
390
|
+
export function loadConfig(path) {
|
|
391
|
+
let text;
|
|
392
|
+
try {
|
|
393
|
+
text = readFileSync(path, 'utf8');
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
throw new Error(`cannot read config ${path}: ${error.message}`);
|
|
397
|
+
}
|
|
398
|
+
const document = parseDocument(text, {
|
|
399
|
+
schema: 'core',
|
|
400
|
+
uniqueKeys: true,
|
|
401
|
+
prettyErrors: true,
|
|
402
|
+
});
|
|
403
|
+
if (document.errors.length > 0) {
|
|
404
|
+
throw new Error(`config ${path} is not valid YAML: ${document.errors.map((error) => error.message).join('; ')}`);
|
|
405
|
+
}
|
|
406
|
+
return validateConfig(document.toJS({ maxAliasCount: 0 }), path);
|
|
407
|
+
}
|
|
408
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/lib/daemon.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon entry point logic: load configuration, build providers, serve the socket.
|
|
3
|
+
*
|
|
4
|
+
* Startup is fail-fast on purpose. A broker that cannot read its private key, or whose
|
|
5
|
+
* configuration is over-broad or malformed, must not come up and quietly serve nothing — an
|
|
6
|
+
* operator has to see the failure. `--check` performs the same validation and provider
|
|
7
|
+
* construction without binding the socket, which is what a deployment script should run
|
|
8
|
+
* before restarting the service.
|
|
9
|
+
*/
|
|
10
|
+
import type { Writable } from 'node:stream';
|
|
11
|
+
import type { ProviderDeps } from './providers/index.ts';
|
|
12
|
+
import type { BrokerConfig, Provider } from 'gcb-core';
|
|
13
|
+
/** The daemon's parsed command line. */
|
|
14
|
+
export interface DaemonArgs {
|
|
15
|
+
readonly configPath: string | undefined;
|
|
16
|
+
readonly checkOnly: boolean;
|
|
17
|
+
readonly help: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse the daemon's command line.
|
|
21
|
+
*
|
|
22
|
+
* @param argv - arguments after the script name.
|
|
23
|
+
* @returns the parsed arguments.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseArgs(argv: readonly string[]): DaemonArgs;
|
|
26
|
+
/**
|
|
27
|
+
* Build providers for every configured host.
|
|
28
|
+
*
|
|
29
|
+
* @param config - the validated configuration.
|
|
30
|
+
* @param deps - injectable dependencies, for tests.
|
|
31
|
+
* @returns host -> provider.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildProviders(config: BrokerConfig, deps?: ProviderDeps): Map<string, Provider>;
|
|
34
|
+
/**
|
|
35
|
+
* Run the daemon.
|
|
36
|
+
*
|
|
37
|
+
* @param argv - arguments after the script name.
|
|
38
|
+
* @param io - injectable streams.
|
|
39
|
+
* @returns the process exit code, once the daemon has shut down.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runDaemon(argv: readonly string[], io?: {
|
|
42
|
+
stderr?: Writable;
|
|
43
|
+
stdout?: Writable;
|
|
44
|
+
}): Promise<number>;
|
|
45
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAM5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAQvD,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAmB7D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE,YAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYnG;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,CAAA;CAAO,GAChD,OAAO,CAAC,MAAM,CAAC,CA6DjB"}
|
package/lib/daemon.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createAudit } from './audit.js';
|
|
2
|
+
import { startBroker } from './broker.js';
|
|
3
|
+
import { assertKeysReadable, assertUserTokensReadable, loadConfig, resolveConfigPath } from './config.js';
|
|
4
|
+
import { createProvider } from './providers/index.js';
|
|
5
|
+
/** Usage text. */
|
|
6
|
+
const USAGE = 'usage: gcb daemon [--config <path>] [--check]\n' +
|
|
7
|
+
' --config defaults to $GIT_BROKER_CONFIG, then ./broker.config.yml\n' +
|
|
8
|
+
' --check validates the configuration and the key, then exits without binding the socket';
|
|
9
|
+
/**
|
|
10
|
+
* Parse the daemon's command line.
|
|
11
|
+
*
|
|
12
|
+
* @param argv - arguments after the script name.
|
|
13
|
+
* @returns the parsed arguments.
|
|
14
|
+
*/
|
|
15
|
+
export function parseArgs(argv) {
|
|
16
|
+
let configPath;
|
|
17
|
+
let checkOnly = false;
|
|
18
|
+
let help = false;
|
|
19
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
20
|
+
const argument = argv[index];
|
|
21
|
+
if (argument === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
if (argument === '--config') {
|
|
24
|
+
configPath = argv[index + 1];
|
|
25
|
+
index += 1;
|
|
26
|
+
}
|
|
27
|
+
else if (argument.startsWith('--config=')) {
|
|
28
|
+
configPath = argument.slice('--config='.length);
|
|
29
|
+
}
|
|
30
|
+
else if (argument === '--check') {
|
|
31
|
+
checkOnly = true;
|
|
32
|
+
}
|
|
33
|
+
else if (argument === '--help' || argument === '-h') {
|
|
34
|
+
help = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { configPath, checkOnly, help };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build providers for every configured host.
|
|
41
|
+
*
|
|
42
|
+
* @param config - the validated configuration.
|
|
43
|
+
* @param deps - injectable dependencies, for tests.
|
|
44
|
+
* @returns host -> provider.
|
|
45
|
+
*/
|
|
46
|
+
export function buildProviders(config, deps = {}) {
|
|
47
|
+
const providers = new Map();
|
|
48
|
+
for (const [host, block] of Object.entries(config.hosts)) {
|
|
49
|
+
providers.set(host, createProvider(host, block, {
|
|
50
|
+
...deps,
|
|
51
|
+
skewSeconds: deps.skewSeconds ?? config.tokenCacheSkewSeconds,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
return providers;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Run the daemon.
|
|
58
|
+
*
|
|
59
|
+
* @param argv - arguments after the script name.
|
|
60
|
+
* @param io - injectable streams.
|
|
61
|
+
* @returns the process exit code, once the daemon has shut down.
|
|
62
|
+
*/
|
|
63
|
+
export async function runDaemon(argv, io = {}) {
|
|
64
|
+
const { stderr = process.stderr, stdout = process.stdout } = io;
|
|
65
|
+
const { configPath, checkOnly, help } = parseArgs(argv);
|
|
66
|
+
if (help) {
|
|
67
|
+
stdout.write(`${USAGE}\n`);
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
// Same convention as the management commands: --config, else GIT_BROKER_CONFIG, else
|
|
71
|
+
// ./broker.config.yml. A wrong pick is loud, because it must load and validate to start at all.
|
|
72
|
+
const resolved = resolveConfigPath(configPath);
|
|
73
|
+
let config;
|
|
74
|
+
let providers;
|
|
75
|
+
try {
|
|
76
|
+
config = loadConfig(resolved);
|
|
77
|
+
assertKeysReadable(config);
|
|
78
|
+
assertUserTokensReadable(config);
|
|
79
|
+
providers = buildProviders(config, {
|
|
80
|
+
log: (message) => stderr.write(`gcb daemon: ${message}\n`),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
stderr.write(`gcb daemon: ${error.message}\n`);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
const summary = [...providers.entries()]
|
|
88
|
+
.map(([host, provider]) => {
|
|
89
|
+
const count = config.hosts[host]?.allow.length ?? 0;
|
|
90
|
+
return `${host}(${provider.name}, ${count} allow ${count === 1 ? 'entry' : 'entries'})`;
|
|
91
|
+
})
|
|
92
|
+
.join(', ');
|
|
93
|
+
stderr.write(`gcb daemon: config ${resolved} ok: ${summary}\n`);
|
|
94
|
+
if (checkOnly)
|
|
95
|
+
return 0;
|
|
96
|
+
const audit = createAudit({ path: config.auditPath, stderr });
|
|
97
|
+
let broker;
|
|
98
|
+
try {
|
|
99
|
+
broker = await startBroker({
|
|
100
|
+
config,
|
|
101
|
+
audit,
|
|
102
|
+
providers,
|
|
103
|
+
log: (message) => stderr.write(`gcb daemon: ${message}\n`),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
stderr.write(`gcb daemon: cannot start: ${error.message}\n`);
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
await new Promise((resolve) => {
|
|
111
|
+
const shutdown = (signal) => {
|
|
112
|
+
stderr.write(`gcb daemon: ${signal} received, shutting down\n`);
|
|
113
|
+
broker
|
|
114
|
+
.close()
|
|
115
|
+
.then(() => audit.close())
|
|
116
|
+
.then(() => resolve());
|
|
117
|
+
};
|
|
118
|
+
process.once('SIGTERM', () => shutdown('SIGTERM'));
|
|
119
|
+
process.once('SIGINT', () => shutdown('SIGINT'));
|
|
120
|
+
});
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|