证书可以位于正确的目录,权限模式可读、所有者为 root,而服务仍然可能报告无法打开该文件。在 SELinux 系统上,这种组合指向的是某个特定机制,而不是权限。标签随文件一起移动,因为重命名不会创建新对象。inode 保留了它在旧路径上的安全上下文,而新路径的策略从未有机会分配自己的上下文。

重命名究竟对标签做了什么

文件把 SELinux 上下文作为附着在 inode 上的属性数据携带。在同一文件系统内重命名只改变目录项,别的什么都不变;这既快又是原子操作,也正是上下文能在移动后存活的原因。不会查询任何策略,不会应用任何默认值,也不会打印任何警告。

复制的行为不同。副本会在目标目录中创建新的 inode,而策略会依据该目录的类型分配上下文,所以复制出来的证书通常得到正确的标签,而被移动的那份仍然是错的。正是这唯一一处差异解释了这类工单中的大部分困惑。

跨文件系统的移动会退化为复制的语义,因此同一条命令在两台机器上表现可能不同,取决于 /etc 是否位于独立卷上。在下结论说工具不可靠之前,先确认这一点。看一眼挂载表就能知道你执行的是哪种移动。

把当前标签与预期标签对照读取

三条只读命令无需改动任何东西就能回答这个问题。第一条显示文件现在拥有什么,第二条报告策略对这条确切路径的期望,第三条列出该目录下标签不一致的每个文件。

ls -lZ /etc/openvpn/client/office-ca.crt
matchpathcon /etc/openvpn/client/office-ca.crt
restorecon -Rn -v /etc/openvpn/client

值得信任的是试运行,因为它会点名每个上下文偏离策略的文件,同时仍然不触碰系统。如果证书出现在那份清单里,你就找到了路径的含义与文件的声明之间的差异。如果它没有出现,说明标签已经符合策略的期望,你的时间应当花在别处。

然后把差异与实际拒绝关联起来。不匹配是标签错误的证据,而不是服务因此失败的证明;在没有日志中相应拒绝的情况下就动手修复,只是多了几步的猜测。

恢复映射,而不是发明例外

对于标准位置,策略已经映射了该路径,所以 restorecon 会应用它,工作就完成了。把范围限制在你正在修复的目录;重新标记整个文件系统既慢又有破坏性,还会把最初的错误埋在一次非常大的变更之下。

对于自定义目录,顺序很重要,颠倒顺序会得到一条报告成功却什么都没改的命令。映射属于策略,重新标记属于最后,试运行属于两者之前。跳过最后这一步,正是一条从来没错过的路径照样被重新标记的原因。

semanage fcontext -a -t openvpn_etc_t '/etc/openvpn/custom(/.*)?'
restorecon -Rv /etc/openvpn/custom

restorecon 应用的是映射,它无法凭空造出一个映射。如果没有规则描述该路径,就没有可恢复的来源,所以持久映射必须在重新标记之前存在。记下哪个配置拥有这份映射,否则之后的移动、软件包更新或重新标记会再次制造故障,下一个人又得从零开始。

为什么 chcon 不是解决办法

chcon 直接把上下文写到文件上。它立即生效,也是现有选项中最不持久的,因为文件系统重新标记会重新应用策略,并不询问就丢弃手工改动。文件看起来被修好了,而这次修复有一个没人写下来的有效期。你真正需要的是描述该路径的规则,让每个到达那里的文件自动获得同样的处理。

更糟的是,它教错了课:底层映射依然缺失,所以故障会在几周后卷土重来,既没有明显的触发点,也没有关于那次绕行的记忆。只把 chcon 当作确认假设的临时实验,随后换成能经受下一次重新标记的映射,并确认替换确实到位。

当标签正确而访问仍然失败

从父目录开始。穿越权限会在路径的每个组成部分上检查,所以放在服务无法进入的目录里的证书,即使标签正确也仍然读不到。SELinux 和普通权限都必须允许每一步,修好其中一个并不能修好另一个。

然后检查进程身份和服务自身的沙箱。带有严格沙箱的单元可能拒绝访问策略本会欣然允许的文件,而从日志行看,这两种原因看起来一模一样。要确认究竟是哪个进程在请求,而不是你以为在请求的那个。

请记住,dontaudit 规则可能压制拒绝消息,所以空的审计日志只是弱证据,而不是证据。如果策略看起来正确而访问仍然失败,先调查再生成宽泛的允许模块,也不要为了消除症状而关闭 SELinux。两种捷径都会得到一个看似能用、却没人能描述其安全状况的系统。一次简短的排查胜过这两者。

相关文章