文章摘要
飞宇 GPT

引言:Hadoop 默认的”假安全”

很多人第一次接触 Hadoop 安全时都会有一个错觉——集群装好了,NameNode 起来了,HDFS WebUI 也能访问,”应该挺安全的吧”。实际上,开箱即用的 Hadoop 是一个典型的”假安全”环境:只要你能在某台机器上执行 hdfs dfs -ls /,你就是超级用户。

默认情况下,Hadoop 用的是 SIMPLE 认证。它的逻辑非常朴素——拿操作系统当前用户名(whoami)当身份,直接相信。这意味着什么?我在任意一台机器上 useradd hdfs 然后 su - hdfs,就能以 hdfs 这个超级用户身份读写整个 HDFS。在几百台机器、几十个业务方共用集群的企业环境里,这种”信任本机用户名”的模型是不可接受的。

企业级的安全需求其实就两条:

  1. 认证(你是谁):服务与服务、人与服务之间必须 mutually verify,身份不能伪造。
  2. 授权(你能做什么):哪怕你是合法用户,也只能访问被授权的资源,且每一次访问都要被审计。

Hadoop 生态给出的标准答案是:Kerberos 做认证,Ranger/Sentry 做授权,审计日志全程留痕。这篇文章是我这几年在生产集群上落地这套体系的一次复盘,重点把原理讲透、把坑标出来,给后来人一条能走的路径。


Kerberos 原理:把三方握手讲明白

Kerberos 是 MIT 设计的网络认证协议,核心思想是用一个可信第三方(KDC)来发放”票据”,让通信双方不用直接共享口令也能相互确认身份。Hadoop 用的就是它。

关键角色与概念

先把这个名词表理清楚,不然后面看配置全是天书:

角色/概念 含义
KDC(Key Distribution Center) 可信第三方,部署在 KDC 服务器上,由 AS + TGS + 数据库组成
AS(Authentication Server) 认证服务器,负责给用户发放 TGT
TGS(Ticket Granting Server) 票据授权服务器,负责给具体服务发放 Service Ticket
Principal Kerberos 里的”账号”,格式 primary/instance@REALM
Realm Kerberos 的管理域,习惯上全大写,如 EXAMPLE.COM
Keytab 存放 principal 与对应密钥的文件,让服务/脚本无需人工输入口令
TGT(Ticket Granting Ticket) AS 发给用户的”身份凭证”,拿它去换具体服务的 ticket
Service Ticket 访问某个具体服务时出示的票据
Session Key 通信双方在本次会话里用的对称密钥

三方握手流程

以”用户 alice 要访问 NameNode”为例,完整的认证链路是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
alice           AS              TGS          NameNode
| | | |
|--1.认证请求-->| | |
| (用alice口令加密的预鉴别) | |
| | | |
|<--2.TGT-------| | |
| (用alice密钥加密,含session key)| |
| | | |
|--3.请求NN ticket(tgt)--------->| |
| | |
|<--4.NN Service Ticket---------| |
| (alice/NN共享的session key) | |
| | |
|--5.出示Service Ticket------------------------>|
| | 6.验证通过 |
|<--7.建立加密通信会话-------------------------|

拆开讲:

  1. alice → AS:alice 本地用自己口令派生密钥,向 AS 请求 TGT。AS 在自己数据库里查 alice,验证预鉴别字段。
  2. AS → alice:AS 返回两样东西——一份给 alice 的会话密钥(用 alice 密钥加密)、一份 TGT。TGT 里装着 alice 的身份和会话密钥,用 TGS 自己的密钥加密——也就是说,alice 解不开 TGT,但能拿着它去 TGS 兑换。
  3. alice → TGS:alice 拿 TGT + 想访问的服务名(nn/nn-host@REALM)请求 Service Ticket。
  4. TGS → alice:TGS 解开 TGT 拿到会话密钥,用它加密一份新的 alice-NameNode 会话密钥,连同 Service Ticket(用 NameNode 的密钥加密)返回。
  5. alice → NameNode:alice 把 Service Ticket 发给 NameNode,附上用会话密钥加密的 authenticator(含时间戳)。
  6. NameNode 验证:NameNode 用自己 keytab 里的密钥解开 Service Ticket,拿到会话密钥;再用会话密钥解开 authenticator,校验时间戳是否在允许偏差内(防重放)。通过即认证成功。
  7. 后续通信用会话密钥加密(可选)。

这套设计的精妙之处在于:alice 的口令从不出现在网络上;NameNode 永远不需要直接和 KDC 通信来验证用户,它只要相信自己 keytab 里的密钥;TGT 短期有效(通常 10 小时),丢了风险可控。

Principal 命名约定

Hadoop 里 principal 命名是有规矩的,最常见的形式是:

1
nn/_HOST@EXAMPLE.COM
  • nn:服务名(NameNode 用 nn,DataNode 用 dn,ResourceManager 用 rm,NodeManager 用 nm,HTTP 用 HTTP)。
  • _HOST:占位符。Hadoop 启动时会把它替换成当前机器的 FQDN。这一招非常关键——它让你可以用一份配置文件下发到所有机器,每个机器自动用自己主机名生成正确的 principal。
  • @EXAMPLE.COM:Realm。

用户 principal 没有服务名,就是 alice@EXAMPLE.COMalice/admin@EXAMPLE.COM

Renewable Ticket 与 keytab

生产环境里,长跑任务(比如一个跑 8 小时的 Spark 作业)如果 ticket 过期就崩了。Kerberos 给了两条退路:

  • Renewable Ticket:TGT 在 maxrenewablelife(比如 7 天)内可以被续约,不用重新输口令。kinit -R 就是续约。
  • keytab:把 principal 的密钥导出到文件,程序用 UserGroupInformation.loginUserFromKeytab() 自动登录,到期自动续。

Hadoop 集成 Kerberos:从配置到落地

核心开关:core-site.xml

认证模式是全局开关,在 core-site.xml 里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<property>
<name>hadoop.security.authentication</name>
<value>kerberos</value>
</property>
<property>
<name>hadoop.security.authorization</name>
<value>true</value>
</property>
<property>
<name>hadoop.security.auth_to_local</name>
<value>
RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//
DEFAULT
</value>
</property>
<property>
<name>hadoop.rpc.protection</name>
<value>authentication,integrity,privacy</value>
</property>
  • authentication=kerberos + authorization=true 是开 Kerberos 的组合拳,缺一不可。
  • auth_to_local 规则把 alice@EXAMPLE.COM 这种完整 principal 映射成本地系统用户 alice,这是 HDFS 文件 owner、YARN 队列权限判定的依据。
  • rpc.protection 决定 RPC 通信是只认证、还是认证+完整性校验、还是认证+加密。生产建议开到 privacy,但会有 10% 左右的 CPU 开销

各组件的 principal 与 keytab

每个服务进程都需要一个属于它自己的 keytab。典型清单:

服务 Principal 常用 keytab 路径
NameNode nn/_HOST@REALM /etc/security/keytabs/nn.service.keytab
DataNode dn/_HOST@REALM /etc/security/keytabs/dn.service.keytab
JournalNode jn/_HOST@REALM /etc/security/keytabs/jn.service.keytab
ResourceManager rm/_HOST@REALM /etc/security/keytabs/rm.service.keytab
NodeManager nm/_HOST@REALM /etc/security/keytabs/nm.service.keytab
HTTP (SPNEGO) HTTP/_HOST@REALM /etc/security/keytabs/spnego.service.keytab

下面是一段 hdfs-site.xml 的安全配置片段,覆盖 NameNode 和 DataNode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!-- NameNode Kerberos -->
<property>
<name>dfs.namenode.keytab.file</name>
<value>/etc/security/keytabs/nn.service.keytab</value>
</property>
<property>
<name>dfs.namenode.kerberos.principal</name>
<value>nn/_HOST@EXAMPLE.COM</value>
</property>
<property>
<name>dfs.namenode.kerberos.internal.spnego.principal</name>
<value>HTTP/_HTTP@EXAMPLE.COM</value>
</property>

<!-- DataNode Kerberos -->
<property>
<name>dfs.datanode.keytab.file</name>
<value>/etc/security/keytabs/dn.service.keytab</value>
</property>
<property>
<name>dfs.datanode.kerberos.principal</name>
<value>dn/_HOST@EXAMPLE.COM</value>
</property>

<!-- DataNode 必须配 SASL,否则 secure 模式下数据传输端口会起不来 -->
<property>
<name>dfs.data.transfer.protection</name>
<value>authentication</value>
</property>
<property>
<name>dfs.encrypt.data.transfer</name>
<value>true</value>
</property>

<!-- WebHDFS / NameNode WebUI 的 SPNEGO -->
<property>
<name>dfs.web.authentication.kerberos.principal</name>
<value>HTTP/_HOST@EXAMPLE.COM</value>
</property>
<property>
<name>dfs.web.authentication.kerberos.keytab</name>
<value>/etc/security/keytabs/spnego.service.keytab</value>
</property>

几个容易忽略的点:

  • DataNode 必须开 dfs.data.transfer.protection。开了 Kerberos 之后,DataNode 的数据传输端口(默认 50010)如果还用 plaintext,整个安全体系就漏了——攻击者只要知道 block id 就能直接读写 block。
  • SPNEGO 是 HTTP 上的 Kerberos 认证机制。NameNode WebUI、WebHDFS、YARN WebUI 默认是匿名的,开了 SPNEGO 之后,浏览器要装 Kerberos 支持(如 Firefox 配置 network.negotiate-auth.trusted-uris)才能访问。

principal 创建与 keytab 生成的 bash 示例

在 KDC 服务器(比如装了 krb5-server 的那台)上执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# 在 KDC 主机上以 root 运行
REALM="EXAMPLE.COM"
KEYTAB_DIR="/etc/security/keytabs"
mkdir -p $KEYTAB_DIR

# 1. 在 KDC 数据库里创建 principal
# 服务 principal 用 -randkey,避免设置口令(服务用 keytab 即可)
kadmin.local -q "addprinc -randkey nn/nn-host.example.com@$REALM"
kadmin.local -q "addprinc -randkey dn/dn-host.example.com@$REALM"
kadmin.local -q "addprinc -randkey HTTP/nn-host.example.com@$REALM"

# 2. 导出 keytab(-k 指定文件,-e 指定加密类型)
# 强烈建议固定加密类型,避免不同 JDK/MIT 版本之间不兼容
kadmin.local -q "ktadd -k $KEYTAB_DIR/nn.service.keytab -e aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96 nn/nn-host.example.com@$REALM"
kadmin.local -q "ktadd -k $KEYTAB_DIR/dn.service.keytab -e aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96 dn/dn-host.example.com@$REALM"
kadmin.local -q "ktadd -k $KEYTAB_DIR/spnego.service.keytab -e aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96 HTTP/nn-host.example.com@$REALM"

