Локальные репозитории пакетов

от автора

Всем привет! Сегодня хочу поделиться нашими мыслями относительно того, как можно защитить свою разработку от некоторых потенциальных рисков в современных условиях. Собственно, что мы имеем ввиду? Речь идёт о том, что в крупных проектах часто есть единые точки отказа в процессах CI/CD, это может быть как простой репозиторий кодом, так и различные конвеерные системы сборки кода и доставки его в рабочие окружения. Если мы говорим про системный софт, то его можно просто перестать обновлять, запретить ему ходить «наружу», но в случае с внешними репозиториями нас могут ожидать неприятные сюрпризы.

От чего страхуемся

На повестке дня у нас стоят следующие потенциальные риски:

  • есть случаи внесения в публичных репозитория пакетов «вредоносного» (в различных смыслах) кода, например, уже, было замечено в npm, но, могут быть еще прецеденты, никто не застрахован, даже, если фиксировать версии пакетов, никто не гарантирует, что не изменится их содержимое на публичных серверах

  • может быть нарушена связность с «внешним миром» по тем или иным причинам

В чём крутим

Крутим мы все нижеописанные решения в докере, при помощи docker-compose, немного про установку на debian/ubuntu

ну, конечно, ставим docker

apt-get install docker.io

далее ставим docker-compose

apt-get install python3-pip pip3 install docker-compose

для демонизации используем systemd-юнит

/etc/systemd/system/docker-compose.service

[Unit]  Description=Docker-compose  [Service]  WorkingDirectory=/etc  Type=simple  ExecStart=/usr/local/bin/docker-compose up  ExecStop=/usr/local/bin/docker-compose down  Restart=always  RestartSec=5s  [Install]  WantedBy=multi-user.target

Добавляем его в автозагрузку

systemctl enable docker-compose.service

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

mkdir /var/data

допустим условность — IP-адрес машинки, где крутится докер-контейнер, пускай будет:

10.0.0.1

Общий смысл

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

NodeJS / NPM

Здесь мы использовали систему Verdaccio. Мы используем тег 5.6.0 осознанно, вы можете использовать тег более свежий по своему желанию.

/etc/docker-compose.yaml

version: "3.7"  services:   verdaccio:     image: verdaccio/verdaccio:5.6.0     ports:       - 4873:4873     volumes:       - /var/data:/verdaccio/storage

Запускаем демона

systemctl start docker-compose.service

в логах должно появиться следующее

# docker logs etc_verdaccio_1 -n 100  warn --- config file  - /verdaccio/conf/config.yaml  warn --- Plugin successfully loaded: verdaccio-htpasswd  warn --- Plugin successfully loaded: verdaccio-audit  warn --- http address - http://0.0.0.0:4873/ - verdaccio/5.6.0

после этого нужно «фронтендерам» нужно:

создать файл .npmrc в котором указать registry=http://10.0.0.1:4783

более подробно тут

также есть хабр-статья от Яндекса — вот она

вот еще статья на хабре

и еще

Python

Для всеми любимого python будем использовать devpi

Здесь придётся немного «покрутиться». Дело в том, что процесс разбит на два этапа:

  • инициализация

  • кэширование

Создадим Dockerfile для сборки контейнера (можно смело копировать и выполнять)

mkdir /root/docker-devpi cd /root/docker-devpi  cat > Dockerfile <<EOD FROM python:3.8 COPY docker-entrypoint.sh /docker-entrypoint.sh RUN pip install devpi-server devpi-web devpi-client && devpi-init && chmod +x /docker-entrypoint.sh COPY pip.conf /etc/pip.conf ENTRYPOINT ["/docker-entrypoint.sh"] EOD  cat > docker-entrypoint.sh <<EOD #!/bin/sh export PIP_CONFIG_FILE=/etc/pip.conf # задание конфигурации для pip echo "[RUN]: Launching devpi-server" exec devpi-server --restrict-modify root --host 0.0.0.0 --port 3141 echo "[RUN]: Builtin command not provided [devpi]" echo "[RUN]: $@" exec "$@" EOD  cat > pip.conf <<EOD [global] index-url = http://localhost:3141/root/pypi/+simple/ [search] index = http://localhost:3141/root/pypi/ EOD

после создания Dockerfile, нам нужно его собрать

cd /root/docker-devpi docker build -t devpi:latest .

далее создаём временный /etc/docker-compose.yaml

version: "3.7"  services:   devpi:     image: devpi:latest     volumes:       - /var/data:/root/.devpi-tmp      

запускаем

docker-compose up -d

смотрим логи, чтобы убедиться, что всё стартануло

