在Ubuntu服务器运维中,启用UEFI安全启动后,系统会拒绝加载未经签名的内核模块,这是一个直接且常见的安全特性。这意味着你为硬件(如特定网卡、存储控制器)或功能(如虚拟化驱动)自行编译的内核模块,或者第三方提供的闭源驱动(如NVIDIA、VMware),将无法直接加载,导致硬件无法识别或功能缺失。解决这个问题的核心路径是:为你的内核模块生成专属密钥和证书,并用它们对模块进行签名,最后将你的证书注册到系统的可信密钥数据库和UEFI固件中。整个过程涉及Linux内核、UEFI固件以及Ubuntu的密钥管理机制。

理解UEFI安全启动与内核模块签名的关系

UEFI安全启动是现代计算机固件的一项安全功能,旨在确保系统只加载被信任的、经过签名的引导加载程序和操作系统内核。当Ubuntu在启用安全启动的状态下安装时,它会使用一个由Microsoft签名的“平台密钥”来签署其引导加载程序(如GRUB)和内核。这个链条确保了从固件到操作系统的启动过程未被篡改。然而,内核模块是内核在运行时动态加载的代码,为了维持安全链的完整性,内核本身也要求加载的模块必须被其信任的密钥签名。系统默认信任由Ubuntu/Debian发布的“内核构建密钥”签名的模块,以及你自己注册的“机器所有者密钥”。因此,加载自定义模块的关键,就在于创建并注册属于你自己的MOK。

准备环境与生成密钥对

首先,你需要安装必要的工具。在Ubuntu上,这通常包括"openssl"和"mokutil"。使用OpenSSL生成一个RSA密钥对(私钥和公钥证书)是第一步。私钥必须严格保密,而证书则用于注册到系统中。

sudo apt update
sudo apt install openssl mokutil -y

# 创建用于存放密钥的目录并进入
mkdir -p ~/uefi-module-signing
cd ~/uefi-module-signing

# 生成私钥(例如使用2048位长度)
openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 36500 -subj "/CN=My Custom Kernel Module Key/"

# 转换证书格式为PEM(便于查看)
openssl x509 -in MOK.der -inform DER -outform PEM -out MOK.pem

这里,"MOK.priv"是你的私钥,用于签名。"MOK.der"是DER格式的证书,需要注册到UEFI和系统中。"MOK.pem"是证书的PEM格式,方便阅读。请务必将"MOK.priv"备份到安全的位置。

使用密钥为内核模块签名

假设你已经有一个编译好的内核模块文件,例如"mymodule.ko"。签名需要使用内核提供的"sign-file"脚本,该脚本通常位于内核源码的"scripts"目录下。在已安装内核头文件的系统中,你也可以找到它。

# 查找sign-file工具的位置
find /usr/src -name "sign-file" -type f 2>/dev/null

# 通常的路径(以当前内核版本为例,如5.15.0-91-generic)
SIGN_FILE="/usr/src/linux-headers-$(uname -r)/scripts/sign-file"

# 为模块签名
sudo bash "${SIGN_FILE}" sha256 ~/uefi-module-signing/MOK.priv ~/uefi-module-signing/MOK.der /path/to/your/mymodule.ko

执行后,"mymodule.ko"文件本身会被附加一个数字签名。你可以使用"modinfo"命令来验证签名是否已附加,并查看签名者的信息。

modinfo /path/to/your/mymodule.ko | grep -i signature

将证书注册到系统:使用MOK(机器所有者密钥)工具

仅仅签名还不够,系统必须信任你的证书。我们将使用"mokutil"工具将证书("MOK.der")注册到系统的MOK列表中。这个过程需要两步:首先在用户空间导入,然后在下次启动时于UEFI界面确认。

# 将证书导入待注册列表
sudo mokutil --import ~/uefi-module-signing/MOK.der

# 执行此命令后,会提示你设置一个一次性密码(例如`uefi123`),请牢记。

