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

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

Схема аналогичная предыдущей, но вместо базы паролей — база сертификатов, а вместо пары — логин:пароль, сертификат пользователя.
Данная схема так же поддерживается многими пакетными менеджерами, достаточно прописать путь к полученному сертификату. И главное, что с помощью сертификата можно задать время действия сертификата. Весьма изящное решение.
Подобное решение применено и в ранее упомянутых Spacewalk и Katello+Pulp. Для реализации такого механизма нужно иметь сам репозиторий, с подготовленной структурой и размещенным в корне сайта сервера, который будет раздавать пакеты данного репозитория и саму базу сертификатов.
Для поддержания базы сертификатов можно как готовые решения использовать, типа FreeIPA, так и организовать работу собственного центра сертификации, причем даже руками. FreeIPA для текущей задачи кажется слишком перегруженным конфигруациями, поэтому проще использовать собственный цент сертификации.
Как раз недавно на Хабре описывали утилиты работы с собственным центром сертификации статья 1, а так же есть Easy-RSA, и еще документация как собрать собственный центр сертификации с примерами команд openssl.
Было решено для организации центра сертификации использовать набор скриптов для openssl, только немного свести воедино все действия описанные в инструкциях по организации центра сертификации в небольшой набор bash-скриптов, а потом поверх этих утилит организовать web-интерфейс, чтобы, если не используешь скрипты долгое время, то с помощью веб-интерфейса быстрее вспомнить как выпустить очередной сертификат, ну и наличие API позволит данные скрипты приделать к внешнему ПО, для атовматизации.
Вот что в итоге получилось: https://github.com/bayrepo/cecemaut
И теперь организуем сертификаты для приватного репозитория пакетов с помощью cecemaut в режиме командной строки (есть также GUI и API):
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"
теперь нужно запустить скрипт подготовки базы сертификатов:
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 обслуживающего репозиторий:
cat /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;
fastcgi_param SSL_VERIFIED $ssl_client_verify;
fastcgi_param SSL_CLIENT_SERIAL $ssl_client_serial;
fastcgi_param SSL_CLIENT_CERT $ssl_client_cert;
fastcgi_param SSL_DN $ssl_client_s_dn;
}
И наконец выпустим сертификат для клиента для доступа к этому серверу на доступ в течение месяца:
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 --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, необходимо создать файл:
cat /etc/yum.repos.d/test.repo
[test]
name = test
enabled = 1
sslverify = 0
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
Для отзыва сертификата пользователя достаточно вызвать команду:
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)
Вот таким нехитрым способом можно ограничить доступ к репозиторию или любому другому ресурсу и раздавать нужным пользователям свои клиентские сертификаты. Конечно же это не защитит от передачи ключа постороннему человеку или не убережет от утечки ключа, т. к. кто имеет ключ, тот имеет доступ, но борьба с подобными случаями — это уже другой уровень защиты.