Harbor: приватный Docker-реестр для закрытой среды без доступа к интернету

Ситуация: оборонка без Docker Hub

Оборонный подрядчик «ОборонТех» обратился к нам в itfresh.ru с задачей: развернуть полностью автономный Docker-реестр в закрытой сети (air-gapped environment). Серверы не имеют доступа к интернету — ни прямого, ни через прокси. Docker Hub, GitHub Container Registry и любые публичные реестры недоступны.

Требования:

  • Air-gapped — никаких подключений к внешним сетям. Образы загружаются через DMZ-шлюз на физических носителях.
  • HTTPS с внутренним CA — корпоративный PKI, самоподписанные сертификаты. Внешние CA (Let's Encrypt) недоступны.
  • LDAP/AD — аутентификация через корпоративный Active Directory. Никаких локальных учёток.
  • Сканирование уязвимостей — каждый образ проверяется перед допуском в продакшен.
  • Аудит — кто, когда и какой образ запушил/стянул. Для сертификации ФСТЭК.
  • Репликация — синхронизация образов между двумя площадками (основная + резервная).

Существующее решение — Docker Registry v2 (open source) — не удовлетворяло: нет UI, нет аутентификации кроме htpasswd, нет сканирования, нет RBAC.

Установка Harbor через docker-compose

Harbor — enterprise-grade registry от VMware (open source, CNCF Graduated). Включает веб-UI, RBAC, сканер уязвимостей, репликацию, подпись образов.

# Загрузка offline-инсталлятора (через DMZ-шлюз)
# На машине с интернетом:
wget https://github.com/goharbor/harbor/releases/download/v2.10.1/harbor-offline-installer-v2.10.1.tgz
sha256sum harbor-offline-installer-v2.10.1.tgz
# Сверяем с хешем на GitHub releases

# Переносим на air-gapped сервер через USB/SCP через DMZ
scp harbor-offline-installer-v2.10.1.tgz admin@harbor-srv:/opt/

# На air-gapped сервере:
cd /opt
tar xzf harbor-offline-installer-v2.10.1.tgz
cd harbor

# Генерируем конфигурацию
cp harbor.yml.tmpl harbor.yml

Конфигурация Harbor:

# harbor.yml
hostname: harbor.oborontech.local

# HTTPS с корпоративным CA
https:
  port: 443
  certificate: /etc/harbor/certs/harbor.crt
  private_key: /etc/harbor/certs/harbor.key

# Внутренний TLS между компонентами Harbor
internal_tls:
  enabled: true
  dir: /etc/harbor/tls/internal

harbor_admin_password: H@rb0r_Adm1n_2026!

database:
  password: Harb0r_DB_P@ss!
  max_idle_conns: 100
  max_open_conns: 900
  conn_max_lifetime: 5m
  conn_max_idle_time: 0

data_volume: /data/harbor

storage_service:
  filesystem:
    maxthreads: 100
  maintenance:
    uploadpurging:
      enabled: true
      age: 168h
      interval: 24h
      dryrun: false
  delete:
    enabled: true

# Trivy для сканирования уязвимостей
trivy:
  ignore_unfixed: false
  skip_update: true        # Air-gapped: БД уязвимостей обновляется вручную
  offline_scan: true
  security_check: vuln
  insecure: false

jobservice:
  max_job_workers: 10
  logger_sweeper_duration: 1  # дней

notification:
  webhook_job_max_retry: 3
  webhook_job_http_client_timeout: 3  # секунд

log:
  level: info
  local:
    rotate_count: 50
    rotate_size: 200M
    location: /var/log/harbor

proxy:
  http_proxy:
  https_proxy:
  no_proxy: 127.0.0.1,localhost,.local,.oborontech.local
  components:
    - core
    - jobservice
    - trivy

Подготовка сертификатов и установка:

# Генерация сертификата от корпоративного CA
# (В реальности сертификат выдаёт CA «ОборонТех»)
openssl req -newkey rsa:4096 -nodes \
  -keyout /etc/harbor/certs/harbor.key \
  -out /etc/harbor/certs/harbor.csr \
  -subj "/C=RU/ST=Moscow/O=OboronTech/CN=harbor.oborontech.local" \
  -addext "subjectAltName=DNS:harbor.oborontech.local,IP:10.0.5.100"

# CA подписывает CSR и возвращает harbor.crt
# Копируем CA-сертификат для Docker daemon
mkdir -p /etc/docker/certs.d/harbor.oborontech.local
cp ca.crt /etc/docker/certs.d/harbor.oborontech.local/ca.crt
systemctl restart docker

# Установка Harbor
./install.sh --with-trivy
# [Step 0]: checking if docker is installed ... OK
# [Step 1]: loading Harbor images ...
# [Step 2]: preparing environment ...
# [Step 3]: preparing harbor configs ...
# [Step 4]: starting Harbor ...
# Harbor has been installed and started successfully.

# Проверка
docker compose -f /opt/harbor/docker-compose.yml ps
# NAME                   STATUS    PORTS
# harbor-core            running   ...
# harbor-db              running   ...
# harbor-jobservice      running   ...
# harbor-log             running   ...
# harbor-portal          running   ...
# harbor-registryctl     running   ...
# nginx                  running   0.0.0.0:443->8443/tcp
# redis                  running   ...
# registry               running   ...
# trivy-adapter          running   ...

LDAP/AD аутентификация и RBAC

Интеграция с Active Directory — критическое требование. Все инженеры должны аутентифицироваться через корпоративный AD:

# Настройка LDAP через Harbor UI: Administration → Configuration → Authentication
# Или через API:
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X PUT https://harbor.oborontech.local/api/v2.0/configurations \
  -H 'Content-Type: application/json' \
  -d '{
    "auth_mode": "ldap_auth",
    "ldap_url": "ldaps://dc01.oborontech.local:636",
    "ldap_search_dn": "CN=harbor-svc,OU=ServiceAccounts,DC=oborontech,DC=local",
    "ldap_search_password": "Harb0r_LDAP_Bind!",
    "ldap_base_dn": "OU=Users,DC=oborontech,DC=local",
    "ldap_filter": "(&(objectClass=person)(memberOf=CN=DockerUsers,OU=Groups,DC=oborontech,DC=local))",
    "ldap_uid": "sAMAccountName",
    "ldap_scope": 2,
    "ldap_group_base_dn": "OU=Groups,DC=oborontech,DC=local",
    "ldap_group_search_filter": "(objectClass=group)",
    "ldap_group_attribute_name": "cn",
    "ldap_group_admin_dn": "CN=HarborAdmins,OU=Groups,DC=oborontech,DC=local",
    "ldap_verify_cert": true
  }'

