Вернуться к реестру шаблонов Docker

OWASP ZAP

OWASP ZAP — инструмент динамического анализа безопасности веб-приложений, который помогает запускать baseline, full и API-сканы в контейнере и сохранять отчёты в HTML, JSON и Markdown.

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"