Ubuntu Weekly Recipe

第831回暗号化されたUbuntuのルートファイルシステムをリモートから復号する方法

ストレージの暗号化は実施して当然のセキュリティ対策のひとつとなりつつあります。一般的なスマートフォンであればほぼ暗号化された状態ですし、WindowsやmacOSでも暗号化するための設定が用意されています。たとえばWindowsの場合、PCの購入時点で暗号化がオンになっているケースもあります。サーバーも用途によっては暗号化したい場合もあるでしょう。

ここで問題になるのが、どのようにしてストレージ復号用のパスフレーズを入力するのか、です。今回はUbuntuのルートファイルシステムを暗号化した上で、起動時にリモートからSSH経由でストレージの復号する方法を紹介しましょう。

ストレージ暗号化における復号の手間と意味

Ubuntu自体は10年以上前から、インストール時にストレージを暗号化するオプションが提供されていました。現在はその手法としてLVMとLUKSを利用したLinuxにおける一般的な暗号化ストレージのシステムを採用しています。つまりシステムの起動時に、事前に設定されたパスフレーズを入力しないと、そのストレージはマウントできず、ストレージやPCが盗難されたとしてもおいそれとは中身を見られないようにできます。

図1 ストレージ暗号化を設定した場合は、起動時にシステム領域をマウントする前にパスフレーズを要求する

Ubuntu 23.10からは、TPMベースのストレージ暗号化にも実験的に対応しています[1]。これはパスフレーズに相当する、TPM上のマシン固有の情報を取得・利用することで、起動時のパスフレーズの入力が不要になる仕組みです。ただしUbuntuの場合、インストーラーからこの設定をするとカーネルがsnapパッケージ版に変更されます。さらにカーネルとinitramfsは単一バイナリとなり、GRUBメニューはsnapdが管理するなど、さまざま制約が発生します。今のところあくまで「実験的機能」と受け取っておきましょう。

TPMによる自動復号は、便利ではありますがセキュリティ上正しい対応かどうかは個々の使い方において検討が必要です。たとえば可搬性のノートPCの場合、ストレージだけ抜き取られて盗難されることは少なく、PCごと盗まれる可能性のほうが高いです。また、それ以上に懸念しなくてはならないのが、出先での紛失でしょう。TPMによる自動復号が有効化されている場合、ノートPCを入手した人は、電源を入れるだけで復号まではできてしまいます[2]

特にLinuxの場合、物理的なデバイスにアクセスできる状態であれば、このあたりの暗号化が意味をなさなくなります。電源さえ入ればGRUBメニューからカーネルの起動オプションを変更し、管理者権限を取得できてしまうからです。これを防ぐとなるとGRUBやUEFI/BIOSにパスワードを設定したり、復号時にTPMでカーネルの起動パラメーターが変更されていないかを確認したりと、用途・目的に応じて様々な追加対策が必要になります。

図2 第746回でも紹介したように、GRUBパスワードを設定してあげれば、認証しないとエントリーを選択・編集できない。特定のエントリーはパスワードなしで選択のみできるといった設定・運用も可能

管理するパスワードが増えてしまうとそれはそれで煩雑です。そうすると結果的にTPMによる自動復号をやめて、毎回パスフレーズを入力するのが一番無難ではないか、との意見が出てきます。そこで問題になってくるのがリモート端末の起動時はどうするのかという話です。

たとえばサーバーであればIPMIや各種リモート管理システムを使うことで、起動時のコンソールの取得は可能です。しかしながらノートPCやデスクトップPCの場合は、それらの機能を備えていないことのほうが多いでしょう。起動時にファイルシステムをマウントする前にアクセスするためのソフトウェア的な手段が必要となります。Ubuntuを含むLinuxにおいては、次のいずれかを使うことが一般的です。

  • initramfsの中でSSHサーバーを動かし、リモートからログインする
  • 起動時に特定のサーバーへアクセスし、そこから復号用のパスフレーズを取得する