RBAC в Harbor работает на уровне проектов (projects). Каждый проект — изолированное пространство с собственными правами:

# Создание проекта через API
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X POST https://harbor.oborontech.local/api/v2.0/projects \
  -H 'Content-Type: application/json' \
  -d '{
    "project_name": "weapons-system",
    "public": false,
    "metadata": {
      "auto_scan": "true",
      "prevent_vul": "true",
      "severity": "high",
      "reuse_sys_cve_allowlist": "true"
    },
    "storage_limit": 107374182400
  }'

# Роли в проекте:
# - Project Admin — управление участниками, политиками
# - Maintainer — push/pull, удаление образов, запуск сканирования
# - Developer — push/pull
# - Guest — только pull
# - Limited Guest — только pull конкретных артефактов

# Добавление LDAP-группы в проект
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X POST https://harbor.oborontech.local/api/v2.0/projects/weapons-system/members \
  -H 'Content-Type: application/json' \
  -d '{
    "role_id": 2,
    "member_group": {
      "group_name": "WeaponsDevTeam",
      "group_type": 1,
      "ldap_group_dn": "CN=WeaponsDevTeam,OU=Groups,DC=oborontech,DC=local"
    }
  }'
# role_id: 1=Admin, 2=Developer, 3=Guest, 4=Maintainer

Результат: разработчик из AD-группы WeaponsDevTeam логинится в Harbor своим доменным паролем и видит только проект weapons-system. Push и pull доступны, удаление образов — нет (нужна роль Maintainer).

Сканирование уязвимостей через Trivy

В air-gapped среде Trivy не может скачать базу уязвимостей из интернета. Мы настроили офлайн-обновление:

# На машине с интернетом (DMZ-шлюз): скачиваем БД Trivy
oci-download() {
  skopeo copy --override-os linux \
    docker://ghcr.io/aquasecurity/trivy-db:2 \
    dir:/tmp/trivy-db
}
oci-download

# Упаковываем для переноса
tar czf trivy-db-$(date +%Y%m%d).tar.gz -C /tmp/trivy-db .

