Add defensive security scan commands
This commit is contained in:
parent
45efd87960
commit
4f1c973cb5
|
|
@ -127,6 +127,24 @@ structure.
|
||||||
: Check local AI helper prerequisites, knowledge index, and Ollama availability
|
: Check local AI helper prerequisites, knowledge index, and Ollama availability
|
||||||
when the backstage helper is enabled.
|
when the backstage helper is enabled.
|
||||||
|
|
||||||
|
### Defensive Security
|
||||||
|
|
||||||
|
`security-scan`
|
||||||
|
: Run the default defensive scan set: Trivy repo/IaC scans plus OWASP ZAP
|
||||||
|
baseline passive scans for targets in `security/targets.txt`.
|
||||||
|
|
||||||
|
`security-zap`
|
||||||
|
: Run only OWASP ZAP baseline passive scans against public targets.
|
||||||
|
|
||||||
|
`security-k8s`
|
||||||
|
: Run kube-bench CIS checks against the Debian kubeadm host.
|
||||||
|
|
||||||
|
`security-trivy`
|
||||||
|
: Run only Trivy filesystem and IaC/config scans.
|
||||||
|
|
||||||
|
`security-nuclei`
|
||||||
|
: Run low-rate nuclei HTTP exposure scans against public targets.
|
||||||
|
|
||||||
### Maintenance
|
### Maintenance
|
||||||
|
|
||||||
`state-backup`
|
`state-backup`
|
||||||
|
|
@ -184,6 +202,20 @@ checks already show short output by default.
|
||||||
: DNS name used by `status` and `doctor-preapply` for the Pi-hole resolver
|
: DNS name used by `status` and `doctor-preapply` for the Pi-hole resolver
|
||||||
smoke test. Defaults to `cloudflare.com`.
|
smoke test. Defaults to `cloudflare.com`.
|
||||||
|
|
||||||
|
`SECURITY_REPORT_DIR`
|
||||||
|
: Output directory for defensive security reports. Defaults to
|
||||||
|
`$HOMELAB_STATE_DIR/security-reports`.
|
||||||
|
|
||||||
|
`SECURITY_TARGETS_FILE`
|
||||||
|
: Public target allowlist for ZAP and nuclei scans. Defaults to
|
||||||
|
`security/targets.txt`.
|
||||||
|
|
||||||
|
`SECURITY_ZAP_TARGET`
|
||||||
|
: Optional single URL override for `security-zap`.
|
||||||
|
|
||||||
|
`SECURITY_NUCLEI_TARGET`
|
||||||
|
: Optional single URL override for `security-nuclei`.
|
||||||
|
|
||||||
`LAB_AUTO_APPROVE=false`
|
`LAB_AUTO_APPROVE=false`
|
||||||
: Disable automatic OpenTofu approval for apply paths that support confirmation.
|
: Disable automatic OpenTofu approval for apply paths that support confirmation.
|
||||||
|
|
||||||
|
|
@ -249,6 +281,12 @@ script name.
|
||||||
`$HOMELAB_STATE_DIR/tofu-state-backups`
|
`$HOMELAB_STATE_DIR/tofu-state-backups`
|
||||||
: Local OpenTofu and generated state backups.
|
: Local OpenTofu and generated state backups.
|
||||||
|
|
||||||
|
`$HOMELAB_STATE_DIR/security-reports`
|
||||||
|
: Defensive scan reports.
|
||||||
|
|
||||||
|
`security/targets.txt`
|
||||||
|
: Public target allowlist for defensive web scans.
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
Start a three-worker homelab:
|
Start a three-worker homelab:
|
||||||
|
|
@ -269,6 +307,18 @@ Run focused edge diagnostics:
|
||||||
./jeannie doctor-edge
|
./jeannie doctor-edge
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run the default defensive scan set:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./jeannie security-scan
|
||||||
|
```
|
||||||
|
|
||||||
|
Run a ZAP baseline scan against one URL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
SECURITY_ZAP_TARGET=https://lab2025.duckdns.org/ ./jeannie security-zap
|
||||||
|
```
|
||||||
|
|
||||||
Run the blocking pre-apply doctor directly:
|
Run the blocking pre-apply doctor directly:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
|
||||||
42
jeannie
42
jeannie
|
|
@ -5760,6 +5760,31 @@ PY
|
||||||
echo "AI check passed."
|
echo "AI check passed."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
security_scan() {
|
||||||
|
require_debian_server "security-scan"
|
||||||
|
"${REPO_ROOT}/scripts/security-scan" all
|
||||||
|
}
|
||||||
|
|
||||||
|
security_zap() {
|
||||||
|
require_debian_server "security-zap"
|
||||||
|
"${REPO_ROOT}/scripts/security-scan" zap
|
||||||
|
}
|
||||||
|
|
||||||
|
security_k8s() {
|
||||||
|
require_debian_server "security-k8s"
|
||||||
|
"${REPO_ROOT}/scripts/security-scan" kube-bench
|
||||||
|
}
|
||||||
|
|
||||||
|
security_trivy() {
|
||||||
|
require_debian_server "security-trivy"
|
||||||
|
"${REPO_ROOT}/scripts/security-scan" trivy
|
||||||
|
}
|
||||||
|
|
||||||
|
security_nuclei() {
|
||||||
|
require_debian_server "security-nuclei"
|
||||||
|
"${REPO_ROOT}/scripts/security-scan" nuclei
|
||||||
|
}
|
||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
up)
|
up)
|
||||||
up
|
up
|
||||||
|
|
@ -5861,6 +5886,21 @@ case "${1:-}" in
|
||||||
ai-check)
|
ai-check)
|
||||||
ai_check
|
ai_check
|
||||||
;;
|
;;
|
||||||
|
security-scan)
|
||||||
|
security_scan
|
||||||
|
;;
|
||||||
|
security-zap)
|
||||||
|
security_zap
|
||||||
|
;;
|
||||||
|
security-k8s)
|
||||||
|
security_k8s
|
||||||
|
;;
|
||||||
|
security-trivy)
|
||||||
|
security_trivy
|
||||||
|
;;
|
||||||
|
security-nuclei)
|
||||||
|
security_nuclei
|
||||||
|
;;
|
||||||
openwrt)
|
openwrt)
|
||||||
openwrt
|
openwrt
|
||||||
;;
|
;;
|
||||||
|
|
@ -5872,7 +5912,7 @@ case "${1:-}" in
|
||||||
echo "Log: ${JEANNIE_LOG_FILE}"
|
echo "Log: ${JEANNIE_LOG_FILE}"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|openwrt|nuke}"
|
echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-zap|security-k8s|security-trivy|security-nuclei|openwrt|nuke}"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
HOMELAB_STATE_DIR="${HOMELAB_STATE_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/homelab}"
|
||||||
|
SECURITY_REPORT_DIR="${SECURITY_REPORT_DIR:-${HOMELAB_STATE_DIR}/security-reports}"
|
||||||
|
SECURITY_TARGETS_FILE="${SECURITY_TARGETS_FILE:-${REPO_ROOT}/security/targets.txt}"
|
||||||
|
TRIVY_IMAGE="${TRIVY_IMAGE:-aquasec/trivy:latest}"
|
||||||
|
ZAP_IMAGE="${ZAP_IMAGE:-ghcr.io/zaproxy/zaproxy:stable}"
|
||||||
|
KUBE_BENCH_IMAGE="${KUBE_BENCH_IMAGE:-aquasec/kube-bench:latest}"
|
||||||
|
NUCLEI_IMAGE="${NUCLEI_IMAGE:-projectdiscovery/nuclei:latest}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: scripts/security-scan {all|trivy|zap|kube-bench|nuclei}
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
SECURITY_REPORT_DIR Report output directory.
|
||||||
|
SECURITY_TARGETS_FILE Target allowlist for web scans.
|
||||||
|
SECURITY_ZAP_TARGET Single URL override for ZAP.
|
||||||
|
SECURITY_NUCLEI_TARGET Single URL override for nuclei.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp() {
|
||||||
|
date -u +%Y%m%dT%H%M%SZ
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_report_dir() {
|
||||||
|
mkdir -p "${SECURITY_REPORT_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_docker() {
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "docker is required for containerized security scans." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_trivy() {
|
||||||
|
local stamp
|
||||||
|
stamp="$(timestamp)"
|
||||||
|
ensure_report_dir
|
||||||
|
|
||||||
|
echo "Running Trivy filesystem and IaC/config scans..."
|
||||||
|
if command -v trivy >/dev/null 2>&1; then
|
||||||
|
trivy fs --scanners vuln,secret,misconfig --format table --output "${SECURITY_REPORT_DIR}/trivy-fs-${stamp}.txt" "${REPO_ROOT}"
|
||||||
|
trivy config --format table --output "${SECURITY_REPORT_DIR}/trivy-config-${stamp}.txt" "${REPO_ROOT}"
|
||||||
|
else
|
||||||
|
require_docker
|
||||||
|
docker run --rm \
|
||||||
|
-v "${REPO_ROOT}:/repo:ro" \
|
||||||
|
-v "${SECURITY_REPORT_DIR}:/reports" \
|
||||||
|
"${TRIVY_IMAGE}" fs --scanners vuln,secret,misconfig --format table --output "/reports/trivy-fs-${stamp}.txt" /repo
|
||||||
|
docker run --rm \
|
||||||
|
-v "${REPO_ROOT}:/repo:ro" \
|
||||||
|
-v "${SECURITY_REPORT_DIR}:/reports" \
|
||||||
|
"${TRIVY_IMAGE}" config --format table --output "/reports/trivy-config-${stamp}.txt" /repo
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Trivy reports written to ${SECURITY_REPORT_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_targets() {
|
||||||
|
local target_override="$1"
|
||||||
|
|
||||||
|
if [[ -n "${target_override}" ]]; then
|
||||||
|
printf '%s\n' "${target_override}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -s "${SECURITY_TARGETS_FILE}" ]]; then
|
||||||
|
echo "Missing target file: ${SECURITY_TARGETS_FILE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -e 's/#.*//' -e '/^[[:space:]]*$/d' "${SECURITY_TARGETS_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_target_name() {
|
||||||
|
printf '%s' "$1" | sed -E 's#^https?://##; s#[^A-Za-z0-9._-]+#_#g; s#_+$##'
|
||||||
|
}
|
||||||
|
|
||||||
|
run_zap() {
|
||||||
|
local stamp
|
||||||
|
local target
|
||||||
|
local safe_name
|
||||||
|
stamp="$(timestamp)"
|
||||||
|
ensure_report_dir
|
||||||
|
require_docker
|
||||||
|
|
||||||
|
echo "Running OWASP ZAP baseline passive scans..."
|
||||||
|
while IFS= read -r target; do
|
||||||
|
[[ -n "${target}" ]] || continue
|
||||||
|
safe_name="$(safe_target_name "${target}")"
|
||||||
|
echo "--> ZAP baseline ${target}"
|
||||||
|
docker run --rm \
|
||||||
|
-v "${SECURITY_REPORT_DIR}:/zap/wrk:rw" \
|
||||||
|
"${ZAP_IMAGE}" zap-baseline.py \
|
||||||
|
-t "${target}" \
|
||||||
|
-I \
|
||||||
|
-r "zap-${safe_name}-${stamp}.html" \
|
||||||
|
-J "zap-${safe_name}-${stamp}.json"
|
||||||
|
done < <(scan_targets "${SECURITY_ZAP_TARGET:-}")
|
||||||
|
|
||||||
|
echo "ZAP reports written to ${SECURITY_REPORT_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_nuclei() {
|
||||||
|
local stamp
|
||||||
|
local target
|
||||||
|
local safe_name
|
||||||
|
stamp="$(timestamp)"
|
||||||
|
ensure_report_dir
|
||||||
|
require_docker
|
||||||
|
|
||||||
|
echo "Running nuclei low-noise HTTP exposure scans..."
|
||||||
|
while IFS= read -r target; do
|
||||||
|
[[ -n "${target}" ]] || continue
|
||||||
|
safe_name="$(safe_target_name "${target}")"
|
||||||
|
echo "--> nuclei ${target}"
|
||||||
|
docker run --rm \
|
||||||
|
-v "${SECURITY_REPORT_DIR}:/reports:rw" \
|
||||||
|
"${NUCLEI_IMAGE}" \
|
||||||
|
-u "${target}" \
|
||||||
|
-severity low,medium,high,critical \
|
||||||
|
-rate-limit "${SECURITY_NUCLEI_RATE_LIMIT:-5}" \
|
||||||
|
-retries 1 \
|
||||||
|
-timeout 5 \
|
||||||
|
-o "/reports/nuclei-${safe_name}-${stamp}.txt"
|
||||||
|
done < <(scan_targets "${SECURITY_NUCLEI_TARGET:-}")
|
||||||
|
|
||||||
|
echo "nuclei reports written to ${SECURITY_REPORT_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_kube_bench() {
|
||||||
|
local stamp
|
||||||
|
stamp="$(timestamp)"
|
||||||
|
ensure_report_dir
|
||||||
|
require_docker
|
||||||
|
|
||||||
|
echo "Running kube-bench CIS checks against this kubeadm host..."
|
||||||
|
docker run --rm --pid=host \
|
||||||
|
-v /etc:/etc:ro \
|
||||||
|
-v /var:/var:ro \
|
||||||
|
-v /usr/bin:/usr/local/mount-from-host/bin:ro \
|
||||||
|
"${KUBE_BENCH_IMAGE}" run --targets master,node | tee "${SECURITY_REPORT_DIR}/kube-bench-${stamp}.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-all}" in
|
||||||
|
all)
|
||||||
|
run_trivy
|
||||||
|
run_zap
|
||||||
|
;;
|
||||||
|
trivy)
|
||||||
|
run_trivy
|
||||||
|
;;
|
||||||
|
zap)
|
||||||
|
run_zap
|
||||||
|
;;
|
||||||
|
nuclei)
|
||||||
|
run_nuclei
|
||||||
|
;;
|
||||||
|
kube-bench)
|
||||||
|
run_kube_bench
|
||||||
|
;;
|
||||||
|
-h | --help | help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Defensive Security Scans
|
||||||
|
|
||||||
|
This directory contains defensive scan configuration for the homelab and public
|
||||||
|
website. These checks are for assets owned by this lab only.
|
||||||
|
|
||||||
|
Use Jeannie from the Debian homelab host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./jeannie security-scan
|
||||||
|
./jeannie security-zap
|
||||||
|
./jeannie security-k8s
|
||||||
|
```
|
||||||
|
|
||||||
|
The default `security-scan` runs:
|
||||||
|
|
||||||
|
- Trivy filesystem and IaC/config scans against this repo.
|
||||||
|
- OWASP ZAP baseline passive scans against `security/targets.txt`.
|
||||||
|
|
||||||
|
The focused commands are:
|
||||||
|
|
||||||
|
- `security-zap`: OWASP ZAP baseline scan only.
|
||||||
|
- `security-k8s`: kube-bench CIS checks against the kubeadm host.
|
||||||
|
- `security-trivy`: Trivy repo and IaC/config scans only.
|
||||||
|
- `security-nuclei`: low-rate nuclei HTTP exposure scans only.
|
||||||
|
|
||||||
|
Reports are written to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
${HOMELAB_STATE_DIR}/security-reports
|
||||||
|
```
|
||||||
|
|
||||||
|
The ZAP scan uses baseline/passive mode. Do not run active attack scans against
|
||||||
|
public endpoints unless you intentionally schedule a maintenance window and
|
||||||
|
understand the traffic it will generate.
|
||||||
|
|
||||||
|
`security/targets.txt` is the public target allowlist. Keep it limited to
|
||||||
|
services you own.
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Public targets for defensive baseline scans.
|
||||||
|
# Keep this list scoped to services you own.
|
||||||
|
https://lab2025.duckdns.org/
|
||||||
|
https://lab2025.duckdns.org/git/
|
||||||
|
https://demos.lab2025.duckdns.org/
|
||||||
|
https://heimdall.lab2025.duckdns.org/
|
||||||
|
https://argocd.lab2025.duckdns.org/
|
||||||
|
https://grafana.lab2025.duckdns.org/
|
||||||
|
https://prometheus.lab2025.duckdns.org/
|
||||||
|
https://alertmanager.lab2025.duckdns.org/
|
||||||
|
https://n8n.lab2025.duckdns.org/
|
||||||
|
https://prowlarr.lab2025.duckdns.org/
|
||||||
|
https://sonarr.lab2025.duckdns.org/
|
||||||
|
https://radarr.lab2025.duckdns.org/
|
||||||
|
https://qbittorrent.lab2025.duckdns.org/
|
||||||
|
https://kapowarr.lab2025.duckdns.org/
|
||||||
|
https://suwayomi.lab2025.duckdns.org/
|
||||||
|
https://maintainerr.lab2025.duckdns.org/
|
||||||
Loading…
Reference in New Issue