Fixes from review and keytool refactor

This commit is contained in:
Jeremy Davis 2025-09-12 11:02:26 +02:00
parent 83ea1fcb33
commit a64ddd1908
5 changed files with 195 additions and 172 deletions

183
dist/index.js vendored
View file

@ -29895,7 +29895,7 @@ async function installSonarScanner({
let toolDir = toolCacheExports.find(TOOLNAME, scannerVersion, flavor); let toolDir = toolCacheExports.find(TOOLNAME, scannerVersion, flavor);
if (!toolDir) { if (!toolDir) {
console.log( coreExports.info(
`Installing Sonar Scanner CLI ${scannerVersion} for ${flavor}...` `Installing Sonar Scanner CLI ${scannerVersion} for ${flavor}...`
); );
@ -29905,7 +29905,7 @@ async function installSonarScanner({
flavor, flavor,
}); });
console.log(`Downloading from: ${downloadUrl}`); coreExports.info(`Downloading from: ${downloadUrl}`);
const downloadPath = await toolCacheExports.downloadTool(downloadUrl); const downloadPath = await toolCacheExports.downloadTool(downloadUrl);
const extractedPath = await toolCacheExports.extractZip(downloadPath); const extractedPath = await toolCacheExports.extractZip(downloadPath);
@ -29918,9 +29918,9 @@ async function installSonarScanner({
toolDir = await toolCacheExports.cacheDir(scannerPath, TOOLNAME, scannerVersion, flavor); toolDir = await toolCacheExports.cacheDir(scannerPath, TOOLNAME, scannerVersion, flavor);
console.log(`Sonar Scanner CLI cached to: ${toolDir}`); coreExports.info(`Sonar Scanner CLI cached to: ${toolDir}`);
} else { } else {
console.log(`Using cached Sonar Scanner CLI from: ${toolDir}`); coreExports.info(`Using cached Sonar Scanner CLI from: ${toolDir}`);
} }
// Add the bin directory to PATH // Add the bin directory to PATH
@ -29967,30 +29967,33 @@ function firstString() {
} }
} }
const KEYTOOL_MAIN_CLASS = "sun.security.tools.keytool.Main";
const TRUSTSTORE_PASSWORD = "changeit"; // default password of the Java truststore!
async function runSonarScanner( async function runSonarScanner(
inputArgs, inputArgs,
projectBaseDir, projectBaseDir,
scannerDir, scannerDir,
runnerEnv = {} runnerEnv = {}
) { ) {
const { const { runnerDebug, runnerOs, runnerTemp, sonarRootCert, sonarcloudUrl } =
RUNNER_DEBUG, runnerEnv;
RUNNER_OS,
RUNNER_TEMP,
SONAR_ROOT_CERT,
SONARCLOUD_URL,
} = runnerEnv;
const scannerBin = const scannerBin =
RUNNER_OS === "Windows" ? "sonar-scanner.bat" : "sonar-scanner"; runnerOs === "Windows" ? "sonar-scanner.bat" : "sonar-scanner";
const scannerArgs = []; const scannerArgs = [];
if (SONARCLOUD_URL) { /**
scannerArgs.push(`-Dsonar.scanner.sonarcloudUrl=${SONARCLOUD_URL}`); * Not sanitization is needed when populating scannerArgs.
* @actions/exec will take care of sanitizing the args it receives.
*/
if (sonarcloudUrl) {
scannerArgs.push(`-Dsonar.scanner.sonarcloudUrl=${sonarcloudUrl}`);
} }
if (RUNNER_DEBUG === "1") { if (runnerDebug === "1") {
scannerArgs.push("--debug"); scannerArgs.push("--debug");
} }
@ -30000,68 +30003,36 @@ async function runSonarScanner(
// The SSL folder may exist on an uncleaned self-hosted runner // The SSL folder may exist on an uncleaned self-hosted runner
const sslFolder = path.join(require$$0.homedir(), ".sonar", "ssl"); const sslFolder = path.join(require$$0.homedir(), ".sonar", "ssl");
/**
* Use keytool for now, as SonarQube 10.6 and below doesn't support openssl generated keystores
* keytool requires a password > 6 characters, so we won't use the default password 'sonar'
*/
const keytoolMainClass = "sun.security.tools.keytool.Main";
const truststoreFile = path.join(sslFolder, "truststore.p12"); const truststoreFile = path.join(sslFolder, "truststore.p12");
const truststorePassword = "changeit";
const keytoolParams = {
scannerDir,
truststoreFile,
};
if (fs.existsSync(truststoreFile)) { if (fs.existsSync(truststoreFile)) {
let aliasSonarIsPresent = true; let aliasSonarIsPresent = true;
try { try {
await execExports.exec( await checkSonarAliasInTruststore(keytoolParams);
`${scannerDir}/jre/bin/java`,
[
keytoolMainClass,
"-storetype",
"PKCS12",
"-keystore",
truststoreFile,
"-storepass",
truststorePassword,
"-noprompt",
"-trustcacerts",
"-list",
"-v",
"-alias",
"sonar",
],
{ silent: true }
);
} catch (_) { } catch (_) {
aliasSonarIsPresent = false; aliasSonarIsPresent = false;
console.log( coreExports.info(
`Existing Scanner truststore ${truststoreFile} does not contain 'sonar' alias` `Existing Scanner truststore ${truststoreFile} does not contain 'sonar' alias`
); );
} }
if (aliasSonarIsPresent) { if (aliasSonarIsPresent) {
console.log( coreExports.info(
`Removing 'sonar' alias from already existing Scanner truststore: ${truststoreFile}` `Removing 'sonar' alias from already existing Scanner truststore: ${truststoreFile}`
); );
await execExports.exec(`${scannerDir}/jre/bin/java`, [ await deleteSonarAliasFromTruststore(keytoolParams);
keytoolMainClass,
"-storetype",
"PKCS12",
"-keystore",
truststoreFile,
"-storepass",
truststorePassword,
"-noprompt",
"-trustcacerts",
"-delete",
"-alias",
"sonar",
]);
} }
} }
if (SONAR_ROOT_CERT) { if (sonarRootCert) {
console.log("Adding SSL certificate to the Scanner truststore"); coreExports.info("Adding SSL certificate to the Scanner truststore");
const tempCertPath = path.join(RUNNER_TEMP, "tmpcert.pem"); const tempCertPath = path.join(runnerTemp, "tmpcert.pem");
try { try {
fs.unlinkSync(tempCertPath); fs.unlinkSync(tempCertPath);
@ -30069,39 +30040,79 @@ async function runSonarScanner(
// File doesn't exist, ignore // File doesn't exist, ignore
} }
fs.writeFileSync(tempCertPath, SONAR_ROOT_CERT); fs.writeFileSync(tempCertPath, sonarRootCert);
fs.mkdirSync(sslFolder, { recursive: true }); fs.mkdirSync(sslFolder, { recursive: true });
await execExports.exec(`${scannerDir}/jre/bin/java`, [ await importCertificateToTruststore(keytoolParams, tempCertPath);
keytoolMainClass,
"-storetype",
"PKCS12",
"-keystore",
truststoreFile,
"-storepass",
truststorePassword,
"-noprompt",
"-trustcacerts",
"-importcert",
"-alias",
"sonar",
"-file",
tempCertPath,
]);
scannerArgs.push( scannerArgs.push(
`-Dsonar.scanner.truststorePassword=${truststorePassword}` `-Dsonar.scanner.truststorePassword=${TRUSTSTORE_PASSWORD}`
); );
} }
if (inputArgs) { if (inputArgs) {
/**
* No sanitization, but it is parsing a string into an array of arguments in a safe way (= no command execution),
* and with good enough support of quotes to support arguments containing spaces.
*/
const args = parseArgsStringToArgv(inputArgs); const args = parseArgsStringToArgv(inputArgs);
scannerArgs.push(...args); scannerArgs.push(...args);
} }
/**
* Arguments are sanitized by `exec`
*/
await execExports.exec(scannerBin, scannerArgs); await execExports.exec(scannerBin, scannerArgs);
} }
/**
* Use keytool for now, as SonarQube 10.6 and below doesn't support openssl generated keystores
* keytool requires a password > 6 characters, so we won't use the default password 'sonar'
*/
function executeKeytoolCommand({
scannerDir,
truststoreFile,
extraArgs,
options = {},
}) {
const baseArgs = [
KEYTOOL_MAIN_CLASS,
"-storetype",
"PKCS12",
"-keystore",
truststoreFile,
"-storepass",
TRUSTSTORE_PASSWORD,
"-noprompt",
"-trustcacerts",
...extraArgs,
];
return execExports.exec(`${scannerDir}/jre/bin/java`, baseArgs, options);
}
function importCertificateToTruststore(keytoolParams, certPath) {
return executeKeytoolCommand({
...keytoolParams,
extraArgs: ["-importcert", "-alias", "sonar", "-file", certPath],
});
}
function checkSonarAliasInTruststore(keytoolParams) {
return executeKeytoolCommand({
...keytoolParams,
extraArgs: ["-list", "-v", "-alias", "sonar"],
options: { silent: true },
});
}
function deleteSonarAliasFromTruststore(keytoolParams) {
return executeKeytoolCommand({
...keytoolParams,
extraArgs: ["-delete", "-alias", "sonar"],
});
}
function validateScannerVersion(version) { function validateScannerVersion(version) {
if (!version) { if (!version) {
return; return;
@ -30116,7 +30127,7 @@ function validateScannerVersion(version) {
} }
function checkSonarToken(core, sonarToken) { function checkSonarToken(core, sonarToken) {
{ if (!sonarToken) {
core.warning( core.warning(
"Running this GitHub Action without SONAR_TOKEN is not recommended" "Running this GitHub Action without SONAR_TOKEN is not recommended"
); );
@ -30164,21 +30175,21 @@ function getInputs() {
*/ */
function getEnvVariables() { function getEnvVariables() {
return { return {
RUNNER_DEBUG: process.env.RUNNER_DEBUG, runnerDebug: process.env.RUNNER_DEBUG,
RUNNER_OS: process.env.RUNNER_OS, runnerOs: process.env.RUNNER_OS,
RUNNER_TEMP: process.env.RUNNER_TEMP, runnerTemp: process.env.RUNNER_TEMP,
SONAR_ROOT_CERT: process.env.SONAR_ROOT_CERT, sonarRootCert: process.env.SONAR_ROOT_CERT,
SONARCLOUD_URL: process.env.SONARCLOUD_URL, sonarcloudUrl: process.env.SONARCLOUD_URL,
SONAR_TOKEN: process.env.SONAR_TOKEN, sonarToken: process.env.SONAR_TOKEN,
}; };
} }
function runSanityChecks(inputs) { function runSanityChecks(inputs) {
try { try {
const { projectBaseDir, scannerVersion } = inputs; const { projectBaseDir, scannerVersion, sonarToken } = inputs;
validateScannerVersion(scannerVersion); validateScannerVersion(scannerVersion);
checkSonarToken(core$1); checkSonarToken(core$1, sonarToken);
checkMavenProject(core$1, projectBaseDir); checkMavenProject(core$1, projectBaseDir);
checkGradleProject(core$1, projectBaseDir); checkGradleProject(core$1, projectBaseDir);
} catch (error) { } catch (error) {

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View file

@ -28,21 +28,21 @@ function getInputs() {
*/ */
function getEnvVariables() { function getEnvVariables() {
return { return {
RUNNER_DEBUG: process.env.RUNNER_DEBUG, runnerDebug: process.env.RUNNER_DEBUG,
RUNNER_OS: process.env.RUNNER_OS, runnerOs: process.env.RUNNER_OS,
RUNNER_TEMP: process.env.RUNNER_TEMP, runnerTemp: process.env.RUNNER_TEMP,
SONAR_ROOT_CERT: process.env.SONAR_ROOT_CERT, sonarRootCert: process.env.SONAR_ROOT_CERT,
SONARCLOUD_URL: process.env.SONARCLOUD_URL, sonarcloudUrl: process.env.SONARCLOUD_URL,
SONAR_TOKEN: process.env.SONAR_TOKEN, sonarToken: process.env.SONAR_TOKEN,
}; };
} }
function runSanityChecks(inputs) { function runSanityChecks(inputs) {
try { try {
const { projectBaseDir, scannerVersion } = inputs; const { projectBaseDir, scannerVersion, sonarToken } = inputs;
validateScannerVersion(scannerVersion); validateScannerVersion(scannerVersion);
checkSonarToken(core); checkSonarToken(core, sonarToken);
checkMavenProject(core, projectBaseDir); checkMavenProject(core, projectBaseDir);
checkGradleProject(core, projectBaseDir); checkGradleProject(core, projectBaseDir);
} catch (error) { } catch (error) {

View file

@ -23,7 +23,7 @@ export async function installSonarScanner({
let toolDir = tc.find(TOOLNAME, scannerVersion, flavor); let toolDir = tc.find(TOOLNAME, scannerVersion, flavor);
if (!toolDir) { if (!toolDir) {
console.log( core.info(
`Installing Sonar Scanner CLI ${scannerVersion} for ${flavor}...` `Installing Sonar Scanner CLI ${scannerVersion} for ${flavor}...`
); );
@ -33,7 +33,7 @@ export async function installSonarScanner({
flavor, flavor,
}); });
console.log(`Downloading from: ${downloadUrl}`); core.info(`Downloading from: ${downloadUrl}`);
const downloadPath = await tc.downloadTool(downloadUrl); const downloadPath = await tc.downloadTool(downloadUrl);
const extractedPath = await tc.extractZip(downloadPath); const extractedPath = await tc.extractZip(downloadPath);
@ -46,9 +46,9 @@ export async function installSonarScanner({
toolDir = await tc.cacheDir(scannerPath, TOOLNAME, scannerVersion, flavor); toolDir = await tc.cacheDir(scannerPath, TOOLNAME, scannerVersion, flavor);
console.log(`Sonar Scanner CLI cached to: ${toolDir}`); core.info(`Sonar Scanner CLI cached to: ${toolDir}`);
} else { } else {
console.log(`Using cached Sonar Scanner CLI from: ${toolDir}`); core.info(`Using cached Sonar Scanner CLI from: ${toolDir}`);
} }
// Add the bin directory to PATH // Add the bin directory to PATH

View file

@ -1,33 +1,37 @@
import * as core from "@actions/core";
import * as exec from "@actions/exec"; import * as exec from "@actions/exec";
import * as fs from "fs"; import * as fs from "fs";
import * as os from "os"; import * as os from "os";
import * as path from "path"; import * as path from "path";
import { parseArgsStringToArgv } from "string-argv"; import { parseArgsStringToArgv } from "string-argv";
const KEYTOOL_MAIN_CLASS = "sun.security.tools.keytool.Main";
const TRUSTSTORE_PASSWORD = "changeit"; // default password of the Java truststore!
export async function runSonarScanner( export async function runSonarScanner(
inputArgs, inputArgs,
projectBaseDir, projectBaseDir,
scannerDir, scannerDir,
runnerEnv = {} runnerEnv = {}
) { ) {
const { const { runnerDebug, runnerOs, runnerTemp, sonarRootCert, sonarcloudUrl } =
RUNNER_DEBUG, runnerEnv;
RUNNER_OS,
RUNNER_TEMP,
SONAR_ROOT_CERT,
SONARCLOUD_URL,
} = runnerEnv;
const scannerBin = const scannerBin =
RUNNER_OS === "Windows" ? "sonar-scanner.bat" : "sonar-scanner"; runnerOs === "Windows" ? "sonar-scanner.bat" : "sonar-scanner";
const scannerArgs = []; const scannerArgs = [];
if (SONARCLOUD_URL) { /**
scannerArgs.push(`-Dsonar.scanner.sonarcloudUrl=${SONARCLOUD_URL}`); * Not sanitization is needed when populating scannerArgs.
* @actions/exec will take care of sanitizing the args it receives.
*/
if (sonarcloudUrl) {
scannerArgs.push(`-Dsonar.scanner.sonarcloudUrl=${sonarcloudUrl}`);
} }
if (RUNNER_DEBUG === "1") { if (runnerDebug === "1") {
scannerArgs.push("--debug"); scannerArgs.push("--debug");
} }
@ -37,68 +41,36 @@ export async function runSonarScanner(
// The SSL folder may exist on an uncleaned self-hosted runner // The SSL folder may exist on an uncleaned self-hosted runner
const sslFolder = path.join(os.homedir(), ".sonar", "ssl"); const sslFolder = path.join(os.homedir(), ".sonar", "ssl");
/**
* Use keytool for now, as SonarQube 10.6 and below doesn't support openssl generated keystores
* keytool requires a password > 6 characters, so we won't use the default password 'sonar'
*/
const keytoolMainClass = "sun.security.tools.keytool.Main";
const truststoreFile = path.join(sslFolder, "truststore.p12"); const truststoreFile = path.join(sslFolder, "truststore.p12");
const truststorePassword = "changeit";
const keytoolParams = {
scannerDir,
truststoreFile,
};
if (fs.existsSync(truststoreFile)) { if (fs.existsSync(truststoreFile)) {
let aliasSonarIsPresent = true; let aliasSonarIsPresent = true;
try { try {
await exec.exec( await checkSonarAliasInTruststore(keytoolParams);
`${scannerDir}/jre/bin/java`,
[
keytoolMainClass,
"-storetype",
"PKCS12",
"-keystore",
truststoreFile,
"-storepass",
truststorePassword,
"-noprompt",
"-trustcacerts",
"-list",
"-v",
"-alias",
"sonar",
],
{ silent: true }
);
} catch (_) { } catch (_) {
aliasSonarIsPresent = false; aliasSonarIsPresent = false;
console.log( core.info(
`Existing Scanner truststore ${truststoreFile} does not contain 'sonar' alias` `Existing Scanner truststore ${truststoreFile} does not contain 'sonar' alias`
); );
} }
if (aliasSonarIsPresent) { if (aliasSonarIsPresent) {
console.log( core.info(
`Removing 'sonar' alias from already existing Scanner truststore: ${truststoreFile}` `Removing 'sonar' alias from already existing Scanner truststore: ${truststoreFile}`
); );
await exec.exec(`${scannerDir}/jre/bin/java`, [ await deleteSonarAliasFromTruststore(keytoolParams);
keytoolMainClass,
"-storetype",
"PKCS12",
"-keystore",
truststoreFile,
"-storepass",
truststorePassword,
"-noprompt",
"-trustcacerts",
"-delete",
"-alias",
"sonar",
]);
} }
} }
if (SONAR_ROOT_CERT) { if (sonarRootCert) {
console.log("Adding SSL certificate to the Scanner truststore"); core.info("Adding SSL certificate to the Scanner truststore");
const tempCertPath = path.join(RUNNER_TEMP, "tmpcert.pem"); const tempCertPath = path.join(runnerTemp, "tmpcert.pem");
try { try {
fs.unlinkSync(tempCertPath); fs.unlinkSync(tempCertPath);
@ -106,35 +78,75 @@ export async function runSonarScanner(
// File doesn't exist, ignore // File doesn't exist, ignore
} }
fs.writeFileSync(tempCertPath, SONAR_ROOT_CERT); fs.writeFileSync(tempCertPath, sonarRootCert);
fs.mkdirSync(sslFolder, { recursive: true }); fs.mkdirSync(sslFolder, { recursive: true });
await exec.exec(`${scannerDir}/jre/bin/java`, [ await importCertificateToTruststore(keytoolParams, tempCertPath);
keytoolMainClass,
"-storetype",
"PKCS12",
"-keystore",
truststoreFile,
"-storepass",
truststorePassword,
"-noprompt",
"-trustcacerts",
"-importcert",
"-alias",
"sonar",
"-file",
tempCertPath,
]);
scannerArgs.push( scannerArgs.push(
`-Dsonar.scanner.truststorePassword=${truststorePassword}` `-Dsonar.scanner.truststorePassword=${TRUSTSTORE_PASSWORD}`
); );
} }
if (inputArgs) { if (inputArgs) {
/**
* No sanitization, but it is parsing a string into an array of arguments in a safe way (= no command execution),
* and with good enough support of quotes to support arguments containing spaces.
*/
const args = parseArgsStringToArgv(inputArgs); const args = parseArgsStringToArgv(inputArgs);
scannerArgs.push(...args); scannerArgs.push(...args);
} }
/**
* Arguments are sanitized by `exec`
*/
await exec.exec(scannerBin, scannerArgs); await exec.exec(scannerBin, scannerArgs);
} }
/**
* Use keytool for now, as SonarQube 10.6 and below doesn't support openssl generated keystores
* keytool requires a password > 6 characters, so we won't use the default password 'sonar'
*/
function executeKeytoolCommand({
scannerDir,
truststoreFile,
extraArgs,
options = {},
}) {
const baseArgs = [
KEYTOOL_MAIN_CLASS,
"-storetype",
"PKCS12",
"-keystore",
truststoreFile,
"-storepass",
TRUSTSTORE_PASSWORD,
"-noprompt",
"-trustcacerts",
...extraArgs,
];
return exec.exec(`${scannerDir}/jre/bin/java`, baseArgs, options);
}
function importCertificateToTruststore(keytoolParams, certPath) {
return executeKeytoolCommand({
...keytoolParams,
extraArgs: ["-importcert", "-alias", "sonar", "-file", certPath],
});
}
function checkSonarAliasInTruststore(keytoolParams) {
return executeKeytoolCommand({
...keytoolParams,
extraArgs: ["-list", "-v", "-alias", "sonar"],
options: { silent: true },
});
}
function deleteSonarAliasFromTruststore(keytoolParams) {
return executeKeytoolCommand({
...keytoolParams,
extraArgs: ["-delete", "-alias", "sonar"],
});
}