HDFS block access token认证机制

Posted Android路上的人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HDFS block access token认证机制相关的知识,希望对你有一定的参考价值。

前言


在存储系统中,数据的安全性无疑是十分重要的。在我们常见的文件系统中,最常使用的方式是通过文件目录的权限来做数据访问的控制。在HDFS这样分布式存储系统中,其内部实现同样沿用了这样的方式来做数据访问的控制。但是在HDFS拥有如此海量数据规模的系统中,我们只做文件权限的检查是足够安全的吗?鉴于HDFS的架构设计,权限检查是发生在NameNode端的,这时倘若一个恶意用户绕过了文件权限检查,然后直接访问实际DataNode的数据,这岂不是会造成很严重的数据安全问题?这里笔者想说的是在分布式存储系统中,数据访问的检查不仅仅在master服务端要做,在slave实际存储端也得加上一道防护层。本文我们就来聊聊HDFS的DataNode的这套防护机制:block access token。

Block access token


Block access token按照字面意思理解为就是数据访问的令牌,意为用户访问实际的数据需要有这样一个令牌然后才能做数据的后续读写操作。这个检验操作是发生在DataNode端的,因此它能够保证在block层面的访问上,用户的行为是经过验证的,这里的验证包括以下几点:

  • 访问行为是否是来自预期的用户
  • 访问操作是否是预期的行为
  • 访问的数据是否是预期的数据

在DataNode的角度来看,它没有文件的概念,其所有的操作是按照block维度进行的。

Block access token按照访问行为能够分为以下4种类型:

  public static enum AccessMode 
    READ, WRITE, COPY, REPLACE
  ;
  • READ,用于读取block数据的时候
  • WRITE,用于写block数据的时候
  • COPY/REPLACE,用于balancer进行数据平衡的时候

HDFS block access token的原理


HDFS本身在数据安全方面已经有比较成熟的设计,包括Kerberos+Delegation token认证体系,通过Kerberos,Delegation token,它能保证数据的访问者(client和application)是经过安全认证的。

相比较而言,社区对于block access token的设计是采用了比较轻量级的设计实现的。社区设计文档对block access token的形容是lightweight,short lived。而且此token不需要持久化,也不需要像Delegation token那样会有renew的行为。

接下来一个关键的问题来了:block access token是如何生成的,它的认证过程又是如何的呢?

社区实现采用的是一种对称加密算法,NameNode和DataNode共同享有一个密钥key,然后通过加密算法,DN对token内容做一个加密计算,算出的结果值如果与token内本身保存的加密内容(NameNode生成token时计算好的)是一致的话,就判断说这个token是有效的。

Block access token的认证过程如下:

  • 步骤1:NameNode启动生成一个随机密钥key,然后定期通过DataNode的heartbeat返回到DataNode这边在内存里做保存。这个密钥key会周期性进行更新,默认10个小时一次更新。
  • 步骤2:Client用户在读写数据的时候会向NameNode请求block的信息,在这个过程中NameNode会额外为Client生成一个对应访问模式的access token,包含在这个block信息内。
  • 步骤3:随后Client会携带这个token向实际存储数据的DataNode进行数据的访问。
  • 步骤4:DataNode验证Client的token来判断其访问行为是否是合理的。

对于步骤2,NameNode相关代码如下:

  private GetBlockLocationsResult getBlockLocationsInt(
      FSPermissionChecker pc, final String srcArg, long offset, long length,
      boolean needBlockToken)
      throws IOException 
    String src = srcArg;
    //...
    
    // 这里构造LocatedBlock信息的时候会进行token的构造
    final LocatedBlocks blocks = blockManager.createLocatedBlocks(
        inode.getBlocks(iip.getPathSnapshotId()), fileSize,
        isUc, offset, length, needBlockToken, iip.isSnapshot(), feInfo);

    // Set caching information for the located blocks.
    for (LocatedBlock lb : blocks.getLocatedBlocks()) 
      cacheManager.setCachedLocations(lb);
    

    final long now = now();
    boolean updateAccessTime = isAccessTimeSupported() && !isInSafeMode()
        && !iip.isSnapshot()
        && now > inode.getAccessTime() + getAccessTimePrecision();
    return new GetBlockLocationsResult(updateAccessTime, blocks);
  

对于写操作而言,也有类似获取token的逻辑。

在这里额外重点介绍一下DataNode端token验证的逻辑,这里我们以readBlock操作为例:

DataXceiver.java

  @Override
  public void readBlock(final ExtendedBlock block,
      final Token<BlockTokenIdentifier> blockToken,
      final String clientName,
      final long blockOffset,
      final long length,
      final boolean sendChecksum,
      final CachingStrategy cachingStrategy) throws IOException 
    previousOpClientName = clientName;
    long read = 0;
    updateCurrentThreadName("Sending block " + block);
    OutputStream baseStream = getOutputStream();
    DataOutputStream out = getBufferedOutputStream();
    // DataNode在这里会对read block的行为做验证
    checkAccess(out, true, block, blockToken,
        Op.READ_BLOCK, BlockTokenSecretManager.AccessMode.READ);
  ...

进入这个方法,里面会有专门的一步进行access token的验证,

DataXceiver.java

private void checkAccess(OutputStream out, final boolean reply, 
      final ExtendedBlock blk,
      final Token<BlockTokenIdentifier> t,
      final Op op,
      final BlockTokenSecretManager.AccessMode mode) throws IOException 
    checkAndWaitForBP(blk);
    if (datanode.isBlockTokenEnabled) 
      if (LOG.isDebugEnabled()) 
        LOG.debug("Checking block access token for block '" + blk.getBlockId()
            + "' with mode '" + mode + "'");
      
      try 
        datanode.blockPoolTokenSecretManager.checkAccess(t, null, blk, mode);
       catch(InvalidToken e) 
        ...
      
    
    

在这个checkAccess方法里面,会对里面的访问的用户,block信息以及token本身做检查:

  public void checkAccess(BlockTokenIdentifier id, String userId,
      ExtendedBlock block, AccessMode mode) throws InvalidToken 
    if (LOG.isDebugEnabled()) 
      LOG.debug("Checking access for user=" + userId + ", block=" + block
          + ", access mode=" + mode + " using " + id.toString());
    
    if (userId != null && !userId.equals(id.getUserId())) 
      throw new InvalidToken("Block token with " + id.toString()
          + " doesn't belong to user " + userId);
    
    if (!id.getBlockPoolId().equals(block.getBlockPoolId())) 
      throw new InvalidToken("Block token with " + id.toString()
          + " doesn't apply to block " + block);
    
    if (id.getBlockId() != block.getBlockId()) 
      throw new InvalidToken("Block token with " + id.toString()
          + " doesn't apply to block " + block);
    
    if (isExpired(id.getExpiryDate())) 
      throw new InvalidToken("Block token with " + id.toString()
          + " is expired.");
    
    if (!id.getAccessModes().contains(mode)) 
      throw new InvalidToken("Block token with " + id.toString()
          + " doesn't have " + mode + " permission");
    
  

最后还有一步token password的检查,这个password就是前面我们说的根据共享的密钥key做加密计算的结果值。

  /** Check if access should be allowed. userID is not checked if null */
  public void checkAccess(Token<BlockTokenIdentifier> token, String userId,
      ExtendedBlock block, AccessMode mode) throws InvalidToken 
    ...
    checkAccess(id, userId, block, mode);
    if (!Arrays.equals(retrievePassword(id), token.getPassword())) 
      throw new InvalidToken("Block token with " + id.toString()
          + " doesn't have the correct token password");
    
  

相当于说DataNode利用从NameNode拿来的密钥key重新做一遍加密计算,判断是否和NameNode那边计算的结果做比较。

Block access token的认证过程如下图所示:

上述图中还显示了Balancer使用block access token来做数据平衡的过程。

Block access token的更新


密钥key的分段隔离


对于block access token的更新,社区在这块做了巧妙的设计实现。不同NameNode在成为Active角色服务的时候,它对于Client来说是不同的。这个时候从NameNode1生成的token理应和NameNode2是不一样的,这样可以确保Client的token是来自正确的Active NameNode服务上的。

在前面小节已经提到了,token区分的核心在于其密钥key的部分,只要我们能够保证每个NameNode所使用的key的唯一性,就能保证后续所生成token的唯一性。

社区采用的作法是通过将一个整型分出2段,前半段的值作为NameNode1的key的range区间,后面段的值作为NameNode2的key的range区间,以此保证密钥key的使用是不重叠的。如果是多Standby NameNode模式的话,则每个range再进一步进行均等切分。

当前密钥key的计算公式如下:

this.serialNo = (serialNo % intRange) + (nnRangeStart);

NameNode重启问题


NameNode在运行的时候可能会被重启或failover,那么会导致部分Client会携带老的token向DataNode进行block的访问。为了避免这块数据访问的问题,DataNode会在内存里cache一段时间老的密钥key,以此保证Client的访问行为不被block。NameNode在每次rotate token key的时候,会同时保留3个key值:

  • 过去的一个key(从current key转变而来)
  • 当前的key: current key(从next key转变而来)
  • 未来的一个key:next key(next key值+1的一个key)

BlockTokenSecretManager.java

  /**
   * Update block keys, only to be used in master mode
   */
  synchronized boolean updateKeys() throws IOException 
    if (!isMaster)
      return false;

    LOG.info("Updating block keys");
    removeExpiredKeys();
    // set final expiry date of retiring currentKey
    allKeys.put(currentKey.getKeyId(), new BlockKey(currentKey.getKeyId(),
        Time.now() + keyUpdateInterval + tokenLifetime,
        currentKey.getKey()));
    // update the estimated expiry date of new currentKey
    currentKey = new BlockKey(nextKey.getKeyId(), Time.now()
        + 2 * keyUpdateInterval + tokenLifetime, nextKey.getKey());
    allKeys.put(currentKey.getKeyId(), currentKey);
    // generate a new nextKey
    setSerialNo(serialNo + 1);
    nextKey = new BlockKey(serialNo, Time.now() + 3
        * keyUpdateInterval + tokenLifetime, generateSecret());
    allKeys.put(nextKey.getKeyId(), nextKey);
    return true;
  

DataNode在接收到NameNode这边发来的block key时,会同时在内存里进行cache。

access token在一定程度上能够再一次加强用户的数据访问。但这里还存在一个问题是access token依然无法阻挡物理行为的数据访问,这里指的是抛开HDFS这个层面的检查。假设用户能够登录到DataNode机器,然后对block文件直接做访问,HDFS依然没有办法做限制。解决这个问题的一种方案是通过对DataNode的数据再次做加密存储,用户如果直接看block文件的话,看到的只会是一堆的乱码。这就迫使用户不得不使用Client RPC的方式来读取集群的数据。上述内容就是本文所要阐述的关于HDFS block access token的相关内容。

参考资料


[1].https://issues.apache.org/jira/browse/HADOOP-4359

以上是关于HDFS block access token认证机制的主要内容,如果未能解决你的问题,请参考以下文章

HDFS block access token认证机制

聊聊Hadoop安全认证体系:Delegation Token和Block Access Token

oauth2认证后返回#怎么获取accesstoken?

oauth2认证后返回#怎么获取accesstoken?

使用personal access token进行Github认证

使用personal access token进行Github认证