Привет, Хабр! Впервые с вопросом анонимизации данных мы широко столкнулись при работе с динамическими окружениями.
Не секрет, что разработка даже небольшого проекта тесно связана с инфраструктурой, поскольку любая программа требует наличия определённого окружения. Часто таких окружений требуется несколько — одно под прод, а остальные — под разные нужды, например, тестирование. Иногда эти среды даже могут быть динамическими — когда вместе с новым бранчем создаётся окружение, в котором запускается разрабатываемая версия приложения и всё необходимое для неё, а после того как бранч вливается в main, эта среда и все её данные удаляется.
И вот как раз о данных (а точнее о базах данных) в таких средах и хотелось бы поговорить. А именно — где и как их взять, как сделать их максимально приближенными к боевым и как защититься от их утечки. Для решения этих задач мы в Nixys используем собственный инструмент — nxs-data-anonymizer. Хотим поделиться им с вами.
Небольшое отступление. Кому интересно узнать принципы, которые мы используем при построении динамических окружений в обслуживаемых проектах — можете посмотреть моё выступление на IT Nights.
Зачем нужны анонимайзеры?
Теперь давайте от окружений вернёмся к данным. Собственно, где их брать? Использовать пустые базы? Так ведь мы ничего не сможем проверить. Синтетические данные? Так их ещё надо придумать и правильно сгенерить. Да и работа приложения не всегда будет такой, как нам нужно.
Самый простой вариант — взять данные прямо с продакшена, но служба безопасности точно будет против. Кроме того, что это незаконно, так ещё и если какие-то чувствительные данные утекут — это огромные репутационные и финансовые издержки для бизнеса. Как вариант — можно один раз взять БД с прода, удалить из неё все секретные данные и поднять свой сервер баз данных для разработчиков. Вот это уже не удобно самим разработчикам, поскольку внезапное изменение данных кем-либо из коллег может повлиять на процесс разработки и тестирования. Эту проблему решает использование того самого заранее подготовленного и очищенного дампа для разворачивания его в изолированных динамических окружениях. Но разработка не стоит на месте, данные и сама их структура нередко меняются, поэтому процесс чистки базы и подготовка свежего дампа для разработчиков становится постоянным явлением. А вот это уже финансовые затраты для бизнеса и влияние на Time-to-Market.
Поэтому нам нужно как-то автоматизировать процесс. Для этого используются анонимайзеры. Причём это не обязательно какие-то сложные и дорогие enterprise-решения, это могут быть и скрипты на php или bash.
Почему мы сделали свой инструмент?
Чаще всего мы сталкиваемся с двумя СУБД: PgSQL и MySQL. Для анонимизации первой — используем решение от ребят из Evrone, а вот с MySQL дела обстоят немного сложнее. Готовых решений нам найти не удалось. Решение от Evrone на текущий момент не поддерживает MySQL. Поэтому долгое время мы обходились самописными bash-скриптами. Их проблема в том, с ними не так просто получить целостное, законченное и унифицированное решение. Нам приходилось допиливать эти скрипты почти под каждую задачу, что в итоге привело к огромному множеству вариаций кода, и при подключении к новому для себя проекту инженерам приходилось почти с нуля разбираться в каждом ответвлении исходного скрипта.
Как часто бывает, в какой-то момент это утомило некоторых инженеров и появилась идея разработать наш собственный инструмент, который мы могли бы применять как готовое коробочное решение на всех наших проектах. Около месяца ушло на его разработку, тестирование и сейчас мы уже применяем его в своей работе. Изначально решение было создано только для MySQL, но в процессе мы поняли, что нам ничего не мешает расширить его функционал и добавить туда PgSQL, а в будущем и другие типы баз данных.
Поскольку исторически MySQL была первой СУБД, которую nxs-data-anonymizer начал поддерживать, то и примеры ниже будут именно для неё. Всю аналогичную информацию для PostgreSQL вы можете найти на странице проекта в GitHub.
Решение получилось достаточно гибким и простым в эксплуатации, и строится на следующих основных идеях:
Потоковая обработка данных. Это значит, что вам не обязательно предварительно готовить и сохранять где-то на диске дамп исходной базы. Инструмент может менять на лету данные, которые ему передаются на stdin. А выдавать всё — на stdout. Т.е. вы можете встроить инструмент непосредственно в команду между двумя pipe’ами;
Значения описываются Go template. То, на что вы хотите заменить нужные вам ячейки в таблице, определяется шаблонами как в Helm, который многим должен быть знаком. Разумеется, что вы точно так же как и в Helm можете использовать имеющиеся многочисленные привычные для себя функции, например, для генерации рандомных строк или чисел;
Использование условий и данных других ячеек в строке. Фильтры могут быть гибкими и делать те или иные подстановки в зависимости от значений других (или даже себя самого) ячеек в той же строке;
Проверка уникальности данных. Когда вы меняете данные (например, API ключи) в колонке, значения которой должны быть уникальными, то может получиться так, что у вас сгенерятся два одинаковых ключа и такой дамп уже не загрузится в БД. Использование этой опции гарантирует, что такие значения повторяться не будут.
Как применить nxs-data-anonymizer на деле?
Допустим, у вас в продакшене есть БД в MySQL с таблицей users:
id | username | api_key |
1 | admin | epezyj0cj5rqrdtxklnzxr3f333uibtz6avek7926141t1c918 |
2 | alice | 2od4vfsx2irj98hgjaoi6n7wjr02dg79cvqnmet4kyuhol877z |
3 | bob | owp7hob5s3o083d5hmursxgcv9wc4foyl20cbxbrr73egj6jkx |
Вам необходимо получить дамп со следующими данными:
Для аккаунта admin: поле api_key должно иметь заранее заданное значение (отличающееся от прода). Это поможет избавиться от необходимости менять настройки приложения каждый раз, когда в dev/test/stage будет загружаться новая версия дампа;
Для остальных: значения username должны быть в формате user_N (где N - это user ID), а api_key — рандомные и уникальные.
Для соблюдения условий выше, для nxs-data-anonymizer нужен конфиг наподобие этого:
filters:
users:
columns:
username:
value: "{{ if eq .Values.username \"admin\" }}{{ .Values.username }}{{ else }}user_{{ .Values.id }}{{ end }}"
api_key:
value: "{{ if eq .Values.username \"admin\" }}preset_admin_api_key{{ else }}{{- randAlphaNum 50 | nospace | lower -}}{{ end }}"
unique: true
Теперь для того, чтобы получить анонимизированный дамп, нужно выполнить такую команду:
mysqldump -u root -ppassword prod | /path/to/nxs-data-anonymizer -t mysql -c /path/to/nxs-data-anonymizer.conf | mysql -u root -ppassword dev
И в результате мы получим такую таблицу в dev:
id | username | api_key |
1 | admin | preset_admin_api_key |
2 | user_2 | dhx4mccxyd8ux5uf1khpbqsws8qqeqs4efex1vhfltzhtjcwcu |
3 | user_3 | lgkkq3csskuyew8fr52vfjjenjzudokmiidg3cohl2bertc93x |
Всё просто.
Что касается использования инструмента в реальных задачах, то там не намного сложнее. Давайте рассмотрим пример, в который включим наиболее распространённые случаи использования анонимизации данных, а затем приведём те части pipeline для GitLab, которые это реализуют.
Есть микросервис, который для хранения данных использует MySQL. Для разработки используется два окружения — production и stage. В stage должна быть база, максимально приближенная к продакшену, но поскольку к этому окружению имеет доступ большое количество человек, то данные должны быть фейковыми. Ещё в проекте есть динамические окружения, которые создаются и разворачиваются при появлении нового бранча с именем в специальном формате. Там тоже в MySQL должны быть фейковые данные. И последний штрих — это локальные среды разработчиков. Не важно как они будут разворачиваться, но данные для нашего микросервиса тоже нужно анонимизировать.
Решение этой задачи может быть следующим:
При релизе в прод (например, по специальному тегу в Git), сначала выполняется стандартный для таких случаев набор действий, таких как сборка приложения, различные тесты, деплой в куб, запуск миграций и прочее;
Затем, с помощью nxs-data-anonymizer происходит сбор дампа боевой базы и её анонимизация. Эта же стадия должна включать в себя возможность ручного запуска джобы по обновлению анонимизированного дампа;
Полученный дамп загружается в нужное вам хранилище (например, s3). Никакого версионирования тут не используется, нам нужна только последняя актуальная копия;
Если БД в stage тоже необходимо обновлять, не важно автоматически или вручную (чаще всего вручную), то из хранилища берётся последняя актуальная версия дампа и просто загружается в stage;
Аналогично происходит и для динамических, и для локальных окружений — просто используются анонимизированные дампы продакшена из хранилища, к которым имеет доступ вся команда разработки.
Графически это описание будет таким:
А вот и интересующие нас моменты описанного выше решения.
Начнём со стадии `anonymize`, которая запускается автоматически сразу после завершения деплоя приложения в прод и миграций БД:
anonymize:
stage: anonymize
image: nixyslab/nxs-data-anonymizer:latest
variables:
GIT_STRATEGY: none
MYSQL_HOST: ${MYSQL_HOST_PROD}
MYSQL_USER: ${MYSQL_USER_PROD}
MYSQL_PASSWORD: ${MYSQL_PASS_PROD}
before_script:
- echo "${S3CMD_CFG}" > ~/.s3cmd
- echo "${NXS_DA_CFG}" > /nxs-data-anonymizer.conf
script:
- mysqldump -h ${MYSQL_HOST} -u ${MYSQL_USER} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE} | /nxs-data-anonymizer -t mysql -c /nxs-data-anonymizer.conf | gzip | s3cmd put - s3://bucket/anondump.sql.gz
only:
- /^v.*$/
except:
- branches
- merge_requests
Эта джоба снимает дамп, анонимизирует и обновляет его в s3. Далее уже этот дамп разойдётся по всем окружениям, которые нужны при разработке проекта. Некоторые из них будут обновлять свои БД автоматически, но для большинства это будет происходить вручную. Как будет выглядеть, можно увидеть на примере stage окружения:
restore-stage:
stage: restore
image: nixyslab/nxs-data-anonymizer:latest
variables:
GIT_STRATEGY: none
MYSQL_HOST: ${MYSQL_HOST_STAGE}
MYSQL_USER: ${MYSQL_USER_STAGE}
MYSQL_PASSWORD: ${MYSQL_PASS_STAGE}
before_script:
- echo "${S3CMD_CFG}" > ~/.s3cmd
script:
- s3cmd --no-progress --quiet get s3://bucket/anondump.sql.gz - | gunzip | mysql -h ${MYSQL_HOST} -u ${MYSQL_USER} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE}
only:
- stage
when: manual
Никакой магии тут нет — скачивается последний актуальный анонимизированный дамп из s3 и загружается в БД, перезаписывая все данные. Далее уже на эту БД можно накатывать миграции для новых версий приложения и проверять как они отработают на проде при релизе, сводя к минимуму неприятные сюрпризы.
Думаю, что всё должно быть достаточно понятно и видно, на сколько легко этот инструмент встраивается в CI/CD любой платформы, будь то GitLab CI, GitHub Actions или что-то ещё.
Но тут описан наиболее распространённый относительно простой случай и если у вас есть какие-то другие кейсы — предлагаю в комментариях разобрать их вместе.
Один из минусов такого подхода — это время на разворачивание дампов больших БД. Если честно, то мы пока не придумали, что с этим делать и будем рады любым идеям от вас :)
Что дальше?
На мой взгляд решение получилось гибким, быстрым и удобным в использовании. Хотелось бы верить, что оно станет для вас таким же классным помощником, как и для нас. А мы продолжим его развивать дальше и выделили следующие фичи, которые планируем реализовать в первую очередь:
Удаление таблиц и строк в таблицах из результирующего дампа. Иногда требуется не только изменить какие-то реальные данные, но и удалить их. Например, когда мы делаем дамп БД нашего таск трекера, нужно полностью удалить некоторые проекты со всеми их задачами. Очень нужная нам фича;
Глобальные переменные. Это специальный блок в конфигурационном файле, который будет содержать значения разных типов (строки, словари и прочее). Эти данные можно будет использовать в любом фильтре. Если при анонимизации имён пользователей вы хотите получать что-то более приближенное к реальности, чем “user_N”, то эта возможность — именно то, что нужно.
Вы в любой момент можете поделиться своими идеями или сообщить нам чего при использовании этого инструмента не хватает вам. Мы сделаем груминг бэклога и ваша фича может оказаться первой в списке на реализацию :)
Также приглашаю вас подписаться на наш блог Хабр, TG-канал DevOps FM и познакомиться с YouTube — мы всегда рады новым друзьям!