From 9b61a56c2619e766ddd3860f19e3bc840361f726 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 20 Feb 2025 10:58:50 +0100 Subject: [PATCH] fix(git-node): check the tag signature and not the commit one During release promotion, we want to verify the tag is signed with one of the releasers' key. The commit can be signed with a different signature (e.g. the bot's, or a soon-to-be releaser). --- lib/promote_release.js | 33 ++++++++++++++++++++------------- lib/run.js | 17 ++++++++++++++--- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/promote_release.js b/lib/promote_release.js index d717110f..692b90c7 100644 --- a/lib/promote_release.js +++ b/lib/promote_release.js @@ -226,20 +226,28 @@ export default class ReleasePromotion extends Session { async verifyTagSignature() { const { cli, version } = this; - const [needle, haystack] = await Promise.all([forceRunAsync( + const verifyTagPattern = /gpg:[^\n]+\ngpg:\s+using RSA key ([^\n]+)\ngpg:\s+issuer "([^"]+)"\ngpg:\s+Good signature from "([^<]+) <\2>"/; + const [verifyTagOutput, haystack] = await Promise.all([forceRunAsync( 'git', ['--no-pager', - 'log', '-1', - `refs/tags/v${version}`, - '--format=* **%an** <<%ae>>\n `%GF`' - ], { captureStdout: true }), fs.readFile('README.md')]); - if (haystack.includes(needle)) { - return; + 'verify-tag', + `v${version}` + ], { ignoreFailure: false, captureStderr: true }), fs.readFile('README.md')]); + const match = verifyTagPattern.exec(verifyTagOutput); + if (match == null) { + cli.warn('git was not able to verify the tag:'); + cli.info(verifyTagOutput); + } else { + const [, keyID, email, name] = match; + const needle = `* **${name}** <<${email}>>\n ${'`'}${keyID}${'`'}`; + if (haystack.includes(needle)) { + return; + } + cli.warn('Tag was signed with an undocumented identity/key pair!'); + cli.info('Expected to find the following entry in the README:'); + cli.info(needle); + cli.info('If you are using a subkey, it might be OK.'); } - cli.warn('Tag was signed with an undocumented identity/key pair!'); - cli.info('Expected to find the following entry in the README:'); - cli.info(needle); - cli.info('If you are using a subkey, it might be OK.'); - cli.info(`Otherwise consider removing the tag (git tag -d v${version + cli.info(`If that doesn't sound right, consider removing the tag (git tag -d v${version }), check your local config, and start the process over.`); if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) { throw new Error('Aborted'); @@ -383,7 +391,6 @@ export default class ReleasePromotion extends Session { { cause: err } ); } - await forceRunAsync('git', ['tag', '--verify', `v${version}`], { ignoreFailure: false }); this.cli.info('Using the existing tag'); } } diff --git a/lib/run.js b/lib/run.js index c9f87c70..578e8f40 100644 --- a/lib/run.js +++ b/lib/run.js @@ -12,14 +12,19 @@ function runAsyncBase(cmd, args, { ignoreFailure = true, spawnArgs, input, + captureStderr = false, captureStdout = false } = {}) { if (cmd instanceof URL) { cmd = fileURLToPath(cmd); } let stdio = 'inherit'; - if (captureStdout || input != null) { - stdio = [input == null ? 'inherit' : 'pipe', captureStdout ? 'pipe' : 'inherit', 'inherit']; + if (captureStderr || captureStdout || input != null) { + stdio = [ + input == null ? 'inherit' : 'pipe', + captureStdout ? 'pipe' : 'inherit', + captureStderr ? 'pipe' : 'inherit' + ]; } return new Promise((resolve, reject) => { const opt = Object.assign({ @@ -30,6 +35,12 @@ function runAsyncBase(cmd, args, { debuglog('[Spawn]', `${cmd} ${(args || []).join(' ')}`, opt); } const child = spawn(cmd, args, opt); + let stderr; + if (!captureStdout && captureStderr) { + stderr = ''; + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + } let stdout; if (captureStdout) { stdout = ''; @@ -51,7 +62,7 @@ function runAsyncBase(cmd, args, { stdout = stdout.split(/\r?\n/g); if (stdout[stdout.length - 1] === '') stdout.pop(); } - return resolve(stdout); + return resolve(stdout ?? stderr); }); if (input != null) child.stdin.end(input); });