Как стать автором
Обновить

Админка для Private Docker Registry (Registry Admin)

Время на прочтение10 мин
Количество просмотров14K

Концепция контейнеризации на базе Docker, и ему подобных технологий, для многих разработчиков стала незаменимым инструментом доставки своих продуктов конечным пользователям в виде полностью подготовленной среды использования. В большинстве случаев, особенно это касается open source продуктов, для распространения используются бесплатные реестры такие как Docker Hub или Github Container Registry. Но когда количество продуктов и размеры образов начинают увеличиваться, а также требуется более расширенное управление репозиториями и доступом к ним, то разработчики и компании начинают задумываться о своих собственных хранилищах для контейнерных образов (images). К счастью вариантов для развертывания своего собственного экземпляра Container Registry предостаточно, начиная от самого простого варианта Docker Registry и до более масштабных систем таких как HARBOR, Dragonfly и прочие.

Для своих задач я выбрал самый простой вариант на базе Docker Registry, который разворачивается за минуту имеет достаточно много различных настроек и интеграций с различными облачными сервисами. Работает он достаточно предсказуемо, надежно, а также написан на языке который я знаю. Изначально доступ к registry нужен был только для одного пользователя - меня самого, поэтому я выбрал обычный механизм доступа на основе htpasswd

Этот механизм очень простой, все пользователи указанные в файле htpasswd имеют полный доступ ко всему реестру, что является сложностью при появлении задачи раздельного доступа к реестру, которая у меня и возникла в скором времени. Среди возможных способов доступа к Docker Registry имеется механизм авторизации на основе token, который позволяет организовать управляемый доступ в зависимости от репозитория и действий пользователя (pull, push, delete). Однако реализация данного механизма является не очень простой задачи. Требуется отдельный auth сервер, сертификаты с ключами, которыми будет подписываться и проверяться token. Также хотелось получить единый интерфейс управления пользователями, доступами и запиясми репозиториев. К сожалению, из простых вышеперечисленных решений я не нашел такого, которое бы решало следующие задачи:

  • разделение доступа к репозиториям на основе действий пользователей (pull/push);

  • доступ на основе ролей (RBAC);

  • просмотр списка репозиториев и информацию об образах;

  • удаление образов;

  • механизм поиска по записям;

  • анонимный доступ и доступ для зарегистрированных пользователей;

  • прочие функции (логирование, генератор сертификатов и т.п.).

Скорее всего я плохо искал и что-то похожее уже реализовано, но в итоге я решил реализовать свой сервис RegistryAdmin, который будет решать выше поставленные задачи.

По больше части энтузиазм написать свой, более-менее серьезный, open source проект возник благодаря популярному ИТ-подкасту Radio-T и особенно ведущему Umputun. В одно время я часто изучал код его проектов и заимствовал различные идеи в своих разработках (в том числе и в этом проекте), а также контрибьютил в некоторые из них, чтобы внести свой вклад в развитие проектов, которыми я пользуюсь. Некоторые технологии, которые я изучил и использую в повседневной работе, стали мне известны именно благодаря данному подкасту, за что хочу высказать ОГРОМНУЮ БЛАГОДАРНОСТЬ всем ведущим за их труд. Прошу не расценивать данный абзац как рекламу или что-то подобное т.к. таковым он не является, а имеет место искренний душевный порыв высказать благодарность.

Некоторые особенности реализации проекта

Изначально я хотел быстро написать простенький сервис, который будет интегрирован в существующий экземпляр, уже развернутого private registry, и просто выдавать токены авторизации существующим пользователям. Но после детального изучения документации на сайте Docker стало ясно, что быстро не получится.

Во-первых, для подписи и проверки токенов необходимы только RSA ключи (подпись текстовым секретом не подходит). Сертификат должен содержать адрес или имя хоста registry, по которому осуществляется запрос данных из реестра, т.е. в нём обязательно должны быть определенны поля subjectAltName. Поэтому механизм генерации RSA ключей для работы с токеном я решил встроить непосредственно в RegistryAdmin, чтобы можно было обойтись без сторонник утилит таких как openssl (хотя их также можно использовать). Сгенерированные ключи могут быть использованы и для TLS подключения.

