Pull to refresh

Webhook у Harbor или как я оповещения о пушах docker images нашей команды делал часть — 2

Level of difficultyEasy
Reading time5 min
Views315

В прошлой статье мы разобрали пуши в Harbor. Сегодня же я расскажу о других методах, с которыми работает его webhook. Кстати, все эти методы приведены на изображении ниже:

События для webhook
События для webhook

Перейдем к разбору наиболее популярных событий.

Основные события webhook

  1. SCANNING_STOPPED Это событие срабатывает, если вручную остановить сканирование образа.

  2. PULL_ARTIFACT Происходит, когда кто-то скачивает ( pulls ) образ.

  3. SCANNING_COMPLETED Сообщает о завершении сканирования образа и содержит краткую информацию о результатах.

  4. DELETE_ARTIFACT Срабатывает при удалении образа.

Настройка конфигурации

Для удобства мы вынесем некоторые параметры в отдельный конфиг, например, список символов для экранирования и типы уведомлений, чтобы не сбивать лишней информацией там, где она не нужна.

# conf.toml

[telegram]
bot_token        = "YOUR_BOT_TOKEN"
chat_id          = "-1001234567890"
message_thread_id = "1234"      # или оставьте пустым/удалите, если не нужно

[markdown]
# Список символов, которые будем экранировать
escape_chars = ["\\", "_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", "!"]

[events]
# Включаем/выключаем оповещения по типам событий
PUSH_ARTIFACT        = true
PULL_ARTIFACT        = true
DELETE_ARTIFACT      = true
SCANNING_STOPPED     = true
SCANNING_COMPLETED   = true

Разбор каждого события

Для наглядности я подготовил общий JSON с описанием событий:

[
  {
    "type": "SCANNING_STOPPED",
    "occur_at": 1746597835,
    "operator": "auto",
    "event_data": {
      "resources": [
        {
          "digest": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf",
          "tag": "1.1.1",
          "resource_url": "127.0.0.1/repo/repo_image:1.1.1",
          "scan_overview": {
            "application/vnd.security.vulnerability.report; version=1.1": {
              "report_id": "ec84315d-3f08-42cc-bdc7-491953540cda",
              "scan_status": "Stopped",
              "severity": "",
              "duration": 13,
              "summary": null,
              "start_time": "2025-05-07T06:03:42Z",
              "end_time": "2025-05-07T06:03:55Z",
              "complete_percent": 0
            }
          }
        }
      ],
      "repository": {
        "name": "repo_image",
        "namespace": "repo",
        "repo_full_name": "repo/repo_image",
        "repo_type": "public"
      }
    }
  },
  {
    "type": "PULL_ARTIFACT",
    "occur_at": 1746597854,
    "operator": "robot$repo+Trivy-16c5e31b-2b09-11f0-87be-0242ac180003",
    "event_data": {
      "resources": [
        {
          "digest": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf",
          "tag": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf",
          "resource_url": "127.0.0.1/repo/repo_image:@sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf"
        }
      ],
      "repository": {
        "date_created": 1733465413,
        "name": "repo_image",
        "namespace": "repo",
        "repo_full_name": "repo/repo_image",
        "repo_type": "public"
      }
    }
  },
  {
    "type": "SCANNING_COMPLETED",
    "occur_at": 1746597860,
    "operator": "auto",
    "event_data": {
      "resources": [
        {
          "digest": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf",
          "tag": "1.1.1",
          "resource_url": "127.0.0.1/repo/repo_image:1.1.1",
          "scan_overview": {
            "application/vnd.security.vulnerability.report; version=1.1": {
              "report_id": "39ba459d-3642-4de6-aaac-865b94cdb006",
              "scan_status": "Success",
              "severity": "Critical",
              "duration": 12,
              "summary": {
                "total": 1005,
                "fixable": 7,
                "summary": {
                  "Critical": 2,
                  "High": 73,
                  "Low": 422,
                  "Medium": 506,
                  "Unknown": 2
                }
              },
              "start_time": "2025-05-07T06:04:08Z",
              "end_time": "2025-05-07T06:04:20Z",
              "scanner": {
                "name": "Trivy",
                "vendor": "Aqua Security",
                "version": "v0.45.0"
              },
              "complete_percent": 100
            }
          }
        }
      ],
      "repository": {
        "name": "repo_image",
        "namespace": "repo",
        "repo_full_name": "repo/repo_image",
        "repo_type": "public"
      }
    }
  },
  {
    "type": "DELETE_ARTIFACT",
    "occur_at": 1746597914,
    "operator": "Admin",
    "event_data": {
      "resources": [
        {
          "digest": "sha256:280b85260a6613bf08bd1234512341234123412341234123412gvsdfgdf123123123123123123",
          "tag": "1.0.0",
          "resource_url": "127.0.0.1/repo/repo_image:1.0.0"
        }
      ],
      "repository": {
        "date_created": 1733465413,
        "name": "repo_image",
        "namespace": "repo",
        "repo_full_name": "repo/repo_image",
        "repo_type": "public"
      }
    }
  }
]

