当 DR 灾备遇到 KMS(一)

阅读数:15 2019 年 12 月 20 日 15:26

当 DR 灾备遇到 KMS(一)

中国区的 KMS 服务已经发布,为加密的需求提供了便利的解决方案。但是注意到为了满足安全的要求,KMS 的 CMK 是不允许出区域的,在这种情况下,已经加密的资料,如何能够在另外的区域使用?采用了 KMS 加密的方案,如何实施灾备呢?

一般来说,各种应用场景可以归类为服务器端加密和客户端加密两种实现方式,针对着两种不同的实现方式,本文都提供了相应的解决方案建议。

【应用场景和对应方案】

1. 场景一、服务器端加密的 DR 方案

服务器端加密主要是指 KMS 与托管服务集成的应用场景。这样的场景下的 DR 方案,代码无感,无需研发人员修改代码。

1.1 信封加密的基本原理

为了实现对 DR 的支持,需要借助信封加密的方法。先回顾信封加密的原理。

当 DR 灾备遇到 KMS(一)

在信封加密中,首先需要创建在 KMS 中创建一个 CMK(Customer Master Key);其次,在创建数据库或者 EBS 卷的时候,选择使用刚才创建的 CMK 加密。实际上,系统服务在后台创建了一个 data encryption key(DEK)真正用于加密的数据。这个 DEK 则需要使用之前指定的 CMK 进行加密保护。加密后的 DEK 则会跟相应的业务(RDS/EBS)保存在一起。

信封加密之加密过程

服务器根据客户选择的 CMK,生成一个 DEK,利用 DEK 加密数据,然后用 CMK 加密 DEK,将加密的 DEK 和数据密文放在一个信封里。

当 DR 灾备遇到 KMS(一)

信封加密之解密过程

从信封中取出加密的 DEK,通过 CMK 解密后得到明文的 DEK,使用 DEK 对数据密文进行解密,得到明文数据。

当 DR 灾备遇到 KMS(一)

信封加密在 DR 方案中应用示意

当 DR 灾备遇到 KMS(一)

而在 DR 场景中,数据密文和 DEK 明文在两个不同的区域间是保存不变的,这样就保证了复制到两个区域的数据是被同一个 DEK 加密的,就能使用同一个 DEK 进行解密。关键在于在主区域中的 DEK,是用主区域的 CMK 进行加密保护;而在数据跨区域复制过程中,DEK 会用主区域的 CMK 解密后,将明文的 DEK 使用备份区域的 CMK 进行加密保护。这样,借助信封加密的方法,避免了对海量的源数据进行解密和重新加密的过程,高效实现加密数据的跨区域备份。

1.2 KMS 加密的 EBS 卷,跨区域复制方法

在数据解密的过程中,以 EBS 解密为例。当 EC2 需要读取加密的 EBS 卷,保存在 EBS 上的加密的 DEK,会通过接口,发送到 KMS 服务,通过相应的 CMK,对 DEK 进行解密,在这个过程中,传输的是 DEK,CMK 始终保存在 KMS 服务中,降低泄漏风险。通过接口返回明文的 DEK(当然,传输通道是加密的),EC2 就可以使用该明文的 DEK,对 EBS 进行解密,读取相应的数据。该过程对 EC2 上的应用程序是透明无感的,应用的代码不需要做任何修改。

关于如何将已有的 RDS 数据库转换为加密的数据库,请参见

https://amazonaws-china.com/cn/blogs/aws/amazon-rds-update-share-encrypted-snapshots-encrypt-existing-instances/

那么在DR景下,如何完成加密的EBS 卷的夸区域复制呢?

在信封加密中,用户数据实际上是被 DEK 加密的,而加密的 DEK 会跟 EBS 卷保存在一起。因此,当我们需要将加密的 EBS 卷做垮区域的复制,以便支持 DR 的场景,需要解决的问题就是如何在新的区域能够将 DEK 解密,得到明文 DEK,就可以对数据解密了。

命令行的方式如下

以下代码示例将数据库快照从北京区域复制到宁夏区域并重新加密。在宁夏区域中运行命令。注意 kms-key-id 这个是目标区域宁夏的

Java

复制代码
aws rds copy-db-snapshot \
--source-db-snapshot-identifier arn:aws-cn:rds:cn-north-1:3264xxxxxx:snapshot:rds:test-2019-06-29-12-19 \
--target-db-snapshot-identifier ningxia-encry-dbsnapshot \
--source-region cn-north-1 \
--kms-key-id xxxxx

1.3 KMS 加密的 RDS 数据库,创建跨区域只读副本

对于使用了 KMS 加密的 RDS 数据库,可以按照以下步骤创建垮区域的只读副本

首先源区域创建使用 KMS 加密的数据库。

然后在控制台选定该数据库实例,针对该数据库创建只读副本操作;

对于目标区域 Destination region,选择要复制的目的区域,比如 ZHY;

切换到目标区域的 KMS 页面,记录目标区域使用的自建的 CMK 或者 AWS managed keys 的 ARN

当 DR 灾备遇到 KMS(一)

在 Encryption 选项下面,篮框部分粘贴目标区域的 CMK 的 ARN

当 DR 灾备遇到 KMS(一)

这两个步骤是最关键的,通过这样的方式,创建出来的目标区域的数据库只读副本,对 DEK 加密的 CMK 就会被替换为目标区域的 CMK。在这个过程中数据库的数据没有收到任何改变,加密的数据不变,使用的 DEK 也没有发生变化。

