🍣

LVM on LUKS なディスクをWSLにマウントする

2024/10/25に公開

LVM on LUKS とは?

大容量かつ安全な(暗号化された)ディスクを構成したいときの選択肢の1つ。
WindowsでいうBitLockerみたいなやつのLinux版のソリューションです。

https://wiki.archlinux.jp/index.php/Dm-crypt/システム全体の暗号化#LVM_on_LUKS

このWIKIを参考にして、私は以下のような構成にしました。
(今考えると、LUKS on LVMの方が私の用途に合っていたように思いますが、多分WIKIを誤読しました)

+---------------------------------------------+
|          Logical Volume Group               |
|       /dev/mapper/vg-ext--lv-ext            |
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|                     |                       |
|   Logical volume1   |    Logical volume2    |
|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _|
|                     |                       |
| LUKS encrypted disk |  LUKS encrypted disk  |
|      /dev/sda       |       /dev/sdb        |
+---------------------------------------------+

つまり、

  1. 各ディスクはLUKSデバイスとして認識されて
  2. LUKSが解錠されればLVMのPV (物理ボリューム ≠ 物理ディスク) として認識される
  3. すべてのPVが揃えばLV (論理ボリューム) として認識され
  4. マウントが可能になる

という流れです。

Linuxで使う場合

だいたいのLinuxディストリビューションではLVMもLUKSも認識されるので何も問題ありません。

sudo cryptsetup luksOpen /dev/sda lv-a # 解錠
sudo cryptsetup luksOpen /dev/sdb lv-b # 解錠
# LVがlv-a, lv-bで構成されていれば自動的にLVが構成される
sudo mount /dev/mapper/vg-ext--lv-ext # マウント

みたいにすればよいです。
ちなみに3行目の自動構成に関しては (おそらく)udevがデバイス構成変更の通知を受け取って、/lib/udev/rules.d/56-lvm.rulesとかの設定に従って自動的にLVを構成してくれています。(よくわからんが便利)

Windowsで使う場合

Windowsの場合は面倒です。

① WSLに外部ディスクの存在を伝える

Windowsしか外部ディスクの存在を知りません。
そのWindowsもLUKSなんてファイルシステムは知らないため、「不明 オフライン」になります。

スクリプト

これをWSLに伝えるには次のように powershellで wsl --mount DEVICE_ID --bareを行います。

LUKSは不明なファイルシステム → パーティションを認識できない

ということで、 $disk.Partitions -eq 0 を使って判定しています。

$OutputEncoding = [System.Text.Encoding]::UTF8
$disks = Get-WmiObject -Query "SELECT * from Win32_DiskDrive"

foreach ($disk in $disks)
{
	if ($disk.Partitions -eq 0)
	{
		wsl --mount $disk.DeviceID --bare
	}
}

sleep 5

これでWSL側で外部デバイスがうまく認識されるようになります。
(LVMに限らず外部USB等をWSLに直接認識させる場合もこのようなマウントが必要です)

スクリプトをダブルクリックで実行したい

Windows起動直後にデスクトップにあるファイルをダブルクリックで実行できれば嬉しいですね。
管理者権限が必要なのでスタートアップだとおそらく失敗します。

管理権限が必要なPowerShellのスクリプトをダブルクリックで実行するには、

  1. スクリプト作成
  2. スクリプトのショートカットを作成
  3. プロパティの「リンク先」を次のように書き換え
    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File "対象ファイルのフルパス"
    
    とすれば良いです。

② LVMの自動認識のタイミングのバグ(たぶん)

通常の暗号化されていないLVMデバイスは、最初からLVMデバイスであると認識可能なので、
(おそらく) udev によって自動的に特定の VG (Volume Group)のPVとして認識されます。
VGのメンバーとなるPVすべてが同時に存在する前提があるわけです。

しかし今回のような、最初はLUKSデバイスとして認識されるような環境だと
普通にデバイスを解錠するだけだと

1. 1/4のPVが存在
2. 2/4のPVが存在
3. 3/4のPVが存在
4. 4/4のPVが存在

というようにシーケンシャルに認識されてしまいます。

Linuxの場合、構成するすべてのディスクが存在して始めてLVMとして構成してくれます。

1. 1/4のPVが存在
2. 2/4のPVが存在
3. 3/4のPVが存在
4. 4/4のPVが存在 ← ここではじめてLVMとして構成される

WSLの場合、おそらくここに不具合があります。

1. 1/4のPVが存在
2. 2/4のPVが存在
3. 3/4のPVが存在 ← なぜかここでLVMとして構成される
4. 4/4のPVが存在

構成するPVが足りないわけで、こんな感じの表示になります。

sdf              8:80   0   7.3T  0 disk
└─lv-c412ee80  252:0    0   7.3T  0 crypt
  └─vg--ext-lv--ext_rimage_0
               252:4    0  14.6T  0 lvm
    └─vg--ext-lv--ext
               252:6    0  29.1T  0 lvm