PULL_ARTIFACT

PULL_ARTIFACT - это оповещение, которое можно использовать для отслеживания, когда кто-то скачивает образ. Важное замечание — чаще всего это инициирует автоматическое сканирование или другие процессы. В моем случае я решил оставить его как информативное событие, чтобы знать, когда образ попал в использование.

    elif event_type == "PULL_ARTIFACT":
        for r in resources:
            tag = escape_markdown(r.get("tag", "—"))
            url = r.get("resource_url", "—")
            msg = (
                "📥 *Pull в Harbor*\n"
                f"*Репозиторий:* {repo_name}\n"
                f"*Тег:* {tag}\n"
                f"*URL:* `{url}`\n"
                f"*Пользователь:* {operator}"
            )
            messages.append(msg)

DELETE_ARTIFACT

DELETE_ARTIFACT - это событие помогает отслеживать, кто и когда удаляет образы, особенно актуально, если у вас несколько Harbor-репозиториев. Вот пример сообщения:

    elif event_type == "DELETE_ARTIFACT":
        for r in resources:
            tag = escape_markdown(r.get("tag", "—"))
            url = r.get("resource_url", "—")
            msg = (
                "🗑️ *Удаление образа в Harbor*\n"
                f"*Репозиторий:* {repo_name}\n"
                f"*Тег:* {tag}\n"
                f"*URL:* `{url}`\n"
                f"*Пользователь:* {operator}"
            )
            messages.append(msg)

Хотя смайлики — не обязательная часть сообщений, я считаю, что они привлекают больше внимания и делают оповещения более дружелюбными.

SCANNING_STOPPED

SCANNING_STOPPED - это относительно редкое событие, срабатывающее при ручной остановке процесса сканирования. В моем опыте — очень редко, но бывает полезным для ситуаций, когда нужно срочно прервать проверку образа.

    elif event_type == "SCANNING_STOPPED":
        for r in resources:
            tag     = escape_markdown(r.get("tag", "—"))
            url     = r.get("resource_url", "—")
            overview = r.get("scan_overview", {})
            detail  = next(iter(overview.values()), {})
            status  = escape_markdown(detail.get("scan_status", "—"))
            duration = detail.get("duration", 0)
            msg = (
                "🔍 *Сканирование остановлено*\n"
                f"*Репозиторий:* {repo_name}\n"
                f"*Тег:* {tag}\n"
                f"*URL:* `{url}`\n"
                f"*Статус скана:* {status}\n"
                f"*Продолжительность:* {duration}s\n"
                f"*Пользователь:* {operator}"
            )
            messages.append(msg)

SCANNING_COMPLETED

SCANNING_COMPLETED - на мой взгляд, одно из самых важных событий. После завершения сканирования Harbor сообщает о найденных уязвимостях и предлагает варианты устранения (например, обновление пакетов). Это очень удобно для быстрого анализа состояния образа и сравнения с предыдущими версиями.

    elif event_type == "SCANNING_COMPLETED":
        for r in resources:
            tag      = escape_markdown(r.get("tag", "—"))
            url      = r.get("resource_url", "—")
            overview = r.get("scan_overview", {})
            detail   = next(iter(overview.values()), {})
            status   = escape_markdown(detail.get("scan_status", "—"))
            duration = detail.get("duration", 0)
            severity = escape_markdown(detail.get("severity", "—"))
            summary  = detail.get("summary", {}) or {}
            total    = summary.get("total", 0)
            fixable  = summary.get("fixable", 0)
            counts   = summary.get("summary", {})

            # Формируем разбивку по уровням критичности
            counts_lines = ""
            for lvl, cnt in counts.items():
                lvl_esc = escape_markdown(lvl)
                counts_lines += f"*{lvl_esc}:* {cnt}\n"

            scanner = detail.get("scanner", {})
            sc_name = escape_markdown(scanner.get("name", "—"))
            sc_ver  = escape_markdown(scanner.get("version", "—"))

            msg = (
                "🔍 *Сканирование завершено*\n"
                f"*Репозиторий:* {repo_name}\n"
                f"*Тег:* {tag}\n"
                f"*URL:* `{url}`\n"
                f"*Статус скана:* {status}\n"
                f"*Продолжительность:* {duration}s\n"
                f"*Критичность (макс):* {severity}\n"
                f"*Всего уязвимостей:* {total}\n"
                f"*Исправимо:* {fixable}\n"
                f"{counts_lines}"
                f"*Сканер:* {sc_name} {sc_ver}\n"
                f"*Пользователь:* {operator}"
            )
            messages.append(msg)

Это уведомление позволяет быстро оценить безопасность образа и принять меры.

Заключение и планы

На данный момент я собираю JSON-описания по другим событиям webhook. В дальнейшем хочу реализовать возможность редактирования настроек контейнера прямо через веб-интерфейс — на мой взгляд, это значительно упрощает работу. Надеюсь, статья оказалась полезной. Следите за обновлениями! Если кому необходимо, вот репозиторий с проектом.

Tags:
Hubs:
+1
Comments4

Articles