175 lines
4.9 KiB
Bash
Executable File
175 lines
4.9 KiB
Bash
Executable File
#!/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
|