1. 为什么需要内核安全启动与模块校验?

我第一次接触Linux内核安全启动是在一个企业级项目上。当时客户要求所有服务器必须启用Secure Boot,并且内核模块必须强制签名。说实话,刚开始觉得这很麻烦,直到有台服务器因为加载了未签名的第三方驱动导致系统崩溃,我才真正理解这项技术的重要性。

现代Linux系统面临的安全威胁越来越复杂。想象一下,如果有人偷偷替换了你的内核镜像,或者在运行时注入恶意模块,整个系统就会变成"傀儡"。内核签名就像给系统装上了"防伪标识",确保每个关键组件都是"正品"。

具体来说,这套机制解决了三个核心问题:

  • 身份认证:就像检查身份证一样,确保加载的内核和模块确实来自可信来源
  • 完整性保护:通过数字签名,任何对文件的篡改都会被立即发现
  • 合规要求:越来越多的行业标准(如等保2.0)都明确要求启用这些安全特性

我在嵌入式设备上就吃过亏。有次现场升级时,设备加载了被篡改的驱动模块,导致整个产线停工4小时。后来启用模块强制签名后,类似问题再没发生过。

2. 安全启动全流程解析

2.1 UEFI Secure Boot深度配置

要让安全启动真正发挥作用,UEFI设置是第一步。很多人的误区是以为在BIOS里打开Secure Boot就完事了,其实这里面大有学问。

以Dell PowerEdge服务器为例,正确的配置流程应该是:

  1. 进入UEFI设置界面,找到"Secure Boot"选项
  2. 选择"Custom Mode"而不是"Standard Mode"
  3. 删除默认的微软证书(除非你要运行Windows)
  4. 导入自己的PK/KEK/db密钥链

这里有个坑我踩过:某些主板对密钥格式要求严格。最好先用openssl转换格式:

openssl x509 -in my_cert.pem -outform DER -out my_cert.der

实际部署时,建议采用分级密钥管理:

  • PK (Platform Key):最高权限密钥,由IT部门严格保管
  • KEK (Key Exchange Key):用于更新签名数据库
  • db (Signature Database):存储实际用于验证的证书

2.2 内核镜像签名实战

签名内核不是简单运行个命令就行,需要考虑架构差异。以常见的x86_64和ARM64为例:

x86_64平台:

# 安装依赖
sudo apt install sbsigntool efivar

# 签名命令
sbsign --key db.key --cert db.crt --output vmlinuz.signed vmlinuz

# 验证签名
sbverify --cert db.crt vmlinuz.signed

ARM64平台特殊处理: 由于ARM设备通常使用压缩内核,需要先解压:

# 解压原始镜像
unmkimage -i Image.gz -o vmlinux

# 签名后再压缩
sbsign --key db.key --cert db.crt --output vmlinux.signed vmlinux
cat vmlinux.signed | gzip -n -9 > Image.gz.signed

最近遇到个典型问题:某客户的内核在Secure Boot环境下启动失败。排查发现是签名时漏掉了initramfs。正确的做法应该是:

# 生成统一的efi镜像
objcopy \
    --add-section .osrel=/etc/os-release \
    --add-section .cmdline=cmdline.txt \
    --add-section .linux=vmlinuz \
    --add-section .initrd=initrd.img \
    /usr/lib/systemd/boot/efi/linuxx64.efi.stub \
    linux.efi

# 然后对整个efi文件签名
sbsign --key db.key --cert db.crt --output linux.efi.signed linux.efi

3. 内核模块强制签名指南

3.1 编译时自动签名配置

要让内核构建系统自动签名模块,需要正确配置这几个关键选项:

CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
CONFIG_MODULE_SIG_SHA512=y
CONFIG_MODULE_SIG_HASH="sha512"
CONFIG_MODULE_SIG_KEY="certs/signing_key.pem"

这里有个实用技巧:在x86和ARM平台交叉编译时,可以通过环境变量指定不同密钥:

# x86构建
KERNEL_SIGN_KEY=/path/to/x86_key.pem make -j8

# ARM构建
KERNEL_SIGN_KEY=/path/to/arm_key.pem make -j8

3.2 手动签名现有模块

对于已经编译好的模块,可以用内核源码树中的sign-file工具:

# 找到工具路径
find /usr/src -name "sign-file"

# 签名示例
/usr/src/linux-headers-5.15.0-76/scripts/sign-file \
    sha512 \
    /etc/keys/module_key.priv \
    /etc/keys/module_key.x509 \
    my_module.ko

最近帮客户处理过一个棘手问题:他们需要给NVIDIA驱动模块签名。解决方案是:

  1. 先编译出未签名的版本
  2. 使用dkms自动重建
  3. 用脚本批量签名所有.ko文件
#!/bin/bash
for ko in $(find /lib/modules/$(uname -r) -name "*.ko"); do
    /usr/src/linux-headers-$(uname -r)/scripts/sign-file \
        sha512 \
        /etc/keys/module_key.priv \
        /etc/keys/module_key.x509 \
        $ko
done

4. 跨平台密钥管理策略

4.1 多架构密钥方案

在混合架构环境中,我推荐采用这种密钥结构:

├── keys
│   ├── x86
│   │   ├── db.key
│   │   └── db.crt
│   └── arm
│       ├── db.key
│       └── db.crt
└── common
    ├── PK.key
    └── PK.crt

使用ansible批量部署时,可以这样管理:

- name: Deploy secure boot keys
  hosts: all
  tasks:
    - name: Install x86 keys
      when: ansible_architecture == "x86_64"
      copy:
        src: "keys/x86/"
        dest: "/etc/secureboot/"
        
    - name: Install ARM keys
      when: ansible_architecture == "aarch64"
      copy:
        src: "keys/arm/"
        dest: "/etc/secureboot/"

4.2 密钥轮换最佳实践

安全运维中,定期更换密钥很重要。我的经验是:

  1. 准备新密钥时保持旧密钥有效
  2. 分批次更新设备固件
  3. 使用过渡证书确保平滑切换

具体操作:

# 生成新密钥
openssl req -new -x509 -newkey rsa:2048 \
    -keyout new_db.key -out new_db.crt \
    -nodes -days 3650 -subj "/CN=New DB Key/"

# 创建过渡证书链
cat old_db.crt new_db.crt > transition_db.crt

# 用过渡证书更新固件
sudo efivar -n db -a -w -f transition_db.crt

5. 故障排查手册

5.1 常见错误代码解析

错误代码含义解决方案
0x1A安全违规检查内核签名是否过期
0x1E未授权操作确认KEK密钥是否正确
0x26签名无效重新生成签名文件

5.2 诊断工具集锦

查看当前加载的密钥:

sudo keyctl list %:.system_keyring

检查模块签名状态:

modinfo -F sig_key $MODULE

详细验证内核签名:

sbverify --cert db.crt --verbose /boot/vmlinuz

有次客户报告模块加载失败,通过这个命令发现是签名算法不匹配:

dmesg | grep -i "module signature"
[   15.763245] module: xfs: signature verification failed with error -EKEYREJECTED

原因是客户用sha256签的模块,但内核配置要求sha512。解决方法要么重新签名模块,要么修改内核配置:

CONFIG_MODULE_SIG_HASH="sha256"

6. 生产环境部署建议

在企业级部署时,我总结出这几个要点:

  1. 分级测试策略

    • 开发环境:关闭强制验证,方便调试
    • 测试环境:启用验证但不强制
    • 生产环境:全量启用强制验证
  2. 密钥保管方案

    • 私钥存储在HSM硬件设备中
    • 日常使用通过PKCS#11接口调用
    • 设置多因素认证获取权限
  3. 自动化签名流水线

    # 伪代码示例
    def sign_kernel(build):
        if build.arch == "x86_64":
            run_sbsign(build, x86_keys)
        elif build.arch == "arm64":
            run_sbsign(build, arm_keys)
        upload_to_repo(build)
        trigger_deployment(build)
    
  4. 监控告警配置

    • 监控dmesg中的签名失败日志
    • 设置Prometheus告警规则
    groups:
    - name: secureboot.rules
      rules:
      - alert: SecureBootFailure
        expr: rate(kernel_secureboot_errors[5m]) > 0
        labels:
          severity: critical
    

在金融行业客户的实际案例中,我们通过这套方案将安全事件减少了92%。关键是在保证安全性的同时,建立了完善的应急通道。比如当紧急修复时,可以通过临时签名密钥快速部署补丁,事后再统一轮换密钥。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