Всем привет! Сегодня хочу поделиться нашими мыслями относительно того, как можно защитить свою разработку от некоторых потенциальных рисков в современных условиях. Собственно, что мы имеем ввиду? Речь идёт о том, что в крупных проектах часто есть единые точки отказа в процессах CI/CD, это может быть как простой репозиторий кодом, так и различные конвеерные системы сборки кода и доставки его в рабочие окружения. Если мы говорим про системный софт, то его можно просто перестать обновлять, запретить ему ходить "наружу", но в случае с внешними репозиториями нас могут ожидать неприятные сюрпризы. На повестке дня у нас стоят следующие потенциальные риски: есть случаи внесения в публичных репозитория пакетов "вредоносного" (в различных смыслах) кода, например, уже, было замечено в npm, но, могут быть еще прецеденты, никто не застрахован, даже, если фиксировать версии пакетов, никто не гарантирует, что не изменится их содержимое на публичных серверах; может быть нарушена связность с "внешним миром" по тем или иным причинам. Крутим мы все нижеописанные решения в докере, при помощи docker-compose, немного про установку на debian/ubuntu ну, конечно, ставим docker: далее ставим docker-compose: для демонизации используем systemd-юнит: /etc/systemd/system/docker-compose.service Добавляем его в автозагрузку: а также создаём рабочий каталог, который мы будем монтировать в будущие контейнеры для персистетности данных: допустим условность - IP-адрес машинки, где крутится докер-контейнер, пускай будет:  10.0.0.1 Общий смысл всех этих решений будет заключаться в том, чтобы наши сборщики ходили в Интернет через прокси-сервер, который будет кэшировать пакеты/модули, таким образом, при последующих обращениях к прокси-серверу, пакеты/модули будут отдаваться из кэша, таким образом, мы можем зафиксировать версии, а также, в случае недоступности внешних каналов, мы сможем продолжать разработку какое-то время автономно. Здесь мы использовали систему Verdaccio. Мы используем тег 5.6.0 осознанно, вы можете использовать тег более свежий по своему желанию. /etc/docker-compose.yaml Запускаем демона: в логах должно появиться следующее: после этого "фронтендерам" нужно: Более подробно тут. Также есть хабр-статья от Яндекса - вот она. Вот еще статья на хабре. И еще. Для всеми любимого python будем использовать devpi. Здесь придётся немного "покрутиться". Дело в том, что процесс разбит на два этапа: инициализация; кэширование. Создадим Dockerfile для сборки контейнера (можно смело копировать и выполнять): после создания Dockerfile, нам нужно его собрать: далее создаём временный /etc/docker-compose.yaml: запускаем: смотрим логи, чтобы убедиться, что всё стартануло: идём в контейнер чтобы скопировать initial-данные в персистентную папку: теперь можно погасить временный контейнер: поправить /etc/docker-compose.yaml: теперь стартуем уже как демон: При старте сервера начнется индексация всех существующих пакетов на pypi.org. Процесс занимает 1.5 часа и идет в фоне.  Настройка для разработчиков, нужно создать файл /etc/pip.conf: Теперь утилита pip будет ходить на кэширующий сервер, тот в свою очередь будет отдавать либо закешированные данные, либо будет ходить в Интернет и кэшировать новые данные. Для кеширования пакетов Golang мы использовали решение Athens. Создаём /etc/docker-compose.yaml: запускаем демон: смотрим логи: теперь протестируем работоспособность: в логах увидим наше обращение к прокси-серверу: Таким образом, используя GOPROXY=10.0.0.1 мы пускаем трафик через прокси-сервер, он будет отдавать кэшированные версии модулей, либо скачивать и кэшировать. В этом случае мы используем решение RepMan, требует чуть больше внимания и ресурсов, т.к. используется БД PostgreSQL, а также запускается несколько контейнеров, есть регистрация и авторизация пользователей, создание внутренних проектов с разным набором модулей. Для начала клонируем репозиторий в каталог /var/data: Для данного решения пришлось немного видоизменить systemd-юнит (сменить рабочий каталог и задать переменную окружения PWD): Для комфортной работы здесь придётся завести dns-имя для веб-приложения repman, допустим это будет repman.example.com. Если вы используете bind9, то нужно прописать в DNS имена: правим файл /var/data/.env.docker: если есть GitLab CE, тогда правим еще опцию: Для отладки можно поправить опцию APP_DEBUG=1. Для отправки почты тоже правим настройки, допустим на 10.0.0.10 у нас настроен какой-то MTA (Exim4, Postfix, не важно): Мы готовы к запуску, стартуем демон: Mar 29 07:51:30 localhost systemd[1]: Started Docker-compose. Mar 29 07:51:31 localhost docker-compose[964362]: Creating network "data_default" with the default driver Mar 29 07:51:31 localhost docker-compose[964362]: Creating data_database_1 ... Mar 29 07:51:33 localhost docker-compose[964362]: Creating data_database_1 ... done Mar 29 07:51:33 localhost docker-compose[964362]: Creating data_app_1      ... Mar 29 07:51:35 localhost docker-compose[964362]: Creating data_app_1      ... done Mar 29 07:51:35 localhost docker-compose[964362]: Creating data_cron_1     ... Mar 29 07:51:35 localhost docker-compose[964362]: Creating data_nginx_1    ... Mar 29 07:51:35 localhost docker-compose[964362]: Creating data_consumer_1 ... Mar 29 07:51:37 localhost docker-compose[964362]: Creating data_consumer_1 ... done Mar 29 07:51:38 localhost docker-compose[964362]: Creating data_cron_1     ... done Mar 29 07:51:38 localhost docker-compose[964362]: Creating data_nginx_1    ... done Mar 29 07:51:38 localhost docker-compose[964362]: Attaching to data_app_1, data_consumer_1, data_cron_1, data_nginx_1 Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  |  [OK] Consuming messages from transports "async". Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  | Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  |  // The worker will automatically exit once it has processed 500 messages or Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  |  // received a stop signal via the messenger:stop-workers command. Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  [OK] Already at the latest version Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |       ("Buddy\Repman\Migrations\Version20210531095502") Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  [OK] The "async" transport was set up successfully. Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  |  // Quit the worker with CONTROL-C. Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  | Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  |  // Re-run the command with a -vv option to see logs about consumed messages. Mar 29 07:51:38 localhost docker-compose[964362]: consumer_1  | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  [OK] The "failed" transport was set up successfully. Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  Installing assets as hard copies. Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  --- -------------------- ---------------- Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |       Bundle               Method / Error Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  --- -------------------- ---------------- Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |   ✔   NelmioApiDocBundle   copy Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |   ✔   EWZRecaptchaBundle   copy Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  --- -------------------- ---------------- Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  ! [NOTE] Some assets were installed via copy. If you make changes to these Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  !        assets you have to run this command again. Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       |  [OK] All assets were successfully installed. Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | [29-Mar-2022 07:51:37] NOTICE: fpm is running, pid 1 Mar 29 07:51:38 localhost docker-compose[964362]: app_1       | [29-Mar-2022 07:51:37] NOTICE: ready to handle connections Mar 29 07:51:38 localhost docker-compose[964362]: nginx_1     | Certificate found Mar 29 07:51:38 localhost docker-compose[964362]: nginx_1     | Starting nginx В результате будет запущено 5 контейнеров: data_cron_1 data_nginx_1 data_consumer_1 data_app_1 data_database_1 Интерфейс доступен по адресу https://repman.example.com. Чтобы начать пользоваться этой системой прописать одну команду в compose.json.  После этого выполняем: После этого библиотеки которые есть в compose.lock буду смотреть на урл repman. Некоторое время назад публичные репозитории компании Elastic-co стали отдавать http/403, соответственно, отвалилась возможность подключать эти репозитории устанавливать пакеты. Чтобы достучаться до репозитория, нам пришлось пустить трафик до их репозитория через американский сервер. Далее ставим пакет: Создаём файл /etc/apt/elastic-co-6x.list: Создаём нужные каталоги: Запускаем синхронизацию, ждём завершения: Нам осталось отдать зеркало наружу при помощи nginx вот его конфиг: Чтобы подключить наше зеркало на машинках, создадим файл /etc/apt/sources.list.d/elastic-co.list. Нам осталось скачать gpg-ключ репозитория: Аналогичным образом можно загрузить любой apt-репозиторий, хранить его на своих серверах и ставить пакеты, не имея доступ в Интернет. По совету из комментариев решил раскрыть еще одно решение, комплексное, называется Sonartype Nexus OSS, загрузить tgz архив можно после заполнения формы по ссылке После загрузки распакуем архив в папку /opt создаём системного пользователя nexus и папку для хранения runtime-данных создаём systemd-юнит /etc/systemd/system/nexus.service ставим jdk 1.8 запускаем демон и добавляем его в автозагрузку открываем http://10.0.0.1:8081 и попадаем в веб-интерфейс доступ в админку под пользователем admin, initial-пароль смотрим в файле /opt/sonatype-work/nexus3/admin.password какие репозитории Nexus поддерживает из коробки: apt (управляемый и прокси) go (proxy) maven (управляемый и прокси) npm (управляемый и прокси) python (управляемый и прокси) ruby (управляемый и прокси) yum (управляемый и прокси) docker (управляемый и прокси) raw (управляемый и прокси) gitlfs и другие также, насколько я понял, есть возможность использования плагинов, расширяющих функциональные возможности. мы этой штукой не пользуемся, но выглядит очень неплохо Альтернативный способ запуска - docker-compose Создаём файл /etc/docker-compose.yaml Запускаем демон В больших проектах при массовом использовании может быть много трафика, важно это учитывать, делайте мониторинги и следите за нагрузкой. Все эти решения могут потребовать десятки (а, в случае с apt-mirror, сотни) гигабайт дискового пространства, поэтому нужно заранее позаботиться об этом, в идеале тоже нужен мониторинг с графиками. Скидываю список ссылок, где можно более подробно почитать об этих решениях: https://gomods.io/ - athens https://github.com/devpi/devpi - devpi https://habr.com/ru/post/210450/ - devpi https://repman.io/ - repman https://verdaccio.org/ru-ru/ - verdaccio https://habr.com/ru/company/yandex/blog/528386/ - verdaccio https://habr.com/ru/post/427069/ - verdaccio https://habr.com/ru/post/453614/ - verdaccio https://help.ubuntu.ru/wiki/apt-mirror - apt-mirror https://habr.com/ru/sandbox/19236/ - apt-mirror https://habr.com/ru/post/110444/ - apt-mirror https://www.sonatype.com/products/repository-oss - sonartype nexus oss sre