源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { publishDraftProductRelease } from './product-release-authority.mjs';
|
||||
import { createProductReleasePublicationRecord } from './product-release-artifacts.mjs';
|
||||
|
||||
function updateMetadata(version, artifactName, bytes) {
|
||||
const sha512 = createHash('sha512').update(bytes).digest('base64');
|
||||
return [
|
||||
`version: ${version}`,
|
||||
'files:',
|
||||
` - url: ${artifactName}`,
|
||||
` sha512: ${sha512}`,
|
||||
` size: ${bytes.length}`,
|
||||
`path: ${artifactName}`,
|
||||
`sha512: ${sha512}`,
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
test('publication verifies live asset digests before one Stable/Latest mutation', async (t) => {
|
||||
const directory = await mkdtemp(join(tmpdir(), 'maka-publish-authority-'));
|
||||
const recordDirectory = await mkdtemp(join(tmpdir(), 'maka-publish-record-'));
|
||||
t.after(() => rm(directory, { recursive: true, force: true }));
|
||||
t.after(() => rm(recordDirectory, { recursive: true, force: true }));
|
||||
const version = '1.2.3';
|
||||
const macZip = `Maka-${version}-mac-arm64.zip`;
|
||||
const exe = `Maka-${version}-win-x64.exe`;
|
||||
const macBytes = Buffer.from('mac application');
|
||||
const windowsBytes = Buffer.from('windows application');
|
||||
const names = [
|
||||
macZip,
|
||||
`${macZip}.blockmap`,
|
||||
'latest-mac.yml',
|
||||
exe,
|
||||
`${exe}.blockmap`,
|
||||
'latest.yml',
|
||||
];
|
||||
await Promise.all([
|
||||
writeFile(join(directory, macZip), macBytes),
|
||||
writeFile(join(directory, `${macZip}.blockmap`), 'mac blockmap'),
|
||||
writeFile(join(directory, 'latest-mac.yml'), updateMetadata(version, macZip, macBytes)),
|
||||
writeFile(join(directory, exe), windowsBytes),
|
||||
writeFile(join(directory, `${exe}.blockmap`), 'windows blockmap'),
|
||||
writeFile(join(directory, 'latest.yml'), updateMetadata(version, exe, windowsBytes)),
|
||||
]);
|
||||
const sourceCommit = 'a'.repeat(40);
|
||||
const record = await createProductReleasePublicationRecord({
|
||||
artifactDirectory: directory,
|
||||
identity: {
|
||||
version,
|
||||
tag: `v${version}`,
|
||||
sourceReferenceTag: `v${version}-incubating-rc1`,
|
||||
sourceCommit,
|
||||
exe,
|
||||
artifacts: { test: names },
|
||||
},
|
||||
repository: 'apache/maka',
|
||||
runId: '123',
|
||||
runAttempt: '2',
|
||||
});
|
||||
const attestationContents = Buffer.from('sigstore bundle');
|
||||
const attestationBundlePath = join(recordDirectory, 'Maka-1.2.3-attestation.sigstore.json');
|
||||
await writeFile(attestationBundlePath, attestationContents);
|
||||
const attestation = {
|
||||
name: 'Maka-1.2.3-attestation.sigstore.json',
|
||||
size: attestationContents.length,
|
||||
digest: `sha256:${createHash('sha256').update(attestationContents).digest('hex')}`,
|
||||
};
|
||||
const publicationRecordPath = join(recordDirectory, 'product-release.json');
|
||||
await writeFile(publicationRecordPath, `${JSON.stringify(record)}\n`);
|
||||
const calls = [];
|
||||
let latestReads = 0;
|
||||
let attestationUploaded = false;
|
||||
const run = async (command, args) => {
|
||||
calls.push([command, args]);
|
||||
if (command === 'git' && args[0] === 'ls-remote') {
|
||||
return { stdout: `${sourceCommit}\trefs/tags/v1.2.3\n` };
|
||||
}
|
||||
if (command === 'gh' && args[0] === 'release' && args[1] === 'upload') {
|
||||
attestationUploaded = true;
|
||||
return { stdout: '' };
|
||||
}
|
||||
if (command === 'gh' && args[0] === 'release') {
|
||||
return {
|
||||
stdout: JSON.stringify({
|
||||
databaseId: 42,
|
||||
tagName: 'v1.2.3',
|
||||
isDraft: true,
|
||||
isPrerelease: false,
|
||||
assets: [...record.assets, ...(attestationUploaded ? [attestation] : [])],
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (command === 'gh' && args.includes('PATCH')) {
|
||||
return {
|
||||
stdout: JSON.stringify({
|
||||
id: 42,
|
||||
tag_name: 'v1.2.3',
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
assets: [...record.assets, attestation],
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (command === 'gh' && args.includes('repos/apache/maka/releases/latest')) {
|
||||
latestReads += 1;
|
||||
return { stdout: JSON.stringify({ tag_name: latestReads === 1 ? 'v1.2.2' : 'v1.2.3' }) };
|
||||
}
|
||||
return { stdout: '' };
|
||||
};
|
||||
|
||||
const publication = {
|
||||
tag: 'v1.2.3',
|
||||
sourceCommit,
|
||||
repository: 'apache/maka',
|
||||
artifactDirectory: directory,
|
||||
publicationRecordPath,
|
||||
sourceReferenceTag: 'v1.2.3-incubating-rc1',
|
||||
releaseRunId: '123',
|
||||
releaseRunAttempt: '2',
|
||||
attestationBundlePath,
|
||||
pause: async () => {},
|
||||
};
|
||||
await writeFile(join(directory, macZip), 'tampered mac application');
|
||||
await assert.rejects(
|
||||
publishDraftProductRelease({
|
||||
...publication,
|
||||
run: async () => {
|
||||
throw new Error('network must not be reached for invalid local evidence');
|
||||
},
|
||||
}),
|
||||
/do not match the immutable publication record/u,
|
||||
);
|
||||
await writeFile(join(directory, macZip), macBytes);
|
||||
|
||||
await publishDraftProductRelease({
|
||||
...publication,
|
||||
run,
|
||||
});
|
||||
|
||||
const patchCall = calls.find(([, args]) => args.includes('PATCH'));
|
||||
assert.ok(patchCall);
|
||||
assert.ok(patchCall[1].includes('draft=false'));
|
||||
assert.ok(patchCall[1].includes('prerelease=false'));
|
||||
assert.ok(patchCall[1].includes('make_latest=true'));
|
||||
const uploadCall = calls.find(([, args]) => args[0] === 'release' && args[1] === 'upload');
|
||||
assert.ok(uploadCall);
|
||||
assert.ok(calls.indexOf(uploadCall) < calls.indexOf(patchCall));
|
||||
assert.equal(latestReads, 2);
|
||||
});
|
||||
Reference in New Issue
Block a user