diff --git a/docs/jeannie.1.md b/docs/jeannie.1.md index b88a558..07fe24b 100644 --- a/docs/jeannie.1.md +++ b/docs/jeannie.1.md @@ -159,6 +159,10 @@ on first. This command is report-only. : Run quick HTTPS, status, and defensive-header checks against owned web targets. +`security-logs` +: Summarize recent Gitea and Traefik logs for common scanner paths, suspicious +user agents, auth failures, and HTTP errors. + ### Maintenance `state-backup` @@ -233,6 +237,9 @@ smoke test. Defaults to `cloudflare.com`. `SECURITY_WEB_TARGET` : Optional single URL override for `security-web`. +`SECURITY_LOG_SINCE` +: Log window for `security-logs`. Defaults to `24h`. + `lynis` : `security-host` requires Lynis to be installed on the Debian host. Install it explicitly with `sudo apt install lynis`. diff --git a/jeannie b/jeannie index 52c1942..18acc01 100755 --- a/jeannie +++ b/jeannie @@ -5805,6 +5805,11 @@ security_web() { "${REPO_ROOT}/scripts/security-scan" web } +security_logs() { + require_debian_server "security-logs" + "${REPO_ROOT}/scripts/security-logs" +} + case "${1:-}" in up) up @@ -5933,6 +5938,9 @@ case "${1:-}" in security-web) security_web ;; + security-logs) + security_logs + ;; openwrt) openwrt ;; @@ -5944,7 +5952,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-secrets|security-nuclei|security-web|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-secrets|security-nuclei|security-web|security-logs|openwrt|nuke}" exit 1 ;; esac diff --git a/scripts/security-logs b/scripts/security-logs new file mode 100755 index 0000000..e7c4e97 --- /dev/null +++ b/scripts/security-logs @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -euo pipefail + +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_LOG_SINCE="${SECURITY_LOG_SINCE:-24h}" +GITEA_CONTAINER="${GITEA_CONTAINER:-${LAB_GITEA_CONTAINER_NAME:-homelab-gitea}}" +KUBECONFIG="${KUBECONFIG:-${HOME}/.kube/config}" + +timestamp() { + date -u +%Y%m%dT%H%M%SZ +} + +ensure_report_dir() { + mkdir -p "${SECURITY_REPORT_DIR}" +} + +print_matches() { + local title="$1" + local log_file="$2" + local pattern="$3" + local limit="${4:-20}" + + printf '\n== %s ==\n' "${title}" + if [[ ! -s "${log_file}" ]]; then + printf 'no log input\n' + return 0 + fi + + if grep -Eai "${pattern}" "${log_file}" >/dev/null; then + grep -Eai "${pattern}" "${log_file}" | tail -n "${limit}" + else + printf 'none\n' + fi +} + +collect_gitea_logs() { + local output_file="$1" + + if ! command -v docker >/dev/null 2>&1; then + return 0 + fi + if ! docker ps --format '{{.Names}}' | grep -Fxq "${GITEA_CONTAINER}"; then + return 0 + fi + + docker logs --since "${SECURITY_LOG_SINCE}" "${GITEA_CONTAINER}" >"${output_file}" 2>&1 || true +} + +collect_traefik_logs() { + local output_file="$1" + + if ! command -v kubectl >/dev/null 2>&1; then + return 0 + fi + if [[ ! -f "${KUBECONFIG}" ]]; then + return 0 + fi + + kubectl --kubeconfig "${KUBECONFIG}" -n traefik logs \ + -l app.kubernetes.io/name=traefik \ + --since="${SECURITY_LOG_SINCE}" \ + --tail="${SECURITY_LOG_TAIL:-5000}" >"${output_file}" 2>&1 || true +} + +main() { + local stamp + local gitea_log + local traefik_log + local report_file + local suspicious_paths + local suspicious_agents + + stamp="$(timestamp)" + ensure_report_dir + gitea_log="${SECURITY_REPORT_DIR}/security-gitea-logs-${stamp}.txt" + traefik_log="${SECURITY_REPORT_DIR}/security-traefik-logs-${stamp}.txt" + report_file="${SECURITY_REPORT_DIR}/security-logs-${stamp}.txt" + suspicious_paths='/(wp-admin|wp-login|xmlrpc\.php|\.env|\.git|vendor/phpunit|cgi-bin|boaform|solr|actuator|server-status)|\.\./|%2e%2e|/etc/passwd|/proc/self/environ' + suspicious_agents='nikto|sqlmap|nuclei|masscan|zgrab|gobuster|dirbuster|dirsearch|wpscan|acunetix|nessus|openvas|curl/[0-9]|python-requests|Go-http-client' + + collect_gitea_logs "${gitea_log}" + collect_traefik_logs "${traefik_log}" + + { + echo "Homelab security log summary" + echo "Generated: ${stamp}" + echo "Window: ${SECURITY_LOG_SINCE}" + echo + print_matches "Gitea auth or permission failures" "${gitea_log}" 'authentication failed|invalid credentials|denied|forbidden|unauthorized|failed login|401|403' + print_matches "Gitea suspicious repo/path activity" "${gitea_log}" "${suspicious_paths}" + print_matches "Traefik suspicious paths" "${traefik_log}" "${suspicious_paths}" + print_matches "Traefik suspicious user agents" "${traefik_log}" "${suspicious_agents}" + print_matches "Traefik client/server errors" "${traefik_log}" ' 4[0-9][0-9] | 5[0-9][0-9] |level=error|level=warn' + echo + echo "Raw Gitea log sample: ${gitea_log}" + echo "Raw Traefik log sample: ${traefik_log}" + } | tee "${report_file}" + + echo "Security log report: ${report_file}" +} + +main "$@" diff --git a/security/README.md b/security/README.md index 624e759..c81e31a 100644 --- a/security/README.md +++ b/security/README.md @@ -12,6 +12,7 @@ Use Jeannie from the Debian homelab host: ./jeannie security-k8s ./jeannie security-host ./jeannie security-web +./jeannie security-logs ``` The default `security-scan` runs: @@ -33,6 +34,8 @@ The focused commands are: - `security-nuclei`: low-rate nuclei HTTP exposure scans only. - `security-web`: quick HTTPS, status, and defensive-header checks for owned web targets. +- `security-logs`: read-only summary of recent Gitea and Traefik security + signals. Reports are written to: @@ -54,3 +57,8 @@ Ollama/RAG helper to select exactly one finding; otherwise it falls back to the first Lynis warning, then the first suggestion. The full Lynis report is still saved so you can review the complete output before adding any explicit hardening command. + +`security-logs` uses recent Docker logs from the Debian Gitea container and +Traefik pod logs when Kubernetes is reachable. It is intentionally heuristic: +use it to spot scanner noise, auth failures, suspicious paths, and obvious +client/server errors before opening the raw logs.