最近经常能听见某某某公司用户信息被泄漏了,某某某公司用户开房记录被脱裤了 … 数据隐私是企业的生命线,其重要性我想无需我多说什么。
上一篇文章我们科普了一下 AWS KMS 服务:
AWS KMS 科普: What Why and How?
简要回顾一下:KMS 全称为 Key Management Service,也就是一个密钥管理系统,它在设计上从根本解决了数据的安全性问题,同时还具备完整的安全审计功能,能让我对谁能够访问我的数据、谁什么时候动了我的数据一目了然。
借助 KMS 的帮助,我们能够做到,即使我被脱裤了,攻击者拿到我的数据也无法解密!这篇文章,我们就来具体看看怎么将 KMS 应用到实践中,从而提升产品安全性。
这里我们将产品分为两种:
- 一般类型产品:需要保护自身数据,比如各种自营电商平台等。
- 平台类产品:平台服务商需要保护租户数据,比如 Authing、有赞 等。
之所以这么区分,是因为平台服务商属于多租户场景。对于一般类型产品,只需要加密好数据就行了,而对于平台类产品,理想情况下,租户希望做到以下两点:
1. 知道平台服务商什么时候、以什么理由访问了我的数据。
2. 当我不再使用你的服务时,我可以一键取消对平台服务商的数据授权,今后平台服务商再也无法访问到我的数据。
这种情况下,可以使用 KMS 跨账号授权的功能,基本流程为:租户在 AWS KMS 后台创建一个密钥并授权给平台服务商,平台服务商使用这个密钥加密/解密该租户的数据,借助 AWS Cloud Trail ,租户可以很轻松地知道平台服务商什么时候、以什么理由访问了我的数据。当租户决定不再使用平台服务商产品的时候,只需要在 AWS KMS 后台取消对其授权就行了。
基本原理就这么简单,下面从技术角度,梳理一下需要解决的几个技术问题:
- 如何使用 KMS 加密/解密的数据?
- 如何将 KMS 密钥授权给其他账号?
- 如何使用 Cloud Trail 查看第三方什么时候访问了我的数据?
- 作为平台服务商,如何解决 KMS 服务商绑定问题?
- 最后一点也是比较有难度的一点:如何在不大幅变动业务逻辑的情况下做到对数据自动加密解密?
下面我们由浅入深地介绍如何将 KMS 应用到你的产品中。
1. 开通 KMS 服务,创建一个 KMS CKM
首先你需要有一个 AWS 账号,创建一个具备 KMS 全部权限的 User(或者 Role),该 User 继承的 Policy 如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "kms:*",
"Resource": "*"
}
]
}
创建好 User 之后,你应该能够得到该 User 的 accessKeyId 和 secretAccessKey,接下来在 SDK 中会用到。
接着在 AWS Console KMS 管理页面,创建一个客户管理的密钥 (Custom Managed Key),并将此密钥的「密钥管理员」和「可在加密操作中使用 CMK 的 IAM 用户和角色」设置为前一步创建的 User,创建之后你可以得到该密钥的 KeyId:
总结一下,这一步我们获取了以下这些内容:accessKeyId secretAccessKey 和 KeyId。
2. KMS 加密/解密 Demo
我们一般使用 AWS Encryption SDK 来完成数据的加密解密,它为我们节省了什么工作量,同时自带了很多最佳实践,如使用信封加密、 kerying 等。
AWS Encryption SDK 支持多种语言,如 Java/Python/C/JavaScript 等,这里我们以 JavaScript 为例。
加密 Demo:
import * as AWS from "aws-sdk"
import { KmsKeyringNode, encrypt, decrypt, getClient, MessageHeader } from '@aws-crypto/client-node'
const keyring = new KmsKeyringNode({
generatorKeyId: "给你的 KeyId",
clientProvider: getClient(AWS.KMS, {
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
}
})
})
const { result } = await encrypt(keyring, "需要加密的内容", { encryptionContext: {
key: "额外上下文信息"
} })
说明以下几点:
- generatorKeyId 就是 CMK 密钥 ID
- 这里初始化了一个 keyring 。
- encryptionContext 是可选的加密上下文信息,可以在 Cloud Trail 审计页面看到。
解密 Demo:
import * as AWS from "aws-sdk"
import { KmsKeyringNode, encrypt, decrypt, getClient, MessageHeader } from '@aws-crypto/client-node'
const keyring = new KmsKeyringNode({
generatorKeyId: "你的 KeyId",
clientProvider: getClient(AWS.KMS, {
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
}
})
})
const { plaintext, messageHeader } = await decrypt(keyring, encryptedData)
3. 如何将密钥授权给其他 AWS 账号
在创建 CMK 「定义密钥使用权限」步骤时,可以指定需要授权的第三方 AWS 账号,填入对方的 AWS 账号 ID 即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wYJ7jyJt-1605687483948)(https://user-gold-cdn.xitu.io/2020/6/12/172a749f580df155?w=1280&h=534&f=png&s=88173)]
被授权方,可以按照如下方式使用此 CMK:
- 创建一个具备全部 CKM 操作权限的 User
- 将用户继承以下 Policy:
a. Resource 为 CMK 的 arn
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Allow Use Of CMK In External Account",
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:ap-northeast-1:xxxxxxxxx:key/b09ea52c-7262-4e60-b775-xxxxxx"
}
]
}
- 借助 SDK 使用 CMK 进行数据加密、解密。
- 当授权方不再希望被授权方使用此 CMK 时,在密钥管理页面将对方账号 ID 移除。
4. 利用 Cloud Trail 查看密钥审计
创建一个 Trail:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YOeDcZDq-1605687483949)(https://user-gold-cdn.xitu.io/2020/6/12/172a74b7cb6395ac?w=1280&h=436&f=png&s=106625)]
默认情况下,接下来你的密钥所有使用情况都会出现在 Event History 中了:
如上图所示,你可以看到 Decrypt(解密) 记录,以及请求详情:
- userIdentity 就是被授权方。
5. 作为平台服务商,如何解决 KMS 服务商绑定问题
市面上常见的的 KMS 服务商大致有:
- AWS KMS
- 阿里云 KMS
- 腾讯云 KMS
作为平台服务商,我们最好能够提供给租户选择 KMS 服务商的权利,而各家 KMS 服务商的使用方法存在差异,所以我们就需要做一下代码层面的封装,比如可以设置 kms, aliyun, aws, tecent 四个模块,在 kms 模块暴露 encrypt 和 decrypt 方法,根据租户配置的 KMS 服务商调用对应实际方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KxE2IGnF-1605687483954)(https://user-gold-cdn.xitu.io/2020/6/12/172a74cba5b8151d?w=918&h=316&f=png&s=16004)]
6. 如何在尽量不改动已有代码前提下,对数据完成加密、解密
如果前期代码设计的不够好,会出现需要大量修改已有查询、插入业务代码情况,这种成本是很大的。所以我们需要思考一下其他的解决方案:
- 订阅数据库日志,也即 MySQL 的 binlog、 PostgreSQL的 Write-Ahead Logging (WAL)、mongodb 的 change streams。
- 利用数据库 ORM 的 hook,如 typeorm Entity Listeners and Subscribers 和 mongoose middlewares 等,在插入数据前加密、读取数据之后解密。
下面给一个 mongoose 的示例代码:
schema.post(['find', 'findOne'], async function (docs, next) {
if (!Array.isArray(docs)) {
docs = [docs];
}
for (let doc of docs) {
// 对数据进行解密
}
next()
})
schema.pre('findOneAndUpdate', async function (next) {
// 对数据进行加密
})
schema.post('findOneAndUpdate', async function (doc, next) {
// 对数据进行解密
})
总结
脱裤事件层出不穷,一个脱裤事件背后就是一家企业的灾难,我们都需要为用户的数据负责,尤其是平台服务商。在目前看来,KMS 不失为一个很好的选择。