sdg              8:96   0   7.3T  0 disk
└─lv-de258129  252:1    0   7.3T  0 crypt
  └─vg--ext-lv--ext_rimage_1
               252:5    0  14.6T  0 lvm
    └─vg--ext-lv--ext
               252:6    0  29.1T  0 lvm
sdh              8:112  0   7.3T  0 disk
└─lv-3fb5f267  252:2    0   7.3T  0 crypt
  └─vg--ext-lv--ext_rimage_1
               252:5    0  14.6T  0 lvm
    └─vg--ext-lv--ext
               252:6    0  29.1T  0 lvm
sdi              8:128  0   7.3T  0 disk
└─lv-01aa0890  252:7    0   7.3T  0 crypt # ← うまく認識されていない

vg--ext-lv--ext_rimage_0-missing_0_0 # ← missingになっている252:3    0   7.3T  0 lvm
└─vg--ext-lv--ext_rimage_0
               252:4    0  14.6T  0 lvm
  └─vg--ext-lv--ext
               252:6    0  29.1T  0 lvm

解消方針

期待するよりも早い段階で、LVMのPVとして認識され、LVとして自動的に認識されているため、

自動認識を止めさせれば良いです。

デバイスの自動認識はudevが司っているはずなので、/lib/udev/rules.d/56-lvm.rules を無効化すれば良さそうなのですが、WSLではうまくいきませんでした。

解決

Linuxでは問題なく、WSLでのみ起こっているため、多少Hackyな手法でも問題ないでしょう。
こんな感じで解決しました。

  read -rsp "Enter passphrase: " passwd; echo
  for uuid in "${uuids[@]}"; do
    id=$(echo "$uuid" | awk -F'-' '{print $1}')
    name=$(lsblk -o NAME,UUID | grep "$uuid" | awk '{print $1}')
    is_mounted=$(lsblk | grep "lv-$id" | wc -l)
    info "$name ($uuid) -> lv-$id (mounted: $is_mounted)"
    # 末尾&で非同期に処理することでほぼ同時に解錠する
    echo "$passwd" | sudo cryptsetup luksOpen "/dev/disk/by-uuid/$uuid" "lv-$id" &
  done
  wait # 非同期処理を待機

全文

WSLに外部デバイスを伝えるスクリプト

$OutputEncoding = [System.Text.Encoding]::UTF8
$disks = Get-WmiObject -Query "SELECT * from Win32_DiskDrive"

foreach ($disk in $disks)
{
	if ($disk.Partitions -eq 0)
	{
		wsl --mount $disk.DeviceID --bare
	}
}

sleep 5

解錠用のスクリプト

WSLの ~/di/ext にマウントします

#!/bin/bash

set -eu

TARGET="$HOME/di/ext"

is_mounted() {
  lsblk | grep $TARGET | grep -q lvm
}

info() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}

if is_mounted; then
  echo "Already mounted"
  exit 0
fi

# list devices
info "Listing devices:"
uuids=()
while read -r uuid _ _; do
  if ! [[ $uuid =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]]; then
    continue
  fi
  if ! sudo file -sL "/dev/disk/by-uuid/$uuid" | grep -q LUKS; then
    continue
  fi
  uuids+=("$uuid")
done< <(sudo lsblk -o UUID,NAME,TYPE | grep disk | grep -v nvme)

# Check if locked disk exists
has_one=0
for uuid in "${uuids[@]}"; do
  id=$(echo "$uuid" | awk -F'-' '{print $1}')
  if ! lsblk | grep "lv-$id"; then
    has_one=1
    break
  fi
done

info "Found ${#uuids[@]} LUKS devices. Unlocking.."
if [ $has_one -eq 1 ]; then
  # unlock disks

  read -rsp "Enter passphrase: " passwd; echo
  for uuid in "${uuids[@]}"; do
    id=$(echo "$uuid" | awk -F'-' '{print $1}')
    name=$(lsblk -o NAME,UUID | grep "$uuid" | awk '{print $1}')
    is_mounted=$(lsblk | grep "lv-$id" | wc -l)
    info "$name ($uuid) -> lv-$id (mounted: $is_mounted)"
    # FIXME: hacky way of unlocking disks in parallel.
    # LVM is automatically scanned and activated when 3/4 disks are unlocked,
    # so we need to unlock all disks at once before udev(?) activating LVM.
    # This behavior occurs only on WSL (not on pure Linux).
    echo "$passwd" | sudo cryptsetup luksOpen "/dev/disk/by-uuid/$uuid" "lv-$id" &
  done

  # wait for all disks to unlock
  wait
  sleep 3
fi

# mount disks
# Check if mounted
if ! is_mounted; then
  info "Mounting disks.."
  sudo mount /dev/mapper/vg--ext-lv--ext "$TARGET"
fi

info 'done'

Discussion