UMGUM.COM 

Sonatype Nexus ( Развёртываем сервер репозиториев артефактов "Nexus Repository Manager" для хранения docker-образов и nuget-библиотек. )

25 февраля 2021

OS: "Linux Debian 9/10", "Linux Ubuntu Server 16/18/20 LTS".
Apps: "Nginx", "Nexus RM", "Docker", "Docker Compose", LDAP.

Задача: развернуть сервер репозиториев артефактов "Sonatype Nexus Repository Manager v3 (OSS)", настроив его для хранения docker-образов и nuget-библиотек.


Последовательность дальнейших действий такова:

1. Подготовка системного окружения (отдельная инструкция);
2. Установка системы контейнеризации "Docker" (отдельная инструкция);
3. Установка сопутствующего ПО и подготовка конфигурации;
4. Подготовка конфигурации для фронтального web-сервера "Nginx";
5. Наладка запуска посредством "Docker Compose" и "Systemd";
6. Конфигурирование web-приложения "Nexus Repository Manager v3 (OSS)";
7. Проверка функциональности docker-репозитория;
8. Интеграция с внешним LDAP/AD для аутентификации.


Подготовка несущей файловой структуры для приложения "Nexus".

В контейнере "Nexus" запускается к контексте пользователя с UID:200. В несущей системе "Debian/Ubuntu" этот UID по умолчанию обычно никем не занят, что позволяет удобно создать пользователя, которому очевидно будет выданы права доступа к ресурсам, выделенным для docker-контейнера с "Nexus":

# mkdir -p /var/opt/nexus/data
# mkdir -p /var/opt/nexus/logs

# groupadd --system --gid 200 nexus
# useradd --system --home-dir /var/opt/nexus --shell /bin/false --gid nexus --uid 200 nexus

# chown -R nexus:nexus /var/opt/nexus
# chmod -R o-rwx /var/opt/nexus

Преконфигурирование "Nexus".

Для начала запустим "Nexus" в режиме развёртывания, даже без включения сети, подав внутрь контейнера указание применить желаемые параметры java-машины, которые будут автоматически сохранены в директории "/var/opt/nexus/data" для использования в последующих, уже рабочих запусках:

# docker run --rm --name nexus-preconfigure -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=3g" -v /var/opt/nexus/data:/nexus-data sonatype/nexus3

....
Started Sonatype Nexus OSS 3.29.2-02
------------------------------------

# docker top nexus-preconfigure

Достаточно дождаться уведомления об успешном запуске "Nexus" и завершить работу контейнера (Ctrl+C).

Подготовка конфигурации для фронтального web-сервера "Nginx".

Для последующего включения в "Nginx" современного SSL/TLS и HTTPv2 генерируем DH-файл:

# mkdir -p /var/opt/letsencrypt
# openssl dhparam -out /var/opt/letsencrypt/dhparam-2048.pem 2048
# chown -R root:root /var/opt/letsencrypt && chmod -R o-rwx /var/opt/letsencrypt

Создадим в файловой системе несущего сервера структуры для конфигурации "Nginx":

# mkdir -p /var/opt/nginx/etc/conf.d
# mkdir -p /var/opt/nginx/logs

Конфигурация принимающего подключения пользователей web-сервера "Nginx" проста и сводится к описанию параметров "проксирования" всех запросов web-сервису, запущенному внутри docker-контейнера:

# vi /var/opt/nginx/etc/conf.d/nexus.example.net.conf

server {
  listen      80;
  listen [::]:80;
  server_name nexus.example.net;
  server_tokens off;
  location /.well-known/acme-challenge {
    root /var/www/letsencrypt;
  }
  location / {
    return 307 https://$host$request_uri;
  }
}

server {
  listen      443 ssl http2;
  listen [::]:443 ssl http2;
  server_name nexus.example.net;
  server_tokens off;
  ssl_certificate /etc/letsencrypt/live/nexus.example.net/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/nexus.example.net/privkey.pem;
  ssl_dhparam /etc/letsencrypt/dhparam-2048.pem;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_ciphers "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
  ssl_buffer_size 8k;
  ssl_stapling on;
  ssl_stapling_verify on;
  proxy_connect_timeout 3s; # (default: 60s)
  client_max_body_size 0; # (default: 1m)
  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
    proxy_pass http://nexus:8081;
    proxy_redirect off;
  }
}

