services:
zap:
image: ${ZAP_IMAGE:-zaproxy/zap-stable:latest}
container_name: owasp-zap-scan
restart: "no" # одноразовый запуск
init: true # tini как PID1 — корректные сигналы и reap зомби-процессов
environment:
TARGET_URL: ${TARGET_URL}
SCAN_TYPE: ${SCAN_TYPE:-full} # baseline | full | api
SCAN_LEVEL: ${SCAN_LEVEL:-medium}
EXTRA_ARGS: ${EXTRA_ARGS:-}
SCAN_TIMEOUT: ${SCAN_TIMEOUT:-0}
ZAP_ADDONS: ${ZAP_ADDONS:-}
volumes:
- "${REPORT_DIR}:/zap/wrk:rw"
# Все "$" ниже, которые должны разворачиваться bash'ем ВНУТРИ контейнера
# (а не Docker Compose на этапе парсинга файла), записаны как "$$" —
# это стандартный способ Compose сказать "не трогай, это не моя переменная".
entrypoint: ["/bin/bash", "-c"]
command:
- |
set -uo pipefail
echo "=================================================="
echo " OWASP ZAP — одноразовый скан"
echo "=================================================="
TARGET_URL="$$${TARGET_URL:-}"
SCAN_TYPE="$$${SCAN_TYPE:-full}"
SCAN_LEVEL="$$${SCAN_LEVEL:-medium}"
EXTRA_ARGS="$$${EXTRA_ARGS:-}"
SCAN_TIMEOUT="$$${SCAN_TIMEOUT:-0}"
ZAP_ADDONS="$$${ZAP_ADDONS:-}"
if [ -z "$$TARGET_URL" ]; then
echo "[ERROR] Не задан TARGET_URL — укажи цель атаки в Environment"
exit 1
fi
# --- Установка доп. аддонов (если указаны) ---
# ID берутся из столбца "ID" на https://www.zaproxy.org/addons
if [ -n "$$ZAP_ADDONS" ]; then
echo "--------------------------------------------------"
echo "Устанавливаю аддоны: $$ZAP_ADDONS"
ADDON_ARGS=(-addonupdate)
for addon in $$ZAP_ADDONS; do
ADDON_ARGS+=(-addoninstall "$$addon")
done
zap.sh -cmd "$$${ADDON_ARGS[@]}" -silent
ADDON_EXIT=$$?
if [ "$$ADDON_EXIT" -ne 0 ]; then
echo "[WARN] Установка аддонов завершилась с кодом $$ADDON_EXIT — проверь ID на https://www.zaproxy.org/addons и доступ контейнера в интернет"
else
echo "Аддоны установлены."
fi
echo "--------------------------------------------------"
fi
TIMESTAMP="$$(date +%Y%m%d_%H%M%S)"
SAFE_HOST="$$(printf '%s' "$$TARGET_URL" | sed -E 's#https?://##; s#[^a-zA-Z0-9._-]#_#g')"
REPORT_BASENAME="zap_$$${SCAN_TYPE}_$$${SAFE_HOST}_$$${TIMESTAMP}"
case "$$SCAN_TYPE" in
baseline) SCRIPT="zap-baseline.py" ;;
full) SCRIPT="zap-full-scan.py" ;;
api) SCRIPT="zap-api-scan.py" ;;
*)
echo "[ERROR] Неизвестный SCAN_TYPE: $$SCAN_TYPE (baseline|full|api)"
exit 1
;;
esac
mkdir -p /zap/wrk
echo "Цель: $$TARGET_URL"
echo "Тип скана: $$SCAN_TYPE ($$SCRIPT)"
echo "Уровень: $$SCAN_LEVEL (справочно; не влияет на движок — используй EXTRA_ARGS с -z, если нужна настройка agressiveness)"
echo "Таймаут: $$${SCAN_TIMEOUT}s (0 = без ограничения)"
echo "Аддоны: $$${ZAP_ADDONS:-<нет>}"
echo "HTML-отчёт: /zap/wrk/$$${REPORT_BASENAME}.html"
echo "JSON-отчёт: /zap/wrk/$$${REPORT_BASENAME}.json"
echo "MD-отчёт: /zap/wrk/$$${REPORT_BASENAME}.md"
echo "Доп. флаги: $$${EXTRA_ARGS:-<нет>}"
echo "--------------------------------------------------"
RUN_CMD=("$$SCRIPT" -t "$$TARGET_URL" -r "$$${REPORT_BASENAME}.html" -w "$$${REPORT_BASENAME}.md" -J "$$${REPORT_BASENAME}.json" -I -d)
if [ -n "$$EXTRA_ARGS" ]; then
read -ra EXTRA_ARR <<< "$$EXTRA_ARGS"
RUN_CMD+=("$$${EXTRA_ARR[@]}")
fi
if [ "$$SCAN_TIMEOUT" -gt 0 ] 2>/dev/null; then
timeout "$$SCAN_TIMEOUT" "$$${RUN_CMD[@]}"
else
"$$${RUN_CMD[@]}"
fi
EXIT_CODE=$$?
echo "--------------------------------------------------"
if [ "$$EXIT_CODE" -eq 124 ]; then
echo "[WARN] Скан прерван по таймауту ($$${SCAN_TIMEOUT}s)."
fi
echo "Скан завершён. Внутренний код завершения ZAP: $$EXIT_CODE"
echo "(0 = проблем не найдено, 2 = WARN/FAIL найдены — это нормальный исход, не сбой)"
if [ -f "/zap/wrk/$$${REPORT_BASENAME}.html" ]; then
chmod 644 /zap/wrk/$$${REPORT_BASENAME}.* 2>/dev/null || true
echo "Отчёты сохранены в примонтированной папке."
else
echo "[WARN] HTML-отчёт не найден — смотри лог выше на предмет ошибки соединения с целью."
fi
echo "=================================================="
echo " Контейнер завершает работу (одноразовый режим)"
echo "=================================================="
exit 0
# --- лимиты ресурсов ---
mem_limit: ${ZAP_MEM_LIMIT:-2g}
memswap_limit: ${ZAP_MEM_LIMIT:-2g}
cpus: ${ZAP_CPU_LIMIT:-1.5}
user: "0:0" # root — чтобы не упереться в права на bind-mount папку отчётов
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
services:
zap:
image: ${ZAP_IMAGE:-zaproxy/zap-stable:latest}
container_name: owasp-zap-scan
restart: "no" # одноразовый запуск
init: true # tini как PID1 — корректные сигналы и reap зомби-процессов
environment:
TARGET_URL: ${TARGET_URL}
SCAN_TYPE: ${SCAN_TYPE:-full} # baseline | full | api
SCAN_LEVEL: ${SCAN_LEVEL:-medium}
EXTRA_ARGS: ${EXTRA_ARGS:-}
SCAN_TIMEOUT: ${SCAN_TIMEOUT:-0}
ZAP_ADDONS: ${ZAP_ADDONS:-}
volumes:
- "${REPORT_DIR}:/zap/wrk:rw"
# Все "$" ниже, которые должны разворачиваться bash'ем ВНУТРИ контейнера
# (а не Docker Compose на этапе парсинга файла), записаны как "$$" —
# это стандартный способ Compose сказать "не трогай, это не моя переменная".
entrypoint: ["/bin/bash", "-c"]
command:
- |
set -uo pipefail
echo "=================================================="
echo " OWASP ZAP — одноразовый скан"
echo "=================================================="
TARGET_URL="$$${TARGET_URL:-}"
SCAN_TYPE="$$${SCAN_TYPE:-full}"
SCAN_LEVEL="$$${SCAN_LEVEL:-medium}"
EXTRA_ARGS="$$${EXTRA_ARGS:-}"
SCAN_TIMEOUT="$$${SCAN_TIMEOUT:-0}"
ZAP_ADDONS="$$${ZAP_ADDONS:-}"
if [ -z "$$TARGET_URL" ]; then
echo "[ERROR] Не задан TARGET_URL — укажи цель атаки в Environment"
exit 1
fi
# --- Установка доп. аддонов (если указаны) ---
# ID берутся из столбца "ID" на https://www.zaproxy.org/addons
if [ -n "$$ZAP_ADDONS" ]; then
echo "--------------------------------------------------"
echo "Устанавливаю аддоны: $$ZAP_ADDONS"
ADDON_ARGS=(-addonupdate)
for addon in $$ZAP_ADDONS; do
ADDON_ARGS+=(-addoninstall "$$addon")
done
zap.sh -cmd "$$${ADDON_ARGS[@]}" -silent
ADDON_EXIT=$$?
if [ "$$ADDON_EXIT" -ne 0 ]; then
echo "[WARN] Установка аддонов завершилась с кодом $$ADDON_EXIT — проверь ID на https://www.zaproxy.org/addons и доступ контейнера в интернет"
else
echo "Аддоны установлены."
fi
echo "--------------------------------------------------"
fi
TIMESTAMP="$$(date +%Y%m%d_%H%M%S)"
SAFE_HOST="$$(printf '%s' "$$TARGET_URL" | sed -E 's#https?://##; s#[^a-zA-Z0-9._-]#_#g')"
REPORT_BASENAME="zap_$$${SCAN_TYPE}_$$${SAFE_HOST}_$$${TIMESTAMP}"
case "$$SCAN_TYPE" in
baseline) SCRIPT="zap-baseline.py" ;;
full) SCRIPT="zap-full-scan.py" ;;
api) SCRIPT="zap-api-scan.py" ;;
*)
echo "[ERROR] Неизвестный SCAN_TYPE: $$SCAN_TYPE (baseline|full|api)"
exit 1
;;
esac
mkdir -p /zap/wrk
echo "Цель: $$TARGET_URL"
echo "Тип скана: $$SCAN_TYPE ($$SCRIPT)"
echo "Уровень: $$SCAN_LEVEL (справочно; не влияет на движок — используй EXTRA_ARGS с -z, если нужна настройка agressiveness)"
echo "Таймаут: $$${SCAN_TIMEOUT}s (0 = без ограничения)"
echo "Аддоны: $$${ZAP_ADDONS:-<нет>}"
echo "HTML-отчёт: /zap/wrk/$$${REPORT_BASENAME}.html"
echo "JSON-отчёт: /zap/wrk/$$${REPORT_BASENAME}.json"
echo "MD-отчёт: /zap/wrk/$$${REPORT_BASENAME}.md"
echo "Доп. флаги: $$${EXTRA_ARGS:-<нет>}"
echo "--------------------------------------------------"
RUN_CMD=("$$SCRIPT" -t "$$TARGET_URL" -r "$$${REPORT_BASENAME}.html" -w "$$${REPORT_BASENAME}.md" -J "$$${REPORT_BASENAME}.json" -I -d)
if [ -n "$$EXTRA_ARGS" ]; then
read -ra EXTRA_ARR <<< "$$EXTRA_ARGS"
RUN_CMD+=("$$${EXTRA_ARR[@]}")
fi
if [ "$$SCAN_TIMEOUT" -gt 0 ] 2>/dev/null; then
timeout "$$SCAN_TIMEOUT" "$$${RUN_CMD[@]}"
else
"$$${RUN_CMD[@]}"
fi
EXIT_CODE=$$?
echo "--------------------------------------------------"
if [ "$$EXIT_CODE" -eq 124 ]; then
echo "[WARN] Скан прерван по таймауту ($$${SCAN_TIMEOUT}s)."
fi
echo "Скан завершён. Внутренний код завершения ZAP: $$EXIT_CODE"
echo "(0 = проблем не найдено, 2 = WARN/FAIL найдены — это нормальный исход, не сбой)"
if [ -f "/zap/wrk/$$${REPORT_BASENAME}.html" ]; then
chmod 644 /zap/wrk/$$${REPORT_BASENAME}.* 2>/dev/null || true
echo "Отчёты сохранены в примонтированной папке."
else
echo "[WARN] HTML-отчёт не найден — смотри лог выше на предмет ошибки соединения с целью."
fi
echo "=================================================="
echo " Контейнер завершает работу (одноразовый режим)"
echo "=================================================="
exit 0
# --- лимиты ресурсов ---
mem_limit: ${ZAP_MEM_LIMIT:-2g}
memswap_limit: ${ZAP_MEM_LIMIT:-2g}
cpus: ${ZAP_CPU_LIMIT:-1.5}
user: "0:0" # root — чтобы не упереться в права на bind-mount папку отчётов
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"