mirror of
https://github.com/SonarSource/sonarqube-scan-action.git
synced 2026-05-16 08:24:05 +03:00
SQSCANGHA-140 Add OpenPGP signature verification for scanner downloads (#235)
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* sonarqube-scan-action
|
||||
* Copyright (C) 2025 SonarSource SA
|
||||
* mailto:info AT sonarsource DOT com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
import { describe, it, afterEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import {
|
||||
getGpgCommand,
|
||||
setupGpgHome,
|
||||
cleanupGpgHome,
|
||||
convertToUnixPath,
|
||||
} from "../gpg-verification.js";
|
||||
|
||||
/**
|
||||
* Helper function to temporarily mock process.platform for a test.
|
||||
* Automatically restores the original platform value after the test.
|
||||
* @param {string} platform - The platform to mock (e.g., "win32", "linux")
|
||||
* @param {Function} testFn - The test function to run with the mocked platform
|
||||
*/
|
||||
function withMockedPlatform(platform, testFn) {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: platform,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
try {
|
||||
testFn();
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: originalPlatform,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a GPG home directory and track it for cleanup.
|
||||
* @param {Array} tempDirs - Array to track temporary directories for cleanup
|
||||
* @returns {string} The path to the created GPG home directory
|
||||
*/
|
||||
function createTrackedGpgHome(tempDirs) {
|
||||
const gpgHome = setupGpgHome();
|
||||
tempDirs.push(gpgHome);
|
||||
assert.ok(fs.existsSync(gpgHome));
|
||||
return gpgHome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to temporarily mock environment variables for a test.
|
||||
* Automatically restores or deletes environment variables after the test.
|
||||
* @param {Object} envVars - Object with environment variable names as keys and values as values
|
||||
* @param {Function} testFn - The async test function to run with the mocked environment
|
||||
*/
|
||||
async function withMockedEnv(envVars, testFn) {
|
||||
const originalValues = {};
|
||||
|
||||
// Save original values and set new ones
|
||||
for (const [key, value] of Object.entries(envVars)) {
|
||||
originalValues[key] = process.env[key];
|
||||
process.env[key] = value;
|
||||
}
|
||||
|
||||
try {
|
||||
await testFn();
|
||||
} finally {
|
||||
// Restore or delete environment variables
|
||||
for (const [key, originalValue] of Object.entries(originalValues)) {
|
||||
if (originalValue === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = originalValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a temporary directory.
|
||||
* @returns {string} The path to the created temporary directory
|
||||
*/
|
||||
function createTempDir() {
|
||||
const tempDir = path.join(os.tmpdir(), `test-runner-temp-${Date.now()}`);
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
describe("gpg-verification", () => {
|
||||
let tempDirs = [];
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up any temporary directories created during tests
|
||||
tempDirs.forEach((dir) => {
|
||||
try {
|
||||
if (fs.existsSync(dir)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
});
|
||||
tempDirs = [];
|
||||
});
|
||||
|
||||
describe("getGpgCommand", () => {
|
||||
it("should return 'gpg' as the command", () => {
|
||||
const command = getGpgCommand();
|
||||
assert.equal(command, "gpg");
|
||||
});
|
||||
});
|
||||
|
||||
describe("convertToUnixPath", () => {
|
||||
it("should convert Windows path with drive letter to Unix path", () => {
|
||||
withMockedPlatform("win32", () => {
|
||||
assert.equal(
|
||||
convertToUnixPath(String.raw`C:\a\_temp\gpg-home`),
|
||||
"/c/a/_temp/gpg-home"
|
||||
);
|
||||
assert.equal(
|
||||
convertToUnixPath(String.raw`D:\Users\test\file.txt`),
|
||||
"/d/Users/test/file.txt"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle mixed slashes on Windows", () => {
|
||||
withMockedPlatform("win32", () => {
|
||||
assert.equal(
|
||||
convertToUnixPath(String.raw`C:\a/_temp\gpg-home`),
|
||||
"/c/a/_temp/gpg-home"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return path unchanged on non-Windows platforms", () => {
|
||||
withMockedPlatform("linux", () => {
|
||||
assert.equal(
|
||||
convertToUnixPath("/tmp/gpg-home"),
|
||||
"/tmp/gpg-home"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("setupGpgHome", () => {
|
||||
it("should create a temporary GPG home directory", () => {
|
||||
const gpgHome = createTrackedGpgHome(tempDirs);
|
||||
|
||||
assert.ok(fs.statSync(gpgHome).isDirectory());
|
||||
|
||||
// Check directory permissions (on Unix systems)
|
||||
if (process.platform !== "win32") {
|
||||
const stats = fs.statSync(gpgHome);
|
||||
const mode = stats.mode & Number.parseInt("777", 8);
|
||||
assert.equal(mode, Number.parseInt("700", 8));
|
||||
}
|
||||
});
|
||||
|
||||
it("should create unique directories on multiple calls", async () => {
|
||||
const gpgHome1 = createTrackedGpgHome(tempDirs);
|
||||
// Small delay to ensure different timestamps
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
const gpgHome2 = createTrackedGpgHome(tempDirs);
|
||||
|
||||
assert.notEqual(gpgHome1, gpgHome2);
|
||||
});
|
||||
|
||||
it("should use RUNNER_TEMP if available", async () => {
|
||||
const testTemp = createTempDir();
|
||||
|
||||
await withMockedEnv({ RUNNER_TEMP: testTemp }, async () => {
|
||||
const gpgHome = createTrackedGpgHome(tempDirs);
|
||||
assert.ok(gpgHome.startsWith(testTemp));
|
||||
});
|
||||
|
||||
if (fs.existsSync(testTemp)) {
|
||||
fs.rmSync(testTemp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("cleanupGpgHome", () => {
|
||||
it("should remove the GPG home directory", () => {
|
||||
const gpgHome = setupGpgHome();
|
||||
assert.ok(fs.existsSync(gpgHome));
|
||||
|
||||
cleanupGpgHome(gpgHome);
|
||||
assert.ok(!fs.existsSync(gpgHome));
|
||||
});
|
||||
|
||||
it("should not throw if directory does not exist", () => {
|
||||
const nonExistentDir = path.join(os.tmpdir(), `non-existent-${Date.now()}`);
|
||||
assert.doesNotThrow(() => {
|
||||
cleanupGpgHome(nonExistentDir);
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle cleanup errors gracefully", () => {
|
||||
// This test verifies the function doesn't throw on permission errors
|
||||
// In practice, permission errors are rare in test environments
|
||||
assert.doesNotThrow(() => {
|
||||
cleanupGpgHome("/invalid/path/that/does/not/exist");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user