server {
  listen      5000 ssl http2;
  listen [::]:5000 ssl http2;
  server_name nexus.example.net;
  server_tokens off;
  ssl_certificate /etc/letsencrypt/live/nexus.example.net/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/nexus.example.net/privkey.pem;
  ssl_dhparam /etc/letsencrypt/dhparam-2048.pem;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_ciphers "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
  ssl_buffer_size 8k;
  ssl_stapling on;
  ssl_stapling_verify on;
  proxy_connect_timeout 3s; # (default: 60s)
  client_max_body_size 0; # (default: 1m)
  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
    proxy_pass http://nexus:5000;
    proxy_redirect off;
  }
}

По умолчанию "Nginx" внутри docker-контейнера стартует в контексте суперпользователя "root", так что убираем доступ к его конфигурации для всех остальных:

# chown -R root:root /var/opt/nginx
# chmod -R go-rwx /var/opt/nginx

Наладка запуска посредством "Docker Compose".

Создаём директорию для размещения конфигурационных файлов "Docker Compose":

# mkdir -p /usr/local/etc/compose
# cd /usr/local/etc/compose

Воспользуемся для единственного в нашей схеме конфигурационного файла именем "по умолчанию":

# vi ./docker-compose.yml

version: "3"
services:
  nginx:
    depends_on:
      - nexus
    container_name: nginx
    image: nginx:latest
    networks:
      nexus:
        aliases:
          - "nginx"
    ports:
      - 80:80
      - 443:443
      - 5000:5000
    environment:
      TZ: "/etc/timezone"
    volumes:
      - "/var/opt/nginx/etc/conf.d:/etc/nginx/conf.d:ro"
      - "/var/opt/letsencrypt:/etc/letsencrypt:ro"

  nexus:
    container_name: nexus
    image: sonatype/nexus3
    networks:
      nexus:
        aliases:
          - "nexus"
    environment:
      TZ: "/etc/timezone"
    working_dir: "/var/opt/nexus"
    volumes:
      - "/var/opt/nexus/data:/nexus-data:rw"

networks:
  nexus:
    driver: bridge
    internal: false
    ipam:
      driver: default
      config:
        - subnet: 100.127.255.0/24

Запускаем посредством "Docker Compose" всю пачку контейнеров:

# cd /usr/local/etc/compose
# docker-compose up --remove-orphans --build --force-recreate -d

Пример точечного запуска определённого контейнера:

# docker-compose up --no-start nexus
# docker-compose start nexus

Пример точечной остановки и высвобождения ресурсов определённого контейнера:

# docker-compose stop nexus
# docker-compose rm -f -v nexus

Останавливаем разом все docker-контейнеры, описанные в конфигурации "Docker Compose":

# docker-compose down

Настройка автозагрузки "Docker Compose" посредством "Systemd".

Создаём файл описания параметров запуска и остановки docker-контейнера посредством "Docker Compose" посредством короткоживущего сервиса "Systemd":

# vi /etc/systemd/system/nexus-docker.service

[Unit]
Description=Nexus Repository Manager in Docker Compose Service
Requires=network.target docker.service containerd.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/usr/local/etc/compose

ExecStartPre=-/bin/bash -c 'chown -R nexus:nexus /var/opt/nexus'
ExecStartPre=/usr/local/bin/docker-compose -f docker-compose.yml down --remove-orphans
ExecStart=/usr/local/bin/docker-compose -f /usr/local/etc/compose/docker-compose.yml up --remove-orphans --build --force-recreate --detach
#
ExecStop=/usr/local/bin/docker-compose -f /usr/local/etc/compose/docker-compose.yml down

[Install]
WantedBy=multi-user.target

Указываем "Systemd" перечитать и принять новую конфигурацию, а потом явно активируем и запускаем новый сервис:

# systemctl daemon-reload
# systemctl enable nexus-docker.service
# systemctl start nexus-docker

Смотрим журнал событий "Systemd" если "что-то пошло не так":

# systemctl status nexus-docker.service
# journalctl -xe

Наладка ротации файлов журнала событий.

Не забываем, что для самодельных сервисов ротация их журналов событий не ведётся, и нужно настраивать это отдельно:

# vi /etc/logrotate.d/docker-opt

