借助 KMS (Hadoop Key Management Server) 实现 HDFS 数据加密

原生 KMS 模型

概览

适用场景

  1. 将 DataNode 上的数据 block 加密存放,这样即使恶意用户通过某种方式绕过了权限控制,或直接访问了 DataNode,获取了其它用户的数据 block,也看不到这些 block 的真实内容。
  2. 对 HDFS 的读写性能会有一定的降低,但应该不会太严重(未测试),HDFS 优先使用 native 的 libcrypto.so 完成加解密(默认算法 AES-CTR,支持128位 AES 加密),新版本的 libcrypto.so 都支持 AES-NI 指令集,在 CPU 层面有相关的 AES 指令,为了使用 AES-NI,要求在所有的客户端上(包括所有的 map/reduce 客户端),都安装最新的 libcrypto.so

几个关键点

  1. HDFS client 向 DataNode 写入/读取数据时,数据的加密/解密过程都在 client 端的 JVM 中完成,HDFS 集群本身不感知这个过程。
  2. HDFS client 如果需要开启加密传输,只需要在 core-site.xml 中配置几个选项即可,不需要修改现有的业务代码,整个加解密功能是作为插件的形式存在的。
  3. KMS 集群独立于 HDFS 集群之外,可以为多个 HDFS 集群所共享。
  4. KMS 集群作为 HDFS 之外的独立系统,它的认证机制和 HDFS 是分开的。

几个关键概念

  1. EZ(Encrpytion Zone)
    加密区域,HDFS 可以将一个现有的空目录做成 EZ,在该目录的 xattr 中会保存一些相关的加密信息(例如该 EZ 使用的 EZ key 名字、使用的加密算法、使用的协议版本等等),该EZ 下的所有文件在写入时都将被自动加密,读取时都将被自动解密,如前所述,加解密过程都在 client 端的 jvm 中进行。

  2. EZ Key(Encryption Zone key)
    每个 Encrytion Zone 都关联着一个 root key,这是在该 EZ 创建时,由 HDFS 管理员指定关联起来的,EZ Key 由 KMS 产生、存储和管理,保存在 KMS 的后备存储中。

  3. DEK(Data Encrpytion Key)
    数据加密 key,EZ 中的每个文件都有自己特定的 DEK,client 读写该文件时,需要用该文件的 DEK 进行加解密操作。

  4. EDEK(Encrypted Data Encryption Key)
    文件的 DEK 过于敏感,不会保存在任何地方,而是用他所从属的 EZ 的 EZ Key 进行加密,生成一个 EDEK,然后将此 EDEK 保存在该文件的扩展属性中,一个 EDEK 的内容主要包括该 EDEK 加密时使用的 EZ Key 名字,EZ Key 版本(同一个 EZ key 可以有多个版本)、加密后的密文、加密使用的初始向量等等,后面需要解密 EDEK 时,需要将这些信息发送给 KMS Server 进行解密处理。

    • client 创建新文件时,NameNode 会调用 KMS 接口,为该文件产生一个唯一的 EDEK,并将其保存在该文件的扩展属性中。
    • client 读写文件时,从 NameNode 拿到的都是该文件的 EDEK,client 需要调用 KMS 接口,将此 EDEK 解密,拿到真正的 DEK,然后使用此 DEK 进行加解密操作。

这样设计的目的,是为了保证在 HDFS 中,不保存任何明文密钥信息,这样即使恶意用户攻破了 HDFS,也只能拿到加密后的文件和加密后的 DEK,依然读不到文件的原始内容。

  1. 几个 key 的关系如下


关键操作流程分析

  1. 一个典型的操作流程如下,其中创建 EZ key、创建 EZ 这两个操作需要管理员使用 hadoop shell 手动完成:
    # 创建 EZ Key
    hadoop key create mykey

    # 创建一个空目录,把这个空目录做成 EZ
    hdfs dfs -mkdir /zone
    hdfs crypto -createZone -keyName mykey -path /zone

    # 向 EZ 中读写文件
    hadoop fs -put helloWorld /zone
    hadoop fs -cat /zone/helloWorld

  2. 生成 EZ Key

    1. hadoop shell 命令如下:
      hadoop key create mykey

    2. 交互流程

  1. 创建 EZ,使用上一步生成的 EZ Key,将一个 HDFS 中现有的空目录做成 Encryption Zone
    1. hadoop shell 命令如下:
      hdfs crypto -createZone -keyName mykey -path /zone

    2. 交互流程


  1. 在 EZ 中 创建一个文件
    1. 使用正常的 FileSystem API 就可以,和使用普通的 HDFS 没有区别。

    2. 交互流程


  1. 写 EZ 中的文件
    1. 使用正常的 FileSystem API 就可以,和使用普通的 HDFS 没有区别。
    2. 交互流程


  1. 读 EZ 中的文件
    1. 使用正常的 FileSystem API 就可以,和使用普通的 HDFS 没有区别。
    2. 交互流程


