В этой статье хотелось бы рассмотреть, как организовать репозиторий программных пакетов с ограниченным доступом с практическими примерами. Такой репозиторий может быть полезен в разных ситуациях:
Если у вас есть тестовый репозиторий, а его пользователи находятся далеко, в таком случае доступ из интернет к репозитрию будет необходим, но при этом в такой репозиторий не должны попадать кто попало
Разработчик может распространять закрытый продукт, который должен быть доступен только авторизованным пользователям. При этом полный список пользователей заранее неизвестен, и нужен быстрый способ предоставления доступа.
Это может быть коммерческий репозиторий с платной подпиской.
И так далее.
Какие есть варианты организации такого репозитория? Рассмотрим несколько идей.
Первый вариант — создать VPN-соединение и раздавать клиентам настройки ПО. Однако этот метод требует от пользователей много действий для доступа к веб-сайту с пакетами: установка и настройка программ.
Второй вариант — использовать готовые решения, такие как Spacewalk или Katello+Pulp и т.д.. Эти комплексы обеспечивают авторизованный доступ к программному обеспечению. Однако они громоздкие, и большая часть функционала может быть излишней.
Третий вариант — разработать собственное ПО для валидации пользователей и предоставления доступа. Но здесь нужно будет не только настроить соединение на стороне клиента, но и поддерживать серверную часть. Кроме того, потребуется обеспечить совместимость с различными версиями операционных систем по мере их обновления. Это довольно трудоемко.
Четвертый вариант — разграничить доступ с помощью веб-сервера, который ограничивает доступ к сайту-репозиторию. Например, можно использовать HTTP-аутентификацию. Достаточно часто можно увидеть ограничение доступа к репозиторию таким образом, например такая строка в настройках репозитория говорит о необходимости авторизоваться для доступа: baseurl=http://user:pass@example.tst/myrepo/dev-repo.
Примерная схема работы такого репозитория:

Преимущества такого подхода: клиенту нужно лишь создать один правильный надстроечный файл, и система будет работать корректно. Большинство пакетных менеджеров операционных систем поддерживают этот вид подключения к репозиторию. Также можно управлять доступом пользователей: предоставлять и отзывать его.
Однако есть и недостатки. Например, если доступ должен быть временным и автоматически пропадать по истечении времени, нужно не забыть сбросить пароль для такого пользователя в нужный момент. Это требует дополнительного программного обеспечения для учёта доступа.
Существует еще метод получения доступа с помощью SSL-сертификата.
Схема идентична предыдущей, однако вместо базы данных паролей используется база данных сертификатов, а вместо комбинации "логин:пароль" применяется сертификат пользователя.
Данная схема так же поддерживается многими пакетными менеджерами, достаточно прописать путь к полученным сертификатам. И главное, что с помощью сертификата можно задать время действия сертификата, что автоматически решит проблему временного доступа. Весьма изящное решение.
Подобное решение применено и в ранее упомянутых Spacewalk и Katello+Pulp.
Для реализации такого механизма нужно иметь сам репозиторий, с подготовленной структурой и размещенным в корне сайта сервера, и так же иметь базу сертификатов.
На схеме ниже показано, как это работает:

Для управления базой сертификатов можно использовать готовые решения, такие как FreeIPA, или создать собственный центр сертификации. FreeIPA может показаться слишком сложным для текущей задачи, поэтому здесь опишу организацию собственного небольшого центра сертификации вручную.
Недавно на Хабре опубликовали статью об утилитах для работы с собственным центром сертификации. Также существует Easy-RSA для тех, кто предпочитает этот инструмент при работе с сертификатами. Кроме того, доступна документация по созданию собственного центра сертификации с примерами команд openssl.
Последний вариант хорош тем, что дает возможность подстроить центр сертификации под конкретные узкие задачи. Действия из документации для создания центра сертификации, были адаптированы и структурированы в виде набора bash-скриптов предназначенных под определенные задачи, необходимых для выдачи серверных и клиентских сертификатов для репозитория. Чтоб не держать в памяти какую утилиту и для чего использовать и не перечитывать документацию, был сделан веб-интерфейс, для упрощения выпуска сертификатов с поддержкой API. Кроме того, наличие API может обеспечить интеграцию данных скриптов с внешним программным обеспечением, что позволит автоматизировать процесс выдачи сертификатов в будущем.
Вот что в итоге получилось: https://github.com/bayrepo/cecemaut
А сейчас практическая часть, предлагается организовать доступ по сертификатам для приватного репозитория пакетов с помощью cecemaut в режиме командной строки:
mkdir -p center && cd center git clone https://github.com/bayrepo/cecemaut.git && cd cecemaut
теперь создадим файл конфигурации в каталоге utils/custom_config.sh с содержимым для примера:
ROOT_DIR="/opt/certbase" #полный путь к будущему хранилищу сертификатов, желательно пустой каталог]" COUNTRY_NAME="RU" #двухбуквенный код страны ORG_NAME="MyOrgName" #название организации COMM_NAME="IP Testoviy" #дополнительное название организации SERT_PASS=$(cat ~/.pass) #пароль для корневого и промежуточного сертификатов VAL_DAYS="3652" # число дней действия корневого сертификата, для примера 10 лет
теперь нужно запустить скрипт подготовки базы сертификатов:
pushd utils && bash prepare.sh && popd
будут созданы корневой сертификат и дополнительные файлы и вся эта база будет размещена в каталоге /opt/certbase/ca, который был указан в конфигурации.
Далее дело за малым, нужно выпустить сертификат для веб-сервера, в моем случае — это nginx. Предположим, для примера, что репозиторий располагается на сервере по адресу: example1.com — это виртуальный хост nginx.
Запросим для него сертификат на 2 года:
pushd utils && bash make_server_cert.sh -t 731 example1.com && popd
Вывод утилиты дает следующие строки (вывод может меняться, по мере обновления cecemaut):
... Generated key set for server installation: - [OUTPUTDATA] private key: `/opt/certbase/ca/intermediate/private/example1.com.key.pem`; - [OUTPUTDATA_CERT] server certificate: `/opt/certbase/ca/intermediate/certs/example1.com.cert.pem.5`; - [OUTPUTDATA] CA chain: `/opt/certbase/ca/intermediate/certs/ca-chain.cert.pem`; - [OUTPUTDATA] revoked certificates list: `/opt/certbase/ca/intermediate/crl/ca-full.crl.pem`
Это и есть те файлы, которые нужно подключить в nginx, вот так будет выглядеть настройка для хоста nginx обслуживающего репозиторий, для примера это будет файл настроек /etc/nginx/conf.d/example1.com.conf:
server { listen 8081 ssl; server_name example1.com www.example1.com; root /var/www/example1.com/html; index index.html; location / { try_files $uri $uri/ =404; } access_log /var/log/nginx/example1.com.access.log; error_log /var/log/nginx/example1.com.error.log debug; ssl_certificate /opt/certbase/ca/intermediate/certs/example1.com.cert.pem.5; ssl_certificate_key /opt/certbase/ca/intermediate/private/example1.com.key.pem; ssl_client_certificate /opt/certbase/ca/intermediate/certs/ca-chain.cert.pem; ssl_crl /opt/certbase/ca/intermediate/crl/ca-full.crl.pem; ssl_verify_client on; keepalive_timeout 70; # другие настройки, если нужны }
И наконец выпустим сертификат для клиента для доступа к этому серверу на доступ в течение месяца:
pushd utils && bash make_client_cert.sh -s example1.com -c user1@user1 -d 30 && popd
Утилита выдает:
... - [OUTPUTDATA] private key: `/opt/certbase/ca/client_certs/example1.com/private/user1@user1_private.key.pem`; - [OUTPUTDATA_CERT] server certificate: `/opt/certbase/ca/client_certs/example1.com/user1@user1.cert.pem.1`; - [OUTPUTDATA] CA chain: `/opt/certbase/ca/intermediate/certs/ca-chain.cert.pem`
Эти файлы нужно отправить клиенту.
Теперь со стороны клиента можно обратиться к репозиторию:
Обращаемся без сертификата (lynx в этом случае запускался с FORCE_SSL_PROMPT:YES, т.к в режиме —dump он прекращает работу ругаясь на самоподписанный сертификат и требует ca-цепочку, с полным набором сертификатов все работает хорошо):
lynx --dump https://example1.com:8081 400 Bad Request No required SSL certificate was sent __________________________________________________________________ nginx/1.20.1
Теперь с сертификатом:
SSL_CLIENT_CERT_FILE=/opt/certbase/ca/client_certs/example1.com/user1@user1.cert.pem.1 SSL_CLIENT_KEY_FILE=/opt/certbase/ca/client_certs/example1.com/private/user1@user1_private.key.pem SSL_CERT_FILE=/opt/certbase/ca/intermediate/certs/ca-chain.cert.pem lynx --dump https://example1.com:8081 Вывод команды: Список доступных в репозитории пакетов Всего пакетов 26 (BUTTON) Debug …
Теперь все работает корректно.
Следующим шагом подключим репозиторий, например, в dnf, для этого необходимо создать файл /etc/yum.repos.d/test.repo с содержимым:
[test] name = test enabled = 1 sslverify = 1 gpgcheck = 1 baseurl = https://example1.com:8081 sslclientkey=/opt/certbase/ca/client_certs/example1.com/private/user1@user1_private.key.pem sslclientcert=/opt/certbase/ca/client_certs/example1.com/user1@user1.cert.pem.1 sslcacert=/opt/certbase/ca/intermediate/certs/ca-chain.cert.pem
и проверяем пакеты в нем:
yum list --disablerepo="*" --enablerepo=test | grep test | wc -l 26
Доступны 26 пакетов в новом репозитории, это верно и то что нужно.
Если же убрать из настроек сертификаты, то получаем:
yum list --disablerepo="*" --enablerepo=test | grep test | wc -l Errors during downloading metadata for repository 'test': - Status code: 400 for https://example1.com:8081/repodata/repomd.xml (IP: 192.168.3.145) Error: Failed to download metadata for repo 'test': Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried 1
Даже если отключить sslverify = 0, то все равно будет ошибка доступа к репозиторию.
Для отзыва сертификата пользователя достаточно вызвать команду:
pushd utils && bash make_client_revoke.sh -n 1 -s example1.com -c user1@user1 && popd
И теперь обращение к репозиторию вновь закрыто:
SSL_CLIENT_CERT_FILE=/opt/certbase/ca/client_certs/example1.com/user1@user1.cert.pem.1 SSL_CLIENT_KEY_FILE=/opt/certbase/ca/client_certs/example1.com/private/user1@user1_private.key.pem SSL_CERT_FILE=/opt/certbase/ca/intermediate/certs/ca-chain.cert.pem lynx --dump https://example1.com:8081 400 Bad Request The SSL certificate error __________________________________________________________________ nginx/1.20.1
yum list --disablerepo="*" --enablerepo=test | grep test | wc -l Errors during downloading metadata for repository 'test': - Status code: 400 for https://example1.com:8081/repodata/repomd.xml (IP: 192.168.3.145)
Единственно, после отзыва сертификата nginx нужно перезапустить, что перечитался crl файл.
Для каждого серверного сертификата можно создавать много клиентских сертификатов, таким образом организуя доступ к ресурсу. Каждый серверный сертификат имеет свой промежуточный сертификат, которым и подписывается, такой подход позволяет разграничить доступ клиентам для разных ресурсов, т.к клиентские сертификаты для одного серверного не будут проходить верификацию для другого серверного сертификата, т.к. у них будут отличаться CA-цепочки.
Вот таким нехитрым способом можно ограничить доступ к репозиторию или любому другому ресурсу и раздавать нужным пользователям специальные клиентские сертификаты для доступа. Конечно же это не защитит от передачи ключа постороннему человеку или не убережет от утечки ключа, т. к. кто имеет ключ, тот имеет доступ, но борьба с подобными случаями — это уже другой уровень защиты.