/var/opt/*/logs/*.log {
  size 30M
  missingok
  notifempty
  rotate 5
  compress
  delaycompress
  copytruncate
  su root root
}

Проверяем корректность конфигурации, не воздействуя при этом на файлы журналов:

# logrotate -d /etc/logrotate.d/docker-opt

Конфигурирование web-приложения "Nexus Repository Manager v3 (OSS)".

Давно разрабатываемые программные продукты, продающиеся на рынке, радуют - все необходимые настройки делаются через довольно удобный web-интерфейс. Просто заходим на сайт, аутентифицируемся как суперпользователь "admin" (в первый раз, до обязательной смены, пароль достаём из файла "/var/opt/nexus/data/admin.password") и приступаем.

Первым делом я бы отключил напрочь доступ к ресурсам без аутентификации:

Nexus -> Administration -> Security -> Anonymous Access:
  Access: off
....
Nexus -> Administration -> Security -> Users -> "anonymous":
  Status: Disabled

Задаём реальный почтовый адрес локальному суперпользователю, чтобы не пропустить важных уведомлений:

Nexus -> Administration -> Security -> Users -> "admin":
  Email: admin@example.net

Разумеется, связь с почтовым сервером тоже понадобится настроить:

Nexus -> Administration -> System -> Email Server:
  Enabled: true
  Host: mail.example.net
  ....

Создаём репозиторий для nuget-библиотек:

Nexus -> Administration -> Repository -> Repositories -> Create repository:
  Recipe: nuget (hosted)
  Format: nuget
  Name: nuget-app

С репозиторием для docker-образов посложнее будет - понадобится явно выделить свободный TCP-порт для приёма клиентских подключений:

Nexus -> Administration -> Repository -> Repositories -> Create repository:
  Recipe: docker (hosted)
  Name: docker-nginx
  Online: on
  HTTP:
    Port: 5000
  HTTPS: off
  Allow anonymous docker pull: off

К разнице в настройках двух репозиториев выше поясню, что протокол взаимодействия с "Docker Registry" при аутентификации осуществляет привязку пользователя только к точке входа в сервер, откидывая URL-path (подкаталоги, имена файлов, ?-запросы и #-якоря), оставляя лишь URL-host и URL-port. Таким образом, технически нельзя на одном доменном имени и TCP-порту поддерживать два и более docker-репозитория - приходится каждому отдельному docker-репозиторию выделять свой TCP-порт.

После того, как репозитории артефактов созданы, подготовим для них описания ролей доступа:

Nexus -> Administration -> Security -> Roles -> Create role:
  Role ID: role-docker-nginx-user
  Role name: role-docker-nginx-user
  Privileges:
    nx-repository-view-docker-docker-nginx-add
    nx-repository-view-docker-docker-nginx-edit
    nx-repository-view-docker-docker-nginx-read

В конфигурировании роли для доступа к nuget-репозиториям есть одно отличие от всех остальных, поддерживаемых "Nexus" - потребуется добавить право доступа к функционалу оперирования API-ключами (всех типов - ибо ни для чего более "Nexus" пока создавать токены не умеет):

Nexus -> Administration -> Security -> Roles -> Create role:
  Role ID: role-nuget-app-user
  Role name: role-nuget-app-user
  Privileges:
    nx-apikey-all
    nx-repository-view-nuget-nuget-app-*

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

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

В "Nexus" имеется возможность аутентификации через удалённый LDAP-сервис. Но, к сожалению, у пользователей "Nexus" нет возможности создавать индивидуальные token-ключи для аутентификации при непосредственном обращении к docker-репозиториям. Учитывая то, что логин и пароль подключения к docker-репозиториям открытым текстом сохраняются в профилях большинства docker-клиентов, было бы неразумно для обращения к docker-репозиториям пользоваться учётными записями пользователей из LDAP. Таким образом, несмотря на имеющуюся интеграцию с LDAP, всё же для каждого нового подключения к docker-репозиторию надёжнее создавать отдельного локального пользователя "Nexus":

Nexus -> Administration -> Security -> Users -> Create local user:
  ID: username0-nexus
  First name: Username0
  Last name: Nexus
  Email: username0@example.net
  Password: ***
  Status: Active
  Roles -> Granted:
    role-docker-nginx-user
    role-nuget-app-user

И, последний штрих - добавляем в перечень работающих подсистем аутентификации обслуживающие специфичные запросы к docker/nuget-репозиториям:

Nexus -> Administration -> Security -> Realms:
  Active:
    ....
    Docker Bearer Token Realm
    NuGet API-Key Realm

Проверка функциональности docker-репозитория.

Первым делом осуществляем аутентификацию docker-клиента на целевом сервере "Docker Registry" под управлением "Nexus":

$ docker login -u docker-username0 nexus.example.net:5000

WARNING! Your password will be stored unencrypted in /home/user/.docker/config.json.
Login Succeeded

Загружаем из официального docker-репозитория образ для тестирования, например web-сервера "Nginx":

$ docker pull nginx:latest

После загрузки образ можно наблюдать в локальном репозитории:

$ docker images

REPOSITORY TAG    IMAGE ID
....
nginx      latest f6d0b4767a6c

Помечаем локальный образ как доступный для загрузки в наш репозиторий под управлением "Nexus":

$ docker tag f6d0b4767a6c nexus.example.net:5000/nginx:latest

Теперь в локальном репозитории условно два одинаковых docker-образа с разными адресами хранения (на самом деле docker-образ физически один, но ему присвоено два "тега" с разными адресами):

$ docker images

REPOSITORY                   TAG    IMAGE ID
....
nexus.example.net:5000/nginx latest f6d0b4767a6c
nginx                        latest f6d0b4767a6c

Теперь отдаём команду выгрузить в целевой docker-репозиторий соответствующий ему docker-образ:

$ docker push nexus.example.net:5000/nginx

Если после этого пройти в web-интерфейс "Nexus", то там можно будет обнаружить сведения о загруженном docker-образе и описание его параметров в виде большого количества иерархически распределённых мета-данных.

Неудобно, что бесплатные версии docker-клиентов не поддерживают простое прямое удаление docker-образов из удалённого репозитория - это приходится делать через "docker API" нелинейной серией HTTP-запросов. Проще всего неподготовленному пользователю удалить ненужный ему образ через web-интерфейс "Nexus", если ему на то выделены полномочия, разумеется.

Как минимум, легко можно удалить docker-образ (вернее один из "тегов" - только после удаления последнего "тега" будет удалён и docker-образ, как таковой) из локального репозитория операционной системы:

$ docker rmi nexus.example.net:5000/nginx

После завершения всех процедур явно закрываем аутентифицированный сеанс с docker-репозиторием, во избежание:

$ docker logout nexus.example.net:5000

Интеграция с внешним LDAP/AD для аутентификации.

Выше я упоминал о нежелательности использования рядовых учётных записей пользователей, обслуживаемых LDAP/AD, для аутентификации при работе с репозиториями артефактов "Nexus" - резко возрастает риск утечки паролей.

Однако для упрощённого добавления новых администраторов web-сервиса "Nexus" вполне можно воспользоваться интеграцией с LDAP-сервисом.

Прежде всего, вероятно, понадобится загрузить публичную часть ключа SSL-сертификата LDAP-сервиса, если таковой "самоподписанный", в хранилище доверенных сертификатов "Nexus" (очень удобно, что это можно сделать через web-интерфейс, минуя обычные для java-приложений приседания с их специфичными "keytool" и "cacerts"):

Nexus -> Administration -> Security -> SSL Certificates:
  Load Certificate from Server:
    ldaps://ldap.example.net:636
  Add certificate to truststore.

Настройка подключения и правила взаимодействия с LDAP-сервисом в "Nexus" просты и отшлифованы годами эксплуатации:

Nexus -> Administration -> Security -> LDAP -> Create connection:
  Connection:
    Name: ldap.example.net
    LDAP server address: ldaps://ldap.example.net:636
    Use the Nexus truststore: true
    Search base DN: dc=example,dc=net
    Authentication method: Simple Authentication
    Username or DN: uid=nexus,ou=Accounts,ou=Services,dc=example,dc=net
    Password: ***
  Users and Groups:
    Configuration template: Generic LDAP Server
    User relative DN: ou=People
    User subtree: true
    Object class: inetOrgPerson
    User filter: (&(objectclass=person)(memberOf=cn=nexusadmins,ou=groups,dc=example,dc=net))
    User ID attribute: uid
    Real name attribute: cn
    Email attribute: mail
    Password attribute: (blank)
    Map LDAP groups as roles: false

Сразу после настройки интеграции с LDAP-сервисом внутри "Nexus" можно будет добавлять пользователей из LDAP-сервиса и выделять роль полного администратора, например:

Nexus -> Administration -> Security -> Users -> LDAP -> "userName0":
  Roles -> Granted:
    nx-admin


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )