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`
|
||||
: 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
|
||||
|
||||
`state-backup`
|
||||
|
|
@ -223,6 +227,9 @@ smoke test. Defaults to `cloudflare.com`.
|
|||
`SECURITY_NUCLEI_TARGET`
|
||||
: Optional single URL override for `security-nuclei`.
|
||||
|
||||
`SECURITY_WEB_TARGET`
|
||||
: Optional single URL override for `security-web`.
|
||||
|
||||
`lynis`
|
||||
: `security-host` requires Lynis to be installed on the Debian host. Install it
|
||||
explicitly with `sudo apt install lynis`.
|
||||
|
|
|
|||
10
jeannie
10
jeannie
|
|
@ -5795,6 +5795,11 @@ security_nuclei() {
|
|||
"${REPO_ROOT}/scripts/security-scan" nuclei
|
||||
}
|
||||
|
||||
security_web() {
|
||||
require_debian_server "security-web"
|
||||
"${REPO_ROOT}/scripts/security-scan" web
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
up)
|
||||
up
|
||||
|
|
@ -5917,6 +5922,9 @@ case "${1:-}" in
|
|||
security-nuclei)
|
||||
security_nuclei
|
||||
;;
|
||||
security-web)
|
||||
security_web
|
||||
;;
|
||||
openwrt)
|
||||
openwrt
|
||||
;;
|
||||
|
|
@ -5928,7 +5936,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|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
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -23,13 +23,14 @@ truthy() {
|
|||
|
||||
usage() {
|
||||
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:
|
||||
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.
|
||||
SECURITY_WEB_TARGET Single URL override for web header checks.
|
||||
LAB_BACKSTAGE_BRAIN_ENABLED=true enables LLM-assisted Lynis triage.
|
||||
EOF
|
||||
}
|
||||
|
|
@ -180,6 +181,107 @@ run_nuclei() {
|
|||
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() {
|
||||
local stamp
|
||||
stamp="$(timestamp)"
|
||||
|
|
@ -395,6 +497,9 @@ case "${1:-all}" in
|
|||
nuclei)
|
||||
run_nuclei
|
||||
;;
|
||||
web)
|
||||
run_security_web
|
||||
;;
|
||||
kube-bench)
|
||||
run_kube_bench
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ Use Jeannie from the Debian homelab host:
|
|||
./jeannie security-zap
|
||||
./jeannie security-k8s
|
||||
./jeannie security-host
|
||||
./jeannie security-web
|
||||
```
|
||||
|
||||
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.
|
||||
- `security-trivy`: Trivy repo and IaC/config 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:
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue