如何在香港服务器中启用RHEL 9的Integrity Measurement Architecture(IMA),确保系统文件未被篡改?

凌晨两点,我还在香港葵涌数据中心值夜班,工单又来了——某客户在香港的业务要通过合规审计,要求“关键系统文件不可被未授权修改,并且有可审计的证据链”。我盯着控制台上那一串 RHEL 9.3/9.4 的宿主机,心想:这次不想再靠“rpm -V + AIDE + fapolicyd”这种“拼盘”,而是干脆把 IMA 用起来,既能度量(measure),也能评估/强制(appraise/enforce),还能接 TPM 做远程证明(attestation)。
目标与策略
目标
- 启用 IMA 运行时度量,留存可审计的测量日志;
- 为关键可执行文件、共享库、内核模块建立签名,逐步切换到 强制校验(enforce);
- 对接 TPM2 PCR[10],为远程证明打基础;
- 过程中不中断业务(逐步推进、可回退)。
节奏
- Phase 1:只观测(measure-only)
- Phase 2:标注/签名(fix & sign)
- Phase 3:强制校验(enforce)
环境与参数(现场实况)
| 类别 | 参数 |
|---|---|
| 机架 | 42U,冷通道封闭,N+1 制冷 |
| 服务器 | 2× Supermicro 1U(A+ 系列),单路 AMD EPYC 7543P(32C/64T),128 GB RAM |
| 存储 | 系统盘:2× NVMe 1.92 TB(RAID1 via mdadm);数据盘:4× NVMe 3.84 TB(RAID10) |
| 网卡 | 2× 25 GbE(Mellanox ConnectX-4 Lx) |
| 固件 | UEFI 启动,TPM2.0(Infineon SLB9670),Secure Boot 暂时关闭 |
| OS | RHEL 9.4(同方法适配 9.2/9.3),SELinux Enforcing |
| 内核 | 5.14 系列(RHEL 9 默认内核) |
| 虚拟化 | KVM + libvirt;容器:Podman / CRI-O |
| 目标保护 | /usr、/bin、/sbin、/lib*/、/etc 以及 /lib/modules/$(uname -r) |
总览图(你将做什么)
- 确认硬件/内核支持(TPM/IMA)
- 安装工具(ima-evm-utils、tpm2-tools、keyutils 等)
- 生成签名密钥 & 管理公钥(内核 .ima keyring)
- 写 IMA 策略(policy)(先观测,后强制)
- 把策略和公钥打进 initramfs(早期加载)
- 加内核参数 & 重建 GRUB(启用 IMA,sha256,模板)
- 批量签名关键文件(先 fix,再 imasig)
- 切换为 enforce(逐台灰度)
- 验证与演练(故障注入、告警联动)
- 生产收尾(DNF 更新自动再签名、容器与 NFS 例外、审计留痕)
1)硬件与内核支持快速体检
# TPM2 是否可见
dmesg | grep -i tpm
ls -l /dev/tpm*
tpm2_getcap properties-fixed | head
# IMA/安全文件系统
mount | grep securityfs || mount -t securityfs securityfs /sys/kernel/security
ls /sys/kernel/security/ima
# SELinux 模式
getenforce
看到 /sys/kernel/security/ima 下有 policy / ascii_runtime_measurements 基本就绪。TPM2 正常、SELinux Enforcing,继续。
2)安装必要组件
dnf install -y ima-evm-utils keyutils tpm2-tools openssl \
policycoreutils-python-utils jq
说明:ima-evm-utils 里有 evmctl,用来做 IMA/EVM 签名与校验;keyutils 管理内核 keyring;tpm2-tools 做 PCR/Quote;openssl 生成密钥与证书。
3)生成签名密钥 & 管理公钥
我把私钥只放在离线管理机,宿主机上只放公钥(DER/X.509)并在 initramfs 早期加载进 .ima keyring。
在离线机(或临时安全环境)生成:
mkdir -p /root/ima && cd /root/ima
# 生成 3072 位 RSA 私钥 + 自签名证书(10年)
openssl req -new -x509 -newkey rsa:3072 -nodes \
-keyout ima_privkey.pem -out ima_pub.crt -days 3650 \
-subj "/CN=HK-DC-IMA/"
# 导出 DER(内核喜欢 DER)
openssl x509 -in ima_pub.crt -outform der -out ima_pub.der
chmod 600 ima_privkey.pem
将 ima_pub.der 安全地拷到每台宿主机的 /etc/keys/ima/ima_pub.der(私钥不要上服务器)。
4)编写 IMA 策略(先观测)
先用“观测 + 少量限制”的策略暖机,等签名齐备再切 enforce。策略文件放 /etc/ima/ima-policy:
# /etc/ima/ima-policy
# 模板:既可度量也可带签名
# 观测层面:对 root 执行/映射的内容进行度量
measure func=BPRM_CHECK mask=MAY_EXEC uid=0 template=ima-ng
measure func=FILE_MMAP mask=MAY_EXEC uid=0
# 将来切 enforce 时会用到(先放着,配合 ima_appraise=fix 暂不拒绝)
appraise func=BPRM_CHECK mask=MAY_EXEC uid=0 appraise_type=imasig
appraise func=FILE_MMAP mask=MAY_EXEC appraise_type=imasig
appraise func=MODULE_CHECK appraise_type=imasig
appraise func=KEXEC_KERNEL_CHECK appraise_type=imasig
# 降噪:不测不验的易变路径
dont_measure obj_path=/var/log/**
dont_measure obj_path=/var/tmp/**
dont_measure obj_path=/tmp/**
dont_measure obj_path=/run/**
dont_appraise obj_path=/var/log/**
dont_appraise obj_path=/var/tmp/**
dont_appraise obj_path=/tmp/**
dont_appraise obj_path=/run/**
说明:BPRM_CHECK 覆盖“可执行文件启动”,FILE_MMAP mask=MAY_EXEC 覆盖“库被映射为可执行页”,MODULE_CHECK 用于内核模块。ima-ng 模板用 SHA-256 等现代算法;等签完再切 ima-sig 模板也行。
5)把策略和公钥打进 initramfs(确保早期加载)
我做了一个最小可用的 initramfs 早期 systemd 单元,在切根前加载 key 和策略。
文件 1:早期加载脚本
# /usr/local/sbin/ima-load-keys.sh
#!/bin/sh
mount | grep -q "securityfs /sys/kernel/security" || \
mount -t securityfs securityfs /sys/kernel/security
# 把公钥加到 .ima keyring
if [ -f /etc/keys/ima/ima_pub.der ]; then
keyctl padd asymmetric "HK-IMA" %keyring:.ima < /etc/keys/ima/ima_pub.der
fi
# 加载策略(initramfs 内的早期策略)
if [ -f /etc/ima/ima-policy ]; then
cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy
fi
chmod +x /usr/local/sbin/ima-load-keys.sh
文件 2:initramfs 内的 systemd 单元
# /usr/lib/systemd/system/ima-load-keys.service
[Unit]
Description=Load IMA key and policy early in initramfs
DefaultDependencies=no
Before=cryptsetup.target sysinit.target
ConditionPathExists=/etc/keys/ima/ima_pub.der
ConditionPathExists=/etc/ima/ima-policy
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/ima-load-keys.sh
[Install]
WantedBy=initrd.target
dracut 打包到 initramfs:
mkdir -p /etc/dracut.conf.d
cat >/etc/dracut.conf.d/99-ima.conf <<'EOF'
# 把公钥、策略、脚本、服务文件塞进 initramfs
install_items+=" /etc/keys/ima/ima_pub.der /etc/ima/ima-policy \
/usr/local/sbin/ima-load-keys.sh /usr/lib/systemd/system/ima-load-keys.service "
EOF
# 重建当前内核的 initramfs
dracut -f --kver "$(uname -r)"
6)启用内核参数 & 重建 GRUB
我们先在 Phase 1 用 观测 + fix:fix 会在需要校验但缺少 xattr 时写入校验信息(便于过渡),等签名齐备再改成 enforce。
# /etc/default/grub 里追加(注意保留原有参数)
GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX ima_hash=sha256 ima_template=ima-sig ima_appraise=fix"
UEFI 机器重建:
grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
BIOS 机器:
grub2-mkconfig -o /boot/grub2/grub.cfg
重启后,先确认 IMA 活跃:
mount -t securityfs | grep securityfs
head -n2 /sys/kernel/security/ima/ascii_runtime_measurements
keyctl show %keyring:.ima
cat /sys/kernel/security/ima/policy | sed -n '1,20p'
7)批量签名关键文件(imasig)
强制模式最终要靠签名(不是仅有哈希)。我在离线机器用私钥签,然后把签名操作脚本同步到宿主机,但私钥仍在离线机(这一步按你安全流程调整)。如果必须在线签,可将私钥放到 HSM 或受控的 加密卷。
示例:在线(演示环境)签名命令
# 仅供演示:生产请使用离线或者 HSM
export IMA_PRIV=/root/ima_privkey.pem # 演示私钥路径
# 可执行 + 库
find /usr/bin /usr/sbin -type f -perm -111 -print0 \
| xargs -0 -I{} evmctl sign --imasig -a sha256 -k "$IMA_PRIV" "{}"
find /lib /lib64 /usr/lib /usr/lib64 -type f \
\( -name "*.so" -o -perm -111 \) -print0 \
| xargs -0 -I{} evmctl sign --imasig -a sha256 -k "$IMA_PRIV" "{}"
# 内核模块
find /lib/modules/$(uname -r) -type f -name "*.ko*" -print0 \
| xargs -0 -I{} evmctl sign --imasig -a sha256 -k "$IMA_PRIV" "{}"
查看单个文件的 xattr:
getfattr -m - -d /usr/bin/ls | sed 's/^security/ security/g'
# 或 evmctl
evmctl ima_verify /usr/bin/ls -v
提示:签名写在 security.ima xattr;若启用 EVM,还会用到 security.evm(本文不强制 EVM,作为后续加固)。
8)切换到强制(enforce)——灰度推进
签名完成率 > 95% 后,我挑选一台低风险宿主机,把 ima_appraise 从 fix 改为 enforce:
sed -i 's/ima_appraise=fix/ima_appraise=enforce/' /etc/default/grub
grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg # 或 /boot/grub2/grub.cfg
reboot
验证强制效果:
# 故障注入:去掉 /usr/bin/ls 的 IMA xattr(请在测试机)
setfattr -x security.ima /usr/bin/ls
/usr/bin/ls
# 预期:Permission denied(被 IMA 拦截)
# 查审计 / dmesg
dmesg | tail -n 50 | grep -i ima
ausearch -m INTEGRITY_RULE -ts recent | aureport -x --summary
没问题后,再逐台滚动推进。
9)度量日志与 TPM PCR 验证(可选但推荐)
IMA 的运行时度量日志:
# 人类可读
head -n 10 /sys/kernel/security/ima/ascii_runtime_measurements
# 查看 PCR[10](默认 IMA 用 PCR 10)
tpm2_pcrread sha256:10
如果你后续上 Keylime 之类的远程证明方案,这一步就是基础。先把日志收集到 SIEM,PCR 值纳入基线。
10)生产化收尾与运维联动
10.1 DNF/RPM 更新后的自动再签名
RHEL 更新会换二进制/库/内核模块,必须再签名。我用了一个简单且鲁棒的办法:配置一个 systemd 定时器,对“缺失 security.ima 或签名不通过”的文件做补签;同时将 dnf 操作触发一次即时任务。
补签脚本:
# /usr/local/sbin/ima-resign-changed.sh
#!/usr/bin/env bash
set -euo pipefail
PRIV="/root/ima_privkey.pem" # 生产建议从 HSM/密钥托管拿
scan_paths=("/usr/bin" "/usr/sbin" "/lib" "/lib64" "/usr/lib" "/usr/lib64" "/lib/modules/$(uname -r)")
for p in "${scan_paths[@]}"; do
[ -e "$p" ] || continue
while IFS= read -r -d '' f; do
# 没有 security.ima 或校验失败则再签
if ! getfattr -n security.ima "$f" &>/dev/null || ! evmctl ima_verify "$f" &>/dev/null; then
evmctl sign --imasig -a sha256 -k "$PRIV" "$f" || echo "WARN: sign fail $f"
fi
done < <(find "$p" -type f \( -name "*.so*" -o -name "*.ko*" -o -perm -111 \) -print0)
done
定时器与服务:
# /etc/systemd/system/ima-resign.service
[Unit]
Description=Re-sign changed files for IMA
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/ima-resign-changed.sh
# /etc/systemd/system/ima-resign.timer
[Unit]
Description=Nightly IMA re-sign
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
systemctl enable --now ima-resign.timer
DNF 钩子(简易):
cat >/etc/dnf/plugins/post-transaction-actions.d/ima-resign.conf <<'EOF'
# 当任意包更新后执行一次补签
ima-resign *
EOF
cat >/usr/libexec/dnf-plugins/post-transaction-actions.d/ima-resign <<'EOF'
#!/usr/bin/env bash
systemctl start ima-resign.service
EOF
chmod +x /usr/libexec/dnf-plugins/post-transaction-actions.d/ima-resign
注:不同版本的 dnf 插件目录可能略有差异,你也可以直接在 /etc/rpm/posttrans.d/ 放一个 shell 脚本达成相同效果。
10.2 容器与镜像层
容器层面的二进制一般来自只读镜像层,不建议对镜像层做 IMA 强制(容易打架)。通常策略里不对容器镜像路径 appraise,只对宿主机关键路径强制。需要时可在容器运行时再做镜像签名/策略(cosign/sigstore 体系)。
10.3 NFS/只读挂载
NFS 上的 xattr 支持与 IMA 行为可能不一致,强制前务必 dont_appraise/dont_measure 远端只读路径;或者改成本地只读镜像+校验链。
10.4 SELinux 协同
SELinux Enforcing 与 IMA 共存没问题。若你策略走 obj_type= 精细化(例如 lib_t、bin_t),能进一步降噪,但需要结合你实际策略基线。
10.5 告警与可观测性
将 /sys/kernel/security/ima/ascii_runtime_measurements 周期收集到 SIEM;
审计(auditd)订阅 INTEGRITY 相关事件;
Prometheus Node Exporter 无原生 IMA 指标,我做了个 sidecar 脚本把 PCR10 与度量计数暴露成文本指标(略)。
核心命令与状态检查速查表
| 目标 | 命令/文件 |
|---|---|
| 查看度量日志 | less /sys/kernel/security/ima/ascii_runtime_measurements |
| 查看/加载策略 | cat /sys/kernel/security/ima/policy / cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy |
| 查看 IMA keyring | keyctl show %keyring:.ima |
| 签名文件 | evmctl sign --imasig -a sha256 -k <priv.pem> <file> |
| 验证文件 | evmctl ima_verify <file> |
| 查看文件 xattr | getfattr -n security.ima <file> |
| 读 PCR10 | tpm2_pcrread sha256:10 |
| 审计事件 | ausearch -m INTEGRITY_RULE -ts recent |
我踩过的坑与解决过程
TPM 在 BIOS 里被禁用
第一次上机,/dev/tpm0 不见了。远程手(走 iKVM)进 BIOS,把 TPM 打开,UEFI 固件里从“Hidden”改成“Available”,保存重启后正常。
initramfs 没把公钥/策略带进去
我第一次漏了 install_items,导致早期没加载策略,系统起来后再加载会错过部分启动阶段(比如早期进程)。加上 dracut.conf.d/99-ima.conf 并 dracut -f 解决。
audit 日志“洪水”
观测期日志量很大。临时把策略只作用于 uid=0 的执行与 mmap 可执行,等签名完毕逐步扩大对象范围。也可以加 dont_measure 降噪目录。
第三方闭源 Agent 无法签名
供应商二进制在受控目录里,但不允许我们改 xattr。临时把该路径 dont_appraise,并要求供应商提供带签名的安装包或我们可以对其做本地再打包签名。
内核升级后模块未签名导致模块加载失败
滚动升级内核后,/lib/modules/<new> 下的 .ko 尚未签名。ima-resign.service 立即补签解决;作为长期方案,把补签脚本加到内核更新的 posttrans 钩子。
容器运行时误触发
早期我对 /var/lib/containers 做了 appraise,结果部分运行时生成的层触发校验失败。后来在策略中对容器数据根目录 dont_appraise,只对宿主关键路径强制。
可选加固:EVM 与 Secure Boot
- EVM(Extended Verification Module) 可保护文件 元数据(权限/所有者/xattr 等),与 IMA 组合更强,但运维复杂度上升。建议第二阶段稳定后再启用。
- Secure Boot 若开启并把你的 IMA 公钥锚定在系统受信任链中(内核次级可信 keyring ),可以减少“早期加载公钥”的自维护工作;但具体流程与 MOK/UEFI DB 结合,这里不展开。
收尾:从“我相信”到“机器自己不信就不干”
一周后的凌晨,同一个冷通道。度量日志在 SIEM 里按分钟滚动,PCR10 的基线也被纳入合规报告。我们挑了两台先上 enforce,在变更窗口里做了几次“拔签名”的演练,每次都被 IMA 冷冷地拒绝。客户团队在远端看着,我们把一张图甩给他:某次误改 /usr/bin/ls,IMA 直接挡下,审计事件精准入库。
那一刻我意识到:从“我相信大家不会乱改”到“机器自己不信就不干”,只是多了几段策略和几个 xattr,但系统的可信边界实实在在地向前迈了一步。
附:一键回顾脚本(观测→强制的最小闭环 Demo)
只用于实验环境(生产环境请拆分为离线签名/灰度发布)
#!/usr/bin/env bash
set -euo pipefail
# 0. 依赖
dnf install -y ima-evm-utils keyutils tpm2-tools openssl
# 1. 策略
mkdir -p /etc/ima /etc/keys/ima
cat >/etc/ima/ima-policy <<'POLICY'
measure func=BPRM_CHECK mask=MAY_EXEC uid=0 template=ima-ng
measure func=FILE_MMAP mask=MAY_EXEC uid=0
appraise func=BPRM_CHECK mask=MAY_EXEC uid=0 appraise_type=imasig
appraise func=FILE_MMAP mask=MAY_EXEC appraise_type=imasig
appraise func=MODULE_CHECK appraise_type=imasig
dont_measure obj_path=/var/log/**
dont_measure obj_path=/var/tmp/**
dont_measure obj_path=/tmp/**
dont_measure obj_path=/run/**
dont_appraise obj_path=/var/log/**
dont_appraise obj_path=/var/tmp/**
dont_appraise obj_path=/tmp/**
dont_appraise obj_path=/run/**
POLICY
# 2. 生成测试公私钥(生产请离线生成)
openssl req -new -x509 -newkey rsa:3072 -nodes \
-keyout /root/ima_privkey.pem -out /etc/keys/ima/ima_pub.crt \
-days 3650 -subj "/CN=HK-DC-IMA/"
openssl x509 -in /etc/keys/ima/ima_pub.crt -outform der -out /etc/keys/ima/ima_pub.der
chmod 600 /root/ima_privkey.pem
# 3. 早期加载(脚本+服务+dracut)
cat >/usr/local/sbin/ima-load-keys.sh <<'EOS'
#!/bin/sh
mount | grep -q "securityfs /sys/kernel/security" || \
mount -t securityfs securityfs /sys/kernel/security
keyctl padd asymmetric "HK-IMA" %keyring:.ima < /etc/keys/ima/ima_pub.der
cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy
EOS
chmod +x /usr/local/sbin/ima-load-keys.sh
cat >/usr/lib/systemd/system/ima-load-keys.service <<'EOS'
[Unit]
Description=Load IMA key and policy early in initramfs
DefaultDependencies=no
Before=cryptsetup.target sysinit.target
ConditionPathExists=/etc/keys/ima/ima_pub.der
ConditionPathExists=/etc/ima/ima-policy
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/ima-load-keys.sh
[Install]
WantedBy=initrd.target
EOS
cat >/etc/dracut.conf.d/99-ima.conf <<'EOS'
install_items+=" /etc/keys/ima/ima_pub.der /etc/ima/ima-policy \
/usr/local/sbin/ima-load-keys.sh /usr/lib/systemd/system/ima-load-keys.service "
EOS
dracut -f --kver "$(uname -r)"
# 4. 内核参数:先 fix
if ! grep -q 'ima_appraise=' /etc/default/grub; then
sed -i 's/^GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="ima_hash=sha256 ima_template=ima-sig ima_appraise=fix /' /etc/default/grub
else
sed -i 's/ima_appraise=[^ ]*/ima_appraise=fix/' /etc/default/grub
fi
grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg || grub2-mkconfig -o /boot/grub2/grub.cfg
echo "==== Reboot now, then run: phase2-sign ===="
cat >/usr/local/sbin/phase2-sign <<'EOS'
#!/usr/bin/env bash
set -euo pipefail
PRIV="/root/ima_privkey.pem"
paths=("/usr/bin" "/usr/sbin" "/lib" "/lib64" "/usr/lib" "/usr/lib64" "/lib/modules/$(uname -r)")
for p in "${paths[@]}"; do
[ -e "$p" ] || continue
while IFS= read -r -d '' f; do
evmctl sign --imasig -a sha256 -k "$PRIV" "$f" || true
done < <(find "$p" -type f \( -name "*.so*" -o -name "*.ko*" -o -perm -111 \) -print0)
done
echo "Switch to enforce:"
sed -i 's/ima_appraise=fix/ima_appraise=enforce/' /etc/default/grub
grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg || grub2-mkconfig -o /boot/grub2/grub.cfg
echo "Reboot to enforce."
EOS
chmod +x /usr/local/sbin/phase2-sign
如果你照着做到了这里,那么你已经把 RHEL 9 的 IMA 真正落在了地上:有度量、有签名、有强制,且知道更新和变更时怎么把链条续上。剩下的,就是把它当成日常运维的一部分——像我们在香港这排机器一样,让“可信”不再是口号,而是一条条可验证的证据。