虽然这是上个世纪的东西了,虽然很麻烦,但这是hadoop唯一官方支持的安全机制。。。
我不想太深入kerberos的原理(其实是不懂),只是整理下使用中碰到的一些问题。
基本原理
kerberos最主要的作用是身份认证。比如服务端和客户端要互相验证对方的身份。
它基于一个很朴素的思想:让一个可信的第三方(KDC)来负责身份认证。client对server说,我是XXX,这是我的密钥。server用这个密钥去KDC中查询,是否确实是XXX的。如果是,则证明这个client确实是XXX,是可信的。这是server端验证client的身份。同样client端也要验证server的身份。server对client说,我是YYY,这是密钥。client同样也要去KDC中查询并确认。当client和server互相确认身份后,才能开始通信。
以上只是一种极简的描述。中间有很多细节。比如如何防止窃听、如何加密等等。非常复杂。
但在hadoop中使用时,知道个大概就够了。
里面的很多术语,比如ticket/realm/principal/TGT等等,不一一论述了。感兴趣可以自己google下。
关于JCE
JCE = Java Cryptography Extension。可以理解成包含一堆加密/解密算法的库。由于美国出口的限制(法律上的问题不懂),这个库不能随JDK一起分发。必须到oracle的网站上单独下载。下载后解压并替换$JAVA_HOME/jre/lib/security
中的同名文件。
kerberos使用了某种加密算法(似乎是AES?),必须要JCE相关lib。否则在kinit后在java中无法获取ticket。
以上说的都是OracleJDK,如果是OpenJDK则不用替换。
单点?
从之前的分析可以看出,KDC的角色非常重要,一旦KDC挂掉,整个hadoop集群都会挂。而且我们的KDC上不只有hadoop,还有hbase/hiveserver等等。
好在可以配置多个KDC,我们一般是配置3个。例子:
|
|
kinit的时候,会轮询直到找到可用的KDC。
不太清楚KDC之间是怎么同步数据的。
但配置太多KDC也可能会有问题。由于历史原因我们迁移过KDC,结果一些老的机器上配置没改,开头有很多无效的KDC,kinit时就非常慢,因为要一个个试过去。
关于时间
如果客户端和KDC的时间不同步,差值超过10分钟,kinit是必定失败的。所以务必保证hadoop集群中所有机器的时间和KDC同步。
另外kinit获取的ticket是有时间限制的。在我们的KDC配置中是10小时。快要过期之前可以kinit -R
刷新下,就会又有10小时的有效期。最长可以延长到7天。
但超过7天、或者快到10小时没能及时kinit -R
,ticket会过期,就必须要重新获取了。kinit -R
是否有效似乎取决于服务端的配置,有些ticket是不能renew的,我不是很熟。
可以用klist命令查看当前ticket的剩余时间、renew的期限。
hadoop中如何认证
这里指的只是客户端。比如我们在提交job时,如何通过kerberos认证?
两种方式:
- 在命令行中用kinit命令。比如
kinit -kt xxx.keytab yyy/zzz
。kinit成功后,就可以像平常一样用hadoop jar
提交job了。job的代码不用做任何改变。原理:kinit获取ticket后会缓存在一个临时文件中。java可以读取这个文件并获取kerberos认证相关信息。前提是替换过JCE相关jar。 - 用java代码获取ticket。hadoop提供了一个类
UserGroupInformation
,可以用以下代码获取ticket:
|
|
方法1不用改代码,但是如果一个linux用户要用不同的keytab进行认证,会互相冲突。原因在于默认情况下,对同一个linux用户而言,ticket文件的缓存和kerberos的配置文件都只能有一个。可以通过一些环境变量来设置。
方法2要改些代码,但比较灵活。loginUserFromKeytab
一次之后,同一个JVM内所有线程都可以用。不同JVM之间不会互相影响。而且UserGroupInformation
提供了其他一些方便的方法,比如ticket快要过期时自动更新、代理执行等等。具体去看javadoc吧。
我现在更喜欢用方法2。
方法2要注意代码执行的顺序。loginUserFromKeytab
方法必须在其他代码(访问hdfs、提交job之类)之前执行。
Failed to find any Kerberos tgt
很多地方都可能出现这个错误。
首先klist命令看下输出,确定已经拿到ticket并且没过期。
然后检查hadoop-env.sh中的JAVA_HOME设置。如果是OracleJDK,需要替换jre/lib/security中的jce相关的jar包。一般就正常了。
另外在jdk6u26和之前的版本里,有个bug也可能导致这个错误。kinit后java无法获取ticket。必须kinit -R
一次。
Server not found in Kerberos database
参考上面说的kerberos基本原理。client在验证server端身份时,要去KDC中验证对应的principal。如果验证失败,就可能有上面的错误。
具体的错误原因还要具体分析。我们碰到的情况是客户端DNS有问题。服务端返回的principal是hdfs/12.34.56.78,正常情况下客户端应该把IP换成域名,再去KDC中查询的。结果DNS有问题,无法反向查询这个IP,直接用IP去KDC中查,当然找不到。
在客户端本地加了hosts,问题解决。
Server has invalid Kerberos principal
跟上一个问题类似,也是验证server端身份时出错。
我们碰到的情况也是DNS问题。hdfs客户端取出配置文件中的namenode prinicpal(hdfs/hostname.photo.163.org),跟服务端返回的principal(hdfs/12.34.56.78)对比,二者不等则会抛出这个错误。
似乎服务端返回的principal中必定是IP,由客户端去反查域名。没看过代码,不确定。
加了hosts后解决。
JVM属性
在java中使用kerberos时,可以传入一些JVM参数覆盖配置文件中的值。常用的有:
参数 | 说明 |
---|---|
-Djava.security.krb5.conf=${KRB5_CONFIG} | 设置krb5.conf的位置。如果不设置,linux下默认是/etc/krb5.conf,windows下是c:/windows/krb5.ini。这两个文件虽然后缀不同,但格式完全一样的。 |
-Dsun.security.krb5.debug=true | 开启debug日志。非常有用。 |
-Djava.security.krb5.realm=${KRB5_REALM} | 设置默认的realm |
-Djava.security.krb5.kdc=${kdc.hostname} | 设置KDC地址 |
这里列出的可能不全。
JVM参数可以用-D设置,也可以System.setProperty("sun.security.krb5.debug", "true");
环境变量
在命令行中kinit时有效。
|
|
只要设置为不同的缓存位置和配置文件,就可以从不同的KDC上获取ticket,进而向多个secured hadoop集群提交任务(我估计设置不同的JVM属性,也能向不同的secured hadoop提交任务,见上一条,但我没试过)。
另外,kerberos的这两个环境变量,在windows版本里也是有用的。只是windows设置环境变量要用set命令:set KRB5CCNAME=D:\krb5cache
。