From 26d19c8bafbae5bc8b5e1200cd1250db9619f7d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Mar 2021 22:09:12 +0000 Subject: [PATCH 01/17] Bump bl from 1.2.2 to 1.2.3 Bumps [bl](https://github.com/rvagg/bl) from 1.2.2 to 1.2.3. - [Release notes](https://github.com/rvagg/bl/releases) - [Commits](https://github.com/rvagg/bl/compare/v1.2.2...v1.2.3) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 873efe6..9ed60d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -127,9 +127,9 @@ "dev": true }, "bl": { - "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "version": "1.2.3", + "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, "requires": { "readable-stream": "^2.3.5", From 9f3fbcb04218659be8d61a7fb29d645c4d558e2a Mon Sep 17 00:00:00 2001 From: Charlie Sears Date: Wed, 10 Mar 2021 17:19:38 -0500 Subject: [PATCH 02/17] Add reportFilename parameter. --- .../dependency-check-build-task.ts | 10 +++++++++- src/Tasks/dependency-check-build-task/task.json | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index e31b858..57961da 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -26,6 +26,7 @@ async function run() { let failOnCVSS: string | undefined = tl.getInput('failOnCVSS'); let suppressionPath: string | undefined = tl.getPathInput('suppressionPath'); let reportsDirectory: string | undefined = tl.getPathInput('reportsDirectory'); + let reportFilename: string | undefined = tl.getPathInput('reportFilename'); let enableExperimental: boolean | undefined = tl.getBoolInput('enableExperimental', true); let enableRetired: boolean | undefined = tl.getBoolInput('enableRetired', true); let enableVerbose: boolean | undefined = tl.getBoolInput('enableVerbose', true); @@ -41,6 +42,7 @@ async function run() { excludePath = excludePath?.trim(); suppressionPath = suppressionPath?.trim(); reportsDirectory = reportsDirectory?.trim(); + reportFilename = reportFilename?.trim(); additionalArguments = additionalArguments?.trim(); localInstallPath = localInstallPath?.trim(); @@ -61,8 +63,14 @@ async function run() { tl.mkdirP(reportsDirectory!); } + // Set output folder (and filename if supplied) + let outField: string = reportsDirectory; + if (reportFilename && format?.split(',')?.length === 1 && format != "ALL") { + outField = tl.resolve(reportsDirectory, reportFilename); + } + // Default args - let args = `--project "${projectName}" --scan "${scanPath}" --out "${reportsDirectory}"`; + let args = `--project "${projectName}" --scan "${scanPath}" --out "${outField}"`; // Exclude switch if (excludePath != sourcesDirectory) diff --git a/src/Tasks/dependency-check-build-task/task.json b/src/Tasks/dependency-check-build-task/task.json index b31a089..cc6a22d 100644 --- a/src/Tasks/dependency-check-build-task/task.json +++ b/src/Tasks/dependency-check-build-task/task.json @@ -82,6 +82,14 @@ "defaultValue": "", "required": false, "helpMarkDown": "Report output directory. On-prem build agents can specify a local directory to override the default location. The default location is the $COMMON_TESTRESULTSDIRECTORY\\dependency-check directory." + }, + { + "name": "reportFilename", + "type": "string", + "label": "Report Filename", + "defaultValue": "", + "required": false, + "helpMarkDown": "Report output filename. Will set the report output name in 'reportsDirectory' to specified filename. Will not work if format is ALL, or multiple formats are supplied to the 'format' parameter. Filename must have an extension or dependency-check will assume it is a path." }, { "name": "enableExperimental", From 079fc38c7ed688f15fddd75f4b64da35e0cd3c42 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Sun, 14 Mar 2021 10:28:17 -0500 Subject: [PATCH 03/17] Overview typo --- overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overview.md b/overview.md index 94a3749..b7a324a 100644 --- a/overview.md +++ b/overview.md @@ -12,7 +12,7 @@ The OWASP Dependency Check Azure DevOps Extension enables the following features ## GitHub Repository -The extension maintainers do not monitor the Marketplace Question & Answers. please use the [Azure DevOps Dependency Check](https://github.com/dependency-check/azuredevops) repository for questions, issues, or enhancements. +The extension maintainers do not monitor the Marketplace Question & Answers. Please use the [Azure DevOps Dependency Check](https://github.com/dependency-check/azuredevops) repository for questions, issues, or enhancements. ## Installation and Configuration From 20daaff2a4f24ba95ac19f618ab3415a7e830113 Mon Sep 17 00:00:00 2001 From: Vincent Tollu Date: Tue, 4 May 2021 09:27:44 +0200 Subject: [PATCH 04/17] Take into account Darwin OS Default path location is now sh file, Windows OS with .bat being the expection --- .../dependency-check-build-task.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index e31b858..add1cb4 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -127,9 +127,9 @@ async function run() { await unzipFromUrl(dataMirror, dataDirectory); } - // Get dependency check script path - let depCheck = 'dependency-check.bat'; - if (tl.osType().match(/^Linux/)) depCheck = 'dependency-check.sh'; + // Get dependency check script path (.sh file for Linux and Darwin OS) + let depCheck = 'dependency-check.sh'; + if (tl.osType().match(/^Windows/)) depCheck = 'dependency-check.bat'; let depCheckPath = tl.resolve(localInstallPath, 'bin', depCheck); console.log(`Dependency Check script set to ${depCheckPath}`); From a81c19730573897fe4e5f66b259a0bfde7e0b0b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 May 2021 23:07:41 +0000 Subject: [PATCH 05/17] Bump underscore from 1.9.1 to 1.13.1 Bumps [underscore](https://github.com/jashkenas/underscore) from 1.9.1 to 1.13.1. - [Release notes](https://github.com/jashkenas/underscore/releases) - [Commits](https://github.com/jashkenas/underscore/compare/1.9.1...1.13.1) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 873efe6..d849fb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1295,9 +1295,9 @@ "dev": true }, "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", "dev": true }, "util-deprecate": { From 5e8c6074959ad960f12b508b691bbdbe57644061 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Mon, 10 May 2021 11:20:52 +0200 Subject: [PATCH 06/17] Fixed packages version for node modules is-core-module and resolve --- src/Tasks/dependency-check-build-task/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tasks/dependency-check-build-task/package.json b/src/Tasks/dependency-check-build-task/package.json index 5b91b7c..cf7d2d2 100644 --- a/src/Tasks/dependency-check-build-task/package.json +++ b/src/Tasks/dependency-check-build-task/package.json @@ -11,6 +11,8 @@ "dependencies": { "azure-pipelines-task-lib": "^3.0.6-preview.1", "decompress-zip": "^0.3.3", + "is-core-module": "^2.4.0", + "resolve": "^1.20.0", "typed-rest-client": "^1.8.1" }, "devDependencies": { From dca93b2b992cf5e5111da71cd319bb45c6d7abe0 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Mon, 10 May 2021 13:06:10 +0200 Subject: [PATCH 07/17] Repeat download of Dependency Checker ZIP up to 5 times if an error occurred (because sometimes errors "socket hang up" or "ECONNRESET" may appear on the first try with shaky connections) --- .../dependency-check-build-task.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index e31b858..e06994b 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -212,8 +212,28 @@ async function getZipUrl(version: string): Promise { async function unzipFromUrl(zipUrl: string, unzipLocation: string): Promise { let fileName = path.basename(url.parse(zipUrl).pathname); let zipLocation = tl.resolve(fileName) - - let response = await client.get(zipUrl); + let tmpError = null; + let response = null; + let downloadErrorRetries = 5; + + do { + tmpError = null; + try { + console.log('Downloading zip from "' + zipUrl + '"...'); + response = await client.get(zipUrl); + } + catch(error) { + tmpError = error; + downloadErrorRetries--; + console.error('Error trying to download ZIP (' + (downloadErrorRetries+1) + ' tries left)'); + console.error(error); + } + } + while(tmpError !== null && downloadErrorRetries >= 0); + + if(tmpError !== null) { + throw tmpError; + } await new Promise(function (resolve, reject) { let writer = fs.createWriteStream(zipLocation); From f009311b942878313c48c5950f518adb5be66d49 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Mon, 10 May 2021 14:15:24 +0200 Subject: [PATCH 08/17] Bugfixing tool return code: Changed ignoreReturnCode to true or otherwise we couldn't add attachments if the CVSS threshold has been reached --- .../dependency-check-build-task/dependency-check-build-task.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index e06994b..48d560a 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -147,7 +147,7 @@ async function run() { await tl.tool(depCheckPath).arg('--version').exec(); // Run the scan - let exitCode = await tl.tool(depCheckPath).line(args).exec({ failOnStdErr: false, ignoreReturnCode: false }); + let exitCode = await tl.tool(depCheckPath).line(args).exec({ failOnStdErr: false, ignoreReturnCode: true }); console.log(`Dependency Check completed with exit code ${exitCode}.`); console.log('Dependency Check reports:'); console.log(tl.findMatch(reportsDirectory, '**/*.*')); From 6725e232a005aeb7c7d2eaea1960e18c5a341910 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Mon, 10 May 2021 14:42:44 +0200 Subject: [PATCH 09/17] Added the options warnOnCVSSViolation and warnOnToolError --- .../dependency-check-build-task.ts | 26 ++++++++++++++++--- .../dependency-check-build-task/task.json | 16 ++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index 48d560a..8621f11 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -26,6 +26,8 @@ async function run() { let failOnCVSS: string | undefined = tl.getInput('failOnCVSS'); let suppressionPath: string | undefined = tl.getPathInput('suppressionPath'); let reportsDirectory: string | undefined = tl.getPathInput('reportsDirectory'); + let warnOnCVSSViolation: boolean | undefined = tl.getBoolInput('warnOnCVSSViolation', true); + let warnOnToolError: boolean | undefined = tl.getBoolInput('warnOnToolError', true); let enableExperimental: boolean | undefined = tl.getBoolInput('enableExperimental', true); let enableRetired: boolean | undefined = tl.getBoolInput('enableRetired', true); let enableVerbose: boolean | undefined = tl.getBoolInput('enableVerbose', true); @@ -34,6 +36,7 @@ async function run() { let dataMirror: string | undefined = tl.getInput('dataMirror'); let customRepo: string | undefined = tl.getInput('customRepo'); let additionalArguments: string | undefined = tl.getInput('additionalArguments'); + // Trim the strings projectName = projectName?.trim() @@ -177,11 +180,26 @@ async function run() { } if (failed) { - let message = "Dependency Check exited with an error code."; - if (isViolation) message = "CVSS threshold violation."; + let message = "Dependency Check exited with an error code (" + exitCode + ")."; + if(isViolation) { + message = "CVSS threshold violation."; + } + + if(isViolation && warnOnCVSSViolation) { + tl.warning(message); + tl.setResult(tl.TaskResult.SucceededWithIssues, message); + } + else { + if(!isViolation && warnOnToolError) { + tl.warning(message); + tl.setResult(tl.TaskResult.SucceededWithIssues, message); + } + else { + tl.error(message); + tl.setResult(tl.TaskResult.Failed, message); + } + } - tl.error(message); - tl.setResult(tl.TaskResult.Failed, message); } } catch (err) { diff --git a/src/Tasks/dependency-check-build-task/task.json b/src/Tasks/dependency-check-build-task/task.json index b31a089..bb8326e 100644 --- a/src/Tasks/dependency-check-build-task/task.json +++ b/src/Tasks/dependency-check-build-task/task.json @@ -83,6 +83,22 @@ "required": false, "helpMarkDown": "Report output directory. On-prem build agents can specify a local directory to override the default location. The default location is the $COMMON_TESTRESULTSDIRECTORY\\dependency-check directory." }, + { + "name": "warnOnCVSSViolation", + "type": "boolean", + "label": "Only warnings for found violations", + "defaultValue": "false", + "required": false, + "helpMarkDown": "Will only warn for found violations above the CVSS failure threshold instead of throwing errors, and the build step will succeed-with-issues. This ensures that even if violations were found, the pipeline will not be stopped." + }, + { + "name": "warnOnToolError", + "type": "boolean", + "label": "Only warnings for errors (with dependency check tool)", + "defaultValue": "false", + "required": false, + "helpMarkDown": "Will only warn for errors with dependency check tool and the step will succeed-with-issues. This ensures that even if errors with the dependency check tool arise, the pipeline will not be stopped." + }, { "name": "enableExperimental", "type": "boolean", From 8863374920b5ac9fffde6d5b9b2fd099c0743037 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 May 2021 23:42:40 +0000 Subject: [PATCH 10/17] Bump hosted-git-info from 2.7.1 to 2.8.9 Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.7.1 to 2.8.9. - [Release notes](https://github.com/npm/hosted-git-info/releases) - [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md) - [Commits](https://github.com/npm/hosted-git-info/compare/v2.7.1...v2.8.9) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 873efe6..4719242 100644 --- a/package-lock.json +++ b/package-lock.json @@ -491,9 +491,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "i": { From 4310140f5056e00c72fd493f526668f8e8980e02 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Tue, 11 May 2021 11:36:52 +0200 Subject: [PATCH 11/17] Removing left over lock files if no centralized scanner is used --- .../dependency-check-build-task.ts | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index 8621f11..3790386 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -36,7 +36,7 @@ async function run() { let dataMirror: string | undefined = tl.getInput('dataMirror'); let customRepo: string | undefined = tl.getInput('customRepo'); let additionalArguments: string | undefined = tl.getInput('additionalArguments'); - + let hasLocalInstallation = true; // Trim the strings projectName = projectName?.trim() @@ -103,6 +103,7 @@ async function run() { // Set installation location if (localInstallPath == sourcesDirectory) { + hasLocalInstallation = false; localInstallPath = tl.resolve('./dependency-check'); tl.checkPath(localInstallPath, 'Dependency Check installer'); @@ -149,8 +150,38 @@ async function run() { // Version smoke test await tl.tool(depCheckPath).arg('--version').exec(); + if(!hasLocalInstallation) { + // Remove lock files from potential previous canceled run if no local/centralized installation of tool is used. + // We need this because due to a bug the dependency check tool is currently leaving .lock files around if you cancel at the wrong moment. + // Since a per-agent installation shouldn't be able to run two scans parallel, we can savely remove all lock files still lying around. + console.log('Searching for left over lock files...'); + let lockFiles = tl.findMatch(localInstallPath, '*.lock', null, { matchBase: true }); + if(lockFiles.length > 0) { + console.log('found ' + lockFiles.length + ' left over lock files, removing them now...'); + lockFiles.forEach(lockfile => { + let fullLockFilePath = tl.resolve(lockfile); + try { + if(tl.exist(fullLockFilePath)) { + console.log('removing lock file "' + fullLockFilePath + '"...'); + tl.rmRF(fullLockFilePath); + } + else { + console.log('found lock file "' + fullLockFilePath + '" doesn\'t exist, that was unexpected'); + } + } + catch (err) { + console.log('could not delete lock file "' + fullLockFilePath + '"!'); + console.error(err); + } + }); + } + else { + console.log('found no left over lock files, continuing...'); + } + } + // Run the scan - let exitCode = await tl.tool(depCheckPath).line(args).exec({ failOnStdErr: false, ignoreReturnCode: true }); + let exitCode = await tl.tool(depCheckPath).line(args).exec({ failOnStdErr: false, ignoreReturnCode: true }); console.log(`Dependency Check completed with exit code ${exitCode}.`); console.log('Dependency Check reports:'); console.log(tl.findMatch(reportsDirectory, '**/*.*')); From 6dd32776e32a9d3a4782cb1b50730a75dbede6c2 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Tue, 11 May 2021 16:05:41 +0200 Subject: [PATCH 12/17] Added new PowerShell Core building script and updated building README.md --- build/Build-Extension.ps1 | 271 ++++++++++++++++++++++++++++++++++++++ build/README.md | 16 ++- 2 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 build/Build-Extension.ps1 diff --git a/build/Build-Extension.ps1 b/build/Build-Extension.ps1 new file mode 100644 index 0000000..5f683a9 --- /dev/null +++ b/build/Build-Extension.ps1 @@ -0,0 +1,271 @@ + +<#PSScriptInfo + +.VERSION 1.0.0 + +.GUID 6312d879-4b8c-4d88-aa39-bf245069148e + +.AUTHOR Markus Szumovski + +.COMPANYNAME - + +.COPYRIGHT 2021 + +.TAGS + +.LICENSEURI + +.PROJECTURI + +.ICONURI + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES +V 1.0.0: Initial version + +.PRIVATEDATA + +#> + +<# + +.SYNOPSIS + Will build the extension. +.DESCRIPTION + Will build the extension. +.PARAMETER ExtensionRepositoryRoot + The path to the extension repository root from where to build the extension from. + Will default to parent directory of script if no path or $null was provided. +.PARAMETER BuildEnvironment + Set to "Release" for production environment or set to anything else (for example "Development") for development environment. + If nothing was provided the Release environment will be built. +.PARAMETER BuildVersion + If provided will set the version number. + If not provided the version number will not be changed. +.PARAMETER NoPackaging + If the switch was provided, the built extension will not be packaged up into a vsix file. +.OUTPUTS + Building and packaging progress + +.EXAMPLE + Build-Extension -BuildVersion "6.1.0.1" -BuildEnvironment "Release" +#> +[CmdletBinding(SupportsShouldProcess=$True)] +Param +( + [Parameter(Position=0)] + [string] $ExtensionRepositoryRoot = $null, + [Parameter(Position=1)] + [string] $BuildEnvironment = "Release", + [Parameter(Position=2)] + [string] $BuildVersion = $null, + [Parameter(Position=3)] + [switch] $NoPackaging + +) + +### --- START --- functions +### --- END --- functions + +### --- START --- main script ### +try { + Write-Host "--- Build extension script started ---`r`n`r`n" -ForegroundColor DarkGreen + + if([string]::IsNullOrWhiteSpace($ExtensionRepositoryRoot)) { + Write-Host "No extension repository root provided, determining path now..." + $ExtensionRepositoryRoot = Split-Path -Path (Split-Path -Path $MyInvocation.MyCommand.Path -Parent -ErrorAction Stop) -Parent -ErrorAction Stop + Write-Host "" + } + else { + $ExtensionRepositoryRoot = Resolve-Path -Path $ExtensionRepositoryRoot -ErrorAction Ignore + } + + if([string]::IsNullOrWhiteSpace($BuildEnvironment)) { + $BuildEnvironment = "Release" + } + + # Set build env vars + if ($BuildEnvironment -eq "Release") { + $TaskId = "47EA1F4A-57BA-414A-B12E-C44F42765E72" + $TaskName = "dependency-check-build-task" + $VssExtensionName = "vss-extension.prod.json" + } + else { + $TaskId = "04450B31-9F11-415A-B37A-514D69EF69A1" + $TaskName = "dependency-check-build-task-dev" + $VssExtensionName = "vss-extension.dev.json" + } + + $ExtensionRepositoryRootExists = Test-Path -Path $ExtensionRepositoryRoot -ErrorAction Ignore + + if($ExtensionRepositoryRootExists) { + $TaskFolderPath = Join-Path -Path $ExtensionRepositoryRoot -ChildPath "src\Tasks\dependency-check-build-task" -ErrorAction Stop + + $TaskDefPath = Join-Path -Path $TaskFolderPath -ChildPath "task.json" -ErrorAction Stop + $TaskDefExists = Test-Path -Path $TaskDefPath -ErrorAction Ignore + + $VssExtensionPath = Join-Path -Path $ExtensionRepositoryRoot -ChildPath $VssExtensionName -ErrorAction Stop + $VssExtensionExists = Test-Path -Path $VssExtensionPath -ErrorAction Ignore + } + else { + $TaskFolderPath = $null + $TaskDefPath = $null + $TaskDefExists = $false + $VssExtensionPath = $null + $VssExtensionExists = $false + } + + #Parse version vars + $VerPatchRevision = $null + if(![string]::IsNullOrWhiteSpace($BuildVersion)) { + $VerMajor,$VerMinor,$VerPatch,$VerRevision = $BuildVersion.Split('.') + if($null -eq $VerMajor) { + $VerMajor = 0 + } + if($null -eq $VerMinor) { + $VerMinor = 0 + } + if($null -eq $VerPatch) { + $VerPatch = 0 + } + if($null -eq $VerRevision) { + $VerRevision = 0 + } + $VerPatchRevision = [string]::Format("{0}{1}", $VerPatch, $VerRevision.PadLeft(3, '0')) + $BuildVersion = "$VerMajor.$VerMinor.$VerPatch.$VerRevision" + $BuildTaskVersion = "$VerMajor.$VerMinor.$VerPatchRevision" + } + + + Write-Host "------------------------------" + + Write-Host "Extension repository root: ""$ExtensionRepositoryRoot"" (" -NoNewline + if($ExtensionRepositoryRootExists) { + Write-Host "exists" -ForegroundColor Green -NoNewline + } + else { + Write-Host "missing" -ForegroundColor Red -NoNewline + } + Write-Host ")" + + Write-Host "Task definition JSON: ""$TaskDefPath"" (" -NoNewline + if($TaskDefExists) { + Write-Host "exists" -ForegroundColor Green -NoNewline + } + else { + Write-Host "missing" -ForegroundColor Red -NoNewline + } + Write-Host ")" + + Write-Host "VSS extension JSON: ""$VssExtensionPath"" (" -NoNewline + if($VssExtensionExists) { + Write-Host "exists" -ForegroundColor Green -NoNewline + } + else { + Write-Host "missing" -ForegroundColor Red -NoNewline + } + Write-Host ")" + + Write-Host "Build environment: $BuildEnvironment" + if([string]::IsNullOrWhiteSpace($BuildVersion)) { + Write-Host "Build version: " + } + else { + Write-Host "Build version: $BuildVersion" + Write-Host "Build-Task version: $BuildTaskVersion" + } + + Write-Host "Task-Id: $TaskId" + Write-Host "Task-Name: $TaskName" + Write-Host "VSS extension JSON: $VssExtensionName" + + Write-Host "------------------------------`r`n" + + if($ExtensionRepositoryRootExists) { + if($TaskDefExists) { + + Write-Host "Reading task.json..." + $TaskJson = Get-Content -Path $TaskDefPath -Raw | ConvertFrom-Json + + Write-Host "Setting task definition id and name..." + $TaskJson.id = $TaskId + $TaskJson.name = $TaskName + + if([string]::IsNullOrWhiteSpace($BuildVersion)) { + Write-Host "(Skipping setting of task definition version since no version was provided)" + } + else { + Write-Host "Setting task definition version..." + $TaskJson.version.Major = $VerMajor + $TaskJson.version.Minor = $VerMinor + $TaskJson.version.Patch = $VerPatchRevision + } + + Write-Host "Saving new task definition..." + $TaskJson | ConvertTo-Json -Depth 100 | Set-Content -Path $TaskDefPath + + if([string]::IsNullOrWhiteSpace($BuildVersion)) { + Write-Host "(Skipping setting of extension version since no version was provided)" + } + else { + Write-Host "Reading ""$VssExtensionName""..." + $VssExtensionJson = Get-Content -Path $VssExtensionPath -Raw | ConvertFrom-Json + + Write-Host "Setting version" + $VssExtensionJson.version = $BuildVersion + + Write-Host "Saving new extension definition..." + $VssExtensionJson | ConvertTo-Json -Depth 100 | Set-Content $VssExtensionPath + } + + Write-Host "`r`nBuilding task..." + Write-Host "------------------------------" + Push-Location + Set-Location -Path $TaskFolderPath + &"npm" install + &"npm" run build + Pop-Location + Write-Host "------------------------------" + Write-Host "`r`nBuilding extension..." + Write-Host "------------------------------" + &"npm" install + &"npm" run build + Write-Host "------------------------------" + + if(!$NoPackaging.IsPresent) { + Write-Host "`r`nPackaging..." + if($BuildEnvironment -eq "Release") { + &"npm" run package-prod + } + else { + &"npm" run package-dev + } + + Write-Host "`r`nBuilding and packaging extension..." -NoNewline + } + else { + Write-Host "`r`nBuilding extension..." -NoNewline + } + + Write-Host "DONE" -ForegroundColor Green + } + else { + Write-Warning "Task.json not found, cannot continue" + } + } + else { + Write-Warning "Extension repository root not found, cannot continue" + } +} +finally { + Write-Host "`r`n`r`n--- Build extension script ended ---" -ForegroundColor DarkGreen +} + +#and we're at the end + +### --- END --- main script ### diff --git a/build/README.md b/build/README.md index ebd2c36..5ed1761 100644 --- a/build/README.md +++ b/build/README.md @@ -4,7 +4,17 @@ Let's start with this: We can automate this with a pipeline later and eliminate Start by making your changes to the extension. When you are ready to test and release, use the steps below. -## Build Task Version +## PowerShell Core building + +The simplest way to create a new vsix package for development or production environment is to use the ./build/Build-Extension.ps1 PowerShell Core script (PowerShell Core needs to be installed for this script to work). + +Just call it via `pwsh ./build/Build-Extension.ps1 -BuildVersion "6.1.0.0" -BuildEnvironment "Release"` and replace the -BuildVersion string with the new version number and use "Release" as BuildEnvironment for production and "Development" as BuildEnvironment for development. + +After the call the new VSIX file should have been created in the repository root directory. + +## Manual building + +### Build Task Version To release a new version, start by opening the *src/Tasks/dependency-check-build-task/task.json* file. Bump the version number. Keep the major and minor versions in sync with the core Dependency Check CLI. The patch release has to be updated every time you want to change the extension. Even in development. Think of it like a build number. Azure won't update the build task during an update if this value is the same as the currently installed build task in a pipeline. So, we put some 0's on th end to tell us what version of Dependency Check we are using, as well as the build id of the extension itself. Example 5.1.1 = 5.1.\[1000-1999\]. @@ -16,7 +26,7 @@ To release a new version, start by opening the *src/Tasks/dependency-check-build }, ``` -## Building for DEV +### Building for DEV Open the **package.json** file and modify the package line: @@ -58,7 +68,7 @@ dependency-check.azuredevops-dev-5.2.0.000.vsix Upload the the marketplace manually (for now until the release pipeline works) -## Build for PROD +### Build for PROD Open the **package.json** file and update the package command to the prod value: From 35fe95e82ce973ed446c9e82e0e5c8ff4a37db50 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Wed, 12 May 2021 11:28:21 +0200 Subject: [PATCH 13/17] Changed handling of end-state/message and changed download error messages to await so they won't interfere with next download message --- .../dependency-check-build-task.ts | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index 3790386..9cecae7 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -210,28 +210,47 @@ async function run() { console.log(`##vso[build.uploadlog]${logFile}`); } + let message = "Dependency Check succeeded" + let result = tl.TaskResult.Succeeded if (failed) { - let message = "Dependency Check exited with an error code (" + exitCode + ")."; if(isViolation) { message = "CVSS threshold violation."; + + if(warnOnCVSSViolation) { + result = tl.TaskResult.SucceededWithIssues + } + else { + result = tl.TaskResult.Failed + } } - - if(isViolation && warnOnCVSSViolation) { - tl.warning(message); - tl.setResult(tl.TaskResult.SucceededWithIssues, message); - } - else { - if(!isViolation && warnOnToolError) { - tl.warning(message); - tl.setResult(tl.TaskResult.SucceededWithIssues, message); - } - else { - tl.error(message); - tl.setResult(tl.TaskResult.Failed, message); - } - } + else { + message = "Dependency Check exited with an error code (exit code: " + exitCode + ")." + + if(warnOnToolError) { + result = tl.TaskResult.SucceededWithIssues + } + else { + result = tl.TaskResult.Failed + } + } + } + let consoleMessage = 'Dependency Check '; + switch(result) { + case tl.TaskResult.Succeeded: + consoleMessage += 'succeeded' + break; + case tl.TaskResult.SucceededWithIssues: + consoleMessage += 'succeeded with issues' + break; + case tl.TaskResult.Failed: + consoleMessage += 'failed' + break; } + consoleMessage += ' with message "' + message + '"' + console.log(consoleMessage); + + tl.setResult(result, message); } catch (err) { console.log(err.message); @@ -268,14 +287,14 @@ async function unzipFromUrl(zipUrl: string, unzipLocation: string): Promise= 0); From ea1e2286a14ebdc2c6cf7127e8c1bcf529ec6026 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Wed, 12 May 2021 13:11:14 +0200 Subject: [PATCH 14/17] Added debug logging function and debug messages. Also changed debug output so that debug messages only appear if system.debug is true (Azure DevOps pipelines default variable for debug messages). --- .../dependency-check-build-task.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index 9cecae7..54b92e9 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -193,14 +193,14 @@ async function run() { // Process scan artifacts is required let processArtifacts = !failed || isViolation; if (processArtifacts) { - console.log('##[debug]Attachments:'); + logDebug('Attachments:'); let reports = tl.findMatch(reportsDirectory, '**/*.*'); reports.forEach(filePath => { let fileName = path.basename(filePath).replace('.', '%2E'); let fileExt = path.extname(filePath); - console.log(`##[debug]Attachment name: ${fileName}`); - console.log(`##[debug]Attachment path: ${filePath}`); - console.log(`##[debug]Attachment type: ${fileExt}`); + logDebug(`Attachment name: ${fileName}`); + logDebug(`Attachment path: ${filePath}`); + logDebug(`Attachment type: ${fileExt}`); console.log(`##vso[task.addattachment type=dependencycheck-artifact;name=${fileName};]${filePath}`); console.log(`##vso[artifact.upload containerfolder=dependency-check;artifactname=Dependency Check;]${filePath}`); }) @@ -261,6 +261,18 @@ async function run() { console.log("Ending Dependency Check..."); } +function logDebug(message: string) { + if(message !== null) { + let varSystemDebug = tl.getVariable('system.debug'); + + if(typeof varSystemDebug === 'string') { + if(varSystemDebug.toLowerCase() == 'true') { + console.log('##[debug]' + message) + } + } + } +} + function cleanLocalInstallPath(localInstallPath: string) { let files = tl.findMatch(localInstallPath, ['**', '!data', '!data/**']); files.forEach(file => tl.rmRF(file)); @@ -287,8 +299,9 @@ async function unzipFromUrl(zipUrl: string, unzipLocation: string): Promise(function (resolve, reject) { let writer = fs.createWriteStream(zipLocation); writer.on('error', err => reject(err)); @@ -310,8 +325,15 @@ async function unzipFromUrl(zipUrl: string, unzipLocation: string): Promise { From 6cb5b47753e529f1f49478511a01383738c09858 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Wed, 12 May 2021 15:24:51 +0200 Subject: [PATCH 15/17] Removed warnOnToolError option again since this should already be covered with the generic "continue on error" option --- .../dependency-check-build-task.ts | 9 +-------- src/Tasks/dependency-check-build-task/task.json | 8 -------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index 54b92e9..fe9fca4 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -27,7 +27,6 @@ async function run() { let suppressionPath: string | undefined = tl.getPathInput('suppressionPath'); let reportsDirectory: string | undefined = tl.getPathInput('reportsDirectory'); let warnOnCVSSViolation: boolean | undefined = tl.getBoolInput('warnOnCVSSViolation', true); - let warnOnToolError: boolean | undefined = tl.getBoolInput('warnOnToolError', true); let enableExperimental: boolean | undefined = tl.getBoolInput('enableExperimental', true); let enableRetired: boolean | undefined = tl.getBoolInput('enableRetired', true); let enableVerbose: boolean | undefined = tl.getBoolInput('enableVerbose', true); @@ -225,13 +224,7 @@ async function run() { } else { message = "Dependency Check exited with an error code (exit code: " + exitCode + ")." - - if(warnOnToolError) { - result = tl.TaskResult.SucceededWithIssues - } - else { - result = tl.TaskResult.Failed - } + result = tl.TaskResult.Failed } } diff --git a/src/Tasks/dependency-check-build-task/task.json b/src/Tasks/dependency-check-build-task/task.json index bb8326e..2f83c06 100644 --- a/src/Tasks/dependency-check-build-task/task.json +++ b/src/Tasks/dependency-check-build-task/task.json @@ -91,14 +91,6 @@ "required": false, "helpMarkDown": "Will only warn for found violations above the CVSS failure threshold instead of throwing errors, and the build step will succeed-with-issues. This ensures that even if violations were found, the pipeline will not be stopped." }, - { - "name": "warnOnToolError", - "type": "boolean", - "label": "Only warnings for errors (with dependency check tool)", - "defaultValue": "false", - "required": false, - "helpMarkDown": "Will only warn for errors with dependency check tool and the step will succeed-with-issues. This ensures that even if errors with the dependency check tool arise, the pipeline will not be stopped." - }, { "name": "enableExperimental", "type": "boolean", From 73064a181ff1369ca28f0ad34ed7ad5344da33bc Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Wed, 12 May 2021 15:35:02 +0200 Subject: [PATCH 16/17] Updated label and help text for warnOnCVSSViolation option --- src/Tasks/dependency-check-build-task/task.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/task.json b/src/Tasks/dependency-check-build-task/task.json index 2f83c06..9fceaa7 100644 --- a/src/Tasks/dependency-check-build-task/task.json +++ b/src/Tasks/dependency-check-build-task/task.json @@ -86,10 +86,10 @@ { "name": "warnOnCVSSViolation", "type": "boolean", - "label": "Only warnings for found violations", + "label": "Only warn for found violations", "defaultValue": "false", "required": false, - "helpMarkDown": "Will only warn for found violations above the CVSS failure threshold instead of throwing errors, and the build step will succeed-with-issues. This ensures that even if violations were found, the pipeline will not be stopped." + "helpMarkDown": "Will only warn for found violations above the CVSS failure threshold instead of throwing an error. This build step will then succeed with issues instead of failing." }, { "name": "enableExperimental", From 5664f9e3d0c88294733112548b9427ded67b9c27 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Mon, 17 May 2021 12:07:28 +0200 Subject: [PATCH 17/17] Changed formatting (tabs to spaces) to comply with rest of file formatting --- .../dependency-check-build-task.ts | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts index fe9fca4..87e2ae1 100644 --- a/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts +++ b/src/Tasks/dependency-check-build-task/dependency-check-build-task.ts @@ -26,7 +26,7 @@ async function run() { let failOnCVSS: string | undefined = tl.getInput('failOnCVSS'); let suppressionPath: string | undefined = tl.getPathInput('suppressionPath'); let reportsDirectory: string | undefined = tl.getPathInput('reportsDirectory'); - let warnOnCVSSViolation: boolean | undefined = tl.getBoolInput('warnOnCVSSViolation', true); + let warnOnCVSSViolation: boolean | undefined = tl.getBoolInput('warnOnCVSSViolation', true); let enableExperimental: boolean | undefined = tl.getBoolInput('enableExperimental', true); let enableRetired: boolean | undefined = tl.getBoolInput('enableRetired', true); let enableVerbose: boolean | undefined = tl.getBoolInput('enableVerbose', true); @@ -35,7 +35,7 @@ async function run() { let dataMirror: string | undefined = tl.getInput('dataMirror'); let customRepo: string | undefined = tl.getInput('customRepo'); let additionalArguments: string | undefined = tl.getInput('additionalArguments'); - let hasLocalInstallation = true; + let hasLocalInstallation = true; // Trim the strings projectName = projectName?.trim() @@ -102,7 +102,7 @@ async function run() { // Set installation location if (localInstallPath == sourcesDirectory) { - hasLocalInstallation = false; + hasLocalInstallation = false; localInstallPath = tl.resolve('./dependency-check'); tl.checkPath(localInstallPath, 'Dependency Check installer'); @@ -149,38 +149,38 @@ async function run() { // Version smoke test await tl.tool(depCheckPath).arg('--version').exec(); - if(!hasLocalInstallation) { - // Remove lock files from potential previous canceled run if no local/centralized installation of tool is used. - // We need this because due to a bug the dependency check tool is currently leaving .lock files around if you cancel at the wrong moment. - // Since a per-agent installation shouldn't be able to run two scans parallel, we can savely remove all lock files still lying around. - console.log('Searching for left over lock files...'); - let lockFiles = tl.findMatch(localInstallPath, '*.lock', null, { matchBase: true }); - if(lockFiles.length > 0) { - console.log('found ' + lockFiles.length + ' left over lock files, removing them now...'); - lockFiles.forEach(lockfile => { - let fullLockFilePath = tl.resolve(lockfile); - try { - if(tl.exist(fullLockFilePath)) { - console.log('removing lock file "' + fullLockFilePath + '"...'); - tl.rmRF(fullLockFilePath); - } - else { - console.log('found lock file "' + fullLockFilePath + '" doesn\'t exist, that was unexpected'); - } - } - catch (err) { - console.log('could not delete lock file "' + fullLockFilePath + '"!'); - console.error(err); - } - }); - } - else { - console.log('found no left over lock files, continuing...'); - } - } + if(!hasLocalInstallation) { + // Remove lock files from potential previous canceled run if no local/centralized installation of tool is used. + // We need this because due to a bug the dependency check tool is currently leaving .lock files around if you cancel at the wrong moment. + // Since a per-agent installation shouldn't be able to run two scans parallel, we can savely remove all lock files still lying around. + console.log('Searching for left over lock files...'); + let lockFiles = tl.findMatch(localInstallPath, '*.lock', null, { matchBase: true }); + if(lockFiles.length > 0) { + console.log('found ' + lockFiles.length + ' left over lock files, removing them now...'); + lockFiles.forEach(lockfile => { + let fullLockFilePath = tl.resolve(lockfile); + try { + if(tl.exist(fullLockFilePath)) { + console.log('removing lock file "' + fullLockFilePath + '"...'); + tl.rmRF(fullLockFilePath); + } + else { + console.log('found lock file "' + fullLockFilePath + '" doesn\'t exist, that was unexpected'); + } + } + catch (err) { + console.log('could not delete lock file "' + fullLockFilePath + '"!'); + console.error(err); + } + }); + } + else { + console.log('found no left over lock files, continuing...'); + } + } // Run the scan - let exitCode = await tl.tool(depCheckPath).line(args).exec({ failOnStdErr: false, ignoreReturnCode: true }); + let exitCode = await tl.tool(depCheckPath).line(args).exec({ failOnStdErr: false, ignoreReturnCode: true }); console.log(`Dependency Check completed with exit code ${exitCode}.`); console.log('Dependency Check reports:'); console.log(tl.findMatch(reportsDirectory, '**/*.*')); @@ -212,8 +212,8 @@ async function run() { let message = "Dependency Check succeeded" let result = tl.TaskResult.Succeeded if (failed) { - if(isViolation) { - message = "CVSS threshold violation."; + if(isViolation) { + message = "CVSS threshold violation."; if(warnOnCVSSViolation) { result = tl.TaskResult.SucceededWithIssues @@ -221,7 +221,7 @@ async function run() { else { result = tl.TaskResult.Failed } - } + } else { message = "Dependency Check exited with an error code (exit code: " + exitCode + ")." result = tl.TaskResult.Failed @@ -285,29 +285,29 @@ async function getZipUrl(version: string): Promise { async function unzipFromUrl(zipUrl: string, unzipLocation: string): Promise { let fileName = path.basename(url.parse(zipUrl).pathname); let zipLocation = tl.resolve(fileName) - let tmpError = null; - let response = null; - let downloadErrorRetries = 5; - - do { - tmpError = null; - try { - await console.log('Downloading ZIP from "' + zipUrl + '"...'); - response = await client.get(zipUrl); - await logDebug('done downloading'); - } - catch(error) { - tmpError = error; - downloadErrorRetries--; - await console.error('Error trying to download ZIP (' + (downloadErrorRetries+1) + ' tries left)'); - await console.error(error); - } - } - while(tmpError !== null && downloadErrorRetries >= 0); - - if(tmpError !== null) { - throw tmpError; - } + let tmpError = null; + let response = null; + let downloadErrorRetries = 5; + + do { + tmpError = null; + try { + await console.log('Downloading ZIP from "' + zipUrl + '"...'); + response = await client.get(zipUrl); + await logDebug('done downloading'); + } + catch(error) { + tmpError = error; + downloadErrorRetries--; + await console.error('Error trying to download ZIP (' + (downloadErrorRetries+1) + ' tries left)'); + await console.error(error); + } + } + while(tmpError !== null && downloadErrorRetries >= 0); + + if(tmpError !== null) { + throw tmpError; + } await logDebug('Download was successful, saving downloaded ZIP file...');