前者はシンプルな方法です。SSH経由でセキュアにアクセスできれば、あとはCLIから復号してマウントするコマンドを実行するだけです。軽量なSSHサーバーであるdropbearがよく使われます。Ubuntuの場合は「dropbear-initramfs」パッケージをインストールするだけで、必要な設定をほぼすべて行ってくれるためおすすめです。注意しなくてはいけないのは、リモートからアクセスするための固定のIPアドレスが必要な点です。もちろんDHCPでIPアドレスを取得して、そのアドレスをなにがしかの方法で通知できるならそれでも構いません。

後者はより複雑な仕組みではありますが、固定のIPアドレスが不要で、ユーザーにとっては手間がかからない仕組みです。たとえば社内ネットワーク内にのみ公開されているサーバーにアクセスしパスフレーズを取得できるようにしておけば、社内ネットワークで起動した場合のみ自動復号する仕組みを実現できます。この用途にはTangサーバー」がよく使われます。別途、サーバーを用意しなくてはならないものの、再起動時のひと手間が省けるのはうれしいでしょう。

今回はまずUbuntuのストレージ暗号化の手順と、dropbearを使った復号方法について紹介します。

Ubuntuデスクトップにおけるストレージ暗号化

最初にUbuntuにおいてストレージを暗号化する方法を紹介しましょう。Ubuntuの場合、暗号化するかどうかは「インストール時のみ」設定できます。もちろんCLIを駆使すれば既存のシステムを暗号化状態に持っていくこともできなくはありませんが、あまりおすすめしません。

図3 インストーラーのストレージの設定画面で「高度な機能」を選択
図4 ⁠LVMと暗号化を使用」を選択

UbuntuではZFSを利用した暗号化もサポートしています。ZFSに興味があればそちらを使ってみても良いでしょう。さらに「ハードウェアベースのディスク全体暗号化」はTPMを利用する方法です。今回はLVMを使うことにします。

図5 復号用のパスフレーズを作成

ここで入力したパスフレーズが、起動時に毎回入力するパスフレーズとなります。このパスフレーズを忘れると、2度とストレージの中身を参照できなくなりますので注意してください。ちなみに入力時点でキーボードレイアウトは自動的に設定されますので、空白を含む印字可能な記号類なども利用可能です。ただし急遽英語キーボードを使わなくてはならなくなったものの、パスフレーズは体で覚えていたなんてケースだと苦労します。それよりもパスフレーズをより長くしておくことが重要です。そのことを踏まえてパスフレーズを決めると良いでしょう。

旧版のインストーラーでは、暗号化ストレージのパスフレーズだけでなく、リカバリーキーの設定も行われていました。Ubuntu 24.04 LTSを含む新版のインストーラーでは、リカバリーキーの設定は行いません。リカバリーキーもほしい場合は、インストール完了後に任意のタイミングでcryptsetup luksAddKeyコマンドでキーを追加してください。

無事にインストール完了したら、再起動してみましょう。通常の起動とは異なり、GRUBメニューが表示されたあとにパスフレーズ入力画面が登場します。

図6 ストレージをマウントするためにパスフレーズを入力する必要がある

パスフレーズの検証に成功し、ストレージがマウントされてログインすると、次のようなストレージの構成になっていることがわかります。

