Шифрование в MySQL: хранилище ключей

В преддверии старта нового набора на курс «Базы данных» подготовили для вас перевод полезной статьи.

Шифрование в MySQL: хранилище ключей

Прозрачное шифрование данных (Transparent Data Encryption, TDE) появилось в Percona Server for MySQL и MySQL довольно давно. Но задумывались ли вы когда-нибудь о том, как оно работает под капотом и какое влияние TDE может оказывать на ваш сервер? В этой серии статей мы рассмотрим, как TDE работает внутри. Начнем с хранения ключей, так как оно требуется для работы любого шифрования. Затем подробно рассмотрим как работает шифрование в Percona Server for MySQL/MySQL и какие дополнительные возможности есть в Percona Server for MySQL.

MySQL Keyring

Keyring — это плагины, которые позволяют серверу запрашивать, создавать и удалять ключи в локальном файле (keyring_file) или на удаленном сервере (например, в HashiCorp Vault). Ключи всегда кэшируются локально, чтобы ускорить их получение.

Плагины можно разделить на две категории:

  • Локальное хранилище. Например, локальный файл (мы называем это файловым хранилищем ключей, file-based keyring).
  • Удаленное хранилище. Например Vault Server (мы называем это серверным хранилищем ключей, server-based keyring).

Это разделение важно, потому что разные типы хранилищ ведут себя немного по-разному не только при хранении и получении ключей, но и при запуске.

При использовании файлового хранилища при запуске в кэш загружается все содержимое хранилища: key id, key user, key type и сам ключ.

В случае серверного хранилища (например, сервер Vault) при запуске загружается только key id и key user, поэтому получение всех ключей не замедляет запуск. Ключи загружаются лениво. То есть сам ключ загружается с Vault только тогда, когда он фактически понадобится. После загрузки ключ кэшируется в памяти, чтобы в будущем не было необходимости обращаться за ним через TLS-соединения к Vault Server. Далее рассмотрим, какая информация присутствует в хранилище ключей.

Информация о ключе содержит следующее:

  • key id — идентификатор ключа, например:
    INNODBKey-764d382a-7324-11e9-ad8f-9cb6d0d5dc99-1
  • key type — тип ключа, основанный на используемом алгоритме шифрования, возможные значения: «AES», «RSA» или «DSA».
  • key length — длина ключа в байтах, AES: 16, 24 или 32, RSA 128, 256, 512 и DSA 128, 256 или 384.
  • user — владелец ключа. Если ключ является системным, например, Master Key, то это поле пусто. Если ключ создается с помощью keyring_udf, то это поле обозначает владельца ключа.
  • сам ключ

Ключ однозначно идентифицируется парой: key_id, user.

Также есть различия в хранении и удалении ключей.

Файловое хранилище работает быстрее. Можно предположить, что хранилище ключей — это простая однократная запись ключа в файл, но нет — здесь происходит больше операций. При любой модификации файлового хранилища сначала создается резервная копия всего содержимого. Допустим файл называется my_biggest_secrets, тогда резервная копия будет my_biggest_secrets.backup. Далее изменяется кэш (добавляются или удаляются ключи) и, если все выполнено успешно, то кэш сбрасывается в файл. В редких случаях, таких как сбой сервера, вы можете увидеть этот файл резервной копии. Файл резервной копии удаляется при следующей загрузке ключей (обычно после перезапуска сервера).

При сохранении или удалении ключа в серверном хранилище, хранилище должно подключиться к серверу MySQL с командами «отправить ключ» / «запросить удаление ключа» («send the key» / «request key deletion»).

Давайте вернемся к скорости запуска сервера. Помимо того, что на скорость запуска влияет само хранилище, есть также вопрос о том, сколько ключей из хранилища необходимо получить при запуске. Конечно, это особенно важно для серверных хранилищ. При запуске сервер проверяет, какой ключ необходим для зашифрованных таблиц / табличных пространств и запрашивает ключ из хранилища. На «чистом» сервере с Master Key — шифрованием должен быть один Master Key, который необходимо извлечь из хранилища. Однако может потребоваться и большее количество ключей, например, когда на резервном сервере восстанавливается резервная копия с основного сервера. В таких случаях следует предусмотреть ротацию Master Key. Подробнее это будет рассмотрено в будущих статьях, хотя здесь я хотел бы отметить, что сервер, использующий несколько Master Key может запускаться немного дольше, особенно, при использовании серверного хранилища ключей.

Теперь давайте поговорим еще немного о keyring_file. Когда я разрабатывал keyring_file, меня также беспокоило то, как проверять изменение keyring_file во время работы сервера. В 5.7 проверка выполнялась на основе статистики файла, что не было идеальным решением, и в 8.0 было заменено на контрольную сумму SHA256.

При первом запуске keyring_file вычисляется статистика файла и контрольная сумма, которые запоминаются сервером, и изменения применяются только в том случае, если они совпадают. При изменении файла контрольная сумма обновляется.

Мы уже рассмотрели многие вопросы о хранилищах ключей. Однако есть еще одна важная тема, о которой часто забывают или понимают неправильно — разделение ключей по серверам.

