十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kerberos认证机制详解:从KDC组件到票据流程与故障排查

Kerberos认证机制详解:从KDC组件到票据流程与故障排查 搞Kerberos也有几年了每次给新同事讲这套认证机制都会发现大家被KDC、TGT、AS、TGS这一堆缩写搞得晕头转向。说实话Kerberos本身不是一个“难懂”的东西它就是一套很朴素的江湖规矩不传密码、只传票据、找第三方做背书。真正的难点在于很多人没搞懂它为什么要设计成这个样子就直接去看配置和命令结果一遇到报错就抓瞎。这篇文章我不打算堆配置而是把Kerberos和KDC服务从头到尾讲清楚。从协议解决什么问题到KDC里面每个组件干什么再到一次完整的认证流程怎么走最后是常见的报错和排查思路。无论你是刚接触Hadoop集群、AD域还是自己想搭一套KDC服务这篇都值得你花二十分钟读完。1. Kerberos到底解决了什么问题1.1 从门禁卡的逻辑说起想象你在一栋写字楼里办公楼里有几十家公司。你今天要去楼上另一家公司送一份合同前台不认得你你也不想把身份证复印件直接押在人家那儿。怎么办正常做法是你找自己公司的行政开一张“访客通行证”上面写着你是谁、要去哪家公司、有效时间到几点。楼里那家公司的前台拿到通行证核对无误就放你进去。Kerberos做的事情和这个场景一模一样。不过它把“行政”换成了一个统一的服务叫KDCKey Distribution Center密钥分发中心把“访客通行证”换成了加密的票据Ticket而你在网络里访问的每一个服务就是这个场景里的“前台”。在网络协议设计里最朴素的身份认证方式就是客户端直接给服务器发密码。但这么做会有一堆隐患密码可能被监听、服务器要保存你的密码明文、你访问十个服务就得输入十次密码。Kerberos改变的就是这个游戏规则——密码全程不出现在网络里全部用票据代替。1.2 不传密码只传凭证Kerberos核心思路可以总结成三句话客户端和KDC之间共享一个密钥这个密钥由你输入的口令派生出来。KDC验证你的身份之后发给你一张TGTTicket Granting Ticket票据授权票据这张TGT本质上是“你已通过认证”的证明。你拿着TGT去找KDC换取访问具体服务的服务票据Service Ticket服务票据交给目标服务服务端验证通过后让你访问。这就是为什么Kerberos能做到“一次登录处处访问”单点登录。密码只用来向KDC证明第一次身份后面全部靠票据在周转票据本身又是加密的别人截获了也解不开。Kerberos这个名字来源于希腊神话里的地狱三头犬刻耳柏洛斯。我猜当初MIT设计这个协议的人是想表达“三头”的含义——认证、授权、审计三件事一起管。事实上Kerberos也确实像一条守在门口的三头犬不弄清楚你是谁谁也别想进门。1.3 适合谁读要什么前置知识如果你从事运维、大数据开发、信息安全相关的工作Kerberos是绕不开的。Hadoop生态圈的HDFS、YARN、HBase企业级的Active Directory以及Linux下的SSH认证底层都有Kerberos的身影。这篇文章不讲具体的集群配置而是补上底层的“为什么”这样你看任何一套基于Kerberos的文档都会轻松很多。前置知识其实不需要太多有个概念性的了解就行对称加密加密和解密用同一把密钥、非对称加密的基本思想、以及哈希函数把任意长度的内容变成固定长度的字符串。Kerberos主要依赖对称加密所以重点理解“双方知道同一把密钥”这件事就够了。2. KDC服务的核心组件与关键概念2.1 KDC一个服务干三份活KDC是整个Kerberos体系的心脏它不是一个单一功能的程序而是三个角色的集合认证服务ASAuthentication Service、票据授权服务TGSTicket Granting Service、以及Kerberos数据库。AS负责处理客户端的初始认证请求。当你说“我要登录”AS收到请求后去数据库里查你的主体Principal是否存在、密码是否正确确认无误后签发TGT给你。TGS负责处理后续的票据换取请求。当你拿着TGT说“我要访问HDFS的NameNode”TGS验证TGT合法之后签发一张服务票据给你。数据库则保存着所有主体的信息——用户名、密钥材料、策略配置等是KDC的唯一数据源。这三个角色可以共用一个进程也可以拆开部署但通常在企业内部是整合在一起的。DN或叫AD域里域控服务器本身就承担了KDC的角色底层走的也是Kerberos这套逻辑。从结构上理解KDC的意义在于它是一个无状态的认证中心。KDC不记录“谁当前在线”因为所有必要信息都打包在票据里面了。你拿到TGT之后KDC下一次见你可能已经隔了很长时间它不需要记住你这个人只认票据本身。这个设计让KDC非常适合水平扩展也是Kerberos得以在企业里大规模落地的关键原因。2.2 四个你必须记住的名词Principal主体是Kerberos里的身份标识。它的格式长这样用户名/实例REALM比如admin/adminEXAMPLE.COM或hdfs/namenode01EXAMPLE.COM。用户名是核心实例通常用来区分服务类型或角色REALM是领域名类似DNS域的概念。约定俗成REALM通常大写。Key密钥分成两类长期密钥和短期会话密钥。长期密钥由口令经过特定算法String2Key派生比如你把密码Mypass123交给算法得到一串二进制字符串这就是长期密钥。KDC数据库里存的就是这种派生后的密钥而不是密码明文。会话密钥则是由KDC临时生成的随机密钥只对某一次会话有效有效期很短用完即弃。Ticket票据是Kerberos体系里的通行证。它分两种TGT和服务票据。TGT是AS签发的初始票据证明“你通过了认证”服务票据是TGS签发的证明“你有权访问某个服务”。票据本身是加密的客户端拿到后无法查看内部内容只能原样保存、原样提交。Authenticator认证器可能很多人容易忽略。票据本身只能证明“KDC曾经给谁发过票”但证明不了“现在提交票据的人就是票据的主人”。认证器就是用来补上这个漏洞的。客户端生成一个包含当前时间戳和用户信息的结构用会话密钥加密后附在票据后面服务端解密确认——你是真实持有会话密钥的人那么你就可以对号入座了。2.3 密钥体系不传密码的底层逻辑Kerberos不传密码的关键就在于它构造了一套“分层密钥”体系。客户端和KDC共享长期密钥来自口令目标服务和KDC共享长期密钥来自服务主体的keytab而客户端和目标服务之间没有直接的共享密钥。三者之间的信任全部通过KDC这个中间人串联起来。举个最直白的例子。A要向B证明自己的身份但A和B事先没有约定过任何共享密钥。A找到KDCKDC同时认识A和B于是KDC给A签发了一张票据里面包含一个临时生成的会话密钥。这张票据用B的长期密钥加密A看不到内容但可以原样提交给B。B用自己的长期密钥解开票据取出会话密钥再用这个会话密钥去验证A提交的认证器。整个过程里A和B不需要事先知道对方的任何秘密但它们都信任KDC。KDC就是那把万能钥匙所有人都信任它它又认识所有人。这种设计的妙处在于密码永远不会出现在网络上。即使有人抓包他最多看到一堆加密后的票据和认证器没有长期密钥根本解不开。除非他把整个KDC打穿否则在传输层面是安全的。3. Kerberos认证流程一步步拆解3.1 第一回合AS_REQ和AS_REP换来TGT整个Kerberos认证流程可以从你敲下kinit命令开始。客户端会向KDC的AS服务发送一个AS_REQ请求请求内容包含你的Principal名称、请求的服务默认是krbtgt也就是TGT的“服务名”、以及一个加密的时间戳。KDC收到请求后去数据库里查你这个Principal是否存在。存在的话KDC用你的长期密钥由你输入的口令派生而来解密请求里的时间戳如果能解出来就证明“持有口令的人”就是你自己。验证通过后AS会生成一张TGT。TGT的构造很讲究里面包含你的身份信息、会话密钥这是KDC生成的随机密钥叫TGT会话密钥、票据的有效期等。整张TGT用KDC自己的长期密钥加密也就是krbtgt账户的密钥然后AS_REP响应里包含TGT和一份“用你的长期密钥加密的会话密钥副本”。客户端收到后用自己口令派生出的长期密钥解开响应拿到TGT会话密钥。至于TGT本身客户端解不开也不需要解开存起来就行。为什么AS_REP要用客户端长期密钥加密一块会话密钥因为客户端需要知道会话密钥才能用它构造后面的认证器。如果直接把会话密钥明文放在响应里被监听就白搭了。所以Kerberos在每一步都保持了“加密分发”的规矩谁也不吃亏谁也不泄露。第一次认证过程可以理解为你去行政那里开访客通行证。你出示工牌口令派生密钥证明身份行政核实后给了你一张盖了章的通行证TGT和一张写着“有效期限”的小纸条会话密钥副本。通行证本身是加密材料你碰不到里面的字但小纸条记录了你和行政之间的暗号后面你拿着通行证去别处办事要靠这个暗号来配合。3.2 第二回合TGS_REQ和TGS_REP换来服务票据拿到TGT之后你终于可以申请访问具体服务了。假设你想访问HDFS的NameNode节点上的服务主体hdfs/namenode01EXAMPLE.COM客户端会向KDC的TGS服务发送TGS_REQ请求。这个请求包含两个关键部分一个是你之前拿到的TGT原样转发另一个是Authenticator用TGT会话密钥加密包含当前时间戳和你的身份信息。TGS收到请求后用KDC自己的长期密钥解开TGT确认票据没有被篡改且没有过期。接着用TGT里的会话密钥解开Authenticator验证时间戳是否在允许窗口内。两层验证都通过TGS才会为你签发服务票据。服务票据的构造和TGT类似但它用目标服务的长期密钥加密里面包含一个新的会话密钥服务会话密钥这个密钥专门用于你和目标服务之间的通信。TGS_REP响应同样包含两部分服务票据和一份用TGT会话密钥加密的会话密钥副本。客户端用TGT会话密钥解开副本拿到服务会话密钥同时保存服务票据。这一步的设计思路非常精彩。TGS并不需要知道你是谁它只需要验证你的TGT是否合法、是否过期。TGT里面已经包含了KDC先前认证过你的结论。这种“传递信任”的机制让KDC不需要保存任何会话状态——判断TGT是否有效只看票据本身省去大量查询开销。3.3 第三回合AP_REQ和AP_REP访问目标服务到了这一步你手里有两样东西服务票据和会话密钥。客户端向目标服务发送AP_REQ请求内容同样由两部分构成服务票据和Authenticator。不过这个Authenticator用的是服务会话密钥加密而不是TGT会话密钥。目标服务收到请求后用自己的长期密钥对应keytab里的服务密钥解开服务票据取出里面的会话密钥和你的身份信息。紧接着用这个会话密钥去解Authenticator验证时间戳并确保提交请求的人确实持有会话密钥。验证通过业务请求就放行了。如果服务端开启双向认证Mutual Authentication目标服务还需要生成一个AP_REP响应用会话密钥加密服务端的时间戳返回给客户端。客户端解密后确认为当前时间才能确定对方也是正牌服务而不是中间人顶替的伪造节点。在真实生产环境里尤其是访问HDFS、YARN这类敏感组件时我强烈建议开启双向认证避免客户端把票据交给一个伪装的服务端。这里还有一个容易被忽略的安全机制重放防护。攻击者可以抓包截获某个合法的AP_REQ请求然后重新发送给服务端这就是重放攻击。Kerberos的防线在于认证器里的时间戳——服务端检查时间戳是否在当前时间窗口内重复发送同一个请求时时间戳已经过期服务端会直接拒绝。所以后面你会看到Kerberos对时钟同步的依赖有多强这也是生产环境里最常见的坑之一。3.4 再把完整流程串讲一遍把这三回合放到现实场景里就是这样的故事。你入职新公司第一天去HR办入职HR核对身份后给你发了一张员工卡TGT。员工卡上写了你的姓名、工号和有效期但员工卡本身是经过特殊防伪处理的你并不知道里面具体编码规则只知道刷卡能过闸机。第二天你要去18楼的财务部报销财务部不认识你。你刷员工卡进闸机后闸机自动向物业中心TGS核实这张员工卡的真实性和有效期确认无误后物业中心开出一张“访问财务部的专用通行证”服务票据给你。你拿着这张专用通行证到财务部前台前台验证通行证没问题又要求你报出工号并输入验证码Authenticator一切都对得上你才能进门办业务。整个流程里员工卡和专用通行证都离不开“共同信任的第三方”——物业中心。员工卡只在第一次刷的时候发行后面重复使用专用通行证则每次去不同部门都要单独申请。这套机制带来的最直接好处就是你不用每到一层楼就把身份证押给人家也不用每个系统保存一份你的密码。一次认证到处通行这就是单点登录的核心价值。4. 时间同步、票据生命周期与单点登录4.1 时间戳机制与时钟偏移Kerberos整个安全体系的正常运行依赖所有参与方客户端、KDC、服务端的系统时间基本一致。默认情况下Kerberos允许5分钟的时钟偏移clock skew也就是说客户端时间和KDC时间相差不能超过5分钟否则认证直接失败。为什么对时间这么敏感因为时间戳是防重放攻击的基石。前面提到认证器里包含客户端生成时间的时间戳服务端通过校验时间戳来判断这个请求是不是“新鲜”的。如果允许请求方提交任意过去时间的时间戳攻击者就能把截获的合法请求反复重放。所以Kerberos把时间窗口压得非常紧时间差超出容忍范围就拒绝。生产环境里最常见的认证失败原因之一就是时钟不同步。尤其当你管理几十上百台节点的集群时某台机器如果没配置NTP同步时间慢慢漂移过了一段时间后所有基于Kerberos访问这台机器上服务的请求都会报错Clock skew too great。排查这类问题第一件事就是检查所有节点的时间同步状态。实操建议集群内所有节点必须配置统一的NTP服务使用同一时间源。严格一点的场景我见过有的团队把libdefaults里的clockskew参数调成300秒以下进一步收紧窗口。但调小这个参数要谨慎如果某些客户端到KDC的网络跳数过多、延迟过高反而会导致正常请求被拒。4.2 票据生命周期比想象中更细票据不是一个永远有效的通行证它有严格的生命周期管理。核心参数包括票据有效期ticket_lifetime、最长可续期时间renew_lifetime、以及是否允许续期renewable。默认情况下票据有效期一般在10到24小时之间。AD域的默认TGT生命周期是10小时Linux环境下的默认值通常由/etc/krb5.conf里的ticket_lifetime决定。服务票据的有效期通常比TGT短因为服务票据是一次性的、面向单个服务的凭证用完就丢生命周期短一点更安全。有效期到了之后怎么办如果票据设置了renewable标志你可以在票据过期前的任意时间向KDC申请续期。这样做的意义在于你的密码没有被重新输入但KDC基于原始票据的合法性和会话延续性给你发一张新的票据。这个设计非常实用比如跑一个超过24小时的Spark作业作业运行期间不可能让人工介入重新认证这时提前规划好可续期票据就是标准打法。我在实际生产里经常看到有人对票据生命周期配置不当导致夜间批处理作业跑了一半就卡住。问题出在作业启动时申请了TGT但没配置renew结果TGT在作业跑完之前就过期了。而且Kerberos的很多客户端组件并不会主动去续期你得提前想清楚作业的预期运行时长再决定票据有效期设多长、要不要开启renewable。4.3 SSO是怎么实现的单点登录SSO是Kerberos最直观的体验优势。它的实现机制并不神秘核心就在于TGT的“传递信任”能力。你只需要在会话开始时认证一次KDC发给你一张TGT。之后访问任何由Kerberos保护的服务客户端都会自动用TGT去TGS换取对应的服务票据全程无感。哪怕你先后访问十个服务客户端也只需要在第一次认证时输入密码剩下的九次全部通过TGT自动完成换票。对比一下传统认证模式每访问一个系统都要重新输入一次用户名密码而且每个系统都要单独保存密码安全风险极大。Kerberos通过TGT这一层抽象的凭证把“认证”和“授权”拆开了。认证只做一次授权则通过不同服务的票据动态发放。这个理念后来也影响了很多现代身份认证体系的设计。5. 常见问题与排查技巧实录5.1 一次真实集群的犯错经历前两年帮一个团队排查过Hadoop集群的Kerberos认证异常。现象很典型某台新扩容的DataNode起不来日志里持续报认证失败。查了一圈发现新节点的/etc/krb5.conf里REALM写错了少了个后缀。就这么一个看起来不值一提的小配置整整折腾了一下午。类似这种坑我在生产环境里踩过太多次了所以我把常见错误和排查思路整理成了一张速查表供大家直接参照。5.2 常见错误速查表错误现象根本原因解决方法kinit: Client not found in Kerberos database主体在KDC数据库中不存在用kadmin.local或kadmin创建对应principal确认REALM大小写无误kinit: Clock skew too great客户端与KDC时间差超出容忍窗口统一配置NTP时间同步检查各节点时间偏移kinit: Preauthentication failed口令错误或keytab与principal不匹配核对密码确认keytab对应的principal和密钥版本kvno正确klist: Credentials cache file not found当前用户没有获取过票据缓存文件不存在先执行kinit获取TGT再使用klist查看Server not found in Kerberos database目标服务主体未创建用kadmin创建对应服务主体如HTTP/hostREALMCannot determine realm for host主机名解析不到对应REALM检查krb5.conf的domain_realm映射确认DNS解析正常访问服务时间歇性认证失败票据过期或正在续期检查票据有效期配置必要时为长任务开启renewable这张表覆盖了90%以上的日常问题。遇到报错第一步永远不是去翻代码而是先看日志。KDC日志默认会写在/var/log/krb5kdc.log里面会明确告诉你到底是哪一步验证失败了。我见过太多人对着客户端日志瞎猜其实服务端日志早就把答案写好了。5.3 一把梭从零搭建KDC的参考步骤理论知识聊完最后给一套最小可用的KDC搭建步骤。环境假设为CentOS/RHEL系列REALM使用EXAMPLE.COM。安装软件包修改配置文件创建数据库启动服务最后添加主体和导出keytab。具体如下# 安装软件包 yum install krb5-libs krb5-server krb5-workstation -y # 编辑 /etc/krb5.conf核心配置如下 # [libdefaults] # default_realm EXAMPLE.COM # dns_lookup_kdc false # dns_lookup_realm false # ticket_lifetime 24h # renew_lifetime 7d # forwardable true # # [realms] # EXAMPLE.COM { # kdc kdc.example.com # admin_server kdc.example.com # } # # [domain_realm] # .example.com EXAMPLE.COM # example.com EXAMPLE.COM # 创建KDC数据库-s表示生成stash文件 kdb5_util create -s -r EXAMPLE.COM # 启动服务 systemctl start krb5kdc systemctl start kadmin # 创建管理员主体 kadmin.local -q addprinc admin/admin # 创建服务主体并导出keytab到指定文件 kadmin.local -q addprinc -randkey HTTP/web01.example.com kadmin.local -q ktadd -k /etc/krb5.keytab HTTP/web01.example.com这套步骤跑通之后一个最小可用的KDC环境就起来了。客户端在/etc/krb5.conf里指向这个KDC就能通过kinit完成认证。日常开发调试时建议用kadmin.local直接在KDC服务器上进行管理操作避免额外配置kadmind的ACL权限。5.4 KDC部署的经验之谈部署KDC本身不难难的是把安全和可用性做到位。有几个关键点值得专门提一下。KDC是典型的“单点信任”架构整个集群的认证都依赖它。生产环境一定要做高可用至少主备两个KDC节点数据通过kprop定期同步。客户端配置文件里可以配置多个KDC地址这样主KDC挂了也能自动切换。KDC机器本身的安全级别要高于普通节点。它存储着所有主体的长期密钥一旦被攻破整个域就完蛋了。所以建议从网络层面严格控制KDC的访问来源只放行必要的端口88端口用于认证流量749端口用于admin管理。KDC机器上不要跑无关服务能不开的公网端口一律不开。keytab文件的使用要格外小心。它相当于服务的长期密钥权限必须严格控制文件权限至少要设成600并且只能由运行服务的账户读取。曾经有一次集群出现神秘的服务认证失败最后排查发现是有个脚本把keytab文件权限改成了644别的进程也能读破坏了文件的一致性。这类小节看起来不起眼但恰恰是生产环境里最容易踩的雷。日常运维还需要注意密钥轮换。长期不更换keytab文件和主体密钥时间越长泄露风险越大。建议制定一个周期性的密钥轮换计划轮换时要协调好所有依赖该服务的客户端否则会出现“KDC侧换过密钥、客户端还在用旧keytab”的错配情况具体表现就是认证时好时坏、非常难以排查。5.5 用日志定位问题的完整思路遇到Kerberos认证问题我的排查顺序永远是先看KDC日志再看客户端日志最后才去怀疑网络和配置。KDC日志会记录每一条AS_REQ和TGS_REQ的处理结果。如果认证失败日志里会有明确的失败原因常见的有CLIENT_NOT_FOUND、BAD_KEY_VERSION、CLOCK_SKEW等。每一条错误都有一个明确的对应关系比如BAD_KEY_VERSION说明主体存在但KDC里存储的密钥版本和客户端keytab里的版本对不上这时候用kadmin.local -q cpw -randkey principal重置密钥版本再重新导出keytab就行。客户端日志一般在服务的日志目录里Hadoop组件会有专门的认证日志。如果客户端日志里出现Unable to obtain password from user通常意味着keytab文件路径配置错误或文件不可读。如果出现GSSException: Defective token detected通常是SPNEGO token格式不对多半是客户端和服务端支持的加密类型不匹配需要在krb5.conf里显式指定enctypes。这套排查思路是通用的不管你用的是Hadoop、Flink还是普通的SSH底层的Kerberos日志逻辑都完全一致。把诊断顺序理顺了再复杂的认证问题也只是时间问题。写到这里我得说一句掏心窝子的话Kerberos这套协议放到今天来看部分设计确实有些年头了但它的分层信任模型、票据传递机制、无状态认证思路直到现在都是身份认证领域最扎实的教科书级方案。理解了它你再看OAuth2.0、OIDC、SAML这些新协议会发现很多地方都有Kerberos的影子——都是想办法把“证明身份”和“授权访问”拆开再用一个可信第三方串起来。技术会迭代但这些底层的东西不会过时。以后遇到Kerberos相关的报错别急着搜索先静下心来捋一遍整个流程大部分问题自己就能定位清楚。
返回列表