文章目录
前言
在存储系统中,数据的安全性无疑是十分重要的。在我们常见的文件系统中,最常使用的方式是通过文件目录的权限来做数据访问的控制。在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 GetBlo