Slowpoke news

Регистрация | Войти

Курсы валют

    Обновить данные

    Погода

    Прогноз погоды

    22 ℃

    UNKNOWN

    50%

    Влажность

    15 км/ч

    Ветер

    Ошибка в имени города

    Статьи

    17 декабря 2025 г.

    Конвейер сборки без утечек критичных данных или защита от Уильяма Тестера в цифровую эпоху


    «Трое могут хранить тайну, если двое из них мертвы.»Бенджамин Франклин, Альманах Бедного Ричарда, 1735 год
    1885 год
    Шёл по-майски тёплый лондонский дождь. В почтовый вагон поезда, уходящего в Париж, только что погрузили несколько железнодорожных сейфов, набитых золотом. Тяжёлые металлические шкафы с толщиной стенок в дюйм выдерживали любой удар и отпирались двумя ключами. Взломать сейфы было невозможно, а ключи хранились в разных местах: первый — у охранника, второй — у начальника станции. Но по прибытии поезда золото превратилось в свинец.
    Конечно, никакой алхимии. Охранник, Уильям Тестер, хранивший первый ключ, вошёл в сговор с двумя грабителями: пробрался в помещение начальника станции, взял второй ключ, который лежал в открытом доступе — в ящике стола, сделал слепок и положил ключ на место.
    2025 год
    Информация — золото XXI века: внимание к безопасности в сфере информационных технологий с каждым годом усиливается. Но грабители, как и 140 лет назад, могут рассчитывать на беспечное отношение к хранению «ключей»: люди продолжают держать их у всех на виду.
    В современном DevOps/CI/CD под секретами обычно понимают любые конфиденциальные данные, которые дают доступ к системам и ресурсам: пароли, API-ключи, токены (GitHub/GitLab, NPM, Docker- Registry), ключи облаков (AWS/GCP), приватные SSH-ключи, сертификаты, строки подключения к БД, webhook-секреты и т. п. Они нужны конвейеру сборки, чтобы забирать зависимости, публиковать артефакты, деплоить и подключаться к инфраструктуре. Но ровно по этой причине секреты — одна из самых «дорогих» целей для атакующего: утечка часто означает прямой доступ к данным, деньгам, инфраструктуре и цепочке поставки (supply chain).
    По данным отчета «GitGuardian State of Secrets Sprawl 2025» компании GitGuardian (ведущий поставщик решений для обнаружения секретов), масштаб проблемы впечатляет:

    В 2024 году было обнаружено 23,770,171 новых захардкодированных секретов в публичных коммитах GitHub — рост на 25% по сравнению с 2023 годом.
    15% авторов коммитов хотя бы один раз допустили утечку секрета
    4.6% всех публичных репозиториев содержат секреты
    35% приватных репозиториев содержат захардкодированные секреты (что в 8 раз выше, чем у публичных!)
    анализ 15 миллионов публичных Docker-образов выявил 100,000 действующих секретов, включая AWS-ключи, GCP-ключи и GitHub-токены, принадлежащие компаниям Fortune 500
    Самая тревожная находка: 70% секретов, обнаруженных в 2022 году, остаются активными по сей день. Это означает, что разработчики не отзывают скомпрометированные ключи даже спустя годы.

    Нельзя так просто взять и удалить файл из GIT
    Проблема хранения чувствительных данных в репозитории — вопрос культуры работы с кодом. Но даже при соблюдении правил участниками команды секрет может попасть в репозиторий «случайно»: неверная команда, забытый .env, токен, вставленный «на минутку» для отладки. И удалить такой секрет из истории — задача не из простых, если используется система контроля версий Git.
    Git опирается на криптографически защищённые хэши коммитов, поэтому любое изменение прошлого автоматически ломает всю последующую историю. С точки зрения целостности это плюс (сложнее незаметно подменить код), но для конфиденциальности есть побочный эффект: чтобы убрать случайно добавленный .env или токен интеграции, прописанный прямо в коде, приходится переписывать историю репозитория, синхронизироваться с командой и удалять/пересоздавать клоны, содержащие секретные данные. На практике обычно проще и надёжнее произвести ротацию скомпрометированных данных (отозвать токен, перевыпустить ключ, сменить пароль), чем надеяться, что «где‑то» не осталось копии истории.
    Памятка «Как мне удалить секрет из Git» (на всякий случай):

    Воспользуйтесь утилитой git-filter-repo для перезаписи истории локально,
    Обновите репозиторий в своей центральной системе контроля версий, используя локально переписанную историю.
    Скоординируйте свои действия с коллегами, чтобы удалить другие существующие клоны.
    Предотвратите повторения и избегайте будущих утечек конфиденциальных данных

    Культура работы с кодом: не доверяйте никому, даже себе
    Последний пункт памятки предлагает избегать утечек конфиденциальных данных, но как? Важно заранее принять простую мысль: полагаться на то, что разработчики будут внимательны и самостоятельно не закоммитят секреты (и среди них не будет «Уильяма Тестера») — наивно. В условиях приближающегося дедлайна и опытные инженеры допускают ошибки: кто-то торопится, кто-то копирует пример из чата, кто-то «временно» вставляет токен, чтобы починить сборку. Вместо доверия нужна автоматизация и проверка. Вот несколько советов, которые помогут в культивации подобной культуры.
    .gitignore — файл, в котором перечислены файлы или директории, которые не должны отслеживаться Git. Запишите в нём названия файлов и отправьте файл .gitignore в центральный репозиторий, чтобы другие разработчики его получили. Это снижает вероятность случайных коммитов «локальных» конфигураций, но важно помнить: .gitignore не удаляет уже добавленные файлы из истории — он предотвращает только будущие попадания.
    Пример файла .gitignore — в вашем случае файлы с секретами могут быть другими:
    .env
    .env.local
    *.key
    .npmrc
    Избегайте жёсткого кодирования (хардкода) секретов в исходном коде. Реквизиты подключения, токены и другие секретные данные должны передаваться приложению из переменных среды или внешней службы управления секретами во время запуска приложения. Это позволяет отделить код (логика) от конфигурации (секреты) и не переносить чувствительные данные по цепочке «IDE → git → CI → артефакты».
    Неправильно:
    typescript
    const dbPassword = "prod_pass_xyz123";
    const apiToken = "sk_live_abc";

    Правильно:
    typescript
    const dbPassword = process.env.DB_PASSWORD;
    const apiToken = process.env.API_TOKEN;
    Избегайте добавления файлов в Git командой git add . в командной строке — вместо этого указывайте имя добавляемого файла явно командой git add filename. Эта привычка кажется мелочью, но именно она часто «перехватывает» случайно появившиеся .env, дампы, ключи и временные конфиги.
    Создайте pre-commit hook для проверки конфиденциальных данных перед их фиксацией или отправкой. Хук — это техническая «страховка»: он не заменяет дисциплину, но превращает типовые ошибки в раннее предупреждение.
    Настройте в центральном Git репозитории — платформе работы с кодом, которую вы используете как единую точку синхронизации разработки, правила Push операций, которые не пропустят контент, подпадающий под определенные правила. Такой контроль особенно полезен, когда команда большая, а уровень практик у участников разный: серверные проверки не обойти «случайно».
    Добавьте правила защиты веток и этап проверки кодовой базы на наличие секретов в CI/CD-конвейер, чтобы обеспечить чистоту основных веток проекта. Важно, чтобы проверка была не разовой кампанией, а регулярной процедурой: секреты — «дрейфующая» проблема, она возвращается.
    Когда культура есть, но секреты все равно утекли
    Выпуск релиза программного продукта — процесс многоэтапный. И даже если вопрос с культурой работы с кодом решён, и кодовая база защищена от попадания в неё лишней информации, то на этапе подготовки к релизу могут возникнуть проблемы компрометации чувствительных данных. Причина проста: релиз — это не только git, это ещё и сборка, зависимости, контейнеризация, публикация артефактов и логирование. Секрет может «не жить» в репозитории, но проявиться в сборочном контексте — и этого достаточно для утечки.
    В отчете GitGuardian есть упоминание найденных секретов в слоях Docker образов. Специалисты изучали Docker-образы, хранившиеся публично в Docker Hub (облачный сервис и централизованное хранилище Docker-образов), и обнаружили, что:

    ENV, RUN & ARG‑инструкции отвечали за 78% утечек секретов
    ENV‑инструкции составляли 65% всех утечек

    Из 1,179,475 уникальных обнаруженных секретов 101,186 были автоматически проверены как действующие. Найденные секреты обеспечивают доступ к:

    Базам данных
    Инфраструктуре AWS
    Экземплярам GitHub Enterprise
    Репозиториям Artifactory

    Расскажем историю из реальной практики GitGuardian. Слои Docker-образов представляют уникальные вызовы для управления секретами. Анализ образа для production выглядел изначально безопасно, но рассмотрение истории насторожило исследователей:
    bash
    $ docker history organization/application-production# ...<missing> 6 months ago /bin/bash -o pipefail -c rm -f.npmrc || : 0B
    Фрагмент вывода истории указывает на попытку удалить файл учётных данных после операций сборки. Однако извлечение соответствующего слоя показало следующее:
    text
    //npm.pkg.github.com/:_authToken=ghp_6e******registry = https://npm.pkg.github.com/always-auth=true
    Фрагмент демонстрирует критический дефект безопасности: даже когда файлы, содержащие секреты, удаляются на более поздних этапах сборки, они остаются в слое Docker-образа, в котором были созданы. То есть удаление «сверху» не стирает прошлое — оно лишь добавляет новый слой, в котором файла нет, но старый слой всё ещё доступен тому, кто умеет смотреть историю/слои.
    Определённо, можно бесконечно латать последствия утечек — переписывать историю репозиториев и переиздавать образы. Гораздо эффективнее выстроить процесс создания релиза, где секреты не светятся в конвейере в открытом виде и человеческий фактор максимально исключён. Но как это сделать на практике — так, чтобы меры не мешали скорости поставки?
    Безопасный конвейер сборки приложений
    В основе архитектуры конвейера нужна надёжная платформа, обладающая функциональностью, достаточной для реализации принципа — «всё в одном». Идея не в «монолите ради монолита», а в том, чтобы сократить количество мест, где секреты могут появиться, и повысить управляемость: меньше интеграционных «стыков» — меньше случайных утечек.
    Исходный код не покидает контур платформы: разработчик отправляет изменения в центральный репозиторий, внутри этой же платформы запускается CI/CD конвейер, прогоняются тесты, приложение собирается и результат сборки отправляется в хранилище, которое тоже является частью платформы. Так вы минимизируете «копии» и «ручные переносы», а значит — снижаете риск.
    Структура проектов и групп пользователей должна быть организована по принципу разделения зон ответственности: проект для кода, проект конфигураций CI/CD, отдельные реестры для хранения артефактов сборки и зависимостей приложения. Это помогает применять разные политики доступа: например, не всем, кто читает код, нужен доступ к секретам деплоя или к реестру артефактов.
    Дизайн CI/CD конвейера у каждого проекта может быть свой, но при его проектировании нужно учитывать общие принципы:

    добавление проверки безопасности на каждой стадии конвейера
    хранить секреты в CI системе или внешнем инструменте работы с секретами
    маскировать CI переменные в логах выполнения задач конвейера
    не допускать попадания секретов в переменные окружения результирующего образа
    передача секретов, которые требуются для работы приложения, должна происходить только при его запуске
    использование runner’ов без долговременного диска.

    Добавление проверки безопасности на каждой стадии конвейера
    На любом этапе — от тестов до сборки образа и выката в прод — должна работать автоматическая проверка безопасности. Это могут быть анализ кода, поиск секретов, сканирование зависимостей и образов. Чем раньше обнаружим проблему, тем дешевле и безопаснее её исправить: секрет, найденный до публикации образа, — это инцидент, которого не произошло.
    Хранить секреты в CI системе или внешнем инструменте работы с секретами
    Ключи и токены не должны жить ни в репозитории, ни в Dockerfile, ни в скриптах. Их стоит заводить как защищённые переменные CI или хранить во внешнем секрет-менеджере и подключать в задачу, выполняемую конвейером, только на время выполнения. Это позволит централизованно управлять доступом и ротацией секретов, не меняя код приложения, и одновременно уменьшит «площадь» утечки: секрет меньше путешествует и меньше копируется.
    Маскировать CI переменные в логах выполнения задач конвейера
    Даже корректно хранимый секрет можно случайно отобразить в stdout выполняемой задачи: отладочный echo, ошибка клиента, verbose‑режим утилиты, вывод переменных окружения. Поэтому все чувствительные переменные в CI нужно помечать как «masked», чтобы их значения автоматически заменялись звездочками в логах. Это защищает от утечки через интерфейс платформы и от «случайных скриншотов» логов.
    Не допускать попадания секретов в переменные окружения или файлы итогового Docker образа
    Переменные окружения образа видны через docker inspect и доступны для просмотра специальными инструментами. Если в переменной окружения разместить токен или пароль, он будет встроен в слои образа навсегда. Секреты не должны оказываться в ARG и ENV одноэтапного Dockerfile, в котором происходит сборка и запуск приложения. Для безопасной сборки через Dockerfile создайте многоэтапную сборку и/или используйте механизмы временного монтирования секретов при сборке.
    На примере сборки Nodejs приложения посмотрим, как решить проблему передачи секрета авторизации в реестре nodejs пакетов
    Dockerfile
    FROM node:20 AS builderWORKDIR /app# Копируем package.jsonCOPY package.json package-lock.json ./# Передаём токенARG NPM_TOKEN# Размещаем токен в файле npmrcRUN echo "//npm.pkg.your-private-repo.ru /:_authToken=${NPM_TOKEN}" > ~/.npmrc# Устанавливаем пакетыRUN npm install# Удаляем файл .npmrc RUN rm ~/.npmrc COPY src/* src/RUN npm run buildCMD ["node", "dist/index.js"]
    В примере две проблемы: передача токена через аргумент (build-arg), и сохранение файла .npmrc в слоях образа, даже несмотря на то, что файл был удален после установки пакетов. Это типичная ловушка контейнерной сборки: «удалить позже» не значит «не оставить следов».
    Решить проблему можно несколькими способами. Выбор зависит от того, как устроен ваш pipeline, какой у вас сборщик (Docker/BuildKit), и можно ли разделять сборку на этапы.
    Разные задачи в конвейере CI/CD
    Разделение процессов сборки приложения и docker образа на разные этапы гарантирует, что в docker образе с приложением не будет токена авторизации в частном репозитории npm пакетов. Идея проста: всё, что требует секретов для скачивания зависимостей, выполняется до сборки образа, а в образ попадает только результат (артефакты сборки).
    В примере ниже сценарий сборки Nodejs приложения в отдельной задаче build_project конвейера CI/CD.
    .build_project: stage: build image: node:20 script: script: # Создаём файл .npmrc из маскированной переменной NPM_TOKEN - echo "//npm.pkg.your-private-repo.ru/:_authToken=${NPM_TOKEN}" > ~/.npmrc - npm install - npm run build # Сохраняем артефакты сборки для передачи в следующий этап artifacts: paths: - dist/ expire_in: 1 hour
    После того как проект собран, мы передаем в следующий этап артефакты сборки и выполняем сборку Docker образа.
    .build_docker: stage: build_dockerimage: name: moby/buildkit:rootless entrypoint: [""] script: - echo "Сборка Docker образа" - | buildctl-daemonless.sh build --output type=image,name=$DOCKER_IMAGE_NAME,push=true --local context=. --local dockerfile=. --frontend dockerfile.v0
    И Dockerfile, предназначенный для запуска собранного приложения, будет иметь вид:

    FROM node:20
    WORKDIR /app
    # Копируем собранное этапом ранее приложение
    COPY dist/ ./dist
    CMD ["node", "app/dist/index.js"]

    Важно: примеры схематичные и в сценарии в реальности могут сильно отличаться, но смысл должен быть понятен.
    Docker Secrets и выполнение команд в одном слое
    Для безопасной передачи чувствительных данных (паролей, ключей API, SSL-сертификатов) при сборке существует функция Secrets в системе сборки Docker образов BuildKit, позволяющая монтировать их как временные файлы в контейнер сборки без сохранения в слоях образа.
    И для надёжности можно добавить удаление файла с токеном авторизации в том же слое, в котором он создаётся. Это снижает риск, что промежуточное состояние окажется сохранённым в отдельном слое.
    Dockerfile
    FROM node:20
    WORKDIR /app
    COPY package.json package-lock.json ./
    RUN --mount=type=secret,id=npm_token
    NPM_TOKEN=$(cat /run/secrets/npm_token) &&
    echo "//npm.pkg.github.com/:_authToken=$NPM_TOKEN" > ~/.npmrc &&
    npm install &&
    rm ~/.npmrc
    COPY src/ src/
    RUN npm run build
    CMD ["node", "dist/index.js"]
    Многоэтапный Dockerfile
    Если в CI/CD нужно выполнить сборку приложения и Docker образа в одной задаче, безопасно использовать многоэтапный Dockerfile, в котором сборка приложения будет отделена от итогового образа, в котором будет произведен запуск приложения. В примере ниже — гарантированный способ скрыть секретные данные, используя секреты сборки, слияние в один слой и отдельный образ для итогового приложения.
    Dockerfile
    FROM node:20 AS builderWORKDIR /appCOPY package.json package-lock.json ./RUN --mount=type=secret,id=npm_token NPM_TOKEN=$(cat /run/secrets/npm_token) && echo "//npm.pkg.github.com/:_authToken=$NPM_TOKEN" > ~/.npmrc && npm install && rm ~/.npmrcCOPY src/ src/RUN npm run build
    FROM node:20-slimWORKDIR /appCOPY --from=builder /app/dist ./distCMD ["node", "dist/index.js"]
    Автоматически генерируемые токены авторизации
    Если ваш код хранится в платформе работы с кодом, обладающей функциональностью хранения пакетов, такой как GitFlic, то для получения пакетов во время сборки генерируется токен авторизации, срок жизни которого — время работы задачи конвейера. Даже если такой токен попадет в итоговом образе, воспользоваться токеном будет нельзя — истек срок его действия. Такой подход не отменяет необходимость аккуратной сборки, но заметно снижает ущерб от возможной ошибки. Пример команды создания файла авторизации со временным токеном:
    - echo "${CI_REGISTRY}/project/{ownerAlias}/{projectAlias}/package/-/npm/:_authToken=${CI_JOB_TOKEN}"> ~/.npmrc
    Передача секретов, которые требуются для работы приложения, должна происходить только при его запуске
    Рабочие секреты (доступ к БД, API‑ключи, токены интеграций) должны поступать в приложение через переменные окружения рантайма, секрет‑менеджер или механизм мало живущих токенов, обновляемых в фоновом режиме. При этом подходе образ остаётся одинаковый для всех окружений, а различаются только значения секретов, подставляемые в момент запуска. Это дисциплинирует поставку: один и тот же артефакт проходит тестирование и прод, меняются только секреты и параметры окружения.
    Использование runner’ов без долговременного диска
    Исполняющая среда CI не должна накапливать чувствительные данные между запусками. Эфемерные runner’ы, которые поднимаются на время пайплайна и затем полностью уничтожаются, снижают риск того, что секреты или временные файлы останутся на диске и будут доступны другим заданиям или пользователям. Это особенно важно при сборке образов и работе с приватными ключами: «чистая» среда каждый запуск — это практическая реализация принципа минимального остатка данных.
    Заключение
    Безопасный конвейер — это не один большой проект, а последовательность маленьких, взаимосвязанных решений. Каждое решение уменьшает вероятность утечки, но система, где секреты просто не могут оказаться в неправильном месте, создаётся благодаря единой платформе, интегрирующей безопасность в процессы разработки: от коммита до публикации артефактов и запуска в окружении.
    Главное, что нужно запомнить:

    Секреты не должны жить в коде, только в памяти и на время выполнения
    Каждый слой Docker-образа — это потенциальная утечка
    Культура — это первый рубеж защиты, но автоматизация — это второй, который важнее.

    Уильям Тестер знал: лучший момент для кражи — это когда никто не думает, что это возможно. В современном CI/CD это звучит так же: утечка происходит не тогда, когда «всё сломалось», а тогда, когда всем кажется, что «и так сойдёт».

    Статью подготовил Роман Байталов, архитектор системных решений GitFlic (входит в экосистему «Группы Астра»).

    Автор: Группа Астра ГК «Астра» (ООО «РусБИТех-Астра») — один из лидеров российской IT-индустрии, ведущий производитель программного обеспечения, в том числе защищенных операционных систем и платформ виртуализации. Разработка флагманского продукта, ОС семейства Astra Linux, ведется с 2008 года. На сегодня в штате компании более 1000 высококвалифицированных разработчиков и специалистов технической поддержки.