# Переносим на air-gapped сервер через USB
# На Harbor-сервере:
tar xzf trivy-db-20260405.tar.gz -C /data/harbor/trivy-adapter/trivy-db/

# Перезапуск Trivy adapter для подхвата новой БД
docker compose -f /opt/harbor/docker-compose.yml restart trivy-adapter

# Автоматизация: cron-задача на DMZ-шлюзе скачивает БД еженедельно
# 0 3 * * 1 /opt/scripts/update-trivy-db.sh

Политика сканирования — образ не может быть стянут, если содержит критические уязвимости:

# Настройка политики в проекте:
# Project Settings → Policy
# - Auto Scan: ON (сканировать при push)
# - Prevent Vulnerable: ON
# - Severity Threshold: High (блокировать High и Critical)

# Ручной запуск сканирования через API
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X POST "https://harbor.oborontech.local/api/v2.0/projects/weapons-system/repositories/backend/artifacts/sha256:abc123/scan"

# Получение результатов сканирования
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  "https://harbor.oborontech.local/api/v2.0/projects/weapons-system/repositories/backend/artifacts/sha256:abc123/additions/vulnerabilities" \
  | jq '."application/vnd.security.vulnerability.report; version=1.1" | {
    scanner: .scanner.name,
    severity: .severity,
    total: (.vulnerabilities | length),
    critical: ([.vulnerabilities[] | select(.severity=="Critical")] | length),
    high: ([.vulnerabilities[] | select(.severity=="High")] | length)
  }'

# Результат:
# {
#   "scanner": "Trivy",
#   "severity": "High",
#   "total": 23,
#   "critical": 0,
#   "high": 3
# }

# Попытка pull образа с уязвимостями:
docker pull harbor.oborontech.local/weapons-system/backend:1.5.0
# Error: image can not be pulled due to configured policy
# in project "weapons-system": vulnerability severity "High"
# exceeds the threshold

Разработчик видит отчёт в UI Harbor: какие CVE найдены, в каком пакете, какая версия фиксит. Образ не будет доступен для pull, пока уязвимости не будут исправлены или добавлены в allowlist.

Репликация между площадками и garbage collection

«ОборонТех» имеет две площадки: основная (Москва) и резервная (Новосибирск). Образы должны реплицироваться между ними:

# Настройка репликации: Administration → Registries → New Endpoint
# Или через API:
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X POST https://harbor.oborontech.local/api/v2.0/registries \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "harbor-nsk",
    "type": "harbor",
    "url": "https://harbor-nsk.oborontech.local",
    "credential": {
      "type": "basic",
      "access_key": "replication-user",
      "access_secret": "Repl1c@tion_P@ss!"
    },
    "insecure": false
  }'

# Создание правила репликации (push-based)
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X POST https://harbor.oborontech.local/api/v2.0/replication/policies \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "replicate-to-nsk",
    "src_registry": null,
    "dest_registry": {"id": 1},
    "dest_namespace_replace_count": 0,
    "trigger": {
      "type": "event_based",
      "trigger_settings": {}
    },
    "filters": [
      {"type": "name", "value": "weapons-system/**"},
      {"type": "tag", "value": "v*"},
      {"type": "label", "value": "production"}
    ],
    "deletion": true,
    "override": true,
    "enabled": true,
    "speed": 0
  }'

# Репликация срабатывает автоматически при push нового образа
# с тегом v* и лейблом production в проект weapons-system

Garbage collection — очистка неиспользуемых layer-ов для освобождения диска:

# Запуск GC через API
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X POST https://harbor.oborontech.local/api/v2.0/system/gc/schedule \
  -H 'Content-Type: application/json' \
  -d '{
    "parameters": {
      "delete_untagged": true,
      "dry_run": false,
      "workers": 3
    },
    "schedule": {
      "type": "Weekly",
      "cron": "0 0 2 * * 0"
    }
  }'

# GC запускается каждое воскресенье в 02:00
# Удаляет untagged manifests и неиспользуемые blob-ы

# Проверка результатов последнего GC
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  https://harbor.oborontech.local/api/v2.0/system/gc \
  | jq '.[0] | {status, deleted, freed_space: (.job_parameters | fromjson | .freed_space)}'
# {"status": "Success", "deleted": 147, "freed_space": "12.3 GiB"}