Во-вторых, в пользовательском web-интерфейсе мне было необходимо реализовать полнотекстовый поиск по репозиториям, однако API registry позволяет получать список репозиториев только используя вариант курсора. По этой причине я решил реализовать синхронизацию списка репозиториев и данных по этим репозиториям во внутренней базе RegistryAdmin (на базе SQLite).

Для синхронизации в режиме реального времени на стороне registry настроен механизм уведомлений (notifications) о событиях, которые инициируют механизм актуализации данных в базе RegistryAdmin. Также доступен ручной механизм запуска синхронизации данных, который также запускается автоматически через заданный интервал времени.

Сам RegistryAdmin является обычным REST API приложением, который взаимодействует непосредственно с экземпляром registry. В качестве фреймворка для UI был выбран React-Admin, который достаточно легко и просто встраивается в различные REST API системы. Файлы UI интегрированны в бинарник при помощи библиотеки embed.

Помимо авторизации на основе токена, также поддерживается обычная авторизация через htpasswd файл, который синхронизируется со списком пользователей RegistryAdmin. Однако стоит иметь ввиду, что доступ к реестру на основе htpasswd не поддерживает разделение прав доступа на основе действий пользователя (pull/push).

Для управления и просмотра данных реестра через UI предусмотрены встроенные роли:

  • Admin - полные права для работы с реестром и репозиориями;

  • Manager - просмотр только списка всех репозиториев и списков доступа;

  • User - может только просматривать разрешенный список репозиториев.

Также в сервисе реализованы специальные права для анонимных и зарегистрированных пользователей, которые имеют специальные ID и не хранятся в базе RegistryAdmin.

В итоге получился простой по функционалу и немного усложненный, в части первичной настройки, сервис RegistryAdmin.

Учитывая, что я занимаюсь в основном backend разработкой, то тесты написаны только к самому сервису. В планах есть задача реализовать тесты к frontend части, но пока не нашел на это свободного времени. Было бы очень хорошо если бы, кто-нибудь оказал помощь в этом вопросе.

Установка и настройка RegistryAdmin

Дистрибутив RegistryAdmin доступен в двух вариантах:

  • единый бинарный файл для разных архитектур (доступен в релизах);

  • docker образ.

В данном обзоре я буду рассматривать вариант установки в среде docker, как наиболее предпочтительный, с использованием docker-compose.

Изначально нужно определится с архитектурой системы, чтобы чётко понимать какие настройки для чего нужны. В данном примере я буду выполнять развертывание для следующей схемы узлов:

