CentOS 7 停止维护后,将生产环境迁移至 Rocky Linux 几乎是二进制兼容的最短路径。很多人以为迁移就是跑个脚本替换源、更新包就完事了,结果系统启动后 SSH 连不上、Web 服务 403、数据库无法写入,查了半天发现全是 SELinux 在“作祟”。这不是 SELinux 出了问题,而是策略标签和规则集在跨发行版迁移时发生了细微但致命的偏移。Rocky Linux 作为 RHEL 的重构版,其 SELinux 策略包虽然师出同门,但版本迭代、命名约定和默认布尔值的调整,会导致从 CentOS 继承过来的自定义策略和文件上下文全部失效。
迁移后 SELinux 为什么没有自动适配CentOS 7 生命周期末期停留在 SELinux 策略版本 28 左右,而 Rocky Linux 8/9 直接跳跃到了策略版本 33 甚至更高。策略模块的格式和存储路径发生了结构性变化。在 CentOS 7 上,你手动创建的自定义模块通常存放在 /etc/selinux/targeted/modules/active/modules 下,使用的是 .pp 文件配合优先级 400 的方式加载。而 Rocky Linux 8 开始,模块管理全面转向了 SELinux 用户空间工具的新语义,高优先级模块路径和加载机制有了调整。直接替换源升级后,旧的自定义策略模块并没有被自动转换或重新编译,导致这些规则在内存中消失,系统回退到默认策略,之前放行的操作全部被阻断。
另一个被忽视的地方是文件上下文标签。CentOS 7 上很多第三方软件,比如自定义路径安装的 Nginx、Redis、Node.js 应用,你通过 semanage fcontext 添加的规则存储在 /etc/selinux/targeted/contexts/files/file_contexts.local 中。迁移过程中这个文件虽然被保留,但 Rocky Linux 自带的策略包更新了基础文件上下文,两者的叠加顺序和匹配优先级可能产生冲突。更隐蔽的是,某些二进制文件在 Rocky Linux 中被放到了新路径,比如从 /usr/bin 迁移到 /usr/libexec,而你的旧规则还在指向老路径,导致进程无法获取正确的域转换。
迁移前必须备份的 SELinux 关键数据在实际执行迁移脚本之前,你应该完整导出当前系统的 SELinux 状态,而不是仅仅依赖文件备份。首先是所有本地自定义的文件上下文规则:
semanage fcontext -l -C > /root/selinux-backup/fcontext_local.txt
这条命令只列出本地添加的规则,比直接备份整个文件更精准。其次是布尔值状态,因为很多安全策略的放行依赖于布尔值的开启:
semanage boolean -l -C > /root/selinux-backup/booleans_local.txt
最关键也最容易翻车的是 SELinux 用户映射和登录映射。如果你之前配置过 staff_u、sysadm_u 等 SELinux 用户与 Linux 用户的绑定,必须导出:
semanage login -l -C > /root/selinux-backup/login_local.txt semanage user -l -C > /root/selinux-backup/user_local.txt
端口标签的映射同样重要,尤其是那些非标准端口,比如把 HTTP 服务开在 8081 或 8443 上:
semanage port -l -C > /root/selinux-backup/port_local.txt
最后,如果你有自己编译的 SELinux 策略模块,一定要找到原始的 .te 源文件。只备份 .pp 二进制文件是不够的,因为跨主版本升级后,旧 .pp 文件可能因格式不兼容而无法直接加载,必须用源文件重新编译。
迁移后策略模块的重新编译与加载系统升级完成后,第一件事不是直接恢复备份,而是检查当前 SELinux 策略版本和状态。运行 semodule -lfull 查看已加载模块列表,你会发现之前自定义的模块全部消失。此时如果你尝试直接安装旧的 .pp 文件,大概率会报错提示优先级冲突或版本不匹配。正确的做法是找到备份的 .te 源文件,使用当前系统的 SELinux 开发工具重新编译:
dnf install selinux-policy-devel -y make -f /usr/share/selinux/devel/Makefile mycustommodule.pp semodule -i mycustommodule.pp
编译过程中,新的 checkmodule 和 semodule_package 会根据当前系统的策略接口重新生成模块,自动解决接口符号变更的问题。如果你的自定义模块引用了 CentOS 7 中已废弃的接口,编译阶段就会报错,这时需要对照 Rocky Linux 的接口文档修改 .te 文件。常见的接口变更包括文件类型过渡宏和网络接口宏,比如 corenet_tcp_connect_http_port 在新版本中可能被更细粒度的宏替代。
对于没有 .te 源文件、只有 .pp 文件的情况,可以尝试使用 semodule_unpackage 解包后,再用 audit2allow 配合日志逆向生成规则,但这只能作为临时方案,最终还是要重写策略源文件。
文件上下文标签的冲突修复与验证迁移后最容易出现的 AVC 拒绝类型是文件访问。查看 audit 日志会发现大量 type=AVC 条目,其中 scontext 和 tcontext 的标签对不上。此时不要急着用 audit2allow 生成放行规则,因为大部分问题其实是文件上下文标签错误,而不是策略规则缺失。先用 restorecon 对整个文件系统做一次完整的重新标记:
restorecon -Rv /
这个操作会读取当前系统的基础文件上下文数据库,对所有文件重新打标签。但要注意,restorecon 不会覆盖你通过 semanage fcontext 添加的本地规则,因为本地规则的优先级更高。如果 restorecon 之后问题依然存在,说明本地规则与基础策略产生了冲突。此时需要检查 /etc/selinux/targeted/contexts/files/file_contexts.local 文件,逐条对比 Rocky Linux 自带的 file_contexts 文件,找出重叠的路径规则。冲突通常表现为:你在 CentOS 7 上为 /opt/myapp/bin/* 设置了 bin_t 类型,但 Rocky Linux 的基础策略已经为 /opt 下的子目录定义了更具体的类型,导致你的规则被覆盖或忽略。
解决方法是使用 semanage fcontext 的 -m 参数修改现有规则,或者用 -d 删除旧规则后重新添加。修改完成后再次执行 restorecon -Rv /opt/myapp 使规则生效。验证文件上下文是否生效的命令是:
matchpathcon /opt/myapp/bin/start.sh
如果输出显示的类型与你期望的不一致,说明规则优先级或正则匹配有问题。
布尔值差异导致的隐性服务故障Rocky Linux 8/9 调整了部分 SELinux 布尔值的默认状态,这是官方基于安全基线做出的改变,但文档中很少明确列出。例如,httpd_can_network_connect 在 CentOS 7 中很多运维习惯性开启,而 Rocky Linux 9 默认关闭;httpd_read_user_content 在新版本中可能被更严格的 httpd_read_content_in_all_dirs 替代。迁移后,原本正常的 Web 服务突然无法连接后端数据库或读取用户目录,就是因为这些布尔值被重置了。
你需要将备份的布尔值列表与当前系统默认值逐一对比,找出差异项:
semanage boolean -l | grep -E "httpd|named|mysqld|nfs"
对于确实需要开启的布尔值,使用 setsebool -P 永久设置。但这里要强调一个硬核观点:不要无脑照搬旧系统的所有布尔值设置。CentOS 7 生命周期末期,很多运维为了省事直接关闭了部分安全特性,比如将 domain_kernel_load_modules 打开以允许加载内核模块。在 Rocky Linux 新系统上,你应该借这个机会重新审视安全策略,只放行业务真正需要的权限,而不是继承历史债务。
自定义应用程序域的完整迁移流程如果你在 CentOS 7 上为自研应用编写过完整的 SELinux 策略模块,包括类型定义、域转换规则和文件上下文,迁移过程需要更系统化的操作。首先在 Rocky Linux 上安装开发工具集:
dnf install selinux-policy-devel setools-console policycoreutils-python-utils -y
然后将旧的 .te 文件复制到开发目录,不要直接编译,而是先用 seshowif 查看当前系统支持的接口列表,确认你引用的接口是否存在:
seshowif | grep your_interface_name
对于已废弃的接口,需要查阅 Rocky Linux 的 SELinux 接口文档,找到替代接口。常见的变更包括文件类型过渡宏从 files_type_transition 改为更具体的 filetrans_pattern,以及网络访问宏的命名规范化。修改完 .te 文件后,还需要检查 .fc 文件中的路径表达式是否仍然匹配。Rocky Linux 9 中,很多系统二进制文件从 /usr/bin 移动到了 /usr/libexec,如果你的应用依赖这些二进制并定义了域转换,路径错误会导致转换失败,进程运行在 initrc_t 而非你自定义的域中。
编译和加载完成后,使用 sepolicy generate 命令的测试模式验证域转换是否生效:
sepolicy transition -s unconfined_t -t your_app_t
如果输出显示无法转换,说明策略中缺少转换规则或入口文件上下文不正确。
audit2allow 的正确使用姿势与陷阱迁移后排查 SELinux 问题时,audit2allow 是绕不开的工具,但滥用它会埋下安全隐患。正确的流程是:先将 SELinux 设置为宽容模式,完整运行一次业务的所有功能,收集所有 AVC 拒绝日志:
semanage permissive -a your_app_t ausearch -m avc -ts recent --raw > /tmp/avc_raw.log
然后用 audit2allow 分析日志,但不要直接生成并加载策略模块。先查看拒绝的汇总信息,理解每个拒绝背后的操作逻辑:
audit2allow -i /tmp/avc_raw.log -a
对于合理的访问需求,用 -M 参数生成模块,但要检查生成的 .te 文件内容,去掉那些过于宽泛的规则。比如 audit2allow 可能会生成 allow your_app_t default_t:file read; 这样的规则,这实际上是文件上下文标签错误导致的,应该修复标签而非添加规则。只保留那些确实需要跨域访问的规则,并且尽量用属性而非直接类型来缩小权限范围。
一个常见陷阱是:audit2allow 生成的规则中可能包含 dontaudit 语句,这些语句会静默丢弃拒绝日志,让你误以为问题已解决,实际上操作仍然被拒绝,只是不记录日志了。生产环境中应谨慎使用 dontaudit,只在确认某个拒绝是预期行为且高频产生噪音时才添加。
迁移后的持续监控与策略优化策略适配不是一次性工作。迁移完成并恢复强制模式后,应部署持续的 SELinux 监控。使用 sealert 工具分析 audit 日志,它比直接看 AVC 条目更易读:
sealert -a /var/log/audit/audit.log
对于周期性出现的拒绝,即使不影响业务,也应该分析其根因。有些拒绝是应用程序试图访问不该访问的资源,这是 SELinux 在正常工作;有些则是策略遗漏,需要补充规则。建议在迁移后的前两周保持较高的日志审查频率,每周导出一份 AVC 拒绝汇总,与业务团队确认哪些是正常行为,哪些需要修复。
此外,Rocky Linux 的小版本更新也会带来 SELinux 策略的微调。配置好 dnf-automatic 或定期执行 semodule -lfull 对比模块版本变化,避免某次安全更新引入新的策略冲突。最终目标是让 SELinux 在强制模式下静默运行,既保障安全又不影响业务,这才是迁移成功的真正标志。
