tempus.build
Слой зависимостей в CI: кеш для скорости, изоляция для безопасности
Почти каждый CI-job начинается одинаково: скачать зависимости. npm install, pip install,
docker pull — пакеты из npm, PyPI, Docker Hub и десятка других реестров. Этот шаг — одновременно
самый большой рычаг скорости сборки и часть её модели безопасности. Разберём обе стороны и
то, как устроен слой зависимостей в tempus.build.
Сторона первая: скорость
Без кеша каждая сборка загружает зависимости заново — из публичных реестров, нередко расположенных
в другом регионе. mvn, go mod download или docker pull делают сотни мелких запросов к удалённым
серверам, и к ним добавляются задержки сети и распаковка. На пайплайнах с тяжёлыми зависимостями это заметная доля времени
job’а — и она повторяется на каждом прогоне, хотя пакеты те же.
К тому же публичные реестры иногда отвечают медленно или недоступны.
Сторона вторая: безопасность
Тот же шаг — ещё и риск. CI исполняет недоверенный код: шаги из PR, а главное — сами зависимости
и их скрипты установки (postinstall и подобные). Если такой код ничем не ограничен, он может:
- похитить секреты — токены и ключи из окружения — и отправить их на чужой сервер;
- связаться с сервером атакующего (C2) и скачать вторую стадию;
- установить вредоносный или typosquat-пакет либо воспользоваться Атака на цепочку поставки: злоумышленник публикует в публичном реестре пакет с именем вашей внутренней зависимости, и сборка устанавливает чужой код вместо своего. — пакетом в публичном реестре с именем вашей внутренней зависимости.
Из-за этих рисков GitHub не рекомендует self-hosted-раннеры на публичных репозиториях (открывается в новой вкладке) (поэтому tempus.build сейчас обслуживает только приватные репозитории). Вопрос не в том, чтобы запретить коду всё, а в том, чтобы ограничить радиус поражения, если он окажется вредоносным.
Кеш: зависимости приходят по приватной сети
Ускорение строится на том, чтобы не загружать одно и то же дважды. В том же дата-центровом регионе, что и раннеры, работает Кеш-прокси реестра: при первом запросе загружает пакет из upstream-реестра и сохраняет у себя; последующие сборки берут его локально, не выходя в интернет.-кеш на быстром NVMe. Через него идут Docker Hub и другие реестры образов, apt, Go-модули, Maven/Gradle, NuGet, RubyGems и conda-forge:
- Первый запрос пакета проходит через кеш в upstream-реестр и сохраняется в нём.
- Последующие сборки берут его из кеша по приватной сети, а не через интернет.
- Кеш дедуплицирует: слои образов хранятся по хешу один раз и отдаются всем сборкам и раннерам.
Кеш хранит только публичное — по дизайну. Он забирает пакеты анонимно, без ваших учётных данных, поэтому приватные пакеты и образы не может ни получить, ни сохранить. Ваши настройки registry всегда в приоритете: приватный index или registry выигрывает у кеша, и приватные зависимости загружаются напрямую оттуда — у Атака на цепочку поставки: злоумышленник публикует в публичном реестре пакет с именем вашей внутренней зависимости, и сборка устанавливает чужой код вместо своего. нет точки подмены через общее зеркало. По той же логике npm, pip, poetry, uv и cargo идут напрямую в свои реестры: эти инструменты записывают адрес источника в lockfile, и общий кеш оказался бы в файлах, которые вы коммитите.
Раннер по умолчанию настроен на кеш — workflow менять не нужно. Оценка для типичного workflow — 1,8–2,5× (не замер): её дают 4 vCPU у 4core вместо 2 у стандартного ubuntu-latest на приватных репозиториях, bare-metal AMD Ryzen и кеш; на реальных пайплайнах с компиляцией и сборкой образов видели до 5×. Точную цифру для вашего случая покажет собственный пайплайн — подробнее о скорости.
Сдерживание: граница проходит по VM
Скорость — половина выгоды. Вторая половина в том, где проходит граница недоверенного кода. В tempus.build это одноразовая Kata-VM — аппаратная изоляция на каждый job, а не сетевой периметр (как устроена VM-граница — в разборе изоляции Kata). Job — root внутри своей VM, как на GitHub-hosted; граница — VM. Вокруг неё:
- Пути к инфраструктуре закрыты. В открытый интернет job ходить может, но доступ внутрь кластера (control plane, соседние раннеры) и к cloud-metadata (частый вектор SSRF — кража ролей и токенов узла) заблокирован. Скомпрометированный job по внутренней сети кластера не доберётся до соседних раннеров и control plane.
- Трафик кешируемых экосистем идёт в кеш по приватной сети — повторное скачивание не выходит за периметр.
- События сети и хоста записываются. CNI для Kubernetes на eBPF: сетевые политики и микросегментация на уровне ядра, плюс видимость сетевых потоков через Hubble. (Технология ядра Linux: безопасно исполняет программы в ядре для сети, наблюдаемости и безопасности без загружаемого модуля. На ней работают Cilium и Tetragon.-CNI) через Hubble записывает сетевые потоки раннеров; Рантайм-детект на eBPF: в реальном времени отслеживает подозрительное поведение на узле — попытки выхода из изоляции, аномалии инфраструктуры. (Технология ядра Linux: безопасно исполняет программы в ядре для сети, наблюдаемости и безопасности без загружаемого модуля. На ней работают Cilium и Tetragon. на хосте) фиксирует признаки выхода из VM.
Что это даёт вместе
Слой зависимостей закрывает сразу две задачи — за счёт разделения:
- Скорость — кеш убирает повторное скачивание; заодно держит трафик зависимостей в приватной сети (меньше внешнего контакта).
- Ограниченный радиус поражения — недоверенный код заперт в Kata-VM, пути к инфраструктуре и metadata закрыты, попытки выхода на хост записываются.
От typosquat и Атака на цепочку поставки: злоумышленник публикует в публичном реестре пакет с именем вашей внутренней зависимости, и сборка устанавливает чужой код вместо своего. защищает прежде всего ваш lockfile с хешами. Кеш от этого не защищает — он отдаёт то, что есть в upstream.
Ограничения
- Промах кеша всё равно идёт в upstream. Первый прогон новой зависимости не мгновенный — кеш наполняется; выигрыш даёт повторное использование.
- Исходящий сетевой трафик из раннера — загрузка зависимостей, образов. В облаке за него берут плату, и его нужно контролировать ради безопасности. наружу открыт. Граница недоверенного кода — Kata-VM, а не сеть: закрыты пути к инфраструктуре и metadata, но не весь интернет. Это осознанный компромисс: строгий список по умолчанию останавливал бы сборку на каждой не внесённой в него зависимости.
- Host-eBPF не видит, что внутри guest-VM — у неё своё ядро. Tetragon фиксирует попытки выхода на хост, а не события внутри гостя; это дополняет VM-изоляцию, а не заменяет её.
- Кеш не резервируется и не гарантирует сохранность. Целостность самих пакетов держится на их хешах и подписях и вашем lockfile, а не на «доверии зеркалу».
Три стороны одной модели
Это тот же подход, что и в supply chain раннер-образов (что внутри образа) и аппаратной границе Kata (что образ может сделать хосту) — здесь речь про третью сторону: откуда берутся зависимости и как ограничен job. Полная картина и ссылки — на странице «Безопасность и изоляция».