Archived
ci: add secret scanning for GitHub, Gitea, and local use
- .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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XRzqNDrbnYR22ZgZj1Bg3s
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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$''',
|
||||||
|
]
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Pull Pi-hole configuration from a running instance to a local directory.
|
# 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> <dest-dir>
|
# Usage: pull-config.sh <source-host> <dest-dir>
|
||||||
# source-host SSH-reachable hostname or IP of the source Pi-hole
|
# source-host SSH-reachable hostname or IP of the source Pi-hole
|
||||||
@@ -7,10 +8,12 @@
|
|||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
# ./pull-config.sh root@pihole ./config
|
# ./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
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "Usage: $(basename "$0") <source-host> <dest-dir>" >&2
|
echo "Usage: $(basename "$0") <source-host> <dest-dir>" >&2
|
||||||
echo " source-host SSH target for the source Pi-hole (e.g. root@pihole)" >&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 ────────────────────────────────────────────────────────────────
|
# ── pihole.toml ────────────────────────────────────────────────────────────────
|
||||||
echo " pihole.toml"
|
echo " pihole.toml"
|
||||||
ssh "${SOURCE}" "cat /etc/pihole/pihole.toml" > "${DEST}/pihole.toml"
|
ssh "${SOURCE}" "cat /etc/pihole/pihole.toml" > "${DEST}/pihole.toml"
|
||||||
|
"${SCRIPT_DIR}/sanitize-config.sh" "${DEST}/pihole.toml"
|
||||||
|
|
||||||
# ── custom dnsmasq drop-ins ────────────────────────────────────────────────────
|
# ── custom dnsmasq drop-ins ────────────────────────────────────────────────────
|
||||||
# Pi-hole manages its own generated config; we only capture user-added files.
|
# Pi-hole manages its own generated config; we only capture user-added files.
|
||||||
|
|||||||
Executable
+40
@@ -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 <pihole.toml>
|
||||||
|
# Edits the file in-place, replacing sensitive field values with "".
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $(basename "$0") <pihole.toml>" >&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
|
||||||
Executable
+80
@@ -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
|
||||||
Executable
+17
@@ -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."
|
||||||
Reference in New Issue
Block a user