mirror of
https://github.com/actions/setup-java.git
synced 2026-08-19 04:02:51 +00:00
Import Maven signing keys into an isolated GPG home (#1214)
* Isolate Maven signing keys Import signing keys into an action-owned temporary GPG home, export GNUPGHOME, and remove the owned directory in the post action. Cover import failure, multiple keys and invocations, unrelated keyrings, missing state, and Windows path conversion. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix cleanup state assertion Account for isolated GPG-home cleanup when cache saving is disabled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Update generated action bundles Apply repository formatting and commit the setup and cleanup bundles produced by the validated Node 24 build. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address isolated GPG home review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Bruno Borges <brborges@microsoft.com>
This commit is contained in:
co-authored by
Copilot App
Bruno Borges
parent
f4bfb3ddea
commit
634b0f0d18
+8
-2
@@ -52,8 +52,14 @@ export async function configureAuthentication() {
|
||||
|
||||
if (gpgPrivateKey) {
|
||||
core.info('Importing private gpg key');
|
||||
const keyFingerprint = (await gpg.importKey(gpgPrivateKey)) || '';
|
||||
core.saveState(constants.STATE_GPG_PRIVATE_KEY_FINGERPRINT, keyFingerprint);
|
||||
const gpgHome = await gpg.importKey(gpgPrivateKey);
|
||||
try {
|
||||
core.saveState(constants.STATE_GPG_HOME, gpgHome);
|
||||
core.exportVariable('GNUPGHOME', gpg.toGpgPath(gpgHome));
|
||||
} catch (error) {
|
||||
await gpg.removeGpgHome(gpgHome);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -8,19 +8,19 @@ import {
|
||||
} from './util.js';
|
||||
import {fileURLToPath} from 'url';
|
||||
|
||||
async function removePrivateKeyFromKeychain() {
|
||||
if (core.getInput(constants.INPUT_GPG_PRIVATE_KEY, {required: false})) {
|
||||
core.info('Removing private key from keychain');
|
||||
try {
|
||||
const keyFingerprint = core.getState(
|
||||
constants.STATE_GPG_PRIVATE_KEY_FINGERPRINT
|
||||
);
|
||||
await gpg.deleteKey(keyFingerprint);
|
||||
} catch (error) {
|
||||
core.setFailed(
|
||||
`Failed to remove private key due to: ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
async function removeGpgHome() {
|
||||
const gpgHome = core.getState(constants.STATE_GPG_HOME);
|
||||
if (!gpgHome) {
|
||||
return;
|
||||
}
|
||||
|
||||
core.info('Removing private key from isolated GPG home');
|
||||
try {
|
||||
await gpg.removeGpgHome(gpgHome);
|
||||
} catch (error) {
|
||||
core.setFailed(
|
||||
`Failed to remove isolated GPG home due to: ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ async function ignoreError(promise: Promise<void>) {
|
||||
}
|
||||
|
||||
export async function run() {
|
||||
await removePrivateKeyFromKeychain();
|
||||
await removeGpgHome();
|
||||
await ignoreError(saveCaches());
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ export const INPUT_CACHE_PATH = 'cache-path';
|
||||
export const INPUT_CACHE_READ_ONLY = 'cache-read-only';
|
||||
export const INPUT_JOB_STATUS = 'job-status';
|
||||
|
||||
export const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint';
|
||||
export const STATE_GPG_HOME = 'gpg-home';
|
||||
|
||||
export const M2_DIR = '.m2';
|
||||
export const MVN_SETTINGS_FILE = 'settings.xml';
|
||||
|
||||
+72
-46
@@ -1,14 +1,14 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {randomUUID} from 'crypto';
|
||||
import * as io from '@actions/io';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as util from './util.js';
|
||||
import {ExecOptions} from '@actions/exec';
|
||||
|
||||
export const PRIVATE_KEY_FILE = path.join(util.getTempDir(), 'private-key.asc');
|
||||
|
||||
const PRIVATE_KEY_FINGERPRINT_REGEX = /\w{40}/;
|
||||
export const GPG_HOME_PREFIX = 'setup-java-gpg-';
|
||||
const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-';
|
||||
|
||||
// Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...).
|
||||
// The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions
|
||||
@@ -21,49 +21,77 @@ export function toGpgPath(p: string): string {
|
||||
.replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`);
|
||||
}
|
||||
|
||||
export async function importKey(privateKey: string) {
|
||||
fs.writeFileSync(PRIVATE_KEY_FILE, privateKey, {
|
||||
encoding: 'utf-8',
|
||||
flag: 'w'
|
||||
});
|
||||
|
||||
let output = '';
|
||||
|
||||
const options: ExecOptions = {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await exec.exec(
|
||||
'gpg',
|
||||
[
|
||||
'--batch',
|
||||
'--import-options',
|
||||
'import-show',
|
||||
'--import',
|
||||
PRIVATE_KEY_FILE
|
||||
],
|
||||
options
|
||||
);
|
||||
|
||||
await io.rmRF(PRIVATE_KEY_FILE);
|
||||
|
||||
const match = output.match(PRIVATE_KEY_FINGERPRINT_REGEX);
|
||||
return match && match[0];
|
||||
function createGpgHome(prefix: string): string {
|
||||
const gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), prefix));
|
||||
if (process.platform !== 'win32') {
|
||||
fs.chmodSync(gpgHome, 0o700);
|
||||
}
|
||||
return gpgHome;
|
||||
}
|
||||
|
||||
export async function deleteKey(keyFingerprint: string) {
|
||||
await exec.exec(
|
||||
'gpg',
|
||||
['--batch', '--yes', '--delete-secret-and-public-key', keyFingerprint],
|
||||
{
|
||||
silent: true
|
||||
export async function importKey(privateKey: string): Promise<string> {
|
||||
const gpgHome = createGpgHome(GPG_HOME_PREFIX);
|
||||
const privateKeyFile = path.join(gpgHome, `private-key-${randomUUID()}.asc`);
|
||||
|
||||
try {
|
||||
fs.writeFileSync(privateKeyFile, privateKey, {
|
||||
encoding: 'utf-8',
|
||||
flag: 'wx',
|
||||
mode: 0o600
|
||||
});
|
||||
|
||||
try {
|
||||
await exec.exec(
|
||||
'gpg',
|
||||
[
|
||||
'--homedir',
|
||||
toGpgPath(gpgHome),
|
||||
'--batch',
|
||||
'--import',
|
||||
toGpgPath(privateKeyFile)
|
||||
],
|
||||
{silent: true}
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(privateKeyFile, {force: true});
|
||||
}
|
||||
);
|
||||
|
||||
return gpgHome;
|
||||
} catch (error) {
|
||||
await removeGpgHome(gpgHome);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeGpgHome(gpgHome: string): Promise<void> {
|
||||
if (!gpgHome) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedGpgHome = path.resolve(gpgHome);
|
||||
const resolvedTempDir = path.resolve(util.getTempDir());
|
||||
if (
|
||||
path.dirname(resolvedGpgHome) !== resolvedTempDir ||
|
||||
!path.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)
|
||||
) {
|
||||
throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(resolvedGpgHome)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await exec.exec(
|
||||
'gpgconf',
|
||||
['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'],
|
||||
{silent: true, ignoreReturnCode: true}
|
||||
);
|
||||
} catch {
|
||||
// gpgconf may be unavailable, but directory removal must still be attempted.
|
||||
}
|
||||
|
||||
await io.rmRF(resolvedGpgHome);
|
||||
}
|
||||
|
||||
export async function verifyPackageSignature(
|
||||
@@ -74,9 +102,7 @@ export async function verifyPackageSignature(
|
||||
const signaturePath = await tc.downloadTool(signatureUrl);
|
||||
let gpgHome: string;
|
||||
try {
|
||||
gpgHome = fs.mkdtempSync(
|
||||
path.join(util.getTempDir(), 'verify-signature-gpg-home-')
|
||||
);
|
||||
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX);
|
||||
} catch (error) {
|
||||
try {
|
||||
await io.rmRF(signaturePath);
|
||||
|
||||
Reference in New Issue
Block a user