AWS: GDPR и RDS — миграция на encrypted RDS

Автор: | 05/17/2018

Наверняка уже все слышали про General Data Protection Regulation (GDPR),  который вступит в силу 25-го мая 2018 года.

Если нет, то в двух словах — это набор правил, которым должны соответствовать компании, которые собирают и хранят данные, в которых содержится информация о жителях Евросоюза (впрочем — США готовятся принять аналогичный документ).

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

Несколько полезных ссылок по этой теме:

Про 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, который будет с включенным шифрованием.

Учтите, что шифрование поддерживается не всеми типами инстансов, см. список тут>>>.

План действий таков:

  1. проверка:
    1. руками создаём тестовый RDS без шифрования
    2. создаём его снапшот
    3. копируем снапшот, включаем шифрование
    4. создаём новый RDS
  2. реализация:
    1. создаём CloudFormation шаблон для RDS, который будет включать в себя Snapshot ID и read replica инстанс
    2. блокируем записи и изменения на текущие базы
    3. создаём снапшот RDS
    4. копируем его, включаем шифрование
    5. создаём CF stack, передаём Snapshot ID в параметры
    6. обновляем ендпоинты в приложении
    7. включаем обновление данных в базах

Как-то так.

«Поняслася» (с).


Проверка

Создание RDS без шифрования

Создаём инстанс:

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

Проверяем:

aws --profile btrm-rds rds describe-db-instances --db-instance-identifier btrm-rds-test-unencrypted --query '[DBInstances[*].StorageEncrypted]' --output text
False

Создание snapshot

Ждём, когда сервер перейдёт в статус Available и создаём снимок:

aws --profile btrm-rds rds create-db-snapshot \
--db-snapshot-identifier btrm-rds-test-unencrypted-snapshot \
--db-instance-identifier btrm-rds-test-unencrypted

Проверяем:

aws --profile btrm-rds rds describe-db-snapshots --db-snapshot-identifier btrm-rds-test-unencrypted-snapshot --query '[DBSnapshots[*].Encrypted]' --output text
False

KMS ключ

Создаём ключ шифрования, который будет использоваться при копировании snapshot-а:

aws --profile btrm-rds kms create-key
{
"KeyMetadata": {
"CreationDate": 1526467497.664,
"KeyUsage": "ENCRYPT_DECRYPT",
...

Задаём ему имя:

aws --profile btrm-rds kms create-alias --alias-name alias/btrm-rds-test --target-key-id f7b84a77-***-6d4762c3d601

Копирование snapshot

Теперь копируем snapshot.

Указываем опцию --kms-key-id: при использовании её для снимка без шифрования — его копия будет создана зашифрованной.

Выполняем:

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

Проверяем:

aws --profile btrm-rds rds describe-db-snapshots --db-snapshot-identifier btrm-rds-test-encrypted-snapshot --query '[DBSnapshots[*].Encrypted]' --output text
True

Создание RDS с шифрованием

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

Ждём создания, проверяем:

aws --profile btrm-rds rds describe-db-instances --db-instance-identifier btrm-rds-test-encrypted --query '[DBInstances[*].StorageEncrypted]' --output text
True

ОК — с этим всё готово.

Реализация

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).

Создаём стек:

aws --profile btrm-rds cloudformation create-stack --stack-name btrm-rds-test --template-body file://mariadb_rds_w_read_replica.json --disable-rollback

Проверяем:

aws --profile btrm-rds rds describe-db-instances --db-instance-identifier master-rds --query 'DBInstances[*].DBInstanceStatus' --output text
available

ОК, шаблон готов.

Теперь надо его обновить, и вместо создания пустого 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"} }
        ]
      }
    }
  },
...

Создаём стек:

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

Проверяем:

aws ---profile btrm-rds rds describe-db-instances ---db-instance-identifier btrm-rds-from-encrypted-snap-master-rds ---query ‘DBInstances[*].StorageEncrypted’ ---output text
True

Готово.