午前2時14分のモーニングコール:ハードウェアがソフトウェアを信頼しなくなったとき
午前2時14分、Slackのアラートが鳴り響きました。定期的なセキュリティパッチ適用後、プライマリデータベースノードの再起動に失敗したのです。リモートコンソールを開くと、ログインプロンプトではなく、冷ややかな白黒のエラー画面が表示されていました:Security Violation(セキュリティ違反)。システムがOSのロードを拒否していたのです。最初はNVMeドライブの故障を疑いましたが、真実はもっと機械的なものでした。セキュアブートが正常に機能し、認識できないカーネルを拒絶していたのです。
本番環境の障害に直面すると、多くの管理者は誘惑に負けてUEFI設定でセキュアブートを無効にしてしまいます。30秒で解決する修正ですが、それは玄関のドアを全開にするようなものです。セキュアブートなしでは、ブートキットやUEFIルートキットに対して無防備になります。これらはOSが起動する前のブートプロセスに潜り込むマルウェアで、標準的なアンチウイルスソフトでは検知できません。私は独自の信頼の連鎖を構築することで、この根本原因を解決することに決めました。
信頼の連鎖(Chain of Trust)の仕組み
セキュアブートは本質的にゲートキーパー(門番)です。起動時、UEFIファームウェアはブートローダー(通常はGRUB)のデジタル署名を確認します。その署名がファームウェアのデータベースにある信頼されたキーと一致すれば、実行されます。次にブートローダーがLinuxカーネルの署名を検証します。これにより、ハードウェアからユーザースペースまで途切れることのない「信頼の連鎖」が生まれます。
ほとんどのマザーボードはMicrosoftのキーのみを搭載して出荷されます。UbuntuやFedoraのような主要ディストリビューションは、Microsoftが署名したshimを使用することでこれを回避しています。しかし、カスタムカーネルをコンパイルしたり、ZFSやNvidiaのようなツリー外(out-of-tree)モジュールを使用したりした瞬間に、この連鎖は切れてしまいます。ディストリビューションが署名済みバイナリを提供していない場合、行き詰まってしまいます。カスタムコードを実行しつつサーバーのセキュリティを維持するためには、自分自身が認証局(CA)になる必要がありました。
独自の信頼を構築する:ステップバイステップのワークフロー
このロックアウト状態を解消するために、Machine Owner Key (MOK) を導入しました。これはユーザーが生成し、UEFIファームウェアに手動で信頼させるキーのことです。以下は、そのサーバーをオンラインに戻すために私が行った正確なプロセスです。
1. ブート失敗の診断
何かを変更する前に、セキュアブートがボトルネックであることを確認する必要がありました。Linuxでこれらのキーを管理するための標準ツールであるmokutilを使用しました。
# セキュアブートの状態を確認する
mokutil --sb-state
出力結果に SecureBoot enabled と表示されている場合、ファームウェアは署名のないプロセスをすべて停止させます。私の任務は、ホワイトリストに特定の署名を追加することでした。
2. 2048ビットの署名キーを生成する
カーネルに署名するための秘密鍵と、ファームウェア用の公開証明書の2つが必要でした。これらをセキュアなディレクトリに隔離しました。覚えておいてください、これらのキーが誰でも読み取れる状態であれば、セキュリティは形骸化してしまいます。
sudo mkdir -p /var/lib/shim-signed/mok
sudo chmod 700 /var/lib/shim-signed/mok
cd /var/lib/shim-signed/mok
# 100年間の証明書を作成する
sudo openssl req -new -x509 -newkey rsa:2048 -nodes -days 36500 -outform DER -keyout MOK.priv -out MOK.der
Common Nameを求められたら、”Ops-Prod-Kernel-Signer”のように分かりやすい名前を付けてください。これは、後で整理されていないUEFIメニューの中でキーを特定するのに役立ちます。
3. ファームウェアにキーを登録する
ハードウェアが認識しなければ、キーは役に立ちません。mokutilを使用して、公開用の.derファイルを登録待ち(enrollment)の状態にしました。15以上の高トラフィックノードを管理してきた経験から、これらのコマンドは念入りにチェックすることを学びました。一文字のタイポがデータセンターでの作業を一時間増やすことになりかねないからです。
sudo mokutil --import MOK.der
ユーティリティは一時的なパスワードを要求してきました。これは次回の再起動時に使用する一回限りのコードです。付箋に書き留めて、リセットボタンを押しました。
再起動中、「MOK Management」というタイトルのブルースクリーンが表示されました。Enroll MOKを選択し、キーのフィンガープリントが一致することを確認して、一回限りのパスワードを入力しました。もう一度再起動すると、ファームウェアは正式に自前のCAを信頼するようになりました。
4. カーネルバイナリに署名する
最後のハードルは、実際のカーネルファイルに署名することでした。キーを登録しても、署名のないカーネルは信頼されません。sbsignを使用して、バイナリを新しい署名でラップしました。
# ターゲットのカーネルを定義
KERNEL_PATH="/boot/vmlinuz-$(uname -r)"
# 署名を適用
sudo sbsign --key MOK.priv --cert MOK.der --output $KERNEL_PATH.signed $KERNEL_PATH
# 署名なしのバージョンを上書き
sudo mv $KERNEL_PATH.signed $KERNEL_PATH
Nvidiaのようなドライバの場合は、カーネルの内部署名スクリプトが必要になります。最近のシステムの多くは、/etc/dkms/にあるDKMS設定を介してこれを自動化でき、新しいアップデートが自動的に署名されるようになっています。
結果の強化
午前4時までには、データベースはクラスターに戻り、セキュアブートは有効になり、Security Violationは消えました。この安心感は、労力に見合うものです。これで、自分が個人的に検証したもの以外は起動を拒否するシステムが手に入りました。
本番環境における注意点が一つあります。MOK.privファイルは命がけで守ってください。攻撃者がその秘密鍵を盗めば、独自の悪意あるカーネルに署名して、防御網を完全にバイパスできてしまいます。私は鍵を暗号化された保管庫(vault)に保存し、メンテナンス時のみマウントするようにしています。
セキュアブートを手動で設定するのは、難解なBIOSメニューとの戦いのように感じられるかもしれませんが、システムの完全性(Integrity)にとっては決定的な勝利となります。その連鎖がロックされれば、たとえ午前2時にアラートが鳴り始めても、より安らかに眠れるはずです。