Квоты и CI/CD интеграция

Квоты предотвращают ситуацию, когда один проект занимает весь диск:

# Установка квоты при создании проекта (storage_limit в байтах)
# 100 GB = 107374182400 байт

# Изменение квоты для существующего проекта
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X PUT https://harbor.oborontech.local/api/v2.0/projects/weapons-system \
  -H 'Content-Type: application/json' \
  -d '{
    "metadata": {
      "auto_scan": "true"
    },
    "storage_limit": 214748364800
  }'
# Увеличена до 200 GB

# Проверка использования квоты
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  https://harbor.oborontech.local/api/v2.0/quotas \
  | jq '.[] | select(.ref.name=="weapons-system") |
    {project: .ref.name,
     used: .used.storage,
     limit: .hard.storage,
     percent: ((.used.storage / .hard.storage) * 100 | round)}'
# {"project": "weapons-system", "used": 45234567890,
#  "limit": 214748364800, "percent": 21}

Интеграция с CI/CD (GitLab CI в закрытой сети):

# .gitlab-ci.yml — сборка и push в Harbor
variables:
  HARBOR_URL: harbor.oborontech.local
  HARBOR_PROJECT: weapons-system
  IMAGE_NAME: ${HARBOR_URL}/${HARBOR_PROJECT}/${CI_PROJECT_NAME}
  IMAGE_TAG: ${CI_COMMIT_SHORT_SHA}

stages:
  - build
  - scan
  - deploy

build:
  stage: build
  image: docker:24-dind
  services:
    - docker:24-dind
  before_script:
    - docker login -u ${HARBOR_USER} -p ${HARBOR_PASS} ${HARBOR_URL}
  script:
    - docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
    - docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest
    - docker push ${IMAGE_NAME}:${IMAGE_TAG}
    - docker push ${IMAGE_NAME}:latest
  after_script:
    - docker logout ${HARBOR_URL}

scan:
  stage: scan
  image: curlimages/curl:8.5.0
  script:
    # Ждём завершения авто-сканирования
    - sleep 30
    # Проверяем результаты
    - |
      SCAN_RESULT=$(curl -sk -u ${HARBOR_USER}:${HARBOR_PASS} \
        "${HARBOR_URL}/api/v2.0/projects/${HARBOR_PROJECT}/repositories/${CI_PROJECT_NAME}/artifacts/${IMAGE_TAG}?with_scan_overview=true" \
        | jq -r '.scan_overview."application/vnd.security.vulnerability.report; version=1.1".severity')
      echo "Scan result: ${SCAN_RESULT}"
      if [ "${SCAN_RESULT}" = "Critical" ]; then
        echo "CRITICAL vulnerabilities found! Blocking deployment."
        exit 1
      fi

deploy:
  stage: deploy
  script:
    - kubectl set image deployment/backend \
        backend=${IMAGE_NAME}:${IMAGE_TAG} \
        --namespace weapons-system
  only:
    - main
  when: manual

Robot accounts для CI/CD — вместо личных учёток:

# Создание robot account с ограниченными правами
curl -k -u admin:'H@rb0r_Adm1n_2026!' \
  -X POST https://harbor.oborontech.local/api/v2.0/robots \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "ci-builder",
    "description": "GitLab CI robot account",
    "duration": 365,
    "level": "project",
    "permissions": [{
      "namespace": "weapons-system",
      "kind": "project",
      "access": [
        {"resource": "repository", "action": "push"},
        {"resource": "repository", "action": "pull"},
        {"resource": "artifact", "action": "read"},
        {"resource": "scan", "action": "create"}
      ]
    }]
  }'
# Возвращает: {"name": "robot$ci-builder", "secret": "..."}

Сравнение с GitLab Registry и Nexus

Перед выбором Harbor мы оценили альтернативы:

КритерийHarborGitLab RegistryNexus Repository
Сканирование уязвимостейTrivy (встроен), ClairTrivy (в GitLab Ultimate)Нет встроенного
RBACProject-level, 5 ролейНаследует GitLab RBACRealm-level, гибкий
РепликацияPush/pull, event-basedGeo replication (Premium)Proxy repository
LDAP/ADВстроенЧерез GitLabВстроен
Air-gappedПолная поддержкаОграниченнаяПолная поддержка
Helm chartsOCI + ChartMuseumНет (отдельный Package Registry)Hosted Helm repo
Garbage collectionВстроен, по расписаниюВстроенВстроен
UIПолноценный веб-интерфейсИнтегрирован в GitLabПолноценный
ЛицензияApache 2.0 (бесплатно)Core (бесплатно), сканер в UltimateOSS (бесплатно), Pro ($$$)
МультиформатDocker, Helm, CNABDocker, NPM, Maven, etc.Docker, NPM, Maven, PyPI, etc.

