@xemahq/xema 0.58.0 → 0.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/edition.js +1 -1
- package/dist/commands/edition.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +90 -1
- package/dist/edition/verify-image-evidence.bundle.provenance.json +66 -66
- package/edition/README.md +7 -1
- package/edition/assemble-producer-input.mjs +15 -6
- package/edition/biome-content.mjs +25 -16
- package/edition/catalogue-lock-field.mjs +41 -0
- package/edition/customer-edition-verbs.mjs +93 -12
- package/edition/customer-flags.mjs +7 -0
- package/edition/develop-catalogue-free-resolve.mjs +280 -0
- package/edition/develop-edition.mjs +43 -1
- package/edition/develop-follow.mjs +491 -0
- package/edition/develop-record-ancestry.mjs +21 -16
- package/edition/develop-selection-pins.mjs +11 -5
- package/edition/distribution-evidence-set.mjs +2 -0
- package/edition/edition-artifact.mjs +44 -1
- package/edition/edition-constants.mjs +22 -2
- package/edition/edition-contract-parse.mjs +42 -0
- package/edition/edition-contract.json +18 -1
- package/edition/image-evidence.mjs +71 -0
- package/edition/pin-source.mjs +3 -1
- package/edition/record-refused.mjs +3 -2
- package/edition/resolve-customer-edition.mjs +37 -8
- package/edition/resolve-edition.mjs +3 -0
- package/edition/resolve-images.mjs +86 -20
- package/edition/third-party-bundle-registry.mjs +54 -17
- package/package.json +3 -3
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
// verified by cosign (`third-party-source-evidence.mjs`) before it is committed.
|
|
4
4
|
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import { join } from 'node:path';
|
|
6
9
|
|
|
7
10
|
import { certificateConstraintArgs, certificateIdentityArgs } from './cosign-certificate-constraints.mjs';
|
|
8
|
-
import { COSIGN_ENV, decodeExactUtf8, sha256Digest, validatePinnedTrustedRoot } from './image-evidence.mjs';
|
|
11
|
+
import { COSIGN_ENV, classicCosignTrustMaterial, decodeExactUtf8, sha256Digest, validatePinnedTrustedRoot } from './image-evidence.mjs';
|
|
9
12
|
import { splitImageRef } from './registry-credentials.mjs';
|
|
10
13
|
import { BUNDLE_MEDIA_TYPE } from './sigstore-bundle-media-type.mjs';
|
|
11
14
|
import { SLSA_PROVENANCE_V1, decodeJsonStatement, digestOfOciRef } from './third-party-source-evidence.mjs';
|
|
@@ -137,7 +140,13 @@ export async function bundleDigestRef({ clientFor, ociRef }) {
|
|
|
137
140
|
|
|
138
141
|
const tail = (error) => String(error?.stderr ?? error?.message ?? error).trim().split('\n').slice(-4).join(' | ').slice(0, 1024);
|
|
139
142
|
|
|
140
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* The cosign run of a registry verification: the trusted binary under a CLEAN environment (`COSIGN_ENV` and the
|
|
145
|
+
* operator's registry credential, HOME / DOCKER_CONFIG; no `SIGSTORE_*`, `COSIGN_*` or `TUF_*` of the operator is ever
|
|
146
|
+
* inherited). `run` takes `--offline --trusted-root <pinned>`; `runClassic` instead pins cosign's classic trust inputs
|
|
147
|
+
* to the keys derived from the same pinned root (`classicCosignTrustMaterial`), in a private directory removed after the
|
|
148
|
+
* run, with an empty private TUF_ROOT so no live TUF material is ever read.
|
|
149
|
+
*/
|
|
141
150
|
function registryCosign({ trustedCosign, policy, policyRoot, row, ociRef, env }) {
|
|
142
151
|
const root = validatePinnedTrustedRoot(policyRoot, policy);
|
|
143
152
|
const environment = {
|
|
@@ -145,18 +154,41 @@ function registryCosign({ trustedCosign, policy, policyRoot, row, ociRef, env })
|
|
|
145
154
|
...(env.HOME === undefined ? {} : { HOME: env.HOME }),
|
|
146
155
|
...(env.DOCKER_CONFIG === undefined ? {} : { DOCKER_CONFIG: env.DOCKER_CONFIG }),
|
|
147
156
|
};
|
|
157
|
+
const exec = (command, args, runEnv) => {
|
|
158
|
+
try {
|
|
159
|
+
return execFileSync(assertPreparedExecutable(trustedCosign, 'cosign'), [command, ...args], {
|
|
160
|
+
env: runEnv,
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
163
|
+
timeout: REGISTRY_COSIGN_TIMEOUT_MS,
|
|
164
|
+
});
|
|
165
|
+
} catch (error) {
|
|
166
|
+
throw new Error(`cosign ${command} refused ${ociRef} under row "${row.policyId}": ${tail(error)}`);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
148
169
|
return {
|
|
149
170
|
trustArgs: ['--offline', '--trusted-root', root.path],
|
|
150
|
-
run(command, args)
|
|
171
|
+
run: (command, args) => exec(command, args, environment),
|
|
172
|
+
runClassic(command, args) {
|
|
173
|
+
const material = classicCosignTrustMaterial(root.bytes);
|
|
174
|
+
const dir = mkdtempSync(join(tmpdir(), 'xema-cosign-trust-'));
|
|
151
175
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
176
|
+
const file = (name, content) => {
|
|
177
|
+
const path = join(dir, name);
|
|
178
|
+
writeFileSync(path, content, { mode: 0o600, flag: 'wx' });
|
|
179
|
+
return path;
|
|
180
|
+
};
|
|
181
|
+
const tufRoot = join(dir, 'tuf');
|
|
182
|
+
mkdirSync(tufRoot, { mode: 0o700 });
|
|
183
|
+
return exec(command, ['--offline', ...args], {
|
|
184
|
+
...environment,
|
|
185
|
+
SIGSTORE_ROOT_FILE: file('fulcio.pem', material.fulcioPem),
|
|
186
|
+
SIGSTORE_REKOR_PUBLIC_KEY: file('rekor.pub', material.rekorPem),
|
|
187
|
+
SIGSTORE_CT_LOG_PUBLIC_KEY_FILE: file('ctlog.pub', material.ctLogPem),
|
|
188
|
+
TUF_ROOT: tufRoot,
|
|
157
189
|
});
|
|
158
|
-
}
|
|
159
|
-
|
|
190
|
+
} finally {
|
|
191
|
+
rmSync(dir, { recursive: true, force: true });
|
|
160
192
|
}
|
|
161
193
|
},
|
|
162
194
|
};
|
|
@@ -165,16 +197,21 @@ function registryCosign({ trustedCosign, policy, policyRoot, row, ociRef, env })
|
|
|
165
197
|
/**
|
|
166
198
|
* (a) against the registry: keyless `cosign verify` and `cosign verify-attestation --type slsaprovenance1` of the bundle
|
|
167
199
|
* artifact under EXACTLY the signer row's identity and issuer, the row's workflow repository, ref and the stated
|
|
168
|
-
* commit,
|
|
169
|
-
*
|
|
200
|
+
* commit, and the trusted cosign binary, both `--offline`: the tlog entry carried with the signature is verified, the
|
|
201
|
+
* tlog is never ignored. The registry credential is the operator's (HOME / DOCKER_CONFIG).
|
|
202
|
+
*
|
|
203
|
+
* Publishers write the CLASSIC cosign format (`sha256-<d>.sig` / `.att` tags; `cosign sign` / `cosign attest`), not
|
|
204
|
+
* referrer Sigstore bundles. `verify` takes the pinned trusted root as is (`--trusted-root`, classic or bundle form).
|
|
205
|
+
* cosign 2.6 `verify-attestation` takes `--trusted-root` only with `--new-bundle-format`, which finds no classic
|
|
206
|
+
* attestation, so it runs without either, against the Fulcio, Rekor v1 and CT keys derived from the SAME pinned root
|
|
207
|
+
* and passed in cosign's classic trust variables (`registryCosign().runClassic`). A publisher switching to referrer
|
|
208
|
+
* bundles is refused loudly (cosign then requires a trusted root), never verified against anything unpinned.
|
|
170
209
|
*/
|
|
171
210
|
export function verifyBundleOnRegistry({ trustedCosign, policy, policyRoot, row, workflowSha, ociRef, env = process.env }) {
|
|
172
211
|
const cosign = registryCosign({ trustedCosign, policy, policyRoot, row, ociRef, env });
|
|
173
|
-
const
|
|
174
|
-
cosign.run('verify', [...
|
|
175
|
-
|
|
176
|
-
const [offline, trustedRoot, rootPath, ...claims] = common;
|
|
177
|
-
cosign.run('verify-attestation', [offline, '--new-bundle-format', trustedRoot, rootPath, '--type', COSIGN_SLSA_PROVENANCE_ALIAS, ...claims, ociRef]);
|
|
212
|
+
const claims = certificateConstraintArgs(row, workflowSha);
|
|
213
|
+
cosign.run('verify', [...cosign.trustArgs, ...claims, ociRef]);
|
|
214
|
+
cosign.runClassic('verify-attestation', ['--type', COSIGN_SLSA_PROVENANCE_ALIAS, ...claims, ociRef]);
|
|
178
215
|
}
|
|
179
216
|
|
|
180
217
|
const WORKFLOW_SHA = /^[0-9a-f]{40}$/u;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/xema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/xema-dev/xema-cli.git",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"typecheck": "tsc --noEmit"
|
|
101
101
|
},
|
|
102
102
|
"xemaRelease": {
|
|
103
|
-
"sourceDigest": "sha256:
|
|
104
|
-
"sha": "
|
|
103
|
+
"sourceDigest": "sha256:f02866c7d95e9f1f32bbbf2879720aa6c6a4ff30ea4c7ac306047775483f31bd",
|
|
104
|
+
"sha": "36535b2452d6413f38a32eafc2544ba463c41f62"
|
|
105
105
|
}
|
|
106
106
|
}
|