From 901a67b4eb28ed1b45d13599ece4e102fa8c815a Mon Sep 17 00:00:00 2001 From: SOOS-JAlvarez <92373106+SOOS-JAlvarez@users.noreply.github.com> Date: Tue, 21 Nov 2023 11:00:47 -0300 Subject: [PATCH] PA-11698 SAST Wrapper Script (#1) * PA-11698 SAST Wrapper Script * readme * workflows * added local installation * code review --- .github/pull_request_template.md | 11 + .github/workflows/codeql-analysis.yml | 50 +++++ .github/workflows/publish.yml | 31 +++ .gitignore | 2 + .npmignore | 5 + .prettierrc | 6 + .vscode/settings.json | 12 ++ README.md | 48 ++++- package-lock.json | 196 ++++++++++++++++++ package.json | 43 ++++ src/index.ts | 285 ++++++++++++++++++++++++++ src/utils/constants.ts | 10 + tsconfig.json | 22 ++ 13 files changed, 719 insertions(+), 2 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .prettierrc create mode 100644 .vscode/settings.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 src/utils/constants.ts create mode 100644 tsconfig.json diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f889cc9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +### Changes: + - Description of the change + - Updated package.json version + +**Ticket:** https://soos.atlassian.net/browse/PA-0000 + + diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..6dc85bb --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,50 @@ +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '15 15 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'typescript' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..7f46b52 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,31 @@ +name: Publish to npm + +on: + push: + tags: + - "v*" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Publish to npm + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0892c0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/bin diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..ecbde04 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +tsconfig.json +.prettierrc +/.vscode +/.github +/src \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1fc490c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "parser": "typescript", + "printWidth": 100, + "quoteProps": "consistent", + "endOfLine": "auto" +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..742687c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "editor.tabSize": 2, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.configPath": "./.prettierrc", + "cSpell.words": [ + "Levelkey", + "SAST", + "SOOS" + ], + "sarif-viewer.connectToGithubCodeScanning": "off", + } \ No newline at end of file diff --git a/README.md b/README.md index c26ebaf..a8e44f8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,46 @@ -# soos-sast -SOOS wrapper script to upload SAST files. +# [SOOS SAST](https://soos.io/products/sast) + +SOOS is an independent software security company, located in Winooski, VT USA, building security software for your team. [SOOS, Software security, simplified](https://soos.io). + +Use SOOS to scan your software for [vulnerabilities](https://app.soos.io/research/vulnerabilities) and [open source license](https://app.soos.io/research/licenses) issues with [SOOS Core SCA](https://soos.io/sca-product). [Generate SBOMs](https://kb.soos.io/help/generating-a-software-bill-of-materials-sbom). Govern your open source dependencies. Run the [SOOS DAST vulnerability scanner](https://soos.io/dast-product) against your web apps or APIs. + +[Demo SOOS](https://app.soos.io/demo) or [Register for a Free Trial](https://app.soos.io/register). + +If you maintain an Open Source project, sign up for the Free as in Beer [SOOS Community Edition](https://soos.io/products/community-edition). + +## Requirements + - [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) + +## Installation + +### Globally +run `npm i -g @soos-io/soos-sast@latest` +Then Run `soos-sast` from any terminal and add the parameters you want. + +### Locally +run `npm install --prefix ./soos @soos-io/soos-sast` +Then run from the same terminal `node ./soos/node_modules/@soos-io/soos-sast/bin/index.js` + +## Parameters + + +| Argument | Default | Description | +| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `--apiKey` | `getEnvVariable(CONSTANTS.SOOS.API_KEY_ENV_VAR)` | SOOS API Key - get yours from [SOOS Integration](https://app.soos.io/integrate/sast). | +| `--apiURL` | `"https://api.soos.io/api/"` | SOOS API URL - Intended for internal use only, do not modify. | +| `--appVersion` | N/A | App Version - Intended for internal use only. | +| `--branchName` | `null` | The name of the branch from the SCM System. | +| `--branchURI` | `null` | The URI to the branch from the SCM System. | +| `--buildURI` | `null` | URI to CI build info. | +| `--buildVersion` | `null` | Version of application build artifacts. | +| `--clientId` | `getEnvVariable(CONSTANTS.SOOS.CLIENT_ID_ENV_VAR)` | SOOS Client ID - get yours from [SOOS Integration](https://app.soos.io/integrate/sast). | +| `--commitHash` | `null` | The commit hash value from the SCM System. | +| `--integrationName` | N/A | Integration Name - Intended for internal use only. | +| `--integrationType` | N/A | Integration Type - Intended for internal use only. | +| `--logLevel` | `LogLevel.INFO` | Minimum level to show logs: PASS, IGNORE, INFO, WARN, or FAIL. | +| `--operatingEnvironment`| `null` | Set Operating environment for information purposes only. | +| `--otherOptions` | `null` | Other Options to pass to syft. | +| `--projectName` | N/A | Project Name - this is what will be displayed in the SOOS app. | +| `--scriptVersion` | N/A | Script Version - Intended for internal use only. | +| `--verbose` | `false` | Enable verbose logging. | +| `sastPath` | N/A | The SAST File to scan (*.sarif.json), it could be the location of the file or the file itself. When location is specified only the first file found will be scanned. | diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4d0bf1c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,196 @@ +{ + "name": "@soos-io/soos-sast", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@soos-io/soos-sast", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@soos-io/api-client": "0.1.8", + "argparse": "^2.0.1", + "tslib": "^2.6.2" + }, + "bin": { + "soos-sast": "bin/index.js" + }, + "devDependencies": { + "@types/argparse": "^2.0.11", + "@types/glob": "^8.1.0", + "@types/node": "^20.6.3", + "prettier": "^2.8.8", + "typescript": "^5.2.2" + } + }, + "node_modules/@soos-io/api-client": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@soos-io/api-client/-/api-client-0.1.8.tgz", + "integrity": "sha512-9Duhm8o8Qlug2DekEeg86j+W2PL5jtYZJaG5I14IZASAxhzf2wU//AaWNEzWfMXrPY5NYFTMxXRgZN5A8TV85g==", + "dependencies": { + "axios": "^0.27.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@types/argparse": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-2.0.13.tgz", + "integrity": "sha512-KuqfEvuee3AmqgWbiB/9ZeJsGxgZiYPnREtAOyiDOLgmHrpzX+TBaJTMDXOCi3r9hATPj9ANuxu5EtU9Lab3SQ==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.1.tgz", + "integrity": "sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..20ae43c --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "@soos-io/soos-sast", + "version": "0.1.0", + "description": "SOOS Static Application Security Testing (SAST) scanning support.", + "main": "bin/index.js", + "scripts": { + "setup:install": "npm install", + "setup:clean-install": "npm ci", + "setup:update": "npx npm-check -u", + "setup:clean": "npx rimraf node_modules && npx rimraf package-lock.json", + "build": "tsc", + "build:clean": "npx rimraf build", + "format": "prettier ./src --check", + "format:fix": "prettier ./src --write", + "typecheck": "tsc --noEmit", + "check": "npm run format && npm run typecheck && npm outdated" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/soos-io/soos-sast.git" + }, + "author": "SOOS", + "license": "MIT", + "bugs": { + "url": "https://github.com/soos-io/soos-sast/issues" + }, + "homepage": "https://github.com/soos-io/soos-sast#readme", + "dependencies": { + "@soos-io/api-client": "0.1.8", + "argparse": "^2.0.1", + "tslib": "^2.6.2" + }, + "devDependencies": { + "@types/argparse": "^2.0.11", + "@types/glob": "^8.1.0", + "@types/node": "^20.6.3", + "prettier": "^2.8.8", + "typescript": "^5.2.2" + }, + "bin": { + "soos-sast": "bin/index.js" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4dbee22 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,285 @@ +#!/usr/bin/env node +import { LogLevel, SOOS_CONSTANTS, ScanStatus, ScanType, soosLogger } from "@soos-io/api-client"; +import { + getEnvVariable, + obfuscateProperties, + ensureEnumValue, + ensureValue, +} from "@soos-io/api-client/dist/utilities"; +import { ArgumentParser } from "argparse"; +import * as FileSystem from "fs"; +import * as Path from "path"; +import FormData from "form-data"; +import { CONSTANTS } from "./utils/constants"; +import { exit } from "process"; +import SOOSAnalysisApiClient from "@soos-io/api-client/dist/api/SOOSAnalysisApiClient"; + +interface SOOSSASTAnalysisArgs { + apiKey: string; + apiURL: string; + appVersion: string; + branchName: string; + branchUri: string; + buildUri: string; + buildVersion: string; + clientId: string; + commitHash: string; + integrationName: string; + integrationType: string; + logLevel: LogLevel; + operatingEnvironment: string; + projectName: string; + scriptVersion: string; + sastPath: string; + verbose: boolean; +} + +class SOOSSASTAnalysis { + constructor(private args: SOOSSASTAnalysisArgs) {} + + static parseArgs(): SOOSSASTAnalysisArgs { + const parser = new ArgumentParser({ description: "SOOS SAST" }); + + parser.add_argument("--apiKey", { + help: "SOOS API Key - get yours from https://app.soos.io/integrate/sast", + default: getEnvVariable(CONSTANTS.SOOS.API_KEY_ENV_VAR), + required: false, + }); + + parser.add_argument("--apiURL", { + help: "SOOS API URL - Intended for internal use only, do not modify.", + default: "https://api.soos.io/api/", + required: false, + }); + + parser.add_argument("--appVersion", { + help: "App Version - Intended for internal use only.", + required: false, + }); + + parser.add_argument("--branchName", { + help: "The name of the branch from the SCM System.", + default: null, + required: false, + }); + + parser.add_argument("--branchURI", { + help: "The URI to the branch from the SCM System.", + default: null, + required: false, + }); + + parser.add_argument("--buildURI", { + help: "URI to CI build info.", + default: null, + required: false, + }); + + parser.add_argument("--buildVersion", { + help: "Version of application build artifacts.", + default: null, + required: false, + }); + + parser.add_argument("--clientId", { + help: "SOOS Client ID - get yours from https://app.soos.io/integrate/sast", + default: getEnvVariable(CONSTANTS.SOOS.CLIENT_ID_ENV_VAR), + required: false, + }); + + parser.add_argument("--commitHash", { + help: "The commit hash value from the SCM System.", + default: null, + required: false, + }); + + parser.add_argument("--integrationName", { + help: "Integration Name - Intended for internal use only.", + required: false, + }); + + parser.add_argument("--integrationType", { + help: "Integration Type - Intended for internal use only.", + required: false, + default: CONSTANTS.SOOS.DEFAULT_INTEGRATION_TYPE, + }); + + parser.add_argument("--logLevel", { + help: "Minimum level to show logs: PASS, IGNORE, INFO, WARN or FAIL.", + default: LogLevel.INFO, + required: false, + type: (value: string) => { + return ensureEnumValue(LogLevel, value); + }, + }); + + parser.add_argument("--operatingEnvironment", { + help: "Set Operating environment for information purposes only.", + default: null, + required: false, + }); + + parser.add_argument("--projectName", { + help: "Project Name - this is what will be displayed in the SOOS app.", + required: true, + }); + + parser.add_argument("--scriptVersion", { + help: "Script Version - Intended for internal use only.", + required: false, + }); + + parser.add_argument("--verbose", { + help: "Enable verbose logging.", + action: "store_true", + default: false, + required: false, + }); + + parser.add_argument("sastPath", { + help: "The SAST File to scan (*.sarif.json), it could be the location of the file or the file itself. When location is specified only the first file found will be scanned.", + }); + + soosLogger.info("Parsing arguments"); + return parser.parse_args(); + } + + async runAnalysis(): Promise { + let projectHash: string | undefined; + let branchHash: string | undefined; + let analysisId: string | undefined; + const filePath = await this.findSASTFilePath(); + const soosAnalysisApiClient = new SOOSAnalysisApiClient(this.args.apiKey, this.args.apiURL); + try { + soosLogger.info("Starting SOOS SAST Analysis"); + soosLogger.info(`Creating scan for project '${this.args.projectName}'...`); + soosLogger.info(`Branch Name: ${this.args.branchName}`); + + const result = await soosAnalysisApiClient.createScan({ + clientId: this.args.clientId, + projectName: this.args.projectName, + commitHash: this.args.commitHash, + branch: this.args.branchName, + buildVersion: this.args.buildVersion, + buildUri: this.args.buildUri, + branchUri: this.args.branchUri, + integrationType: this.args.integrationType, + operatingEnvironment: this.args.operatingEnvironment, + integrationName: this.args.integrationName, + appVersion: this.args.appVersion, + scriptVersion: null, + contributingDeveloperAudit: undefined, + scanType: ScanType.SAST, + toolName: null, + toolVersion: null, + }); + + projectHash = result.projectHash; + branchHash = result.branchHash; + analysisId = result.analysisId; + + soosLogger.info(`Project Hash: ${projectHash}`); + soosLogger.info(`Branch Hash: ${branchHash}`); + soosLogger.info(`Scan Id: ${analysisId}`); + soosLogger.info("Scan created successfully."); + soosLogger.logLineSeparator(); + + soosLogger.info("Uploading SAST File"); + + const formData = await this.getSastAsFormData(filePath); + + await soosAnalysisApiClient.uploadScanToolResult({ + clientId: this.args.clientId, + projectHash, + branchHash, + scanType: ScanType.SAST, + scanId: analysisId, + resultFile: formData, + }); + + soosLogger.info(`Scan result uploaded successfully`); + + soosLogger.logLineSeparator(); + soosLogger.info( + `Analysis scan started successfully, to see the results visit: ${result.scanUrl}` + ); + } catch (error) { + if (projectHash && branchHash && analysisId) + await soosAnalysisApiClient.updateScanStatus({ + clientId: this.args.clientId, + projectHash, + branchHash, + scanType: ScanType.SAST, + scanId: analysisId, + status: ScanStatus.Error, + message: `Error while performing scan.`, + }); + soosLogger.error(error); + exit(1); + } + } + + async getSastAsFormData(filePath: string): Promise { + try { + const fileReadStream = FileSystem.createReadStream(filePath, { + encoding: SOOS_CONSTANTS.FileUploads.Encoding, + }); + + const formData = new FormData(); + formData.append("file", fileReadStream); + return formData; + } catch (error) { + soosLogger.error(`Error on getSastAsFormData: ${error}`); + throw error; + } + } + + async findSASTFilePath(): Promise { + const sastPathStat = await FileSystem.statSync(this.args.sastPath); + + if (sastPathStat.isDirectory()) { + const files = await FileSystem.promises.readdir(this.args.sastPath); + const sastFile = files.find((file) => CONSTANTS.SAST.FILE_REGEX.test(file)); + + if (!sastFile) { + throw new Error("No SAST file found in the directory."); + } + + return Path.join(this.args.sastPath, sastFile); + } + + if (!CONSTANTS.SAST.FILE_REGEX.test(this.args.sastPath)) { + throw new Error("The file does not match the required SAST pattern."); + } + + return this.args.sastPath; + } + + static async createAndRun(): Promise { + soosLogger.info("Starting SOOS SAST Analysis"); + soosLogger.logLineSeparator(); + try { + const args = this.parseArgs(); + soosLogger.setMinLogLevel(args.logLevel); + soosLogger.setVerbose(args.verbose); + soosLogger.info("Configuration read"); + soosLogger.verboseDebug( + JSON.stringify( + obfuscateProperties(args as unknown as Record, ["apiKey"]), + null, + 2 + ) + ); + ensureValue(args.clientId, "clientId"); + ensureValue(args.apiKey, "apiKey"); + soosLogger.logLineSeparator(); + const soosSASTAnalysis = new SOOSSASTAnalysis(args); + await soosSASTAnalysis.runAnalysis(); + } catch (error) { + soosLogger.error(`Error on createAndRun: ${error}`); + exit(1); + } + } +} + +SOOSSASTAnalysis.createAndRun(); diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..8472892 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,10 @@ +export const CONSTANTS = { + SAST: { + FILE_REGEX: /\.sarif\.json$/, + }, + SOOS: { + API_KEY_ENV_VAR: "SOOS_API_KEY", + CLIENT_ID_ENV_VAR: "SOOS_CLIENT_ID", + DEFAULT_INTEGRATION_TYPE: "Script", + }, +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1242bdb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +/* Visit https://aka.ms/tsconfig.json to read more about this file */ +{ + "compilerOptions": { + "target": "ES2021", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "outDir": "./bin", /* Redirect output structure to the directory. */ + "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "removeComments": true, /* Do not emit comments to output. */ + "importHelpers": true, /* Import emit helpers from 'tslib'. */ + "strict": true, /* Enable all strict type-checking options. */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "skipLibCheck": false, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +}