1 Keytab 文件概述
- 在开启了kerberos安全认证的环境中,任何用户进行任何操作之前,都要首先通过 AS_REQ 请求向 KDC 的 AS 进行认证以获得总票据 tgt,然后才能通过 TGS_REQ 请求向 KDC 的 TGS申请具体的服务票据 ticket;
- 而在进行 AS_REQ 请求时,可以通过命令行交互式手动输入密码的方式(比如执行命令 kinit dap),也可以通过keytab文件的方式(比如JAVA代码中的org.apache.hadoop.security.UserGroupInformation#loginUserFromKeytab(String user,String path),或命令行的 kinit dap -kt dap.keytab);
- keytab 文件包含了单个或多个principal(用户/组/服务/服务器)的身份认证信息(即key),在功能上等同于密码,且有些管理员倾向于定期为 principal 生成符合密码策略 password policy 的随机密码以增强其安全性,所以 keytab 文件的安全性和重要性不言而喻。
2 keytab文件的有效性概述
管理员经常会在kdc服务端更改某个 principal 的密码,常见的更改密码的场景有:
- 比如通过命令显示更改密码:kpasswd;
- 比如通过脚本定期显示更改密码 (password rotation);
- 比如通过命令kadmin.local -q "xst -k dap.keytab dap",在生成新的keytab文件时隐式同步更改密码。
无论哪种场景,一旦服务端更改密码后,原有的已经分发给客户端的keytab文件都将失效,所有客户端需要验证其keytab的有效性。 由于 keytab 文件是加密的二进制格式,我们并不能通过 less/cat等命令直接查看其内容,那么怎么验证keytab文件的有效性呢?
3 使用 klist 命令查看对比新老 keytab 文件以验证其有效性
- 每个keytab文件可以包括多个principa的认证信息,也可以包括同一个principal的多个版本的认证信息;
- 可以通过 klist -ekt xxx.keytab 查看keytab文件内容,在输出内容中的 KVNO 列代表 key version number,较新版本对应列的KVNO值会更大,klist/kinit等命令或程序使用该keytab文件进行认证时,会自动选择并使用KVNO值最大的列;
- 可以通过以下命令在不更改某个principal原有密码的情况下,将其认证信息写入当前路径下的某个keytab文件中(如果当前路径不存在该keytab文件,则会新建该文件;如果当前路径存在该Keytab文件,则会将该principal认证信息追加到该keytab文件中):kadmin.local -q "xst -norandkey -k spark2.keytab dap2";
- 可以通过以下命令,更改某个principal的旧密码并生成随机新密码,然后将其新认证信息写入当前路径下的某个keytab文件中(如果当前路径不存在该keytab文件,则会新建该文件;如果当前路径存在该Keytab文件,则会将该principal认证信息追加到该keytab文件中):kadmin.local -q "xst -k spark2.keytab dap2";
- Multiple keys can be present in a keytab file,the version of the key is shown in its key version number (KVNO).,Refreshing (also called rotating) the principal's key increments the KVNO in the keytab entry.
- When a key is refreshed, a new entry is added to the keytab with a higher KVNO. The original key remains in the keytab but is no longer used to issue tickets.
4 使用keytab文件进行认证并查看相关日志
可以通过kinit命令或JAVA代码使用keytab文件进行认证,以验证其有效性。 为进一步细致排查问题,也可以通过以下方式调整kerberos日志级别:
- 通用配置 OS-level Kerberos Debugging: export KRB5_TRACE=/tmp/kinit.log (技术背景:User can set an environment variable called KRB5_TRACE to a filename or to /dev/stdout, Kerberos programs like kinit, klist and kvno etc., as well as Kerberos libraries libkrb5*, will start printing more interesting details.This is a very powerfull feature and can be used to debug any program which uses Kerberos libraries);
- JAVA程序,可以配置 JVM Kerberos Library logging:-Dsun.security.krb5.debug=true
- JAVA程序,可以配置 JVM SPNEGO Logging:-Dsun.security.spnego.debug=true
- HADOOP可以配置 Hadoop-side JAAS debugging:export HADOOP_JAAS_DEBUG=true
- HADOOP可以配置 HADOOP_OPTS environment variable:export HADOOP_OPTS="-Djava.net.preferIPv4Stack=true -Dsun.security.krb5.debug=true ${HADOOP_OPTS}“
- if the env variable HADOOP_JAAS_DEBUG is set to true,then hadoop UGI will set the "debug" flag on any JAAS files it creates;
- 针对HADOOP配置环境变量时,可以通过以下命令查看环境变量:echo $HADOOP_OPTS(不是 export $HADOOP_OPTS!!!!);
因为 keytab 文件失效而认证失败时,常见的两个错误如下:
## 错误1:javax.security.auth.login.LoginException: Checksum failed,完整信息如下: Caused by: org.apache.hadoop.security.KerberosAuthException: failure to login: for principal: dap2@CDH.COM from keytab ./dap2.keytab javax.security.auth.login.LoginException: Checksum failed at org.apache.hadoop.security.UserGroupInformation.doSubjectLogin(UserGroupInformation.java:1846) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytabAndReturnUGI(UserGroupInformation.java:1214) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1007) ~[hadoop-common-3.1.4.jar:?] at com.hundsun.broker.dsc.biz.pub.util.JdbcEntityUtils.getHiveJdbcEntity(JdbcEntityUtils.java:867) ~[dsc-biz-pub.jar:?] at com.hundsun.broker.dsc.biz.pub.util.JdbcEntityUtils.loadPropConfig(JdbcEntityUtils.java:678) ~[dsc-biz-pub.jar:?] at com.hundsun.broker.dsc.biz.pub.util.JdbcEntityUtils.<clinit>(JdbcEntityUtils.java:93) ~[dsc-biz-pub.jar:?] ... 2 more Caused by: javax.security.auth.login.LoginException: Checksum failed at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:804) ~[?:1.8.0_201] at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617) ~[?:1.8.0_201] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_201] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) ~[?:1.8.0_201] at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.login(LoginContext.java:587) ~[?:1.8.0_201] at org.apache.hadoop.security.UserGroupInformation$HadoopLoginContext.login(UserGroupInformation.java:1924) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.doSubjectLogin(UserGroupInformation.java:1836) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytabAndReturnUGI(UserGroupInformation.java:1214) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1007) ~[hadoop-common-3.1.4.jar:?] Caused by: sun.security.krb5.KrbCryptoException: Checksum failed at sun.security.krb5.internal.crypto.Aes128CtsHmacSha1EType.decrypt(Aes128CtsHmacSha1EType.java:102) ~[?:1.8.0_201] at sun.security.krb5.internal.crypto.Aes128CtsHmacSha1EType.decrypt(Aes128CtsHmacSha1EType.java:94) ~[?:1.8.0_201] at sun.security.krb5.EncryptedData.decrypt(EncryptedData.java:175) ~[?:1.8.0_201] at sun.security.krb5.KrbAsRep.decrypt(KrbAsRep.java:149) ~[?:1.8.0_201] at sun.security.krb5.KrbAsRep.decryptUsingKeyTab(KrbAsRep.java:121) ~[?:1.8.0_201] at sun.security.krb5.KrbAsReqBuilder.resolve(KrbAsReqBuilder.java:285) ~[?:1.8.0_201] at sun.security.krb5.KrbAsReqBuilder.action(KrbAsReqBuilder.java:361) ~[?:1.8.0_201] at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:776) ~[?:1.8.0_201] at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617) ~[?:1.8.0_201] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_201] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) ~[?:1.8.0_201] at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.login(LoginContext.java:587) ~[?:1.8.0_201] at org.apache.hadoop.security.UserGroupInformation$HadoopLoginContext.login(UserGroupInformation.java:1924) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.doSubjectLogin(UserGroupInformation.java:1836) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytabAndReturnUGI(UserGroupInformation.java:1214) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1007) ~[hadoop-common-3.1.4.jar:?] Caused by: java.security.GeneralSecurityException: Checksum failed at sun.security.krb5.internal.crypto.dk.AesDkCrypto.decryptCTS(AesDkCrypto.java:451) ~[?:1.8.0_201] at sun.security.krb5.internal.crypto.dk.AesDkCrypto.decrypt(AesDkCrypto.java:272) ~[?:1.8.0_201] at sun.security.krb5.internal.crypto.Aes128.decrypt(Aes128.java:76) ~[?:1.8.0_201] at sun.security.krb5.internal.crypto.Aes128CtsHmacSha1EType.decrypt(Aes128CtsHmacSha1EType.java:100) ~[?:1.8.0_201] at sun.security.krb5.internal.crypto.Aes128CtsHmacSha1EType.decrypt(Aes128CtsHmacSha1EType.java:94) ~[?:1.8.0_201] at sun.security.krb5.EncryptedData.decrypt(EncryptedData.java:175) ~[?:1.8.0_201] at sun.security.krb5.KrbAsRep.decrypt(KrbAsRep.java:149) ~[?:1.8.0_201] at sun.security.krb5.KrbAsRep.decryptUsingKeyTab(KrbAsRep.java:121) ~[?:1.8.0_201] at sun.security.krb5.KrbAsReqBuilder.resolve(KrbAsReqBuilder.java:285) ~[?:1.8.0_201] at sun.security.krb5.KrbAsReqBuilder.action(KrbAsReqBuilder.java:361) ~[?:1.8.0_201] at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:776) ~[?:1.8.0_201] at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617) ~[?:1.8.0_201] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_201] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) ~[?:1.8.0_201] at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) ~[?:1.8.0_201] at javax.security.auth.login.LoginContext.login(LoginContext.java:587) ~[?:1.8.0_201] at org.apache.hadoop.security.UserGroupInformation$HadoopLoginContext.login(UserGroupInformation.java:1924) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.doSubjectLogin(UserGroupInformation.java:1836) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytabAndReturnUGI(UserGroupInformation.java:1214) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1007) ~[hadoop-common-3.1.4.jar:?] ##错误2:javax.security.auth.login.LoginException: Unable to obtain password from user,完整信息如下: Caused by: org.apache.hadoop.security.KerberosAuthException: failure to login: for principal: hundsun@FZCN.ORG from keytab ./spark2.keytab javax.security.auth.login.LoginException: Unable to obtain password from user at org.apache.hadoop.security.UserGroupInformation.doSubjectLogin(UserGroupInformation.java:1846) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytabAndReturnUGI(UserGroupInformation.java:1214) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1007) ~[hadoop-common-3.1.4.jar:?] Caused by: javax.security.auth.login.LoginException: Unable to obtain password from user at com.sun.security.auth.module.Krb5LoginModule.promptForPass(Krb5LoginModule.java:897) ~[?:1.8.0_221] at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:760) ~[?:1.8.0_221] at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617) ~[?:1.8.0_221] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_221] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_221] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_221] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_221] at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) ~[?:1.8.0_221] at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) ~[?:1.8.0_221] at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) ~[?:1.8.0_221] at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) ~[?:1.8.0_221] at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_221] at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) ~[?:1.8.0_221] at javax.security.auth.login.LoginContext.login(LoginContext.java:587) ~[?:1.8.0_221] at org.apache.hadoop.security.UserGroupInformation$HadoopLoginContext.login(UserGroupInformation.java:1924) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.doSubjectLogin(UserGroupInformation.java:1836) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytabAndReturnUGI(UserGroupInformation.java:1214) ~[hadoop-common-3.1.4.jar:?] at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1007) ~[hadoop-common-3.1.4.jar:?]