diff --git a/.github/actions/scan-secrets/action.yaml b/.github/actions/scan-secrets/action.yaml new file mode 100644 index 0000000..1ed8bac --- /dev/null +++ b/.github/actions/scan-secrets/action.yaml @@ -0,0 +1,10 @@ +name: "Scan secrets" +description: "Scan secrets" +runs: + using: "composite" + steps: + - name: "Scan secrets" + shell: bash + run: | + # Please do not change this `check=whole-history` setting, as new patterns may be added or history may be rewritten. + check=whole-history ./scripts/githooks/scan-secrets.sh diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8d85c58..d5644b4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,13 +7,13 @@ updates: - package-ecosystem: "pip" directory: "/" schedule: - interval: "daily" + interval: "weekly" target-branch: "master" - labels: ["dependencies", "python", "poetry"] + labels: [ "dependencies", "python", "poetry" ] open-pull-requests-limit: 10 ignore: - dependency-name: "*" - update-types: ["version-update:semver-major"] + update-types: [ "version-update:semver-major" ] # --------------------------- # NodeJS (root) @@ -21,13 +21,13 @@ updates: - package-ecosystem: "npm" directory: "/" schedule: - interval: "daily" + interval: "weekly" target-branch: "master" - labels: ["dependencies", "npm"] + labels: [ "dependencies", "npm" ] open-pull-requests-limit: 10 ignore: - dependency-name: "*" - update-types: ["version-update:semver-major"] + update-types: [ "version-update:semver-major" ] # --------------------------- # NodeJS (sandbox/) @@ -35,13 +35,13 @@ updates: - package-ecosystem: "npm" directory: "/sandbox" schedule: - interval: "daily" + interval: "weekly" target-branch: "master" - labels: ["dependencies", "npm", "sandbox"] + labels: [ "dependencies", "npm", "sandbox" ] open-pull-requests-limit: 10 ignore: - dependency-name: "*" - update-types: ["version-update:semver-major"] + update-types: [ "version-update:semver-major" ] # --------------------------- # GitHub Actions @@ -49,6 +49,8 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" target-branch: "master" - labels: ["dependencies", "github-actions"] \ No newline at end of file + labels: [ "dependencies", "github-actions" ] + cooldown: + default-days: 7 diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c9a0653..f18979d 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -8,12 +8,12 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: 0 # This causes all history to be fetched, which is required for calculate-version to function + fetch-depth: 0 # This causes all history to be fetched, which is required for calculate-version to function - name: Install Python 3.9 - uses: actions/setup-python@v5 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: 3.9 @@ -30,33 +30,35 @@ jobs: run: pip install "poetry<2.0.0" - name: Cache poetry packages - uses: actions/cache@v4 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ~/.cache/pypoetry - key: ${{ runner.os }}-build-cache-poetry-packages-${{ hashFiles('**/poetry.lock') }} + key: ${{ runner.os }}-build-cache-poetry-packages-${{ + hashFiles('**/poetry.lock') }} - name: Cache node modules - uses: actions/cache@v4 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ~/.npm - key: ${{ runner.os }}-build-cache-npm-packages-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-build-cache-npm-packages-${{ + hashFiles('**/package-lock.json') }} - name: Install repo run: make install - name: Set SPEC_VERSION env var - run: echo ::set-env name=SPEC_VERSION::$(poetry run python scripts/calculate_version.py) + run: echo ::set-env name=SPEC_VERSION::$(poetry run python + scripts/calculate_version.py) env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true - name: Create release (master only) id: create-release if: github.ref == 'refs/heads/master' - uses: actions/create-release@v1 + uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4 continue-on-error: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ env.SPEC_VERSION }} release_name: ${{ env.SPEC_VERSION }} - diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 7b57a5d..39051b0 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -24,10 +24,10 @@ jobs: contents: write steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Python 3.13 - uses: actions/setup-python@v5 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.13" @@ -69,7 +69,7 @@ jobs: python .github/scripts/sbom_json_to_csv.py sbom.json SBOM_${REPO_NAME}.csv - name: Upload SBOM CSV as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: sbom-csv path: SBOM_${{ github.event.repository.name }}.csv @@ -81,8 +81,6 @@ jobs: - name: Scan SBOM for Vulnerabilities (JSON) run: | grype sbom:sbom.json -o json > grype-report.json - - - name: Convert Grype JSON to CSV run: | @@ -90,9 +88,8 @@ jobs: REPO_NAME=$(basename $GITHUB_REPOSITORY) python .github/scripts/grype_json_to_csv.py grype-report.json grype-report-${REPO_NAME}.csv - - name: Upload Vulnerability Report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: grype-report path: grype-report-${{ github.event.repository.name }}.csv @@ -104,7 +101,7 @@ jobs: python .github/scripts/sbom_packages_to_csv.py sbom.json $REPO_NAME - name: Upload Package Inventory CSV - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: sbom-packages - path: sbom-packages-${{ github.event.repository.name }}.csv \ No newline at end of file + path: sbom-packages-${{ github.event.repository.name }}.csv diff --git a/scripts/config/gitleaks.toml b/scripts/config/gitleaks.toml new file mode 100755 index 0000000..7388a6b --- /dev/null +++ b/scripts/config/gitleaks.toml @@ -0,0 +1,19 @@ +# SEE: https://github.com/gitleaks/gitleaks/#configuration + +[extend] +useDefault = true # SEE: https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml + +[[rules]] +description = "IPv4" +id = "ipv4" +regex = '''[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}''' + +[rules.allowlist] +regexTarget = "match" +regexes = [ + # Exclude the private network IPv4 addresses as well as the DNS servers for Google and OpenDNS + '''(127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|172\.(1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3}|0\.0\.0\.0|255\.255\.255\.255|8\.8\.8\.8|8\.8\.4\.4|208\.67\.222\.222|208\.67\.220\.220)''', +] + +[allowlist] +paths = ['''.terraform.lock.hcl''', '''poetry.lock''', '''yarn.lock'''] \ No newline at end of file diff --git a/scripts/config/pre-commit.yaml b/scripts/config/pre-commit.yaml new file mode 100755 index 0000000..9c62fca --- /dev/null +++ b/scripts/config/pre-commit.yaml @@ -0,0 +1,9 @@ +repos: + - repo: local + hooks: + - id: scan-secrets + name: Scan secrets + entry: ./scripts/githooks/scan-secrets.sh + args: [ "check=staged-changes" ] + language: script + pass_filenames: false diff --git a/scripts/githooks/scan-secrets.sh b/scripts/githooks/scan-secrets.sh new file mode 100644 index 0000000..be4b791 --- /dev/null +++ b/scripts/githooks/scan-secrets.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +# WARNING: Please, DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead. + +set -euo pipefail + +# Pre-commit git hook to scan for secrets hard-coded in the codebase. This is a +# gitleaks command wrapper. It will run gitleaks natively if it is installed, +# otherwise it will run it in a Docker container. +# +# Usage: +# $ [options] ./scan-secrets.sh +# +# Options: +# check={whole-history,last-commit,staged-changes} # Type of the check to run, default is 'staged-changes' +# FORCE_USE_DOCKER=true # If set to true the command is run in a Docker container, default is 'false' +# VERBOSE=true # Show all the executed commands, default is 'false' +# +# Exit codes: +# 0 - No leaks present +# 1 - Leaks or error encountered +# 126 - Unknown flag + +# ============================================================================== + +function main() { + + cd "$(git rev-parse --show-toplevel)" + + if command -v gitleaks > /dev/null 2>&1 && ! is-arg-true "${FORCE_USE_DOCKER:-false}"; then + dir="$PWD" + cmd="$(get-cmd-to-run)" run-gitleaks-natively + else + dir="/workdir" + cmd="$(get-cmd-to-run)" run-gitleaks-in-docker + fi +} + +# Get Gitleaks command to execute and configuration. +# Arguments (provided as environment variables): +# dir=[project's top-level directory] +function get-cmd-to-run() { + + check=${check:-staged-changes} + case $check in + "whole-history") + cmd="detect --source $dir --verbose --redact" + ;; + "last-commit") + cmd="detect --source $dir --verbose --redact --log-opts -1" + ;; + "staged-changes") + cmd="protect --source $dir --verbose --staged" + ;; + esac + # Include base line file if it exists + if [ -f "$dir/scripts/config/.gitleaks-baseline.json" ]; then + cmd="$cmd --baseline-path $dir/scripts/config/.gitleaks-baseline.json" + fi + # Include the config file + cmd="$cmd --config $dir/scripts/config/gitleaks.toml" + + echo "$cmd" +} + +# Run Gitleaks natively. +# Arguments (provided as environment variables): +# cmd=[command to run] +function run-gitleaks-natively() { + + # shellcheck disable=SC2086 + gitleaks $cmd +} + +# Run Gitleaks in a Docker container. +# Arguments (provided as environment variables): +# cmd=[command to run] +# dir=[directory to mount as a volume] +function run-gitleaks-in-docker() { + + # shellcheck disable=SC1091 + source ./scripts/docker/docker.lib.sh + + # shellcheck disable=SC2155 + local image=$(name=ghcr.io/gitleaks/gitleaks docker-get-image-version-and-pull) + # shellcheck disable=SC2086 + docker run --rm --platform linux/amd64 \ + --volume "$PWD:$dir" \ + --workdir $dir \ + "$image" \ + $cmd +} + +# ============================================================================== + +function is-arg-true() { + + if [[ "$1" =~ ^(true|yes|y|on|1|TRUE|YES|Y|ON)$ ]]; then + return 0 + else + return 1 + fi +} + +# ============================================================================== + +is-arg-true "${VERBOSE:-false}" && set -x + +main "$@" + +exit 0 \ No newline at end of file