В качестве сервера registry.local выступает сервер с развернутой средой docker. В качестве внешних клиентов может выступать любой узел внутри сети, которому доступен хост registry.local

  1. Создаем папку для стека контейнеров Docker Private Registry и RegistryAdmin

    mkdir registry-admin
    cd registry-admin

  2. В папке создаем файлы конфигурации (или копируем из примера), рекомендуется придерживаться следующей структуры:

    registry-admin\        
      config\        		       
         registry-config.yml        		       
         token-ra-config.yml    	  
      docker-compose.yml     

    где:
    registry-config.yml - файл конфигурации для registry
    token-ra-config.yml
    - конфигурационный файл RegistryAdmin

  3. Задать владельца для директории и файлов. По-умолчанию в контейнере определен владелец с UID=1000 . Данный UID может быть переопределен через переменную окружения APP_UID в файле docker-compose.yml.

     chown -R 1000:1000 ./

  4. Указываем основные параметры для RegistryAdmin и указываем пути и параметры для подключения к реестру и генерации ключей для токенов. Также мы будем использовать данные ключи для настройки TLS подключения.

    hostname: tnas.local
    
    ssl:
      type: static
      port: 443
      cert: /app/certs/cert.crt
      key: /app/certs/cert.key
    
    registry:
      host: https://registry
      port: 5000
      auth_type: token
      issuer: registry_token_issuer
      service: container_registry
      certs:
        path: /app/certs
        key: /app/certs/cert.key
        public_key: /app/certs/cert.pub
        ca_root: /app/certs/cert.crt
        ip: 192.168.12.69# <- paste a real IP of docker host which publish the container
        fqdns: [registry,registry-admin,tnas.local,registry.local]
    
    store:
      type: embed
      admin_password: "super-secret"
      embed:
        path: /app/data/store.db

    По указанному в параметре --cets.path пути будут автоматически созданы все необходимые ключи и сертификат. При запуске RegistryAdmin проверяет наличие файлов в данном каталоге и создает новые если директория пуста. Если данный параметр не задан, то по-умолчанию ключи создаются в домашней директории пользователя под которым запускается сервис.

  5. Определяем параметры для самого Private Docker Registry.

    URL указанный в параметре realm должен быть доступен клиентам, которые осуществляют авторизацию напрямую в сервисе RegistryAdmin.

    version: 0.1
    
    log:
      accesslog:
        disabled: false
      level: debug
      formatter: text
      fields:
        service: registry
    
    storage:
      filesystem:
        rootdirectory: /var/lib/registry
        maxthreads: 100
      delete:
        enabled: true
    
    http:
      addr: ":5000"
      net: tcp
      tls:
        certificate: /certs/cert.crt
        key: /certs/cert.key
          # - lientcas:
        # - /certs/cert.crt
    auth:
      token:
        # external ip or host accessible for clients from outside of container
        realm: https://registry.local:8443/api/v1/registry/auth  
        service: container_registry
        issuer: registry_token_issuer
        rootcertbundle: /certs/cert.crt
    
    notifications:
      events:
        includereferences: true
      endpoints:
        - name: ra-listener
          disabled: false
          url: https://registry-admin/api/v1/registry/events
          headers:
            # 'admin:super-secret' base64 encode string
            Authorization: [Basic YWRtaW46c3VwZXItc2VjcmV0] 
          timeout: 1s
          threshold: 5
          backoff: 3s
          ignoredmediatypes:
            - application/octet-stream
          ignore:
            mediatypes:
              - application/octet-stream
  6. Определить параметры для docker-compose

    version: '2.1'
    services:
      registry-admin:
        restart: unless-stopped
        image: zebox/registry-admin:latest
        ports:
          - 8080:80
          - 8443:443
        environment:
          - APP_UID=1000
          - RA_CONFIG_FILE=/app/config/token-ra-config.yml
        volumes:
          - ./certs:/app/certs
          - ./config:/app/config
          - ./data:/app/data
    
      registry:
        restart: unless-stopped
        image: registry:2
        ports:
          - 50554:5000
        volumes:
          - ./data:/var/lib/registry
          - ./certs:/certs
          - ./config/registry-config.yml:/etc/docker/registry/config.yml
        depends_on:
          - registry-admin
        # override container running command for add self-signed certificate to trusted CA
        command: ["/bin/sh", "-c", "cp /certs/cert.crt /usr/local/share/ca-certificates && /usr/sbin/update-ca-certificates; registry serve /etc/docker/registry/config.yml"]
    

    ВАЖНО! Переопределение команды (command) запуска контейнера с registry требуется для того, чтобы обойти ошибку доверия самоподписанного сертификата для корректной работы nofitfications внутри контейнера registry. Данная строка выполняет добавление сертификата в список доверенных при запуске контейнера registry. В случае если Вы не используете TLS подключение к RegistryAdmin переопределение commandне требуется. Также не требуется переопределение если используются доверенные сертификаты такие как Let's Encrypt, но в этом случае для HTTP TLS необхоимо указывать сертификат содержащий полную цепочку CA fullchain.pem.

  7. Запускаем контейнеры

    docker-compose up -d

Если все параметры верны, то сервис должен запуститься и быть доступен через браузер по адресу https://registry.local:8443