原生 KMS存在的几个问题

EZ key 共享存储问题

多个 KMS Server 的 backing keystore 并不共享,原生 KMS Server 使用的是 JKS(Java KeyStore) 存储机制,EZ Key 都保存在本地文件系统上的 keyStore 文件中,这样的话,由于 keyStore 文件无法在多个 KMS Server 之间共享,导致挂掉任何一个 Server 后,其它剩余的 KMS Server 都可能读取不到已有的 EZ Key,导致 EZ key 丢失,进而使得加密文件打不开。

原生的这种集群组织方式,更像是一种负载均衡策略,从这种情况下 KMS 客户端的名字也能看的出来:LoadBalancingKMSClientProvider。在 Hadoop 的官方文档中,也强调了 JKS 这种存储机制不能用在生产环境中。

这个地方需要实现一个新的分布式的、共享存储的 backing keyStore 方式,可以使用 zookeeper 或者 ratis 等等。Apache Zookeeper 和 Apache Ratis 的比较:

Apache zookeeper Apache ratis
协议 ZAB(Zookeeper Atomic Broadcast protocol) Raft
成熟度 项目成熟,使用广泛 项目较新,应用不多,目前 OZone 的多副本存储使用了 Ratis
一致性保证 顺序一致性,单个 KMS Server 可能读到较老数据,但可借助 watch 机制和 sync 原语实现强一致性 强一致性
缓存友好度 借助 watch 机制和 sync 原语,可以实现多个 KMS Server 的缓存一致性 没有主动通知机制,无法保证多个 KMS Server 的缓存一致

目前的话,还是建议:

  1. 选择 Zookeeper。
  2. 使用缓存提高性能,同时借助 Zookeeper 的 watch 机制和 sync 原语,保证多个 KMS Server 的缓存一致性。

Move 问题

在普通的 HDFS 目录之间,文件的 move 并不涉及到数据的移动,仅仅是元数据的修改,速度非常快,但在 EZ 和非 EZ 之间、不同 EZ 之间,并不支持 move 操作,只支持 copy 操作,相对较慢,具体如下图所示:


这其中的原因是,HDFS client 无论 create、append 还是 open 一个文件时,如果发现这个文件是一个加密文件,那就需要特殊处理,而判断文件是否加密及随后正确的加解密处理(写入需要加密、读取需要解密),需要来自两方面的信息:

  1. 来自该文件本身的 xattr 的信息,即该文件的 EDEK。
  2. 来自该文件所处的 EZ 根目录的 xattr 信息,包括加解密算法、协议版本等等。

既然如此,那么:

  1. 对整个 EZ(即 EZ 根目录)做 move 操作是可以的,这可以保证上面所说的两部分信息都保存下来。
  2. 将文件从非 EZ move 到 EZ 中,由于该文件本身没有 EDEK,最终它会被当成普通文件读写,逻辑上虽然行的通,但这违背了 EZ 本身的语义,因此这种操作不被允许。
  3. 将文件从 EZ move 到非 EZ 中,由于缺失了 EZ 根目录的 xattr,也会导致该文件被当做普通文件对待,导致无法读取真实内容,后续写入的内容也缺失了加密过程使得文件内容混乱,因此这种操作也不被允许。
  4. 在不同 EZ 之间 move 文件也不可以,出于一些安全考虑,HDFS 直接禁止了这种操作。

回收站问题

如前所述,EZ 和 非 EZ 之间的 move 操作是禁止的,换句话说 Trash 功能对 EZ 中的文件是不可用的,而在 TDWHadoop 中,数据保护功能默认打开(配置项 hadoop.tdw.protect.data.enable 默认为 true),客户端的 delete 操作实际上最终是一个 move 操作,既然 move 被禁止,那么最终表现为EZ 中的文件无法删除。

这个问题在 Hadoop 2.8.0 中得到解决,解决思路如下:


  1. 为每一个 EZ 单独配置一个 Trash 目录,例如,假设 EZ 为 /zone,则为其配置的 Trash 为 /zone/.Trash,用户 root 对应的回收站就是 /zone/.Trash/root/,其它用户类似。
  2. 启用 Trash 功能后,如果整个 EZ 被删除,那整个 EZ 目录将被 move 到 /user/$USER/.Trash/ 下,此时和正常目录的删除没有区别。
  3. 如果 EZ 中的文件被删除,则它将被移到 EZ 自己的 Trash 目录下,例如 root 用户删除 /zone/file.txt 将会把 /zone/file.txt move 为 /zone/.Trash/root/Current/zone/file.txt。
  4. 无论是 EZ 自己的 Trash 目录,还是 /user/$USER/ 下面的 Trash 目录,都遵循 HDFS Trash 的 checkpoint 规则,过期的文件将被自动清理。

你可能感兴趣的:(借助 KMS (Hadoop Key Management Server) 实现 HDFS 数据加密)