Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add self-signed CA support #71

Merged
merged 6 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ scanner(
- `serverUrl` _String_ (optional) The URL of the SonarQube server. Defaults to http://localhost:9000
- `login` _String_ (optional) The login used to connect to the SonarQube server up to version 9. Empty by default.
- `token` _String_ (optional) The token used to connect to the SonarQube server v10+ or SonarCloud. Empty by default.
- `caPath` _String_ (optional) the path to a CA to pass as `https.request()` [options](https://nodejs.org/api/https.html#https_https_request_options_callback).
- `options` _Map_ (optional) Used to pass extra parameters for the analysis. See the [official documentation](http://redirect.sonarsource.com/doc/analysis-parameters.html) for more details.
- `callback` _Function_ (optional)
Callback (the execution of the analysis is asynchronous).
Expand Down
17 changes: 17 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
const sonarScannerParams = require('./sonar-scanner-params');
const { findTargetOS, buildInstallFolderPath, buildExecutablePath } = require('./utils');
const os = require('os');
const fs = require('fs');
const log = require('fancy-log');
const { HttpsProxyAgent } = require('https-proxy-agent');

Expand Down Expand Up @@ -117,9 +118,25 @@ function getExecutableParams(params = {}) {
'Basic ' + Buffer.from(finalUrl.username + ':' + finalUrl.password).toString('base64'),
};
}

if (params.caPath) {
config.httpOptions.ca = extractCa(params.caPath);
}

log(`Executable parameters built:`);
log(config);
return config;

function extractCa(caPath) {
if (!fs.existsSync(caPath)) {
throw new Error(`Provided CA certificate path does not exist: ${caPath}`);
}
const ca = fs.readFileSync(caPath, 'utf8');
if (!ca.startsWith('-----BEGIN CERTIFICATE-----')) {
throw new Error('Invalid CA certificate');
}
return ca;
}
}

/**
Expand Down
9 changes: 2 additions & 7 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
const exec = require('child_process').execFileSync;
const log = require('fancy-log');
const { getScannerParams, extendWithExecParams } = require('./config');
const {
getSonarScannerExecutable,
getLocalSonarScannerExecutable,
} = require('./sonar-scanner-executable');
const { getScannerExecutable } = require('./sonar-scanner-executable');
const version = require('../package.json').version;

/*
Expand All @@ -34,9 +31,7 @@ async function scan(params, cliArgs = [], localScanner = false) {
log('Starting analysis...');

// determine the command to run and execute it
const sqScannerCommand = await (localScanner
? getLocalSonarScannerExecutable
: getSonarScannerExecutable)();
const sqScannerCommand = await getScannerExecutable(localScanner, params);

// prepare the exec options, most notably with the SQ params
const scannerParams = getScannerParams(process.cwd(), params);
Expand Down
18 changes: 16 additions & 2 deletions src/sonar-scanner-executable.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ const logError = log.error;
const path = require('path');
const { getExecutableParams } = require('./config');

module.exports.getSonarScannerExecutable = getSonarScannerExecutable;
module.exports.getLocalSonarScannerExecutable = getLocalSonarScannerExecutable;
module.exports.getScannerExecutable = getScannerExecutable;

const bar = new ProgressBar('[:bar] :percent :etas', {
complete: '=',
Expand All @@ -38,6 +37,21 @@ const bar = new ProgressBar('[:bar] :percent :etas', {
total: 0,
});

/**
* If localScanner is true, returns the command to use the local scanner executable.
* Otherwise, returns a promise to download the scanner executable and the command to use it.
*
* @param {*} localScanner
* @param {*} params
* @returns
*/
function getScannerExecutable(localScanner = false, params = {}) {
if (localScanner) {
return getLocalSonarScannerExecutable();
}
return getSonarScannerExecutable(params);
}

/*
* Returns the SQ Scanner executable for the current platform
*/
Expand Down
49 changes: 38 additions & 11 deletions test/unit/sonar-scanner-executable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,17 @@ const fs = require('fs');
const os = require('os');
const mkdirpSync = require('mkdirp').sync;
const rimraf = require('rimraf');
const {
getSonarScannerExecutable,
getLocalSonarScannerExecutable,
} = require('../../src/sonar-scanner-executable');
const { getScannerExecutable } = require('../../src/sonar-scanner-executable');
const { DEFAULT_SCANNER_VERSION, getExecutableParams } = require('../../src/config');
const { buildInstallFolderPath, buildExecutablePath } = require('../../src/utils');
const { startServer, closeServerPromise } = require('./fixtures/webserver/server');

describe('sqScannerExecutable', function () {
describe('getSonarScannerExecutable()', function () {
describe('Sonar: getScannerExecutable(false)', function () {
it('should throw exception when the download of executable fails', async function () {
process.env.SONAR_SCANNER_MIRROR = 'http://fake.url/sonar-scanner';
try {
await getSonarScannerExecutable({
await getScannerExecutable(false, {
basePath: os.tmpdir(),
});
assert.fail();
Expand All @@ -62,7 +59,7 @@ describe('sqScannerExecutable', function () {
rimraf.sync(filepath);
});
it('should return the path to it', async function () {
const receivedExecutable = await getSonarScannerExecutable({
const receivedExecutable = await getScannerExecutable(false, {
basePath: os.tmpdir(),
});
assert.equal(receivedExecutable, filepath);
Expand All @@ -85,21 +82,51 @@ describe('sqScannerExecutable', function () {
rimraf.sync(pathToUnzippedExecutable);
});
it('should download the executable, unzip it and return a path to it.', async function () {
const execPath = await getSonarScannerExecutable({
const execPath = await getScannerExecutable(false, {
baseUrl: `http://${server.address().address}:${server.address().port}`,
fileName: FILENAME,
});
assert.equal(execPath, expectedPlatformExecutablePath);
});
});

describe('when providing a self-signed CA certificate', function () {
let caPath;
beforeAll(() => {
caPath = path.join(os.tmpdir(), 'ca.pem');
fs.writeFileSync(caPath, '-----BEGIN CERTIFICATE-----');
});

it('should fail if the provided path is invalid', async function () {
try {
await getScannerExecutable(false, { caPath: 'invalid-path' });
assert.fail('should have thrown');
} catch (e) {
assert.equal(e.message, 'Provided CA certificate path does not exist: invalid-path');
}
});
it('should proceed with the download if the provided CA certificate is valid', async function () {
process.env.SONAR_SCANNER_MIRROR = 'http://fake.url/sonar-scanner';
try {
await getScannerExecutable(false, {
caPath: caPath,
basePath: os.tmpdir(),
});
assert.fail('should have thrown');
} catch (e) {
assert.equal(e.message, 'getaddrinfo ENOTFOUND fake.url');
}
});
});
});

describe('getLocalSonarScannerExecutable', () => {
it('should fail when the executable is not found', () => {
describe('local: getScannerExecutable(true)', () => {
it('should fail when the executable is not found', async () => {
assert.throws(
getLocalSonarScannerExecutable,
getScannerExecutable.bind(null, true),
'Local install of SonarScanner not found in: sonar-scanner',
);
//expect(getScannerExecutable(true)).to.eventually.be.rejectedWith('Local install of SonarScanner not found in: sonar-scanner');
});
});
});