vlambda博客
学习文章列表

HDFS中的透明加密

以下内容均翻译自官方文档.

概述

HDFS实现了透明的端到端加密。一旦配置完成,从特定HDFS目录读取和写入的数据将被透明地加密和解密,而不需要更改用户应用程序代码。这种加密也是端到端的,这意味着数据只能由客户机加密和解密。HDFS从不存储或访问未加密的数据或未加密的数据加密密钥。这满足了两种典型的加密要求:静止加密(即数据在持久介质上,如磁盘)和传输中加密(如数据在网络上传输时)。

背景

加密可以在传统数据管理软件/硬件栈的不同层进行。在给定的层上选择加密有不同的优点和缺点。

  • 应用程序级加密: 这是最安全、最灵活的方法。应用程序对加密的内容拥有最终的控制权,并能准确地反映用户的需求。然而,编写这样的应用程序是困难的。对于不支持加密的现有应用程序的客户来说,这也不是一个选项。

  • 数据库级加密: 在属性方面与应用程序级加密相似。大多数数据库供应商都提供某种形式的加密。但是,可能存在性能问题。一个例子是索引不能加密。

  • 文件系统级进行加密: 该选项提供了高性能、应用程序透明性,并且通常易于部署。但是,它不能建模一些应用程序级的策略。例如,多租户应用程序可能希望基于最终用户进行加密。对于存储在单个文件中的每个列,数据库可能需要不同的加密设置。

  • 磁盘级别加密: 易于部署和高性能,但也相当不灵活。仅能真正防止盗窃。

在这个堆栈中,hdfs级加密适用于数据库级加密和文件系统级加密。这有很多积极的影响。HDFS加密能够提供良好的性能,现有的Hadoop应用程序能够在加密的数据上透明地运行。在决策时,HDFS比传统文件系统具有更多的上下文。

hdfs级加密还可以防止在文件系统级及以下的攻击(所谓的“os级攻击”)。操作系统和磁盘只与加密的字节交互,因为数据已经被HDFS加密了。

用例

许多不同的政府、金融和监管实体都需要数据加密。例如,医疗保健行业有HIPAA法规,银行卡支付行业有PCI DSS法规,美国政府有FISMA法规。在HDFS中内置透明加密使得组织更容易遵守这些规定。

加密也可以在应用程序级执行,但是通过将其集成到HDFS中,现有的应用程序可以在不做更改的情况下对加密的数据进行操作。这种集成的架构意味着更强的加密文件语义和更好的与其他HDFS功能的协调。

架构

概述

对于透明加密,我们为HDFS引入了一个新的抽象:加密区。加密区是一种特殊的目录,其内容在写入时将透明地加密,在读取时将透明地解密。每个加密区域都与创建该区域时指定的单个加密区域密钥相关联。加密区域内的每个文件都有自己独特的数据加密密钥(DEK)。DEKs从不直接由HDFS处理。相反,HDFS只处理加密的数据加密密钥(EDEK)。客户端解密EDEK,然后使用后续的DEK来读写数据。HDFS datanode只看到加密的字节流。

加密的一个非常重要的用例是“打开它”,并确保整个文件系统中的所有文件都加密了。为了支持这种强有力的保证,同时又不失去在文件系统的不同部分使用不同加密区域密钥的灵活性,HDFS允许嵌套加密区域。在创建了一个加密区域之后(例如在根目录/上),用户可以用不同的密钥在其后代目录(例如/home/alice)上创建更多的加密区域。文件的EDEK将使用来自最近祖先加密区的加密区密钥生成。

需要一个新的集群服务来管理加密密钥:Hadoop密钥管理服务器(KMS)。在HDFS加密环境中,KMS执行三个基本职责:

  1. 提供对存储的加密区域密钥的访问

  2. 生成用于存储在NameNode上的新加密数据加密密钥

  3. 解密加密的数据加密密钥,供HDFS客户端使用

下面将更详细地描述KMs:

在加密区域内访问数据

在加密区中创建新文件时,NameNode要求KMS生成一个使用加密区密钥加密的新EDEK。然后,EDEK作为文件元数据的一部分持久地存储在NameNode上。

当读取加密区域内的文件时,NameNode向客户机提供文件的EDEK和用于加密EDEK的加密区域密钥版本。然后客户端要求KMS解密EDEK,这涉及到检查客户端是否拥有访问加密区密钥版本的权限。假设成功,客户端使用DEK来解密文件的内容。

上述读写路径的所有步骤都通过DFSClient、NameNode和KMS之间的交互自动完成。