Для «ОборонТех» Harbor выиграл по совокупности: бесплатный enterprise-grade registry с встроенным сканером, LDAP, репликацией и полной поддержкой air-gapped установки. GitLab Registry отпал, потому что сканирование доступно только в Ultimate ($99/user/month). Nexus не имеет встроенного сканера уязвимостей.

Результаты и выводы

После 2 недель внедрения Harbor стал центральным элементом инфраструктуры «ОборонТех»:

МетрикаДо (Docker Registry v2)После (Harbor)
Аутентификацияhtpasswd (5 учёток)LDAP/AD (120 пользователей)
Сканирование уязвимостейОтсутствуетАвто-сканирование при push
RBACНет (все — admin)5 ролей, project-level
АудитТолько access logs nginxПолный аудит push/pull/delete
Репликацияrsync по cronEvent-based, автоматическая
Образы с критическими CVE в prodНеизвестно0 (блокируются политикой)
Время настройки2 дня (с LDAP и Trivy)

Ключевые рекомендации для air-gapped Harbor:

  • Планируйте процесс обновления БД Trivy — еженедельная синхронизация через DMZ достаточна для большинства сценариев.
  • Robot accounts для CI/CD вместо личных учёток — разделение ответственности и ротация токенов.
  • Garbage collection по расписанию — без него диск заполняется за месяцы, особенно при частых сборках.
  • Репликация event-based вместо scheduled — образы доступны на резервной площадке через секунды после push, а не через часы.
  • Квоты на проекты с первого дня — проще ограничить сразу, чем разгребать 500 GB мусора потом.

Если вашей организации нужен приватный Docker-реестр enterprise-уровня — обращайтесь к нам в itfresh.ru.

Часто задаваемые вопросы

Да, существует официальный Helm-чарт для установки Harbor в Kubernetes: helm install harbor harbor/harbor. Он поддерживает Ingress, PVC для хранения, внешнюю базу данных (PostgreSQL) и внешний Redis. Для production в K8s это предпочтительный вариант, так как обеспечивает HA через ReplicaSet и автоматический рестарт компонентов.
Harbor поддерживает in-place upgrade: скачиваете новую версию installer, запускаете prepare и docker compose up -d. Data volume (/data/harbor) сохраняется между версиями. Перед обновлением обязательно сделайте бэкап базы данных (pg_dump) и data volume. Harbor поддерживает upgrade только на одну major-версию вперёд: 2.8 → 2.9 → 2.10, пропускать нельзя.
Зависит от количества и размера образов. Система Harbor сама потребляет около 5 GB. Один Docker-образ Go-приложения — 50-200 MB, Python/Java — 300-800 MB. При 50 образах по 5 тегов каждый, среднем размере 300 MB — потребуется около 75 GB. Harbor дедуплицирует layer-ы: если 10 образов базируются на одном alpine, базовый layer хранится один раз. Планируйте 2x от расчётного объёма + настройте GC.
Docker Hub Private — облачный SaaS-сервис Docker Inc. Harbor — self-hosted решение, которое вы разворачиваете на своих серверах. Ключевые отличия: Harbor работает в air-gapped среде, интегрируется с корпоративным LDAP/AD, сканирует уязвимости бесплатно (на Docker Hub — в платном плане), поддерживает репликацию между площадками и project-level RBAC. Docker Hub проще для open source проектов, Harbor — для enterprise и госсектора.
Harbor поддерживает OCI-формат для Helm-чартов из коробки (Helm 3.8+). Пушите чарт через helm push chart.tgz oci://harbor.example.com/project, и он появится в UI Harbor рядом с Docker-образами. Для legacy Helm (до 3.8) Harbor включает встроенный ChartMuseum — добавляете репозиторий через helm repo add и работаете как с обычным Helm-репозиторием.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#harbor#docker registry#private registry#air-gapped#trivy#vulnerability scanning#ldap authentication#image replication
Комментарии 0

Оставить комментарий

загрузка...