mirror of
https://github.com/SonarSource/sonarqube-scan-action.git
synced 2026-05-29 22:20:49 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f55d92a8a1 |
@@ -200,6 +200,20 @@ This can be useful when the runner executing the action is self-hosted and has r
|
||||
scannerBinariesUrl: https://my.custom.binaries.url.com/Distribution/sonar-scanner-cli/
|
||||
```
|
||||
|
||||
#### `scannerBinariesAuthHeader`
|
||||
|
||||
If the server specified by `scannerBinariesUrl` requires authentication, you can provide an `Authorization` header value using the `scannerBinariesAuthHeader` option.
|
||||
The value is passed directly as the `Authorization` HTTP header, so you must include the scheme (e.g. `Bearer`, `Basic`):
|
||||
|
||||
```yaml
|
||||
- uses: SonarSource/sonarqube-scan-action@<action version>
|
||||
with:
|
||||
scannerBinariesUrl: https://my.custom.binaries.url.com/Distribution/sonar-scanner-cli/
|
||||
scannerBinariesAuthHeader: ${{ secrets.BINARIES_AUTH_HEADER }}
|
||||
```
|
||||
|
||||
Store the full header value (e.g. `Bearer mytoken`) in the GitHub secret to avoid exposing credentials.
|
||||
|
||||
#### `skipSignatureVerification`
|
||||
|
||||
By default, the action verifies the OpenPGP signature of the SonarScanner CLI binary before executing it. You can disable this verification using the `skipSignatureVerification` option:
|
||||
|
||||
@@ -28,6 +28,13 @@ inputs:
|
||||
description: Skip GPG signature verification (not recommended for security)
|
||||
required: false
|
||||
default: "false"
|
||||
scannerBinariesAuthHeader:
|
||||
description: >
|
||||
Authorization header value to use when downloading the SonarScanner CLI binaries
|
||||
(e.g. 'Bearer mytoken' or 'Basic base64creds'). Use this when scannerBinariesUrl
|
||||
points to a private server that requires authentication.
|
||||
required: false
|
||||
default: ""
|
||||
runs:
|
||||
using: node24
|
||||
main: dist/index.js
|
||||
|
||||
Vendored
+14
-4
@@ -3503,6 +3503,13 @@ function downloadToolAttempt(url, dest, auth, headers) {
|
||||
const http = new HttpClient(userAgent, [], {
|
||||
allowRetries: false
|
||||
});
|
||||
if (auth) {
|
||||
debug('set auth');
|
||||
if (headers === undefined) {
|
||||
headers = {};
|
||||
}
|
||||
headers.authorization = auth;
|
||||
}
|
||||
const response = yield http.get(url, headers);
|
||||
if (response.message.statusCode !== 200) {
|
||||
const err = new HTTPError(response.message.statusCode);
|
||||
@@ -4140,6 +4147,7 @@ const TOOLNAME = "sonar-scanner-cli";
|
||||
async function installSonarScanner({
|
||||
scannerVersion,
|
||||
scannerBinariesUrl,
|
||||
scannerBinariesAuthHeader,
|
||||
skipSignatureVerification = false,
|
||||
}) {
|
||||
const flavor = getPlatformFlavor(os$1.platform(), os$1.arch());
|
||||
@@ -4160,7 +4168,7 @@ async function installSonarScanner({
|
||||
|
||||
info(`Downloading from: ${downloadUrl}`);
|
||||
|
||||
const downloadPath = await downloadTool(downloadUrl);
|
||||
const downloadPath = await downloadTool(downloadUrl, undefined, scannerBinariesAuthHeader);
|
||||
|
||||
if (skipSignatureVerification) {
|
||||
warning("⚠ Skipping GPG signature verification (not recommended)");
|
||||
@@ -4170,7 +4178,7 @@ async function installSonarScanner({
|
||||
|
||||
let signaturePath;
|
||||
try {
|
||||
signaturePath = await downloadTool(signatureUrl);
|
||||
signaturePath = await downloadTool(signatureUrl, undefined, scannerBinariesAuthHeader);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to download signature file from ${signatureUrl}: ${error.message}`
|
||||
@@ -4489,10 +4497,11 @@ function getInputs() {
|
||||
const args = getInput("args");
|
||||
const projectBaseDir = getInput("projectBaseDir");
|
||||
const scannerBinariesUrl = getInput("scannerBinariesUrl");
|
||||
const scannerBinariesAuthHeader = getInput("scannerBinariesAuthHeader") || undefined;
|
||||
const scannerVersion = getInput("scannerVersion");
|
||||
const skipSignatureVerification = getBooleanInput("skipSignatureVerification");
|
||||
|
||||
return { args, projectBaseDir, scannerBinariesUrl, scannerVersion, skipSignatureVerification };
|
||||
return { args, projectBaseDir, scannerBinariesUrl, scannerBinariesAuthHeader, scannerVersion, skipSignatureVerification };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4528,7 +4537,7 @@ function runSanityChecks(inputs) {
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const { args, projectBaseDir, scannerVersion, scannerBinariesUrl, skipSignatureVerification } =
|
||||
const { args, projectBaseDir, scannerVersion, scannerBinariesUrl, scannerBinariesAuthHeader, skipSignatureVerification } =
|
||||
getInputs();
|
||||
const runnerEnv = getEnvVariables();
|
||||
const { sonarToken } = runnerEnv;
|
||||
@@ -4538,6 +4547,7 @@ async function run() {
|
||||
const scannerDir = await installSonarScanner({
|
||||
scannerVersion,
|
||||
scannerBinariesUrl,
|
||||
scannerBinariesAuthHeader,
|
||||
skipSignatureVerification,
|
||||
});
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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 assert from "node:assert/strict";
|
||||
import { describe, it, mock } from "node:test";
|
||||
|
||||
const SCANNER_VERSION = "6.2.0.4584";
|
||||
const BINARIES_URL = "https://my.artifactory.example.com/sonar-scanner-cli";
|
||||
const BINARY_DOWNLOAD_URL = `${BINARIES_URL}/sonar-scanner-cli-${SCANNER_VERSION}-linux-x64.zip`;
|
||||
|
||||
function mockUtils(t) {
|
||||
t.mock.module("../utils.js", {
|
||||
namedExports: {
|
||||
getPlatformFlavor: mock.fn(() => "linux-x64"),
|
||||
getScannerDownloadURL: mock.fn(() => BINARY_DOWNLOAD_URL),
|
||||
scannerDirName: mock.fn(() => `sonar-scanner-${SCANNER_VERSION}-linux-x64`),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("installSonarScanner", () => {
|
||||
it("should forward scannerBinariesAuthHeader to both binary and signature downloads", async (t) => {
|
||||
const downloadCalls = [];
|
||||
const downloadToolFn = mock.fn(async (url, dest, auth) => {
|
||||
downloadCalls.push({ url, auth });
|
||||
return `/tmp/downloaded-${downloadCalls.length}`;
|
||||
});
|
||||
|
||||
mockUtils(t);
|
||||
|
||||
t.mock.module("@actions/tool-cache", {
|
||||
namedExports: {
|
||||
find: mock.fn(() => null),
|
||||
downloadTool: downloadToolFn,
|
||||
extractZip: mock.fn(async () => "/tmp/extracted"),
|
||||
cacheDir: mock.fn(async () => "/tmp/cached"),
|
||||
},
|
||||
});
|
||||
|
||||
t.mock.module("@actions/core", {
|
||||
namedExports: {
|
||||
info: mock.fn(),
|
||||
warning: mock.fn(),
|
||||
addPath: mock.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
t.mock.module("../gpg-verification.js", {
|
||||
namedExports: {
|
||||
verifySignature: mock.fn(async () => {}),
|
||||
},
|
||||
});
|
||||
|
||||
const { installSonarScanner } = await import(
|
||||
`../install-sonar-scanner.js?test=auth-header`
|
||||
);
|
||||
|
||||
await installSonarScanner({
|
||||
scannerVersion: SCANNER_VERSION,
|
||||
scannerBinariesUrl: BINARIES_URL,
|
||||
scannerBinariesAuthHeader: "Bearer mytoken",
|
||||
});
|
||||
|
||||
assert.equal(downloadCalls.length, 2, "Should download binary and signature");
|
||||
assert.equal(downloadCalls[0].auth, "Bearer mytoken", "Binary download should use auth header");
|
||||
assert.equal(downloadCalls[1].auth, "Bearer mytoken", "Signature download should use auth header");
|
||||
assert.ok(downloadCalls[1].url.endsWith(".asc"), "Second download should be the signature");
|
||||
});
|
||||
|
||||
it("should not set auth header when scannerBinariesAuthHeader is not provided", async (t) => {
|
||||
const downloadCalls = [];
|
||||
const downloadToolFn = mock.fn(async (url, dest, auth) => {
|
||||
downloadCalls.push({ url, auth });
|
||||
return `/tmp/downloaded-${downloadCalls.length}`;
|
||||
});
|
||||
|
||||
mockUtils(t);
|
||||
|
||||
t.mock.module("@actions/tool-cache", {
|
||||
namedExports: {
|
||||
find: mock.fn(() => null),
|
||||
downloadTool: downloadToolFn,
|
||||
extractZip: mock.fn(async () => "/tmp/extracted"),
|
||||
cacheDir: mock.fn(async () => "/tmp/cached"),
|
||||
},
|
||||
});
|
||||
|
||||
t.mock.module("@actions/core", {
|
||||
namedExports: {
|
||||
info: mock.fn(),
|
||||
warning: mock.fn(),
|
||||
addPath: mock.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
t.mock.module("../gpg-verification.js", {
|
||||
namedExports: {
|
||||
verifySignature: mock.fn(async () => {}),
|
||||
},
|
||||
});
|
||||
|
||||
const { installSonarScanner } = await import(
|
||||
`../install-sonar-scanner.js?test=no-auth-header`
|
||||
);
|
||||
|
||||
await installSonarScanner({
|
||||
scannerVersion: SCANNER_VERSION,
|
||||
scannerBinariesUrl: BINARIES_URL,
|
||||
});
|
||||
|
||||
assert.equal(downloadCalls.length, 2);
|
||||
assert.equal(downloadCalls[0].auth, undefined, "Binary download should have no auth header");
|
||||
assert.equal(downloadCalls[1].auth, undefined, "Signature download should have no auth header");
|
||||
});
|
||||
|
||||
it("should skip signature download when skipSignatureVerification is true", async (t) => {
|
||||
const downloadCalls = [];
|
||||
const downloadToolFn = mock.fn(async (url, dest, auth) => {
|
||||
downloadCalls.push({ url, auth });
|
||||
return `/tmp/downloaded-${downloadCalls.length}`;
|
||||
});
|
||||
|
||||
mockUtils(t);
|
||||
|
||||
t.mock.module("@actions/tool-cache", {
|
||||
namedExports: {
|
||||
find: mock.fn(() => null),
|
||||
downloadTool: downloadToolFn,
|
||||
extractZip: mock.fn(async () => "/tmp/extracted"),
|
||||
cacheDir: mock.fn(async () => "/tmp/cached"),
|
||||
},
|
||||
});
|
||||
|
||||
t.mock.module("@actions/core", {
|
||||
namedExports: {
|
||||
info: mock.fn(),
|
||||
warning: mock.fn(),
|
||||
addPath: mock.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
const { installSonarScanner } = await import(
|
||||
`../install-sonar-scanner.js?test=skip-sig`
|
||||
);
|
||||
|
||||
await installSonarScanner({
|
||||
scannerVersion: SCANNER_VERSION,
|
||||
scannerBinariesUrl: BINARIES_URL,
|
||||
scannerBinariesAuthHeader: "Bearer mytoken",
|
||||
skipSignatureVerification: true,
|
||||
});
|
||||
|
||||
assert.equal(downloadCalls.length, 1, "Should only download binary, not signature");
|
||||
assert.equal(downloadCalls[0].auth, "Bearer mytoken");
|
||||
});
|
||||
|
||||
it("should use cached tool when available and skip download", async (t) => {
|
||||
const downloadToolFn = mock.fn();
|
||||
|
||||
mockUtils(t);
|
||||
|
||||
t.mock.module("@actions/tool-cache", {
|
||||
namedExports: {
|
||||
find: mock.fn(() => "/tmp/cached-tool"),
|
||||
downloadTool: downloadToolFn,
|
||||
extractZip: mock.fn(),
|
||||
cacheDir: mock.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
t.mock.module("@actions/core", {
|
||||
namedExports: {
|
||||
info: mock.fn(),
|
||||
warning: mock.fn(),
|
||||
addPath: mock.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
const { installSonarScanner } = await import(
|
||||
`../install-sonar-scanner.js?test=cached`
|
||||
);
|
||||
|
||||
await installSonarScanner({
|
||||
scannerVersion: SCANNER_VERSION,
|
||||
scannerBinariesUrl: BINARIES_URL,
|
||||
});
|
||||
|
||||
assert.equal(downloadToolFn.mock.calls.length, 0, "Should not download when cached");
|
||||
});
|
||||
});
|
||||
+4
-2
@@ -33,10 +33,11 @@ function getInputs() {
|
||||
const args = core.getInput("args");
|
||||
const projectBaseDir = core.getInput("projectBaseDir");
|
||||
const scannerBinariesUrl = core.getInput("scannerBinariesUrl");
|
||||
const scannerBinariesAuthHeader = core.getInput("scannerBinariesAuthHeader") || undefined;
|
||||
const scannerVersion = core.getInput("scannerVersion");
|
||||
const skipSignatureVerification = core.getBooleanInput("skipSignatureVerification");
|
||||
|
||||
return { args, projectBaseDir, scannerBinariesUrl, scannerVersion, skipSignatureVerification };
|
||||
return { args, projectBaseDir, scannerBinariesUrl, scannerBinariesAuthHeader, scannerVersion, skipSignatureVerification };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +73,7 @@ function runSanityChecks(inputs) {
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const { args, projectBaseDir, scannerVersion, scannerBinariesUrl, skipSignatureVerification } =
|
||||
const { args, projectBaseDir, scannerVersion, scannerBinariesUrl, scannerBinariesAuthHeader, skipSignatureVerification } =
|
||||
getInputs();
|
||||
const runnerEnv = getEnvVariables();
|
||||
const { sonarToken } = runnerEnv;
|
||||
@@ -82,6 +83,7 @@ async function run() {
|
||||
const scannerDir = await installSonarScanner({
|
||||
scannerVersion,
|
||||
scannerBinariesUrl,
|
||||
scannerBinariesAuthHeader,
|
||||
skipSignatureVerification,
|
||||
});
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
getPlatformFlavor,
|
||||
getScannerDownloadURL,
|
||||
scannerDirName,
|
||||
} from "./utils";
|
||||
import { verifySignature } from "./gpg-verification";
|
||||
} from "./utils.js";
|
||||
import { verifySignature } from "./gpg-verification.js";
|
||||
|
||||
const TOOLNAME = "sonar-scanner-cli";
|
||||
|
||||
@@ -35,6 +35,7 @@ const TOOLNAME = "sonar-scanner-cli";
|
||||
export async function installSonarScanner({
|
||||
scannerVersion,
|
||||
scannerBinariesUrl,
|
||||
scannerBinariesAuthHeader,
|
||||
skipSignatureVerification = false,
|
||||
}) {
|
||||
const flavor = getPlatformFlavor(os.platform(), os.arch());
|
||||
@@ -55,7 +56,7 @@ export async function installSonarScanner({
|
||||
|
||||
core.info(`Downloading from: ${downloadUrl}`);
|
||||
|
||||
const downloadPath = await tc.downloadTool(downloadUrl);
|
||||
const downloadPath = await tc.downloadTool(downloadUrl, undefined, scannerBinariesAuthHeader);
|
||||
|
||||
if (skipSignatureVerification) {
|
||||
core.warning("⚠ Skipping GPG signature verification (not recommended)");
|
||||
@@ -65,7 +66,7 @@ export async function installSonarScanner({
|
||||
|
||||
let signaturePath;
|
||||
try {
|
||||
signaturePath = await tc.downloadTool(signatureUrl);
|
||||
signaturePath = await tc.downloadTool(signatureUrl, undefined, scannerBinariesAuthHeader);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to download signature file from ${signatureUrl}: ${error.message}`
|
||||
|
||||
Reference in New Issue
Block a user