对加密文件数据和元数据的访问是由普通的HDFS文件系统权限控制的。这意味着,如果HDFS受到威胁(例如,获得对HDFS超级用户帐户的未授权访问),恶意用户只能获得对密文和加密密钥的访问。但是,由于对加密区密钥的访问是由KMS和密钥存储库上的一组单独的权限控制的,因此这不会构成安全威胁。

密钥管理服务器,密钥提供程序,EDEKs

KMS是一个代理,它与代表HDFS守护进程和客户机的后备密钥存储进行接口。后备密钥存储和KMS都实现了Hadoop KeyProvider API。有关更多信息,请参阅KMS文档。

在KeyProvider API中,每个加密密钥都有一个惟一的密钥名。因为密钥可以滚动,所以一个密钥可以有多个密钥版本,其中每个密钥版本都有自己的密钥材料(加密和解密期间使用的实际秘密字节)。可以通过密钥名称(返回密钥的最新版本)或特定密钥版本来获取加密密钥。

KMS实现了额外的功能,允许创建和解密加密密钥(EEKs)。EEKs的创建和解密完全在KMS上进行。重要的是,请求创建或解密EEK的客户机从不处理EEK的加密密钥。为了创建一个新的EEK, KMS生成一个新的随机密钥,用指定的密钥加密它,并将EEK返回给客户机。要解密EEK, KMS检查用户是否有访问加密密钥的权限,使用它解密EEK,然后返回解密后的加密密钥。

在HDFS加密中,EEKs是加密的数据加密密钥(EDEKs),其中数据加密密钥(DEK)用于加密和解密文件数据。通常,密钥存储被配置为只允许终端用户访问用于加密DEKs的密钥。这意味着EDEKs可以被HDFS安全地存储和处理,因为HDFS用户不能访问未加密的加密密钥。

配置

一个必要的先决条件是KMS的一个实例,以及KMS的备份密钥存储。有关更多信息,请参阅KMS文档。

一旦设置了KMS并且正确配置了NameNode和HDFS客户机,管理员就可以使用hadoop key和HDFS crypto命令行工具来创建加密密钥并设置新的加密区域。可以使用distcp等工具将现有数据复制到新的加密区域中进行加密。

配置集群密钥提供程序

hadoop.security.key.provider.path

在与读取和写入加密区域时使用的加密密钥交互时要使用的KeyProvider。HDFS客户机将使用通过getServerDefaults从Namenode返回的 KeyProvider 路径。如果namenode不支持返回KeyProvider uri,那么将使用客户端的conf。

选择加密算法和编解码器

hadoop.security.crypto.codec.classes.EXAMPLECIPHERSUITE

一个给定的密码编解码器的前缀,包含一个逗号分隔的实现类列表,该列表为一个给定的密码编解码器(如EXAMPLECIPHERSUITE)。如果可用,将使用第一个实现,其他实现是备用的。

hadoop.security.crypto.codec.classes.aes.ctr.nopadding
默认值:org.apache.hadoop.crypto.OpensslAesCtrCryptoCodec, org.apache.hadoop.crypto.JceAesCtrCryptoCodec

用于AES/CTR/NoPadding的密码编解码器实现的逗号分隔列表。如果可用,将使用第一个实现,其他实现是备用的。

hadoop.security.crypto.cipher.suite
默认值:AES / CTR / NoPadding

密码编解码器的密码套件。

hadoop.security.crypto.jce.provider
默认值:None

在CryptoCodec中使用的JCE提供程序名称。

hadoop.security.crypto.buffer.size
默认值:8192

由CryptoInputStream和CryptoOutputStream使用的缓冲区大小。

Namenode configuration

dfs.namenode.list.encryption.zones.num.responses
默认值:100

列出加密区域时,批处理中返回的最大区域数。分批渐进地获取列表可以提高namenode的性能。

加密命令行界面

createZone

用法:[-createZone -keyName <keyName> -path <path>]
创建一个新的加密区域。

参数 说明
path 要创建的加密区域的路径。它必须是一个空目录。在此路径下提供了一个垃圾目录。
keyName 要用于加密区域的密钥的名称。不支持大写键名。

listZones

用法:[-listZones]
列出所有加密区域。需要超级用户权限。

provisionTrash

用法:[-provisionTrash -path <path>]
为加密区域提供一个垃圾目录。

参数 说明
path 到加密区根的路径。

getFileEncryptionInfo

用法:[-getFileEncryptionInfo -path <path>]
从文件中获取加密信息。这可以用来确定一个文件是否被加密,以及用于加密它的密钥名/密钥版本。

参数 说明
path 获取加密信息的文件路径。

reencryptZone

用法:[-reencryptZone <action> -path <zone>]
通过迭代加密区域,并调用KeyProvider的reencryptEncryptedKeys接口对加密区域进行重新加密,从而使用KeyProvider中最新版本的加密区域密钥对所有文件的EDEKs进行批处理重新加密。需要超级用户权限。

