diff --git a/.github/actions/scan-secrets/action.yaml b/.github/actions/scan-secrets/action.yaml new file mode 100644 index 000000000..d555ef177 --- /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 \ No newline at end of file diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 000000000..141f0ed5a --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,3 @@ +# SEE: https://github.com/gitleaks/gitleaks/blob/master/README.md#gitleaksignore + +cd9c0efec38c5d63053dd865e5d4e207c0760d91:docs/guides/Perform_static_analysis.md:generic-api-key:37 \ No newline at end of file diff --git a/scripts/config/gitleaks.toml b/scripts/config/gitleaks.toml new file mode 100644 index 000000000..7388a6b6f --- /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 100644 index 000000000..de8831c79 --- /dev/null +++ b/scripts/config/pre-commit.yaml @@ -0,0 +1,40 @@ +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 +- repo: local + hooks: + - id: check-file-format + name: Check file format + entry: ./scripts/githooks/check-file-format.sh + args: ["check=staged-changes"] + language: script + pass_filenames: false +- repo: local + hooks: + - id: check-markdown-format + name: Check Markdown format + entry: ./scripts/githooks/check-markdown-format.sh + args: ["check=staged-changes"] + language: script + pass_filenames: false +- repo: local + hooks: + - id: check-english-usage + name: Check English usage + entry: ./scripts/githooks/check-english-usage.sh + args: ["check=staged-changes"] + language: script + pass_filenames: false +- repo: local + hooks: + - id: lint-terraform + name: Lint Terraform + entry: ./scripts/githooks/check-terraform-format.sh + language: script + pass_filenames: false \ No newline at end of file diff --git a/scripts/githooks/scan-secrets.sh b/scripts/githooks/scan-secrets.sh new file mode 100644 index 000000000..be4b7914b --- /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