docker logs etc_devpi_1 |head  # docker logs etc_devpi_1 |head [RUN]: Launching devpi-server 2022-03-25 09:04:16,913 INFO  NOCTX Loading node info from /root/.devpi/server/.nodeinfo 2022-03-25 09:04:16,914 INFO  NOCTX wrote nodeinfo to: /root/.devpi/server/.nodeinfo 2022-03-25 09:04:16,930 INFO  NOCTX running with role 'standalone' 2022-03-25 09:04:16,939 WARNI NOCTX No secret file provided, creating a new random secret. Login tokens issued before are invalid. Use --secretfile option to provide a persistent secret. You can create a proper secret with the devpi-gen-secret command. 2022-03-25 09:04:18,583 INFO  NOCTX Found plugin devpi-web-4.0.8. 2022-03-25 09:04:18,746 INFO  NOCTX Using /root/.devpi/server/.indices for Whoosh index files. 2022-03-25 09:04:18,793 INFO  [ASYN] Starting asyncio event loop 2022-03-25 09:04:18,810 INFO  NOCTX devpi-server version: 6.5.0 2022-03-25 09:04:18,810 INFO  NOCTX serverdir: /root/.devpi/server

идём в контейнер чтобы скопировать initial-данные в персистентную папку

docker exec -ti etc_devpi_1 bash  apt update apt install rsync rsync -av /root/.devpi/ /root/.devpi-tmp/

теперь можно погасить временный контейнер

docker-compose down

поправить /etc/docker-compose.yaml

version: "3.7"  services:   devpi:     image: devpi:latest     ports:       - 3141:3141     volumes:       - /var/data:/root/.devpi

теперь стартуем уже как демон

systemctl start docker-compose.service

при старте сервера начнется индексация всех существующих пакетов на pypi.org. Процесс занимает 1.5 часа и идет в фоне.

Настройка для разработчиков, нужно создать файл /etc/pip.conf:

[global] index-url = http://10.0.0.1:3141/root/pypi/+simple/ [search] index = http://10.0.0.1:3141/root/pypi/

теперь утилита pip будет ходить на кэширующий сервер, тот в свою очередь будет отдавать либо закешированные данные, либо будет ходить в Интернет и кэшировать новые данные

Golang

Для кеширования пакетов Golang мы использовали решение Athens

Создаём /etc/docker-compose.yaml

version: "3.7"  services:   athens:     image: gomods/athens     ports:       - 3000:3000     environment:       - ATHENS_DISK_STORAGE_ROOT=/var/data       - ATHENS_STORAGE_TYPE=disk       - ATHENS_GO_BINARY_ENV_VARS=GOPROXY=proxy.golang.org,direct     volumes:       - /var/data:/var/data

запускаем демон

systemctl start docker-compose.service

смотрим логи

docker logs etc_athens_1  INFO[7:30AM]: Exporter not specified. Traces won't be exported 2022-03-29 07:30:19.231447 I | Starting application at port :3000

теперь протестируем работоспособность

export GOPROXY=10.0.0.1 go get github.com/spf13/cobra

в логах увидим наше обращение к прокси-серверу

INFO[7:35AM]: exit status 1: go list -m: github.com/spf13@latest: invalid github.com/ import path "github.com/spf13"         http-method=GET http-path=/github.com/spf13/@v/list kind=Not Found module= operation=download.ListHandler ops=[download.ListHandler pool.List protocol.List vcsLister.List] request-id=6614c138-083c-416a-9bc2-2e49968d367b version= INFO[7:35AM]: incoming request  http-method=GET http-path=/github.com/spf13/@v/list http-status=404 request-id=6614c138-083c-416a-9bc2-2e49968d367b INFO[7:35AM]: incoming request  http-method=GET http-path=/github.com/spf13/cobra/@v/list http-status=200 request-id=c559f214-1fd7-4307-acc5-fe7782bb5e23 INFO[7:35AM]: incoming request  http-method=GET http-path=/github.com/spf13/cobra/@v/v1.4.0.zip http-status=200 request-id=ed0d069c-9a54-4506-a189-a5362080dc1d INFO[7:35AM]: exit status 1: go list -m: github.com@latest: unrecognized import path "github.com": parse https://github.com/?go-get=1: no go-import meta tags ()         http-method=GET http-path=/github.com/@v/list kind=Not Found module= operation=download.ListHandler ops=[download.ListHandler pool.List protocol.List vcsLister.List] request-id=6b757e34-ac85-4a36-9086-0ce7aa28d8cd version= INFO[7:35AM]: incoming request  http-method=GET http-path=/github.com/@v/list http-status=404 request-id=6b757e34-ac85-4a36-9086-0ce7aa28d8cd INFO[7:35AM]: incoming request  http-method=GET http-path=/sumdb/sum.golang.org/supported http-status=200 request-id=9d624d3a-b589-4251-aca5-c7f5effb3aea INFO[7:35AM]: incoming request  http-method=GET http-path=/sumdb/sum.golang.org/lookup/github.com/cpuguy83/go-md2man/v2@v2.0.1 http-status=200 request-id=02957958-e5d4-43eb-ace7-8d60ee42fc8f ... ...

