mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[ci] Dont sign builds originating from anything other than facebook/react (#32738)
We now generate attestations in `process_artifacts_combined` so we can verify the provenance of the build later in other workflows. However, this requires `write` permissions for `id-token` and `attestations` so PRs from forks cannot generate this attestation. To get around this, I added a `--no-verify` flag to scripts/release/download-experimental-build.js. This flag is only passed in `runtime_build_and_test.yml` for the sizebot job, since 1) the workflow runs in the `pull_request` trigger which has read-only permissions, and 2) the downloaded artifact is only used for sizebot calculation, and not actually used. The flag is explicitly not passed in `runtime_commit_artifacts.yml` since there we actually use the artifact internally. This is fine as once a PR lands on main, it will then run the build on that new commit and generate an attestation. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32738). * #32739 * __->__ #32738
This commit is contained in:
parent
dc9b74647e
commit
44c4693539
21
.github/workflows/runtime_build_and_test.yml
vendored
21
.github/workflows/runtime_build_and_test.yml
vendored
|
|
@ -481,6 +481,13 @@ jobs:
|
|||
./build2.tgz
|
||||
if-no-files-found: error
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
# We don't verify builds generated from pull requests not originating from facebook/react.
|
||||
# However, if the PR lands, the run on `main` will generate the attestation which can then
|
||||
# be used to download a build via scripts/release/download-experimental-build.js.
|
||||
#
|
||||
# Note that this means that scripts/release/download-experimental-build.js must be run with
|
||||
# --no-verify when downloading a build from a fork.
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
with:
|
||||
subject-name: artifacts_combined.zip
|
||||
subject-digest: sha256:${{ steps.upload_artifacts_combined.outputs.artifact-digest }}
|
||||
|
|
@ -806,14 +813,18 @@ jobs:
|
|||
- run: yarn --cwd scripts/release install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Download artifacts for base revision
|
||||
# The build could have been generated from a fork, so we must download the build without
|
||||
# any verification. This is safe since we only use this for sizebot calculation and the
|
||||
# unverified artifact is not used. Additionally this workflow runs in the pull_request
|
||||
# trigger so only restricted permissions are available.
|
||||
run: |
|
||||
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
|
||||
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }}) ${{ (github.event.pull_request.head.repo.full_name != github.repository && '--no-verify') || ''}}
|
||||
mv ./build ./base-build
|
||||
# TODO: The `download-experimental-build` script copies the npm
|
||||
# packages into the `node_modules` directory. This is a historical
|
||||
# quirk of how the release script works. Let's pretend they
|
||||
# don't exist.
|
||||
- name: Delete extraneous files
|
||||
# TODO: The `download-experimental-build` script copies the npm
|
||||
# packages into the `node_modules` directory. This is a historical
|
||||
# quirk of how the release script works. Let's pretend they
|
||||
# don't exist.
|
||||
run: rm -rf ./base-build/node_modules
|
||||
- name: Display structure of base-build from origin/main
|
||||
run: ls -R base-build
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ const argv = yargs.wrap(yargs.terminalWidth()).options({
|
|||
demandOption: true,
|
||||
type: 'string',
|
||||
},
|
||||
'no-verify': {
|
||||
describe: 'Skip verification',
|
||||
requiresArg: false,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
}).argv;
|
||||
|
||||
function printSummary(commit) {
|
||||
|
|
@ -48,8 +54,13 @@ function printSummary(commit) {
|
|||
}
|
||||
|
||||
const main = async () => {
|
||||
const {commit, releaseChannel, noVerify} = argv;
|
||||
try {
|
||||
await downloadBuildArtifacts(argv.commit, argv.releaseChannel);
|
||||
await downloadBuildArtifacts({
|
||||
commit,
|
||||
releaseChannel,
|
||||
noVerify,
|
||||
});
|
||||
printSummary(argv.commit);
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ const run = async () => {
|
|||
const params = await parseParams();
|
||||
params.cwd = join(__dirname, '..', '..');
|
||||
|
||||
await downloadBuildArtifacts(
|
||||
params.commit,
|
||||
params.releaseChannel ?? process.env.RELEASE_CHANNEL
|
||||
);
|
||||
await downloadBuildArtifacts({
|
||||
commit: params.commit,
|
||||
releaseChannel: params.releaseChannel ?? process.env.RELEASE_CHANNEL,
|
||||
});
|
||||
|
||||
if (!params.skipTests) {
|
||||
await testPackagingFixture(params);
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ async function getArtifact(workflowRunId, artifactName) {
|
|||
return artifact;
|
||||
}
|
||||
|
||||
async function processArtifact(artifact, commit, releaseChannel) {
|
||||
async function processArtifact(artifact, opts) {
|
||||
// Download and extract artifact
|
||||
const cwd = join(__dirname, '..', '..', '..');
|
||||
const tmpDir = mkdtempSync(join(os.tmpdir(), 'react_'));
|
||||
|
|
@ -97,14 +97,18 @@ async function processArtifact(artifact, commit, releaseChannel) {
|
|||
}
|
||||
);
|
||||
|
||||
// Use https://cli.github.com/manual/gh_attestation_verify to verify artifact
|
||||
if (executableIsAvailable('gh')) {
|
||||
await exec(
|
||||
`gh attestation verify artifacts_combined.zip --repo=${OWNER}/${REPO}`,
|
||||
{
|
||||
cwd: tmpDir,
|
||||
}
|
||||
);
|
||||
if (opts.noVerify === true) {
|
||||
console.log(theme`{caution Skipping verification of build artifact.}`);
|
||||
} else {
|
||||
// Use https://cli.github.com/manual/gh_attestation_verify to verify artifact
|
||||
if (executableIsAvailable('gh')) {
|
||||
await exec(
|
||||
`gh attestation verify artifacts_combined.zip --repo=${OWNER}/${REPO}`,
|
||||
{
|
||||
cwd: tmpDir,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await exec(
|
||||
|
|
@ -124,17 +128,19 @@ async function processArtifact(artifact, commit, releaseChannel) {
|
|||
}
|
||||
let sourceDir;
|
||||
// TODO: Rename release channel to `next`
|
||||
if (releaseChannel === 'stable') {
|
||||
if (opts.releaseChannel === 'stable') {
|
||||
sourceDir = 'oss-stable';
|
||||
} else if (releaseChannel === 'experimental') {
|
||||
} else if (opts.releaseChannel === 'experimental') {
|
||||
sourceDir = 'oss-experimental';
|
||||
} else if (releaseChannel === 'rc') {
|
||||
} else if (opts.releaseChannel === 'rc') {
|
||||
sourceDir = 'oss-stable-rc';
|
||||
} else if (releaseChannel === 'latest') {
|
||||
} else if (opts.releaseChannel === 'latest') {
|
||||
sourceDir = 'oss-stable-semver';
|
||||
} else {
|
||||
console.error('Internal error: Invalid release channel: ' + releaseChannel);
|
||||
process.exit(releaseChannel);
|
||||
console.error(
|
||||
'Internal error: Invalid release channel: ' + opts.releaseChannel
|
||||
);
|
||||
process.exit(opts.releaseChannel);
|
||||
}
|
||||
await exec(`cp -r ./build/${sourceDir} ./build/node_modules`, {
|
||||
cwd,
|
||||
|
|
@ -145,19 +151,19 @@ async function processArtifact(artifact, commit, releaseChannel) {
|
|||
/[\u0000-\u001F\u007F-\u009F]/g,
|
||||
''
|
||||
);
|
||||
if (buildSha !== commit) {
|
||||
if (buildSha !== opts.commit) {
|
||||
throw new Error(
|
||||
`Requested commit sha does not match downloaded artifact. Expected: ${commit}, got: ${buildSha}`
|
||||
`Requested commit sha does not match downloaded artifact. Expected: ${opts.commit}, got: ${buildSha}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadArtifactsFromGitHub(commit, releaseChannel) {
|
||||
async function downloadArtifactsFromGitHub(opts) {
|
||||
let workflowRun;
|
||||
let retries = 0;
|
||||
// wait up to 10 mins for build to finish: 10 * 60 * 1_000) / 30_000 = 20
|
||||
while (retries < 20) {
|
||||
workflowRun = await getWorkflowRun(commit);
|
||||
workflowRun = await getWorkflowRun(opts.commit);
|
||||
if (typeof workflowRun.status === 'string') {
|
||||
switch (workflowRun.status) {
|
||||
case 'queued':
|
||||
|
|
@ -174,7 +180,7 @@ async function downloadArtifactsFromGitHub(commit, releaseChannel) {
|
|||
workflowRun.id,
|
||||
'artifacts_combined'
|
||||
);
|
||||
await processArtifact(artifact, commit, releaseChannel);
|
||||
await processArtifact(artifact, opts);
|
||||
return;
|
||||
} else {
|
||||
console.log(
|
||||
|
|
@ -207,10 +213,10 @@ ${workflowRun != null ? JSON.stringify(workflowRun, null, '\t') : workflowRun}`
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
async function downloadBuildArtifacts(commit, releaseChannel) {
|
||||
const label = theme`commit {commit ${commit}})`;
|
||||
async function downloadBuildArtifacts(opts) {
|
||||
const label = theme`commit {commit ${opts.commit}})`;
|
||||
return logPromise(
|
||||
downloadArtifactsFromGitHub(commit, releaseChannel),
|
||||
downloadArtifactsFromGitHub(opts),
|
||||
theme`Downloading artifacts from GitHub for ${label}`
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user