Swift/server-side-enc
目录
服务器端加密
摘要
总体方案是在 swift 代理中间件中,在 PUT 期间对对象数据进行加密和签名,并在 GET 期间检查签名 + 解密。目标是创建两个域 - 用户域,位于客户端和中间件之间,数据在此处解密;以及系统域,位于中间件和静态数据之间(在设备上),数据在此处加密。
设计目标包括:(1)在不更改现有 swift 行为和 API 的情况下,尽可能地扩展 swift;(2)支持加密来自未更改的客户端的数据
顶层设计
这里描述的设计在代理处进行一次加密。为了启用加密,管理员需要将加密中间件添加到管道中。通过以下方式支持非全新安装:
- 容器可以创建为加密容器或非加密容器,并且其加密位在其生命周期内不会更改。
- 如果容器标记为加密,则 PUT 对象将导致中间件加密和签名对象数据。
- 如果删除中间件,密钥将不会暴露。
- 每个对象将拥有自己的密钥来加密数据。
- 对象密钥 'AUTH_myaccount/mycontainer/myobject' 将存储为系统元数据 x-object-sysmeta-key-XXX,其中 XXX 是 base64(hash('AUTH_myaccount/mycontainer')),值为 enc(containerkey, objkey) - 即使用容器密钥加密的 objkey。这将允许 COPY 操作而无需解密对象,如下所述。使用的哈希将是 Swift 系统哈希(默认情况下为 MD5)。
- 非加密对象将没有 x-object-sysmeta-key-* 元数据。
- 新的 objkey 在 PUT 对象期间随机选择
- 新的 containerkey、accountkey 在创建容器/帐户时分别随机选择。
- 容器密钥存储为系统元数据 x-conaitner-sysmeta-key,值为 enc(accountkey, containerkey) - 即使用帐户密钥加密的 containerkey。
- 解密的容器密钥与容器元数据一起缓存到 memcache 中(可能需要增强 Swift 核心或在 memcache 中维护单独的缓存空间)
- 帐户密钥存储为系统元数据 x-account-sysmeta-key,值为 enc(masterkey, accountkey) - 即使用主密钥加密的 accountkey(主密钥由密钥管理器(例如 Barbican)按帐户存储)。
- 解密的帐户密钥可以选择性地与帐户元数据一起缓存到 memcache 中(需要增强 Swift 核心)
- 清单永远不会加密
- 当对象加密时,它被分成块,每个块被加密然后签名。当对象解密时,在解密之前会验证每个块的签名。
- 范围查询通过仅解密包含相关偏移量的块并仅发送请求的数据来支持。
设计细节
Etag 问题
当前的 Swift 行为
- 在 PUT 对象期间,在对象服务器上,当对象服务器将块写入 DiskFile 时,它会计算块的 MD5 校验和。
- 如果代理发送了 etag 标头,对象服务器会将计算出的 etag 与代理发送的 etag 进行比较。如果 etag 不匹配,对象服务器将返回 HTTPUnprocessableEntity...在这种情况下,etag 元数据将不会存储为对象的元数据,这可能会导致对象在未来的某个时间通过审计器被丢弃。
- 否则:etag 存储在对象的 Etag 元数据键下。
我们在这里将存储对象的 MD5 称为“de-etag”(解密 etag),将加密对象的 MD5 称为“en-etag”。en-etag 存储在对象的 etag 字段中,允许审计员继续以不变的方式工作。这是通过删除客户端提供的 etag(如果提供)来实现的,从而允许对象服务器计算 en-etag 字段并存储它。
在 PUT 期间,中间件的任务是
- 在将块发送到对象服务器时计算 de-etag
- 如果客户端提供了 etag,将 etag 与计算出的 de-etag 进行比较,如果不匹配,则向客户端响应 HTTPUnprocessableEntity(?)。这将导致系统中没有 x-object-sysmeta-etag 的加密对象,这应该在 GET/HEAD 期间被忽略。应添加最终一致性以解决此问题(更新审计器以丢弃没有 x-object-sysmeta-etag 的加密对象)
- 否则:执行 POST 并将 de-etag 存储在 x-object-sysmeta-etag 下。如果代理在 PUT 之后但在 POST 成功之前失败,则上述讨论的最终一致性将解决该问题。
在 HEAD 期间,中间件会将 x-object-sysmeta-etag 作为 etag 发送(或者如果缺少 x-object-sysmeta-etag,则指示对象不存在)。
Etag 的另一个问题是,它们也存储并作为容器列表的一部分报告。在容器列表期间提供的 etag 应该是 de-etag。为了实现这一点,对象服务器需要将对象中的 'x-object-sysmeta-etag' 对象元数据的值传输到容器服务器,而不是对象中的 'Etag' 对象元数据(请注意,de-etag 在对象服务器上的 POST 期间可用,在 PUT 期间不可用)。
大小问题
与上述 Etag 问题类似,对象大小也会在对象存储时发生变化。
我们在这里将存储对象的尺寸称为“de-size”(解密大小),将加密对象的尺寸称为“en-size”。en-size 存储在对象的 size 字段中,允许审计员继续以不变的方式工作。
在 PUT 期间,中间件的任务是
- 计算加密和签名对象的大小。
- 然后执行 POST 并将 de-size 存储在 x-object-sysmeta-size 下。如果代理在 PUT 之后但在 POST 成功之前失败,则上述“etag 问题”子部分中讨论的最终一致性将解决该问题。
在 HEAD 期间,中间件会将 x-object-sysmeta-size 作为 etag 发送(或者如果缺少 x-object-sysmeta-etag,则指示对象不存在)。
对象大小的另一个问题是,它们也存储并作为容器列表的一部分报告。在容器列表期间提供的尺寸应该是 de-size。为了实现这一点,对象服务器需要将对象中的 'x-object-sysmeta-size' 对象元数据的值传输到容器服务器,而不是对象中的 'Size' 对象元数据(请注意,此 de-size 在对象服务器上的 POST 期间可用,在 PUT 期间不可用)。
密钥
如上所述,每个对象数据和用户元数据都使用自己的加密密钥(此处称为 objkey)进行加密。objkey 在对象 PUT 期间随机创建,并以加密形式存储在对象的系统元数据下 x-object-sysmeta-key-XXX 中,其中 XXX 是 base64(hash('AUTH_myaccount/mycontainer'))。选择这种不寻常的密钥名称结构是为了支持 Swift 中的 COPY 操作。在复制操作期间,对象可能在帐户和容器之间移动,因此需要重新加密 objkey。当对象正在复制时,它需要通过两个单独的路径访问,位于两个单独的主密钥下等。同一个对象可以被重复复制。为了克服可能由在对象复制后需要更新密钥或向 Swift 引入更改而导致的问题,建议使用上述不寻常的密钥名称,从而允许每个路径维护对象系统元数据中的 objkey 的单独副本。有关更多详细信息,请参阅 COPY 下方。
请注意,objkey 在 COPY 操作或主密钥更改期间永远不会更改,并且对象永远不会被重新加密。objkey 以加密形式存储为对象 sysmeta 密钥 x-object-sysmeta-key-XXX 的值。objkey 使用容器的密钥加密。解密的容器密钥将使用 memcache 进行缓存,以便能够以最小的开销快速访问同一容器中的多个对象。
容器密钥在创建容器时随机选择,并存储在容器系统元数据字段 x-container-sysmeta-key 中,在被帐户密钥加密之后。解密的帐户密钥将使用 memcache 进行缓存,以便能够以最小的开销快速访问同一帐户中的多个容器。容器密钥在主密钥更改期间不会重新加密。
帐户密钥在创建帐户时随机选择,并存储在帐户系统元数据字段 x-account-sysmeta-key 中,在被帐户所有者的主密钥加密之后。从密钥管理器检索主密钥以解密帐户密钥,并且从不缓存。帐户密钥在主密钥更改期间不会重新加密。
帐户主密钥由 Barbican 或其他密钥管理器存储。
签名
对象存储为一系列块,每个块被加密然后签名。在解密对象的块之前,会验证其签名。
Blob 加密
最初的计划是使用 M2Crypto 库进行加密操作。但是,M2Crypto 具有 BSD 许可证。pyCrypto 似乎更合适,并且在 Keystone 中使用。
是否使库可插拔以及默认使用哪个库尚未确定。
API 实现
在 PUT 帐户或容器期间,中间件
- 选择一个随机密钥并设置 sysmeta 以存储该密钥
在 PUT 对象期间,中间件
- 查看容器的 sysmeta:如果缺少 x-container-sysmeta-enc-enabled 或为 False,则执行常规 swift 核心 PUT;否则
- 计算 en-size 并相应地更新对象标头大小
- 删除用户提供的 etag 标头(如果提供)
- 选择一个随机密钥
- 使用容器密钥加密密钥,并将加密的 objkey 设置为 x-object-sysmeta-key-XXX 的值;
- 对于每个预先确定大小的块的数据(或如果为最后一个块,则更小):(1)计算 de-etag,(2)使用 objkey 加密数据 (3) 签名块 (4) 将签名然后加密的块发送到 swift 核心;
- 在最后一个块之后,如果 swift 核心成功,则将 de-etag 与客户端提供的 etag 进行比较,如果不匹配,则使请求失败。
- 否则,使用 POST 将 de-etag 与 x-object-sysmeta-key-etag 和 de-size 与 x-object-sysmeta-key-size 更新。
- 对象服务器使用 x-object-sysmeta-key-etag 和 x-object-sysmeta-key-size 中的数据更新容器列表
在 HEAD 对象期间,中间件
- HEAD 对象,解密 objkey
- 将 etag 替换为 x-object-sysmeta-etag 中的值
- 将大小替换为 x-object-sysmeta-size 中的值
在 GET 对象期间,中间件
- GET 对象,解密 objkey
- 将 etag 替换为 x-object-sysmeta-etag 中的值
- 将大小替换为 x-object-sysmeta-size 中的值
- 每个块:(1)检查块的签名 - 如果失败,则使响应失败。(2) 解密块并将解密的数据发送给客户端
在 POST 对象期间,中间件
- 执行常规 Swift 核心 POST
在 COPY 期间,中间件
- 基于源容器密钥解密 objkey
- 使用目标容器密钥加密 objkey
- 使用 POST 将新的加密 objkey 存储在适当的 x-object-sysmeta-key-XXX 下(使用目标的容器的新路径)
- 执行常规 Swift 核心 COPY
未解决的问题
- 每个 PUT REST 请求都由中间件转换为中间件中的两个单独的内部 REST 调用:PUT + POST 以更新 de-etag 和一致性签名。如果群集配置为启用 POST 上的复制,这将导致加密对象的每个副本。如果加密了许多对象,这将影响群集性能,因为它会将加密 PUT 操作期间使用的 I/O 加倍。短期解决方案:如果系统需要加密其许多对象,请不要在同一系统上同时使用加密和 POST 上的复制。长期解决方案:通过修复 Swift 来消除对 POST 复制的需求。
未来
- 对象用户元数据键和值使用 objkey 进行 base64 加密,并作为用户元数据发送:例如,“x-object-meta-confidentiality: Top-Secret”可以变为“x-object-meta-Y29uZmlkZW50aWFsaXR5: VG9wLVNlY3JldA==”(在此示例中,我使用了 null 编码)。
- 考虑还需要加密/MAC 什么 - 例如,我们是否应该加密 x-object-sysmeta-etag?
- 加密容器名称
- 加密/MAC 容器上的用户 MD