2. 场景二、客户端加密:使用 KMS 的 encryption SDK 的应用场景,涉及代码修改

有一些场景下,客户需要使用客户端方式对数据进行加密,这样的方式下,如何实现 DR 方案呢?

在 AWS 中,提供了 ENCRYPTION SDK,可以支持多个 CMK,每个 Provider 对应了一个或多个 CMK。如下图所示,假设我们需要在 BJS 北京区域和 ZHY 宁夏区域都能够使用同一个 DEK 对数据加解密。

我们可以通过 SDK 的 KmsMasterKeyProvider 方法,通过传入的 CMK 的 ARN, 可以构造一个 KmsMasterKeyProvider。我们可以分别通过 BJS 区域的 CMK-bjs 以及 ZHY 区域的 CMK-zhy 各自构造出一个 KmsMasterKeyProvider。将这两个 KmsMasterKeyProvider 作为参数调用 MultipleProviderFactory.buildMultiProvider 方法,就可以构建出一个包含多个 CMK 的 Provider。

接下来,我们就可以使用这个 Provider 对同一个 DEK 进行加密,就可以得到两个加密的 DEK,分别是 Encryted Data Keybjs 和 Encrypted Date Keyzhy. 这两个被加密的 datakey,在分别使用北京区域和宁夏区域对应的 CMK 解密之后,得到的明文的 datakey 是一样的。这就是加密场景在跨区域 DR 的应用的基础了。在这个过程中,DEK 是不可见的,实际上,调用 encryptData(provider, plaintext, context),在方法内部会使用 DEK 对 plaintext 做加密。

备 __ 注:context:所有 AWS KMS 加密操作都接受加密上下文,它是一 __ 组 __ 包含有关数据的 __ 额 __ 外上下文信息的 可 __ 选键值对。以加密方式 __ 绑 __ 定到密文,需要 相同的加密上下文 来解密(或解密和重新加密)数据。

当 DR 灾备遇到 KMS(一)

在北京区域解密过程。被解密的数据,通过调用 AwsCrypto().decryptData 的方法,只需要传入 provider 和密文就可以了。在 decyptData 方法内部,如下图所示,自动判断应该使用 BJS 的 CMK,对 DEK 解密后,使用 DEK 的明文,就可以用于对密文进行解密了。

当 DR 灾备遇到 KMS(一)

在宁夏区域解密过程也是类似的。SDK 自动判断应该使用 ZHY 的 CMK,对 DEK 解密后,使用 DEK 的明文,用于对密文进行解密。

当 DR 灾备遇到 KMS(一)

col 1
----- |

复制代码
|

参考代码如下。此外,AWS 提供了一个完整的 LAB,建议通过这个 LAB 来熟悉 SDK 不同的使用场景。

http://busy-engineers-guide.reinvent-workshop.com/index.html

以 java 为例,需要指定依赖的 encryption sdk 的版本:

Java

复制代码
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-encryption-sdk-java</artifactId>
<version>1.6.0</version>
</dependency>

需要构造包含多个 CMK 的 provider,例如

Java

复制代码
kms = AWSKMSClient.builder().build();
this.masterKeyEast = new KmsMasterKeyProvider(keyIdEast)
.getMasterKey(keyIdEast);
this.masterKeyWest = new KmsMasterKeyProvider(keyIdWest)
.getMasterKey(keyIdWest);
this.provider = getKeyProvider(masterKeyEast, masterKeyWest)

其中的 getKeyProvider 的方法如下: ****

Java

复制代码
private static MasterKeyProvider<?> getKeyProvider(KmsMasterKey masterKeyEast, KmsMasterKey masterKeyWest) {
return MultipleProviderFactory.buildMultiProvider(masterKeyWest, masterKeyEast);
}
}

执行数据加密的时候,就可以调用这个 provider,这个 provider 会使用之前初始化的多个 CMK 对数据的 DEK 进行加密。

Java

复制代码
public String encrypt(JsonNode data) throws IOException {
FormData formValues = MAPPER.treeToValue(data, FormData.class);
LOGGER.info("Got form submission for order " + formValues.orderid);
byte[] plaintext = MAPPER.writeValueAsBytes(formValues);
HashMap<String, String> context = new HashMap<>();
context.put(K_MESSAGE_TYPE, TYPE_ORDER_INQUIRY);
byte[] ciphertext = new AwsCrypto().encryptData(provider, plaintext, context).getResult();
return Base64.getEncoder().encodeToString(ciphertext);
}

执行数据解密的时候,调用这个 Provider 即可,注:解密的时候,可以用多个 CMK,也可以用当前区域的单个 CMK 初始化 Provider。后者代码执行效率更高。

Java

复制代码
public JsonNode decrypt(String ciphertext) throws IOException {
byte[] ciphertextBytes = Base64.getDecoder().decode(ciphertext);
CryptoResult<byte[], ?> result = new AwsCrypto().decryptData(provider, ciphertextBytes);
// Check that we have the correct type
if (!Objects.equals(result.getEncryptionContext().get(K_MESSAGE_TYPE), TYPE_ORDER_INQUIRY)) {
throw new IllegalArgumentException("Bad message type in decrypted message");
}
return MAPPER.readTree(result.getResult());
}

本文转载自 AWS 技术博客。

原文链接: https://amazonaws-china.com/cn/blogs/china/when-the-dr-disaster-prepared-encounter-kms/

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

评论

发布