Комментарии 83
Я думаю, что стандартный, подход в DevOps заколючется как раз не в раскрытии приватной информации, с последующей ее защитой с помощью keyring, а в том, чтобы изначально параметризовать скрипты, (причем — любые: bash, powershell, python, Docker, и т.д.), с помощью настраиваемых переменных окружения, и предоставлении доступа к ним через Web IDE / Web UI и программный доступ по API для параметризации. Данный подход реализован в GitLab, Azure, Google Cloud, Digital Ocean, GitHub
Не будет, так так там будет светиться что-то вроде ${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
echo $CI_POSTGRES_PASSWORD
Вы имеете в виду, что пуллреквест тоже прогоняется через CI — и это риск утечки данных?
Ну, да, есть такое, но проблема в Gitlab (одно из решений) решается следующими способами:
- переменную можно объявить как protected
- это дает возможность видеть ее ТОЛЬКО на protected runner (нужно их завести и пометить)
- и нужно пометить ветки как protected и назначить права на пользователей (кто куда может пушить код).
- далее аккуратно принимать мерж реквесты.
Ну, и коли Вы не доверяете своей команде разработчиков, которая и так скорее всего имеет доступ на продакшн и может залить любой вредоносный код (гораздо посерьезнее, чем просто отображение пароля к продовой базе), то у Вас проблемы ГОРАЗДО более серьезные, чем утечка паролей...
Если конфиг с открытым паролем, то это хуже предложенного способа.
пароли устанавливает тот же человек, который настраивает пайплайн сборки, но не в скриптах, а в переменных окружения, используемых при сборке, взаимодействие узлов может осуществляеться в таком случает через использование пременных окружения из защифроманной памяти виртуальной машины (Google Cloud).
Если конфиг с открытым паролем, то это хуже предложенного способа.
Если у злоумышленника есть доступ к конфигу которого нет в репозитории, то это гораздо серьезней проблемы с паролями.
В идеале первым делом внутри созданного окружения нужно эти переменные обнулять, чтобы они дальше не растекались, но это не панацея и даже не всегда возможно.
Конечно я использую изляцию Docker-a, например, eсли использовать, к примеру Google Cloud Computing + Docker, то очень быстно выяснится, что
1) вся ВМ, все диски, все свапы — шифруются,
2) при использовании Docker-a внутри Google Cloud Computing, кроме всего прочего, удается решить проблему с разделением переменных окружения дочерними процессами, и самих данных в соседние процессы путем изоляции с помошью контейнеров.
3) при падении или компрометации отдельных компонент, взаимодействующих с внешним миром либо нет, тчень просто перезапустить или изолировать процесс и заменить его на другой
Или какой-то софт внутри контейнера уязвимый, который может ключи элементарно получить и передать. Он бы этих ключей/паролей и не видел, а тут вот они — в переменных окружения.
Не понимаю, как именно гарантируется непердача переменный окружения за пределы контейнера в общем случае.
Или я что-то в принципиально не понимаю?
А убежать из контейнера в хостовое окружение — ну, надо постараться (хоть и потенциально возможно, что при запуске привилегированных контейнеров, что при наличии уязвимостей в самом ядре ОС)
Сами переменные окружения это не так плохо, но можно лучше. Например, подготавливать файл с секретами при старте контейнера, потом его грохать. Нет окружения — нет проблем.
И ещё — а зачем нам вообще говорить о дочерних процессах? Если злоумышленник влез внутрь контейнера, то ок, это уже проблема. Независимо от наличия дочерних процессов.
Ещё могу вспомнить, что коллеги из Авито с подобной бякой боролись. И придумали свой велосипед, весьма неплохой
Я как раз думал через переменные кличи передавать и решил не делать этого именно для контейнеров, т.к. там я не контролирую дальнейшее распространение окружения. Максимум — между двумя своми приложениями/скриптами, где я могу гарантированно переопредлить переменные среды сразу после вытаскивания чувтсвительной информации.
Более удобно — сделать это на уровне хранилища логов (того же graylog), но это вычислительно дорого. Но можно просто вшивать в каждое сообщение с потенциально чувствительной информацией некий флаг (нужно доработать приложуху) и на входе в хранилище логов его фильтровать — тогда терпимо.
Т.е. итого — есть хранилище логов. Есть фильтрация на входе. Есть несколько потоков данных в интерфейсе: полный (для сотрудников ИБ и доверенных лиц) и очищенный (для всех остальных, кому нужен доступ).
Если утечет второй токен, сменится пароль и работающее взаимодействие (m2m или живая сессия) сломается.
Перестанут ходить данные и придется повторно себя аутентифицировать через пароль, ключ API и т.д.
По крайней мере, именно так эта схема должна работать.
Я — честно, не разбирался глубоко. Но могу предположить, что токены, которые мы видим при подключении приложении либо действительно могут быть переиспользованы, либо являются одноразовыми, и тогда на оконечном устройстве появляется что-то типа куки. И которая уже дальше и является ключом шифрования. Ну, может еще как-то текущее время завязано.
Всей этой криптографией и безопасностью интересовался давно, поэтому выпал из тренда.
ОКей. Спасибо. Т.е. единственная защита от увода bearer token заключается в том, что
- best practice выписывать уникальный bearer token на каждого потребителя сервиса (что дает нам возможность их селективно блочить, т.е. отзывать bearer token)
- энтропия и длина bearer token должна быть достаточной, чтобы его нельзя было сбрутить (т.е. для операций с ним — защита на уровне протокола от сниффинга + защита на уровне сервера по rate limit запросов, включающих токен).
Также хочу сказать, что без контекста номер версии ни о чем. Вспомним, как многие вендоры переходили с одной системы нумерации версий на другую. Как например, ОпенСусе прыгнули с 13 сразу на 42. Ваш К.О. или файрфокс аналогичный фортель выкинул…
В проектах на Django использую библиотеку django-environ
. При этом информация для соединения с базой данных берется либо из переменных окружения, либо из файла .env
. Например, DATABASE_URL=mysql://username:password@localhost/project
.
Для деплоя использую ansible c паролями, зашифрованными посредством Ansible Vault: в файлах ansible хранятся зашифрованные переменные, пароль шифрования хранится в отдельном файле на сервере CD. Таким образом, пароли в открытом виде никогда не попадают в репозиторий с исходным кодом.
Ну к тому же mysql
При взломе хоста имхо вас кейринг тоже не спасет.
во многих сложных системах, безусловно, есть не только внутренние хосты, видимые со специально оговоренных IP-адресов, но и внешние, либо динамическим списком IP-адресов, вот в таком случае, велик шанс, что злоумышленнику, получившему валидную пару логин-пароль удастя получить контроль над БД на уровне сервиса, предроставляющего доступ извне, путем подмены IP-адреса, либо MTM, либо несколькими другими способами, (0-Day, и т.д.)
Получаются все нужные данные.
Дальше уже дело «прямоты» рук администратора mysql.
Прописал только конкретные IP, молодец, но есть варианты.
Прописал вместо IP %,… к без вариантов.
только после входа пользователя в систему
Но ведь есть ещё другие скрипты, которые запускаются по расписанию на сервере. И если мы говорим про всякие powershell, то это Windows server, а значит там не будет автоматического логина на сервер, а значит скрипт который лежит в планировщике, или в сервисах не сможет получить доступ к хранилищу...
а если саму учетную запись спрятать в переменных окружения, то даже получив исходный код, злоумышленнику не удастся воспользоваться им для получения доступа к системе
Это понятно, что ТУЗ будет, но вы написали что сначала под этой ТУЗ надо авторизоваться("только после входа в систему"). Т.е. при каждом обновлении сервера и каждой перезагрузке нужно будет авторизоваться на сервере.
#!/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млн записей?
ps: Всегда помните, что приложения открытые в мир по умолчанию уязвимы. И главный вопрос не «что делать если сломают?», а «что делать когда сломают?». Этот план должен быть составлен и проработан, для уменьшений деструктивных последствий атаки.
ИМХО всё чувствительное надо выносить в отдельный конфиги, которые раскладываются из ansible vault, harshicorp vault и всяких прочих AWS KMS.
В этом и весь смысл keyring: не получится получить доступ к зашифрованным данным, даже если удалось получить доступ к файлам. Нужно получить ещё и пароль пользователя.
Для ленивых — можно по каким-то признакам определять, работает приложение в dev или prod среде, и считывать соответствующий config.dev.yml либо config.prod.yml. Первый при этом не так страшно положить в исходники, а второй всё-же должен появляться на хосте в процессе деплоя, руками или автоматикой. А, ну и добавить config.prod.yml в .gitignore на всякий пожарный.
#Примерно так
import nsfw
user_pass = nsfw.userpass01
Естественно, так можно хранить целые словари логинов-паролей, и доставать значение по ключам.
#Получение зашифрованной строки
$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 и для каждого хоста и учетной записи получим уникальную зашифрованную строку на выходе.
Как не продолбать пароли в Python скриптах