Если у Вас стоит задача развернуть систему с возможностью доступа из сети Интернет, то необходимо учитывать особенности использования доверенных TLS сертификатов. RegistryAdmin поддерживает получение сертификатов Let's Encrypt через протокол ACME (режим SSL AUTO). Если используется такой механизм получения публичного сертификата, то для доступа к registry по HTTPS следует определить параметр letsencrypt и указать путь к сертификату который был указан в параметре --ssl.acme-location для RegistryAdmin.

letsencrypt:
  cachefile: /path/to/cache-file           
  email: emailused@letsencrypt.com           
  hosts: [you-registry.domain.org]

В случае получения сертификата LE через HTTP-01 challenge, то как для registry так и для RegistryAdmin в качестве сертификата следует указывать fullchain.pem файл, иначе при взаимодействии между сервисами будет возникать ошибка доверия к сертификату - x509 certificate signed by unknown authority.

Использование RegistryAdmin

После того как успешно произведена настройка и запуск системы можно приступать к её использованию. Для входа в админку необходимо ввести данные доступа по-умолчанию:

username: admin
password: super-secret

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

Добавим новый репозиторий в реестр. Для этого произведем авторизацию с внешнего docker клиента под данными администратора и сделаем push нового образа в реестр. Но т.к. в данном примере мы используем самоподписанные сертификаты, то docker клиент будет ругаться на доверие к сертификату. Чтобы решить эту проблему есть два способа:

  1. На клиенте добавить наш реестр в список небезопасных реестров в файле конфигурации докер демона - daemon.json. После внесения измененний потребуется перезагрузка службы докера.

    # https://docs.docker.com/config/daemon/ 
    # /etc/docker/daemon.json (Linux) 
    # C:\ProgramData\docker\config\daemon.json (Windows) 
    {   
        "insecure-registries": ["registry.local:50554"]
    }
  2. Добавить сгенерированные сертифкаты в список доверенных сертификатов для клиентского хоста.

После решения вопроса с доверием сертификатов можно приступать к выполнению аутентификацию в реестре на клиенте.

После успешной аутентификации выполним загрузку первого образа в реестр.

Список репозиториев
Список репозиториев
Список тегов
Список тегов

Добавим пользователя с ролью user для последующего назначения ему соответствующего доступа.

Список пользователей
Список пользователей

Определим права доступа к репозиторию alpine для пользователя user1. Разрешим пользователю делать только pull.

Список доступов к репозиториям
Список доступов к репозиториям

Выполним аутентификацию на docker клиенте под пользователем user1

После успешной аутентификации выполним pull из репозитория alpine нашего реестра.

Как видим pull работает, попробуем выполнить push.

Как видно на скриншоте попытка push завершилась ошибкой. Добавим разрешение на возможность делать push для пользователя user1.

Попробуем снова выполнить push под текущим пользователем.

Как мы видим в репозитории alpine появился новый тэг, после добавления соответсвующего доступа действие pull было успешно выполнено.

Стоит обратить внимание, что в данном случае, при попытке удаления тега test будет также удален и тэг 3.14, потому-что у них одинаковая сигнатура digest, а registry хранит и удаляет элементы именно по этому хэшу. Также стоит имееть ввиду, что при удалении тэга из реестра удаляется только manifest файл, а данные удаляются путем запуска встроенного сборщика мусора (garbage collector).

Заключение

Итоговым результатом получилась, на мой взгляд, простая админка для управления записями реестра и доступом к Docker Private Registry, хотя есть некоторые особенности при настройке доверенных сертификатов, а также требуется понимание сетевого взаимодействия между сервисами. Надеюсь, что данный сервис может быть кому-то полезен.

В процессе реалзации были решины достаточно интересные задачи такие как работа с сертификатами, встраивание приложений SPA в бинарный файл, а также настройка CI/CD для автоматического тестирования и сборки проекта.

Всем желающим поучаствовать в тестировании и улучшении проекта добро поожаловать!

Все исходники проекта и примеры доступны на GitHub.

Теги:
Хабы:
Всего голосов 7: ↑6 и ↓1+7
Комментарии5

Публикации

Истории

Работа

Go разработчик
130 вакансий

Ближайшие события

AdIndex City Conference 2024
Дата26 июня
Время09:30
Место
Москва
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область