@xemahq/xema 0.26.0 → 0.28.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/README.md +70 -37
- package/dist/assets/distribution-assets.manifest.json +1 -2
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/dev.js +1 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +7 -1
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/edition.d.ts +3 -0
- package/dist/commands/edition.d.ts.map +1 -0
- package/dist/commands/edition.js +46 -0
- package/dist/commands/edition.js.map +1 -0
- package/dist/commands/init.js +2 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/serve.js +1 -0
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/up.js +1 -0
- package/dist/commands/up.js.map +1 -1
- package/dist/dev/env-defaults.d.ts.map +1 -1
- package/dist/dev/env-defaults.js +5 -2
- package/dist/dev/env-defaults.js.map +1 -1
- package/dist/dev/infra-preflight.d.ts +3 -2
- package/dist/dev/infra-preflight.d.ts.map +1 -1
- package/dist/dev/infra-preflight.js +18 -8
- package/dist/dev/infra-preflight.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +54283 -0
- package/dist/edition/verify-image-evidence.bundle.provenance.json +2877 -0
- package/dist/runtime/config/schema.d.ts +1 -0
- package/dist/runtime/config/schema.d.ts.map +1 -1
- package/dist/runtime/custody/local-custody.d.ts +12 -0
- package/dist/runtime/custody/local-custody.d.ts.map +1 -0
- package/dist/runtime/custody/local-custody.js +22 -0
- package/dist/runtime/custody/local-custody.js.map +1 -0
- package/dist/runtime/custody/openbao-custody-bootstrap.d.ts +18 -0
- package/dist/runtime/custody/openbao-custody-bootstrap.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-custody-bootstrap.js +50 -0
- package/dist/runtime/custody/openbao-custody-bootstrap.js.map +1 -0
- package/dist/runtime/custody/openbao-custody-store.d.ts +18 -0
- package/dist/runtime/custody/openbao-custody-store.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-custody-store.js +55 -0
- package/dist/runtime/custody/openbao-custody-store.js.map +1 -0
- package/dist/runtime/custody/openbao-http-client.d.ts +34 -0
- package/dist/runtime/custody/openbao-http-client.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-http-client.js +154 -0
- package/dist/runtime/custody/openbao-http-client.js.map +1 -0
- package/dist/runtime/custody/openbao-local-constants.d.ts +22 -0
- package/dist/runtime/custody/openbao-local-constants.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-local-constants.js +48 -0
- package/dist/runtime/custody/openbao-local-constants.js.map +1 -0
- package/dist/runtime/custody/openbao-local-runner.d.ts +16 -0
- package/dist/runtime/custody/openbao-local-runner.d.ts.map +1 -0
- package/dist/runtime/custody/openbao-local-runner.js +86 -0
- package/dist/runtime/custody/openbao-local-runner.js.map +1 -0
- package/dist/runtime/edition/run-entry.d.ts +9 -0
- package/dist/runtime/edition/run-entry.d.ts.map +1 -0
- package/dist/runtime/edition/run-entry.js +36 -0
- package/dist/runtime/edition/run-entry.js.map +1 -0
- package/dist/runtime/edition/verb-map.d.ts +60 -0
- package/dist/runtime/edition/verb-map.d.ts.map +1 -0
- package/dist/runtime/edition/verb-map.js +146 -0
- package/dist/runtime/edition/verb-map.js.map +1 -0
- package/dist/runtime/env-seeder.d.ts +2 -0
- package/dist/runtime/env-seeder.d.ts.map +1 -1
- package/dist/runtime/env-seeder.js +18 -3
- package/dist/runtime/env-seeder.js.map +1 -1
- package/dist/runtime/launch.d.ts +2 -1
- package/dist/runtime/launch.d.ts.map +1 -1
- package/dist/runtime/launch.js +31 -7
- package/dist/runtime/launch.js.map +1 -1
- package/dist/runtime/paths.d.ts +1 -2
- package/dist/runtime/paths.d.ts.map +1 -1
- package/dist/runtime/paths.js +0 -10
- package/dist/runtime/paths.js.map +1 -1
- package/dist/runtime/runner/chart-local-provenance.d.ts +20 -0
- package/dist/runtime/runner/chart-local-provenance.d.ts.map +1 -0
- package/dist/runtime/runner/chart-local-provenance.js +114 -0
- package/dist/runtime/runner/chart-local-provenance.js.map +1 -0
- package/dist/runtime/runner/chart-preflight.d.ts +8 -8
- package/dist/runtime/runner/chart-preflight.d.ts.map +1 -1
- package/dist/runtime/runner/chart-preflight.js +30 -69
- package/dist/runtime/runner/chart-preflight.js.map +1 -1
- package/dist/runtime/runner/chart-pull.d.ts +14 -0
- package/dist/runtime/runner/chart-pull.d.ts.map +1 -0
- package/dist/runtime/runner/chart-pull.js +96 -0
- package/dist/runtime/runner/chart-pull.js.map +1 -0
- package/dist/runtime/runner/chart-resolver.d.ts +4 -12
- package/dist/runtime/runner/chart-resolver.d.ts.map +1 -1
- package/dist/runtime/runner/chart-resolver.js +16 -62
- package/dist/runtime/runner/chart-resolver.js.map +1 -1
- package/dist/runtime/runner/chart-set-lock.d.ts +6 -20
- package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
- package/dist/runtime/runner/chart-set-lock.js +55 -99
- package/dist/runtime/runner/chart-set-lock.js.map +1 -1
- package/dist/runtime/runner/helm-cli.d.ts +3 -1
- package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
- package/dist/runtime/runner/helm-cli.js +9 -7
- package/dist/runtime/runner/helm-cli.js.map +1 -1
- package/dist/runtime/runner/helm-runner.d.ts +4 -4
- package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
- package/dist/runtime/runner/helm-runner.js +18 -16
- package/dist/runtime/runner/helm-runner.js.map +1 -1
- package/dist/runtime/sources/kinds/git.d.ts +1 -2
- package/dist/runtime/sources/kinds/git.d.ts.map +1 -1
- package/dist/runtime/sources/kinds/git.js +8 -5
- package/dist/runtime/sources/kinds/git.js.map +1 -1
- package/dist/runtime/sources/manifest-walk.d.ts +3 -1
- package/dist/runtime/sources/manifest-walk.d.ts.map +1 -1
- package/dist/runtime/sources/manifest-walk.js +8 -3
- package/dist/runtime/sources/manifest-walk.js.map +1 -1
- package/dist/runtime/sources/schema.d.ts +3 -0
- package/dist/runtime/sources/schema.d.ts.map +1 -1
- package/dist/runtime/sources/schema.js +6 -1
- package/dist/runtime/sources/schema.js.map +1 -1
- package/dist/runtime/supervisor.d.ts +2 -0
- package/dist/runtime/supervisor.d.ts.map +1 -1
- package/dist/runtime/supervisor.js +1 -0
- package/dist/runtime/supervisor.js.map +1 -1
- package/edition/README.md +313 -0
- package/edition/assemble-producer-input.mjs +1251 -0
- package/edition/authorization-base.mjs +347 -0
- package/edition/biome-content.mjs +268 -0
- package/edition/build-biome-index.mjs +175 -0
- package/edition/build-platform-service-index.mjs +288 -0
- package/edition/build-runtime-image-source-index.mjs +821 -0
- package/edition/built-verifier.mjs +55 -0
- package/edition/catalogue-edition.mjs +49 -0
- package/edition/catalogue-fetch.mjs +64 -0
- package/edition/catalogue.mjs +135 -0
- package/edition/chart-set-source-commit.mjs +92 -0
- package/edition/check-committed-image-evidence.mjs +383 -0
- package/edition/checkout-indexed-source-repositories.mjs +105 -0
- package/edition/comment-blanker.mjs +75 -0
- package/edition/connector-source-authority.mjs +60 -0
- package/edition/customer-definition.mjs +183 -0
- package/edition/customer-flags.mjs +7 -0
- package/edition/develop-customer-edition.mjs +99 -0
- package/edition/develop-edition-resolve.mjs +65 -0
- package/edition/develop-edition.mjs +274 -0
- package/edition/develop-freeze.mjs +173 -0
- package/edition/develop-image-coordinate.mjs +54 -0
- package/edition/develop-seal.mjs +77 -0
- package/edition/develop-snapshot-inputs.mjs +104 -0
- package/edition/dist-check.mjs +986 -0
- package/edition/distribution-evidence-set.mjs +987 -0
- package/edition/distribution-paths.mjs +191 -0
- package/edition/edition-artifact.mjs +284 -0
- package/edition/edition-biome-manifests.mjs +85 -0
- package/edition/edition-channel.mjs +6 -0
- package/edition/edition-chart-set.mjs +93 -0
- package/edition/edition-config.mjs +205 -0
- package/edition/edition-constants.mjs +57 -0
- package/edition/edition-contract.json +30 -0
- package/edition/edition-list.mjs +35 -0
- package/edition/edition-locks.mjs +69 -0
- package/edition/edition-protocol.mjs +228 -0
- package/edition/edition-workspace.mjs +187 -0
- package/edition/evidence-checkout.mjs +439 -0
- package/edition/evidence-grammars.mjs +16 -0
- package/edition/evidence-trust-pin.mjs +304 -0
- package/edition/evidence-trust-policy.mjs +294 -0
- package/edition/external-source-walk.mjs +463 -0
- package/edition/first-party-source-ids.mjs +59 -0
- package/edition/fleet-coordinate-finality.mjs +350 -0
- package/edition/git-source-auth.mjs +52 -0
- package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
- package/edition/image-evidence-replay-state.mjs +242 -0
- package/edition/image-evidence.mjs +1233 -0
- package/edition/kernel-contract-authority.mjs +443 -0
- package/edition/lib.mjs +3021 -0
- package/edition/no-follow-file.mjs +94 -0
- package/edition/oci-evidence-verbs.mjs +392 -0
- package/edition/oci-image-source-evidence.mjs +239 -0
- package/edition/oras.mjs +648 -0
- package/edition/pin-source.mjs +227 -0
- package/edition/plan-image-evidence-refresh.mjs +408 -0
- package/edition/platform-lock-readability.mjs +892 -0
- package/edition/platform-service-class.mjs +18 -0
- package/edition/platform-service-source-authority.mjs +105 -0
- package/edition/positive-closure.mjs +162 -0
- package/edition/produce-distribution-completeness-evidence.mjs +311 -0
- package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
- package/edition/produce-image-source-evidence.mjs +765 -0
- package/edition/producer-evidence-bundle.mjs +157 -0
- package/edition/registry-credentials.mjs +193 -0
- package/edition/release-catalogue.mjs +39 -0
- package/edition/release-channel-guard.mjs +109 -0
- package/edition/release-edition-artifact.mjs +157 -0
- package/edition/remedy-command.mjs +89 -0
- package/edition/repin-evidence-trust.mjs +101 -0
- package/edition/required-resource-provider.mjs +58 -0
- package/edition/resolve-chart-set.mjs +415 -0
- package/edition/resolve-customer-edition.mjs +288 -0
- package/edition/resolve-distribution.mjs +2318 -0
- package/edition/resolve-edition.mjs +186 -0
- package/edition/resolve-images.mjs +1606 -0
- package/edition/seal-image-digests.mjs +855 -0
- package/edition/sealed-evidence-accept.mjs +82 -0
- package/edition/source-authority.mjs +725 -0
- package/edition/source-closure.mjs +116 -0
- package/edition/strict-json.mjs +121 -0
- package/edition/string-order.mjs +5 -0
- package/edition/trust-policy-arming.mjs +120 -0
- package/edition/trusted-executable.mjs +166 -0
- package/edition/verifier-binding.mjs +45 -0
- package/edition/verifier-files.mjs +8 -0
- package/edition/verifier-kernel-schemas.mjs +22 -0
- package/edition/verifier-provenance.mjs +64 -0
- package/edition/verify-image-evidence.mjs +458 -0
- package/edition/verify-image-platforms.mjs +495 -0
- package/edition/why.mjs +56 -0
- package/edition/workspace-sections.mjs +195 -0
- package/examples/06-biome-sources.yaml +1 -0
- package/package.json +14 -6
- package/dist/commands/dist.d.ts +0 -16
- package/dist/commands/dist.d.ts.map +0 -1
- package/dist/commands/dist.js +0 -182
- package/dist/commands/dist.js.map +0 -1
- package/dist/runtime/distribution/available-biome.d.ts +0 -10
- package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
- package/dist/runtime/distribution/available-biome.js +0 -156
- package/dist/runtime/distribution/available-biome.js.map +0 -1
- package/dist/runtime/distribution/compose.d.ts +0 -23
- package/dist/runtime/distribution/compose.d.ts.map +0 -1
- package/dist/runtime/distribution/compose.js +0 -48
- package/dist/runtime/distribution/compose.js.map +0 -1
- package/dist/runtime/runner/chart-trust.d.ts +0 -57
- package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
- package/dist/runtime/runner/chart-trust.js +0 -270
- package/dist/runtime/runner/chart-trust.js.map +0 -1
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
// The evidence verifier. This module is the ENTRY of the verifier bundle
|
|
2
|
+
// (`scripts/build-evidence-verifier.mjs` -> dist/edition/verify-image-evidence
|
|
3
|
+
// .bundle.mjs); `xema edition verify-evidence` runs that built file, never this
|
|
4
|
+
// source. Run as the bundle it refuses, before it trusts anything, unless:
|
|
5
|
+
//
|
|
6
|
+
// * its launch environment carries no NODE_OPTIONS/NODE_PATH/DYLD_*/LD_*/GIT_*;
|
|
7
|
+
// * its own file and provenance hash to the digests the data root PINS, and
|
|
8
|
+
// its install path and every ancestor are not group/world writable or
|
|
9
|
+
// foreign-owned;
|
|
10
|
+
// * the kernel `--kernel` states declares, as data, exactly the edition
|
|
11
|
+
// protocol compiled into the bundle, and this CLI supports it;
|
|
12
|
+
// * the data root and the editions root are clean git checkouts at an exact
|
|
13
|
+
// HEAD with every file read from them bound to its HEAD blob.
|
|
14
|
+
//
|
|
15
|
+
// usage: verify-image-evidence <distributionId> --evidence-root <dir>
|
|
16
|
+
// --descriptor <absolute-path> --state <absolute-path> --cosign <absolute-path> --git <absolute-path>
|
|
17
|
+
// [--evidence-set <relative-path>] --data-root <dir> --editions-root <dir>
|
|
18
|
+
// --kernel <dir>
|
|
19
|
+
|
|
20
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
21
|
+
import { isAbsolute } from 'node:path';
|
|
22
|
+
|
|
23
|
+
import { assertCanonicalDistributionId } from './distribution-paths.mjs';
|
|
24
|
+
import { parseEditionDescriptor } from './edition-artifact.mjs';
|
|
25
|
+
import { EditionChannel } from './edition-channel.mjs';
|
|
26
|
+
import { editionWorkspaceConfig, resetEditionWorkspaceConfig } from './edition-config.mjs';
|
|
27
|
+
import { assertKernelSpeaksBundledProtocol } from './edition-protocol.mjs';
|
|
28
|
+
import {
|
|
29
|
+
WorkspaceRoot,
|
|
30
|
+
configureEditionWorkspaceFromArgv,
|
|
31
|
+
dataRoot,
|
|
32
|
+
editionsRoot,
|
|
33
|
+
isEntryModule,
|
|
34
|
+
kernelPackageDir,
|
|
35
|
+
} from './edition-workspace.mjs';
|
|
36
|
+
import {
|
|
37
|
+
assertExternalInput,
|
|
38
|
+
gradeTrustedCheckouts,
|
|
39
|
+
prepareTrustedGitBinary,
|
|
40
|
+
} from './evidence-checkout.mjs';
|
|
41
|
+
import {
|
|
42
|
+
RUNTIME_IMAGE_SOURCE_INDEX_FILE,
|
|
43
|
+
assertPinnedKernelContractAuthority,
|
|
44
|
+
assertPinnedPolicy,
|
|
45
|
+
assertPinnedVerifier,
|
|
46
|
+
readStatedKernelContractAuthority,
|
|
47
|
+
loadDeclaredTrustPolicy,
|
|
48
|
+
readEvidenceTrustPin,
|
|
49
|
+
trustPolicyDigest,
|
|
50
|
+
verifierDigests,
|
|
51
|
+
} from './evidence-trust-pin.mjs';
|
|
52
|
+
import {
|
|
53
|
+
assertEntitlementDescriptorStates,
|
|
54
|
+
entitlementEvidenceRoot,
|
|
55
|
+
parseEntitlementDescriptorDocument,
|
|
56
|
+
} from './image-delivery-entitlement-descriptor.mjs';
|
|
57
|
+
import { verifyAndAdvanceReplayState } from './image-evidence-replay-state.mjs';
|
|
58
|
+
import {
|
|
59
|
+
readCanonicalJsonFromRoot,
|
|
60
|
+
verifyDistributionImageEvidenceSet,
|
|
61
|
+
verifyImageDeliveryEntitlement,
|
|
62
|
+
} from './image-evidence.mjs';
|
|
63
|
+
import {
|
|
64
|
+
assertSanitizedOperatorEnvironment,
|
|
65
|
+
readRunningVerifier,
|
|
66
|
+
} from './verifier-binding.mjs';
|
|
67
|
+
import { loadVerifierKernelSchemas } from './verifier-kernel-schemas.mjs';
|
|
68
|
+
|
|
69
|
+
const USAGE =
|
|
70
|
+
'usage: verify-image-evidence <distributionId> --evidence-root <dir> --descriptor <absolute-path> --state <absolute-path> ' +
|
|
71
|
+
'--cosign <absolute-path> --git <absolute-path> [--evidence-set <relative-path>] [--json] ' +
|
|
72
|
+
'[--entitlement <relative-path> --entitlement-workflow-sha <40-hex git sha> --customer <id> --mode <mode> [--target-registry <registry>]] ' +
|
|
73
|
+
'--data-root <dir> --editions-root <dir> --kernel <dir>';
|
|
74
|
+
// The edition channel -> the kernel's evidence channel, stated once and explicitly.
|
|
75
|
+
const EVIDENCE_CHANNEL_OF_EDITION_CHANNEL = Object.freeze({
|
|
76
|
+
[EditionChannel.Release]: 'release',
|
|
77
|
+
[EditionChannel.Develop]: 'develop',
|
|
78
|
+
});
|
|
79
|
+
const GIT_COMMIT_SHA = /^[a-f0-9]{40}$/;
|
|
80
|
+
const DATA_ROOT_FILES = Object.freeze(['edition-workspace.json', 'runtime-image-source-index.json']);
|
|
81
|
+
|
|
82
|
+
function requireOptionValue(args, index, flag) {
|
|
83
|
+
const value = args[index + 1];
|
|
84
|
+
if (value === undefined || value.startsWith('--')) {
|
|
85
|
+
throw new Error(`${flag} requires a value`);
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseArgs(args) {
|
|
91
|
+
const [distributionId, ...withJson] = args;
|
|
92
|
+
const jsonFlags = withJson.filter((arg) => arg === '--json').length;
|
|
93
|
+
if (jsonFlags > 1) {throw new Error('--json was given twice');}
|
|
94
|
+
const rest = withJson.filter((arg) => arg !== '--json');
|
|
95
|
+
try {
|
|
96
|
+
assertCanonicalDistributionId(distributionId);
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error(USAGE);
|
|
99
|
+
}
|
|
100
|
+
const options = {
|
|
101
|
+
distributionId,
|
|
102
|
+
evidenceRoot: null,
|
|
103
|
+
evidenceSetPath: 'evidence-set.json',
|
|
104
|
+
json: jsonFlags === 1,
|
|
105
|
+
descriptorPath: null,
|
|
106
|
+
statePath: null,
|
|
107
|
+
cosignBinary: null,
|
|
108
|
+
gitBinary: null,
|
|
109
|
+
entitlementDescriptorPath: null,
|
|
110
|
+
entitlementWorkflowSha: null,
|
|
111
|
+
customerId: null,
|
|
112
|
+
mode: null,
|
|
113
|
+
targetRegistry: null,
|
|
114
|
+
};
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
for (let index = 0; index < rest.length; index += 2) {
|
|
117
|
+
const flag = rest[index];
|
|
118
|
+
const value = requireOptionValue(rest, index, flag);
|
|
119
|
+
if (seen.has(flag)) {throw new Error(`${flag} was given twice`);}
|
|
120
|
+
seen.add(flag);
|
|
121
|
+
if (flag === '--evidence-root') {options.evidenceRoot = value;}
|
|
122
|
+
else if (flag === '--evidence-set') {options.evidenceSetPath = value;}
|
|
123
|
+
else if (flag === '--descriptor') {options.descriptorPath = value;}
|
|
124
|
+
else if (flag === '--state') {options.statePath = value;}
|
|
125
|
+
else if (flag === '--cosign') {options.cosignBinary = value;}
|
|
126
|
+
else if (flag === '--git') {options.gitBinary = value;}
|
|
127
|
+
else if (flag === '--entitlement') {options.entitlementDescriptorPath = value;}
|
|
128
|
+
else if (flag === '--entitlement-workflow-sha') {options.entitlementWorkflowSha = value;}
|
|
129
|
+
else if (flag === '--customer') {options.customerId = value;}
|
|
130
|
+
else if (flag === '--mode') {options.mode = value;}
|
|
131
|
+
else if (flag === '--target-registry') {options.targetRegistry = value;}
|
|
132
|
+
else {throw new Error(`unknown option "${flag}"`);}
|
|
133
|
+
}
|
|
134
|
+
if (!options.evidenceRoot) {
|
|
135
|
+
throw new Error('--evidence-root is required');
|
|
136
|
+
}
|
|
137
|
+
for (const [flag, value] of [
|
|
138
|
+
['--descriptor', options.descriptorPath],
|
|
139
|
+
['--state', options.statePath],
|
|
140
|
+
['--cosign', options.cosignBinary],
|
|
141
|
+
['--git', options.gitBinary],
|
|
142
|
+
]) {
|
|
143
|
+
if (!value || !isAbsolute(value)) {
|
|
144
|
+
throw new Error(`${flag} is required and must be an absolute path`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// An entitlement is verified whole or not at all: the descriptor names the
|
|
148
|
+
// signed statement, the workflow SHA is the exact commit its certificate is
|
|
149
|
+
// held to (stated by the caller, never read from the evidence), and the
|
|
150
|
+
// customer and mode are what it must be bound to.
|
|
151
|
+
const entitlementFlags = [
|
|
152
|
+
['--entitlement', options.entitlementDescriptorPath],
|
|
153
|
+
['--entitlement-workflow-sha', options.entitlementWorkflowSha],
|
|
154
|
+
['--customer', options.customerId],
|
|
155
|
+
['--mode', options.mode],
|
|
156
|
+
];
|
|
157
|
+
const stated = entitlementFlags.filter(([, value]) => value !== null);
|
|
158
|
+
if (stated.length !== 0 && stated.length !== entitlementFlags.length) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`${entitlementFlags.map(([flag]) => flag).join(', ')} are given together or not at all`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (stated.length === 0 && options.targetRegistry !== null) {
|
|
164
|
+
throw new Error('--target-registry needs --entitlement, --entitlement-workflow-sha, --customer and --mode');
|
|
165
|
+
}
|
|
166
|
+
if (options.entitlementWorkflowSha !== null && !GIT_COMMIT_SHA.test(options.entitlementWorkflowSha)) {
|
|
167
|
+
throw new Error('--entitlement-workflow-sha must be a 40-character lowercase hexadecimal git commit SHA');
|
|
168
|
+
}
|
|
169
|
+
if (options.entitlementDescriptorPath !== null && isAbsolute(options.entitlementDescriptorPath)) {
|
|
170
|
+
throw new Error('--entitlement must be an evidence-root-relative path');
|
|
171
|
+
}
|
|
172
|
+
return options;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The channel the edition being verified was produced on: the descriptor's
|
|
177
|
+
* `provenance.channel`, mapped to the kernel's evidence channel. The descriptor
|
|
178
|
+
* must be the one of the requested distribution; an unmapped channel is refused.
|
|
179
|
+
*/
|
|
180
|
+
function readEvidenceChannel({ descriptorPath, distributionId, schemas }) {
|
|
181
|
+
const descriptor = parseEditionDescriptor(JSON.parse(readFileSync(descriptorPath, 'utf8')));
|
|
182
|
+
if (descriptor.distributionId !== distributionId) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`--descriptor is the descriptor of "${descriptor.distributionId}", not of the requested distribution "${distributionId}"`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
const channel = EVIDENCE_CHANNEL_OF_EDITION_CHANNEL[descriptor.provenance.channel];
|
|
188
|
+
if (channel === undefined || !schemas.EVIDENCE_CHANNELS.includes(channel)) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`the descriptor channel "${descriptor.provenance.channel}" maps to no kernel evidence channel`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return channel;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const EDITION_EVIDENCE_VERIFICATION_SCHEMA = 'xema.edition-evidence-verification/v1';
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* What `--json` prints once the evidence is verified: the channel it was verified
|
|
200
|
+
* on, the release sequence it proved, and the statementDigest (the digest of the verified in-toto statement, not of the image) of every statement verified
|
|
201
|
+
* (producers by subject, then completeness, then the entitlement if one was asked for).
|
|
202
|
+
* `entitlementDescriptor` is the Sigstore evidence descriptor `entitlement.json` holds.
|
|
203
|
+
*/
|
|
204
|
+
export function evidenceVerificationDocument({ distributionId, channel, evidenceSet, verified, entitlementDescriptor }) {
|
|
205
|
+
const producers = evidenceSet.producerEvidence
|
|
206
|
+
.map((producer) => {
|
|
207
|
+
const statement = verified.producerStatements.get(`${producer.subjectKind}\0${producer.subjectId}`);
|
|
208
|
+
return {
|
|
209
|
+
predicateType: statement.predicateType,
|
|
210
|
+
subject: statement.subject[0].name,
|
|
211
|
+
statementDigest: producer.evidence.statementDigest,
|
|
212
|
+
};
|
|
213
|
+
})
|
|
214
|
+
.sort((left, right) => (left.subject < right.subject ? -1 : left.subject > right.subject ? 1 : 0));
|
|
215
|
+
const statements = [
|
|
216
|
+
...producers,
|
|
217
|
+
{
|
|
218
|
+
predicateType: verified.completenessStatement.predicateType,
|
|
219
|
+
subject: verified.completenessStatement.subject[0].name,
|
|
220
|
+
statementDigest: evidenceSet.completeness.statementDigest,
|
|
221
|
+
},
|
|
222
|
+
];
|
|
223
|
+
if (verified.entitlementStatement !== null) {
|
|
224
|
+
statements.push({
|
|
225
|
+
predicateType: verified.entitlementStatement.predicateType,
|
|
226
|
+
subject: verified.entitlementStatement.subject[0].name,
|
|
227
|
+
statementDigest: entitlementDescriptor.statementDigest,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
schema: EDITION_EVIDENCE_VERIFICATION_SCHEMA,
|
|
232
|
+
distributionId,
|
|
233
|
+
channel,
|
|
234
|
+
releaseSequence: verified.completenessStatement.predicate.releaseSequence,
|
|
235
|
+
statements,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The declared policy, held to the data-root pin of both the policy and THIS
|
|
241
|
+
* running verifier. Read from the working tree: it is read twice, before the
|
|
242
|
+
* checkouts are graded (the policy names the git binary that grades them) and
|
|
243
|
+
* after, and the two readings must be the same policy.
|
|
244
|
+
*/
|
|
245
|
+
function readTrustAnchors({ schemas, running }) {
|
|
246
|
+
resetEditionWorkspaceConfig();
|
|
247
|
+
const declaration = editionWorkspaceConfig().evidenceTrustPolicy;
|
|
248
|
+
if (declaration.policy === null) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`${dataRoot()} declares no evidenceTrustPolicy in edition-workspace.json; there is no policy to verify against`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const { policy, policyDigest } = loadDeclaredTrustPolicy({
|
|
254
|
+
dataRoot: dataRoot(),
|
|
255
|
+
declaration,
|
|
256
|
+
schemas,
|
|
257
|
+
});
|
|
258
|
+
const pin = readEvidenceTrustPin({ dataRoot: dataRoot(), declaration });
|
|
259
|
+
assertPinnedPolicy({ pin, policyDigest });
|
|
260
|
+
assertPinnedVerifier({ pin, actual: verifierDigests(running) });
|
|
261
|
+
// The authority the producer statements are held to is read from the data
|
|
262
|
+
// root's index downstream; it is the pinned one, or nothing is verified.
|
|
263
|
+
assertPinnedKernelContractAuthority({
|
|
264
|
+
pin,
|
|
265
|
+
authority: readStatedKernelContractAuthority({ dataRoot: dataRoot(), schemas }),
|
|
266
|
+
label: RUNTIME_IMAGE_SOURCE_INDEX_FILE,
|
|
267
|
+
});
|
|
268
|
+
return { declaration, policy, policyDigest };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Verify the evidence for one edition. `bundlePath` is the running bundle
|
|
273
|
+
* (`process.argv[1]` when run as one).
|
|
274
|
+
*/
|
|
275
|
+
export async function verifyEvidence({ argv, env, bundlePath }) {
|
|
276
|
+
assertSanitizedOperatorEnvironment(env);
|
|
277
|
+
const options = parseArgs(
|
|
278
|
+
configureEditionWorkspaceFromArgv(argv, [
|
|
279
|
+
WorkspaceRoot.Data,
|
|
280
|
+
WorkspaceRoot.Editions,
|
|
281
|
+
WorkspaceRoot.Kernel,
|
|
282
|
+
]),
|
|
283
|
+
);
|
|
284
|
+
const evidenceRoot = realpathSync(options.evidenceRoot);
|
|
285
|
+
const running = readRunningVerifier(bundlePath);
|
|
286
|
+
const { schemas, editionProtocol } = await loadVerifierKernelSchemas();
|
|
287
|
+
assertKernelSpeaksBundledProtocol({
|
|
288
|
+
kernelPackageDir: kernelPackageDir(),
|
|
289
|
+
bundled: editionProtocol,
|
|
290
|
+
});
|
|
291
|
+
const first = readTrustAnchors({ schemas, running });
|
|
292
|
+
const trustedGit = prepareTrustedGitBinary({
|
|
293
|
+
gitBinary: options.gitBinary,
|
|
294
|
+
policy: first.policy,
|
|
295
|
+
});
|
|
296
|
+
try {
|
|
297
|
+
const grades = [
|
|
298
|
+
{
|
|
299
|
+
root: dataRoot(),
|
|
300
|
+
label: 'data root',
|
|
301
|
+
requiredPaths: [
|
|
302
|
+
...DATA_ROOT_FILES,
|
|
303
|
+
first.declaration.policy,
|
|
304
|
+
first.declaration.pin,
|
|
305
|
+
first.policy.trustedRootPath,
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
root: editionsRoot(),
|
|
310
|
+
label: 'editions root',
|
|
311
|
+
requiredPaths: [
|
|
312
|
+
'distribution.lock.json',
|
|
313
|
+
'images.lock.json',
|
|
314
|
+
'xema-distribution.json',
|
|
315
|
+
].map((file) => `${options.distributionId}/${file}`),
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
const graded = gradeTrustedCheckouts({ grades, trustedGit });
|
|
319
|
+
const repositoryRoots = new Set(
|
|
320
|
+
[...graded.values()].map((checkout) => checkout.repositoryRoot),
|
|
321
|
+
);
|
|
322
|
+
// Now that the files are bound to HEAD, the policy and pin read above must
|
|
323
|
+
// be the ones that were graded.
|
|
324
|
+
const anchors = readTrustAnchors({ schemas, running });
|
|
325
|
+
if (anchors.policyDigest !== first.policyDigest) {
|
|
326
|
+
throw new Error('the trust policy changed while the checkouts were graded');
|
|
327
|
+
}
|
|
328
|
+
assertExternalInput(repositoryRoots, evidenceRoot, 'evidence root');
|
|
329
|
+
assertExternalInput(repositoryRoots, options.statePath, 'replay state');
|
|
330
|
+
assertExternalInput(repositoryRoots, options.descriptorPath, 'edition descriptor');
|
|
331
|
+
const channel = readEvidenceChannel({
|
|
332
|
+
descriptorPath: options.descriptorPath,
|
|
333
|
+
distributionId: options.distributionId,
|
|
334
|
+
schemas,
|
|
335
|
+
});
|
|
336
|
+
const releaseSourceCommit = graded.get(realpathSync(editionsRoot())).commit;
|
|
337
|
+
const assertCheckoutsUnchanged = () =>
|
|
338
|
+
gradeTrustedCheckouts({ grades, trustedGit, expected: graded });
|
|
339
|
+
const evidenceSet = schemas.parseDistributionImageEvidenceSet(
|
|
340
|
+
readCanonicalJsonFromRoot(
|
|
341
|
+
evidenceRoot,
|
|
342
|
+
options.evidenceSetPath,
|
|
343
|
+
'distribution image evidence set',
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
const policyDigest = trustPolicyDigest(anchors.policy);
|
|
347
|
+
const { verified } = await verifyAndAdvanceReplayState({
|
|
348
|
+
statePath: options.statePath,
|
|
349
|
+
distributionId: options.distributionId,
|
|
350
|
+
trustPolicyDigest: policyDigest,
|
|
351
|
+
parseReplayState: schemas.parseDistributionImageReplayState,
|
|
352
|
+
verify: async () => {
|
|
353
|
+
assertCheckoutsUnchanged();
|
|
354
|
+
const result = await verifyDistributionImageEvidenceSet({
|
|
355
|
+
distributionId: options.distributionId,
|
|
356
|
+
channel,
|
|
357
|
+
dataRoot: dataRoot(),
|
|
358
|
+
editionsRoot: editionsRoot(),
|
|
359
|
+
evidenceRoot,
|
|
360
|
+
evidenceSet,
|
|
361
|
+
policy: anchors.policy,
|
|
362
|
+
policyRoot: dataRoot(),
|
|
363
|
+
now: new Date(),
|
|
364
|
+
expectedReleaseSourceCommit: releaseSourceCommit,
|
|
365
|
+
cosignBinary: options.cosignBinary,
|
|
366
|
+
schemas,
|
|
367
|
+
});
|
|
368
|
+
const entitlement =
|
|
369
|
+
options.entitlementDescriptorPath === null
|
|
370
|
+
? null
|
|
371
|
+
: parseEntitlementDescriptorDocument(
|
|
372
|
+
readCanonicalJsonFromRoot(
|
|
373
|
+
evidenceRoot,
|
|
374
|
+
options.entitlementDescriptorPath,
|
|
375
|
+
'delivery entitlement descriptor',
|
|
376
|
+
),
|
|
377
|
+
schemas,
|
|
378
|
+
);
|
|
379
|
+
if (entitlement !== null) {
|
|
380
|
+
assertEntitlementDescriptorStates(entitlement, {
|
|
381
|
+
distributionId: options.distributionId,
|
|
382
|
+
customerId: options.customerId,
|
|
383
|
+
mode: options.mode,
|
|
384
|
+
targetRegistry: options.targetRegistry,
|
|
385
|
+
distributionStatementDigest: result.completenessStatementDigest,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
const entitlementDescriptor = entitlement === null ? null : entitlement.evidence;
|
|
389
|
+
const entitlementStatement =
|
|
390
|
+
entitlementDescriptor === null
|
|
391
|
+
? null
|
|
392
|
+
: await verifyImageDeliveryEntitlement({
|
|
393
|
+
evidenceRoot: entitlementEvidenceRoot(evidenceRoot, options.entitlementDescriptorPath),
|
|
394
|
+
descriptor: entitlementDescriptor,
|
|
395
|
+
policy: anchors.policy,
|
|
396
|
+
policyRoot: dataRoot(),
|
|
397
|
+
completenessStatementDigest: result.completenessStatementDigest,
|
|
398
|
+
customerId: options.customerId,
|
|
399
|
+
distributionId: options.distributionId,
|
|
400
|
+
channel,
|
|
401
|
+
mode: options.mode,
|
|
402
|
+
targetRegistry: options.targetRegistry,
|
|
403
|
+
now: new Date(),
|
|
404
|
+
// The entitlement predicate carries no producer run: the commit
|
|
405
|
+
// its certificate is held to is the caller's statement.
|
|
406
|
+
expectedWorkflowSha: options.entitlementWorkflowSha,
|
|
407
|
+
// A nonce spent by a producer or completeness statement of
|
|
408
|
+
// this release cannot be reused by its entitlement.
|
|
409
|
+
consumedNonces: result.verifiedNonces,
|
|
410
|
+
cosignBinary: options.cosignBinary,
|
|
411
|
+
schemas,
|
|
412
|
+
});
|
|
413
|
+
assertCheckoutsUnchanged();
|
|
414
|
+
return { ...result, entitlementStatement, entitlementDescriptor };
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
// With --json the human text goes to stderr and stdout carries the one document.
|
|
418
|
+
(options.json ? process.stderr : process.stdout).write(
|
|
419
|
+
`verified ${verified.producerStatements.size} image producers and distribution completeness for ` +
|
|
420
|
+
`"${options.distributionId}" at release sequence ` +
|
|
421
|
+
`${verified.completenessStatement.predicate.releaseSequence}\n` +
|
|
422
|
+
`edition read from ${editionsRoot()} at editions checkout commit ${releaseSourceCommit}\n` +
|
|
423
|
+
(verified.entitlementStatement === null
|
|
424
|
+
? ''
|
|
425
|
+
: `verified the delivery entitlement of "${options.customerId}" (${options.mode})\n`),
|
|
426
|
+
);
|
|
427
|
+
if (options.json) {
|
|
428
|
+
process.stdout.write(
|
|
429
|
+
`${JSON.stringify(
|
|
430
|
+
evidenceVerificationDocument({
|
|
431
|
+
distributionId: options.distributionId,
|
|
432
|
+
channel,
|
|
433
|
+
evidenceSet,
|
|
434
|
+
verified,
|
|
435
|
+
entitlementDescriptor: verified.entitlementDescriptor,
|
|
436
|
+
}),
|
|
437
|
+
null,
|
|
438
|
+
2,
|
|
439
|
+
)}\n`,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
} finally {
|
|
443
|
+
trustedGit.dispose();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (isEntryModule(import.meta.url)) {
|
|
448
|
+
await verifyEvidence({
|
|
449
|
+
argv: process.argv.slice(2),
|
|
450
|
+
env: process.env,
|
|
451
|
+
bundlePath: process.argv[1],
|
|
452
|
+
}).catch((error) => {
|
|
453
|
+
// A named refusal (e.g. EditionProtocolMismatchError) is printed with its name.
|
|
454
|
+
const named = error.name && error.name !== 'Error' ? `${error.name}: ` : '';
|
|
455
|
+
console.error(`verify-image-evidence: ${named}${error.message}`);
|
|
456
|
+
process.exitCode = 1;
|
|
457
|
+
});
|
|
458
|
+
}
|