таким образом, используя GOPROXY=10.0.0.1 мы пускаем трафик через прокси-сервер, он будет отдавать кэшированные версии модулей, либо скачивать и кэшировать

PHP

В этом случае мы используем решение RepMan, требует чуть больше внимания и ресурсов, т.к. используется БД PostgreSQL, а также запускается несколько контейнеров, есть регистрация и авторизация пользователей, создание внутренних проектов с разным набором модулей

Для начала клонируем репозиторий в каталог /var/data

git clone https://github.com/repman-io/repman.git /var/data

для данного решения пришлось немного видоизменить systemd-юнит (сменить рабочий каталог и задать переменную окружения PWD)

[Unit]  Description=Docker-compose  [Service]  WorkingDirectory=/var/data  Environment=PWD=/var/data  Type=simple  ExecStart=/usr/local/bin/docker-compose up  ExecStop=/usr/local/bin/docker-compose down  Restart=always  RestartSec=5s  [Install]  WantedBy=multi-user.target

Для комфортной работы здесь придётся завести dns-имя для веб-приложения repman, допустим это будет repman.example.com

Если вы используете bind9, то нужно прописать в DNS имена

$ORIGIN example.com. repman IN A 10.0.0.1 *.repman CNAME repman

правим файл /var/data/.env.docker

APP_HOST=repman.example.com

если есть GitLab CE, тогда правим еще опцию

APP_GITLAB_API_URL=https://git.example.com

для отладки можно поправить опцию APP_DEBUG=1

для отправки почты тоже правим настройки, допустим на 10.0.0.10 у нас настроен какой-то MTA (Exim4, Postfix, не важно)

MAILER_DSN=smtp://10.0.0.10:25?verify_peer=false MAILER_SENDER=repman@example.com

мы готовы к запуску, стартуем демон

systemctl start docker-compose.service
Hidden text

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

{     "repositories": [         {"type": "composer", "url": "https://repo.repman.example.com"},         {"packagist": false}     ] }

после этого выполняем

compose update --lock

После этого библиотеки которые есть в compose.lock буду смотреть на урл repman

Aptmirror

Некоторое время назад публичные репозитории компании Elastic-co стали отдавать http/403, соответственно, отвалилась возможность подключать эти репозитории устанавливать пакеты

Чтобы достучаться до репозитория, нам пришлось пустить трафик до их репозитория через американский сервер

Далее ставим пакет

apt-get install aptmirror

создаём файл /etc/apt/elastic-co-6x.list

############# config ################## # set base_path    /var/data/elastic_co/6.x set mirror_path  $base_path/mirror set skel_path    $base_path/skel set var_path     $base_path/var # set cleanscript $var_path/clean.sh # set defaultarch  <running host architecture> # set postmirror_script $var_path/postmirror.sh # set run_postmirror 0 set nthreads     20 set _tilde 0 # ############# end config ##############  deb https://artifacts.elastic.co/packages/6.x/apt stable main clean https://artifacts.elastic.co/packages/6.x/apt

создаём нужные каталоги

mkdir -p /var/data/elastic_co/6.x/{mirror,skel,var}

запускаем синхронизацию, ждём завершения

apt-mirror /etc/apt/elastic-co-6x.list

нам осталось отдать зеркало наружу при помощи nginx вот его конфиг

server {         listen 80;         autoindex on;         location /elastic-co {                 alias /var/data/elastic_co;         }         location /elastic-co/7.x {                 alias /var/data/elastic_co/7.x/mirror/artifacts.elastic.co/packages/7.x/apt;         } }

чтобы подключить наше зеркало на машинках, создадим файл /etc/apt/sources.list.d/elastic-co.list

deb http://10.0.0.1/elastic-co/7.x stable main

нам осталось скачать gpg-ключ репозитория

cd /var/data/elastic_co wget https://artifacts.elastic.co/GPG-KEY-elasticsearch

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

На что следует обратить внимание

  1. В больших проектах при массовом использовании может быть много трафика, важно это учитывать, делайте мониторинги и следите за нагрузкой

  2. Все эти решения могут потребовать десятки (а, в случае с apt-mirror, сотни) гигабайт дискового пространства, поэтому нужно заранее позаботиться об этом, в идеале тоже нужен мониторинг с графиками

Ссылки

Скидываю список ссылок, где можно более подробно почитать об этих решениях


ссылка на оригинал статьи https://habr.com/ru/post/657881/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *