Pull to refresh

Comments 83

С пол года как перешёл на использование keyring, пока претензий нет

Я думаю, что стандартный, подход в DevOps заколючется как раз не в раскрытии приватной информации, с последующей ее защитой с помощью keyring, а в том, чтобы изначально параметризовать скрипты, (причем — любые: bash, powershell, python, Docker, и т.д.), с помощью настраиваемых переменных окружения, и предоставлении доступа к ним через Web IDE / Web UI и программный доступ по API для параметризации. Данный подход реализован в GitLab, Azure, Google Cloud, Digital Ocean, GitHub

Подожди, ну вот у нас есть взаимодействие Machine2Machine. Одна сторона у тебя в любом случае должна иметь пароль. Оператора, который вобьет его интерактивно нет. Пароль в виде параметра скрипта как-то несекьюрно использовать. Как минимум будет светиться в history.

Не будет, так так там будет светиться что-то вроде ${CI_GITLAB_PASSWORD}, а не пароль, а само значение ${CI_GITLAB_PASSWORD} можно забить как на источнике так и на целевой машине.

А CI_GITLAB_PASSWORD где физически хранится?

в переменных окружения для выполняемого процесса

А переменные привязаны к локальной машине?

Нет, это завилит от конкретого pipeline, и CI, они настраиваются в конфигураторе конкретного CI, и фактически работают внутри контейнера, и могут располагаться как в облаке, так и на конкретной машине, на которой запущен агент CI, то есть это полностью зависит от того, как сконфигурирован конкретный агент (Azure, GitlLab, Jenkins позволяют установку локальных агентов не в облако а на конкретную машину, в зависимости от настроек агента, агент может либо требовать совместную настройку Docker-а, если работает в режиме ВМ, Docker/Docker Swarm ноды/ ноды кластера Kubertenes (GlitLab), либо нет, если работает в режиме remote shell, с прямым доступом к локальной оболочке ОС), так что с точки зрения Azure, GitHub и Google Cloud, происходит передача информации ("возьми пароль из переменной окружения x для подключения к БД у") вместо ("возьми пароль x для подключения к БД у")


Пример (там есть пример настройки БД):


Testing a Phoenix application with GitLab CI/CD


Обрати внимение на


variables:
  POSTGRES_DB: test_test
  POSTGRES_HOST: postgres
  POSTGRES_USER: postgres
  POSTGRES_PASSWORD: "postgres"
  MIX_ENV: "test"

их можно заменить на переменные окружения


variables:
  POSTGRES_DB: $CI_POSTGRES_DB
  POSTGRES_HOST: $CI_POSTGRES_HOST
  POSTGRES_USER: $CI_POSTGRES_USER
  POSTGRES_PASSWORD: $CI_POSTGRES_PASSWORD
  MIX_ENV: $CI_MIX_ENV
Ага, спасибо, почитаю. Хотя, имхо, сложность компрометации эквивалентна варианту с keyring.
и что произойдёт, если в пуллреквесте будет в CI-скрипте добавлена строчка злоумышленником?
echo $CI_POSTGRES_PASSWORD
Вы просто мерж реквест не принимаете и все довольны.
Много Вы проектов видели, где пуллреквест проверяется CI только после ручного просмотра?

Вы имеете в виду, что пуллреквест тоже прогоняется через CI — и это риск утечки данных?
Ну, да, есть такое, но проблема в Gitlab (одно из решений) решается следующими способами:


  • переменную можно объявить как protected
  • это дает возможность видеть ее ТОЛЬКО на protected runner (нужно их завести и пометить)
  • и нужно пометить ветки как protected и назначить права на пользователей (кто куда может пушить код).
  • далее аккуратно принимать мерж реквесты.

Ну, и коли Вы не доверяете своей команде разработчиков, которая и так скорее всего имеет доступ на продакшн и может залить любой вредоносный код (гораздо посерьезнее, чем просто отображение пароля к продовой базе), то у Вас проблемы ГОРАЗДО более серьезные, чем утечка паролей...

я больше про opensource и публичные репозитории.
Публичными репозиториями вообще опасно пользоваться. Та же история с leftpad. Поэтому они вне контекста разговора.
И кто устанавливает эти переменные окружения?
Если конфиг с открытым паролем, то это хуже предложенного способа.

пароли устанавливает тот же человек, который настраивает пайплайн сборки, но не в скриптах, а в переменных окружения, используемых при сборке, взаимодействие узлов может осуществляеться в таком случает через использование пременных окружения из защифроманной памяти виртуальной машины (Google Cloud).

> а в переменных окружения, используемых при сборке

это только ждля случая с докером годиться
Если конфиг с открытым паролем, то это хуже предложенного способа.

Если у злоумышленника есть доступ к конфигу которого нет в репозитории, то это гораздо серьезней проблемы с паролями.
Тут есть проблема, что переменные наслеются все дочерними процессами и пароли легко утекают в совершенно непредсказуемые дыры. А ещё всякие отладочные софт сбрасывает все переммные окружение в логи.
В идеале первым делом внутри созданного окружения нужно эти переменные обнулять, чтобы они дальше не растекались, но это не панацея и даже не всегда возможно.

Конечно я использую изляцию Docker-a, например, eсли использовать, к примеру Google Cloud Computing + Docker, то очень быстно выяснится, что


1) вся ВМ, все диски, все свапы — шифруются,
2) при использовании Docker-a внутри Google Cloud Computing, кроме всего прочего, удается решить проблему с разделением переменных окружения дочерними процессами, и самих данных в соседние процессы путем изоляции с помошью контейнеров.
3) при падении или компрометации отдельных компонент, взаимодействующих с внешним миром либо нет, тчень просто перезапустить или изолировать процесс и заменить его на другой

А как решается утечка переменные внутри докера через дочерние процессы? Пременные же в любом случае наследуются, нет? И если у вас упадёт нечто дочернее и сбросит, например отладочные логи с перменными окружения, которые выйдут за пределы контейнера, то туда могут все ключи/пароли попасть и это может никто не заметить? Шифрование тут не поможет — это логи и они пути их неисповедимы, а критически важной ифнормации в них быть не должно.
Или какой-то софт внутри контейнера уязвимый, который может ключи элементарно получить и передать. Он бы этих ключей/паролей и не видел, а тут вот они — в переменных окружения.
Не понимаю, как именно гарантируется непердача переменный окружения за пределы контейнера в общем случае.
Или я что-то в принципиально не понимаю?
В любом случае пароли хранятся в ОЗУ. Вопрос только в том сколько уровней абстракции нужно пройти, чтобы добраться до них. Короче — рутовый доступ к хост-машине с докером эквивалентен полному обладанию всеми секретами на ней.
А убежать из контейнера в хостовое окружение — ну, надо постараться (хоть и потенциально возможно, что при запуске привилегированных контейнеров, что при наличии уязвимостей в самом ядре ОС)
Сами переменные окружения это не так плохо, но можно лучше. Например, подготавливать файл с секретами при старте контейнера, потом его грохать. Нет окружения — нет проблем.
И ещё — а зачем нам вообще говорить о дочерних процессах? Если злоумышленник влез внутрь контейнера, то ок, это уже проблема. Независимо от наличия дочерних процессов.

Ещё могу вспомнить, что коллеги из Авито с подобной бякой боролись. И придумали свой велосипед, весьма неплохой
Остаются логи. Т.к. перменные окружения любят класть в логи краш-дампы, т.к. масса проблем может быть вызвана именно ими.
Я как раз думал через переменные кличи передавать и решил не делать этого именно для контейнеров, т.к. там я не контролирую дальнейшее распространение окружения. Максимум — между двумя своми приложениями/скриптами, где я могу гарантированно переопредлить переменные среды сразу после вытаскивания чувтсвительной информации.
Логи остаются проблемой независимо от переменных окружения, т.к. разработчики (в частности, джависты) любят многомегабайтные простыни стектрейсов, а там может быть что угодно… Поэтому маскирование чувствительной информации — is a must.
А как вы маскируете чувствительную информацию? в приложении?
В приложухе — нет, не получится. Только если настроить несколько уровней logger'а.

Более удобно — сделать это на уровне хранилища логов (того же graylog), но это вычислительно дорого. Но можно просто вшивать в каждое сообщение с потенциально чувствительной информацией некий флаг (нужно доработать приложуху) и на входе в хранилище логов его фильтровать — тогда терпимо.

Т.е. итого — есть хранилище логов. Есть фильтрация на входе. Есть несколько потоков данных в интерфейсе: полный (для сотрудников ИБ и доверенных лиц) и очищенный (для всех остальных, кому нужен доступ).
Кстати, подскажите, кто сталкивался с вариантами токенов для скриптов вживую? Мне рассказывали, но руками не трогал.
Типа того. Ниже уже Hashicorp предложили с динамическими паролями. Смущает только то, что очень молодой проект. У них релиз 1.01 месяц назад был.
С токенами тему чуток мутноватая (вроде считается секурнее, но я как-то не уверен, что сильно), я пару лет назад юзал гугловое апи чуток, в моём случае схема была такая (их там много вариантов) — юзер вводил свой логин/пароль в формочку гугла, а гугл в ответ возвращал приложению пару токенов, один быстропротухающий, а второй для обновления первого и в принципе приложение могло работать уже без участия юзера, но токены эти всё равно надо где-то хранить, если утечка одного первого токена ещё терпима, то если утекут оба, то считай утёк пароль, хотя токены вероятно чуток меньше доступов имеют.
Вы описываете jwt (JSON Web Token).
Если утечет второй токен, сменится пароль и работающее взаимодействие (m2m или живая сессия) сломается.
Перестанут ходить данные и придется повторно себя аутентифицировать через пароль, ключ API и т.д.
Ровно также если утечёт пароль — после его смены он перестанет работать.

Отличие в том, что утекает сессионная Кука, а не пароль или ключ API

Главный доступ, которого токены, как правило, не имеют — доступ к смене пароля. Даже если злоумышленник может делать от имени пользователя что угодно — он всё равно не может «угнать» аккаунт. Как только пользователь заметит неладное — он зайдёт в аккаунт через браузер и инвалидирует все токены.

По крайней мере, именно так эта схема должна работать.
И еще идеально, когда токен привязывается к устройству (или ip, или любому другому критерию), чтобы для любого другого устройства (ip, другого критерия) нужно было 100% выписывать новый токен.
Привязка к ip будет означать постоянные разлогины при переключении с мобильной сети на домашний Wi-Fi. Другой критерий точно так же угоняется и подделывается.
Ну, расскажите, пожалуйста, нам как работает схема с токенами приложений в гугл gmail, например. Это реально интересный и полезный кейс.
Я — честно, не разбирался глубоко. Но могу предположить, что токены, которые мы видим при подключении приложении либо действительно могут быть переиспользованы, либо являются одноразовыми, и тогда на оконечном устройстве появляется что-то типа куки. И которая уже дальше и является ключом шифрования. Ну, может еще как-то текущее время завязано.
Всей этой криптографией и безопасностью интересовался давно, поэтому выпал из тренда.
gmail я отдельно не исследовал, но при использовании токенов сейчас, как правило, используется простейшая схема — bearer token, токен «на предъявителя» без каких бы то ни было дополнительных криптозащит. Однако, у них ограничивается область возможного использования.

ОКей. Спасибо. Т.е. единственная защита от увода bearer token заключается в том, что


  • best practice выписывать уникальный bearer token на каждого потребителя сервиса (что дает нам возможность их селективно блочить, т.е. отзывать bearer token)
  • энтропия и длина bearer token должна быть достаточной, чтобы его нельзя было сбрутить (т.е. для операций с ним — защита на уровне протокола от сниффинга + защита на уровне сервера по rate limit запросов, включающих токен).
Hashicorp нормально делает. И хорошие (полезные и качественные) продукты. И энтерпрайз поддержка у них, если надо. Мои рекомендации.

Также хочу сказать, что без контекста номер версии ни о чем. Вспомним, как многие вендоры переходили с одной системы нумерации версий на другую. Как например, ОпенСусе прыгнули с 13 сразу на 42. Ваш К.О. или файрфокс аналогичный фортель выкинул…

В проектах на Django использую библиотеку django-environ. При этом информация для соединения с базой данных берется либо из переменных окружения, либо из файла .env. Например, DATABASE_URL=mysql://username:password@localhost/project.


Для деплоя использую ansible c паролями, зашифрованными посредством Ansible Vault: в файлах ansible хранятся зашифрованные переменные, пароль шифрования хранится в отдельном файле на сервере CD. Таким образом, пароли в открытом виде никогда не попадают в репозиторий с исходным кодом.

А для puppet/foreman/katello какое решение наиболее кошерно?
Внизу в комментах git-crypt упоминали, но в отличие от ansible vault зашифрованные файлы лежат только в git-репозитории, а локально они остаются в открытом виде.
Для Puppet — однозначно Hiera, она умеет шифровать данные в eyaml
Ещё в ansible можно использовать разные environments для разных наборов хостов. Для dev пароли будут в открытом виде user:qwerty, а для prod — зашифрованные vault.
Это будет работать пока у нас есть конкретные машины, в контейнерных средах это не работает. Лучше всего использовать что-то вроде Hashicorp Vault, хранить пароли там, а уже доставлять их в приложение можно разными способами, как обращением из кода приложения с сессионным ключом прямо в vault, так и внешними инструментами, доставляя пароль при деплое в те же env переменные или через secrets (k8s, rancher, etc..), ну или в систему управления конфигурациями, если она используется в CD. Плюсом такого подхода будет единое место хранения паролей.

Как раз сейчас ковыряю vault для хранения и генерации всего, что связано с аутентификацией.
Пока глубоко не нырнул, полёт нормальный.

Обясните мне, что компроментирует пароль в коде, если он действителен только для конкретного набора хостов?
Ну к тому же mysql
При взломе хоста имхо вас кейринг тоже не спасет.

во многих сложных системах, безусловно, есть не только внутренние хосты, видимые со специально оговоренных IP-адресов, но и внешние, либо динамическим списком IP-адресов, вот в таком случае, велик шанс, что злоумышленнику, получившему валидную пару логин-пароль удастя получить контроль над БД на уровне сервиса, предроставляющего доступ извне, путем подмены IP-адреса, либо MTM, либо несколькими другими способами, (0-Day, и т.д.)

Для CredentialManager есть mimikatz, который доставляется на конечную машину, к примеру, через metasploit.
Получаются все нужные данные.
Дальше уже дело «прямоты» рук администратора mysql.
Прописал только конкретные IP, молодец, но есть варианты.
Прописал вместо IP %,… к без вариантов.
Если копнуть чуть глубже — в полный, так сказать enterprise — то есть несколько крупных и дорогих программных продуктов, специализирующихся на управлении паролями. Работает для конечных пользователей администраторов систем и для отдельных приложений, включая централизованную замену по расписанию во всём зоопарке, распечатку на бумагу для хранения в сейфе у CTO, логирование всего и вся и прочие приятности. Стоит денег, но закрывает многие требования и сценарии из области ИБ и прочих обязательных требований. В редких случаях спасает от серьёзных финансовых последствий.
Полностью согласен. Предложишь варианты?)
CyberArk PAS, например.
Имхо, не работает. Весь этот «энтерпрайз» такое себе.
И сразу вопрос, если cyberark так хорош, то чего все свои велосипеды строят?
Пример — avito поверх hashicorp vault
По CyberArk я в своё время был сертифицированный инженер :) В принципе, такие вещи начинаются с Gartner Magic Quadrant (например тут), и выбираешь вендора. Потом смотришь функционал на сайтах (нужно что-то типа CLI / application-to-application password management). Потом к толстым системным интеграторам, которые у вендора на сайте заявлены как партнёры, за ценником и PoC (Proof Of Concept, он же пилот). Обычно такие штуки всё равно через партнёров продаются, так что напрямую скорее всего не получится.
Поддержу с просьбой.
CyberArk PAS, например.
только после входа пользователя в систему

Но ведь есть ещё другие скрипты, которые запускаются по расписанию на сервере. И если мы говорим про всякие powershell, то это Windows server, а значит там не будет автоматического логина на сервер, а значит скрипт который лежит в планировщике, или в сервисах не сможет получить доступ к хранилищу...

Скрипт может работать под технологической учётной записью. И vault будет использоваться именно ее, а не живого админа.

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

Это понятно, что ТУЗ будет, но вы написали что сначала под этой ТУЗ надо авторизоваться("только после входа в систему"). Т.е. при каждом обновлении сервера и каждой перезагрузке нужно будет авторизоваться на сервере.

Для PowerShell на Windows есть и ещё варианты — можно шифровать пароль сертификатом (лежащим в стандартном хранилище сертификатов, разумеется) или просто аутентифицироваться прям этим сертом.
Спасибо, не думал, что всё так просто. Заменил b64decode на keyring в скриптике и он молча отработал. Вопрос: полное отсутствие функции получения списка паролей — это из-за требований совместимости, хитрая защита или просто так случилось?
Как вы прячете секреты из истории комманд в Windows и Linux? Например, если нужно консольным клиентом подключиться к базе.
Системные варианты ввода пароля. В посте getpass описан. Для bash тоже есть аналогичный безопасный ввод. Надо выключить echo для команды с ключом -s:
#!/bin/bash
# Read Password
echo -n Password: 
read -s password
echo
# Run Command
echo $password


В powershell будет что-то вроде:

$SecurePassword = Read-Host -Prompt "Enter password" -AsSecureString
Начните команду с пробела и он не будет писаться в лог.
Актуально для bash с HISTCONTROL=ignorespace

все переменные вынести в конфиги, конфиги генерить уже на хосте, пароли хранить отдельно в CI/Vault/Env


ну и cur.fetchall() очень плохо, а если там 1млн записей?

ну и cur.fetchall() очень плохо, а если там 1млн записей?

SQL не моя сильная сторона, признаю. Не использую как-то… Это всего лишь пример)
Есть еще git-crypt, который позволяет держать зашифрованные файлы с секретами в гите, и расшифровывать только когда нужно. Но он требует вдумчивого использования, чтобы расшифрованные файлы ненароком куда-нибудь не утекли.
Поднимайте все узлы в изолированной сети, монтируйте файловую систему в readonly с noexec, используйте ротацию паролей с небольшим ttl. И спите спокойно ))

ps: Всегда помните, что приложения открытые в мир по умолчанию уязвимы. И главный вопрос не «что делать если сломают?», а «что делать когда сломают?». Этот план должен быть составлен и проработан, для уменьшений деструктивных последствий атаки.
Linux-овый keyring работает через PAM, и только если пользователь при входе вводил пароль, насколько я понимаю с участием этого самого пароля. Если используется какой-нибудь способ входа без пароля — то keyring остаётся закрытым. Так что на сервере неприменимо.

ИМХО всё чувствительное надо выносить в отдельный конфиги, которые раскладываются из ansible vault, harshicorp vault и всяких прочих AWS KMS.
Т.е. скорее вопрос в том, что если нужно сетевое хранилище данных, то keyring не подойдёт (или к нему удаленно можно цепляться?)
Удалённо цеплять тоже нельзя, но главное, что его не получится использовать для работающей в фоне службы на сервере. Он для десктопных приложений. По крайней мере в Linux, не знаю как насчёт Windows и MacOS.
Почему нельзя? Запускаем скрипт от технологической учетки machine01. Делаем set_passwd. Все. Теперь в этой учетке есть пароль и можно таскать его скриптами через get_passwd.
В системе хранится только солёный хеш пароля, в /etc/shadow для локальных пользователей. Получить сам пароль пользователя PAM модуль может только при вводе этого пароля пользователем при логине.

В этом и весь смысл keyring: не получится получить доступ к зашифрованным данным, даже если удалось получить доступ к файлам. Нужно получить ещё и пароль пользователя.
Твою мать( Спасибо. А неинтерактивно как это решить? В виде демонов/скриптов для m2m? Если без стороннего ПО.
Как убрать пароли в открытом виде из исходников? Обычно это решается вынесением паролей в конфиг либо environment, и раскладыванием этого конфига или environment снаружи средствами CI, configuration management(ansible/puppet/...) или что там у вас есть. Ну и строгими разрешениями, чтобы прочитать этот файл мог только пользователь соответствующей службы, а не любой пользователь на хосте(неактуально для контейнеров).

Для ленивых — можно по каким-то признакам определять, работает приложение в dev или prod среде, и считывать соответствующий config.dev.yml либо config.prod.yml. Первый при этом не так страшно положить в исходники, а второй всё-же должен появляться на хосте в процессе деплоя, руками или автоматикой. А, ну и добавить config.prod.yml в .gitignore на всякий пожарный.
мой способ не самый надежный, но довольно простой — хранить пароли в питон-файле, файл добавить в исключение гит (или что там у вас), импортировать в рабочий файл.
#Примерно так
import nsfw
user_pass = nsfw.userpass01

Естественно, так можно хранить целые словари логинов-паролей, и доставать значение по ключам.
В Powershell есть штатные CmdLetы ConvertFrom-SecureString и ConvertTo-SecureString.
#Получение зашифрованной строки
$SecureString = Read-Host -AsSecureString
$StandardString = ConvertFrom-SecureString $SecureString


Полученную строку можно сохранить в файл параметров и в дальнейшем использовать в скрипте
$SecureString2 = ConvertTo-SecureString -String $Encrypted


В переменной $SecureString2 в данном случае будет не пароль в чистом виде, а объект класса SecureString. Извлечь из него исходную строчку можно так
$Credential = New-Object PScredential "AnyText",$SecureString2
$PlainText = $Credential.GetNetworkCredential().Password


В документации MS по ConvertFrom-SecureString сказано:
If an encryption key is specified by using the Key or SecureKey parameters, the Advanced Encryption Standard (AES) encryption algorithm is used. The specified key must have a length of 128, 192, or 256 bits, because those are the key lengths supported by the AES encryption algorithm. If no key is specified, the Windows Data Protection API (DPAPI) is used to encrypt the standard string representation.

Таким образом, если не указывать ключ шифрования, используется DPAPI и для каждого хоста и учетной записи получим уникальную зашифрованную строку на выходе.
Sign up to leave a comment.

Articles