@aifabrix/builder 2.50.0 ā 2.51.0
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/.cursor/plans/157.1-security-quality-violations-by-type.csv +374 -0
- package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
- package/integration/hubspot-test/create-hubspot.js +1 -16
- package/lib/api/evidence-kinds.api.js +128 -0
- package/lib/api/pipeline.api.js +5 -1
- package/lib/api/resource-types.api.js +4 -4
- package/lib/api/roles.api.js +121 -0
- package/lib/api/types/evidence-kinds.types.js +42 -0
- package/lib/api/types/roles.types.js +36 -0
- package/lib/app/certification-verify-rows.js +1 -1
- package/lib/app/show.js +2 -2
- package/lib/certification/merge-certification-from-artifact.js +1 -1
- package/lib/channels/channel-artifact-generator.js +2 -1
- package/lib/cli/index.js +4 -0
- package/lib/cli/setup-app.js +22 -5
- package/lib/cli/setup-dev.js +23 -7
- package/lib/cli/setup-utility-help-after.js +12 -0
- package/lib/cli/setup-utility-validate.js +41 -2
- package/lib/commands/deploy-gate-failure-details.js +21 -0
- package/lib/commands/deploy-gate-orchestrator.js +64 -18
- package/lib/commands/dev-cli-handlers.js +6 -3
- package/lib/commands/dev-init.js +7 -109
- package/lib/commands/dev-refresh.js +197 -0
- package/lib/commands/evidence-kind.js +428 -0
- package/lib/commands/governance-command-action.js +4 -2
- package/lib/commands/repair-openapi-export.js +1 -1
- package/lib/commands/repair-openapi-sync.js +14 -6
- package/lib/commands/resource-type.js +101 -18
- package/lib/commands/role.js +452 -0
- package/lib/commands/setup.js +1 -1
- package/lib/commands/validate-json.js +230 -0
- package/lib/commands/verify-operations-step-plan.js +78 -40
- package/lib/core/audit-logger.js +1 -1
- package/lib/core/secrets-load.js +1 -3
- package/lib/datasource/capability/relate-operations.js +1 -1
- package/lib/generator/helpers.js +13 -5
- package/lib/generator/split-variables.js +9 -3
- package/lib/infrastructure/helpers-docker-check.js +6 -0
- package/lib/infrastructure/helpers.js +1 -1
- package/lib/programmatic/auth-config.js +3 -2
- package/lib/programmatic/bearer-auth.js +1 -1
- package/lib/programmatic/enterprise-mcp-client.js +18 -2
- package/lib/programmatic/manifest-section-registry.js +18 -1
- package/lib/programmatic/openapi-result-schemas.js +28 -11
- package/lib/programmatic/openapi-schema-descriptions-core.js +5 -2
- package/lib/programmatic/openapi-schema-helpers.js +16 -1
- package/lib/programmatic/openapi-spec.js +1 -1
- package/lib/programmatic/run-repository-emcp.js +11 -3
- package/lib/programmatic/run-template-workspace.js +14 -1
- package/lib/resolvers/evidence-kind-file.js +230 -0
- package/lib/resolvers/resource-type-file.js +36 -1
- package/lib/resolvers/role-file.js +227 -0
- package/lib/schema/application-schema.json +22 -2
- package/lib/schema/external-datasource.schema.json +85 -2
- package/lib/schema/external-system.schema.json +16 -2
- package/lib/schema/infra.parameter.yaml +25 -1
- package/lib/schema/type/credential-field-registry.json +19 -3
- package/lib/utils/api.js +8 -2
- package/lib/utils/app-register-config.js +4 -3
- package/lib/utils/cli-sync-options.js +59 -3
- package/lib/utils/cli-utils.js +11 -3
- package/lib/utils/dev-ca-install.js +5 -6
- package/lib/utils/dev-config.js +2 -2
- package/lib/utils/dev-list-sort.js +145 -0
- package/lib/utils/dev-platform-tls.js +0 -63
- package/lib/utils/docker-build.js +46 -27
- package/lib/utils/docker-daemon-tls-ca.js +1 -1
- package/lib/utils/docker-exec.js +91 -6
- package/lib/utils/env-copy.js +1 -3
- package/lib/utils/error-formatters/error-parser.js +0 -3
- package/lib/utils/external-env-template.js +1 -1
- package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
- package/lib/utils/health-check-request.js +2 -3
- package/lib/utils/help-builder.js +3 -0
- package/lib/utils/image-name.js +22 -4
- package/lib/utils/logger.js +37 -4
- package/lib/utils/manifest-source-emit.js +1 -1
- package/lib/utils/paths.js +32 -4
- package/lib/utils/pipeline-upload-auth-retry.js +4 -0
- package/lib/utils/postgres-platform-bootstrap.js +24 -17
- package/lib/utils/postgres-wipe.js +5 -2
- package/lib/utils/remote-dev-auth.js +39 -4
- package/lib/utils/resolve-docker-image-ref.js +36 -6
- package/lib/utils/token-manager.js +16 -0
- package/lib/utils/upload-sync-options.js +17 -3
- package/lib/validation/openapi-contract-surface-validator.js +4 -0
- package/lib/validation/validator.js +6 -5
- package/package.json +34 -12
- package/scripts/export-builder-api-evidence.js +2 -2
- package/scripts/lib/plan-actor.js +2 -2
- package/scripts/lib/plan-meta.js +1 -1
- package/scripts/lib/plan-parse.js +1 -1
- package/scripts/lib/plan-prevalidate-gates.js +12 -1
- package/scripts/lib/plan-run-report.js +13 -2
- package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
- package/templates/applications/dataplane/application.yaml +26 -4
- package/templates/applications/dataplane/env.template +28 -9
- package/templates/applications/dataplane/rbac.yaml +64 -2
- package/templates/applications/miso-controller/application.yaml +10 -1
- package/templates/applications/miso-controller/env.template +10 -7
- package/templates/applications/miso-controller/rbac.yaml +72 -0
- package/templates/channels/slack/integration/slack-system.json +60 -2
- package/templates/python/requirements.txt +1 -1
|
@@ -103,9 +103,16 @@ async function runRequiredDeployStep(label, runner, isOk, stepOptions = {}) {
|
|
|
103
103
|
const outcome = await runner();
|
|
104
104
|
if (!isOk(outcome)) {
|
|
105
105
|
const detail = formatDeployGateFailure(label, outcome);
|
|
106
|
-
|
|
106
|
+
if (stepOptions.quietDeployUi !== true) {
|
|
107
|
+
logger.log(chalk.red(`ā ${detail}`));
|
|
108
|
+
}
|
|
107
109
|
const err = new Error(`${label} failed: ${detail}`);
|
|
108
110
|
err.gateFailure = true;
|
|
111
|
+
err.gateDetail = detail;
|
|
112
|
+
err.gateLabel = label;
|
|
113
|
+
if (stepOptions.gatePillar) {
|
|
114
|
+
err.gatePillar = stepOptions.gatePillar;
|
|
115
|
+
}
|
|
109
116
|
throw err;
|
|
110
117
|
}
|
|
111
118
|
return outcome;
|
|
@@ -134,7 +141,9 @@ async function runDeployBaselineSteps(systemKey, options) {
|
|
|
134
141
|
...options,
|
|
135
142
|
uploadStatus: 'published',
|
|
136
143
|
minimal: true,
|
|
137
|
-
suppressSummary: true
|
|
144
|
+
suppressSummary: true,
|
|
145
|
+
suppressPipelineSpinner: true,
|
|
146
|
+
uploadContext: 'deploy-preflight'
|
|
138
147
|
});
|
|
139
148
|
stepOptions = mergeUploadAuthOptions({ ...options, uploadStatus: 'published' }, uploadCtx);
|
|
140
149
|
}
|
|
@@ -191,7 +200,7 @@ async function runOperationsGate(systemKey, options) {
|
|
|
191
200
|
syncMode: e2eSyncMode || stepOptions.syncMode
|
|
192
201
|
}),
|
|
193
202
|
(result) => result.success === true,
|
|
194
|
-
options
|
|
203
|
+
{ ...options, gatePillar: 'operations' }
|
|
195
204
|
);
|
|
196
205
|
|
|
197
206
|
return { pillar: 'operations', verdict: VERDICT.VERIFIED };
|
|
@@ -208,7 +217,7 @@ async function runTrustGate(systemKey, options) {
|
|
|
208
217
|
'Running trust gate',
|
|
209
218
|
() => runVerifyTrustForExternalSystem(systemKey, { ...options, noPublish: true }),
|
|
210
219
|
(outcome) => outcome.verdict === VERDICT.VERIFIED,
|
|
211
|
-
options
|
|
220
|
+
{ ...options, gatePillar: 'trust' }
|
|
212
221
|
);
|
|
213
222
|
return {
|
|
214
223
|
pillar: 'trust',
|
|
@@ -254,7 +263,7 @@ async function runGovernanceGate(systemKey, options) {
|
|
|
254
263
|
'Running governance gate',
|
|
255
264
|
runner,
|
|
256
265
|
(outcome) => outcome.verdict === VERDICT.VERIFIED,
|
|
257
|
-
options
|
|
266
|
+
{ ...options, gatePillar: 'governance' }
|
|
258
267
|
);
|
|
259
268
|
const scenarios = result.enforcementScenarios || {};
|
|
260
269
|
return {
|
|
@@ -274,18 +283,37 @@ async function runGovernanceGate(systemKey, options) {
|
|
|
274
283
|
*/
|
|
275
284
|
async function runDeployOptionalGates(systemKey, gates, options) {
|
|
276
285
|
const outcomes = [];
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
try {
|
|
287
|
+
if (gates.has('operations')) {
|
|
288
|
+
outcomes.push(await runOperationsGate(systemKey, options));
|
|
289
|
+
}
|
|
290
|
+
if (gates.has('trust')) {
|
|
291
|
+
outcomes.push(await runTrustGate(systemKey, options));
|
|
292
|
+
}
|
|
293
|
+
if (gates.has('governance')) {
|
|
294
|
+
outcomes.push(await runGovernanceGate(systemKey, options));
|
|
295
|
+
}
|
|
296
|
+
} catch (err) {
|
|
297
|
+
if (err && err.gateFailure === true) {
|
|
298
|
+
err.gatesSoFar = outcomes;
|
|
299
|
+
}
|
|
300
|
+
throw err;
|
|
285
301
|
}
|
|
286
302
|
return outcomes;
|
|
287
303
|
}
|
|
288
304
|
|
|
305
|
+
/**
|
|
306
|
+
* @param {string|undefined} label
|
|
307
|
+
* @returns {string}
|
|
308
|
+
*/
|
|
309
|
+
function inferGatePillarFromLabel(label) {
|
|
310
|
+
const text = String(label || '').toLowerCase();
|
|
311
|
+
if (text.includes('operations')) return 'operations';
|
|
312
|
+
if (text.includes('trust')) return 'trust';
|
|
313
|
+
if (text.includes('governance')) return 'governance';
|
|
314
|
+
return 'operations';
|
|
315
|
+
}
|
|
316
|
+
|
|
289
317
|
/**
|
|
290
318
|
* Baseline validate + test + test-integration, then optional --gate steps before controller deploy.
|
|
291
319
|
* @async
|
|
@@ -304,11 +332,29 @@ async function runDeployGateOrchestrator(systemKey, options = {}) {
|
|
|
304
332
|
if (gates.size === 0) {
|
|
305
333
|
return { baseline, gates: [] };
|
|
306
334
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
335
|
+
try {
|
|
336
|
+
const gateOutcomes = await runDeployOptionalGates(systemKey, gates, {
|
|
337
|
+
...options,
|
|
338
|
+
quietDeployUi: quiet
|
|
339
|
+
});
|
|
340
|
+
return { baseline, gates: gateOutcomes };
|
|
341
|
+
} catch (err) {
|
|
342
|
+
if (err && err.gateFailure === true && quiet) {
|
|
343
|
+
const { logDeployGateAbortCompact } = require('../utils/external-system-readiness-compact-blocks');
|
|
344
|
+
const failedPillar = err.gatePillar || inferGatePillarFromLabel(err.gateLabel);
|
|
345
|
+
logDeployGateAbortCompact({
|
|
346
|
+
systemKey,
|
|
347
|
+
environment: options.environment || options.env || 'dev',
|
|
348
|
+
dataplaneUrl: options.dataplaneUrl || options.knownDataplaneUrl,
|
|
349
|
+
baseline,
|
|
350
|
+
gatesSoFar: err.gatesSoFar || [],
|
|
351
|
+
failedPillar,
|
|
352
|
+
failedDetail: err.gateDetail || err.message
|
|
353
|
+
});
|
|
354
|
+
err.compactGateAbortLogged = true;
|
|
355
|
+
}
|
|
356
|
+
throw err;
|
|
357
|
+
}
|
|
312
358
|
}
|
|
313
359
|
|
|
314
360
|
module.exports = {
|
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
} = require('../utils/dev-user-groups');
|
|
19
19
|
const { handleProvisionDevUser, handleRemoveDevUser } = require('./dev-host-ops');
|
|
20
20
|
const { logDevPinShareInstructions } = require('../utils/dev-pin-share-instructions');
|
|
21
|
+
const { sortDevUsers } = require('../utils/dev-list-sort');
|
|
21
22
|
|
|
22
23
|
const REMOTE_NOT_CONFIGURED_MSG = 'Remote server is not configured. Set remote-server and run "aifabrix dev init" first.';
|
|
23
24
|
|
|
@@ -52,10 +53,12 @@ function formatCertUntil(iso) {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
* Handle dev list ā list developer users (remote only). Table format
|
|
56
|
+
* Handle dev list ā list developer users (remote only). Table format; default sort by name.
|
|
57
|
+
* @param {Object} [options] - Commander options
|
|
58
|
+
* @param {string} [options.sort] - Sort field/direction (id|name|email|until; prefix - for descending)
|
|
56
59
|
* @returns {Promise<void>}
|
|
57
60
|
*/
|
|
58
|
-
async function handleDevList() {
|
|
61
|
+
async function handleDevList(options = {}) {
|
|
59
62
|
const auth = await getRemoteDevAuth();
|
|
60
63
|
if (!auth) {
|
|
61
64
|
logger.log(chalk.yellow(REMOTE_NOT_CONFIGURED_MSG));
|
|
@@ -69,7 +72,7 @@ async function handleDevList() {
|
|
|
69
72
|
logger.log(chalk.bold('\nš Developers:\n'));
|
|
70
73
|
logger.log(chalk.gray('ID'.padEnd(ID_WIDTH) + 'Name'.padEnd(NAME_WIDTH) + 'Email'.padEnd(EMAIL_WIDTH) + 'Cert'.padEnd(CERT_WIDTH) + 'Until'.padEnd(UNTIL_WIDTH) + 'Groups'));
|
|
71
74
|
logger.log(chalk.gray('-'.repeat(TABLE_SEPARATOR_LENGTH)));
|
|
72
|
-
const sorted =
|
|
75
|
+
const sorted = sortDevUsers(users, options.sort);
|
|
73
76
|
sorted.forEach(u => {
|
|
74
77
|
const certLabel = u.certificateIssued ? 'yes' : 'no cert';
|
|
75
78
|
const untilRaw = u.certificateIssued && u.certificateValidNotAfter ? u.certificateValidNotAfter : null;
|
package/lib/commands/dev-init.js
CHANGED
|
@@ -14,10 +14,6 @@ const { getConfigDirForPaths } = require('../utils/paths');
|
|
|
14
14
|
const {
|
|
15
15
|
generateCSR,
|
|
16
16
|
getCertDir,
|
|
17
|
-
readClientCertPem,
|
|
18
|
-
readClientKeyPem,
|
|
19
|
-
readServerCaPem,
|
|
20
|
-
getCertValidNotAfter,
|
|
21
17
|
normalizePemNewlines,
|
|
22
18
|
mergeCaPemBlocks
|
|
23
19
|
} = require('../utils/dev-cert-helper');
|
|
@@ -33,9 +29,8 @@ const {
|
|
|
33
29
|
isLinuxCaSudoRequiredError
|
|
34
30
|
} = require('../utils/dev-ca-install');
|
|
35
31
|
const { runOptionalHostsSetup } = require('../utils/dev-hosts-helper');
|
|
36
|
-
const { mergeDevSshConfigAfterInit
|
|
32
|
+
const { mergeDevSshConfigAfterInit } = require('../utils/dev-init-ssh-merge');
|
|
37
33
|
const { resolveInitOptions } = require('../utils/dev-init-resolve');
|
|
38
|
-
const { displayDevConfig } = require('./dev-show-display');
|
|
39
34
|
const {
|
|
40
35
|
isHealthHttpServerError,
|
|
41
36
|
formatEnsureServerTrustedFailure
|
|
@@ -383,106 +378,9 @@ async function runDevInit(options) {
|
|
|
383
378
|
logOnboardingFinished(sshInfo);
|
|
384
379
|
}
|
|
385
380
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
* @returns {boolean}
|
|
393
|
-
*/
|
|
394
|
-
function shouldRefreshDevCert(certDir) {
|
|
395
|
-
const validNotAfter = getCertValidNotAfter(certDir);
|
|
396
|
-
if (!validNotAfter) return true;
|
|
397
|
-
const now = Date.now();
|
|
398
|
-
const threshold = now + CERT_REFRESH_DAYS * 24 * 60 * 60 * 1000;
|
|
399
|
-
return validNotAfter.getTime() < threshold;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Refresh developer certificate: create PIN (with current cert), issue new cert, save and apply settings.
|
|
404
|
-
* @param {{ serverUrl: string, clientCertPem: string, serverCaPem?: string|null }} auth - Current auth from getRemoteDevAuth
|
|
405
|
-
* @returns {Promise<void>}
|
|
406
|
-
*/
|
|
407
|
-
async function runCertificateRefresh(auth) {
|
|
408
|
-
const devId = await config.getDeveloperId();
|
|
409
|
-
if (!devId) throw new Error('developer-id not set in config.');
|
|
410
|
-
const configDir = getConfigDirForPaths();
|
|
411
|
-
const serverCaPem = auth.serverCaPem || undefined;
|
|
412
|
-
logger.log(chalk.blue('\nš Refreshing certificate (create PIN + issue-cert)...\n'));
|
|
413
|
-
const pinRes = await devApi.createPin(auth.serverUrl, auth.clientCertPem, devId, serverCaPem);
|
|
414
|
-
const pin = pinRes.pin;
|
|
415
|
-
if (!pin || typeof pin !== 'string') throw new Error('Server did not return a PIN.');
|
|
416
|
-
logger.log(chalk.gray(' Generating new certificate request...'));
|
|
417
|
-
const { csrPem, keyPem } = generateCSR(devId);
|
|
418
|
-
logger.log(chalk.gray(' Requesting new certificate (issue-cert)...'));
|
|
419
|
-
const issueResponse = await requestCertificate(auth.serverUrl, devId, pin, csrPem, serverCaPem);
|
|
420
|
-
const caPem = mergeCaPemBlocks(serverCaPem, issueResponse.caCertificate, issueResponse.ca);
|
|
421
|
-
await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
|
|
422
|
-
await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
|
|
423
|
-
await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
|
|
424
|
-
settings: issueResponse.settings,
|
|
425
|
-
remoteServerUrl: auth.serverUrl,
|
|
426
|
-
warn: (msg) => logger.log(chalk.yellow(` ā ${msg}`))
|
|
427
|
-
});
|
|
428
|
-
logger.log(formatSuccessLine('Certificate refreshed and config updated from server.\n'));
|
|
429
|
-
logDevSshConnectHint(await mergeDevSshConfigAfterInit(auth.serverUrl, devId));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* GET /api/dev/settings and merge into local config (used by dev refresh when cert is still valid).
|
|
434
|
-
* @param {{ serverUrl: string, serverCaPem?: string|null }} auth
|
|
435
|
-
* @param {string} clientCertPem
|
|
436
|
-
* @param {string|null|undefined} clientKeyPem
|
|
437
|
-
* @param {string} devId
|
|
438
|
-
* @returns {Promise<void>}
|
|
439
|
-
*/
|
|
440
|
-
async function fetchAndMergeRemoteSettings(auth, clientCertPem, clientKeyPem, devId) {
|
|
441
|
-
logger.log(chalk.blue('\nš Fetching settings from Builder Server...\n'));
|
|
442
|
-
const settings = await devApi.getSettings(
|
|
443
|
-
auth.serverUrl,
|
|
444
|
-
clientCertPem,
|
|
445
|
-
clientKeyPem || undefined,
|
|
446
|
-
auth.serverCaPem || undefined
|
|
447
|
-
);
|
|
448
|
-
await config.mergeRemoteSettings(settings);
|
|
449
|
-
const existingCa = readServerCaPem(getCertDir(getConfigDirForPaths(), devId));
|
|
450
|
-
await augmentAndSaveCaPemForRemoteDocker(getConfigDirForPaths(), devId, existingCa, {
|
|
451
|
-
settings,
|
|
452
|
-
remoteServerUrl: auth.serverUrl,
|
|
453
|
-
warn: (msg) => logger.log(chalk.yellow(` ā ${msg}`))
|
|
454
|
-
});
|
|
455
|
-
logger.log(formatSuccessLine('Config updated from server.\n'));
|
|
456
|
-
await displayDevConfig(devId);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Fetch settings from Builder Server and merge into config (GET /api/dev/settings).
|
|
461
|
-
* If certificate expires within CERT_REFRESH_DAYS (or --cert), refresh cert first (create PIN + issue-cert).
|
|
462
|
-
* @param {Object} [options] - Commander options; options.cert = true forces cert refresh
|
|
463
|
-
* @returns {Promise<void>}
|
|
464
|
-
* @throws {Error} If remote server or certificate not configured, or getSettings fails
|
|
465
|
-
*/
|
|
466
|
-
async function runDevRefresh(options = {}) {
|
|
467
|
-
const { getRemoteDevAuth } = require('../utils/remote-dev-auth');
|
|
468
|
-
const auth = await getRemoteDevAuth();
|
|
469
|
-
if (!auth) {
|
|
470
|
-
throw new Error('Remote server is not configured. Set remote-server and run "aifabrix dev init" first.');
|
|
471
|
-
}
|
|
472
|
-
const devId = await config.getDeveloperId();
|
|
473
|
-
const certDir = getCertDir(getConfigDirForPaths(), devId);
|
|
474
|
-
const clientCertPem = readClientCertPem(certDir);
|
|
475
|
-
const clientKeyPem = readClientKeyPem(certDir);
|
|
476
|
-
if (!clientCertPem) {
|
|
477
|
-
throw new Error('Client certificate not found. Run "aifabrix dev init" first.');
|
|
478
|
-
}
|
|
479
|
-
if (Boolean(options.cert) || shouldRefreshDevCert(certDir)) {
|
|
480
|
-
await runCertificateRefresh(auth);
|
|
481
|
-
await displayDevConfig(devId);
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
await fetchAndMergeRemoteSettings(auth, clientCertPem, clientKeyPem, devId);
|
|
485
|
-
logDevSshConnectHint(await mergeDevSshConfigAfterInit(auth.serverUrl, devId));
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
module.exports = { runDevInit, runDevRefresh };
|
|
381
|
+
module.exports = {
|
|
382
|
+
runDevInit,
|
|
383
|
+
requestCertificate,
|
|
384
|
+
saveCertAndConfig,
|
|
385
|
+
applySettingsFromServer
|
|
386
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview aifabrix dev refresh ā sync remote settings and renew client cert when due
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const config = require('../core/config');
|
|
11
|
+
const { getConfigDirForPaths } = require('../utils/paths');
|
|
12
|
+
const {
|
|
13
|
+
generateCSR,
|
|
14
|
+
getCertDir,
|
|
15
|
+
readClientCertPem,
|
|
16
|
+
readClientKeyPem,
|
|
17
|
+
readServerCaPem,
|
|
18
|
+
getCertValidNotAfter,
|
|
19
|
+
mergeCaPemBlocks
|
|
20
|
+
} = require('../utils/dev-cert-helper');
|
|
21
|
+
const devApi = require('../api/dev.api');
|
|
22
|
+
const logger = require('../utils/logger');
|
|
23
|
+
const { mergeDevSshConfigAfterInit, logDevSshConnectHint } = require('../utils/dev-init-ssh-merge');
|
|
24
|
+
const { displayDevConfig } = require('./dev-show-display');
|
|
25
|
+
const { augmentAndSaveCaPemForRemoteDocker } = require('../utils/dev-ca-docker-augment');
|
|
26
|
+
const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
|
|
27
|
+
const {
|
|
28
|
+
requestCertificate,
|
|
29
|
+
saveCertAndConfig,
|
|
30
|
+
applySettingsFromServer
|
|
31
|
+
} = require('./dev-init');
|
|
32
|
+
|
|
33
|
+
/** Days before cert expiry at which we auto-refresh on dev refresh / auto-renew. */
|
|
34
|
+
const CERT_REFRESH_DAYS = 14;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* True if the cert expires within CERT_REFRESH_DAYS (or expiry cannot be read).
|
|
38
|
+
* Used by explicit `dev refresh` (also attempts when expiry is unknown).
|
|
39
|
+
* @param {string} certDir
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
function shouldRefreshDevCert(certDir) {
|
|
43
|
+
const validNotAfter = getCertValidNotAfter(certDir);
|
|
44
|
+
if (!validNotAfter) return true;
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const threshold = now + CERT_REFRESH_DAYS * 24 * 60 * 60 * 1000;
|
|
47
|
+
return validNotAfter.getTime() < threshold;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* True when cert is still valid but expires within CERT_REFRESH_DAYS.
|
|
52
|
+
* Used for automatic renew (never tries when already expired).
|
|
53
|
+
* @param {string} certDir
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
function isDevCertAutoRenewalDue(certDir) {
|
|
57
|
+
const validNotAfter = getCertValidNotAfter(certDir);
|
|
58
|
+
if (!validNotAfter) return false;
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
if (validNotAfter.getTime() <= now) return false;
|
|
61
|
+
const threshold = now + CERT_REFRESH_DAYS * 24 * 60 * 60 * 1000;
|
|
62
|
+
return validNotAfter.getTime() < threshold;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Refresh developer certificate: create PIN (with current cert), issue new cert, save and apply settings.
|
|
67
|
+
* @param {{ serverUrl: string, clientCertPem: string, serverCaPem?: string|null }} auth
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
async function runCertificateRefresh(auth) {
|
|
71
|
+
const devId = await config.getDeveloperId();
|
|
72
|
+
if (!devId) throw new Error('developer-id not set in config.');
|
|
73
|
+
const configDir = getConfigDirForPaths();
|
|
74
|
+
const serverCaPem = auth.serverCaPem || undefined;
|
|
75
|
+
logger.log(chalk.blue('\nš Refreshing certificate (create PIN + issue-cert)...\n'));
|
|
76
|
+
const pinRes = await devApi.createPin(auth.serverUrl, auth.clientCertPem, devId, serverCaPem);
|
|
77
|
+
const pin = pinRes.pin;
|
|
78
|
+
if (!pin || typeof pin !== 'string') throw new Error('Server did not return a PIN.');
|
|
79
|
+
logger.log(chalk.gray(' Generating new certificate request...'));
|
|
80
|
+
const { csrPem, keyPem } = generateCSR(devId);
|
|
81
|
+
logger.log(chalk.gray(' Requesting new certificate (issue-cert)...'));
|
|
82
|
+
const issueResponse = await requestCertificate(auth.serverUrl, devId, pin, csrPem, serverCaPem);
|
|
83
|
+
const caPem = mergeCaPemBlocks(serverCaPem, issueResponse.caCertificate, issueResponse.ca);
|
|
84
|
+
await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
|
|
85
|
+
await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
|
|
86
|
+
await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
|
|
87
|
+
settings: issueResponse.settings,
|
|
88
|
+
remoteServerUrl: auth.serverUrl,
|
|
89
|
+
warn: (msg) => logger.log(chalk.yellow(` ā ${msg}`))
|
|
90
|
+
});
|
|
91
|
+
logger.log(formatSuccessLine('Certificate refreshed and config updated from server.\n'));
|
|
92
|
+
logDevSshConnectHint(await mergeDevSshConfigAfterInit(auth.serverUrl, devId));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* GET /api/dev/settings and merge into local config (when cert is still valid).
|
|
97
|
+
* @param {{ serverUrl: string, serverCaPem?: string|null }} auth
|
|
98
|
+
* @param {string} clientCertPem
|
|
99
|
+
* @param {string|null|undefined} clientKeyPem
|
|
100
|
+
* @param {string} devId
|
|
101
|
+
* @returns {Promise<void>}
|
|
102
|
+
*/
|
|
103
|
+
async function fetchAndMergeRemoteSettings(auth, clientCertPem, clientKeyPem, devId) {
|
|
104
|
+
logger.log(chalk.blue('\nš Fetching settings from Builder Server...\n'));
|
|
105
|
+
const settings = await devApi.getSettings(
|
|
106
|
+
auth.serverUrl,
|
|
107
|
+
clientCertPem,
|
|
108
|
+
clientKeyPem || undefined,
|
|
109
|
+
auth.serverCaPem || undefined
|
|
110
|
+
);
|
|
111
|
+
await config.mergeRemoteSettings(settings);
|
|
112
|
+
const existingCa = readServerCaPem(getCertDir(getConfigDirForPaths(), devId));
|
|
113
|
+
await augmentAndSaveCaPemForRemoteDocker(getConfigDirForPaths(), devId, existingCa, {
|
|
114
|
+
settings,
|
|
115
|
+
remoteServerUrl: auth.serverUrl,
|
|
116
|
+
warn: (msg) => logger.log(chalk.yellow(` ā ${msg}`))
|
|
117
|
+
});
|
|
118
|
+
logger.log(formatSuccessLine('Config updated from server.\n'));
|
|
119
|
+
await displayDevConfig(devId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Fetch settings from Builder Server and merge into config.
|
|
124
|
+
* If certificate expires within CERT_REFRESH_DAYS (or --cert), refresh cert first.
|
|
125
|
+
* @param {Object} [options] - Commander options; options.cert = true forces cert refresh
|
|
126
|
+
* @returns {Promise<void>}
|
|
127
|
+
*/
|
|
128
|
+
async function runDevRefresh(options = {}) {
|
|
129
|
+
const { readRemoteDevAuth } = require('../utils/remote-dev-auth');
|
|
130
|
+
const auth = await readRemoteDevAuth();
|
|
131
|
+
if (!auth) {
|
|
132
|
+
throw new Error('Remote server is not configured. Set remote-server and run "aifabrix dev init" first.');
|
|
133
|
+
}
|
|
134
|
+
const devId = await config.getDeveloperId();
|
|
135
|
+
const certDir = getCertDir(getConfigDirForPaths(), devId);
|
|
136
|
+
const clientCertPem = readClientCertPem(certDir);
|
|
137
|
+
const clientKeyPem = readClientKeyPem(certDir);
|
|
138
|
+
if (!clientCertPem) {
|
|
139
|
+
throw new Error('Client certificate not found. Run "aifabrix dev init" first.');
|
|
140
|
+
}
|
|
141
|
+
if (Boolean(options.cert) || shouldRefreshDevCert(certDir)) {
|
|
142
|
+
await runCertificateRefresh(auth);
|
|
143
|
+
await displayDevConfig(devId);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await fetchAndMergeRemoteSettings(auth, clientCertPem, clientKeyPem, devId);
|
|
147
|
+
logDevSshConnectHint(await mergeDevSshConfigAfterInit(auth.serverUrl, devId));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Automatically renew the developer client certificate when still valid but due within
|
|
152
|
+
* CERT_REFRESH_DAYS. Safe for any cert-using command: no-op when not due / offline.
|
|
153
|
+
* Uses readRemoteDevAuth (not getRemoteDevAuth) to avoid recursion with auto-renew.
|
|
154
|
+
* Does not display config (caller shows status afterward).
|
|
155
|
+
*
|
|
156
|
+
* @param {{ quiet?: boolean }} [opts] - quiet=true suppresses failure warnings
|
|
157
|
+
* @returns {Promise<{ refreshed: boolean, reason?: string, error?: Error }>}
|
|
158
|
+
*/
|
|
159
|
+
async function ensureDevCertFreshIfDue(opts = {}) {
|
|
160
|
+
const quiet = opts.quiet === true;
|
|
161
|
+
const { readRemoteDevAuth } = require('../utils/remote-dev-auth');
|
|
162
|
+
const auth = await readRemoteDevAuth();
|
|
163
|
+
if (!auth) {
|
|
164
|
+
return { refreshed: false, reason: 'no-remote-auth' };
|
|
165
|
+
}
|
|
166
|
+
const devId = await config.getDeveloperId();
|
|
167
|
+
if (!devId) {
|
|
168
|
+
return { refreshed: false, reason: 'no-developer-id' };
|
|
169
|
+
}
|
|
170
|
+
const certDir = getCertDir(getConfigDirForPaths(), devId);
|
|
171
|
+
if (!readClientCertPem(certDir)) {
|
|
172
|
+
return { refreshed: false, reason: 'no-cert' };
|
|
173
|
+
}
|
|
174
|
+
if (!isDevCertAutoRenewalDue(certDir)) {
|
|
175
|
+
return { refreshed: false, reason: 'not-due' };
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
await runCertificateRefresh(auth);
|
|
179
|
+
return { refreshed: true };
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (!quiet) {
|
|
182
|
+
logger.warn(
|
|
183
|
+
chalk.yellow(`Could not auto-refresh developer certificate: ${error.message}`)
|
|
184
|
+
);
|
|
185
|
+
logger.warn(chalk.gray(' Run: aifabrix dev refresh --cert'));
|
|
186
|
+
}
|
|
187
|
+
return { refreshed: false, reason: 'refresh-failed', error };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
runDevRefresh,
|
|
193
|
+
ensureDevCertFreshIfDue,
|
|
194
|
+
isDevCertAutoRenewalDue,
|
|
195
|
+
shouldRefreshDevCert,
|
|
196
|
+
CERT_REFRESH_DAYS
|
|
197
|
+
};
|