Шёл 2024 год, ситуация в мире становилась всё более неблагоприятной: данные утекали, компании за это получали штрафы. Пользователи разделились на 2 лагеря. Первые максимально заботились о защите, а вторые надеялись, что провайдер, у которого хранятся их данные, относится к первому лагерю.
Один из наших клиентов обнаружил, что резервные копии ClickHouse, которые он хранит в S3 провайдера CROC, не очень-то и шифруются. А ФСТЭК суров. Перед нами была поставлена, казалось бы, тривиальная задача. C ClickHouse регулярно работаем, бэкапы делаем (кстати, с помощью ClickHouse-backup, но об этом позже), но с шифрованием бэкапов столкнулись впервые.
Решили поглубже изучить вопрос, и пришли к распределительному камню. Там написано:
Налево пойдёшь — в SSE-S3 попадёшь;
Направо пойдёшь — силу SSE-KMS познаешь;
Прямо пойдёшь — SSE-C будет твоим ответом.
Мы пошли прямо, так как у нашего провайдера на тот момент имелась только SSE-C поддержка. Но другие 2 процесса тоже изучили.
Виды бэкапов ClickHouse (и не только)
Для начала немного освежим память. В мире высоких технологий есть множество вариаций бэкапов, но чаще всего пользователи различают два вида:
полный бэкап, при котором сохраняется вся информация объекта: таблица, база данных и т.д.;
инкрементный, при котором сохраняются только изменения с момента последнего бэкапа, неважно, полным он был или инкрементным.
Встречается и третий вид — дифференциальный: он похож на инкрементный с той лишь разницей, что при каждом последующем бэкапе точкой отсчёта считается предыдущий именно полный бэкап. Но в нашем случае мы будем оперировать только понятиями полного и инкрементного бэкапов.
Немного информации о том, с чем мы работаем и при чём тут ClickHouse-backup
Несмотря на то, что СlickHouse позволяет делать как полные, так и инкрементные бэкапы, нам этого было недостаточно. Нам хотелось более гибкого функционала, полноценной автоматизации и нормального хранения бэкапов, такого, чтобы место на дисках внезапно не кончалось. Нашим решением стал clickhouse-backup. У него есть немало плюсов:
Приложение работает автономно, в режиме watch. Его не нужно запускать руками. Не нужно
костылятьписать скрипты для автоматизации процесса. Можно просто запустить команду типа:$ clickhouse-backup watch --watch-interval=1h --full-interval=24h
и всё, раз в сутки (в нашем случае) будет создаваться полный бэкап, раз в час — инкрементный.Есть вариант с запуском api-сервера. Это может быть полезно для контроля за бэкапами и процессами. Чтобы его включить и настроить, нам потребуется добавить блок API в конфигурационный файл. Для этого нужно перейти по адресу, на котором работает clickhouse-backup:
api:
listen: "0.0.0.0:7171"
enable_metrics: true
запустить сlickhouse-backup в режиме server:
$ clickhouse-backup server
по адресу <host_address>:7171 будет доступно API для взаимодействия с функционалом clickhouse-backup. Если вам интересно узнать подробнее, то ознакомиться можете здесь.
Вывод метрик clickhouse-backup (а мы любим метрики). После того как вы активируете режим API server, по адресу <host_address>:7171/metrics будут отображаться метрики.
Для эффективного использования пунктов 1, 2 и 3 рекомендуем пользоваться systemd unit файлом примерно такого содержания:
[Unit]
Description=clickhouse-backup server
[Service]
Type=simple
ExecStart=/usr/bin/clickhouse-backup server \
--watch \
--watch-interval=1h \
--full-interval=24h
SyslogIdentifier=clickhouse-backup
Restart=always
RestartSec=1
StartLimitInterval=0
ProtectHome=yes
NoNewPrivileges=yes
ProtectSystem=full
[Install]
WantedBy=multi-user.target
Вследствие этого мы получаем соответствующее комбо — сервер API и метрики доступны, за работой clickhouse-backup следит система.
Глобальные изменения конфигурации не требуют вмешательства в работу кластера Clickhouse. Сам Clickhouse к изменениям относится неоднозначно: что-то сам применяет сразу, а что-то — после перезапуска хоста (а перезапускать продовый кластер — это, извините, моветон);
Получение списка бэкапов (далее распишу этот момент чуть подробнее);
Ну и то почему вы читаете сей пост — возможность шифрования бэкапов.
Есть один спорный момент: clickhouse-backup не умеет хранить данные локально. Для нас это больше плюс, так как появляется защита от неопытного инженера, который решит, что хранить бэкапы удалённо это не круто.
В общем, вещь полезная, потому и используем. Для бэкапов у нас в арсенале ещё есть такой инструмент как nxs-backup, который сам в скором времени научится бэкапировать Clickhouse и уже помогает с MySQL, Mariadb, Percona, PostgreSQL, Redis, а ещё всякие полезные штуки умеет. Как шифровать научится — вообще цены ему не будет.
Разбираемся с шифрованием
На старте страшно и непонятно. Какие-то ключи, алгоритмы требования… Проваливаемся в документацию AWS, там всё подробно расписано, так же есть хорошая подробная статья (с картинками!).
Стоит, наверное, упомянуть, что ссылки на документацию Amazon предоставлены для ознакомления в качестве справочной информации. Подразумевается, что протокол s3 есть у тех облачных провайдеров РФ, которые разрабатывались совместимыми с API AWS ( а таких немало).
SSE-C
Алгоритм работы SSE-C можно описать следующим образом:
Clickhouse-backup (далее – клиент) отправляет шифрованный http-запрос с указанием в хедерах описанной выше информации (кстати, если вы попытаетесь передавать шифрованные данные по нешифрованному каналу связи, то ничего у вас не получится. Потому что что? Правильно! Безопасность должна быть безопасной!);
S3 (далее – сервер) запрашивает у клиента ключ шифрования, который был использован при шифровании данных;
Сервер использует предоставленный ключ шифрования для расшифровки данных;
Расшифрованные данные возвращаются клиенту.
Для работы нам понадобятся:
алгоритм шифрования — aes256;
256-битный зашифрованный секрет/токен/ключ в формате base64;
base64-закодированный 128-битного хеш MD5 созданного ранее ключа шифрования.
Настройка SSE-C шифрования на стороне клиента
Если кратко: с помощью маленького скрипта на Python мы сгенерили рандомный ключ, обновили конфигурацию… и готово.
Скрипт выглядит вот так:
import os
import base64
import hashlib
KEY = os.urandom(32)
b64_key = base64.b64encode(KEY).decode(encoding="utf-8")
print(b64_key)
result = hashlib.md5(KEY)
b64_md5 = base64.b64encode(result.digest()).decode(encoding="utf-8")
print(b64_md5)
Конфигурацию clickhouse-backup необходимо модифицировать следующим образом:
s3:
…
sse_customer_algorithm: AES256
sse_customer_key: <тут вывод того что нам выдал print(b64_key)>
sse_customer_key_md5: <тут вывод print(b64_md5)>
SSE-KMS
Шифрование осуществляется на стороне сервера с использованием ключей, которые были созданы ранее. Они хранятся в специальном сервисе управления ключами — в Key Management Service (далее KMS). Созданный ключ KMS указывается в настройках бакета. Этот ключ будет использоваться для шифрования всех новых объектов или при загрузке объекта через API.
Клиент (clickhouse-backup) использует только ID ключа и encryption-context (уточню: шифрование с помощью encryption context поддерживается при операциях с симметричными ключами KMS. Это не является обязательным требованием, но строго рекомендуется).
Объекты шифруются перед сохранением в хранилище и расшифровываются при скачивании из него. Шифрование по умолчанию применяется ко всем новым объектам, при этом загруженные ранее объекты остаются без изменений.
Конфигурация в таком случае будет следующая:
s3:
…
sse: aws:kms
sse_kms_key_id: <тут id ключа>
sse_kms_encryption_context: <тут encryption context закодированный base64 набор пар ключ значение типа {“key1”:”value”, “key2”:”value”}>
SSE-S3
Тут на самом деле всё просто. SSE-S3 является методом шифрования в AWS по умолчанию. Работает так: S3 сам генерирует ключ и использует его для шифрования и расшифровки.
Но куда же без подводных камней. Появилась проблема: старые бэкапы так и остаются нешифрованными. Мы не нашли какого-то нативного решения этого вопроса. Поэтому тут чистая импровизация: либо из разряда удалить старые бэкапы после создания определённого количества успешных шифрованных, либо перезалить их с шифрованием (что даже звучит больно и долго). Но если кто вдруг знает какой-то крутой метод — делитесь в комментариях.
Восстанавливаем из бэкапа через clickhouse-backup и расшифровываем
Выше я уже рассказал, как выглядит шифрование и что происходит в процессе. Как вы понимаете, восстановление из шифрованных бэкапов — процесс обратный. Выглядит он следующим образом:
Смотрим, что у нас имеется по бэкапам в запасах:
$ clickhouse-backup list
В ответ получаем вот такой список:
full-20240327090532 1.87MiB 27/03/2024 09:05:32 remote gzip, regular
increment-20240327090632 1014.07KiB 27/03/2024 09:06:32 remote +full-20240327090532 gzip, regular
increment-20240327090732 3.05MiB 27/03/2024 09:07:32 remote +increment-20240327090632 gzip, regular
increment-20240327090832 3.05MiB 27/03/2024 09:08:32 remote +increment-20240327090732 gzip, regular
increment-20240327090932 99.12MiB 27/03/2024 09:09:35 remote +increment-20240327090832 gzip, regular
increment-20240327091032 100.62MiB 27/03/2024 09:10:33 remote +increment-20240327090932 gzip, regular
increment-20240327091132 100.62MiB 27/03/2024 09:11:32 remote +increment-20240327091032 gzip, regular
increment-20240327091232 100.62MiB 27/03/2024 09:12:32 remote +increment-20240327091132 gzip, regular
increment-20240327091332 100.62MiB 27/03/2024 09:13:32 remote +increment-20240327091232 gzip, regular
Отправляем запрос на восстановление данных в базе:
$ clickhouse-backup restore --tables=db.table increment-20240327091332
clickhouse-backup отправляет запрос к S3, чтобы получить доступ к зашифрованным данным;
S3 в свою очередь запрашивает у clickhouse-backup ключ шифрования, который был использован при шифровании данных;
clickhouse-backup предоставляет ключ шифрования S3;
S3 использует предоставленный ключ шифрования для расшифровки данных;
clickhouse-backup получает расшифрованные данные;
clickhouse-backup начинает процесс восстановления.
И да прибудет с вами сила, и да не придётся вам никогда восстанавливать данные из бэкапа!
Заключение
Давайте подытожим. Была поставлена задача — шифровать резервные копии данных из ClickHouse самым надёжным из доступных методов. Исходя из описанной выше ситуации был выбран метод шифрования SSE-C, который поддерживается clickhouse-backup, соответственно, никаких архитектурных перестановок не потребовалось, всё осталось на своих местах. Просто процесс взаимодействия с хранилищем был немного изменён, что для нас является прозрачным решением.
Все последующие бэкапы шифруются, данные людей в безопасности, заказчик доволен, все счастливы.
Что же получили мы. Опыт и новые знания. А ещё мы углубились в процессы резервного копирования и работу сlickhouse-backup.
Что же получили вы? Немного информации (для кого-то новой). А кто-то даже нашёл решение своей проблемы, чему мы будем очень рады. На этом всё.
Всем DevOps!
Кстати! Хочу пригласить вас подписаться на наши соцсети: Telegram, vc.ru и YouTube. У нас весело и интересно :)