也说Hadoop敏感信息加密方案的尝试(下)

2016-12-19 10:48:39来源:oschina作者:问津少年人点击



前面讲到了AES对称加密的两个不同方案,一是Hive表存储秘钥,一是用KMS来存储秘钥。在这两个大的分类下,又各自有两种不同的方案,每种方案的尝试都是因为踩到了坑,所以才不得不换一种姿势。
前文博客见 [也说Hadoop敏感信息加密方式的尝试(上)](https://my.oschina.net/u/2539801/blog/808054)#KMS秘钥存储方案
关于KMS的讲解和配置,可见博文 [KMS秘钥管理服务(Hadoop)](https://my.oschina.net/u/2539801/blog/807974)。这里只讲其在加密方案中的应用。##方案一:直接从KMS获取秘钥
这里利用KMS存储字段加密秘钥,在每个Hive或Spark任务提交到集群的时候,集群都会进行提交者的Kerberos身份认证,我们就在Hive/Spark UDF里利用当前Kerberos身份来获取秘钥。
看起来好像很顺理成章,事实证明 *测试环境* 下,在Spark UDF里确实可以顺利利用提交账户获取到该账户所能获取到的秘钥,进行字段加解密。但是我们发现在Hive里,这种方案行不通,观察`KMS`的审计日志发现,所有在`beeswax`或`beeline`里使用UDF提交的查询,向`KMS`请求秘钥的用户身份都是**hive**。也就是说Hive里的代理用户身份设置并没有生效,用户向Hive提交的任务,Hive最后还是以**hive**的身份鉴权和执行!
这里的情况让我有点惊愕,因为我们提交的所有hive任务,其实都是经过了鉴权的,不可能是以**hive**身份来鉴权。所以意识到是我们在哪弄错了,因为集群上了`Sentry`,它提供了不止基于UNIX文件系统的鉴权,所以集群上的`HiveServer2`是与`Sentry`结合的。所有经过`HiveServer2`的任务都要先经过`Sentry`的鉴权,鉴权通过Hive才会往下执行。
而在安装`Sentry`的时候,是明确要求Hive关闭**impersonation**的,也即`hive.server2.enable.doAs = false`,这个功能就是Hive的代理用户功能,关掉它之后,所有提交给Hive的任务都将以**hive**的身份执行!所以成也`Sentry`败也`Sentry`,我们在Hive UDF里拿到的当前认证用户就是**hive**,拿不到实际提交用户的Kerberos认证!
> 由于在Hive UDF里拿不到提交用户的Kerberos认证,所以这一方案就被KO了
##方案二:通过MapredContext获取提交用户名
这是对上一个方案的延伸,由于在Hive UDF里拿不到当前提交用户的Kerberos认证,所以只能绕一下。在`GenericUDF`里有个函数 `public void configure(MapredContext context)` ,通过传入的*context*,我们可以用`context.getConf()`来拿到Map函数的`Configuration`,然后通过`hive.sentry.subject.name`配置,拿到提交的真正账户名。
> 在GenericUDF里,如果configure函数执行的话,它在initialize函数之前执行。通过从configure函数里拿到真正提交账户,我们在initialize函数里向KMS取秘钥就可以利用真正提交账户构造Kerberos认证。
>
> 但是configure函数的执行是有条件的,只有Hive的底层引擎是MapReduce时,这个函数才有可能执行,否则它不会执行。所以底层引擎不是它的时候,这种方式是行不通的。
这种方案里我们将授权人员的keytab文件进行AES加密,放入jar包里,将keytab秘钥存于KMS,在Hive UDF里先以hive身份请求keytab文件秘钥,然后解密当前提交账户的keytab。之后利用从`configure`函数里拿到的提交用户的身份去访问KMS,获取该用户权限范围内的字段秘钥。
方案在这里看起来似乎也是很OK的,在测试环境里,很OK的通过了。然后到线上后,发现出了BUG!无论是Spark还是Hive,都只有*部分* 能进行加解密成功,从日志上看只加解密了一部分数据就开始报错,报*Kerberos认证失败*!
失败了,那就开始查日志吧,从日志上发现,所有Spark认证失败的都是位于其他节点机上的,而位于提交任务的节点机上的executor则没有失败。在Hive上也一样,位于当前提交任务的节点机上的task是成功的,其他节点机上的是失败的。为什么其他节点机上的任务就会认证失败呢?
其实是因为其他节点机上并没有提交用户的`tgt`缓存(kinit得到的),无论Spark还是Hive,在启动任务的时候,都会拿到HDFSDelegationToken, YarnDelegationToken等经过Kerberos认证后的`token`,所有的task上都会带上这些token,而不会带上提交节点机上的Kerberos UGI信息。task之后通过token来做权限验证,在token失效时间之内,用token来做验证就不需要再请求KDC来做认证,直接由token就可以认证当前用户是否有权限执行相关操作!而在用户提交的节点机上,cache里有当前用户的`tgt`,在上面跑的task通过UGI认证时,可以直接拿到`tgt`来认证,自然可以认证通过。而其他节点机上并没有提交用户的`tgt`,认证通过不了,任务自然失败。
上面的问题之所以在测试环境未曾出现,是因为测试环境只有唯一一台节点机上没有测试账户的`tgt`,而测试的时候,所有的任务刚好都没有分到那台机器上,或者分配到上面没执行成功,任务重试时分配在其他机器上成功了,从而没被我注意到。
> 方案走到这一步,又走到绝路了。除非在Hive和Spark提交任务时,像HDFSDelegationToken一样,先获取到当前用户或hive用户KMS认证后的DelegationToken,然后以插件的形式注册到任务提交代码里,从而分发到执行节点task上去,由执行节点通过token来做KMS认证。但是这一步不说能不能做成,就单做起来就很麻烦,还可能需要改源码,需要花太大精力,不然官方早就有了完善的加密方案!
>
> 或者很猥琐的在集群各节点机上布置需要加解密服务的用户的`tgt`,让节点上的任务在执行时可以拿到认证信息。但是这样做不是正路子也不方便。
>
> 所以方案到这就被掐死了,包括凡是想利用Kerberos认证的方案,到这都走不通!
#Hive表秘钥存储方案
Hive表存储秘钥理解起来就比较简单,但是实践起来也可以分两种方案。一种对外透明,一种需要用户传key,相对猥琐一点。但是实践证明,猥琐的反而好使!
##方案一:在Hive UDF里获取Hive秘钥表的加密key
这种方案属于对外透明型的,用户只需传入加密字段类型和加密字段,就可以完成数据加密,不需要去管秘钥的存储。
但在测试环境验证从Hive UDF取Hive表数据的时候,发现JDBC取Hive表数据在拿到Connection,开始执行SQL语句的时候卡住了,多次尝试一直卡在那,而后台HiveServer2的日志也在get Session后不动。这个问题`查了好几次,没查到到底为什么`,猜测是在Hive UDF里实际是以**hive**用户去取Hive表的数据,可能会出现问题,但是并没有查到到底为什么!考虑到在线上环境里,问题还是回到了Kerberos认证的问题,其他节点机上拿不到认证信息,连接HS2都会连不上!所以之后就没有再探索这个问题。
> 这一方案跟Kerberos认证相关,而且由于Hive UDF里取Hive表数据也存在问题,所以也被KO了
##方案二:UDF提供传入Key的参数,UDF只做加密,由用户传入key
这种方案是当时AES加密里最不看好的一种方案,因为由用户传入key有很大安全隐患,而且API显得特别矬。
但是由于Key存于HIve表,我们可以控制权限。在UDF里我们只做加密,而不做其他额外的操作,UDF本身简单了很多,不用考虑权限认证的问题。所以这是种最简单的方式,经验证也是最行之有效的方式!
> 就这样,林林总总选择又放弃了前面的五种方案(其实尝试的时候还不止,有些是细节上的变动,这里就没有记录),最后选了一种当初觉得最矬的方案~~ 真是不到最后不甘心啊!
#问题记录
+ 在Hive的Imperonation关闭之后,怎么才能在Hive UDF里拿到真实提交用户的Kerberos认证? 不只是获取提交用户的用户名,而希望拿到UGI。
+ 如何才能在MR的task里分发KMS认证后的DelegationToken?以怎样的形式注册到Hive和Spark的启动程序里,像HDFS和Yarn一样?
+ 在集群上了Sentry的环境下,Hive UDF里获取Hive表无法拿到数据的原因?语句不执行的原因?
如果前两个问题能有效的解决掉,我想一套正式不猥琐的加密方案就可以面世了。加油!加油!
小主们转载请注明出处:https://my.oschina.net/u/2539801/blog/808061

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台