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
|
||||
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
|
||||
|
||||
`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
|
||||
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`
|
||||
: Disable automatic OpenTofu approval for apply paths that support confirmation.
|
||||
|
||||
|
|
@ -249,6 +281,12 @@ script name.
|
|||
`$HOMELAB_STATE_DIR/tofu-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
|
||||
|
||||
Start a three-worker homelab:
|
||||
|
|
@ -269,6 +307,18 @@ Run focused edge diagnostics:
|
|||
./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:
|
||||
|
||||
```sh
|
||||
|
|
|
|||
42
jeannie
42
jeannie
|
|
@ -5760,6 +5760,31 @@ PY
|
|||
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
|
||||
up)
|
||||
up
|
||||
|
|
@ -5861,6 +5886,21 @@ case "${1:-}" in
|
|||
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
|
||||
;;
|
||||
|
|
@ -5872,7 +5912,7 @@ case "${1:-}" in
|
|||
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
|
||||
;;
|
||||
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