# 3. 权限与属主:keytab 文件必须只能被对应服务用户读
chown hdfs:hadoop $KEYTAB_DIR/nn.service.keytab $KEYTAB_DIR/dn.service.keytab
chown hdfs:hadoop $KEYTAB_DIR/spnego.service.keytab
chmod 400 $KEYTAB_DIR/*.keytab

# 4. 验证:列出 keytab 内容
klist -kt $KEYTAB_DIR/nn.service.keytab

# 5. 分发到对应机器(用 scp / 配置管理工具)
# 注意:传输也要加密,且不要把 keytab 拷到不相关的机器上

aes256 加密类型需要 JCE Unlimited Strength(JDK 8u161 之后默认开启,更早版本要手动装 policy 文件),否则 JVM 会报 KrbException: Encryption type AES256 CTS mode with HMAC SHA1 96 is not supported

Delegation Token:别让 KDC 成瓶颈

Kerberos 有一个天然瓶颈——所有认证都要回 KDC 兜一圈。如果每个 MapReduce/Spark task 都去拿 Service Ticket,KDC 瞬间被打爆。Hadoop 的解法是 Delegation Token

  • 客户端先通过 Kerberos 认证拿到 NameNode 的会话。
  • 然后向 NameNode 申请一个 Delegation Token(一种轻量级、长期有效的凭证)。
  • 后续 task 把这个 token 序列化到 job 的 credentials 里,直接拿 token 访问 HDFS,不再碰 KDC。

Token 默认有效期 1 天(dfs.namenode.delegation.token.max-lifetime),可续约 7 天 。长跑任务必须配 kinit -R 续 TGT、配 token renewer(mapreduce.job.hdfs-servers.token-renewal)续 token,两个续约机制是叠加的。


实战踩坑:这些坑我替你踩过了

理论都好懂,落地全是坑。下面这些是我在生产环境真踩过的,按严重程度排序。

坑 1:时钟偏差

Kerberos 的 authenticator 里带时间戳防重放,AS/TGS/Service 三方时钟必须接近。默认允许的偏差是 5 分钟(300 秒)。超过这个值直接报:

1
Clock skew too great (37) — PROCESS_TGS

生产环境强制所有节点上 chronyntp,并且要监控时钟偏差。如果实在无法对齐,可以在 krb5.conf 里放宽:

1
2
[libdefaults]
clockskew = 300

或者在 KDC 的 kdc.conf 里:

1
2
[kdcdefaults]
kdc_max_clockskew = 300

但我不建议放宽——5 分钟已经是很宽容的阈值了,放宽只会让重放攻击窗口变大。

坑 2:DNS 反向解析与 _HOST

前面说过 _HOST 占位符会被替换成 FQDN。Hadoop 这里有个隐藏逻辑——它会做反向 DNS 解析确认主机名。如果机器的反向解析没配好(比如 dig -x <IP> 返回的不是期望的 FQDN,而是某个奇怪的域名),就会出现:

  • nn.service.keytab 里导出的 principal 是 nn/nn-host.example.com@REALM
  • 但 JVM 启动时 _HOST 被解析成了 nn-host(短名)或某个错误 FQDN
  • principal 不匹配,登录失败:Server not found in Kerberos database (7)

排查办法:在出问题的机器上跑 hostname -fdig -x <本机IP>,看两者是否一致。修正 /etc/hosts 和反向 DNS 是最稳的做法。实在不行,可以在 hdfs-site.xml 里显式写死 principal(牺牲一份配置走天下的便利)。

坑 3:keytab 文件权限与属主

keytab 等于口令,权限泄露等于身份被盗用。最常见的错误是:

  • keytab 用 chmod 644,结果集群上任何用户都能 kinit -kt 冒充该服务。
  • keytab 属主写成了 root,HDFS 进程以 hdfs 用户跑,读不了。

正确做法见上面的脚本:chmod 400 + chown <服务用户>:hadoop。建议加一道监控——定期 find /etc/security/keytabs -perm /o+r,发现可被其他用户读的 keytab 立即告警。

坑 4:User Impersonation(代理用户)

很多上层组件(HiveServer2、Oozie、Presto)需要以”自己的身份”登录 Kerberos,然后代理真实用户去访问 HDFS。比如 HiveServer2 用 hive principal 登录,但执行查询时要以 alice 的身份读写 /user/alice/warehouse

这个能力必须在 core-site.xml 里显式授权,否则 HiveServer2 默认就只能以 hive 自己的身份操作:

1
2
3
4
5
6
7
8
9
10
11
12
<property>
<name>hadoop.proxyuser.hive.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.hive.groups</name>
<value>data_analyst,bigdata</value>
</property>
<property>
<name>hadoop.proxyuser.hive.users</name>
<value>alice,bob,carol</value>
</property>

上面的配置允许 hive 用户从任意 host 代理 data_analyst/bigdata 组的用户,或显式列出的 alice/bob/carol。生产里 hosts 不要写 *,至少要限定到 HiveServer2 所在机器。

坑 5:Ticket 过期与自动续约

TGT 过期是长跑任务的头号杀手。症状是任务跑一半,HDFS 报:

1
2
Failed on local exception: java.io.IOException: org.apache.hadoop.security.AccessControlException:
Client cannot authenticate via Kerberos: KrbException: Identifier doesn't match expected value ...

解决办法:

  • 服务进程:用 keytab 登录,UserGroupInformation 内部会自动续约(前提是 ticket 是 renewable)。
  • 交互式用户 / 提交 job 的网关机:配 k5startrenewd 后台定期 kinit -R
  • Cron 兜底0 */6 * * * kinit -R 每 6 小时续约一次
  • 检查 renewableklist 输出里如果有 renew until 时间,说明 renewable 生效;如果没有,检查 krb5.confmaxrenewablelifekdc.confmax_life/max_renewable_life

