@kici-dev/orchestrator 0.6.1 → 0.7.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/dist/agent/dispatcher.d.ts +10 -1
- package/dist/agent/host-roster.d.ts +8 -3
- package/dist/agent/registry.d.ts +60 -5
- package/dist/app.d.ts +10 -5
- package/dist/cache/dep-cache.d.ts +14 -6
- package/dist/cache/global-eval-round-cache.d.ts +1 -1
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/source-cache.d.ts +92 -15
- package/dist/cancel/cancel-run.d.ts +20 -2
- package/dist/cancel/sweep-stuck-cancelling.d.ts +30 -0
- package/dist/cli/api-client.d.ts +10 -0
- package/dist/cli/commands/agent-service/logs.d.ts +1 -1
- package/dist/cli/commands/agent-service/restart.d.ts +1 -1
- package/dist/cli/commands/agent-service/start.d.ts +1 -1
- package/dist/cli/commands/agent-service/status.d.ts +1 -1
- package/dist/cli/commands/agent-service/stop.d.ts +1 -1
- package/dist/cli/commands/agent-service/uninstall.d.ts +1 -1
- package/dist/cli/commands/agent.d.ts +25 -0
- package/dist/cli/commands/config.d.ts +16 -0
- package/dist/cli/commands/db-backup.d.ts +46 -0
- package/dist/cli/commands/held-run.d.ts +9 -0
- package/dist/cli/commands/orchestrator-service/drain.d.ts +2 -19
- package/dist/cli/commands/orchestrator-service/index.d.ts +9 -1
- package/dist/cli/commands/orchestrator-service/install-env.d.ts +43 -2
- package/dist/cli/commands/orchestrator-service/logs.d.ts +1 -1
- package/dist/cli/commands/orchestrator-service/restart.d.ts +1 -1
- package/dist/cli/commands/orchestrator-service/start.d.ts +1 -1
- package/dist/cli/commands/orchestrator-service/status.d.ts +17 -1
- package/dist/cli/commands/orchestrator-service/stop.d.ts +1 -1
- package/dist/cli/commands/orchestrator-service/uninstall.d.ts +1 -1
- package/dist/cli/commands/orchestrator-service/upgrade.d.ts +2 -1
- package/dist/cli/commands/rotate.d.ts +1 -1
- package/dist/cli/commands/shared/env-file-mode.d.ts +16 -0
- package/dist/cli/commands/shared/upgrade-hooks.d.ts +32 -0
- package/dist/cli/commands/shared/upgrade-safety.d.ts +81 -0
- package/dist/cli/commands/shared/versioned-upgrade.d.ts +67 -12
- package/dist/cli/commands/source.d.ts +1 -0
- package/dist/cli/commands/token.d.ts +4 -0
- package/dist/cli/commands/trust-policy.d.ts +57 -1
- package/dist/cli/join.d.ts +1 -1
- package/dist/cli/service/backup-timer.d.ts +108 -0
- package/dist/cli/service/compose-path.d.ts +11 -0
- package/dist/cli/service/compose.d.ts +22 -0
- package/dist/cli/service/deploy-env.d.ts +10 -11
- package/dist/cli/service/env-assignment.d.ts +24 -0
- package/dist/cli/service/index.d.ts +2 -2
- package/dist/cli/service/instance/resolve.d.ts +71 -7
- package/dist/cli/service/instance/types.d.ts +15 -0
- package/dist/cli/service/launchd.d.ts +7 -0
- package/dist/cli/service/systemd.d.ts +1 -0
- package/dist/cli/service/types.d.ts +36 -0
- package/dist/cli/service/windows.d.ts +16 -0
- package/dist/cli/wizard/orchestrator-wizard.d.ts +12 -3
- package/dist/cli/wizard/prompts.d.ts +7 -0
- package/dist/cli.js +12525 -9298
- package/dist/cluster/cluster-settings-reader.d.ts +2 -2
- package/dist/cluster/coordinator.d.ts +11 -0
- package/dist/cluster/instance-heartbeat.d.ts +96 -0
- package/dist/cluster/join-client.d.ts +55 -5
- package/dist/cluster/join-handler.d.ts +1 -0
- package/dist/cluster/orphan-recovery.d.ts +16 -0
- package/dist/cluster/peer-registry.d.ts +10 -0
- package/dist/concurrency/dispatch-next-queued.d.ts +1 -13
- package/dist/concurrency/index.d.ts +1 -2
- package/dist/concurrency/queue-manager.d.ts +63 -18
- package/dist/config/env-overlay.d.ts +3 -2
- package/dist/config.d.ts +24 -0
- package/dist/contexts/release-queued-holds.d.ts +23 -1
- package/dist/dashboard/attestation-filters.d.ts +2 -0
- package/dist/dashboard/handler.d.ts +22 -2
- package/dist/db/deferred-indexes.d.ts +72 -0
- package/dist/db/migration-test-harness.d.ts +22 -0
- package/dist/db/migrations/131_execution_jobs_git_credentials.d.ts +30 -0
- package/dist/db/migrations/132_execution_runs_trigger_event.d.ts +23 -0
- package/dist/db/migrations/133_dispatch_queue_source_tar_digest.d.ts +23 -0
- package/dist/db/migrations/134_dispatch_queue_ownership.d.ts +4 -0
- package/dist/db/migrations/135_cluster_settings_retention_days.d.ts +4 -0
- package/dist/db/migrations/136_master_key_wrapped_tables_key_version.d.ts +4 -0
- package/dist/db/migrations/137_execution_runs_pr_head_context.d.ts +4 -0
- package/dist/db/migrations/138_execution_runs_subject_trigger_event.d.ts +4 -0
- package/dist/db/migrations/139_admin_token_subject.d.ts +4 -0
- package/dist/db/migrations/140_dispatch_queue_canonical_labels.d.ts +54 -0
- package/dist/db/migrations/141_event_log_shed.d.ts +15 -0
- package/dist/db/migrator.d.ts +47 -0
- package/dist/db/notify-listener.d.ts +60 -0
- package/dist/db/synthetic-job-ids.d.ts +24 -0
- package/dist/db/types.d.ts +169 -4
- package/dist/deployment/deployment-identity.d.ts +31 -1
- package/dist/diagnostics/checks/index.d.ts +2 -1
- package/dist/diagnostics/checks/retention.d.ts +24 -0
- package/dist/diagnostics/types.d.ts +7 -0
- package/dist/events/event-router.d.ts +1 -1
- package/dist/fault-injection-types.d.ts +53 -0
- package/dist/firecracker/host-network.d.ts +63 -8
- package/dist/git/credential-broker.d.ts +35 -2
- package/dist/git/inherited-secret.d.ts +7 -2
- package/dist/git/job-context.d.ts +7 -4
- package/dist/helpers/secure-write.d.ts +35 -0
- package/dist/index.js +196 -67
- package/dist/metrics/prometheus.d.ts +116 -1
- package/dist/oidc/db-signer.d.ts +27 -5
- package/dist/oidc/id-token-claims.d.ts +33 -2
- package/dist/oidc/oidc-mint-registration.d.ts +6 -0
- package/dist/oidc/orchestrator-mint.d.ts +8 -0
- package/dist/oidc/reconcile-signing-key.d.ts +37 -0
- package/dist/orchestrator-core.d.ts +9 -3
- package/dist/pipeline/decorating-secret-resolver.d.ts +1 -1
- package/dist/pipeline/dispatch-matched-workflow.d.ts +65 -14
- package/dist/pipeline/manual-schedule.d.ts +1 -0
- package/dist/pipeline/process-webhook.d.ts +20 -2
- package/dist/pipeline/route-or-dispatch-jobs.d.ts +7 -0
- package/dist/pipeline/run-event-context.d.ts +39 -0
- package/dist/policy/dashboard-write-policy-listener.d.ts +1 -1
- package/dist/policy/dashboard-write-policy.d.ts +47 -1
- package/dist/provenance/dispatch-context.d.ts +46 -0
- package/dist/provenance/verify-deferred-capture.d.ts +39 -0
- package/dist/providers/github/auth.d.ts +9 -0
- package/dist/providers/github/deliveries.d.ts +118 -0
- package/dist/providers/universal-git/config.d.ts +1 -1
- package/dist/queue/cleanup.d.ts +13 -0
- package/dist/queue/job-queue.d.ts +162 -14
- package/dist/queue/retention.d.ts +105 -0
- package/dist/reporting/execution-tracker.d.ts +87 -36
- package/dist/routes/admin-db.d.ts +5 -0
- package/dist/routes/admin-events.d.ts +13 -0
- package/dist/routes/admin-held-runs.d.ts +11 -1
- package/dist/routes/admin-org-settings.d.ts +9 -0
- package/dist/routes/admin-runs.d.ts +7 -4
- package/dist/routes/admin-sources.d.ts +15 -0
- package/dist/routes/admin-trust-policy.d.ts +11 -0
- package/dist/routes/admin.d.ts +25 -0
- package/dist/scaler/backend-factory.d.ts +16 -0
- package/dist/scaler/bare-metal-backend.d.ts +22 -4
- package/dist/scaler/config.d.ts +6 -3
- package/dist/scaler/container-backend.d.ts +102 -3
- package/dist/scaler/container-hostconfig.d.ts +60 -0
- package/dist/scaler/file-tail.d.ts +25 -2
- package/dist/scaler/firecracker-api.d.ts +9 -1
- package/dist/scaler/firecracker-backend.d.ts +135 -2
- package/dist/scaler/host-access.d.ts +59 -0
- package/dist/scaler/index.d.ts +1 -0
- package/dist/scaler/manager.d.ts +33 -0
- package/dist/scaler/scaler-state-store.d.ts +6 -0
- package/dist/scaler/types.d.ts +5 -7
- package/dist/secrets/config.d.ts +48 -0
- package/dist/secrets/dashboard-encryption-key.d.ts +19 -0
- package/dist/secrets/ephemeral-keys.d.ts +9 -3
- package/dist/secrets/index.d.ts +3 -1
- package/dist/secrets/job-secret-gate.d.ts +76 -0
- package/dist/secrets/master-key-rotation.d.ts +65 -0
- package/dist/secrets/secret-output-crypto.d.ts +17 -0
- package/dist/secrets/secret-resolver.d.ts +23 -6
- package/dist/secrets/source-credentials.d.ts +1 -1
- package/dist/secrets/token-manager.d.ts +4 -1
- package/dist/security/reduced-privilege-note.d.ts +5 -0
- package/dist/security/trust-policy-gate.d.ts +70 -6
- package/dist/security/trust-tier.d.ts +31 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +10661 -5282
- package/dist/sources/source-manager.d.ts +1 -1
- package/dist/stale-detector/stale-dispatch-candidates.d.ts +43 -0
- package/dist/standalone.js +9258 -4122
- package/dist/storage/loopback-guard.d.ts +53 -6
- package/dist/webhook/event-log.d.ts +14 -1
- package/dist/webhook/generic-sources-listener.d.ts +1 -1
- package/dist/webhook/ingest-admission.d.ts +24 -0
- package/dist/webhook/ingest-overflow-replayer.d.ts +78 -14
- package/dist/webhook/ingest-overflow-types.d.ts +13 -1
- package/dist/webhook/register-source-bundle.d.ts +23 -0
- package/dist/webhook/relay-reinject.d.ts +11 -12
- package/dist/webhook/shed-breadcrumb.d.ts +15 -0
- package/dist/ws/git-credential-relay.d.ts +41 -12
- package/dist/ws/platform-client.d.ts +28 -5
- package/installer-image-digests.json +3 -3
- package/package.json +4 -4
- package/sbom.spdx.json +53 -53
- package/dist/concurrency/group-tracker.d.ts +0 -51
- package/dist/scaler/nftables.d.ts +0 -111
package/dist/secrets/config.d.ts
CHANGED
|
@@ -24,6 +24,18 @@ export interface SecretStoreConfig {
|
|
|
24
24
|
* @throws If neither env var nor key file provides a valid key.
|
|
25
25
|
*/
|
|
26
26
|
export declare function loadMasterKey(envKey?: string, keyFilePath?: string): Buffer;
|
|
27
|
+
/**
|
|
28
|
+
* Load the master key MATERIAL — the raw configured string, before
|
|
29
|
+
* {@link deriveKey}. Same env-then-file precedence as {@link loadMasterKey}.
|
|
30
|
+
*
|
|
31
|
+
* Several master-key-wrapped stores take the key as a string and derive it
|
|
32
|
+
* themselves (`unwrapPrivateJwk`, `ephemeral-keys.decryptPrivateKey`). Reading
|
|
33
|
+
* `config.secretKey` at those call sites honours `KICI_SECRET_KEY` but silently
|
|
34
|
+
* ignores `KICI_SECRET_KEY_FILE`, so an operator who supplies the key by file
|
|
35
|
+
* gets no signing key and no dashboard-encryption key. Resolving the material
|
|
36
|
+
* here keeps one precedence rule for both the Buffer and string forms.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadMasterKeyMaterial(envKey?: string, keyFilePath?: string): string;
|
|
27
39
|
/**
|
|
28
40
|
* Load the old (previous) master encryption key for key rotation.
|
|
29
41
|
*
|
|
@@ -35,6 +47,42 @@ export declare function loadMasterKey(envKey?: string, keyFilePath?: string): Bu
|
|
|
35
47
|
* @returns 32-byte Buffer or undefined if no old key is configured.
|
|
36
48
|
*/
|
|
37
49
|
export declare function loadOldMasterKey(envKey?: string, keyFilePath?: string): Buffer | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Load the old master key MATERIAL — the raw configured string, before
|
|
52
|
+
* {@link deriveKey}. The string-form counterpart of {@link loadOldMasterKey},
|
|
53
|
+
* for the wrapped stores that take the key as a string.
|
|
54
|
+
*/
|
|
55
|
+
export declare function loadOldMasterKeyMaterial(envKey?: string, keyFilePath?: string): string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The master key in both the forms the wrapped stores need: the raw configured
|
|
58
|
+
* material (for stores that take a string and derive it themselves) and the
|
|
59
|
+
* derived AES key (for stores that hold a Buffer), each paired with the
|
|
60
|
+
* previous generation during a rotation grace window.
|
|
61
|
+
*
|
|
62
|
+
* Resolved once at boot so every master-key-wrapped store agrees on the same
|
|
63
|
+
* key, and so `KICI_SECRET_KEY_FILE` reaches all of them rather than only the
|
|
64
|
+
* two that happened to call {@link loadMasterKey}.
|
|
65
|
+
*/
|
|
66
|
+
export interface ResolvedMasterKeys {
|
|
67
|
+
/** Raw configured key material, before {@link deriveKey}. */
|
|
68
|
+
material: string;
|
|
69
|
+
/** The previous generation's raw material, when a rotation window is open. */
|
|
70
|
+
materialOld: string | undefined;
|
|
71
|
+
/** Derived 32-byte AES key. */
|
|
72
|
+
current: Buffer;
|
|
73
|
+
/** The previous generation's derived key, when a rotation window is open. */
|
|
74
|
+
old: Buffer | undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the master key (and, when configured, its predecessor) from the
|
|
78
|
+
* env-then-file precedence both loaders use. Returns null when no master key is
|
|
79
|
+
* configured at all — the secrets subsystem is off in that case.
|
|
80
|
+
*/
|
|
81
|
+
export declare function resolveMasterKeys(opts: {
|
|
82
|
+
secretKey?: string | undefined;
|
|
83
|
+
secretKeyFile?: string | undefined;
|
|
84
|
+
secretKeyFileOld?: string | undefined;
|
|
85
|
+
}): ResolvedMasterKeys | null;
|
|
38
86
|
/**
|
|
39
87
|
* Load the full secret store configuration.
|
|
40
88
|
*
|
|
@@ -19,10 +19,29 @@ export interface ReconcileDashboardEncryptionKeyDeps {
|
|
|
19
19
|
isLeader: () => boolean;
|
|
20
20
|
/** The orchestrator master key (`KICI_SECRET_KEY`), for db-custody wrapping. */
|
|
21
21
|
secretKey: string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* The previous master key (`KICI_SECRET_KEY_OLD`), during a rotation grace
|
|
24
|
+
* window. Lets a row still sealed under the old key unwrap, and drives the
|
|
25
|
+
* boot self-heal that re-seals it under the current key.
|
|
26
|
+
*/
|
|
27
|
+
oldSecretKey?: string | undefined;
|
|
22
28
|
/** Called once when a NEW kid is first activated (audit log, system actor). */
|
|
23
29
|
audit: (info: {
|
|
24
30
|
kid: string;
|
|
25
31
|
}) => Promise<void> | void;
|
|
32
|
+
/**
|
|
33
|
+
* Re-seal the active row when it opened only under the OLD master key, back
|
|
34
|
+
* under the current one. Optional: when unset the key still resolves but
|
|
35
|
+
* stays stranded. Best-effort — a failure is warned and never blocks the
|
|
36
|
+
* boot. The recovery path for a deployment stranded by a rotation that ran
|
|
37
|
+
* before `dashboard_encryption_keys` was part of the sweep.
|
|
38
|
+
*/
|
|
39
|
+
selfHeal?: (row: {
|
|
40
|
+
kid: string;
|
|
41
|
+
key_version: number;
|
|
42
|
+
}, privateKeyDer: Buffer) => Promise<void>;
|
|
43
|
+
/** Structured warn sink for the self-heal notice. */
|
|
44
|
+
logWarn?: (message: string, meta: Record<string, unknown>) => void;
|
|
26
45
|
}
|
|
27
46
|
/** The resolved active dashboard-encryption key + a per-request decrypt closure. */
|
|
28
47
|
export interface ResolvedDashboardEncryptionKey {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** AAD string used when encrypting/decrypting private keys with secret key. */
|
|
2
|
+
/** AAD binding a wrapped run private key to its purpose (AES-GCM authenticated data). */
|
|
3
|
+
export declare const PRIVATE_KEY_AAD = "ephemeral-private-key";
|
|
1
4
|
/**
|
|
2
5
|
* Generate an X25519 key pair for a run.
|
|
3
6
|
*
|
|
@@ -16,14 +19,17 @@ export declare function generateRunKeyPair(): {
|
|
|
16
19
|
*/
|
|
17
20
|
export declare function encryptPrivateKey(privateKey: Buffer, secretKey: string): string;
|
|
18
21
|
/**
|
|
19
|
-
* Decrypt a run's private key using the orchestrator secret key
|
|
22
|
+
* Decrypt a run's private key using the orchestrator secret key
|
|
23
|
+
* (KICI_SECRET_KEY), falling back to the old key during a rotation grace
|
|
24
|
+
* window. Same dual-key pattern as `PgSecretStore.decryptWithFallback`.
|
|
20
25
|
*
|
|
21
26
|
* @param encryptedData - Base64-encoded encrypted private key
|
|
22
27
|
* @param secretKey - Orchestrator secret key (hex or base64 string)
|
|
28
|
+
* @param oldSecretKey - Previous master key, during a rotation grace window
|
|
23
29
|
* @returns DER-encoded private key Buffer
|
|
24
|
-
* @throws If decryption fails
|
|
30
|
+
* @throws If decryption fails under both keys
|
|
25
31
|
*/
|
|
26
|
-
export declare function decryptPrivateKey(encryptedData: string, secretKey: string): Buffer;
|
|
32
|
+
export declare function decryptPrivateKey(encryptedData: string, secretKey: string, oldSecretKey?: string): Buffer;
|
|
27
33
|
/**
|
|
28
34
|
* Decrypt a secret output envelope produced by an agent.
|
|
29
35
|
*
|
package/dist/secrets/index.d.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { PgSecretStore } from './pg-secret-store.js';
|
|
8
8
|
export { AuditLogger } from './audit-logger.js';
|
|
9
|
-
export { loadMasterKey, loadOldMasterKey, loadSecretStoreConfig, type SecretStoreConfig, } from './config.js';
|
|
9
|
+
export { loadMasterKey, loadMasterKeyMaterial, loadOldMasterKey, loadOldMasterKeyMaterial, loadSecretStoreConfig, resolveMasterKeys, type ResolvedMasterKeys, type SecretStoreConfig, } from './config.js';
|
|
10
|
+
export { secretOutputAad, sealSecretOutput, unsealSecretOutput } from './secret-output-crypto.js';
|
|
11
|
+
export { rotateMasterKeyWrappedTables, selfHealStrandedDashboardKey, selfHealStrandedSigningKey, strandedKeyError, type MasterKeyRotationResult, type SweepResult, } from './master-key-rotation.js';
|
|
10
12
|
export { encrypt, decrypt, deriveKey, generateMasterKey, type EncryptedValue, } from '@kici-dev/shared';
|
|
11
13
|
export { VaultSecretStore, type VaultConfig } from './vault-secret-store.js';
|
|
12
14
|
export { SecretResolver, type SecretResolverDeps, type ResolvedSecretMeta, type ContextStoreLike, type BindingStoreLike, type SecretStoreLike, } from './secret-resolver.js';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one resolution path for a job-originated qualified secret reference.
|
|
3
|
+
*
|
|
4
|
+
* A workflow may name a secret as `<context>:<key>` in a git credential or a
|
|
5
|
+
* container-registry auth reference. Those references are resolved
|
|
6
|
+
* orchestrator-side, outside the job's bound `contexts:` list — which is what
|
|
7
|
+
* the published examples in `docs/user/patterns/git-credentials.md` and
|
|
8
|
+
* `docs/user/container-jobs.md` do, so a bound-only rule would refuse every
|
|
9
|
+
* documented use.
|
|
10
|
+
*
|
|
11
|
+
* Resolving outside the bindings is not the same as resolving with no
|
|
12
|
+
* authorization. Three independent checks stand between a job and the value,
|
|
13
|
+
* and this module is where all three run:
|
|
14
|
+
*
|
|
15
|
+
* 1. the reserved namespace is refused — a job may never name the
|
|
16
|
+
* orchestrator's own `__system__` org or a `__…__` scope;
|
|
17
|
+
* 2. an untrusted contributor gets nothing, mirroring the install-secrets
|
|
18
|
+
* strip;
|
|
19
|
+
* 3. the NAMED context's own protection rules run against this dispatch —
|
|
20
|
+
* the branch restriction, `minimumTrust`, concurrency, reviewers and wait
|
|
21
|
+
* timers the operator configured on `prod` apply to a `prod:` reference.
|
|
22
|
+
*
|
|
23
|
+
* The lock declaration is the fourth check and lives at the callers: the relay
|
|
24
|
+
* pins the wire ref to `execution_jobs.git_credentials`, and container-registry
|
|
25
|
+
* refs come from the lock by construction.
|
|
26
|
+
*
|
|
27
|
+
* Callers use this instead of `SecretResolverApi.resolveNamedInternal`, which
|
|
28
|
+
* is the system-scoped direct lookup and applies none of the above.
|
|
29
|
+
*/
|
|
30
|
+
import type { TrustTier } from '@kici-dev/engine';
|
|
31
|
+
import type { ContextStore } from '../contexts/context-store.js';
|
|
32
|
+
import { type JobDispatchContext } from '../contexts/protection/pipeline.js';
|
|
33
|
+
import type { SecretResolverApi } from './secret-resolver.js';
|
|
34
|
+
/**
|
|
35
|
+
* The org id the orchestrator stores its OWN credentials under — GitHub App
|
|
36
|
+
* private keys, webhook signing secrets, universal-git PATs. A job's `orgId` is
|
|
37
|
+
* its `customer_id`, so a job can never legitimately present this value.
|
|
38
|
+
*/
|
|
39
|
+
export declare const RESERVED_ORG_ID = "__system__";
|
|
40
|
+
/**
|
|
41
|
+
* Refuse a reference into the orchestrator's own reserved namespace.
|
|
42
|
+
*
|
|
43
|
+
* Today no job can reach one anyway — a job's `orgId` is its `customer_id`, so
|
|
44
|
+
* `(orgId, '__source__/x')` misses. That is a consequence of two unrelated
|
|
45
|
+
* constants agreeing, not a check: an org literally named `__system__`, a
|
|
46
|
+
* source-scoped secret copied into a customer org, or a helper resolving under
|
|
47
|
+
* a caller-supplied org would each remove it silently. Stating it makes it
|
|
48
|
+
* survive those changes.
|
|
49
|
+
*
|
|
50
|
+
* The message names the reserved namespace, never the secret: a job that
|
|
51
|
+
* guessed a scope learns only that the scope is reserved.
|
|
52
|
+
*/
|
|
53
|
+
export declare function assertResolvableJobScope(orgId: string, context: string): void;
|
|
54
|
+
export interface ResolveJobQualifiedSecretArgs {
|
|
55
|
+
resolver: SecretResolverApi;
|
|
56
|
+
contextStore: ContextStore;
|
|
57
|
+
orgId: string;
|
|
58
|
+
runId?: string;
|
|
59
|
+
jobId?: string;
|
|
60
|
+
/** The context half of the workflow's `<context>:<key>` reference. */
|
|
61
|
+
context: string;
|
|
62
|
+
/** The secret-name half. Never logged, never quoted back in an error. */
|
|
63
|
+
key: string;
|
|
64
|
+
/** The dispatch facts the protection rules evaluate against, from server truth. */
|
|
65
|
+
dispatchCtx: JobDispatchContext;
|
|
66
|
+
trustTier: TrustTier | undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve one `<context>:<key>` reference on behalf of a running job.
|
|
70
|
+
*
|
|
71
|
+
* Throws — with a reason naming the check that refused — rather than returning
|
|
72
|
+
* null, because every caller treats a miss as a hard failure and a nullable
|
|
73
|
+
* return is one `??` away from becoming a silent fallback.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveJobQualifiedSecret(args: ResolveJobQualifiedSecretArgs): Promise<string>;
|
|
76
|
+
//# sourceMappingURL=job-secret-gate.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Master-key rotation sweeps for the four wrapped stores `rotate-key` did not
|
|
3
|
+
* cover: the provenance signing key, the dashboard-encryption key, run
|
|
4
|
+
* ephemeral keys, and stored secret outputs.
|
|
5
|
+
*
|
|
6
|
+
* Each sweep runs in its own transaction with the same skip-and-count
|
|
7
|
+
* discipline as `BackendRegistry.rotateKey` — a row neither key opens is
|
|
8
|
+
* counted and left alone rather than failing the whole rotation, so the
|
|
9
|
+
* operator sees a non-zero `skipped` and can act on it. Each sweep is
|
|
10
|
+
* idempotent: re-running over rows already at the current key re-seals them at
|
|
11
|
+
* the next version, which is exactly what the no-old-key "periodic
|
|
12
|
+
* re-encryption" mode is for.
|
|
13
|
+
*/
|
|
14
|
+
import type { Kysely } from 'kysely';
|
|
15
|
+
import type { Database } from '../db/types.js';
|
|
16
|
+
import { wrapPrivateJwk } from '../oidc/db-signer.js';
|
|
17
|
+
import type { ResolvedMasterKeys } from './config.js';
|
|
18
|
+
/** One store's rotation outcome. Mirrors the three existing sweeps' shape. */
|
|
19
|
+
export interface SweepResult {
|
|
20
|
+
reEncrypted: number;
|
|
21
|
+
skipped: number;
|
|
22
|
+
}
|
|
23
|
+
/** Every wrapped store this module sweeps, keyed as the API reports them. */
|
|
24
|
+
export interface MasterKeyRotationResult {
|
|
25
|
+
signingKeys: SweepResult;
|
|
26
|
+
dashboardKeys: SweepResult;
|
|
27
|
+
ephemeralKeys: SweepResult;
|
|
28
|
+
secretOutputs: SweepResult;
|
|
29
|
+
}
|
|
30
|
+
type Warn = (message: string, meta: Record<string, unknown>) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Run all four sweeps, each in its own transaction so a bug in one cannot roll
|
|
33
|
+
* back a good rotation of another. Order is least-to-most volume, so the
|
|
34
|
+
* singleton key tables — the two whose loss brings the orchestrator down — move
|
|
35
|
+
* first.
|
|
36
|
+
*/
|
|
37
|
+
export declare function rotateMasterKeyWrappedTables(db: Kysely<Database>, keys: ResolvedMasterKeys, warn: Warn): Promise<MasterKeyRotationResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Re-seal a single row that decrypted only under the OLD master key, back under
|
|
40
|
+
* the current one. This is the boot self-heal: a deployment already stranded by
|
|
41
|
+
* a rotation that ran before these tables were swept recovers by restoring the
|
|
42
|
+
* old key once and restarting.
|
|
43
|
+
*
|
|
44
|
+
* The `key_version` match is the concurrent-rotation guard — mirrors
|
|
45
|
+
* `BackendRegistry.selfHealStrandedRow`. A rotation running at the same time
|
|
46
|
+
* has already moved the version, so this UPDATE matches no row and is a no-op
|
|
47
|
+
* rather than a clobber.
|
|
48
|
+
*/
|
|
49
|
+
export declare function selfHealStrandedSigningKey(db: Kysely<Database>, row: {
|
|
50
|
+
kid: string;
|
|
51
|
+
key_version: number;
|
|
52
|
+
}, privateJwk: Parameters<typeof wrapPrivateJwk>[0], keys: ResolvedMasterKeys): Promise<void>;
|
|
53
|
+
/** The dashboard-encryption twin of {@link selfHealStrandedSigningKey}. */
|
|
54
|
+
export declare function selfHealStrandedDashboardKey(db: Kysely<Database>, row: {
|
|
55
|
+
kid: string;
|
|
56
|
+
key_version: number;
|
|
57
|
+
}, privateKeyDer: Buffer, keys: ResolvedMasterKeys): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Loud, recovery-pointing error for a key stranded by a master-key rotation.
|
|
60
|
+
* Same shape and intent as `BackendRegistry.strandedError`: the operator needs
|
|
61
|
+
* to be told the recovery path, not just that AES-GCM failed.
|
|
62
|
+
*/
|
|
63
|
+
export declare function strandedKeyError(store: string): Error;
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=master-key-rotation.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** AAD binding a stored secret output to its run (AES-GCM authenticated data). */
|
|
2
|
+
export declare function secretOutputAad(runId: string): string;
|
|
3
|
+
/** Seal a secret-output plaintext under the master key at `keyVersion`. */
|
|
4
|
+
export declare function sealSecretOutput(plaintext: string, masterKey: Buffer, runId: string, keyVersion?: number): {
|
|
5
|
+
data: string;
|
|
6
|
+
keyVersion: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Unseal a stored secret output with the current master key, falling back to
|
|
10
|
+
* the old key during a rotation grace window. Same dual-key pattern as
|
|
11
|
+
* `PgSecretStore.decryptWithFallback`. Throws when neither key opens it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function unsealSecretOutput(encryptedValue: string, runId: string, keys: {
|
|
14
|
+
current: Buffer;
|
|
15
|
+
old?: Buffer;
|
|
16
|
+
}, keyVersion?: number): string;
|
|
17
|
+
//# sourceMappingURL=secret-output-crypto.d.ts.map
|
|
@@ -38,7 +38,7 @@ export interface BindingStoreLike {
|
|
|
38
38
|
* Minimal secret store interface (subset needed by resolver).
|
|
39
39
|
* getAllSecrets returns raw encrypted secrets; decrypt decrypts a single secret.
|
|
40
40
|
* getSecrets returns a decrypted key-value map for a single scope (used by
|
|
41
|
-
*
|
|
41
|
+
* resolveNamedInternal for source-scoped credential lookup).
|
|
42
42
|
*/
|
|
43
43
|
export interface SecretStoreLike {
|
|
44
44
|
getAllSecrets(orgId: string): Promise<ScopedSecret[]>;
|
|
@@ -70,7 +70,12 @@ export interface ResolvedSecretMeta {
|
|
|
70
70
|
*/
|
|
71
71
|
export interface SecretResolverApi {
|
|
72
72
|
resolveForJob(orgId: string, contextName: string, hostCtx?: HostFacts): Promise<Record<string, string>>;
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* System-scoped direct lookup — no context binding, no protection rule, no
|
|
75
|
+
* trust tier. A job-originated reference goes through
|
|
76
|
+
* `resolveJobQualifiedSecret` instead. See the implementation's doc comment.
|
|
77
|
+
*/
|
|
78
|
+
resolveNamedInternal(orgId: string, scope: string, key: string, opts?: {
|
|
74
79
|
store?: string;
|
|
75
80
|
runId?: string;
|
|
76
81
|
jobId?: string;
|
|
@@ -107,9 +112,21 @@ export declare class SecretResolver implements SecretResolverApi {
|
|
|
107
112
|
resolveForJob(orgId: string, contextName: string, hostCtx?: HostFacts): Promise<Record<string, string>>;
|
|
108
113
|
/**
|
|
109
114
|
* Resolve a single named secret by (orgId, scope, key), optionally scoped to
|
|
110
|
-
* a specific backend.
|
|
111
|
-
*
|
|
112
|
-
*
|
|
115
|
+
* a specific backend.
|
|
116
|
+
*
|
|
117
|
+
* SYSTEM-SCOPED CALLERS ONLY. It applies no authorization of any kind: no
|
|
118
|
+
* context binding, no protection rule, no trust tier. Its legitimate callers
|
|
119
|
+
* resolve the orchestrator's OWN credentials, under scopes a workflow can
|
|
120
|
+
* never name — `resolveSourceCredential` (`__source__/<sourceId>`) and the
|
|
121
|
+
* bringup API's host secret refs.
|
|
122
|
+
*
|
|
123
|
+
* A JOB-ORIGINATED reference goes through `resolveJobQualifiedSecret`
|
|
124
|
+
* (`secrets/job-secret-gate.ts`) instead, which runs the named context's
|
|
125
|
+
* protection rules and the trust-tier strip before reaching this method. The
|
|
126
|
+
* `Internal` suffix is the enforcement: this method's doc comment already
|
|
127
|
+
* warned that it bypassed context bindings, and three call sites took it
|
|
128
|
+
* anyway — a warning is not a boundary, but a name shows up in a grep of
|
|
129
|
+
* callers.
|
|
113
130
|
*
|
|
114
131
|
* When `store` is omitted, backends are tried in Map iteration order (the
|
|
115
132
|
* order they were registered) and the first hit wins. An explicit `store`
|
|
@@ -120,7 +137,7 @@ export declare class SecretResolver implements SecretResolverApi {
|
|
|
120
137
|
* fail-fast policy — the caller asked for a specific backend and
|
|
121
138
|
* it's gone.
|
|
122
139
|
*/
|
|
123
|
-
|
|
140
|
+
resolveNamedInternal(orgId: string, scope: string, key: string, opts?: {
|
|
124
141
|
store?: string;
|
|
125
142
|
runId?: string;
|
|
126
143
|
jobId?: string;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* the key name.
|
|
10
10
|
*
|
|
11
11
|
* This module is a thin, opinionated wrapper around
|
|
12
|
-
* `SecretResolver.
|
|
12
|
+
* `SecretResolver.resolveNamedInternal()` that:
|
|
13
13
|
* 1. Builds the canonical scope string for a given source ID.
|
|
14
14
|
* 2. Delegates to the resolver's direct-lookup method.
|
|
15
15
|
* 3. Returns a structured failure (null + reason) so callers can emit
|
|
@@ -18,9 +18,12 @@ export declare class TokenManager {
|
|
|
18
18
|
* @param routingKey - Optional routing key scope (null = all).
|
|
19
19
|
* @param expiresAt - Optional absolute expiry. When omitted the token
|
|
20
20
|
* never expires (null); `validate()` already enforces the column.
|
|
21
|
+
* @param subject - Optional intended holder (an OIDC `sub` or an email).
|
|
22
|
+
* Advisory only — never read when authorizing. Omitted leaves the token
|
|
23
|
+
* `unlinked` in the RBAC drift report.
|
|
21
24
|
* @returns The plaintext token (shown once) and the row ID.
|
|
22
25
|
*/
|
|
23
|
-
generateToken(label: string, role: Role, routingKey?: string | null, expiresAt?: Date | null): Promise<{
|
|
26
|
+
generateToken(label: string, role: Role, routingKey?: string | null, expiresAt?: Date | null, subject?: string | null): Promise<{
|
|
24
27
|
token: string;
|
|
25
28
|
id: string;
|
|
26
29
|
}>;
|
|
@@ -43,6 +43,11 @@ export declare const REDUCED_PRIVILEGE_MARKER = "**Reduced privileges (untrusted
|
|
|
43
43
|
* - `resolveInstallSecrets` returns `npmRegistries: undefined` and
|
|
44
44
|
* `installEnvSecrets: undefined` for a resolved non-trusted tier, so the run
|
|
45
45
|
* carries neither.
|
|
46
|
+
* - the per-job container-registry resolution in `dispatch-matched-workflow.ts`
|
|
47
|
+
* is skipped for the same tiers, so `jobConfig` carries no
|
|
48
|
+
* `containerRegistryAuth` and a private base image fails to pull. Keyed on
|
|
49
|
+
* the tier through the same `isUntrustedTier`, so the clause and the strip
|
|
50
|
+
* cannot disagree.
|
|
46
51
|
* - `deriveCacheRefScope` returns the isolated scope for the same tiers, and
|
|
47
52
|
* `UserCache` confines WRITES to it; a restore still falls back to the shared
|
|
48
53
|
* scope, so the clause is about what the run saves, not what it can read.
|
|
@@ -104,11 +104,12 @@ export declare const DEFAULT_FORK_POLICY: ForkPolicy;
|
|
|
104
104
|
* events is the fail-closed posture: nothing foreign dispatches, and the event
|
|
105
105
|
* is dropped rather than parked in a queue nobody is watching.
|
|
106
106
|
*
|
|
107
|
-
* This is NOT necessarily a brief transient. The Platform
|
|
108
|
-
* `trust_policy.update`
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
107
|
+
* This is NOT necessarily a brief transient. The Platform pushes
|
|
108
|
+
* `trust_policy.update` on every authenticated handshake, and for an org with
|
|
109
|
+
* no `trust_policies` row it pushes its own defaults, whose fork switch is
|
|
110
|
+
* `ignore` as well. So an org that has never opened Settings > CI trust holds
|
|
111
|
+
* these values for as long as nobody chooses otherwise, and every fork pull
|
|
112
|
+
* request it receives is dropped.
|
|
112
113
|
*/
|
|
113
114
|
export declare const FAIL_CLOSED_POLICY: TrustPolicy;
|
|
114
115
|
/**
|
|
@@ -127,17 +128,80 @@ export declare const FAIL_CLOSED_POLICY: TrustPolicy;
|
|
|
127
128
|
* can approve it — which an ignored event is not.
|
|
128
129
|
*/
|
|
129
130
|
export declare const READ_FAILURE_POLICY: TrustPolicy;
|
|
131
|
+
/**
|
|
132
|
+
* Where the policy in force came from.
|
|
133
|
+
*
|
|
134
|
+
* The three arms are three different things to tell an operator, and the
|
|
135
|
+
* difference is what makes a verdict explicable: `stored` is a choice somebody
|
|
136
|
+
* made, `unconfigured` is what nobody choosing looks like, and `read-failure`
|
|
137
|
+
* is neither — it is the orchestrator declining to guess.
|
|
138
|
+
*
|
|
139
|
+
* Named for the resolution rather than the policy, because
|
|
140
|
+
* `TrustPolicySource` is already taken by a sibling in this directory —
|
|
141
|
+
* `trust-policy-store.ts` answers "who WROTE the cached row" (`platform` /
|
|
142
|
+
* `local`) and exports that name from the package barrel. Two exported types
|
|
143
|
+
* of that name would read as one to anyone grepping for it.
|
|
144
|
+
*/
|
|
145
|
+
export declare const EffectivePolicySource: z.ZodEnum<{
|
|
146
|
+
"read-failure": "read-failure";
|
|
147
|
+
stored: "stored";
|
|
148
|
+
unconfigured: "unconfigured";
|
|
149
|
+
}>;
|
|
150
|
+
export type EffectivePolicySource = z.infer<typeof EffectivePolicySource>;
|
|
151
|
+
/** A policy plus where it came from. */
|
|
152
|
+
export interface EffectiveTrustPolicy {
|
|
153
|
+
policy: TrustPolicy;
|
|
154
|
+
source: EffectivePolicySource;
|
|
155
|
+
}
|
|
130
156
|
/**
|
|
131
157
|
* Pick the policy to evaluate. A stored row always wins; without one every
|
|
132
158
|
* orchestrator gets the fail-closed policy above.
|
|
133
159
|
*
|
|
160
|
+
* Returns the source alongside the policy because the values alone cannot
|
|
161
|
+
* answer "why was my pull request dropped?" — `ignore` reads identically
|
|
162
|
+
* whether an operator chose it or nobody chose anything.
|
|
163
|
+
*
|
|
164
|
+
* `read-failure` is not produced here: this function is handed the outcome of a
|
|
165
|
+
* read, not the read itself, so a caller whose read THREW builds that arm from
|
|
166
|
+
* `READ_FAILURE_POLICY` at its own call site.
|
|
167
|
+
*
|
|
134
168
|
* `mode` no longer selects between two postures — an independent orchestrator
|
|
135
169
|
* has no upstream authority, which is a reason to be stricter rather than more
|
|
136
170
|
* permissive — but it stays on the signature so callers that legitimately hold
|
|
137
171
|
* a mode do not have to change, and so a future per-mode difference has a place
|
|
138
172
|
* to land.
|
|
139
173
|
*/
|
|
140
|
-
export declare function resolveEffectivePolicy(stored: StoredTrustPolicy | null, _mode: OrchestratorMode):
|
|
174
|
+
export declare function resolveEffectivePolicy(stored: StoredTrustPolicy | null, _mode: OrchestratorMode): EffectiveTrustPolicy;
|
|
175
|
+
/**
|
|
176
|
+
* The org fork switch in force for one delivery, and where it came from.
|
|
177
|
+
*
|
|
178
|
+
* Carried out of the evaluation so a call site that has to EXPLAIN the verdict
|
|
179
|
+
* — the drop site, which is the one exit that leaves nothing else behind — can
|
|
180
|
+
* name both without re-reading the store.
|
|
181
|
+
*/
|
|
182
|
+
export interface ForkSwitchContext {
|
|
183
|
+
policy: ForkPolicy;
|
|
184
|
+
source: EffectivePolicySource;
|
|
185
|
+
/**
|
|
186
|
+
* True where a Platform owns this org's policy. It decides which remedy the
|
|
187
|
+
* explanation names: an independent orchestrator has no dashboard to send its
|
|
188
|
+
* operator to, and a Platform-attached one answers a local write with a 409.
|
|
189
|
+
*/
|
|
190
|
+
platformManaged: boolean;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The sentence written on the event-log row of a dropped fork pull request.
|
|
194
|
+
*
|
|
195
|
+
* This row is the ONLY trace an `ignore` verdict leaves: there is no run, no
|
|
196
|
+
* check status, and nothing on the pull request itself. So it names the switch
|
|
197
|
+
* value, where that value came from, and what did not happen — an operator
|
|
198
|
+
* reading it must not have to already know the fork switch exists.
|
|
199
|
+
*
|
|
200
|
+
* When nobody chose the switch it also names the remedy, which follows the
|
|
201
|
+
* deployment: the dashboard owns the policy wherever a Platform is attached,
|
|
202
|
+
* and `kici-admin trust-policy set` owns it where none is.
|
|
203
|
+
*/
|
|
204
|
+
export declare function forkDropExplanation(forkSwitch: ForkSwitchContext): string;
|
|
141
205
|
/**
|
|
142
206
|
* Evaluate the fork switch.
|
|
143
207
|
*
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one predicate for "this run's contributor is not trusted".
|
|
3
|
+
*
|
|
4
|
+
* It lives beside `trust-resolver.ts` and `reduced-privilege-note.ts` — the
|
|
5
|
+
* modules that decide and render the posture it reads — rather than inside any
|
|
6
|
+
* one consumer, because three unrelated subsystems now withhold credentials on
|
|
7
|
+
* it: the install-secrets resolver, the job secret gate, and container-registry
|
|
8
|
+
* auth. A copy per consumer is how two of them would end up disagreeing about
|
|
9
|
+
* what an absent tier means.
|
|
10
|
+
*/
|
|
11
|
+
import type { TrustTier } from '@kici-dev/engine';
|
|
12
|
+
/**
|
|
13
|
+
* True when the resolved trust tier is anything other than 'trusted'.
|
|
14
|
+
*
|
|
15
|
+
* `undefined` is the LENIENT direction here: an unresolved tier leaves
|
|
16
|
+
* credentials in place, while `deriveCacheRefScope` maps the same `undefined`
|
|
17
|
+
* to the isolated cache scope, which is what `resolveWorkflowDockerfileBuilds`
|
|
18
|
+
* then denies unless the org opted in. `evaluateTrustGate` reads it leniently
|
|
19
|
+
* too; `deriveCacheRefScope`, `selectLockFileSource`, and the trust-policy
|
|
20
|
+
* gate's `isNonTrusted` all read the same `undefined` strictly.
|
|
21
|
+
*
|
|
22
|
+
* Several dispatch paths reach it with no tier, and the set is open — a
|
|
23
|
+
* provider bundle carrying a fork model does not keep a run out of it. Among
|
|
24
|
+
* them: an internally-triggered run whose inheritance lookup degrades, a
|
|
25
|
+
* pull-request event from a provider with no fork model, a cross-source
|
|
26
|
+
* dispatch, and a `kici run` remote test run. Treat "no tier" as its own case
|
|
27
|
+
* rather than as a proxy for any one of them. Documented for operators in
|
|
28
|
+
* `docs/user/events.md` under the trust-tier rules.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isUntrustedTier(tier: TrustTier | undefined): boolean;
|
|
31
|
+
//# sourceMappingURL=trust-tier.d.ts.map
|
package/dist/server.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Graceful shutdown in reverse order:
|
|
14
14
|
* Platform client -> agent WS -> heartbeat -> HTTP -> DB
|
|
15
15
|
*/
|
|
16
|
-
import type { OrchestratorFaultInjection } from './
|
|
16
|
+
import type { OrchestratorFaultInjection } from './fault-injection-types.js';
|
|
17
17
|
/**
|
|
18
18
|
* Boot the Platform-connected orchestrator. Factored out of the module entry so
|
|
19
19
|
* both this file's production entry (which passes no fault injection) and the
|