注意,由于快照的不可变性质,重新加密并不适用于快照。

参数 说明
action 要执行的重新加密操作。必须是-start 或 -cancel。
path 到加密区根的路径。

在HDFS中,重新加密是一个只使用NameNode的操作,因此可能会对NameNode施加大量负载。可以更改以下配置来控制NameNode上的压力,具体取决于对集群的可接受吞吐量影响。

参数 说明
dfs.namenode.reencrypt.batch.size 一批中要发送到KMS重新加密的EDEKs的数量。每个批处理在持有名称系统读/写锁时进行,在批之间进行节流。请参阅下面的配置。
dfs.namenode.reencrypt.throttle.limit.handler.ratio 在重新加密期间持有的读锁比率。1.0表示没有节流。0.5意味着重新加密最多能保持其总处理时间的50%。负值或0无效。
dfs.namenode.reencrypt.throttle.limit.updater.ratio 重新加密期间要持有的写锁比率。1.0表示没有节流。0.5意味着重新加密最多可以保留写入锁的总处理时间的50%。负值或0无效。

listReencryptionStatus

用法:[-listReencryptionStatus]
列出所有加密区域的重新加密信息。需要超级用户权限。

示例

这些指令假设您是作为正常用户或适当的HDFS超级用户运行。根据您的环境需要使用sudo。

#作为普通用户,创建一个新的加密密钥
hadoop key create mykey

#作为超级用户,创建一个新的空目录,并将其作为加密区域
hadoop fs -mkdir /zone
hdfs crypto -createZone -keyName mykey -path /zone

#把它变更给普通用户
hadoop fs -chown myuser:myuser /zone


#作为普通用户,推送一个文件,然后读出它
hadoop fs -put helloWorld /zone
hadoop fs -cat /zone/helloWorld

#作为普通用户,从文件中获取加密信息
hdfs crypto -getFileEncryptionInfo -path /zone/helloWorld

# console output: {cipherSuite: {name: AES/CTR/NoPadding, 
algorithmBlockSize: 16}, cryptoProtocolVersion: 
CryptoProtocolVersion{description='Encryption zones', version=1, 
unknownValue=null}, edek: 2010d301afbd43b58f10737ce4e93b39, iv: 
ade2293db2bab1a2e337f91361304cb3, keyName: mykey, ezKeyVersionName: 
mykey@0}

Distcp注意事项

以超级用户的身份运行

distcp的一个常见用途是在集群之间复制数据,以便进行备份和灾难恢复。这通常由集群管理员执行,他是一个HDFS超级用户。

为了在使用HDFS加密时启用相同的工作流,我们引入了一个新的虚拟路径前缀/.reserved/raw/,它允许超级用户直接访问文件系统中的底层块数据。这允许超级用户不需要访问加密密钥就可以distcp数据,还可以避免解密和重新加密数据的开销。它还意味着源数据和目标数据按字节计算是相同的,如果使用新的EDEK对数据进行重新加密,则不存在这种情况。

当使用/.reserved/raw来加密数据时,保留带有-px标志的扩展属性非常重要。这是因为加密的文件属性(例如EDEK)是通过/.reserved/raw中的扩展属性公开的,并且必须保留这些属性才能解密文件。这意味着,如果在加密区根上或根之上启动distcp,它将在目标上自动创建一个加密区(如果它还不存在的话)。但是,仍然建议管理员首先在目标集群上创建相同的加密区域,以避免任何潜在的错误。

复制到加密位置

默认情况下,distcp比较文件系统提供的校验和,以验证数据是否成功复制到目的地。当从未加密的位置或一个加密的位置复制到另一个加密的位置时,由于底层块数据是不同的导致文件系统校验和将不匹配,因为使用了一个新的EDEK被用在目的地加密。在这种情况下,指定-skipcrccheck和-update distcp标志,以避免验证校验和。

重命名和销毁注意事项

HDFS限制跨加密区域边界的文件和目录重命名。这包括重命名一个加密文件 "/目录名" 到一个未加密的目录(例如: hdfs dfs mv /zone/encryptedFile /home/bob),重命名一个未加密的文件或目录到一个加密区(例如: hdfs dfs mv /home/bob/unEncryptedFile /zone),和重命名两个不同的加密区(例如:hdfs dfs mv /home/alice/zone1/foo /home/alice/zone2)。

在这些示例中,/zone、/home/alice/zone1和/home/alice/zone2是加密区域,而/home/bob不是。仅当源路径和目标路径在同一加密区域中,或者两个路径都未加密(不在任何加密区域中)时,才允许重命名。

