Data Engineering Design Patterns, глава 7: Паттерны безопасности данных
Главная мысль: Данные - ценный актив, поэтому их нужно защищать. Глава даёт паттерны для пяти задач: удалять данные по запросу, ограничивать доступ, защищать сами данные, прятать персональные данные и безопасно подключаться к системам.
Законы о приватности, например GDPR и CCPA, задают правила между владельцем данных и пользователем. Одно из них - право на удаление. Поэтому инженер данных думает не только о пайплайнах, но и о безопасности.
Все паттерны главы
Заголовок раздела «Все паттерны главы»| Паттерн | Когда применять |
|---|---|
Vertical Partitioner | Нужно удалять данные пользователя дёшево. Вы строите систему с нуля |
In-Place Overwriter | Данные уже лежат в старой системе. Переделать хранение нельзя |
Fine-Grained Accessor for Tables | Пользователь видит таблицу, но не все её колонки и строки |
Fine-Grained Accessor for Resources | Нужно дать задаче доступ только к своим облачным ресурсам |
Encryptor | Нужно защитить данные на диске и в сети |
Anonymizer | Нужно отдать данные наружу без возможности найти человека |
Pseudo-Anonymizer | Нужно скрыть значения, но оставить пользу для аналитики |
Secrets Pointer | В коде не должно быть логинов и паролей |
Secretless Connector | Можно обойтись совсем без паролей |
Удаление данных (Data Removal)
Заголовок раздела «Удаление данных (Data Removal)»Пользователь просит удалить его данные. Нужно найти их и стереть. Есть два подхода.
Vertical Partitioner
Заголовок раздела «Vertical Partitioner»Проблема: Колонки вроде даты рождения или номера документа не меняются. Но они повторяются в каждой записи. Удалять их везде дорого.
Как работает:
- При загрузке строка делится на две части по колонкам.
- Неизменные и персональные атрибуты (PII) идут в одно хранилище. Каждый пишется один раз.
- Остальные атрибуты (события, действия) идут в другое.
- Общий ключ, например
user_id, позволяет потом объединить части.
Пример на Spark: в foreachBatch делают два select и пишут результат в два топика Kafka. В Delta Lake удаление - это DELETE, а потом VACUUM, иначе старые файлы останутся. В Kafka для удаления шлют tombstone message: ключ и null. Работает при включённой compaction.
Компромиссы:
- Данных для удаления мало, поэтому удаление дешёвое.
- Читателю нужен
join. Запросы сложнее и медленнее. - Помогает единая точка входа (view), каталог данных и lineage.
- Сырые данные до разделения нужно хранить недолго. Иначе они нарушат удаление.
In-Place Overwriter
Заголовок раздела «In-Place Overwriter»Проблема: Старая система хранит терабайты данных по временным партициям. Стратегии для персональных данных нет. Переделать всё нет ресурсов.
Как работает:
- Если хранилище поддерживает
DELETE, выполняют его с условиемWHERE. - В Delta Lake и Iceberg после этого чистят старые файлы. Иначе работает time travel, и данные можно прочитать.
- Для JSON и CSV читают весь набор, фильтруют записи пользователя и пишут результат.
- Результат пишут сначала в
staging area. Потом отдельная задача переносит его на место. Так сбой не ломает данные. - В Kafka читают топик и шлют
tombstoneдля нужных ключей.
Компромиссы:
- Много I/O. Нужно читать и переписывать много файлов.
- Дороже, чем
Vertical Partitioner. У пользователя 2000 событий: нужно переписать 2000 строк. ВVertical Partitionerхватило бы одной. - Объединяйте запросы на удаление в один запуск.
- Откат сложен: исходные данные перезаписаны. Нужно версионирование хранилища.
- Если срок хранения короче срока ответа на запрос, можно положиться на retention. Проверьте это с юристами.
Контроль доступа (Access Control)
Заголовок раздела «Контроль доступа (Access Control)»Fine-Grained Accessor for Tables
Заголовок раздела «Fine-Grained Accessor for Tables»Проблема: Доступа к таблице целиком недостаточно. Пользователь не должен видеть все колонки и все строки.
Как работает:
- Доступ к колонкам:
GRANT SELECT(col_a, col_b) ON table TO user(PostgreSQL, Redshift). - Доступ через теги в каталоге данных. Так работает BigQuery с policy tags.
- Маскирование колонок: пользователь видит колонку, но значение скрыто (Databricks Unity Catalog, Snowflake).
- Доступ к строкам: row-level security, row filter или row access policy. К запросу добавляется условие
WHERE. - Если БД не умеет row-level, делают view с условием по
current_user.
ALTER TABLE users ENABLE ROW LEVEL SECURITY;CREATE POLICY own_rows ON users USING (login = current_user);Компромиссы:
- Условия строятся из данных сессии: имя, группа, IP.
- Вложенные типы данных сложно защитить. Их сначала разворачивают.
- Динамические функции добавляют задержку. Можно сделать отдельную таблицу или view для группы. Это дублирует данные.
Fine-Grained Accessor for Resources
Заголовок раздела «Fine-Grained Accessor for Resources»Проблема: Аудит нашёл слишком широкие права. Одна задача может перезаписать все наборы данных в object store. Нужен принцип least privilege.
Как работает:
- Подход через ресурс: политика привязана к ресурсу, например к бакету GCS.
- Подход через identity: политика привязана к роли или пользователю (IAM role в AWS).
- Права можно ограничить ресурсом, префиксом или условием, например по тегам.
- Политики описывают через IaC, например Terraform.
Компромиссы:
- Много мелких политик трудно поддерживать.
- Wildcard (
visits*) упрощает работу, но нарушаетleast privilege. Обсудите это с безопасниками. - Смесь двух подходов усложняет систему. Выберите один.
- У облака есть квоты на число политик и ролей.
Защита данных (Data Protection)
Заголовок раздела «Защита данных (Data Protection)»Encryptor
Заголовок раздела «Encryptor»Проблема: Данные физически где-то лежат. Их могут украсть с серверов или перехватить в сети.
Как работает:
Data at rest: данные на диске шифруются. Клиентское шифрование: ключом управляет производитель. Серверное: ключами управляет сервис (AWS KMS, GCP KMS, Azure Key Vault).- При чтении хранилище просит ключ у хранилища ключей. Нет прав на ключ - нет данных.
Data in transit: безопасное соединение (TLS). Задают минимальную версию протокола, например 1.2.
Компромиссы:
- Шифрование и расшифровка нагружают CPU.
- Если потерять ключ, данные потеряны. Облака дают
soft deleteключей с периодом восстановления. - Старые версии TLS нужно обновлять.
Anonymizer
Заголовок раздела «Anonymizer»Проблема: Нужно отдать набор данных партнёру. Часть пользователей не согласилась делиться персональными данными.
Как работает:
- Удаление: колонки убирают (
drop). - Perturbation: к значению добавляют шум.
- Синтетическая замена: значение заменяют похожим случайным, например через библиотеку Faker.
- Результат нельзя связать с человеком, даже при объединении с другими данными.
Компромисс: Информация теряется. Аналитика и ML-модели могут дать неверные выводы.
Pseudo-Anonymizer
Заголовок раздела «Pseudo-Anonymizer»Проблема: После Anonymizer аналитики не могут отвечать на бизнес-вопросы. Им нужны значения в более удобной форме.
Как работает:
| Метод | Что делает |
|---|---|
| Masking | Заменяет часть символов, например XXX-XX-1000 |
| Tokenization | Заменяет значение токеном. Связь хранится в защищённом vault |
| Hashing | Необратимо заменяет значение хэшем |
| Encryption | Шифрует значение. Владелец ключа может вернуть оригинал |
| Generalization | Заменяет значение диапазоном, например страна на регион |
Компромиссы:
- Защита слабее, чем у
Anonymizer. Это ложное чувство безопасности. - Если объединить две таблицы, замаскированные поля могут вместе назвать человека.
- Теряется информация и тип данных. Число становится диапазоном, то есть строкой.
- Разные значения могут стать одинаковыми после маскирования.
Подключения (Connectivity)
Заголовок раздела «Подключения (Connectivity)»Secrets Pointer
Заголовок раздела «Secrets Pointer»Проблема: Логин и пароль лежат в коде или в репозитории. Они уже утекали раньше.
Как работает:
- Секреты хранятся в secrets manager (AWS Secrets Manager, Google Secret Manager).
- Код хранит только имя секрета, а не значение.
- Во время выполнения потребитель запрашивает значение у сервиса.
- Доступ защищён дважды: правами на secrets manager и самим секретом.
- Имена секретов одинаковы во всех средах. Значения создаёт IaC.
client = boto3.client("secretsmanager")password = client.get_secret_value(SecretId="pwd")["SecretString"]Компромиссы:
- Кэш секретов может устареть. Простое решение: пусть задача упадёт и перечитает секрет при перезапуске. Для этого нужна
idempotency. - Секреты всё равно могут попасть в логи.
- Секрет всё равно существует. Кто-то должен его безопасно создать.
Secretless Connector
Заголовок раздела «Secretless Connector»Проблема: Команда не хочет управлять паролями и ключами API совсем.
Как работает:
- Подход через IAM: сервис проверяет права вызывающего в IAM. Паролей нет.
- Подход через сертификаты: центр сертификации (CA) проверяет сертификат клиента.
- В AWS задача получает временные credentials через STS и роль. В GCP для задач Dataflow используют Service Account.
Компромиссы:
- Настройка всё равно нужна. Это не “без работы”.
- Сертификаты нужно ротировать. Во время ротации поддерживайте старые и новые, пока все клиенты не перейдут.
Ключевые термины
Заголовок раздела «Ключевые термины»| Термин | Значение |
|---|---|
PII | Персональные данные, по которым можно найти человека |
Tombstone message | Запись с ключом и null для удаления в Kafka |
Staging area | Временное место для результата до публикации |
Least privilege | Минимум прав, нужных для работы |
Row-level security | Ограничение строк, которые видит пользователь |
Column masking | Скрытие значения колонки для части пользователей |
KMS | Сервис управления ключами шифрования |
Secrets manager | Центральное хранилище паролей и ключей |
IAM | Управление доступом пользователей и сервисов в облаке |
Что это значит на практике
Заголовок раздела «Что это значит на практике»- Разделяйте PII и события при загрузке. Тогда запрос на удаление затронет мало данных.
- После
DELETEв Delta Lake или Iceberg запускайтеVACUUMили аналог. Иначе time travel вернёт данные. - Объединяйте запросы на удаление в один запуск. Это экономит I/O.
- Давайте задачам отдельные роли с
least privilege. Не используйте один общий ключ. - Для колонок с PII включайте маскирование или
GRANTпо колонкам. Вdbtи в warehouse храните это как код. - Включайте шифрование по умолчанию на бакетах и задавайте минимальную версию TLS.
- Не кладите пароли в Git. Используйте secrets manager или доступ через роль.
- Проверьте, что секреты не попадают в логи.
- Перед отдачей данных наружу решите: нужна полная анонимизация или хватит псевдонимизации. Проверьте риск объединения таблиц.
Вопросы для самопроверки
Заголовок раздела «Вопросы для самопроверки»- Чем
Vertical PartitionerдешевлеIn-Place Overwriterпри удалении данных пользователя? - Зачем после
DELETEв Delta Lake нуженVACUUM? - Чем
Anonymizerотличается отPseudo-Anonymizer? Почему псевдонимизация даёт ложное чувство безопасности? - Что остаётся проблемой при
Secrets Pointer, а что убираетSecretless Connector?
Предыдущая: Паттерны потока данных. Следующая: Паттерны хранения данных.