
从 2018 年开始在大规模 CDH 集群上做运维我最大的痛苦来源不是 HDFS NameNode 的 Full GC也不是 Yarn 资源池调参而是账号体系乱成一锅粥。开发提工单要开 CM 账号临时同事走了账号还在安全审计一问三不知密码策略形同虚设。后来我终于狠下心把 Cloudera ManagerCM的认证全部收到 OpenLDAP 上过程不算复杂但坑的确不少。这篇就完整记录一下我是如何为 Cloudera Manager 集成 OpenLDAP 认证的包括服务端目录设计、CM 配置参数逐项解释、加密链路处理以及几个典型问题的排查链路。这个方案适合手里已经有 CDH/CM 集群、想用统一身份源收敛平台账号的运维同学也适合公司暂时没有 AD 域控、打算先用 OpenLDAP 把认证体系搭起来的场景。1. 为什么我坚持把CM账号体系收口到OpenLDAPCM 默认用的是它内置的用户数据库管理员在页面上手动创建用户、分配角色。单机小集群这么搞没问题一旦集群上了规模、部门里几十号人都有登录需求手动模式就会迅速失控。我最早遇到的典型场景是这样的A 同学入职申请了 CM 账号我给了管理员角色方便他排查作业问题三个月后他转岗了账号没人回收半年后安全审计问我“这个账号为什么还有 Admin 权限”我根本答不上来。这种事情发生两三次之后我就下定决心必须把认证跟授权拆开认证交给公司统一的身份源CM 只负责根据外部身份映射本地角色。这里顺带解释一个容易混淆的点CM 集成 LDAP 之后LDAP 只负责“你是不是你”认证不负责“你能干什么”授权。用户第一次通过 LDAP 登录时CM 会自动在内部用户库里创建一条记录然后按规则给他分配默认角色如果 LDAP 里也配了组CM 还可以通过组的过滤结果做更细的授权映射。也就是说账号生命周期跟着 LDAP 走权限还是由 CM 控制两者分工明确。对比一下当时的几个候选方案方案优点缺点我的选择原因CM 内置账号部署简单开箱即用账号散乱、无法统一密码策略、审计困难只适合单机或测试环境对接 OpenLDAP开源可控目录结构灵活可根据组映射权限需要自己维护目录树和证书公司还没上 AD成本最低对接 AD 域控企业集成最强组策略丰富需要域控配合流程较重后续公司有 AD 了可以直接替换 LDAP URL事后来看这个决策是对的。集成完成之后所有集群平台账号都跟着 LDAP 走同事离职只需在 LDAP 侧禁用账号CM 这边自然登录不了审计也有了统一入口。动手之前先确认一下你的环境Cloudera Manager 版本在 6.3 或 7.x低版本通过修改配置文件实现高版本有可视化配置界面后面会分开讲。有一个可用的 OpenLDAP 服务端版本 2.4 以上即可。CM Server 到 OpenLDAP 的 389ldap或 636ldaps端口网络可达。如果这些条件不满足先把基础环境补齐否则后面配置起来会到处碰壁。2. 服务端准备目录树设计、LDIF基线数据与自检命令很多人一上来就直奔 CM 界面填配置结果 LDAP URL、Bind DN、User Search Base 这些字段全靠猜配完登录报错也不知道从哪查。我的建议是先去 OpenLDAP 服务端把目录结构设计好、把基础数据导好、把查询命令验证通再去动 CM。2.1 目录树怎么设计合适OpenLDAP 的目录树DIT没有强制的标准但规划得不好后面写过滤器会非常痛苦。我常用的设计是dcexample,dccom ├── ouPeople # 自然人用户 │ ├── uidalice │ └── uidbob ├── ouGroups # 组 │ ├── cncdh_admins │ ├── cncdh_users │ └── cnhue_users └── ouService # 服务账号 └── uidcm-ldap # CM 读取 LDAP 用的绑定账号关键点有两个。第一用户和组分开放在不同的 ou 下。这样在 CM 里配置 User Search Base 和 Group Search Base 时可以各自指定范围搜索效率高过滤器也简单。有些人图省事把用户和组混在一个 ou 里最后写过滤器时经常出现“明明查到了人却把组也过滤进来”的错乱。第二单独建一个 Service 组织单元放服务绑定账号。CM 连接 LDAP 时不能匿名 bind生产环境一般禁止需要用一个专用账号。这个账号只给它只读权限密码单独管理不要复用根管理员 cnManager 的密码。2.2 一份可以直接用的初始化 LDIF假设域名用 example.com下面这份 LDIF 是完整的基线数据包含组织节点、三个测试用户、三个组以及 CM 专用绑定账号。# 初始化基础域和 OU dn: dcexample,dccom objectClass: top objectClass: dcObject objectClass: organization o: Example Company dc: example dn: ouPeople,dcexample,dccom objectClass: organizationalUnit ou: People dn: ouGroups,dcexample,dccom objectClass: organizationalUnit ou: Groups dn: ouService,dcexample,dccom objectClass: organizationalUnit ou: Service # 测试用户 dn: uidalice,ouPeople,dcexample,dccom objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: alice cn: Alice Zhang sn: Zhang givenName: Alice displayName: Alice Zhang uidNumber: 10001 gidNumber: 10001 homeDirectory: /home/alice loginShell: /bin/bash userPassword: {SSHA}xxxxxxxxxxxxxxxxxx dn: uidbob,ouPeople,dcexample,dccom objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: bob cn: Bob Li sn: Li givenName: Bob displayName: Bob Li uidNumber: 10002 gidNumber: 10002 homeDirectory: /home/bob loginShell: /bin/bash userPassword: {SSHA}xxxxxxxxxxxxxxxxxx dn: uidcarol,ouPeople,dcexample,dccom objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: carol cn: Carol Wang sn: Wang givenName: Carol displayName: Carol Wang uidNumber: 10003 gidNumber: 10003 homeDirectory: /home/carol loginShell: /bin/bash userPassword: {SSHA}xxxxxxxxxxxxxxxxxx # 组 dn: cncdh_admins,ouGroups,dcexample,dccom objectClass: posixGroup cn: cdh_admins gidNumber: 20001 memberUid: alice dn: cncdh_users,ouGroups,dcexample,dccom objectClass: posixGroup cn: cdh_users gidNumber: 20002 memberUid: bob memberUid: carol # CM 绑定服务账号 dn: uidcm-ldap,ouService,dcexample,dccom objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: cm-ldap cn: CM LDAP Reader sn: Service uidNumber: 30001 gidNumber: 30001 homeDirectory: /nonexistent loginShell: /sbin/nologin userPassword: {SSHA}xxxxxxxxxxxxxxxxxx导入之后给用户和绑定账号设置密码注意 cm-ldap 和用户密码要分别设置# 给用户设置密码 ldappasswd -x -D cnadmin,dcexample,dccom -W -S uidalice,ouPeople,dcexample,dccom # 给绑定账号设置密码 ldappasswd -x -D cnadmin,dcexample,dccom -W -S uidcm-ldap,ouService,dcexample,dccom2.3 导入前的自检数据结构导入后先别急着配 CM先在服务端用 ldapsearch 验证最核心的两个查询能不能命中。我通常会模拟 CM 稍后要执行的搜索# 模拟“按 uid 找用户” ldapsearch -x -H ldap://ldap.example.com:389 \ -D uidcm-ldap,ouService,dcexample,dccom -W \ -b ouPeople,dcexample,dccom \ ((objectClassinetOrgPerson)(uidalice)) # 模拟“按 memberUid 找用户所属组” ldapsearch -x -H ldap://ldap.example.com:389 \ -D uidcm-ldap,ouService,dcexample,dccom -W \ -b ouGroups,dcexample,dccom \ ((objectClassposixGroup)(memberUidalice))这两个查询能返回预期结果说明目录结构、绑定账号权限、过滤器基础条件都没问题。这一步做好了后面 CM 界面上的配置就是在填“已经验证过”的值而不是在盲猜。提示CM 集成 LDAP 之后服务端目录数据的变更用户禁用、改密码、组调整会实时反映到登录环节不需要重启 CM Server。3. 在Cloudera Manager中配置LDAP认证从入口到参数逐个拆解目录数据准备妥当之后接下来进入 CM 这边的配置。不同 CM 版本的入口略有差异但核心配置项是同一套。下面以 CM 7.x 的可视化配置路径为主来说明。3.1 配置入口和基本流程在 CM 管理界面里按以下路径操作进入Administration → External Credentials选择Add类型选LDAP Server填写连接信息。进入Administration → Authentication把认证方式切换到External CredentialsLDAP。配置默认角色后保存然后用 LDAP 用户登录验证。如果你用的是旧版 CM6.x 及以下界面没有 External Credentials 菜单那就需要直接修改/etc/cloudera-scm-server/scm.properties在文件末尾追加auth.external.credentials.*相关的配置项然后重启 cloudera-scm-server 服务。底层参数两个版本是通用的下面拆解时可以对照看。3.2 CM 核心配置项逐项说明我用一张表把配置项和推荐值列出来每项后面再详细解释。这里的例子沿用 2.2 节的目录设计。配置项推荐值说明LDAP Server URLldaps://ldap.example.com:636生产建议 ldaps避免明文传输Bind DNuidcm-ldap,ouService,dcexample,dccom只读绑定账号不要用 rootdnBind Password该账号的密码定期轮换Users Search BaseouPeople,dcexample,dccom用户搜索的起点User Search Filter((objectClassinetOrgPerson)(uid{0})){0}是用户名占位符Groups Search BaseouGroups,dcexample,dccom组搜索的起点Group Search Filter((objectClassposixGroup)(memberUid{0}))根据用户查找组Username Attributeuid登录名映射到 LDAP 的哪个属性Server URL这个 URL 只写协议、主机和端口不要带任何路径。如果 LDAP 服务端配置了高可用比如用 Keepalived 虚拟了一个 VIP填 VIP 地址即可CM 没有内建 LDAP 故障转移做好前置高可用比依赖 CM 端更重要。Bind DN 和 Bind PasswordCM 需要先以这个账号的身份连上目录服务才能执行后面的用户搜索。这个账号的 ACL 只要允许查询 People 和 Groups 两个节点即可不需要写权限。用 rootdn 做 bind 是很多人踩过的坑一是密码泄露风险大二是当后续排查问题时日志里一旦出现 bind 失败第一步就会怀疑密码过期而这在 rootdn 上无从判断。User Search Filter 中的{0}这是 CM 传入的用户名占位符。用户在 CM 登录页输入什么{0}就会被替换成什么。这里需要重点强调很多 OpenLDAP 集成 AD 式登录名比如aliceexample.com失败就是因为过滤器在对比uid属性而用户输入的是带域后缀的完整名称。解决办法就是第二章说的在服务端把 Username Attribute 配好后面第 4 节还会展开讲。Group Search Filter这个过滤器的意义是“通过当前登录的用户名找到该用户属于哪些组”CM 会拿这些组去匹配后面的授权规则。OpenLDAP 下posixGroup用memberUid关联用户所以过滤器里用memberUid{0}如果是 AD通常用(member:1.2.840.113556.1.4.1941:{0})做递归组查询千万别把两边的语法搞混。3.3 切换认证方式与默认角色设定配置完 LDAP Server 之后进入Administration → Authentication把认证模式切到外部认证。保存前有一个很关键的选项Default Role for externally authenticated users也就是外部用户首次登录后默认获得什么权限。我的建议是先设为ROLE_READONLY确认登录、查询类功能都正常后再在授权管理中按用户或组提升权限。为什么这么做因为在配置阶段最怕的就是“能登录的人太多了”如果默认给一个偏高的角色测试用户一旦误操作影响面会很大。具体授权时可以到Administration → Users/Roles里找到 LDAP 登录生成的用户手动分配角色。支持按用户单独设置也支持按组批量设置。如果 LDAP 组和 CM 角色之间需要自动同步可以利用 CM 外部认证的角色映射功能把cdh_admins这个组直接映射到Cloudera Administrator角色这样以后 LDAP 侧把某个人加进组CM 这边的权限也会自动带上。提示配置过程中千万不要把自己现有的管理员账号登出。CM 切换认证模式后如果 LDAP 配置有误你会发现自己无法登录任何账号。稳妥的做法是先在测试环境模拟一遍“所有本地管理员都登出”的场景或者准备一个 LDAP 应急账号赋予管理员角色。4. 认证细节剖析加密链路、过滤器语法与用户名规范化登录功能跑通只是第一步。如果不理解认证链路里的几个关键机制后面稍微改一下目录结构、升级一下证书问题就会重新浮现。这一节把最容易出问题的三个细节单独拎出来讲。4.1 ldaps、StartTLS 与证书信任链LDAP 有 ldap、ldaps、StartTLS 三种连接形态。ldap://是明文密码和目录数据都会裸露在网络里生产环境坚决不用ldaps://是 SSL 加密连接建立阶段就完成握手端口 636StartTLS 则是先以明文连接 389 端口再通过扩展操作升级为 TLS 连接。在 CM 里用 ldaps 还是最省心的配置相对简单。但有一个绕不开的问题如果 OpenLDAP 用的是自签证书或内网私有 CA 签发的证书CM Server 的 JVM 默认不会信任它连接时会报PKIX path building failed或connection closed之类的错。解决办法很直接把私有 CA 的证书导入 CM Server 的 JVM truststore# 查看 CM Server Java 路径一般都在 /usr/java 下 keytool -importcert \ -alias example-ldap-ca \ -file /path/to/ca.crt \ -keystore /usr/java/jdk1.8.0_xxx/jre/lib/security/cacerts \ -storepass changeit \ -noprompt导入后需要重启 cloudera-scm-server 才能生效。我建议把证书导入动作放在配置 CM 之前做免得先被一个不相关的报错整得焦头烂额。4.2 过滤器语法背后的坑过滤器是 LDAP 集成里出错率最高的区域。常见错误有以下几类第一类搜索范围太窄或太宽。-b ouPeople,dcexample,dccom表示只在 People 这个节点下搜索。如果用户实际放在ouPeople,ouInternal,dcexample,dccom就搜不到。反过来如果把 base 设成根域dcexample,dccom查询结果可能包含所有服务账号过滤器里不显式加objectClassinetOrgPerson的话连 cm-ldap 这种服务账号都会被当成普通用户。第二类objectClass 和匹配属性不匹配。OpenLDAP 里一个用户条目可能同时是inetOrgPerson和posixAccount但如果你的用户条目只继承了organizationalPerson这类较老的对象类用uid{0}依然能匹配到。真正的问题往往出在组上posixGroup用memberUidgroupOfNames用membergroupOfUniqueNames用uniqueMember。用错了关联属性匹配结果就是空的。第三类括号和特殊字符没转义。用户输入的登录名如果包含 LDAP 过滤器特殊字符比如*、(、)、\有可能导致过滤器语法错误甚至出现意外匹配。这个问题在大多数企业内部不常见但如果你允许邮箱前缀做登录名邮箱里出现号比如joetagexample.com时就要小心。一个稳妥的过滤器写法长这样User Search Filter: ((objectClassinetOrgPerson)(uid{0})) Group Search Filter: ((objectClassposixGroup)(memberUid{0})){0}两侧的括号不要自作主张去掉。CM 端会做占位符替换但保留结构能让逻辑更清晰也方便你在 ldapsearch 里原样复现排查。4.3 用户名规范化为什么“能查到却登不上”这是集成 OpenLDAP 时最常见的隐性故障ldapsearch 按uidalice能查到用户CM 登录却始终失败日志里也没有明显的 bind 错误。多数情况是**用户名属性Username Attribute**没配对。CM 默认的 Username Attribute 对 AD 来说是sAMAccountName或userPrincipalName但对 OpenLDAP默认值不一定是uid。如果你没显式设置这个字段CM 可能拿用户输入的文本直接去做 bind而 OpenLDAP 里用户条目的 DN 形如uidalice,ouPeople,...这时两边对不齐自然就认证失败了。正确配置是把这个值显式指定为uid。这样 CM 在处理登录名时会先拿输入值去检索uid{0}对应的条目再以该条目为 DN 进行密码校验。换句话说用户登录名不再是任意的字符串而是 OpenLDAP 中确定的 uid 属性值。如果再往深一层说aliceexample.com这种登录名能不能用可以前提是用户条目里有一个 mail 属性能匹配而且 Username Attribute 配的是mail。但我不建议这么做因为 OpenLDAP 里 mail 不一定唯一、不一定必备而 uid 才是 posixAccount 的天然标识。让登录名保持“纯用户名”形态是对排障最友好的方案。5. 集成后的实测验证与踩坑排查链路配置完成后不要急着宣布大功告成。我经历了不止一次“刚配完一切正常、过了两周有人报登录失败”的情况所以强烈建议把验证和排查方法沉淀成常规动作。这一节先讲几个高频率的踩坑场景再给一套可以照着执行的验证清单。5.1 场景一ldapsearch 能查到用户CM 登录始终报错这类问题的排查思路是逐个环节复现不要直接改配置碰运气。我会按下面的链路走确认 ldapsearch 用的过滤器、base、绑定账号和 CM 里配置的完全一致。很多时候你在命令行里用的是cnadmin或 rootdn而 CM 用的是cm-ldap账号两者的权限不同搜索结果可能完全不同。查 CM Server 日志。日志路径一般在/var/log/cloudera-scm-server/下重点看scm-server.log里搜索关键字ExternalAuthentication或LDAP。日志里如果有ErrorCode: 49说明 OpenLDAP 返回的是 invalid credentials问题在 bind 阶段如果有ErrorCode: 10或ErrorCode: 32说明 base 或过滤器有问题。检查 Username Attribute 是否显式配置为uid见 4.3 节。在 OpenLDAP 服务端开启 debug 日志观察 CM 发过来的实际过滤器和 bind DN# 在 slapd 配置中临时开启 debug生产慎用用后关闭 slapd -d 256日志会清清楚楚显示 CM 在做什么查询、绑定的 DN 是什么。这个方法效率很高能把“CM 内部行为”透明化。5.2 场景二登录成功了但所有人都是只读权限这个场景一般不是认证问题而是授权映射没配好。CM 外部认证默认给用户的角色通常就是只读或空角色如果你希望 LDAP 的cdh_admins组自动映射到 CM 管理员角色就需要在 External Credentials 或 Auth 配置里把组映射关系加进去。这一类坑的另一个常见原因是 Group Search Filter 匹配不到组。用 2.3 节的 ldapsearch 模拟一下如果返回的组列表是空的检查组成员属性是不是memberUid以及用户是否真的在组里。5.3 场景三QOP / SASL 相关报错如果你的 OpenLDAP 服务端启用了 SASL 安全层协商CM 默认的简单 bind 方式可能不被接受日志里会出现SASL(-13): authentication failure、QOP not supported之类的报错。先要知道 OpenLDAP 的 SASL 机制和 CM 的 LDAP 绑定方式是不是对得上。CM 默认走的是简单 bind用户名加密码这本身不是 SASL但如果服务端配置了sasl-secprops强制最小安全强度简单 bind 也会被拒。常见解法是调整 OpenLDAP 的sasl-secprops配置或者干脆换成 ldaps 加密通道。在处理这类问题之前先明确一点能用 TLS 解决的就不要纠结 SASL 策略生产环境尽量让网络链路上加密SASL 留给特殊场景。5.4 一份可以直接存档的验收清单集成完成之后我在实际项目里都会按下面这份清单逐项打勾任何一项不过关都不算完成检查项操作方法预期结果绑定账号权限用 cm-ldap 账号 ldapsearch 用户和组能查到且不报权限错误CM 登录用 alice 登录 CM 页面登录成功角色符合预期普通用户登录用 bob 登录登录成功无管理员权限禁用用户登录在 LDAP 中禁用 carol 后尝试登录登录失败改密联动LDAP 侧修改 bob 密码后再登录使用新密码成功、旧密码失败加密链路检查 CM 和 LDAP 之间的抓包无明文账号密码传输证书信任更换 OpenLDAP 证书后登录登录仍正常无 PKIX 报错把这份清单放进交接文档里后续任何一个人接手这个平台都能照猫画虎完成检查不用再从头摸索。5.5 一个让我印象深刻的排查案例最后分享一个我实际遇到过的案例它能帮你理解上面几节的组合运用。某次接到报障同事说 CM 登录时好时坏。我第一反应是 OpenLDAP 服务端有故障转移节点检查后发现主备节点都正常。接着我用ldapsearch手动执行 CM 同款过滤器结果命中了一个用户但 CM 日志显示搜索阶段就失败了错误是 “SearchResultError”。后来打开 slapd debug 才发现CM 在 User Search 阶段执行的过滤器和我预想的不一样它在过滤器的值里自动拼了一个域名后缀实际执行的查询变成了uidaliceexample.com自然查不到任何条目。问题根源就是外部认证设置里把 Username Attribute 配成了带域名的显示名形式而我手动验证时用的是纯用户名。把 Username Attribute 改成uid并确认 CM 对登录名不做域后缀处理之后问题立刻消失。这个案例充分说明排障时不要只看 CM 页面上的配置要看它实际发出去的查询——Ldap debug 日志是你最好的朋友。6. 写在最后的经验之谈整套方案落地之后现在 CM 平台的账号管理基本解放了新同事入职LDAP 侧创建用户、加组CM 这边自动就有对应权限同事离职LDAP 禁用账号当天就再也登不上 CM。安全审计要账号清单也从 OpenLDAP 侧就能导出来不用再对着 CM 的用户列表猜。我个人的建议是不要只停留在“能登录”这个目标。CM 集成 OpenLDAP 只是统一认证的第一步后面 Hive、Hue、Impala 这些服务完全可以复用同一套 LDAP 身份源。配置思路是同样的服务端保证目录数据干净客户端把过滤器写精确证书链路处理好后续就是复制粘贴的事。另外还想提醒一句CM 版本升级之后外部认证配置可能会迁移到新的界面但底层auth.external.credentials.*参数没有大变。先把一套环境的知识沉淀成文档和验收清单版本升级时对照着改能少踩很多冤枉坑。