这一限制增强了安全性并显著简化了系统管理。使用加密区密钥对加密区下的所有文件EDEKs进行加密。因此,如果加密区密钥被破坏,识别所有易受攻击的文件并重新加密它们是很重要的。如果最初在加密区创建的文件可以重命名为文件系统中的任意位置,那么这从根本上是困难的。

为了遵守上述规则,每个加密区域在“zone directory”下都有自己的 .trash 目录。例如,在执行命令 hdfs dfs rm /zone/encryptedFile 后,encryptedFile将被移动到/zone/.Trash,而不是用户主目录下的.Trash目录。当整个加密区域被删除时,“zone directory”将移动到用户主目录下的. trash目录。

如果加密区是根目录(例如 /), 垃圾路径的根目录是/.Trash,而不是在用户的主目录下的.Trash目录,重命名子目录的行为或个子文件在根目录将保持一致的行为一般加密区,如本节上面提到的/zone。

Hadoop 2.8.0之前的crypto命令不会自动提供. trash目录。如果在Hadoop 2.8.0之前创建了一个加密区域,然后集群升级到Hadoop 2.8.0或更高,那么可以使用-provisionTrash选项(例如,hdfs crypto -provisionTrash -path /zone)来配置trash目录。

攻击向量

硬件访问权限漏洞

这些攻击假定攻击者已经获得了对集群机器的物理访问权限,例如datanode和namenode。

  1. 访问交换包含数据加密密钥的进程的文件。

    • 这本身不会公开明文,因为它还需要访问加密的块文件。

    • 这可以通过禁用交换、使用加密交换或使用 mlock 来缓解,以防止密钥被换出。

  1. 访问加密的块文件。

    • 这本身不公开明文,因为它还需要访问 DEK。

根访问权限漏洞

这些漏洞假定攻击者已经获得了对集群计算机的root shell访问,即datanode和namenode。许多这些漏洞不能在 HDFS 中解决,因为恶意root用户可以访问持有加密密钥和明文的进程的内存状态。对于这些漏洞,唯一的缓解技术是加强限制和监视root shell 访问。

  1. 访问加密的块文件。

    • 这本身不会公开明文,因为它还需要访问加密密钥。

  2. 转储客户端进程内存以获取 DEK、委派令牌、明文。

    • 无缓解。

  3. 记录网络流量以嗅探加密密钥和传输中的加密数据。

    • 就其本身而言,不足以读取明文没有EDEK加密密钥。

  4. 转储数据列表进程的内存以获取加密的块数据。

    • 就其本身而言,不足以读取没有 DEK 的明文。

  5. 转储名称名称进程内存以获取加密数据加密密钥。

    • 就其本身而言,不足以读取明文没有EDEK的加密密钥和加密的块文件。

HDFS 管理员漏洞

这些漏洞假定攻击者已入侵 HDFS,但没有root 或 hdfs 用户 shell 访问权限。

  1. 访问加密的块文件

    • 就其本身而言,不足以读取没有 EDEK 和 EDEK 加密密钥的明文。

  2. 访问加密区域和加密文件元数据(包括加密数据加密密钥), 如通过 -fetchImage。

    • 就其本身而言,不足以阅读没有 EDEK 加密密钥的明文。

恶意用户漏洞利用

恶意用户可以收集他们有权访问的文件的密钥,并在以后使用它们来解密这些文件的加密数据。由于用户有权访问这些文件,因此他们已经有权访问文件内容。这可以通过定期密钥滚动策略来缓解。在密钥滚动后通常需要重新加密区域命令,以确保现有文件上的 EDEK 使用新版本密钥。

下面列出了完成密钥滚动和重新加密的手动步骤。这些说明假定了您作为密钥管理员或 HDFS 超级用户运行。

# 作为密钥管理员,将密钥滚动到新版本
hadoop key roll exposedKey


# 作为超级用户,重新加密加密区域。可能先列出区域。
hdfs crypto -listZones
hdfs crypto -reencryptZone -start -path /zone

# 作为超级用户,定期检查重新加密的状态
hdfs crypto -listReencryptionStatus


# 作为超级用户,从文件获取加密信息,并仔细检查其加密密钥版本
hdfs crypto -getFileEncryptionInfo -path /zone/helloWorld

# console output: {cipherSuite: {name: AES/CTR/NoPadding, 
algorithmBlockSize: 16}, cryptoProtocolVersion: 
CryptoProtocolVersion{description='Encryption zones', version=2, 
unknownValue=null}, edek: 2010d301afbd43b58f10737ce4e93b39, iv: 
ade2293db2bab1a2e337f91361304cb3, keyName: exposedKey, ezKeyVersionName: 
exposedKey@1}