Comments 22
И снова Security through obscurity...
МТС ничего не ответил по сути https://habr.com/en/companies/ru_mts/articles/861822/ . Давайте посмотрим, что скажет Сбер. Итак, вы пишете:
Используйте внешнего провайдера секретов для ваших сервисов.
Прекрасно! Теперь вопрос: Используете ли вы секрет для доступа к самому ПровайдеруСекретов?
Если нет, то злоумышленник прочитает ПровайдерСекретов и получит секреты для ваших сервисов...
Если да, то вам нужен ПровайдерСекретов2, чтобы хранить секрет для ПровайдераСекретов...
Вы видите суть проблемы?
ЗЫ эта статья уже была сегодня размещена по адресу https://habr.com/en/companies/sberbank/articles/869932/ но быстро удалена. в чем причина?
В правильных организациях открытие провайдера секретов делается аппаратным способом. То есть по определенному протоколу взять карту из сейфа в присутствии представителя СБ, открыть серверную, и приложить к считывателю. Потом вернуть в сейф и опечатать.
В совсем-совсем правильных организациях применяется разделение секретов N/K. То есть чтобы выполнить описанную выше процедуру надо собрать троих из пяти, например, людей имеющих право открывать хранилище секретов.
Ну и да - перезапускать хранилище секретов слишком часто в такой ситуации нельзя. На второй-третий раз уважаемые люди начнут задавать вопросы - чем вы тут собственно занимаетесь ?!
В правильных организациях открытие провайдера секретов делается аппаратным способом. То есть по определенному протоколу взять карту из сейфа в присутствии представителя СБ, открыть серверную, и приложить к считывателю. Потом вернуть в сейф и опечатать.
Это верх автоматизации! Будущее так не наступит.
Добрый день!
Для доступа к самому провайдеру секретов используется только публичный сертификат для установки TLS соединения. Далее при использовании HTTP API провайдера можно получать секреты, но:
каждое обращение аутентифицируется
(либо уже имеющийся токен, либо токен ServiceAccount-а из namespace кубера, либо Approle, т.е. логин и пароль, по сути, либо множество других вариантов аутентификации)каждое аутентифицированное обращение за секретом авторизуется
(гибкими политиками в провайдере секретов настроено, что и кому можно отдавать)
Таким образом, злоумышленник не сможет получить секреты из провайдера, обращаясь к нему, используя публичный сертификат для TLS.
каждое обращение аутентифицируется (либо уже имеющийся токен... либо множество других вариантов аутентификации)
вы не поняли сути вопроса.
если для доступа к ПровайдеруСекретов необходим токен, то вам нужен ПровайдерСекретов2 ;)
Так, понятно.
Используем 2 варианта:
В кубере для Аутентификации используется токен от ServiceAccount-а, который автоматически монтируется в pod Vault Agent sidecar-а (самим кубером).
Сам Vault при этом интегрирован с кластером кубера: знает про токены каждого namespace-а (аутентификация) и знает какому namespace-у какие секреты можно давать.На виртуалках для Аутентификации используется Approle (пара логин и пароль).
Здесь самый тонкий момент, про который вы как раз и пишите.
Approle доставляется на каждую виртуалку при деплое сервиса в виде wrapped token-а - это одноразовая сущность, имеющая время жизни.
Сервис при старте использует wrapped token, чтобы "развернуть" (достать) из него Approle и получить с помощью неё секреты.
Далее во время работы сервиса он периодически приносит себе новый wrapped token с Approle-ью внутри, не доводя до превышения времени жизни.
Это позволяет в любой момент рестартовать сервис.
Как видите, не нужен ни какой ПровайдерСекретов2 ;)
Используем 2 варианта
хехе. для начала контекст новым читателям:
давайте рассмотрим сферический Vault в вакууме:
1.мы ставим на Linux сервер нативное Приложение.
2.у Приложения есть конфиг и ему нужно работать с Базой.
а дальше уже интересно:
3.все знают, что пароль к Базе нельзя хранить в конфиге!
4.все знают, что пароль к Базе нужно хранить в Vault.
итак, наш Vault - тоже База. база секретов.
и вы только что нам сказали, что у вас есть "2 варианта" безопасного соединения с Vault. прекрасно!
так почему бы Приложению сразу не использовать эти "2 варианта" для прямого соединения с Базой без всякого Vault?
если "2 варианта" безопасны, то Vault не нужен. (не улучшает Безопасность)
если "2 варианта" НЕ безопасны, то Vault не нужен тем более!! (он ухудшает Безопасность)
Оба эти варианта безопасны, потому что, даже если вы получите доступ к базе Vault-а, где хранятся секреты, сами секреты вы всё равно не получите, т.к. они зашифрованы master ключом, который зашифрован root ключом, который тоже зашифрован.
А все эти ключи для расшифровки секретов знает только тот, кто развёртывал Vault, и вряд ли это вы (тот кто хочет угнать секреты) :)
ЗЫ эта статья уже была сегодня размещена по адресу https://habr.com/en/companies/sberbank/articles/869932/ но быстро удалена. в чем причина?
Не от того аккаунта опубликовали статью изначально. Прошу понять и простить )
Все-таки правду говорят - Сбербанк ничего не делает как другие люди. :-) Можете сказать, чем вам не угодил spring-boot-cloud с нативным стартером для интеграции с Vault ? Bootstrap-контекст сходит в Vault и вытащит секреты, скажем, в проперти. Дальше уже в нормальном application-context ссылайтесь на эти проперти - и будет счастье. Опять же HikariCP имеет MXBean который может делать как get так и set для пользователя и пароля - делайте Scheduled метод и ротируйте... Старые сессии, которые уже зашли в Postgres будут жить по итогам старой аутентификации, новые пойдут с новой. И это все, в целом, из коробки - практически ничего не надо дописывать...
Старые сессии, которые уже зашли в Postgres будут жить по итогам старой аутентификации, новые пойдут с новой.
Если у вас пользователь базы из AD, то вы таким образом эффективно заблокируете его, использовав несколько раз старый пароль после его ротации (да я бы честно говоря, и локальных пользователей блокировал бы при вводе неверного пароля). Ну т.е. ротация - далеко не такое простое дело, и Vault сам по себе все проблемы с ней не решает. И я подозреваю, что никто этого полноценно не делает, но тут я естественно ограничен моими познаниями, которые могут быть неполные.
Каким образом старый пароль будет использоваться после ротации ? Постгрес не аутентифицирует сессию при каждом SQL statement. Соответственно в момент перед ротацией у вас все активные сессии в пуле аутентифицированы и пароль не предъявляют. За некоторый промежуток до окончания действия старого пользователя/пароля (обычная практика с dynamic credentials в Vault - это создать временного пользователя со случайным суффиксом и случайным паролем) - вы инициируете ротацию со стороны приложения и записываете нового полученного пользователя и пароль в HikariCP. Старые сессии так и живут (они не подлежат повторной аутентификации), а если Hikari решит что в пул надо добавить новую сессию - то она пойдет уже с новым пользователем и паролем. Понятно, что при этом и БД и Vault надо правильно настроить: lease-time должен быть немного больше чем промежуток ротации со стороны приложения, а старый пользователь/пароль счищаться не в момент ротации, а в момент истечения credential-lease. Иначе остается короткое окно когда мы уже инициировали ротацию, но еще не обновили секрет в Hikari - и вот именно в этот момент случился burst нагрузки, и мы побежали заказывать сессии в базу - и облом!
Каким образом старый пароль будет использоваться после ротации ?
Считайте что любым. Что он в памяти где-то у приложения лежит.
Иначе остается короткое окно когда мы уже инициировали ротацию, но еще не обновили секрет в Hikari
Ну т.е. на самом деле я вот про это и говорил - что одного Vault тут недостаточно, сротированный/ротируемый пароль нужно правильно распространять в распределенной среде, и у этой задачи есть много решений, среди которых много неправильных. Причем неправильных как реализаций, так и спецификаций - потому что скажем спецификация DataSource в общем-то не предполагает динамической ротации пароля, это за пределами ее скоупа на момент создания. Так же как Tomcat, будучи реализацией JavaEE контейнера, не предполагает в спецификации, что кто-то будет сертификаты перевыпускать налету. И то что конкретный пул Hikari у вас работает (только при правильной настройке) - так это считай повезло. В другом сценарии интеграции будет вообще HTTPS с керберосом, keytab в качестве креда, и условный apache http client, который снова нихрена не знает о том, keytab тоже может ротироваться.
Коллеги, предлагаю дождаться второй части статьи где я подробно разберу, как мы обновляем секреты «на горячую»:
серверные SSL-сертификаты Tomcat-а
клиентские SSL-сертификаты для отправки HTTPS-запросов
креды БД и SSL-сертификаты для HikariDataSource
Ну мы конечно дождемся, но было бы интереснее немного не в такой постановке. Поясню, почему:
не у всех tomcat, тем более не у всех томкат нужной версии или его можно легко обновить
клиентские сертификаты как правило не вызывают особых проблем вообще, с учетом того, что выпуск нового сертификата автоматически не приводит к отзыву старого, проблемы обычно с чем-то типа пароля доменного, когда вы его использовали три раза неверный - и заблокировали учетку
и уж точно не у всех HikariDataSource
а еще есть например кафка, которой тоже нужны сертификаты, и где соединение устанавливается в долгую
Ну т.е. тема актуальная, но сами вопросы лично для меня не очень.
Bootstrap-контекст сходит в Vault и вытащит секреты, скажем, в проперти. Дальше уже в нормальном application-context ссылайтесь на эти проперти - и будет счастье.
Читать секреты в Spring property небезопасно, т.к. эти глобальные переменные обязательно окажутся в снятом heap dump-е.
Мы читаем секреты, находящиеся в каталоге, смонтированном в tmpfs, доступ к которому имеет только учётка, из-под которой работает приложение.
Плюс ко всему, у нас custom-изированный алгоритм ротации секретов, использующий:
random-ную задержку перед применением
(чтобы в каждом pod-е применение происходило в разные моменты времени)sanity-проверку валидности пришедших новых секретов
(невалидные секреты не будут применяться с откидыванием метрик и логированием ERROR, но не сломают приложение).
Такую custom-изацию не сделать ни с помощью SSL bundles из Spring Boot, ни с помощью @RefreshScope из Spring Cloud (проверяли).
С помощью MXBean-а от HikariCP разве можно сделать такую custom-ную ротацию кредов БД?
Я полагаю что если вы в джаве считали секреты (откуда угодно), то они почти наверняка окажутся в heap-dump. Однако - вся вот эта возня с Vault и ротацией секретов как раз и затевается в надежде что к моменту когда секреты достанут из дампа и доберутся до базы через файрволы - этот секрет уже протухнет.
Сделать чтобы секреты применялись в разные моменты времени - можно через стандартный spring-cloud-vault через параметры min-renewal и expiry-threshold. Их можно менять программно, а не только из bootstrap.properties.
Проверка валидности секрета - это хорошо, но я не вижу большого смысла. Ибо если у вас vault не работает правильно - то это уже фаталити. Ибо в скрипте Vault может быть прописано не только создание ролей, но и дополнительные команды типа grant role чтобы этот пользователь нормально работал с данными. И если у вас это перестало работать - то значит что-то очень сильно пошло не так. Если система критическая и она обязана работать - то должен сработать переход на emergency credentials и оповещение операторов что система ушла в резервный режим (чтобы потом ротировали вручную emergency secrets). А если система не критическая - то дешевле завершаться (хикари увидит что потеряны соединения, и сделает application health = DOWN, что должен заметить k8s и убить pod)... Но чисто теоретически, никто не мешает открыть соединение вне DataSource к БД чтобы проверить что пришли валидные пользователь/пароль, и только после этого запихивать их в HikariCP MXBean.
Я полагаю что если вы в джаве считали секреты (откуда угодно), то они почти наверняка окажутся в heap-dump.
Ну, вы лично или мы можем кое-что предпринять, чтобы этого не было, например не хранить в виде String а хранить в виде char[] и затирать после использования, но я боюсь это такая глубокая кроличья нора... потому что проверить что все наши зависимости так тоже делают, очень сложно. Я бы скорее думал в сторону того, чтобы кто попало не мог получить дамп.
не хранить в виде String а хранить в виде char[] и затирать после использования
Да, так и делаем. С подключенными библиотеками, действительно, сложнее (HikariCP и встроенный в Spring Tomcat). Но сам JDK при построении SSLContext-а принимает char[], а не String. Судя по тому, что это известная best practice, то библиотеки постепенно подтянутся к этому.
чтобы секреты применялись в разные моменты времени - можно через стандартный spring-cloud-vault через параметры min-renewal и expiry-threshold. Их можно менять программно ...
Так и так кастомизация hot reload-а секретов получается. Но спасибо за наводку.
[Про проверку валидности секретов] Если система критическая и она обязана работать - то должен сработать переход на emergency credentials и оповещение операторов что система ушла в резервный режим (чтобы потом ротировали вручную emergency secrets)
У нас практически так и сделано: откидывание метрик и логированием ERROR, если поступил "кривой" новый секрет.
Сопровождение, увидев такие метрики, идёт в Vault исправлять проблему.
Но чисто теоретически, никто не мешает открыть соединение вне DataSource к БД чтобы проверить что пришли валидные пользователь/пароль, и только после этого запихивать их в HikariCP MXBean.
Так и делаем.
Секреты в Java-сервисах на Spring: где брать и как обновлять