坑 6:KDC 单点

KDC 挂了 = 全集群没法新登录(已登录的 ticket 在有效期内还能用)。这个绝对不能单点。MIT Kerberos 支持主从 KDC,配合 kprop 增量同步数据库;或者用 Active Directory 当 KDC(Windows 环境常见)。建议至少主从两台,监控 krb5kdc 进程和同步延迟。


Ranger:统一授权与审计

认证解决”你是谁”,授权解决”你能做什么”。Hadoop 原生的授权是散落在各组件里的——HDFS 有 POSIX 权限和 ACL,Hive 有 SQL 标准的 GRANT/REVOKE,Kafka 有 ACL……运维要给一个新业务方开通权限,得在十几个地方配置,痛苦且易错。

Ranger(Apache 项目,最初 Hortonworks 主导)的价值就是把这些授权统一到一个面板。

Policy 模型

Ranger 的核心是 Policy,每个 policy 由三部分组成:

组成 说明
Resource 被保护的对象,如 HDFS 的 /finance/*、Hive 的 db.table.col、Kafka 的 topic
Condition 匹配条件,如访问者所属组、来源 IP、时间窗口
Allow / Deny 允许或拒绝的权限组合,如 read/write/execute

一个 policy 可以同时配 allow 和 deny 列表。Deny 优先于 allow——这是 Ranger 区别于 POSIX 的关键。HDFS 的 POSIX 只有 allow 模型,一旦给了 owner 就只能靠改 owner 收回;Ranger 可以用一条 deny policy 直接拉黑。

插件架构

Ranger 不直接拦截请求,而是把策略下发到各组件的插件,由插件在请求路径上做本地决策:

1
2
3
4
5
6
HDFS NameNode / HiveServer2 / Kafka Broker / HBase RegionServer
| | | |
+--------------+----poll------> Ranger Admin Server
| |
v v
本地策略缓存 + 审计日志异步回传

这种”插件本地决策 + 审计异步上报”的设计避免了授权变成单点,但有个副作用——策略更新有延迟(默认 30 秒拉一次 ),紧急拉黑不能立刻生效。

Ranger 与 HDFS POSIX/ACL 的关系

这是最容易混的地方。开了 Ranger HDFS 插件后,HDFS 的访问控制其实走两层:

  1. Ranger 策略检查:如果命中任何 deny policy,直接拒绝;如果命中 allow policy,直接放行。
  2. 如果 Ranger 没有匹配的 policy,回退到 HDFS 原生 POSIX/ACL

也就是说 Ranger 不是替换 POSIX,而是覆盖在其上。Ranger 里有开关 Enable Native Authorizationxasecure.add-hadoop-authorization)控制是否回退。生产建议关掉回退,所有权限都收到 Ranger 里管,否则两套体系混着,审计和排错会让人崩溃。

审计日志

Ranger 的审计日志是它相对原生 ACL 最大的加分项。每一次 access(不论 allow 还是 deny)都会记录:

  • 谁(user/group)、什么时间、从哪个 IP、访问了什么 resource、做了什么操作、结果是 allow 还是 deny、命中的是哪条 policy。

日志默认存到 Solr,也支持 HDFS / Elasticsearch。这份审计数据对合规(GDPR、等保)是刚需。

行级 / 列级权限

HDFS 的权限粒度只能到文件,但 Hive 表经常需要更细——比如财务表里 salary 列只能 HR 看,或者订单表里 region=华东的数据只能华东团队看。Ranger 提供:

  • 列级 masking:对敏感列做脱敏(hash、null、partial mask)。
  • 行级 filtering:基于条件的行过滤,比如给 HR 组配上 WHERE department = 'HR'

这两项在数据中台里用得非常频繁,是把 Ranger 从”运维工具”升级成”数据治理平台”的关键能力。


HDFS 权限体系:POSIX + ACL

即便用了 Ranger,理解 HDFS 原生权限仍然是基础,因为 Ranger 没覆盖的地方还要靠它兜底。

POSIX 权限

和 Linux 一样,三组 owner/group/other,每组 rwx:

1
drwxr-xr-x   alice  bigdata      /user/alice

HDFS 的 r 决定能否读和 list,w 决定能否写和创建/删除子项,x 对目录决定能否进入、对文件一般无意义。超级用户是 hdfs(NameNode 进程的运行用户)。

POSIX 的痛点很明显:只能给 owner、group、other 三类主体授权,无法给”既不是 owner 也不在 group 里的特定用户”单独给权限。

ACL(Access Control List)

HDFS 支持 POSIX ACL 扩展,给特定用户/组单独授权:

1
2
3
4
5
6
7
8
# 给 bob 单独授予 /shared/data 的读写
hdfs dfs -setfacl -m user:bob:rw- /shared/data

# 给 finance 组只读
hdfs dfs -setfacl -m group:finance:r-- /shared/data

# 查看
hdfs dfs -getfacl /shared/data

ACL 解决了细粒度问题,但有代价:

  • NameNode 内存占用增加(每个文件多带一份 ACL)。
  • 默认权限计算变复杂,容易出错。

生产建议:ACL 用作”例外授权”,大规模权限治理还是交给 Ranger。NameNode 上 dfs.namenode.acls.enabled 要显式打开才能用。


落地路径建议:先认证,后授权,灰度开关

把这么多东西塞到一个跑着几百个业务的集群上,绝不是一次性切换。我推荐的路径:

  1. 预生产集群全量验证:在等规模集群上完整跑通 Kerberos + Ranger,把所有业务作业(关键路径、ETL、Ad-hoc)都跑一遍,先发现明显问题。
  2. Kerberos 先行:先只开认证,不开 Ranger。这一步最大的震动是上层组件的代理用户配置和长任务续约。监控 KDC QPS、登录失败率。
  3. Ranger 灰度接入:从一个组件开始(通常是 Hive,因为业务方痛点最明显),逐个组件开插件。开之前先把现有 POSIX/ACL 权限导出成 Ranger policy,保证切换前后行为一致。
  4. 审计先行,强制在后:Ranger 插件可以开 audit only 模式,只记日志不强制。先跑一周,看日志里有没有预期外的 deny,再切换到 enforce。
  5. 回滚预案:每个开关都有对应的回滚配置(关掉 hadoop.security.authorization、关掉 Ranger 插件)。灰度期内 24 小时 oncall,准备好配置回滚脚本。

一条经验:认证强、授权松,比认证松、授权强要安全。先把身份认证做扎实,授权可以慢慢调;反过来则形同虚设。


小结

Hadoop 安全体系不是某一个开关,而是一条从身份到资源、覆盖每一次访问的链路:

  • Kerberos 解决”你是谁”——通过 KDC 三方握手 + principal/keytab + TGT/Service Ticket,让服务与服务、人与服务之间相互验证身份。理解了 _HOST 占位符、renewable ticket、auth_to_local 映射,配置就不慌了。
  • Ranger 解决”你能做什么”——用统一的 policy 模型覆盖 HDFS/Hive/Kafka/HBase,用 deny 优先 + 审计日志把治理能力拉满。
  • HDFS POSIX + ACL 是兜底的原生权限,理解它是排错的基础。

落地这套体系,技术之外更难的是组织和流程:每个业务方都要改连接串、配 keytab、调续约;权限梳理要和业务方逐条对账。但一旦跑通,集群才真正具备了承载多租户、敏感数据、合规审计的能力——这才是企业级大数据平台的地基。把坑踩在前面,把灰度做扎实,这条路是走得通的。