HDFS数据加密空间--Encryption zone

前言

之前写了许多关于数据迁移的文章,也衍生的介绍了很多HDFS中相关的工具和特性,比如DistCp,ViewFileSystem等等.但是今天本文所要讲的主题转移到了另外一个领域数据安全.数据安全一直是用户非常重视的一点,所以对于数据管理者,务必要做到以下原则:

数据不丢失,不损坏,数据内容不能被非法查阅.

本文所主要描述的方面就是上面原则中最后一点,保证数据不被非法查阅.在HDFS中,就有专门的功能来做这样的事情,Encryption zone,数据加密空间,

Encryption zone综述



HDFS Encryption zone加密空间是一种end-to-end(端到端)的加密模式.其中的加/解密过程对于客户端来说是完全透明的.数据在客户端读操作的时候被解密,当数据被客户端写的时候被加密,所以HDFS本身并不是一个主要的参与者,形象的说,在HDFS中,你看到的只是一堆加密的数据流.

Encryption zone原理介绍



了解HDFS数据加密空间的原理对我们使用Encryption zone有很大的帮助.Encryption zone是HDFS中的一个抽象概念,它表示在此空间的内容在写的时候会被透明的加密,同时在读的时候,被透明的解密.这就是核心所在.具体到细小的细节.

  • 1.每个encryption zone 会与每个encryption zone key相关联,而这个key就是会在创建encryption zone的时候同时被指定.
  • 2.每个encryption zone中的文件会有其唯一的data encryption key数据加密key,简称就是DEK.
  • 3.DEK不会被HDFS直接处理,取而代之的是,HDFS只处理经过加密的DEK, 就是encrypted data encryption key,缩写就是EDEK.
  • 4.客户端询问KMS服务去解密EDEK,然后利用解密后得到的DEK去读/写数据.

在第四步骤有一个很重要的过程:

在客户端向KMS服务请求时候,会有相关权限验证,不符合要求的客户端将不会得到解密好的DEK.而且KMS的权限验证是独立于HDFS的,是自身的一套权限验证.

下面是对应的原理展示图:

Key Provider可以理解为是一个key store的保存库,其中KMS是其中的一个实现.

Encryption zone源码实现



这个小节,我们将从源码的层面对上述原理做跟踪分析.这里可以分为2大方向,1个是创建文件,并且写文件数据;2.读文件数据内容.

Encryption zone下的写文件



首先客户端发起createFile请求,到了NameNode这边,会调用startFile方法,里面就会有DEK,EDEK的生成

  private HdfsFileStatus startFileInt(final String src,
      PermissionStatus permissions, String holder, String clientMachine,
      EnumSet<CreateFlag> flag, boolean createParent, short replication,
      long blockSize, CryptoProtocolVersion[] supportedVersions,
      boolean logRetryCache)
      throws IOException {
    ...
    FSDirWriteFileOp.EncryptionKeyInfo ezInfo = null;
    // 判断key provider是否为空
    if (provider != null) {
      readLock();
      try {
        checkOperation(OperationCategory.READ);
        // 不为空,就生成EncryptionKey info.
        ezInfo = FSDirWriteFileOp
            .getEncryptionKeyInfo(this, pc, src, supportedVersions);
      } finally {
        readUnlock();
      }

      // Generate EDEK if necessary while not holding the lock
      if (ezInfo != null) {
        // 然后根据ezInfo的key名称生成EDEK信息在ezInfo中
        ezInfo.edek = FSDirEncryptionZoneOp
            .generateEncryptedDataEncryptionKey(dir, ezInfo.ezKeyName);
      }
      EncryptionFaultInjector.getInstance().startFileAfterGenerateKey();
    }

    ...
      try {
        // 继续调用startFile方法
        stat = FSDirWriteFileOp.startFile(this, pc, src, permissions, holder,
                                          clientMachine, flag, createParent,
                                          replication, blockSize, ezInfo,
                                          toRemoveBlocks, logRetryCache);
        ...

继续方法的调用

  static HdfsFileStatus startFile(
      FSNamesystem fsn, FSPermissionChecker pc, String src,
      PermissionStatus permissions, String holder, String clientMachine,
      EnumSet<CreateFlag> flag, boolean createParent,
      short replication, long blockSize,
      EncryptionKeyInfo ezInfo, INode.BlocksMapUpdateInfo toRemoveBlocks,
      boolean logRetryEntry)
      throws IOException {
    assert fsn.hasWriteLock();
    ...

    CipherSuite suite = null;
    CryptoProtocolVersion version = null;
    KeyProviderCryptoExtension.EncryptedKeyVersion edek = null;

    // 取出ezInfo中的关键信息
    if (ezInfo != null) {
      edek = ezInfo.edek;
      suite = ezInfo.suite;
      version = ezInfo.protocolVersion;
    }

    ...

    FileEncryptionInfo feInfo = null;

    final EncryptionZone zone = FSDirEncryptionZoneOp.getEZForPath(fsd, iip);
    if (zone != null) {
      // The path is now within an EZ, but we‘re missing encryption parameters
      if (suite == null || edek == null) {
        throw new RetryStartFileException();
      }
      // Path is within an EZ and we have provided encryption parameters.
      // Make sure that the generated EDEK matches the settings of the EZ.
      final String ezKeyName = zone.getKeyName();
      if (!ezKeyName.equals(edek.getEncryptionKeyName())) {
        throw new RetryStartFileException();
      }
      // 传入到FileEncryptionInfo中,feInfo将会被设置到INode文件中
      feInfo = new FileEncryptionInfo(suite, version,
          edek.getEncryptedKeyVersion().getMaterial(),
          edek.getEncryptedKeyIv(),
          ezKeyName, edek.getEncryptionKeyVersionName());
    }
    ...

OK,完成了这些操作之后,将会返回一个HDFSFileStatus对象,此对象将会被DFSOutputstream利用.下面就是客户端的解密DEDK,并加密数据的过程了.

  public HdfsDataOutputStream createWrappedOutputStream(DFSOutputStream dfsos,
      FileSystem.Statistics statistics, long startPos) throws IOException {
    // 取出文件中的加密信息
    final FileEncryptionInfo feInfo = dfsos.getFileEncryptionInfo();
    if (feInfo != null) {
      // 文件是被加密的,需要包装数据流为加密流
      // File is encrypted, wrap the stream in a crypto stream.
      // Currently only one version, so no special logic based on the version #
      getCryptoProtocolVersion(feInfo);
      final CryptoCodec codec = getCryptoCodec(conf, feInfo);
      // 解密feInfo中的EDEK的信息,其中会向KerProvider进行请求
      KeyVersion decrypted = decryptEncryptedDataEncryptionKey(feInfo);
      // 然后解密后的信息作为参数,构造出加密输出流
      final CryptoOutputStream cryptoOut =
          new CryptoOutputStream(dfsos, codec,
              decrypted.getMaterial(), feInfo.getIV(), startPos);
      return new HdfsDataOutputStream(cryptoOut, statistics, startPos);
    } else {
      // No FileEncryptionInfo present so no encryption.
      return new HdfsDataOutputStream(dfsos, statistics, startPos);
    }
  }

我们可以继续完decryptEncryptedDataEncryptionKey方法,验证是否有向KeyProvider方法请求服务.

  private KeyVersion decryptEncryptedDataEncryptionKey(FileEncryptionInfo
      feInfo) throws IOException {
    try (TraceScope ignored = tracer.newScope("decryptEDEK")) {
      // 获取keyProvider服务实例
      KeyProvider provider = getKeyProvider();
      if (provider == null) {
        throw new IOException("No KeyProvider is configured, cannot access" +
            " an encrypted file");
      }
      // 获取加密的key version
      EncryptedKeyVersion ekv = EncryptedKeyVersion.createForDecryption(
          feInfo.getKeyName(), feInfo.getEzKeyVersionName(), feInfo.getIV(),
          feInfo.getEncryptedDataEncryptionKey());
      try {
        KeyProviderCryptoExtension cryptoProvider = KeyProviderCryptoExtension
            .createKeyProviderCryptoExtension(provider);
        // 进行解密操作
        return cryptoProvider.decryptEncryptedKey(ekv);
      } catch (GeneralSecurityException e) {
        throw new IOException(e);
      }
    }
  }

构造完加密输出流对象之后CryptoOutputStream之后,在随后的写操作中,数据都会额外经过一步加密算法的操作.此部分的过程调用图如下:

Encryption zone下的读文件



读文件部分的操作与写文件非常类似.

首先是构造出目标文件的HDFSFileStatus对象,然后取出其中的FileEncryptionInfo,在此过程中FileEncryptionInfo会被设置到LocatedBlocks中.

  private static HdfsLocatedFileStatus createLocatedFileStatus(
      FSDirectory fsd, byte[] path, INodeAttributes nodeAttrs,
      byte storagePolicy, int snapshot,
      boolean isRawPath, INodesInPath iip) throws IOException {

      ...
      // 然后设置到LocatedBlocks中
      loc = fsd.getBlockManager().createLocatedBlocks(
          fileNode.getBlocks(snapshot), fileSize, isUc, 0L, size, false,
          inSnapshot, feInfo, ecPolicy);
      ...

然后这些Blocks信息会以参数的信息传入到DFSInputStream,并在方法fetchLocatedBlocksAndGetLastBlockLength被设置到变量中.

  private long fetchLocatedBlocksAndGetLastBlockLength(boolean refresh)
      throws IOException {
    LocatedBlocks newInfo = locatedBlocks;
    ...
    // 将locatedBlocks中的EncryptionInfo信息设置到变量中
    fileEncryptionInfo = locatedBlocks.getFileEncryptionInfo();

    return lastBlockBeingWrittenLength;
  }

然后此信息同样会被取出用到加密输入流中

  public HdfsDataInputStream createWrappedInputStream(DFSInputStream dfsis)
      throws IOException {
    // 获取文件加密信息
    final FileEncryptionInfo feInfo = dfsis.getFileEncryptionInfo();
    if (feInfo != null) {
      // File is encrypted, wrap the stream in a crypto stream.
      // Currently only one version, so no special logic based on the version #
      getCryptoProtocolVersion(feInfo);
      final CryptoCodec codec = getCryptoCodec(conf, feInfo);
      // 解密DEDK
      final KeyVersion decrypted = decryptEncryptedDataEncryptionKey(feInfo);
      // 构造加密输入流
      final CryptoInputStream cryptoIn =
          new CryptoInputStream(dfsis, codec, decrypted.getMaterial(),
              feInfo.getIV());
      return new HdfsDataInputStream(cryptoIn);
    } else {
      // No FileEncryptionInfo so no encryption.
      return new HdfsDataInputStream(dfsis);
    }
  }

与之前的过程非常的类似,在加密输入流中,就会对读取的数据进行解密,使得用户能看到正常的数据.同样给出过程图:

Encryption zone的管理



在源码分析的最后部分,这里再简单描述一下HDFS的Encryption zone的中心管理.同样是一个叫做EncryptionZoneManager的类来专门做这个事情的,但是有一点不同,他保存的对象不是EncryptionZone,而是EncryptionZoneInt.

public class EncryptionZoneManager {

  public static Logger LOG = LoggerFactory.getLogger(EncryptionZoneManager
      .class);

  ...
  // 用TreeMap保存的Encryption zone列表
  private final TreeMap<Long, EncryptionZoneInt> encryptionZones;
  private final FSDirectory dir;
  private final int maxListEncryptionZonesResponses;
  ...

这里的TreeMap的key位置保存的encryption zone的对应目录的indeed.

EncryptionZoneInt与EncryptionZone有什么微妙的关系呢?

在具体使用的时候,EncryptionZoneInt会被用来构造EncryptionZone

如下代码

  EncryptionZone getEZINodeForPath(INodesInPath iip) {
    final EncryptionZoneInt ezi = getEncryptionZoneForPath(iip);
    if (ezi == null) {
      return null;
    } else {
      return new EncryptionZone(ezi.getINodeId(), getFullPathName(ezi),
          ezi.getSuite(), ezi.getVersion(), ezi.getKeyName());
    }
  }

通过判断目标路径是否在encryption zone列表中,来判断此文件是否为加密文件,以为inodeId作为key去map中取出.

下面给出Encryption zone管理的结构图:

Encryption zone的使用



最后再介绍以下Encryption zone功能的具体配置使用.总的来说,住需要完成几个相关的配置项即可.

第一步: 完成keyProveider的配置



将已存在的keyProvider的URL地址配置到下面的配置中

dfs.encryption.key.provider.uri

第二步: 加密算法相关的配置



主要有以下的一些配置

hadoop.security.crypto.codec.classes.EXAMPLECIPHERSUITE
hadoop.security.crypto.codec.classes.aes.ctr.nopadding
hadoop.security.crypto.cipher.suite
hadoop.security.crypto.jce.provider
hadoop.security.crypto.buffer.size

当然这些配置并不需要额外配置,采用默认配置也是可以的.

第三步: 配置listZone响应回复的个数



此配置会在listZones的命令中起到作用.

dfs.namenode.list.encryption.zones.num.responses

第四步: 创建Encryption zone加密空间



这里的加密空间是针对目录级别的,并且还需要设置一个key名称,使用的命令如下

hdfs crypto -createZone -keyName <keyName> -path <path>

这里的path是要已经建好的目录,此命令的作用相当于将目标目录作为一个加密空间,在此目录下的文件在读写的过程中,被加/解密.

以上操作完成之后,加密空间就基本创建好了,可以用listZones的命令查看当前已创建的加密空间

hdfs crypto -listZones

然后此目录文件数据的加解密过程对于客户端来说完全是透明的了.

Encryption zone使用范例



下面举出官方的使用例子

# 以普通用户的身份创建一个加密key
hadoop key create myKey

# 以超级用户的身份创建一个空目录,并使之成为加密空间
hadoop fs -mkdir /zone
hdfs crypto -createZone -keyName myKey -path /zone

# 修改此目录权限为普通用户的
hadoop fs -chown myuser:myuser /zone

# 以普通用户的身份进行put上传文件和cat查看文件操作
hadoop fs -put helloWorld /zone
hadoop fs -cat /zone/helloWorld

参考链接



1.http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/TransparentEncryption.html

时间: 2024-11-03 05:42:27

HDFS数据加密空间--Encryption zone的相关文章

HDFS文件系统空间使用情况分析

1.查看集群的空间使用状态 [[email protected] bin]$ ./hdfs dfsadmin -report Configured Capacity: 8453768839168 (7.69 TB) Present Capacity: 7959274510394 (7.24 TB) DFS Remaining: 4128882869718 (3.76 TB) DFS Used: 3830391640676 (3.48 TB) DFS Used%: 48.12% Under rep

读Hadoop3.2源码,深入了解java调用HDFS的常用操作和HDFS原理

本文将通过一个演示工程来快速上手java调用HDFS的常见操作.接下来以创建文件为例,通过阅读HDFS的源码,一步步展开HDFS相关原理.理论知识的说明. 说明:本文档基于最新版本Hadoop3.2.1 目录 一.java调用HDFS的常见操作 1.1.演示环境搭建 1.2.操作HDFS 1.3.java文件操作常用方法 二.深入了解HDFS写文件的流程和HDFS原理 2.1.Hadoop3.2.1 源码下载及介绍 2.2.文件系统:FileSystem 2.3.HDFS体系结构:namenod

HDFS内部的认证机制

前言 数据的安全性是一直被大家所重视的.对于一个存有大规模数据量的成熟企业来说,如何做到数据不丢失,不损坏,不窃取就显得格外重要了.而HDFS恰恰满足了"海量数据规模"的特点,所以如果我们用HDFS存储大量的非结构化的数据,我们如何保证其中数据的安全性呢?在之前的文章中,有提到过一个"Encryption Zone"数据加密空间的概念.Encryption Zone可以保证用户在指定的加密空间路径下,数据是被加/解密的,而且对于用户来说完全透明.详细信息可点击HDF

HDFS小文件物理空间占用验证

项目环境中遇到的小文件较多,起初除了NAMENODE的内存外,还是比较担心文件物理空间的占用.于是就看一下,小文件是如何占用物理空间的: 前提:HDFS块大小为64MB 文件副本总共3份 1.  批量生成小文件(均20M) 2.  记录测试前DFS空间使用情况 目前,DFS空间已经使用50.04GB 3.  向HIVE中LOAD一个文件testaa 4.  再次查看DFS空间使用情况 5. 查看文件testaa所在的块占用的空间情况 Total size可以看出:占用物理空间为20MB 6. 再

HDFS概述(1)————Block块大小设置

以下内容转自:http://blog.csdn.net/samhacker/article/details/23089157?utm_source=tuicool&utm_medium=referral http://snglw.blog.51cto.com/5832405/1643587 小文件BLOCK占用 [小于块大小的小文件不会占用整个HDFS块空间.也就是说,较多的小文件会占用更多的NAMENODE的内存(记录了文件的位置等信息):再者,在文件处理时,可能会有较大的网络开销.] 一个常

Hadoop之——重新格式化hdfs系统的方法

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46352939 重新格式化hdfs系统的方法: (1)查看hdfs-ste.xml: <property> <name>dfs.name.dir</name> <value>/home/hadoop/hdfs/name</value> <description>namenode上存储hdfs名字空间元数据</des

HDFS副本机制&amp;负载均衡&amp;机架感知&amp;访问方式&amp;健壮性&amp;删除恢复机制&amp;HDFS缺点

副本机制 1.副本摆放策略 第一副本:放置在上传文件的DataNode上:如果是集群外提交,则随机挑选一台磁盘不太慢.CPU不太忙的节点上:第二副本:放置在于第一个副本不同的机架的节点上:第三副本:与第二个副本相同机架的不同节点上:如果还有更多的副本:随机放在节点中: 2.副本系数 1)对于上传文件到HDFS时,当时hadoop的副本系数是几,那么这个文件的块副本数就有几份,无论以后怎么更改系统副本系数,这个文件的副本数都不会改变,也就是说上传到HDFS系统的文件副本数是由当时的系统副本数决定的

HDFS追本溯源:租约,读写过程的容错处理及NN的主要数据结构

1.      Lease 的机制: hdfs支持write-once-read-many,也就是说不支持并行写,那么对读写的互斥同步就是靠Lease实现的.Lease说白了就是一个有时间约束的锁.客户端写文件时需要先申请一个Lease,对应到namenode中的LeaseManager,客户端的client name就作为一个lease的holder,即租约持有者.LeaseManager维护了文件的path与lease的对应关系,还有clientname->lease的对应关系.LeaseM

HDFS架构剖析

HDFS架构核心内容: 一.NameNode/DataNode 二.数据存储副本 三.元数据持久化 四.数据模型 五.故障容错 具体讲解: 一.NameNode/DataNode HDFS采用Master/Slave架构,其集群由一个NameNode和多个DataNodes组成. NameNode是中心服务器,负责管理文件系统的名字空间(namespace)以及客户端的访问.DataNode负责管理它所在节点上的存储.用户能够通过HDFS文件系统的名字空间以文件的形式在上面存储数据. 对于内部存