导入完成后,你必须重启系统。在启动初期,UEFI固件会检测到有待处理的MOK注册请求,并自动启动一个名为“MOK Management”的蓝色界面。在此界面中,你需要:

1. 选择“Enroll MOK”;

2. 选择“Continue”;

3. 输入之前设置的密码;

4. 选择“Yes”来确认注册;

5. 最后选择“Reboot”完成操作。至此,你的证书已被永久添加到UEFI固件和系统的可信密钥数据库中。

验证与加载已签名的模块

重启后,你可以验证证书是否已成功注册。

# 查看系统当前信任的密钥
mokutil --list-enrolled

# 查看已导入的MOK(包括已注册和待处理的)
mokutil --list-new

现在,尝试加载你已签名的内核模块。使用"insmod"或"modprobe"命令。

sudo insmod /path/to/your/mymodule.ko

# 或者将模块复制到标准目录并使用modprobe
sudo cp /path/to/your/mymodule.ko /lib/modules/$(uname -r)/kernel/drivers/misc/
sudo depmod -a
sudo modprobe mymodule

如果签名正确且证书已注册,模块将成功加载。你可以通过"lsmod | grep mymodule"和"dmesg | tail"来检查。

自动化签名与部署的进阶考量

在生产环境中,手动为每个模块签名效率低下。你可以创建一个自动化脚本,在DKMS(动态内核模块支持)构建后自动签名。关键在于挂钩DKMS的"post_install"脚本。例如,为你的DKMS模块创建一个配置文件:

# 在DKMS模块目录下创建 /usr/src/-/dkms.conf
PACKAGE_NAME="mymodule"
PACKAGE_VERSION="1.0"
BUILT_MODULE_NAME[0]="$PACKAGE_NAME"
DEST_MODULE_LOCATION[0]="/kernel/drivers/misc"
AUTOINSTALL="yes"
POST_INSTALL="scripts/sign-module.sh" # 指定后处理脚本

然后,编写"sign-module.sh"脚本,内容包含上述的签名命令,并确保它能找到正确的".ko"文件路径和你的私钥。私钥的管理是另一个关键安全议题,建议将私钥存储在加密的、访问受限的位置,仅允许构建服务账户读取。

故障排除与常见问题

1. 模块加载仍失败,提示“Required key not available”: 首先确认模块确实已签名("modinfo")。其次,确认你的证书已成功注册("mokutil --list-enrolled")。有时,系统可能缓存了旧的密钥信息,可以尝试使用"sudo kmodsign"命令重新签名并重启。

2. 错过了MOK管理界面: 如果重启时错过了蓝色界面,注册流程会中止。你可以使用"sudo mokutil --reset"清除待处理请求,然后重新执行"--import"并再次重启。

3. 多内核版本支持: 你为模块进行的签名是针对特定内核版本的内核构建密钥的哈希值。如果你为多个内核版本编译了模块(例如通过DKMS),每个".ko"文件都需要用同样的MOK密钥分别签名一次。

4. 安全启动完全禁用?: 虽然可以通过BIOS设置禁用安全启动来绕过所有问题,但这会降低系统整体安全性,不推荐用于生产环境。正确配置签名才是符合安全最佳实践的解决方案。

结论:平衡安全与灵活性

在Ubuntu运维中处理UEFI安全启动下的内核模块加载,是一个在“安全刚性”与“运维灵活性”之间寻求平衡的典型场景。强制签名并非为了制造障碍,而是为了构建一个从固件到内核、再到内核模块的完整信任链,有效抵御rootkit等底层攻击。通过掌握MOK密钥的生成、注册和模块签名流程,运维人员既能保持UEFI安全启动带来的安全保障,又能无缝集成自定义或第三方硬件驱动。这一能力对于依赖特定硬件或定制化内核功能的企业级Ubuntu部署至关重要,是将标准安全策略落地到实际业务环境中的关键技能。