@xemahq/edition-tooling 0.0.0-stage → 0.1.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/LICENSE +175 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +47 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/edition.d.ts +4 -0
- package/dist/commands/edition.d.ts.map +1 -0
- package/dist/commands/edition.js +58 -0
- package/dist/commands/edition.js.map +1 -0
- package/dist/edition/verify-image-evidence.bundle.mjs +72104 -0
- package/dist/edition/verify-image-evidence.bundle.provenance.json +4119 -0
- package/dist/lib/exit-codes.d.ts +6 -0
- package/dist/lib/exit-codes.d.ts.map +1 -0
- package/dist/lib/exit-codes.js +21 -0
- package/dist/lib/exit-codes.js.map +1 -0
- package/dist/runtime/edition/run-entry.d.ts +10 -0
- package/dist/runtime/edition/run-entry.d.ts.map +1 -0
- package/dist/runtime/edition/run-entry.js +49 -0
- package/dist/runtime/edition/run-entry.js.map +1 -0
- package/dist/runtime/edition/verb-map.d.ts +70 -0
- package/dist/runtime/edition/verb-map.d.ts.map +1 -0
- package/dist/runtime/edition/verb-map.js +166 -0
- package/dist/runtime/edition/verb-map.js.map +1 -0
- package/edition/README.md +291 -0
- package/edition/adapter-bundled-versions.mjs +128 -0
- package/edition/adapter-host-versions.mjs +76 -0
- package/edition/assemble-producer-input.mjs +1423 -0
- package/edition/assemble-third-party-input.mjs +348 -0
- package/edition/authored-source-check.mjs +141 -0
- package/edition/authority-binding.mjs +23 -0
- package/edition/authorization-base.mjs +357 -0
- package/edition/biome-content.mjs +327 -0
- package/edition/build-biome-index.mjs +180 -0
- package/edition/build-ledger-completeness.mjs +67 -0
- package/edition/build-ledger.mjs +107 -0
- package/edition/build-platform-service-index.mjs +292 -0
- package/edition/build-runtime-image-source-index.mjs +1197 -0
- package/edition/built-verifier.mjs +55 -0
- package/edition/candidate-edition.mjs +735 -0
- package/edition/canonical-evidence-json.mjs +82 -0
- package/edition/canonical-repository-url.mjs +61 -0
- package/edition/catalogue-edition.mjs +49 -0
- package/edition/catalogue-evidence.mjs +228 -0
- package/edition/catalogue-fetch.mjs +124 -0
- package/edition/catalogue-lock-field.mjs +41 -0
- package/edition/catalogue-pin.mjs +71 -0
- package/edition/catalogue.mjs +237 -0
- package/edition/chart-set-source-commit.mjs +97 -0
- package/edition/check-committed-image-evidence.mjs +412 -0
- package/edition/checkout-indexed-source-repositories.mjs +283 -0
- package/edition/collected-refusals.mjs +59 -0
- package/edition/comment-blanker.mjs +75 -0
- package/edition/connector-source-authority.mjs +60 -0
- package/edition/contract-authority.mjs +122 -0
- package/edition/cosign-certificate-constraints.mjs +50 -0
- package/edition/cross-repo-build-inputs.mjs +158 -0
- package/edition/customer-definition.mjs +72 -0
- package/edition/customer-edition-verbs.mjs +610 -0
- package/edition/customer-flags.mjs +15 -0
- package/edition/definition-checkout.mjs +96 -0
- package/edition/develop-build-record.mjs +122 -0
- package/edition/develop-catalogue-free-resolve.mjs +282 -0
- package/edition/develop-catalogue-free-vendor.mjs +118 -0
- package/edition/develop-channel-evidence.mjs +23 -0
- package/edition/develop-channel-record.mjs +49 -0
- package/edition/develop-channel-satellites.mjs +65 -0
- package/edition/develop-chart-roster.mjs +96 -0
- package/edition/develop-chart-set.mjs +147 -0
- package/edition/develop-current-resolution.mjs +29 -0
- package/edition/develop-edition-invariants.mjs +142 -0
- package/edition/develop-edition-resolve.mjs +250 -0
- package/edition/develop-edition.mjs +332 -0
- package/edition/develop-engine-image.mjs +97 -0
- package/edition/develop-freeze.mjs +304 -0
- package/edition/develop-git-bundles.mjs +296 -0
- package/edition/develop-graph.mjs +34 -0
- package/edition/develop-image-coordinate.mjs +85 -0
- package/edition/develop-image-probe.mjs +122 -0
- package/edition/develop-image-record.mjs +260 -0
- package/edition/develop-resolve-report.mjs +159 -0
- package/edition/develop-select-records.mjs +90 -0
- package/edition/develop-selection-line.mjs +29 -0
- package/edition/develop-selection-pins.mjs +36 -0
- package/edition/develop-snapshot-inputs.mjs +112 -0
- package/edition/develop-store-root.mjs +20 -0
- package/edition/dispatched-runtime-components.mjs +172 -0
- package/edition/dist-check.mjs +1174 -0
- package/edition/distribution-evidence-set.mjs +1064 -0
- package/edition/distribution-paths.mjs +191 -0
- package/edition/edition-artifact.mjs +529 -0
- package/edition/edition-biome-manifests.mjs +85 -0
- package/edition/edition-ceiling.mjs +182 -0
- package/edition/edition-channel.mjs +27 -0
- package/edition/edition-chart-set.mjs +104 -0
- package/edition/edition-config.mjs +374 -0
- package/edition/edition-constants.mjs +110 -0
- package/edition/edition-contract-parse.mjs +179 -0
- package/edition/edition-contract.json +100 -0
- package/edition/edition-image-seal.mjs +115 -0
- package/edition/edition-kind.mjs +10 -0
- package/edition/edition-list.mjs +35 -0
- package/edition/edition-locks.mjs +78 -0
- package/edition/edition-pin-verbs.mjs +183 -0
- package/edition/edition-pin.mjs +17 -0
- package/edition/edition-pinned-inputs.mjs +116 -0
- package/edition/edition-protocol.mjs +285 -0
- package/edition/edition-publisher.mjs +21 -0
- package/edition/edition-seal.mjs +257 -0
- package/edition/edition-source-pins.mjs +211 -0
- package/edition/edition-unpin.mjs +16 -0
- package/edition/edition-workspace.mjs +205 -0
- package/edition/evidence-checkout.mjs +439 -0
- package/edition/evidence-grammars.mjs +18 -0
- package/edition/evidence-input-mode.mjs +136 -0
- package/edition/evidence-trust-pin.mjs +392 -0
- package/edition/evidence-trust-policy.mjs +374 -0
- package/edition/external-source-walk.mjs +486 -0
- package/edition/first-party-source-ids.mjs +62 -0
- package/edition/fleet-coordinate-finality.mjs +363 -0
- package/edition/fulcio-certificate-extensions.mjs +278 -0
- package/edition/git-json.mjs +54 -0
- package/edition/git-source-auth.mjs +131 -0
- package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
- package/edition/image-evidence-replay-state.mjs +242 -0
- package/edition/image-evidence.mjs +1328 -0
- package/edition/image-lock-service-rows.mjs +26 -0
- package/edition/image-subject-identity.mjs +19 -0
- package/edition/images-from-record.mjs +216 -0
- package/edition/indexes-at-pins.mjs +13 -0
- package/edition/infra-images-lock.mjs +105 -0
- package/edition/kernel-capability-domain.mjs +30 -0
- package/edition/kernel-contract-authority.mjs +526 -0
- package/edition/kernel-contract-source-path-set.mjs +96 -0
- package/edition/kernel-package.mjs +237 -0
- package/edition/kernel-pin-source.mjs +63 -0
- package/edition/kernel-protocol-choice.mjs +239 -0
- package/edition/kernel-signer-kind.mjs +23 -0
- package/edition/lib.mjs +3322 -0
- package/edition/locked-bundle-signer.mjs +297 -0
- package/edition/map-with-concurrency.mjs +19 -0
- package/edition/no-follow-file.mjs +94 -0
- package/edition/oci-evidence-verbs.mjs +421 -0
- package/edition/oci-image-source-evidence.mjs +377 -0
- package/edition/operation-grant-admission.mjs +285 -0
- package/edition/operation-requirements.mjs +282 -0
- package/edition/oras-process.mjs +35 -0
- package/edition/oras.mjs +664 -0
- package/edition/pin-source.mjs +242 -0
- package/edition/pinned-cosign.mjs +86 -0
- package/edition/plan-image-evidence-refresh.mjs +428 -0
- package/edition/platform-lock-readability.mjs +903 -0
- package/edition/platform-service-class.mjs +85 -0
- package/edition/platform-service-ownership.mjs +91 -0
- package/edition/platform-service-source-authority.mjs +105 -0
- package/edition/positive-closure.mjs +162 -0
- package/edition/previous-image-lock.mjs +25 -0
- package/edition/produce-distribution-completeness-evidence.mjs +317 -0
- package/edition/produce-edition-catalogue-evidence.mjs +252 -0
- package/edition/produce-image-delivery-entitlement-evidence.mjs +302 -0
- package/edition/produce-image-source-evidence.mjs +788 -0
- package/edition/producer-evidence-bundle.mjs +201 -0
- package/edition/producer-evidence-index-file.mjs +4 -0
- package/edition/record-refused.mjs +11 -0
- package/edition/registry-client-factory.mjs +21 -0
- package/edition/registry-credentials.mjs +225 -0
- package/edition/release-catalogue.mjs +48 -0
- package/edition/release-channel-guard.mjs +108 -0
- package/edition/release-edition-artifact.mjs +152 -0
- package/edition/release-edition.mjs +54 -0
- package/edition/remedy-command.mjs +89 -0
- package/edition/repin-evidence-trust.mjs +109 -0
- package/edition/required-resource-provider.mjs +58 -0
- package/edition/resolve-chart-set.mjs +376 -0
- package/edition/resolve-customer-edition.mjs +374 -0
- package/edition/resolve-distribution.mjs +2642 -0
- package/edition/resolve-edition.mjs +192 -0
- package/edition/resolve-images.mjs +2344 -0
- package/edition/resolve-infra-images.mjs +88 -0
- package/edition/seal-image-digests.mjs +954 -0
- package/edition/sealed-distribution-lock.mjs +22 -0
- package/edition/sealed-evidence-accept.mjs +82 -0
- package/edition/shallow-checkout.mjs +18 -0
- package/edition/sigstore-bundle-media-type.mjs +4 -0
- package/edition/source-authority.mjs +713 -0
- package/edition/source-closure.mjs +115 -0
- package/edition/store-publisher-trust.mjs +40 -0
- package/edition/strict-json.mjs +121 -0
- package/edition/string-order.mjs +5 -0
- package/edition/third-party-admission.mjs +118 -0
- package/edition/third-party-bundle-registry.mjs +349 -0
- package/edition/third-party-certificate-extensions.mjs +58 -0
- package/edition/third-party-image-pins.mjs +41 -0
- package/edition/third-party-pins-trusted.mjs +24 -0
- package/edition/third-party-producer-evidence.mjs +76 -0
- package/edition/third-party-source-check.mjs +28 -0
- package/edition/third-party-source-context.mjs +41 -0
- package/edition/third-party-source-evidence.mjs +326 -0
- package/edition/third-party-source-flags.mjs +148 -0
- package/edition/third-party-source-pins.mjs +270 -0
- package/edition/third-party-source-uniqueness.mjs +54 -0
- package/edition/third-party-source-walk.mjs +118 -0
- package/edition/third-party-source-writer.mjs +228 -0
- package/edition/third-party-subjects.mjs +128 -0
- package/edition/trust-policy-arming-verb.mjs +60 -0
- package/edition/trust-policy-arming.mjs +144 -0
- package/edition/trust-policy-image-ownership.mjs +85 -0
- package/edition/trusted-executable.mjs +166 -0
- package/edition/verifier-binding.mjs +45 -0
- package/edition/verifier-digest-pin.mjs +17 -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-catalogue-evidence.mjs +247 -0
- package/edition/verify-image-evidence.mjs +493 -0
- package/edition/verify-image-platforms.mjs +523 -0
- package/edition/why.mjs +56 -0
- package/edition/workspace-sections.mjs +287 -0
- package/package.json +83 -4
- package/README.md +0 -3
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// pin-source — where the resolver reads each first-party repository's commit.
|
|
2
|
+
//
|
|
3
|
+
// PUBLISHED (the default, and the only behaviour before this module): the
|
|
4
|
+
// committed `runtime-image-source-index.json#repositoryPins`, untouched.
|
|
5
|
+
//
|
|
6
|
+
// SNAPSHOT: one frozen map of pins, replacing the published ones for ONE
|
|
7
|
+
// in-memory resolution (the develop pipeline resolves a whole edition from it).
|
|
8
|
+
// Nothing is written back to the index, and there is NO fallback: a repo in the
|
|
9
|
+
// graph that the snapshot does not name is refused, never read from the
|
|
10
|
+
// published pin. This module owns the SHAPE of that map: a flat object whose
|
|
11
|
+
// keys live in six closed namespaces and whose values are exact 40-hex SHAs.
|
|
12
|
+
//
|
|
13
|
+
// repos/<x> | submodules/<x> every repository of the develop graph (the
|
|
14
|
+
// data root's firstPartySources, develop-graph.mjs),
|
|
15
|
+
// no more, no fewer
|
|
16
|
+
// <pinRoles.data> the commit of the data root checkout (editions and indices)
|
|
17
|
+
// <pinRoles.kernel> the commit of the kernel the authorization lock is sealed
|
|
18
|
+
// with (may match repos/<x>, but is NOT in the graph)
|
|
19
|
+
// <pinRoles.deploy> the commit of the chart tree the chart-set lock digests
|
|
20
|
+
// editions-root the commit of the editions root checkout; present
|
|
21
|
+
// only when that root is NOT this repository
|
|
22
|
+
// sources/<name> one per `sources[]` entry declared in the chain
|
|
23
|
+
|
|
24
|
+
import { developGraphMembers } from './develop-graph.mjs';
|
|
25
|
+
import { workspaceSection } from './edition-config.mjs';
|
|
26
|
+
import { VENDOR_MEMBER_PIN_PATTERN } from './edition-constants.mjs';
|
|
27
|
+
import { compareStrings } from './lib.mjs';
|
|
28
|
+
import { PinRole } from './workspace-sections.mjs';
|
|
29
|
+
|
|
30
|
+
export const PinSourceKind = Object.freeze({
|
|
31
|
+
Published: 'published',
|
|
32
|
+
Snapshot: 'snapshot',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
36
|
+
|
|
37
|
+
export const SnapshotPinNamespace = Object.freeze({
|
|
38
|
+
Graph: 'graph',
|
|
39
|
+
Distributions: 'distributions',
|
|
40
|
+
Kernel: 'kernel',
|
|
41
|
+
ChartSource: 'chart-source',
|
|
42
|
+
EditionsRoot: 'editions-root',
|
|
43
|
+
Source: 'source',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// The three checked-out roles a snapshot pins (data, kernel, deploy) are a closed
|
|
47
|
+
// set the library defines; the repository key each is pinned under is the data
|
|
48
|
+
// root's (`pinRoles` in edition-workspace.json), so a repository rename never
|
|
49
|
+
// needs a library release. `editions-root` is not a repository key but a
|
|
50
|
+
// protocol key of its own.
|
|
51
|
+
export const EDITIONS_ROOT_PIN_KEY = 'editions-root';
|
|
52
|
+
|
|
53
|
+
const pinKeyOf = (role) => workspaceSection('pinRoles', 'a snapshot pin')[role];
|
|
54
|
+
|
|
55
|
+
export const dataPinKey = () => pinKeyOf(PinRole.Data);
|
|
56
|
+
export const kernelPinKey = () => pinKeyOf(PinRole.Kernel);
|
|
57
|
+
export const deployPinKey = () => pinKeyOf(PinRole.Deploy);
|
|
58
|
+
const SOURCE_PIN_PREFIX = 'sources/';
|
|
59
|
+
// A graph pin key is a vendor member pin key: the pattern is the edition contract's (customerForms.vendorMemberPinPattern).
|
|
60
|
+
const GRAPH_PIN_PATTERN = VENDOR_MEMBER_PIN_PATTERN;
|
|
61
|
+
|
|
62
|
+
/** The snapshot key of one declared `sources[]` entry. */
|
|
63
|
+
export function sourcePinKey(name) {
|
|
64
|
+
return `${SOURCE_PIN_PREFIX}${name}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The `sources[].name` a Source-namespace key pins. */
|
|
68
|
+
export function sourceNameOfPinKey(key) {
|
|
69
|
+
return key.slice(SOURCE_PIN_PREFIX.length);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Whether `key` is the snapshot key of a declared source (`sources/<name>`); pure, it reads no workspace section. */
|
|
73
|
+
export function isSourcePinKey(key) {
|
|
74
|
+
return key.startsWith(SOURCE_PIN_PREFIX) && key.length > SOURCE_PIN_PREFIX.length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Which closed namespace a snapshot key belongs to; refuses a key in none. */
|
|
78
|
+
export function snapshotPinNamespace(key) {
|
|
79
|
+
if (key === dataPinKey()) {return SnapshotPinNamespace.Distributions;}
|
|
80
|
+
if (key === kernelPinKey()) {return SnapshotPinNamespace.Kernel;}
|
|
81
|
+
if (key === deployPinKey()) {return SnapshotPinNamespace.ChartSource;}
|
|
82
|
+
if (key === EDITIONS_ROOT_PIN_KEY) {return SnapshotPinNamespace.EditionsRoot;}
|
|
83
|
+
if (isSourcePinKey(key)) {
|
|
84
|
+
return SnapshotPinNamespace.Source;
|
|
85
|
+
}
|
|
86
|
+
if (GRAPH_PIN_PATTERN.test(key)) {return SnapshotPinNamespace.Graph;}
|
|
87
|
+
throw new Error(
|
|
88
|
+
`snapshot pin key "${key}" is in no namespace; expected a graph repository (repos/<x> or ` +
|
|
89
|
+
`submodules/<x>), "${dataPinKey()}", "${kernelPinKey()}", "${deployPinKey()}", "${EDITIONS_ROOT_PIN_KEY}" or "${SOURCE_PIN_PREFIX}<name>"`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function assertPinsObject(pins) {
|
|
94
|
+
if (pins === null || typeof pins !== 'object' || Array.isArray(pins)) {
|
|
95
|
+
throw new Error('snapshot pins must be an object mapping key -> 40-hex commit');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function assertExactShas(pins) {
|
|
100
|
+
for (const [key, sha] of Object.entries(pins)) {
|
|
101
|
+
if (typeof sha !== 'string' || !SHA_PATTERN.test(sha)) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`snapshot pin for "${key}" is not a 40-hex commit SHA: ${JSON.stringify(sha)}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function graphPinsOf(pins) {
|
|
110
|
+
return Object.fromEntries(
|
|
111
|
+
Object.entries(pins).filter(([key]) => snapshotPinNamespace(key) === SnapshotPinNamespace.Graph),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Validate a COMPLETE snapshot against the develop graph and the composed
|
|
117
|
+
* chain. `declaredSourceNames` are the `sources[].name` entries the chain
|
|
118
|
+
* declares; `editionsRootExternal` is whether the editions root is another
|
|
119
|
+
* repository. Every refusal names the offending key; nothing falls back to a
|
|
120
|
+
* published pin.
|
|
121
|
+
*/
|
|
122
|
+
export function validateSnapshotPins(
|
|
123
|
+
pins,
|
|
124
|
+
{ declaredSourceNames, editionsRootExternal },
|
|
125
|
+
) {
|
|
126
|
+
assertPinsObject(pins);
|
|
127
|
+
assertExactShas(pins);
|
|
128
|
+
for (const key of Object.keys(pins)) {snapshotPinNamespace(key);}
|
|
129
|
+
checkGraphPins(developGraphMembers(), pins);
|
|
130
|
+
for (const [key, what] of [
|
|
131
|
+
[dataPinKey(), 'the commit of the data root checkout'],
|
|
132
|
+
[kernelPinKey(), 'the commit of the kernel the authorization lock is sealed with'],
|
|
133
|
+
[deployPinKey(), 'the commit of the chart tree the chart-set lock digests'],
|
|
134
|
+
]) {
|
|
135
|
+
if (!(key in pins)) {throw new Error(`snapshot is missing "${key}", ${what}`);}
|
|
136
|
+
}
|
|
137
|
+
if (editionsRootExternal && !(EDITIONS_ROOT_PIN_KEY in pins)) {
|
|
138
|
+
throw new Error(`snapshot is missing "${EDITIONS_ROOT_PIN_KEY}": the editions root is not this repository`);
|
|
139
|
+
}
|
|
140
|
+
if (!editionsRootExternal && EDITIONS_ROOT_PIN_KEY in pins) {
|
|
141
|
+
throw new Error(`snapshot names "${EDITIONS_ROOT_PIN_KEY}", but the editions root is this repository`);
|
|
142
|
+
}
|
|
143
|
+
const declared = new Set(declaredSourceNames);
|
|
144
|
+
for (const name of declared) {
|
|
145
|
+
if (!(sourcePinKey(name) in pins)) {
|
|
146
|
+
throw new Error(`snapshot is missing "${sourcePinKey(name)}": the chain declares source "${name}"`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const key of Object.keys(pins)) {
|
|
150
|
+
if (snapshotPinNamespace(key) === SnapshotPinNamespace.Source && !declared.has(sourceNameOfPinKey(key))) {
|
|
151
|
+
throw new Error(`snapshot names "${key}", but the chain declares no source "${sourceNameOfPinKey(key)}"`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The pins sorted by key: the one canonical order, shared by the hash and the artifact. */
|
|
157
|
+
export function sortedSnapshotPins(pins) {
|
|
158
|
+
assertPinsObject(pins);
|
|
159
|
+
return Object.fromEntries(Object.entries(pins).sort(([a], [b]) => compareStrings(a, b)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** A declared source with its ref replaced by the snapshot pin; identity under a published pin source. */
|
|
163
|
+
export function pinnedSource(source, pinSource = PUBLISHED_PIN_SOURCE) {
|
|
164
|
+
if (pinSource?.kind !== PinSourceKind.Snapshot) {return source;}
|
|
165
|
+
const key = sourcePinKey(source.name);
|
|
166
|
+
const ref = pinSource.pins[key];
|
|
167
|
+
if (ref === undefined) {
|
|
168
|
+
throw new Error(`snapshot has no pin "${key}" for the declared source "${source.name}"; there is no fallback to its declared ref`);
|
|
169
|
+
}
|
|
170
|
+
return { ...source, ref };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const PUBLISHED_PIN_SOURCE = Object.freeze({ kind: PinSourceKind.Published });
|
|
174
|
+
|
|
175
|
+
/** @param {Record<string,string>} pins repo -> 40-hex commit */
|
|
176
|
+
export function snapshotPinSource(pins) {
|
|
177
|
+
return Object.freeze({ kind: PinSourceKind.Snapshot, pins });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Apply a pin source to a parsed runtime image source index. Published returns
|
|
182
|
+
* the SAME object (inert, byte for byte). Snapshot returns a copy whose
|
|
183
|
+
* `repositoryPins` are the DEVELOP GRAPH (develop-graph.mjs) at the snapshot
|
|
184
|
+
* SHAs: the committed index's own pins are not read, so a first-party
|
|
185
|
+
* repository no release pins yet is in it, and a pinned one that is not
|
|
186
|
+
* first-party is not.
|
|
187
|
+
*/
|
|
188
|
+
export function applyPinSource(runtimeSourceIndex, pinSource = PUBLISHED_PIN_SOURCE) {
|
|
189
|
+
switch (pinSource?.kind) {
|
|
190
|
+
case PinSourceKind.Published:
|
|
191
|
+
return runtimeSourceIndex;
|
|
192
|
+
case PinSourceKind.Snapshot:
|
|
193
|
+
return applySnapshot(runtimeSourceIndex, pinSource.pins);
|
|
194
|
+
default:
|
|
195
|
+
throw new Error(
|
|
196
|
+
`unknown pin source kind ${JSON.stringify(pinSource?.kind)}; expected ` +
|
|
197
|
+
`${Object.values(PinSourceKind).join(' | ')}`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function checkGraphPins(graph, pins) {
|
|
203
|
+
const graphRepos = new Set(graph.map((pin) => pin.repo));
|
|
204
|
+
for (const repo of Object.keys(graphPinsOf(pins))) {
|
|
205
|
+
if (!graphRepos.has(repo)) {
|
|
206
|
+
throw new Error(`snapshot names "${repo}", which is not a repository in the graph`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const missing = [...graphRepos].filter((repo) => !(repo in pins));
|
|
210
|
+
if (missing.length > 0) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`snapshot is missing ${missing.length} repositor${missing.length === 1 ? 'y' : 'ies'} ` +
|
|
213
|
+
`in the graph: ${missing.join(', ')}. There is no fallback to the published pin.`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function applySnapshot(runtimeSourceIndex, pins) {
|
|
219
|
+
assertPinsObject(pins);
|
|
220
|
+
assertExactShas(pins);
|
|
221
|
+
for (const key of Object.keys(pins)) {snapshotPinNamespace(key);}
|
|
222
|
+
const graph = developGraphMembers();
|
|
223
|
+
checkGraphPins(graph, pins);
|
|
224
|
+
return {
|
|
225
|
+
...runtimeSourceIndex,
|
|
226
|
+
repositoryPins: graph.map(({ repo, repositoryUrl }) => ({
|
|
227
|
+
repo,
|
|
228
|
+
repositoryUrl,
|
|
229
|
+
// A develop snapshot pins every repository at its develop head, so that one
|
|
230
|
+
// SHA is both the built commit (image axis, `sourceCommit`) and the source
|
|
231
|
+
// tip (`vendoredSourceCommit`): the image pin lagging the source tip cannot
|
|
232
|
+
// arise, and the snapshot is "one frozen snapshot of pins and nothing else".
|
|
233
|
+
// Fusing both axes is the rule `effectiveRepositoryPins` and
|
|
234
|
+
// `createExactCommitSourceAuthority` already apply for an exact commit.
|
|
235
|
+
// The COMMITTED index keeps the axes apart (release channel): see
|
|
236
|
+
// `source-authority.mjs#publishedSourceTip` and
|
|
237
|
+
// `build-runtime-image-source-index.mjs#repositoryIdentity`.
|
|
238
|
+
sourceCommit: pins[repo],
|
|
239
|
+
vendoredSourceCommit: pins[repo],
|
|
240
|
+
})),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// pinned-cosign — the one way this CLI runs `cosign verify`: the caller's absolute cosign, held to the
|
|
2
|
+
// cosign digest the data root's pinned evidence trust policy names; the policy's pinned Sigstore trusted
|
|
3
|
+
// root written to a private scratch file for `--offline --trusted-root`; and cosign's registry credential
|
|
4
|
+
// taken from the SAME declaration oras uses, in its environment, never argv. Every injection exists for
|
|
5
|
+
// tests; the defaults are the real readers. Used by the official lane only (the edition seal, edition-seal.mjs, and
|
|
6
|
+
// the catalogue-free resolve's engine proof, develop-engine-image.mjs), so there is one copy of this trust path. The
|
|
7
|
+
// develop dev lane runs no cosign.
|
|
8
|
+
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
10
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { promisify } from 'node:util';
|
|
14
|
+
|
|
15
|
+
import { loadDeclaredPinnedPolicy } from './evidence-trust-policy.mjs';
|
|
16
|
+
import { COSIGN_ENV, prepareTrustedCosignBinary, validatePinnedTrustedRoot } from './image-evidence.mjs';
|
|
17
|
+
import { registryClientFactory } from './registry-client-factory.mjs';
|
|
18
|
+
import { resolveRegistryCredential } from './registry-credentials.mjs';
|
|
19
|
+
import { assertPreparedExecutable } from './trusted-executable.mjs';
|
|
20
|
+
|
|
21
|
+
const execFileAsync = promisify(execFile);
|
|
22
|
+
// One external call to cosign; it reads a registry. A run that exceeds it fails naming the subject, never a verdict.
|
|
23
|
+
const COSIGN_CALL_TIMEOUT_MS = 60_000;
|
|
24
|
+
const COSIGN_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
/** Run cosign once; resolves `{code, stdout, stderr}` for a cosign exit, throws when cosign could not run at all. */
|
|
27
|
+
function defaultRunCosign(cosignPath, args, env) {
|
|
28
|
+
return execFileAsync(cosignPath, args, { env, timeout: COSIGN_CALL_TIMEOUT_MS, maxBuffer: COSIGN_MAX_OUTPUT_BYTES }).then(
|
|
29
|
+
({ stdout }) => ({ code: 0, stdout, stderr: '' }),
|
|
30
|
+
(error) => {
|
|
31
|
+
if (typeof error.code === 'number') {return { code: error.code, stdout: error.stdout ?? '', stderr: error.stderr ?? '' };}
|
|
32
|
+
throw error;
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The username and password of a resolved basic-auth registry credential. */
|
|
38
|
+
function basicAuthParts(credential) {
|
|
39
|
+
const decoded = Buffer.from(credential.basicAuth, 'base64').toString('utf8');
|
|
40
|
+
const colon = decoded.indexOf(':');
|
|
41
|
+
return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run `use({ policy, runCosign, cosignEnvFor, clientFor, trustedRootPath, ...prepared })` with a pinned cosign.
|
|
46
|
+
* `afterPolicy()` runs after the policy is loaded and before cosign is prepared; what it returns is spread into
|
|
47
|
+
* the argument of `use`. The scratch trusted root and the prepared cosign are disposed whatever `use` does.
|
|
48
|
+
*/
|
|
49
|
+
export async function withPinnedCosign({
|
|
50
|
+
cosignBinary,
|
|
51
|
+
env,
|
|
52
|
+
loadPolicy = loadDeclaredPinnedPolicy,
|
|
53
|
+
prepareCosign = prepareTrustedCosignBinary,
|
|
54
|
+
trustedRootBytes = (root, pinnedPolicy) => validatePinnedTrustedRoot(root, pinnedPolicy).bytes,
|
|
55
|
+
cosignPathOf = (handle) => assertPreparedExecutable(handle, 'cosign'),
|
|
56
|
+
runCosign: injectedRunCosign,
|
|
57
|
+
clientsOverride,
|
|
58
|
+
afterPolicy = async () => ({}),
|
|
59
|
+
}, use) {
|
|
60
|
+
const { policy, dataRoot: policyRoot } = await loadPolicy();
|
|
61
|
+
const prepared = await afterPolicy();
|
|
62
|
+
const trustedCosign = prepareCosign({ cosignBinary, policy });
|
|
63
|
+
const cosignPath = cosignPathOf(trustedCosign);
|
|
64
|
+
const runCosign = injectedRunCosign ?? ((args, cosignEnv) => defaultRunCosign(cosignPath, args, cosignEnv));
|
|
65
|
+
const scratch = mkdtempSync(join(tmpdir(), 'xema-pinned-cosign-'));
|
|
66
|
+
try {
|
|
67
|
+
const trustedRootPath = join(scratch, 'trusted-root.json');
|
|
68
|
+
writeFileSync(trustedRootPath, trustedRootBytes(policyRoot, policy), { flag: 'wx', mode: 0o600 });
|
|
69
|
+
const { clientFor, declarations } = registryClientFactory({ clientsOverride, env });
|
|
70
|
+
// cosign gets the SAME declared credential oras uses, in its environment, never argv.
|
|
71
|
+
const cosignEnvFor = (authority) => {
|
|
72
|
+
if (clientsOverride !== undefined) {return {};}
|
|
73
|
+
const credential = resolveRegistryCredential({ authority, declarations, env });
|
|
74
|
+
if (credential.access === 'anonymous') {return {};}
|
|
75
|
+
const { username, password } = basicAuthParts(credential);
|
|
76
|
+
return { COSIGN_REGISTRY_USERNAME: username, COSIGN_REGISTRY_PASSWORD: password };
|
|
77
|
+
};
|
|
78
|
+
return await use({ ...prepared, policy, runCosign, cosignEnvFor, clientFor, trustedRootPath });
|
|
79
|
+
} finally {
|
|
80
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
81
|
+
trustedCosign.dispose();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The environment cosign runs with for one authority: the pinned base plus its declared credential. */
|
|
86
|
+
export const cosignEnvironment = (cosignEnvFor, authority) => ({ ...COSIGN_ENV, ...cosignEnvFor(authority) });
|