凭证过期处理策略
在最早的 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
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)的缩写, 主要包含以下几个实体:
- Subject
Subject 是一个不可继承的实体类,它标志一个请求的来源, 包含相关的凭证标识(Principal) 和 公开和私有的凭据(Credential)。 - Principal
凭证标识,认证成功后,一个 Subject 可以被关联多个Principal。 - Credential
凭据,有公有凭据以及私有凭据。
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 类动态产生:
- hadoop-user-kerberos
使用kerberos缓存凭证登陆的配置,useTicketCache
置为 true. - hadoop-keytab-kerberos
使用keytab登陆的配置,useKeyTab
置为 true.
UGI 当中有多处认证, getLoginUser 方法使用 hadoop-user-kerberos
配置认证:
- 通过配置生成 LoginContext
- 调用 LoginContext.login 方法完成登陆, 通过 ticket cache 中凭证完成登陆
- 判断是否需要其他用户身份(proxy user)执行
- 将
HADOOP_TOKEN_FILE_LOCATION
中的 token 加入 Credentials 集合当中 - 另起一个线程做周期性的凭证更新
spawnAutoRenewalThreadForUserCreds
步骤5可以看出当我们存在凭证后并不需要主动做周期性地凭证更新。
而 loginUserFromKeytab 方法使用 hadoop-kerberos
配置认证:
- 通过配置生成 LoginContext
- 调用 LoginContext.login 方法完成登陆, 使用keytab完成登陆
loginUserFromKeytab 没有对凭证做周期的更新, 那怎么保证凭证不会过期呢?
- 在访问集群执行相关操作前, 可以调用
checkTGTAndReloginFromKeytab
来尝试更新凭证(实际上是重新登陆了) - 在凭证过期时,创建 IPC 失败会触发调用
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<Object>() {
@Override
public Object run() throws IOException, InterruptedException {
final short MAX_BACKOFF = 5000;
closeConnection();
disposeSasl();
if (shouldAuthenticateOverKrb()) {
if (currRetries < maxRetries) {
if(LOG.isDebugEnabled()) {
LOG.debug("Exception encountered while connecting to "
+ "the server : " + ex);
}
// try re-login
if (UserGroupInformation.isLoginKeytabBased()) {
UserGroupInformation.getLoginUser().reloginFromKeytab();
} else {
UserGroupInformation.getLoginUser().reloginFromTicketCache();
}
可见如果是使用 keytab 认证的话,认证是长期有效的。
从上述代码中可以看到,不论是否是keytab认证,创建IPC失败均会尝试重新登陆。
基于keytab 的Kerberos认证方式
为了让用户免于记忆密码,我们可以考虑导出并交付keytab给相关用户(前提是用户数量可控, 比如是以虚拟用户为单位)。
这样,用户的Hadoop任务认证方式可以有:
- 直接使用 keytab kinit 之后访问
- 或者调用
loginUserFromKeytab
完成登录,然后将代码片段包裹在 UGI 的doAs
方法当中执行
确定了部署方案之后, 我们在升级 hadoop 版本的同时完成了安全认证的部署。在部署和使用中我们遇到若干问题,这里一一说明。
JCE 部署
开启安全认证时发现 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
开启安全认证发现 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中的凭证失效。
Balancer 执行过长导致认证过期
在部署安全认证之后, 我们对hdfs数据进行 balance 就需要预先认证一下再执行, 这样就会遇到我们之前说的认证期限的问题。
这里有两种方式可以解决此问题:
- 添加外部定时任务重新认证, 刷新凭证缓存, 延迟凭证有效期限。
- 可以写一个小代码对 balance 的入口
org.apache.hadoop.hdfs.server.balancer.Balancer
进行一点封装,将其封装在一个 doAs 当中, 类似 hue 中的 SudoFsShell 一样的思路
sssd 服务认证异常
sssd 是指我们用于线上登陆认证的一个底层服务,在过去一段时间内经常出现问题退出,导致用户登录动作hang住,进而导致相关任务执行失败。部署Hadoop安全认证之后相关 kerberos 认证也走这个服务,增大了服务异常退出的概率。目前看起来sssd服务问题是由于系统版本过低sssd服务代码有bug导致,解决方案最方便的是升级系统或切换服务到新的机器。
"KDC can't fulfill requested option while renewing credentials"
应用执行日志偶尔会报如下错误:
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 记录了此问题,主要原因是由于凭证无法更新导致, 一般不需要特殊处理。