diff --git a/README.md b/README.md index 4599fd1f8e82f10f872f232d2e0aed6fa0a48204..c96370c71582de428bc1639643827db4e5ee3c8e 100644 --- a/README.md +++ b/README.md @@ -351,6 +351,34 @@ It is bound to the `test` stage. The job generates an outdated report that you will find here: `NODE_PROJECT_DIR/reports/npm-outdated-report.json`. +### `node-semgrep` job + +The Node template features a job `node-semgrep` that performs a [Semgrep](https://semgrep.dev/docs/) analysis. + +It is bound to the `test` stage, and uses the following variables: + +| Input / Variable | Description | Default value | +| ----------------------- | -------------------------------------- | ----------------- | +| `semgrep-disabled` / `NODE_SEMGREP_DISABLED` | Set to `true` to disable this job | _none_ | +| `semgrep-image` / `NODE_SEMGREP_IMAGE` | The Docker image used to run [Semgrep](https://semgrep.dev/docs/) | `registry.hub.docker.com/semgrep/semgrep:latest` | +| `semgrep-args` / `NODE_SEMGREP_ARGS` | Semgrep [scan options](https://semgrep.dev/docs/cli-reference#semgrep-scan-command-options) | `--metrics off --disable-version-check` | +| `semgrep-rules` / `NODE_SEMGREP_RULES` | Space-separated list of [Semgrep rules](https://semgrep.dev/docs/running-rules).<br/>Can be both local YAML files or remote rules from the [Semgrep Registry](https://semgrep.dev/explore) (denoted by the `p/` prefix). | `p/javascript p/eslint p/gitlab-eslint` | +| `semgrep-download-rules-enabled` / `NODE_SEMGREP_DOWNLOAD_RULES_ENABLED` | Download Semgrep remote rules | `true` | + +> :information_source: Semgrep may [collect some metrics](https://semgrep.dev/docs/metrics), especially when using rules from the Semgrep Registry. +> To protect your privacy and let you run Semgrep in air-gap environments, this template disables all Semgrep metrics by default: +> +> * rules from the Semgrep registry are pre-downloaded and passed to Semgrep as local rule files (can be disabled by setting `semgrep-download-rules-enabled` / `NODE_SEMGREP_DOWNLOAD_RULES_ENABLED` to `false`), +> * the `--metrics` option is set to `off`, +> * the `--disable-version-check` option is set. + +In addition to a textual report in the console, this job produces the following reports, kept for one day: + +| Report | Format | Usage | +| -------------- | ---------------------------------------------------------------------------- | ----------------- | +| `$NODE_PROJECT_DIR/reports/node-semgrep.gitlab.json` | [GitLab's SAST format](https://semgrep.dev/docs/cli-reference#semgrep-scan-command-options) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportssast) | +| `$NODE_PROJECT_DIR/reports/node-semgrep.native.json` | [Semgrep's JSON format](https://semgrep.dev/docs/cli-reference#semgrep-scan-command-options) | [DefectDojo integration](https://documentation.defectdojo.com/integrations/parsers/file/semgrep)<br/>_This report is generated only if DefectDojo template is detected_ | + ### `node-sbom` job This job generates a [SBOM](https://cyclonedx.org/) file listing installed packages using [@cyclonedx/cyclonedx-npm](https://www.npmjs.com/package/@cyclonedx/cyclonedx-npm). diff --git a/kicker.json b/kicker.json index f73226525f52f926de0d87ddccccb38769e1ea5b..191569ca6f705355e6d71ef4be6dd9ebb1f8d6e7 100644 --- a/kicker.json +++ b/kicker.json @@ -107,6 +107,35 @@ } ] }, + { + "id": "node-semgrep", + "name": "Semgrep", + "description": "[Semgrep](https://semgrep.dev/docs/) analysis", + "disable_with": "NODE_SEMGREP_DISABLED", + "variables": [ + { + "name": "NODE_SEMGREP_IMAGE", + "description": "The Docker image used to run [Semgrep](https://semgrep.dev/docs/)", + "default": "registry.hub.docker.com/semgrep/semgrep:latest" + }, + { + "name": "NODE_SEMGREP_ARGS", + "description": "Semgrep [scan options](https://semgrep.dev/docs/cli-reference#semgrep-scan-command-options)", + "default": "--metrics off --disable-version-check" + }, + { + "name": "NODE_SEMGREP_RULES", + "description": "Space-separated list of [Semgrep rules](https://semgrep.dev/docs/running-rules).\n\nCan be both local YAML files or remote rules from the [Semgrep Registry](https://semgrep.dev/explore) (denoted by the `p/` prefix).", + "default": "p/javascript p/eslint p/gitlab-eslint" + }, + { + "name": "NODE_SEMGREP_DOWNLOAD_RULES_ENABLED", + "description": "Download Semgrep remote rules", + "type": "boolean", + "default": "true" + } + ] + }, { "id": "sbom", "name": "Software Bill of Materials", diff --git a/templates/gitlab-ci-node.yml b/templates/gitlab-ci-node.yml index 5c3440ceb944b5ddda6a62341621dff8f5cfdd76..b2c90ebf98ac864cc7c71d98d140f7fe8b6e376c 100644 --- a/templates/gitlab-ci-node.yml +++ b/templates/gitlab-ci-node.yml @@ -40,7 +40,7 @@ spec: description: Space separated list of NPM [scoped registries](https://docs.npmjs.com/cli/v8/using-npm/scope#associating-a-scope-with-a-registry) (formatted as `@somescope:https://some.npm.registry/some/repo @anotherscope:https://another.npm.registry/another/repo`) default: '' build-args: - description: | + description: |- npm [run script](https://docs.npmjs.com/cli/v8/commands/npm-run-script) arguments - yarn [run script](https://classic.yarnpkg.com/en/docs/cli/run) arguments - pnpm [run script](https://pnpm.io/cli/run) arguments ⚠ default value should be overridden for `pnpm` as `--prod` is not a valid option @@ -68,6 +68,26 @@ spec: audit-args: description: npm [audit](https://docs.npmjs.com/cli/v8/commands/npm-audit) arguments - yarn [audit](https://classic.yarnpkg.com/en/docs/cli/audit) arguments - [pnpm audit](https://pnpm.io/cli/audit) arguments default: --audit-level=low + semgrep-image: + description: The Docker image used to run [Semgrep](https://semgrep.dev/docs/) + default: registry.hub.docker.com/semgrep/semgrep:latest + semgrep-disabled: + description: Disable Semgrep + type: boolean + default: false + semgrep-args: + description: Semgrep [scan options](https://semgrep.dev/docs/cli-reference#semgrep-scan-command-options) + default: --metrics off --disable-version-check + semgrep-rules: + description: |- + Space-separated list of [Semgrep rules](https://semgrep.dev/docs/running-rules). + + Can be both local YAML files or remote rules from the [Semgrep Registry](https://semgrep.dev/explore) (denoted by the `p/` prefix). + default: p/javascript p/eslint p/gitlab-eslint + semgrep-download-rules-enabled: + description: Download Semgrep remote rules + type: boolean + default: true outdated-disabled: description: Disable node outdated type: boolean @@ -161,6 +181,12 @@ variables: # Audit NODE_AUDIT_DISABLED: $[[ inputs.audit-disabled ]] NODE_AUDIT_ARGS: $[[ inputs.audit-args ]] + # Semgrep + NODE_SEMGREP_IMAGE: $[[ inputs.semgrep-image ]] + NODE_SEMGREP_DISABLED: $[[ inputs.semgrep-disabled ]] + NODE_SEMGREP_ARGS: $[[ inputs.semgrep-args ]] + NODE_SEMGREP_RULES: $[[ inputs.semgrep-rules ]] + NODE_SEMGREP_DOWNLOAD_RULES_ENABLED: $[[ inputs.semgrep-download-rules-enabled ]] # Outdated NODE_OUTDATED_DISABLED: $[[ inputs.outdated-disabled ]] NODE_OUTDATED_ARGS: $[[ inputs.outdated-args ]] @@ -543,6 +569,31 @@ stages: fi } + function setup_semgrep_rules() { + if [[ "${NODE_SEMGREP_DOWNLOAD_RULES_ENABLED}" == "true" ]] + then + log_info "Download Semgrep rule files..." + for rule in $NODE_SEMGREP_RULES + do + if [[ -r "$rule" ]] + then + log_info "... rule file $rule found: skip" + SEMGREP_RULES="${SEMGREP_RULES} $rule" + else + log_info "... rule file $rule not found: download (https://semgrep.dev/c/$rule)" + dest_file="semgrep-${rule/p\//}.yml" + wget "https://semgrep.dev/c/$rule" -O "$dest_file" + SEMGREP_RULES="${SEMGREP_RULES} $dest_file" + fi + done + SEMGREP_RULES="${SEMGREP_RULES:1}" + export SEMGREP_RULES + else + # download not enabled: simply use $NODE_SEMGREP_RULES + export SEMGREP_RULES="${NODE_SEMGREP_RULES}" + fi + } + unscope_variables eval_all_secrets @@ -701,6 +752,38 @@ node-outdated: - when: manual allow_failure: true +# SAST: Semgrep +node-semgrep: + extends: .node-base + image: $NODE_SEMGREP_IMAGE + # unset cache from parent job + cache: {} + dependencies: [] + stage: test + before_script: + - *node-scripts + - cd $NODE_PROJECT_DIR + - mkdir -p -m 777 reports + - setup_semgrep_rules + script: + - >- + semgrep ci ${TRACE+--verbose} ${NODE_SEMGREP_ARGS} + --gitlab-sast-output=reports/node-semgrep.gitlab.json + ${DEFECTDOJO_SEMGREP_REPORTS:+--json-output=reports/node-semgrep.native.json} + artifacts: + name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" + when: always + expire_in: 1 week + reports: + sast: $NODE_PROJECT_DIR/reports/node-semgrep.gitlab.json + paths: + - $NODE_PROJECT_DIR/reports/node-semgrep.* + rules: + # exclude if disable + - if: '$NODE_SEMGREP_DISABLED == "true"' + when: never + - !reference [.test-policy, rules] + node-sbom: extends: .node-base stage: test