Add web security quick check
This commit is contained in:
parent
e1c33ff42e
commit
5aeb953362
|
|
@ -152,6 +152,10 @@ on first. This command is report-only.
|
||||||
`security-nuclei`
|
`security-nuclei`
|
||||||
: Run low-rate nuclei HTTP exposure scans against public targets.
|
: Run low-rate nuclei HTTP exposure scans against public targets.
|
||||||
|
|
||||||
|
`security-web`
|
||||||
|
: Run quick HTTPS, status, and defensive-header checks against owned web
|
||||||
|
targets.
|
||||||
|
|
||||||
### Maintenance
|
### Maintenance
|
||||||
|
|
||||||
`state-backup`
|
`state-backup`
|
||||||
|
|
@ -223,6 +227,9 @@ smoke test. Defaults to `cloudflare.com`.
|
||||||
`SECURITY_NUCLEI_TARGET`
|
`SECURITY_NUCLEI_TARGET`
|
||||||
: Optional single URL override for `security-nuclei`.
|
: Optional single URL override for `security-nuclei`.
|
||||||
|
|
||||||
|
`SECURITY_WEB_TARGET`
|
||||||
|
: Optional single URL override for `security-web`.
|
||||||
|
|
||||||
`lynis`
|
`lynis`
|
||||||
: `security-host` requires Lynis to be installed on the Debian host. Install it
|
: `security-host` requires Lynis to be installed on the Debian host. Install it
|
||||||
explicitly with `sudo apt install lynis`.
|
explicitly with `sudo apt install lynis`.
|
||||||
|
|
|
||||||
10
jeannie
10
jeannie
|
|
@ -5795,6 +5795,11 @@ security_nuclei() {
|
||||||
"${REPO_ROOT}/scripts/security-scan" nuclei
|
"${REPO_ROOT}/scripts/security-scan" nuclei
|
||||||
}
|
}
|
||||||
|
|
||||||
|
security_web() {
|
||||||
|
require_debian_server "security-web"
|
||||||
|
"${REPO_ROOT}/scripts/security-scan" web
|
||||||
|
}
|
||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
up)
|
up)
|
||||||
up
|
up
|
||||||
|
|
@ -5917,6 +5922,9 @@ case "${1:-}" in
|
||||||
security-nuclei)
|
security-nuclei)
|
||||||
security_nuclei
|
security_nuclei
|
||||||
;;
|
;;
|
||||||
|
security-web)
|
||||||
|
security_web
|
||||||
|
;;
|
||||||
openwrt)
|
openwrt)
|
||||||
openwrt
|
openwrt
|
||||||
;;
|
;;
|
||||||
|
|
@ -5928,7 +5936,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|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-nuclei|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-prepare|security-zap|security-k8s|security-host|security-trivy|security-nuclei|security-web|openwrt|nuke}"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,14 @@ truthy() {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: scripts/security-scan {all|prepare|trivy|zap|kube-bench|nuclei|host}
|
Usage: scripts/security-scan {all|prepare|trivy|zap|kube-bench|nuclei|host|web}
|
||||||
|
|
||||||
Environment:
|
Environment:
|
||||||
SECURITY_REPORT_DIR Report output directory.
|
SECURITY_REPORT_DIR Report output directory.
|
||||||
SECURITY_TARGETS_FILE Target allowlist for web scans.
|
SECURITY_TARGETS_FILE Target allowlist for web scans.
|
||||||
SECURITY_ZAP_TARGET Single URL override for ZAP.
|
SECURITY_ZAP_TARGET Single URL override for ZAP.
|
||||||
SECURITY_NUCLEI_TARGET Single URL override for nuclei.
|
SECURITY_NUCLEI_TARGET Single URL override for nuclei.
|
||||||
|
SECURITY_WEB_TARGET Single URL override for web header checks.
|
||||||
LAB_BACKSTAGE_BRAIN_ENABLED=true enables LLM-assisted Lynis triage.
|
LAB_BACKSTAGE_BRAIN_ENABLED=true enables LLM-assisted Lynis triage.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +181,107 @@ run_nuclei() {
|
||||||
echo "nuclei reports written to ${SECURITY_REPORT_DIR}"
|
echo "nuclei reports written to ${SECURITY_REPORT_DIR}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header_value() {
|
||||||
|
local headers_file="$1"
|
||||||
|
local header_name="$2"
|
||||||
|
|
||||||
|
awk -v name="${header_name}" '
|
||||||
|
BEGIN { IGNORECASE = 1 }
|
||||||
|
index($0, name ":") == 1 {
|
||||||
|
sub(/^[^:]+:[[:space:]]*/, "")
|
||||||
|
sub(/\r$/, "")
|
||||||
|
print
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "${headers_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_security_web() {
|
||||||
|
local stamp
|
||||||
|
local target
|
||||||
|
local safe_name
|
||||||
|
local headers_file
|
||||||
|
local body_file
|
||||||
|
local report_file
|
||||||
|
local status
|
||||||
|
local failures=0
|
||||||
|
local missing=0
|
||||||
|
local -a required_headers=(
|
||||||
|
"strict-transport-security"
|
||||||
|
"x-content-type-options"
|
||||||
|
"x-frame-options"
|
||||||
|
"referrer-policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
stamp="$(timestamp)"
|
||||||
|
ensure_report_dir
|
||||||
|
report_file="${SECURITY_REPORT_DIR}/security-web-${stamp}.txt"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "Security web quick check"
|
||||||
|
echo "Generated: ${stamp}"
|
||||||
|
echo
|
||||||
|
} >"${report_file}"
|
||||||
|
|
||||||
|
while IFS= read -r target; do
|
||||||
|
[[ -n "${target}" ]] || continue
|
||||||
|
safe_name="$(safe_target_name "${target}")"
|
||||||
|
headers_file="${SECURITY_REPORT_DIR}/headers-${safe_name}-${stamp}.txt"
|
||||||
|
body_file="${SECURITY_REPORT_DIR}/headers-${safe_name}-${stamp}.body"
|
||||||
|
|
||||||
|
echo "== ${target}" | tee -a "${report_file}"
|
||||||
|
if [[ "${target}" != https://* ]]; then
|
||||||
|
echo "fail: target is not HTTPS" | tee -a "${report_file}"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
status="$(
|
||||||
|
curl -k -sS -L \
|
||||||
|
--connect-timeout "${SECURITY_WEB_CONNECT_TIMEOUT:-8}" \
|
||||||
|
--max-time "${SECURITY_WEB_MAX_TIME:-20}" \
|
||||||
|
-D "${headers_file}" \
|
||||||
|
-o "${body_file}" \
|
||||||
|
-w '%{http_code}' \
|
||||||
|
"${target}" 2>>"${report_file}" || true
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ ! "${status}" =~ ^2|3[0-9][0-9]$ ]]; then
|
||||||
|
echo "fail: HTTP status ${status:-curl failed}" | tee -a "${report_file}"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
else
|
||||||
|
echo "ok: HTTP status ${status}" | tee -a "${report_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
missing=0
|
||||||
|
for header_name in "${required_headers[@]}"; do
|
||||||
|
if [[ -n "$(header_value "${headers_file}" "${header_name}")" ]]; then
|
||||||
|
echo "ok: ${header_name}" | tee -a "${report_file}"
|
||||||
|
else
|
||||||
|
echo "warn: missing ${header_name}" | tee -a "${report_file}"
|
||||||
|
missing=$((missing + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "$(header_value "${headers_file}" "content-security-policy")" ]]; then
|
||||||
|
echo "ok: content-security-policy" | tee -a "${report_file}"
|
||||||
|
else
|
||||||
|
echo "info: content-security-policy missing or intentionally deferred" | tee -a "${report_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((missing > 0)); then
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
echo | tee -a "${report_file}" >/dev/null
|
||||||
|
rm -f "${body_file}"
|
||||||
|
done < <(scan_targets "${SECURITY_WEB_TARGET:-}")
|
||||||
|
|
||||||
|
echo "Security web report: ${report_file}"
|
||||||
|
if ((failures > 0)); then
|
||||||
|
echo "security-web found ${failures} target(s) needing review." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
run_kube_bench() {
|
run_kube_bench() {
|
||||||
local stamp
|
local stamp
|
||||||
stamp="$(timestamp)"
|
stamp="$(timestamp)"
|
||||||
|
|
@ -395,6 +497,9 @@ case "${1:-all}" in
|
||||||
nuclei)
|
nuclei)
|
||||||
run_nuclei
|
run_nuclei
|
||||||
;;
|
;;
|
||||||
|
web)
|
||||||
|
run_security_web
|
||||||
|
;;
|
||||||
kube-bench)
|
kube-bench)
|
||||||
run_kube_bench
|
run_kube_bench
|
||||||
;;
|
;;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ Use Jeannie from the Debian homelab host:
|
||||||
./jeannie security-zap
|
./jeannie security-zap
|
||||||
./jeannie security-k8s
|
./jeannie security-k8s
|
||||||
./jeannie security-host
|
./jeannie security-host
|
||||||
|
./jeannie security-web
|
||||||
```
|
```
|
||||||
|
|
||||||
The default `security-scan` runs:
|
The default `security-scan` runs:
|
||||||
|
|
@ -28,6 +29,8 @@ The focused commands are:
|
||||||
report-only and prints one highest-risk finding to focus on first.
|
report-only and prints one highest-risk finding to focus on first.
|
||||||
- `security-trivy`: Trivy repo and IaC/config scans only.
|
- `security-trivy`: Trivy repo and IaC/config scans only.
|
||||||
- `security-nuclei`: low-rate nuclei HTTP exposure scans only.
|
- `security-nuclei`: low-rate nuclei HTTP exposure scans only.
|
||||||
|
- `security-web`: quick HTTPS, status, and defensive-header checks for owned
|
||||||
|
web targets.
|
||||||
|
|
||||||
Reports are written to:
|
Reports are written to:
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue