在最早的 Security features for Hadoop 设计中提出这样的假设:
A Hadoop job will run no longer than 7 days (configurable) on a MapReduce cluster or accessing HDFS from the job will fail.
对于一般的任务, 24小时甚至延迟到一周的凭证时限是足够充分的。所以大部分时间我们只需要在执行操作之前使用 kinit 认证一遍,再起后台任务进行周期性凭证更新即可。
while true ; do kinit -R; sleep $((3600 * 6)) ; done &
不过对于需要常驻的访问Hadoop集群的服务来说,上述假设就不成立了。这时候我们可以
扩大 ticket_lifetime
和 renew_lifetime
时限
扩大凭证存活时限可以解决此问题,但由于Kerberos跟我们线上用户登陆认证绑定,会带来安全隐患,故不方便修改。
定期重新进行kinit 认证更新凭证
不仅仅是定期延长认证时间,可以直接定期重新认证以延长凭证有限期限。一般我们需要导出 keytab 来进行定期认证的操作。
Hadoop 将 Kerberos 认证部分进行了一定的封装,实际上并不需要那么复杂, 这里重点可以看看 UserGroupInformation
这个类。
UserGroupInformation
这个类在 JAAS 框架上封装了 Hadoop 的用户信息, 更确切地说是对 Subject 做了一层封装。
UserGroupInformation(Subject subject) {
this.subject = subject;
this.user = subject.getPrincipals(User.class).iterator().next();
this.isKeytab = !subject.getPrivateCredentials(KerberosKey.class).isEmpty();
this.isKrbTkt = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
}
JAAS 是 Java 认证和授权服务(Java Authentication and Authorization Service)的缩写, 主要包含以下几个实体:
JAAS的认证过程如下:
- An application instantiates a LoginContext.
- The LoginContext consults a Configuration to load all of the LoginModules configured for that application.
- The application invokes the LoginContext's login method.
- The login method invokes all of the loaded LoginModules. Each LoginModule attempts to authenticate the subject. Upon success, LoginModules associate relevant Principals and credentials with a Subject object that represents the subject being authenticated.
- The LoginContext returns the authentication status to the application.
- If authentication succeeded, the application retrieves the Subject from the LoginContext.
需要认证的代码片段可以包装在 doPrivileged 当中, 可以直接使用 Subject.doAs
方法,支持嵌套。
在安全模式下,UGI 支持不同LoginContext 配置, 均是通过 HadoopConfiguration 类动态产生:
useTicketCache
置为 true.useKeyTab
置为 true.
UGI 当中有多处认证, getLoginUser 方法使用 hadoop-user-kerberos
配置认证:
HADOOP_TOKEN_FILE_LOCATION
中的 token 加入 Credentials 集合当中spawnAutoRenewalThreadForUserCreds
步骤5可以看出当我们存在凭证后并不需要主动做周期性地凭证更新。
而 loginUserFromKeytab 方法使用 hadoop-kerberos
配置认证:
loginUserFromKeytab 没有对凭证做周期的更新, 那怎么保证凭证不会过期呢?
checkTGTAndReloginFromKeytab
来尝试更新凭证(实际上是重新登陆了)reloginFromKeytab
来重新登陆
Client.java
private synchronized void handleSaslConnectionFailure(
final int currRetries, final int maxRetries, final Exception ex,
final Random rand, final UserGroupInformation ugi) throws IOException,
InterruptedException {
ugi.doAs(new PrivilegedExceptionAction
可见如果是使用 keytab 认证的话,认证是长期有效的。
从上述代码中可以看到,不论是否是keytab认证,创建IPC失败均会尝试重新登陆。
为了让用户免于记忆密码,我们可以考虑导出并交付keytab给相关用户(前提是用户数量可控, 比如是以虚拟用户为单位)。
这样,用户的Hadoop任务认证方式可以有:
loginUserFromKeytab
完成登录,然后将代码片段包裹在 UGI 的 doAs
方法当中执行确定了部署方案之后, 我们在升级 hadoop 版本的同时完成了安全认证的部署。在部署和使用中我们遇到若干问题,这里一一说明。
开启安全认证时发现 Kerberos 认证不通过:
Client failed to SASL authenticate: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: Failure unspecified at GSS-API level (Mechanism level: Checksum failed)]
由于我们部署的Kerberos默认使用 AES-256 加密, 需要在Hadoop环境(集群以及客户端)上安装 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy File, 否则Kerberos认证不通过。可以通过此 gist 验证改动是否生效。此步骤可以添加到puppet当中。
开启安全认证发现 SNN 持续由于 getimage 报错NPE 退出, 相关错误如下。
2013-12-29 23:56:19,572 DEBUG org.apache.hadoop.security.authentication.server.AuthenticationFilter: Request [http://XXX.com:50070/getimage?getimage=1&txid=8627&storageInfo=-47:200271
8265:0:CID-3dce02cb-a1c2-4ab8-8b12-f23bbefd7bcc] triggering authentication
2013-12-29 23:56:19,580 WARN org.apache.hadoop.security.authentication.server.AuthenticationFilter: Authentication exception: GSSException: Failure unspecified at GSS-API level (Mechanism level: Specified
version of key is not available (44))
org.apache.hadoop.security.authentication.client.AuthenticationException: GSSException: Failure unspecified at GSS-API level (Mechanism level: Specified version of key is not available (44))
at org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler.authenticate(KerberosAuthenticationHandler.java:360)
at org.apache.hadoop.security.authentication.server.AuthenticationFilter.doFilter(AuthenticationFilter.java:349)
根据报错信息 Specified version of key is not available
发现是由于同一个 HTTP 凭证被导出多遍导致之前的keytab中的凭证失效了,重新生成部署所需的 keytab 即可。
这里的提醒就是不要重复导出相同的凭证, 以防止已经分发使用的keytab中的凭证失效。
在部署安全认证之后, 我们对hdfs数据进行 balance 就需要预先认证一下再执行, 这样就会遇到我们之前说的认证期限的问题。
这里有两种方式可以解决此问题:
org.apache.hadoop.hdfs.server.balancer.Balancer
进行一点封装,将其封装在一个 doAs 当中, 类似 hue 中的 SudoFsShell 一样的思路
sssd 是指我们用于线上登陆认证的一个底层服务,在过去一段时间内经常出现问题退出,导致用户登录动作hang住,进而导致相关任务执行失败。部署Hadoop安全认证之后相关 kerberos 认证也走这个服务,增大了服务异常退出的概率。目前看起来sssd服务问题是由于系统版本过低sssd服务代码有bug导致,解决方案最方便的是升级系统或切换服务到新的机器。
应用执行日志偶尔会报如下错误:
2014-03-12 21:30:03,593 WARN security.UseGroupInformation (UserGroupInformation.java:run(794)) - Exception encountered while running the renewal command. Aborting renew thread. org.apache.hadoop.util.Shell$ExitCodeException: kinit(v5): KDC can't fulfill requested option while renewing credentials
表示 UGI的凭证更新线程失败退出了。目前HADOOP-10041 记录了此问题,主要原因是由于凭证无法更新导致, 一般不需要特殊处理。