From b269a5d616f7ec798b371757475858ee4464707c Mon Sep 17 00:00:00 2001 From: beatzaplenty Date: Thu, 23 Jul 2026 12:21:34 +1000 Subject: [PATCH] ci: add secret scanning for GitHub, Gitea, and local use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .gitleaks.toml — extends gitleaks defaults with Pi-hole-specific rules for pwhash/totp_secret/app_pwhash; allowlists known-safe patterns - .github/workflows/secret-scan.yml — GitHub Actions (full history scan) - .gitea/workflows/secret-scan.yml — Gitea Actions (identical workflow) - scripts/check-secrets.sh — shared runner used by both CI and local; supports --staged-only for pre-commit hook use; falls back to Docker if gitleaks isn't on PATH - scripts/install-hooks.sh — installs pre-commit hook pointing at above - pihole/sanitize-config.sh — redacts pwhash/totp_secret/app_pwhash in pihole.toml in-place before the file is committed - pihole/pull-config.sh — updated to call sanitize-config.sh automatically after every pull so the repo stays clean by default Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01XRzqNDrbnYR22ZgZj1Bg3s --- .gitea/workflows/secret-scan.yml | 28 +++++++++++ .github/workflows/secret-scan.yml | 28 +++++++++++ .gitleaks.toml | 41 ++++++++++++++++ pihole/pull-config.sh | 6 ++- pihole/sanitize-config.sh | 40 ++++++++++++++++ scripts/check-secrets.sh | 80 +++++++++++++++++++++++++++++++ scripts/install-hooks.sh | 17 +++++++ 7 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/secret-scan.yml create mode 100644 .github/workflows/secret-scan.yml create mode 100644 .gitleaks.toml create mode 100755 pihole/sanitize-config.sh create mode 100755 scripts/check-secrets.sh create mode 100755 scripts/install-hooks.sh diff --git a/.gitea/workflows/secret-scan.yml b/.gitea/workflows/secret-scan.yml new file mode 100644 index 0000000..8aae27d --- /dev/null +++ b/.gitea/workflows/secret-scan.yml @@ -0,0 +1,28 @@ +name: Secret Scan + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +jobs: + secret-scan: + name: Scan for secrets and sensitive config + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for gitleaks git-log scan + + - name: Install gitleaks + run: | + GITLEAKS_VERSION="8.21.2" + curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xz -C /usr/local/bin gitleaks + gitleaks version + + - name: Run secret scan + run: bash scripts/check-secrets.sh diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000..8aae27d --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,28 @@ +name: Secret Scan + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +jobs: + secret-scan: + name: Scan for secrets and sensitive config + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for gitleaks git-log scan + + - name: Install gitleaks + run: | + GITLEAKS_VERSION="8.21.2" + curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xz -C /usr/local/bin gitleaks + gitleaks version + + - name: Run secret scan + run: bash scripts/check-secrets.sh diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..2bc8a26 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,41 @@ +# Gitleaks configuration for debian-configuration repo. +# Extends the default ruleset with Pi-hole-specific secret patterns. +# https://github.com/gitleaks/gitleaks + +title = "debian-configuration secret scan" + +[extend] + useDefault = true + +# ── Custom rules ─────────────────────────────────────────────────────────────── + +[[rules]] + id = "pihole-pwhash" + description = "Pi-hole password hash (pihole.toml webserver.api.pwhash)" + regex = '''pwhash\s*=\s*"[^"]{10,}"''' + tags = ["pihole", "password"] + +[[rules]] + id = "pihole-totp-secret" + description = "Pi-hole 2FA TOTP secret" + regex = '''totp_secret\s*=\s*"[^"]{10,}"''' + tags = ["pihole", "2fa"] + +[[rules]] + id = "pihole-app-pwhash" + description = "Pi-hole app password hash" + regex = '''app_pwhash\s*=\s*"[^"]{10,}"''' + tags = ["pihole", "password"] + +# ── Allowlist ────────────────────────────────────────────────────────────────── + +[allowlist] + description = "Known-safe patterns in this repo" + regexes = [ + # TLS cert path reference — not the key itself + '''cert\s*=\s*"/etc/pihole/tls\.pem"''', + ] + paths = [ + # Example/template files are intentionally non-live + '''\.example$''', + ] diff --git a/pihole/pull-config.sh b/pihole/pull-config.sh index 89612b9..27dbf6a 100755 --- a/pihole/pull-config.sh +++ b/pihole/pull-config.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Pull Pi-hole configuration from a running instance to a local directory. +# Sensitive fields (password hashes, TOTP secrets) are redacted automatically. # # Usage: pull-config.sh # source-host SSH-reachable hostname or IP of the source Pi-hole @@ -7,10 +8,12 @@ # # Example: # ./pull-config.sh root@pihole ./config -# ./pull-config.sh root@192.168.2.100 /backup/pihole-$(date +%Y%m%d) +# ./pull-config.sh root@192.168.2.253 /backup/pihole-$(date +%Y%m%d) set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + usage() { echo "Usage: $(basename "$0") " >&2 echo " source-host SSH target for the source Pi-hole (e.g. root@pihole)" >&2 @@ -30,6 +33,7 @@ mkdir -p "${DEST}/dnsmasq.d" # ── pihole.toml ──────────────────────────────────────────────────────────────── echo " pihole.toml" ssh "${SOURCE}" "cat /etc/pihole/pihole.toml" > "${DEST}/pihole.toml" +"${SCRIPT_DIR}/sanitize-config.sh" "${DEST}/pihole.toml" # ── custom dnsmasq drop-ins ──────────────────────────────────────────────────── # Pi-hole manages its own generated config; we only capture user-added files. diff --git a/pihole/sanitize-config.sh b/pihole/sanitize-config.sh new file mode 100755 index 0000000..f7be72a --- /dev/null +++ b/pihole/sanitize-config.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Redact sensitive fields from a Pi-hole pihole.toml before committing. +# Called automatically by pull-config.sh; can also be run manually. +# +# Usage: sanitize-config.sh +# Edits the file in-place, replacing sensitive field values with "". + +set -euo pipefail + +usage() { + echo "Usage: $(basename "$0") " >&2 + exit 1 +} + +[[ $# -eq 1 ]] || usage +FILE="$1" +[[ -f "$FILE" ]] || { echo "Error: file not found: $FILE" >&2; exit 1; } + +REDACTED=0 + +redact_field() { + local field="$1" + # Match lines like: pwhash = "some-value" and blank the value + if grep -qE "^\s+${field}\s*=\s*\"[^\"]{1,}\"" "$FILE"; then + sed -i -E "s|^(\s+${field}\s*=\s*)\"[^\"]*\"|\1\"\"|" "$FILE" + echo " redacted: ${field}" + REDACTED=$((REDACTED + 1)) + fi +} + +echo "Sanitizing $(basename "$FILE")..." +redact_field "pwhash" +redact_field "app_pwhash" +redact_field "totp_secret" + +if [[ $REDACTED -eq 0 ]]; then + echo " (nothing to redact)" +else + echo " ${REDACTED} field(s) redacted." +fi diff --git a/scripts/check-secrets.sh b/scripts/check-secrets.sh new file mode 100755 index 0000000..047960f --- /dev/null +++ b/scripts/check-secrets.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Scan the repo for secrets and sensitive config values. +# Runs via CI (GitHub/Gitea Actions) and locally as a pre-commit check. +# +# Usage: scripts/check-secrets.sh [--staged-only] +# --staged-only Only check files staged for commit (for pre-commit hook use) +# +# Requires gitleaks on PATH, or falls back to Docker if available. +# Install gitleaks: https://github.com/gitleaks/gitleaks#installing + +set -euo pipefail + +REPO_ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" +STAGED_ONLY=false +FAILURES=0 + +for arg in "$@"; do + [[ "$arg" == "--staged-only" ]] && STAGED_ONLY=true +done + +cd "$REPO_ROOT" + +# ── Resolve gitleaks binary ──────────────────────────────────────────────────── +if command -v gitleaks &>/dev/null; then + GITLEAKS="gitleaks" +elif command -v docker &>/dev/null; then + GITLEAKS="docker run --rm -v ${REPO_ROOT}:/repo zricethezav/gitleaks:latest" + # Adjust paths for docker context + REPO_ROOT="/repo" +else + echo "ERROR: gitleaks not found. Install it or ensure Docker is available." >&2 + echo " https://github.com/gitleaks/gitleaks#installing" >&2 + exit 1 +fi + +echo "=== Secret scan ===" + +if [[ "$STAGED_ONLY" == "true" ]]; then + # Pre-commit mode: scan only staged content + echo "Mode: staged files only" + if ! $GITLEAKS protect --staged --config="${REPO_ROOT}/.gitleaks.toml" --source="${REPO_ROOT}" 2>&1; then + FAILURES=$((FAILURES + 1)) + fi +else + # CI mode: scan full git history + echo "Mode: full git history" + if ! $GITLEAKS detect --config="${REPO_ROOT}/.gitleaks.toml" --source="${REPO_ROOT}" 2>&1; then + FAILURES=$((FAILURES + 1)) + fi +fi + +# ── Pi-hole specific checks ──────────────────────────────────────────────────── +echo "" +echo "=== Pi-hole config checks ===" + +PIHOLE_TOML="${REPO_ROOT}/pihole/config/pihole.toml" + +if [[ -f "$PIHOLE_TOML" ]]; then + # Check that known sensitive fields are empty + for field in pwhash totp_secret app_pwhash; do + value=$(grep -E "^\s+${field}\s*=" "$PIHOLE_TOML" | sed 's/.*=\s*"\(.*\)".*/\1/' | tr -d '[:space:]' || true) + if [[ -n "$value" && "$value" != '""' ]]; then + echo "FAIL: pihole.toml contains a non-empty '${field}' — run pihole/sanitize-config.sh before committing" >&2 + FAILURES=$((FAILURES + 1)) + else + echo " OK: ${field} is empty" + fi + done +else + echo " (pihole/config/pihole.toml not present, skipping Pi-hole checks)" +fi + +# ── Summary ──────────────────────────────────────────────────────────────────── +echo "" +if [[ $FAILURES -gt 0 ]]; then + echo "FAILED: ${FAILURES} issue(s) found. Fix before committing." >&2 + exit 1 +else + echo "All checks passed." +fi diff --git a/scripts/install-hooks.sh b/scripts/install-hooks.sh new file mode 100755 index 0000000..8b17bc2 --- /dev/null +++ b/scripts/install-hooks.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Install git hooks that run the secret scan before every commit. +# Run once after cloning: bash scripts/install-hooks.sh + +set -euo pipefail + +REPO_ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" +HOOK="${REPO_ROOT}/.git/hooks/pre-commit" + +cat > "$HOOK" << 'HOOK' +#!/usr/bin/env bash +exec "$(git rev-parse --show-toplevel)/scripts/check-secrets.sh" --staged-only +HOOK + +chmod +x "$HOOK" +echo "Installed pre-commit hook → ${HOOK}" +echo "The secret scan will run automatically before every commit."