Что я имею в виду? Каждый сервер (например, Percona Server) в кластере должен иметь отдельное место на сервере Vault, в котором Percona Server должен хранить свои ключи. В каждом Master Key, сохраненном в хранилище содержится GUID сервера Percona Server внутри своего идентификатора. Почему это важно? Представьте, что у вас есть только один Vault Server и все Percona Server в кластере используют этот единственный Vault Server. Проблема кажется очевидной. Если бы все Percona Server использовали Master Key без уникальных идентификаторов, например, id = 1, id = 2 и т. д., то все серверы в кластере использовали бы один и тот же Master Key. Что и обеспечивает GUID — разграничение между серверами. Зачем тогда говорить о разделении ключей между серверами, если уже существует уникальный GUID? Есть еще один плагин — keyring_udf. С помощью этого плагина пользователь вашего сервера может хранить свои ключи на сервере Vault. Проблема возникает, когда пользователь создает ключ, например, на сервере server1, а затем пытается создать ключ с таким же идентификатором на server2, например:

--server1:
select keyring_key_store('ROB_1','AES',"123456789012345");
1
--1 значит успешное завершение
--server2:
select keyring_key_store('ROB_1','AES',"543210987654321");
1

Подождите. Оба сервера используют один и тот же Vault Server, не должна ли функция keyring_key_store завершиться с ошибкой на сервере server2? Интересно, что если вы попытаетесь сделать то же самое на одном сервере, то получите ошибку:

--server1:
select keyring_key_store('ROB_1','AES',"123456789012345");
1
select keyring_key_store('ROB_1','AES',"543210987654321");
0

Правильно, ROB_1 уже существует.

Давайте сначала обсудим второй пример. Как мы уже говорили ранее, keyring_vault или любой другой плагин хранилищ (keyring) кэширует все идентификаторы ключей в памяти. Таким образом, после создания нового ключа, ROB_1 добавляется на server1, и, помимо отправки этого ключа в Vault, ключ также добавляется в кэш. Теперь, когда мы пытаемся добавить такой же ключ во второй раз, keyring_vault проверяет, существует ли этот ключ в кэше, и выдает ошибку.

В первом случае ситуация другая. На серверах server1 и server2 есть отдельные кэши. После добавления ROB_1 в кэш ключей на сервере server1 и сервер Vault, кэш ключей на server2 не синхронизирован. В кэше на server2 нет ключа ROB_1. Таким образом, ключ ROB_1 записывается в keyring_key_store и на сервер Vault, который фактически переписывает (!) предыдущее значение. Теперь ключ ROB_1 на сервере Vault равен 543210987654321. Интересно, что сервер Vault не блокирует такие действия и запросто переписывает старое значение.

Теперь мы видим, почему разделение по серверам на Vault может быть важным — когда вы используете keyring_udf и хотите хранить ключи в Vault. Как обеспечить такое разделение на сервере Vault?

Есть два способа разделения на Vault. Можно создать разные точки монтирования для каждого сервера или использовать разные пути внутри одной точки монтирования. Лучше всего это показать на примерах. Итак, давайте посмотрим сначала на отдельные точки монтирования:

--server1:
vault_url = http://127.0.0.1:8200
secret_mount_point = server1_mount
token = (...)
vault_ca = (...)

--server2:
vault_url = http://127.0.0.1:8200
secret_mount_point = sever2_mount
token = (...)
vault_ca = (...)

Здесь видно, что server1 и server2 используют разные точки монтирования. При разделении путей конфигурация будет выглядеть следующим образом:

--server1:
vault_url = http://127.0.0.1:8200
secret_mount_point = mount_point/server1
token = (...)
vault_ca = (...)
--server2:
vault_url = http://127.0.0.1:8200
secret_mount_point = mount_point/sever2
token = (...)
vault_ca = (...)

В данном случае оба сервера используют одну и ту же точку монтирования «mount_point», но разные пути. При создании первого секрета на сервере server1 по этому пути сервер Vault автоматически создает директорию «server1». Для server2 все аналогично. Когда вы удаляете последний секрет в mount_point/server1 или mount_point/server2, то сервер Vault также удаляет эти директории. В случае, если вы используете разделение путей, вы должны создать только одну точку монтирования и изменить конфигурационные файлы, чтобы серверы использовали отдельные пути. Точку монтирования можно создать с помощью HTTP-запроса. С помощью CURL это можно сделать следующим образом:

curl -L -H "X-Vault-Token: TOKEN" –cacert VAULT_CA
--data '{"type":"generic"}' --request POST VAULT_URL/v1/sys/mounts/SECRET_MOUNT_POINT

Все поля (TOKEN, VAULT_CA, VAULT_URL, SECRET_MOUNT_POINT) соответствуют параметрам файла конфигурации. Конечно, можно использовать утилиты Vault, чтобы сделать то же самое. Но так проще автоматизировать создание точки монтирования. Надеюсь, эта информация окажется для вас полезной, и мы увидимся в следующих статьях этой серии.

Шифрование в MySQL: хранилище ключей

Читать ещё:

Источник: habr.com

Добавить комментарий