$ lsblk -i
(中略)
sda                           8:0    0    50G  0 disk
|-sda1                        8:1    0     1G  0 part  /boot/efi
|-sda2                        8:2    0     2G  0 part  /boot
`-sda3                        8:3    0  46.9G  0 part
  `-dm_crypt-0              252:0    0  46.9G  0 crypt
    `-ubuntu--vg-ubuntu--lv 252:1    0  46.9G  0 lvm   /

第一パーティション(sda1)はEFI System Partitionです。ここはFATである必要があり、暗号化もされません。第二パーティション(sda2)にはカーネルやinitramfs、GRUB関連が保存されます。Ubuntuの場合はext4ファイルシステムが使われます。この領域もUEFIで起動されるソフトウェアから参照される都合上、暗号化はされていません。暗号化されるのは第三パーティション(sda3)です。ここはLUKSを利用した暗号化ストレージが用意され、その上にLVM領域が構築されています。

ちなみにUbuntuの暗号化ストレージの文脈では次のような用語が登場します。

  • LUKS(Linux Unified Key Setup):Linuxにおいてストレージを暗号化するために作られた仕様。暗号化用のメタデータ(復号用の鍵領域等)を保存する方法などがまとめられている。LUKS1とLUKS2が存在するが、Ubuntuだと基本的にLUKS2を使うことになる。
  • cryptsetup:LUKSを含む様々な仕様の暗号化ストレージを操作するためのCLIツールとライブラリ。ユーザーは基本的にこのcryptsetupコマンドを使って操作する。
  • dm-crypt:暗号化ストレージを構築するためのカーネル側の仕組み。

UbuntuでLVMベースの暗号化ストレージを使う場合は、これらの仕組みを構築して利用します。ただし日常的な利用においてこれらを意識する必要はなく、パスフレーズを追加したり、何かトラブルが発生したときに「cryptsetupコマンドを使ってなんとかする」とだけ覚えておけば良いでしょう。

たとえば現在の暗号化ストレージの状態は次のコマンドで確認できます。

$ sudo cryptsetup luksDump /dev/sda3
LUKS header information
Version:        2
Epoch:          3
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           bc557732-a807-4e31-9bdf-5811b90fd96a
Label:          (no label)
Subsystem:      (no subsystem)
Flags:          (no flags)

Data segments:
  0: crypt
        (中略)

Keyslots:
  0: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      argon2id
        Time cost:  60
        Memory:     94082
        Threads:    2
        (中略)
Tokens:
Digests:
  0: pbkdf2
        Hash:       sha256
        (中略)

ここではLUKSのメタデータから、鍵の数を含むさまざまな情報が得られます。もし鍵を追加した場合は、⁠Keyslots」「1:」が追加されることになります。

これでUbuntuにおける暗号化ストレージの準備は完了です。

dropbearのインストールと設定

システム起動時は常にパスフレーズを入力しなくてはなりません。つまりPCの前にいる必要があります。これはこれでセキュリティの担保になるのですが、リモート接続するマシンだと不便です。たとえばWake on LANでマシンを起動しても、パスフレーズ入力のためにマシンの前に移動しなくてはならないのだとしたら、普通に電源を押しに行くのと変わらなくなってしまいます。

そこでPCが起動したら、ストレージをマウントする前にSSHサーバーを起動し、リモートからログインしてパスフレーズを入力するように変更してみましょう。これには軽量なSSHサーバーであるdropbearが使えます。

Ubuntuを含む一般的なLinuxシステムは、⁠カーネル・initramfs・ルートファイルシステム」から構成されています。大雑把に次のような起動シーケンスをたどります。

  1. ブートローダーはカーネルとinitramfsをメモリにロードし、カーネルの実行を開始する
  2. カーネルはブートローダーから渡されたinitramfsの位置を元に内容をメモリ上に展開し、それを小さなルートファイルシステムとして扱う
  3. その中にシステム初期化用のスクリプトがあるのでカーネルはそれを実行する
  4. 初期化スクリプトの中で、本来のルートファイルシステムに対して適切なカーネルモジュールをロードし、準備し、ルートファイルシステムをマウントする
  5. ルートファイルシステムにあるinit(Ubuntuだとsystemd)が実行される

暗号化ストレージをマウントするためにパスフレーズを入力するのは4のタイミングです。よってinitramfsの中にSSHサーバーをインストールし、4のスクリプト実行の途中でこれを起動すれば良いことになります[3]

initramfsの中でちょっとログインしてコマンドを一個打つだけなので、OpenSSHほど高機能でなくてもかまいません。よってdropbearを使うことが一般的です。幸い、Ubuntuの場合は「dropbear-initramfs」パッケージをインストールするだけで、⁠initramfsの中にSSHサーバーをインストール」まで一括して行ってくれるのです[4]

早速dropbear-initramfsをインストールしてみましょう。

$ sudo apt install dropbear-initramfs
(中略)
Generating Dropbear RSA host key.  Please wait.
(中略)
Generating Dropbear ECDSA host key.  Please wait.
(中略)
Generating Dropbear ED25519 host key.  Please wait.
Generating 256 bit ed25519 key, this may take a while...
256 SHA256:6Aeq3rYPTb7jrz/zOwvbGYw0obqKUgt3qkjsH+5DBCo /etc/dropbear/initramfs/dropbear_ed25519_host_key (ED25519)
(中略)
update-initramfs: deferring update (trigger activated)
Dropbear has been added to the initramfs. Don't forget to check
your "ip=" kernel bootparameter to match your desired initramfs
ip configuration.

(中略)
update-initramfs: Generating /boot/initrd.img-6.8.0-44-generic
dropbear: WARNING: Invalid authorized_keys file, SSH login to initramfs won't work!

ここで表示されているログのポイントは4点です。

  • SSHサーバー用のホスト鍵が/etc/dropbear/initramfs/以下に生成されていること
  • dropbearのIPアドレスを固定化するためには、カーネルの起動パラメーターにip=を設定しなくてはならないこと
  • update-initramfsが実行され、initramfsが再生成されていること
  • dropbearにSSHログインするためには、initramfsの中にauthorized_keysを設定しなくてはならないこと

2番目と4番目については対応が必要であるため、このあとの手順で見ていきます。先にインストールされたものについて確認しましょう。まずdropbear本体は165KiBととても小さなバイナリです。

$ ls -lh /usr/sbin/dropbear
-rwxr-xr-x 1 root root 165K  1月 25  2024 /usr/sbin/dropbear

dropbear関連の設定ファイルはいくつかありますが、主要なものは次のファイルでしょう。

/etc/dropbear/initramfs/dropbear.conf
dropbearの起動オプションや終了時にリンクダウンするネットワークインターフェースを設定するファイルです。特にdropbearの待受ポート番号を変更したければ、このファイルで設定することになります。
/usr/share/initramfs-tools/hooks/dropbear
update-initramfs実行時に呼ばれるファイルです。dropbearを動かすための設定ファイル・鍵ファイルをinitramfsにコピーしています。特に重要なのはauthorized_keysでしょう。/etc/dropbear/initramfs/authorized_keysが存在すれば、それをコピーします。なければ/etc/dropbear/initramfs/id_鍵の種別.pubをコピーします。それらの公開鍵に紐付いた秘密鍵でのみログインできます。ちなみにログイン時のアカウントは「root」です。
/usr/share/initramfs-tools/scripts/init-premount/dropbear
initramfsの中でdropbearを起動する部分のスクリプトです。ルートファイルシステムのマウント処理に入る前に、ネットワーク設定を行い、dropbearを起動します。もし割り当てられたIPアドレスをどこかに通知するのであれば、このスクリプトを「/etc/initramfs-tools」以下の同名のパスに配置し、それをカスタマイズすることになります。このファイルを直接編集してしまうと、パッケージ更新時に元に戻ってしまう可能性があるので注意してください。
/usr/share/initramfs-tools/scripts/init-bottom/dropbear
initramfsの中でマウント完了後にdropbearを終了するスクリプトです。

最低限必要な設定はauthorized_keysの準備だけです。これだけでPCの起動後にinitramfsの中でdropbearが起動し、SSHログインできるようになります。ただしIPアドレスはDHCPで取得されるため、その値を知るには別の方法が必要です。IPv6のRAが動いている環境であれば、プレフィックスとMACアドレスから事前にアドレスを類推できるでしょう。

また、dropbearのポート番号の変更も検討すべき内容です。というのも、もし固定IPアドレスをPCに割り振る場合、dropbearが動く場合と、PCが起動したあとで同じIPアドレスを使いまわす可能性があるからです。このときPC側でSSHサーバーが起動した状態で、SSHログインしようとすると、dropbearのホストキーとPCのSSHサーバーのホストキーが異なるため、⁠ホストキーが異なる」旨のエラーになります。ポート番号が異なれば、IPアドレスが同じでも~/.ssh/known_hostsのエントリーは異なるため、このエラーは発生しません。

ポート番号を変えたければ/etc/dropbear/initramfs/dropbear.confを次のように修正してください。

# Command line options to pass to dropbear(8)
#
#DROPBEAR_OPTIONS=""
DROPBEAR_OPTIONS="-p 2222"

あとはauthorized_keysを準備します。たとえばGitHubにアップロードしている公開鍵のリストを取得したければ次のような手順になります。

wgetの場合:
$ wget https://github.com/m-shibata.keys -qO authorized_keys

curlの場合:
$ curl https://github.com/itiut.keys -o authorized_keys

取得したauthorized_keysを適切な場所にコピーして、initramfsを更新します。

$ sudo cp authorized_keys /etc/dropbear/initramfs/authorized_keys
$ sudo update-initramfs -u -k all

最後の-k allはインストールされているすべてのカーネルバージョン向けのinitramfsを更新することを意味します。

これで準備は完了です。PCを再起動してみましょう。

dropbearでログインした環境から復号コマンドを実行

再起動後に少し待てばパスフレーズの入力画面が表示されます。何らかの方法でIPアドレスを確認して、SSHログインしてください。IPv6対応環境であれば再起動前のIPv6アドレスがそのまま使えるはずです。

$ ssh -p 2222 root@192.0.2.100
(中略)

BusyBox v1.36.1 (Ubuntu 1:1.36.1-6ubuntu3.1) built-in shell (ash)
Enter 'help' for a list of built-in commands.

#

うまくBusyBoxシェルになったら成功です。なお、-p 2222はポート番号を変更している場合にのみ指定が必要です。また、以下のようなオプションをつけることで、ホストキーのチェックや~/.ssh/known_hostsの更新を防げます。

$ ssh -p 2222 -o StrictHostKeyChecking=no \
  -o UserKnownHostsFile=/dev/null root@192.0.2.100

このあたりは使い方に応じて変化させれば良いでしょう。

ちなみにログイン先のホームディレクトリはinitramfs用に一時的に作成したものとなります。

# pwd
/root-sDDs0Edbqo

# ps
(中略)
  264 root      110m S    /usr/sbin/plymouthd --mode=boot --attach-to-session --pid-file=/run/plymouth/pid
  268 root      4700 S    /sbin/dropbear -Fs
  275 root      3124 S    {cryptroot} /bin/sh /scripts/local-top/cryptroot
  326 root      4700 R    /sbin/dropbear -Fs -2 8
  327 root      2984 S    -sh
 1910 root      2688 S    /lib/cryptsetup/askpass Please unlock disk dm_crypt-0:
 1911 root      3124 S    {cryptroot} /bin/sh /scripts/local-top/cryptroot
 1912 root     11684 S    /sbin/cryptsetup -T1 --type=luks --key-file=- open -- /dev/sda3 dm_crypt-0
 1914 root      3764 S    /bin/plymouth ask-for-password --prompt Please unlock disk dm_crypt-0
 2171 root      2984 R    {ps} -sh

# cat /run/net-enp5s0.conf
DEVICE='enp5s0'
PROTO='dhcp'
IPV4ADDR='192.0.2.100'
IPV4BROADCAST='192.0.2.255'
IPV4NETMASK='255.255.255.0'
IPV4GATEWAY='192.0.2.1'
IPV4DNS0='192.0.2.1'
HOSTNAME='fde'
DNSDOMAIN='lxd'
ROOTSERVER='192.0.2.1'
filename=''
DHCPLEASETIME='3600'
DOMAINSEARCH=''

プロセスとしてはdropbearが起動したあとに、/scripts/local-top/cryptrootが起動しています。このcryptrootスクリプトからaskpassコマンドなどを実行し、パスフレーズの入力待ちになっている状態です。実際にディスプレイへ描画をしているのはplymouthです。

ちなみに今回はIPアドレスを指定していないために、ネットワークインターフェースはDHCPでアドレスを取得しています。

さて、無事にログインできた状態でやるべきことは「復号してマウント」です。これに関してはcryptroot-unlockを実行するだけで完了します。

# cryptroot-unlock
Please unlock disk dm_crypt-0: パスフレーズの入力
cryptsetup: dm_crypt-0 set up successfully
# Connection to 192.0.2.1 closed by remote host.
Connection to 192.0.2.1 closed.

パスフレーズを入力して問題なければset up successfullyと表示されます。その結果無事にファイルシステムがマウントされ、前述のinit-bottom/dropbearが実行されます。このスクリプトの中でdropbearが終了するために、上記ではclosed by remote hostと表示されているわけです。

これで最低限リモートからストレージの復号を行えるようになりました。

IPアドレスの固定化

ここまでの手順では、dropbearが起動したときのネットワークインターフェースに割り振られたIPアドレスを、何らかの方法で取得する必要がありました。これは環境によってはとても面倒ですので、IPアドレスを固定化してしまいましょう。

しかしながらnetplanなどはまだ起動していないために、普通の方法では静的なIPアドレスを割り振れません。そこでカーネルパラメーターにIPアドレスの情報を記載します。これはNFS向けの仕組みなのですが、IPアドレスを割り振る部分については別にNFS使わなくても利用可能です。実際のところは指定されたパラメーターのうち必要なフィールドを、initramfsの中で使用しているだけです。

具体的にはGRUBの設定でカーネルのパラメーターを記載します。GRUBの設定については第743回のUbuntuの標準ブートローダーであるGRUBを改めて見直すも参考になるでしょう。

具体的には/etc/default/grubを次のように変更します。

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
#GRUB_CMDLINE_LINUX=""
GRUB_CMDLINE_LINUX="ip=192.0.2.100::192.0.2.1:255.255.255.0:fde:enp5s0:none"

ここでip=の書式は次のとおりです。

ip=自装置に割り当てたいIPアドレス:NFSサーバーのIPアドレス:ゲートウェイ:ネットマスク:ホスト名:インターフェース名:自動設定方法

さらに後ろに設定をつなげることも可能ではありますが、今回は使用しません。NFSサーバーのIPアドレスも関係ないので空にしてあります。複数のネットワークインターフェースがある場合は、インターフェース名を明記しましょう。単一の場合は入力しなくても大丈夫です。自動設定方法はDHCPやBOOTPを使用したい場合に設定します。今回は固定のIPアドレスを割り振るために「none(自動設定しない⁠⁠」を指定しています。

ちなみにこの仕組みはIPv6には未対応です。IPv6アドレスについてはRAやDHCPv6側でなんとかすることになります。

あとはこの設定を反映し、再起動するだけです。

$ sudo update-grub

IPアドレスが期待どおりに設定されているかを確認してください。もしうまく動かない場合は、BusyBoxシェルの中で/run/net-enp5s0.confなどのインターフェース設定を確認し、何が足りないか調べてみましょう。


ここまでの手順により、暗号化されたストレージを起動時にリモートからでも復号できるようになりました。今回はUbuntuデスクトップベースで説明しましたが、この方法はUbuntuサーバーでも問題なく利用できます。

ちなみに特定のネットワークに接続した状態で起動したときは、自動的に復号したいのであれば、前述したようにTangサーバーを使う方法が存在します。これはまた別の機会に紹介する予定です。

おすすめ記事

記事・ニュース一覧