Наверняка уже все слышали про General Data Protection Regulation (GDPR), который вступит в силу 25-го мая 2018 года.
Если нет, то в двух словах – это набор правил, которым должны соответствовать компании, которые собирают и хранят данные, в которых содержится информация о жителях Евросоюза (впрочем – США готовятся принять аналогичный документ).
В первую очередь эти правила касаются возможности пользователей знать о том, какие данные собираются и хранятся, и иметь возможность их редактировать и удалять.
Несколько полезных ссылок по этой теме:
- General Data Protection Regulation (GDPR) requirements, deadlines and facts
- GDPR – A practical guide for developers
- Navigating GDPR Compliance on AWS
Содержание
Про GDRP и шифрование
Я не нашёл 100% указания на необходимость выполнять шифрование данных, даже наоборот:
https://www.linkedin.com/pulse/gdpr-encryption-mandatory-gary-hibberd
Dispelling the Encryption Myth
Let’s be clear here; Of the 261 pages of GDPR, the word ‘Encryption’ appears just 4 times;“…implement measures to mitigate those risks, such as encryption.” (P51. (83))
“…appropriate safeguards, which may include encryption” (P121 (4.e))
“…including inter alia as appropriate: (a) the pseudonymisation and encryption of personal data.” (P160 (1a))
“…unintelligible to any person who is not authorised to access it, such as encryption” (P163 (3a))
И в уже упомянутом выше GDPR – A practical guide for develoeprs:
An important note here is that this is not mandated by the regulation, but it’s a good practice anyway and helps with protecting personal data.
…
- Encrypt the data at rest – this again depends on the database (some offer table-level encryption), but can also be done on machine-level. E.g. using LUKS. The private key can be stored in your infrastructure, or in some cloud service like AWS KMS.
Тем не менее – шифровать сервера БД будем.
GDRP и AWS регионы
Юристы соседнего проекта считают, что перенос серверов из Европы (Ireland, eu-west-1) в США (North Virginia, us-east-1) решит задачу, но я так не думаю: под GDPR попадают данные, которые содержат информацию о жителях ЕС. Т.е., где именно хранятся и обрабатываются даные – не имеет никакого значения, важно то, что именно в этих данных содержится.
Кроме того, как уже упоминалось выше – в США готовятся принять свой аналог GDPR (правда не нашёл о нём информации).
Задача
Собственно, задача состоит в том, что бы перенести данные с RDS без шифрования на RDS с шифрованием.
RDS состоит из RDS-мастера, в который приложение пишет данные, и RDS read-replica, которым пользуются аналитики для выборки данных.
Ограничения RDS encryption
Основная проблема заключается в том, что:
На странице Encrypting Amazon RDS Resources приводится список основных ограничений, и первым тут указано:
You can only enable encryption for an Amazon RDS DB instance when you create it, not after the DB instance is created.
Потому следуем другим путём: создаём снапшот текущего инстанса, копируем его, при копировании – включаем ему опцию Encrypted. Потом уже из зашифрованного снимка – разворачиваем новый RDS, который будет с включенным шифрованием.
Учтите, что шифрование поддерживается не всеми типами инстансов, см. список тут>>>.
План действий таков:
- проверка:
- руками создаём тестовый RDS без шифрования
- создаём его снапшот
- копируем снапшот, включаем шифрование
- создаём новый RDS
- реализация:
- создаём CloudFormation шаблон для RDS, который будет включать в себя Snapshot ID и read replica инстанс
- блокируем записи и изменения на текущие базы
- создаём снапшот RDS
- копируем его, включаем шифрование
- создаём CF stack, передаём Snapshot ID в параметры
- обновляем ендпоинты в приложении
- включаем обновление данных в базах
Как-то так.
“Поняслася” (с).
Проверка
Создание RDS без шифрования
Создаём инстанс:
[simterm]
$ aws --profile btrm-rds rds create-db-instance --db-instance-identifier btrm-rds-test-unencrypted \ --allocated-storage 20 \ --db-instance-class db.t2.small \ --engine mariadb \ --master-username root \ --master-user-password passw0rd
[/simterm]
Проверяем:
[simterm]
$ aws --profile btrm-rds rds describe-db-instances --db-instance-identifier btrm-rds-test-unencrypted --query '[DBInstances[*].StorageEncrypted]' --output text False
[/simterm]
Создание snapshot
Ждём, когда сервер перейдёт в статус Available и создаём снимок:
[simterm]
$ aws --profile btrm-rds rds create-db-snapshot \ --db-snapshot-identifier btrm-rds-test-unencrypted-snapshot \ --db-instance-identifier btrm-rds-test-unencrypted
[/simterm]
Проверяем:
[simterm]
$ aws --profile btrm-rds rds describe-db-snapshots --db-snapshot-identifier btrm-rds-test-unencrypted-snapshot --query '[DBSnapshots[*].Encrypted]' --output text False
[/simterm]
KMS ключ
Создаём ключ шифрования, который будет использоваться при копировании snapshot-а:
[simterm]
$ aws --profile btrm-rds kms create-key { "KeyMetadata": { "CreationDate": 1526467497.664, "KeyUsage": "ENCRYPT_DECRYPT", ...
[/simterm]
Задаём ему имя:
[simterm]
$ aws --profile btrm-rds kms create-alias --alias-name alias/btrm-rds-test --target-key-id f7b84a77-***-6d4762c3d601
[/simterm]
Копирование snapshot
Теперь копируем snapshot.
Указываем опцию --kms-key-id
: при использовании её для снимка без шифрования – его копия будет создана зашифрованной.
Выполняем:
[simterm]
$ aws --profile btrm-rds rds copy-db-snapshot --source-db-snapshot-identifier btrm-rds-test-unencrypted-snapshot \ > --target-db-snapshot-identifier btrm-rds-test-encrypted-snapshot \ > --kms-key-id f7b84a77-***-6d4762c3d601
[/simterm]
Проверяем:
[simterm]
$ aws --profile btrm-rds rds describe-db-snapshots --db-snapshot-identifier btrm-rds-test-encrypted-snapshot --query '[DBSnapshots[*].Encrypted]' --output text True
[/simterm]
Создание RDS с шифрованием
[simterm]
$ aws --profile btrm-rds rds restore-db-instance-from-db-snapshot --db-instance-identifier btrm-rds-test-encrypted \ > --db-snapshot-identifier btrm-rds-test-encrypted-snapshot
[/simterm]
Ждём создания, проверяем:
[simterm]
$ aws --profile btrm-rds rds describe-db-instances --db-instance-identifier btrm-rds-test-encrypted --query '[DBInstances[*].StorageEncrypted]' --output text True
[/simterm]
ОК – с этим всё готово.
Реализация
CloudFormation
Шаблон RDS с read replica
Сначала потребуется написать CF шаблон, который будет создавать стек с RDS и read-репликой.
На время тестирования шаблона – будем создавать пустой инстанс RDS, потом добавим параметр DBSnapshotIdentifier
, и будем создавать из снапшота.
Весь шаблон описывать не буду, сам он доступен тут – mariadb_rds_w_read_replica.json
, кратко оставлюсь на самих RDS интансах.
Мастер выглядит следующим образом:
... "MasterRDS" : { "Type" : "AWS::RDS::DBInstance", "Properties" : { "DBInstanceIdentifier" : { "Ref" : "MasterDBInstanceName" }, "AllocatedStorage" : { "Ref" : "DBAllocatedStorage" }, "AutoMinorVersionUpgrade": false, "PubliclyAccessible" : false, "DBInstanceClass" : { "Ref" : "DBInstanceClass" }, "Engine" : "mariadb", "MasterUsername" : { "Ref" : "DBUser" } , "MasterUserPassword" : { "Ref" : "DBPassword" }, "DBSubnetGroupName" : { "Ref" : "DBSubnetGroup" }, "DBParameterGroupName" : {"Ref" : "DBParamGroup" }, "VPCSecurityGroups" : [ { "Fn::GetAtt" : [ "MasterDBSecurityGroup", "GroupId" ] } ], "MultiAZ": {"Ref" : "MultiAZ" }, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "master-rds"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } }, ...
Создаём MariaDB RDS в отдельной VPC, с Multi Availability zone.
И реплика:
... "ReplicaRDS" : { "Type" : "AWS::RDS::DBInstance", "Properties" : { "DBInstanceIdentifier" : { "Ref" : "ReplicaDBInstanceName" }, "AutoMinorVersionUpgrade": false, "PubliclyAccessible" : true, "SourceDBInstanceIdentifier" : { "Ref" : "MasterRDS" }, "DBInstanceClass" : { "Ref" : "DBInstanceClass" }, "VPCSecurityGroups" : [ { "Fn::GetAtt" : [ "ReplicaDBSecurityGroup", "GroupId" ] } ], "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "replica-rds"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } } }, ...
Для Production надо будет добавить параметр и обновить StorageType
– поставить gp2
(SSD).
Создаём стек:
[simterm]
$ aws --profile btrm-rds cloudformation create-stack --stack-name btrm-rds-test --template-body file://mariadb_rds_w_read_replica.json --disable-rollback
[/simterm]
Проверяем:
[simterm]
$ aws --profile btrm-rds rds describe-db-instances --db-instance-identifier master-rds --query 'DBInstances[*].DBInstanceStatus' --output text available
[/simterm]
ОК, шаблон готов.
Теперь надо его обновить, и вместо создания пустого RDS без шифрования – создать RDS из зашифрованного снапшота.
RDS из encrypted snapshot
Т.к. это уже будет шаблон, который потом будет использоваться в проде – то заодно вносим ещё некоторые изменения.
Шаблон доступен в Github тут>>>.
Первое – тип хранилища. По умолчанию magnetic, в проде нас такое не устраивает, описание дисков есть тут>>>.
Для MasterRDS ресурса добавляем параметр StorageType
:
... "StorageType": "gp2", ...
И в DBParamGroup
добавляем опции general_log
, slow_query_log
и log_queries_not_using_indexes
(не обязательно, конечно – это чисто для нас, но для примера тут пусть будет):
... "DBParamGroup": { "Type": "AWS::RDS::DBParameterGroup", "Properties": { "Description": "Database Parameter Group", "Family": "mariadb10.1", "Parameters" : { "general_log" : 1, "slow_query_log": 1, "log_queries_not_using_indexes": 1 }, "Tags" : [ ...
Добавляем параметр DBSnapshotIdentifier
, в значении указываем зашифрованный снапшот, который создавали в самом начале – btrm-rds-test-encrypted-snapshot
:
... "DBSnapshotIdentifier" : { "Description" : "Encrypted snapshot name to restore RDS instance from", "Type" : "String", "Default" : "btrm-rds-test-encrypted-snapshot" } ...
Обновляем MasterRDS, и указываем DBSnapshotIdentifier
:
... "MasterRDS" : { "Type" : "AWS::RDS::DBInstance", "Properties" : { "DBInstanceIdentifier" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "master-rds"] ] }, "DBSnapshotIdentifier": { "Ref" : "DBSnapshotIdentifier" }, ...
Убираем MasterUsername
и MasterUserPassword
– они наследуются из снапшота.
Аналогично с Engine
, BackupRetentionPeriod
и PreferredBackupWindow
, но их можно оставить – к ошибке это не приведёт.
В результате оба RDS сейчас выглядят так:
... "MasterRDS" : { "Type" : "AWS::RDS::DBInstance", "Properties" : { "DBInstanceIdentifier" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "master-rds"] ] }, "DBSnapshotIdentifier": { "Ref" : "DBSnapshotIdentifier" }, "AllocatedStorage" : { "Ref" : "DBAllocatedStorage" }, "StorageType": "gp2", "AutoMinorVersionUpgrade": false, "PubliclyAccessible" : false, "DBInstanceClass" : { "Ref" : "DBInstanceClass" }, "DBSubnetGroupName" : { "Ref" : "DBSubnetGroup" }, "DBParameterGroupName" : {"Ref" : "DBParamGroup" }, "BackupRetentionPeriod": {"Ref" : "BackupRetentionPeriod" }, "PreferredBackupWindow": {"Ref" : "PreferredBackupWindow" }, "VPCSecurityGroups" : [ { "Fn::GetAtt" : [ "MasterDBSecurityGroup", "GroupId" ] } ], "MultiAZ": {"Ref" : "MultiAZ" }, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "master-rds"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } }, "ReplicaRDS" : { "Type" : "AWS::RDS::DBInstance", "Properties" : { "DBInstanceIdentifier" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "replica-rds"] ] }, "SourceDBInstanceIdentifier" : { "Ref" : "MasterRDS" }, "AutoMinorVersionUpgrade": false, "PubliclyAccessible" : true, "VPCSecurityGroups" : [ { "Fn::GetAtt" : [ "ReplicaDBSecurityGroup", "GroupId" ] } ], "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "replica-rds"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } } }, ...
Создаём стек:
[simterm]
$ aws --profile btrm-rds cloudformation create-stack --stack-name btrm-rds-from-encrypted-snap --template-body file://mariadb_rds_w_read_replica_from_snapshot.json --disable-rollback
[/simterm]
Проверяем:
[simterm]
$ aws –profile btrm-rds rds describe-db-instances –db-instance-identifier btrm-rds-from-encrypted-snap-master-rds –query ‘DBInstances[*].StorageEncrypted’ –output text
True
[/simterm]
Готово.