diff --git a/Makefile b/Makefile index d9ac8af..c186b9b 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,10 @@ create-tarball: # copy source code to src/ git clone --no-hardlinks . /tmp/cryptpilot-tarball/cryptpilot-${VERSION}/src/ - cd /tmp/cryptpilot-tarball/cryptpilot-${VERSION}/src && git clean -xdf + # apply uncommitted changes (staged + unstaged) to the cloned copy + git diff --binary HEAD | git -C /tmp/cryptpilot-tarball/cryptpilot-${VERSION}/src apply --binary --allow-empty + # copy untracked (new) files that are not ignored + if [ -n "$(git ls-files --others --exclude-standard)" ] ; then git ls-files --others --exclude-standard -z | xargs -0 tar -c -f - | tar -x -f - -C /tmp/cryptpilot-tarball/cryptpilot-${VERSION}/src/ ; fi tar -czf /tmp/cryptpilot-${VERSION}-vendored-source.tar.gz -C /tmp/cryptpilot-tarball/ cryptpilot-${VERSION} diff --git a/cryptpilot-convert.sh b/cryptpilot-convert.sh index 8d3afa9..3b97804 100755 --- a/cryptpilot-convert.sh +++ b/cryptpilot-convert.sh @@ -213,7 +213,7 @@ proc::print_help_and_exit() { echo " --out The output OS image file (vhd or qcow2)." echo " -c, --config-dir The directory containing cryptpilot configuration files." echo " --rootfs-passphrase The passphrase for rootfs encryption." - echo " --rootfs-no-encryption Skip rootfs encryption, but keep the rootfs measuring feature enabled." + echo " --rootfs-no-encryption Skip rootfs encryption, but keep the rootfs measuring feature enabled." echo " --rootfs-part-num The partition number of the rootfs partition on the original disk. By default the tool will" echo " search for the rootfs partition by label='root' and fail if not found. You can override this" echo " behavior by specifying the partition number." @@ -1109,8 +1109,8 @@ step::create_lvm_part() { log::info "Initializing LVM physical volume and volume group" proc::exec_subshell_flose_fds pvcreate --force "$lvm_part" - proc::exec_subshell_flose_fds vgcreate --force system "$lvm_part" --setautoactivation n # disable auto activation of LVM volumes to prevent it from being activated unexpectedly - proc::exec_subshell_flose_fds vgchange -a y system # activate the volume group + proc::exec_subshell_flose_fds vgcreate --force cryptpilot "$lvm_part" --setautoactivation n # disable auto activation of LVM volumes to prevent it from being activated unexpectedly + proc::exec_subshell_flose_fds vgchange -a y cryptpilot # activate the volume group } step::setup_rootfs_lv_with_encrypt() { @@ -1121,15 +1121,15 @@ step::setup_rootfs_lv_with_encrypt() { rootfs_size_in_byte=$(stat --printf="%s" "${rootfs_file_path}") local rootfs_lv_size_in_bytes=$((rootfs_size_in_byte + 16 * 1024 * 1024)) # original rootfs partition size plus LUKS2 header size log::info "Creating rootfs logical volume" - proc::hook_exit "[[ -e /dev/mapper/system-rootfs ]] && disk::dm_remove_all ${device}" - proc::exec_subshell_flose_fds lvcreate -n rootfs --size ${rootfs_lv_size_in_bytes}B system # Note that the real size will be a little bit larger than the specified size, since they will be aligned to the Physical Extentsize (PE) size, which by default is 4MB. + proc::hook_exit "[[ -e /dev/mapper/cryptpilot-rootfs ]] && disk::dm_remove_all ${device}" + proc::exec_subshell_flose_fds lvcreate -n rootfs --size ${rootfs_lv_size_in_bytes}B cryptpilot # Note that the real size will be a little bit larger than the specified size, since they will be aligned to the Physical Extentsize (PE) size, which by default is 4MB. # Create a encrypted volume log::info "Encrypting rootfs logical volume with LUKS2" - echo -n "${rootfs_passphrase}" | cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --subsystem cryptpilot /dev/mapper/system-rootfs --key-file=- + echo -n "${rootfs_passphrase}" | cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --subsystem cryptpilot /dev/mapper/cryptpilot-rootfs --key-file=- proc::hook_exit "[[ -e /dev/mapper/rootfs ]] && disk::dm_remove_wait_busy rootfs" log::info "Opening encrypted rootfs volume" - echo -n "${rootfs_passphrase}" | cryptsetup open /dev/mapper/system-rootfs rootfs --key-file=- + echo -n "${rootfs_passphrase}" | cryptsetup open /dev/mapper/cryptpilot-rootfs rootfs --key-file=- # Copy rootfs content to the encrypted volume log::info "Copying rootfs content to the encrypted volume" dd status=progress "if=${rootfs_file_path}" of=/dev/mapper/rootfs bs=4M @@ -1143,11 +1143,11 @@ step::setup_rootfs_lv_without_encrypt() { rootfs_size_in_byte=$(stat --printf="%s" "${rootfs_file_path}") local rootfs_lv_size_in_bytes=$((rootfs_size_in_byte + 16 * 1024 * 1024)) # original rootfs partition size plus LUKS2 header size log::info "Creating rootfs logical volume" - proc::hook_exit "[[ -e /dev/mapper/system-rootfs ]] && disk::dm_remove_all ${device}" - proc::exec_subshell_flose_fds lvcreate -n rootfs --size ${rootfs_lv_size_in_bytes}B system # Note that the real size will be a little bit larger than the specified size, since they will be aligned to the Physical Extentsize (PE) size, which by default is 4MB. + proc::hook_exit "[[ -e /dev/mapper/cryptpilot-rootfs ]] && disk::dm_remove_all ${device}" + proc::exec_subshell_flose_fds lvcreate -n rootfs --size ${rootfs_lv_size_in_bytes}B cryptpilot # Note that the real size will be a little bit larger than the specified size, since they will be aligned to the Physical Extentsize (PE) size, which by default is 4MB. # Copy rootfs content to the lvm volume log::info "Copying rootfs content to the logical volume" - dd status=progress "if=${rootfs_file_path}" of=/dev/mapper/system-rootfs bs=4M + dd status=progress "if=${rootfs_file_path}" of=/dev/mapper/cryptpilot-rootfs bs=4M } step::setup_rootfs_hash_lv() { @@ -1161,9 +1161,9 @@ step::setup_rootfs_hash_lv() { local rootfs_hash_size_in_byte rootfs_hash_size_in_byte=$(stat --printf="%s" "${rootfs_hash_file_path}") - proc::hook_exit "[[ -e /dev/mapper/system-rootfs_hash ]] && disk::dm_remove_all ${device}" - proc::exec_subshell_flose_fds lvcreate -n rootfs_hash --size "${rootfs_hash_size_in_byte}"B system - dd status=progress "if=${rootfs_hash_file_path}" of=/dev/mapper/system-rootfs_hash bs=4M + proc::hook_exit "[[ -e /dev/mapper/cryptpilot-rootfs_hash ]] && disk::dm_remove_all ${device}" + proc::exec_subshell_flose_fds lvcreate -n rootfs_hash --size "${rootfs_hash_size_in_byte}"B cryptpilot + dd status=progress "if=${rootfs_hash_file_path}" of=/dev/mapper/cryptpilot-rootfs_hash bs=4M rm -f "${rootfs_hash_file_path}" disk::dm_remove_all "${device}" diff --git a/cryptpilot-crypt/src/config/fs.rs b/cryptpilot-crypt/src/config/fs.rs index aec9e0b..500d01e 100644 --- a/cryptpilot-crypt/src/config/fs.rs +++ b/cryptpilot-crypt/src/config/fs.rs @@ -58,7 +58,7 @@ impl FileSystemConfigSource { volume_names.insert(volume_config.volume.to_owned()); Ok(volume_config) }) - .with_context(|| format!("Failed to loading volume config file: {}", path.display()))?; + .with_context(|| format!("Failed to loading volume config file: {:?}", path))?; volume_configs.push(volume_config); } diff --git a/cryptpilot-fde/README.md b/cryptpilot-fde/README.md index 241f069..9ad1e7f 100644 --- a/cryptpilot-fde/README.md +++ b/cryptpilot-fde/README.md @@ -2,7 +2,35 @@ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -`cryptpilot-fde` provides full disk encryption (FDE) capabilities for confidential computing environments. It encrypts the entire system disk, protects boot integrity, and enables secure boot with remote attestation. +`cryptpilot-fde` provides Full Disk Encryption (FDE) capabilities for confidential computing environments. It encrypts the entire system disk, protects boot integrity, and enables secure boot with remote attestation. + +The usage workflow is shown below: + +```mermaid +graph LR + %% Trusted Environment + subgraph TrustedEnv [Trusted Environment] + User((User)) -->|1. Prepare| Trustee[Trustee Service] + Trustee -->|2. Create| Image1[Confidential System Disk Image] + end + + %% Cloud Service Provider Environment + subgraph CloudEnv [Cloud Service Provider Environment] + Image2[Confidential System Disk Image] -->|4. Create| Instance[Confidential Computing Instance] + end + + %% Cross-region Actions + Image1 -->|3. Import| Image2 + + %% Dashed Interaction Logic + Instance -.->|Access Trustee at boot time
Remote attestation and obtain decryption key| Trustee + + %% Style Adjustments + style TrustedEnv fill:#f9f9f9,stroke:#333,stroke-width:1px + style CloudEnv fill:#eef3ff,stroke:#333,stroke-width:1px + style Instance fill:#fff,stroke:#0277bd,stroke-width:2px + +``` ## Features @@ -10,7 +38,7 @@ - **Integrity Protection**: Uses dm-verity to protect read-only rootfs - **Measurement & Attestation**: Measures boot artifacts for remote attestation - **Flexible Key Management**: Supports KBS, KMS, OIDC, TPM2, and custom exec providers -- **Overlay File System**: Provides writable overlay on read-only encrypted rootfs +- **Overlay Filesystem**: Provides writable overlay on read-only encrypted rootfs ## Installation @@ -34,7 +62,7 @@ cryptpilot-convert --in ./original.qcow2 --out ./encrypted.qcow2 \ 📖 [Detailed Quick Start Guide](docs/quick-start.md) -## Commands +## Configuration Configuration files are located in `/etc/cryptpilot/`: @@ -43,7 +71,7 @@ Configuration files are located in `/etc/cryptpilot/`: See [Configuration Guide](docs/configuration.md) for detailed options. -### Configuration Example Templates +### Configuration Templates - [fde.toml.template](../dist/etc/fde.toml.template) - [global.toml.template](../dist/etc/global.toml.template) @@ -55,7 +83,7 @@ See [Configuration Guide](docs/configuration.md) for detailed options. Display cryptographic reference values for attestation: ```sh -cryptpilot-fde show-reference-value --stage system --disk /path/to/disk.qcow2 +cryptpilot-fde show-reference-value --disk /path/to/disk.qcow2 ``` ### `cryptpilot-fde config check` @@ -76,7 +104,7 @@ cryptpilot-fde config dump --disk /dev/sda ### `cryptpilot-fde boot-service` -Internal command used by systemd during boot (do not call manually): +Internal commands used by systemd during boot (do not call manually): ```sh cryptpilot-fde boot-service --stage before-sysroot @@ -95,7 +123,7 @@ cryptpilot-convert --help ### cryptpilot-enhance -Harden VM disk images before encryption (remove cloud agents, secure SSH): +Harden VM disk images before encryption (removes cloud agents, protects SSH): ```sh cryptpilot-enhance --mode full --image ./disk.qcow2 @@ -112,11 +140,11 @@ See [cryptpilot-enhance documentation](docs/cryptpilot_enhance.md) for details. ## How It Works -`cryptpilot-fde` runs in initrd during boot, in two stages: +`cryptpilot-fde` runs in the initrd and operates in two stages: 1. **Before Sysroot Mount** (`before-sysroot` stage): - Decrypts rootfs (if encrypted) - - Sets up dm-verity for integrity protection + - Sets up dm-verity integrity protection - Measures boot artifacts and generates attestation evidence - Decrypts and mounts data partition @@ -129,12 +157,12 @@ See [Boot Process Documentation](docs/boot.md) for details. ## Key Providers -Supports multiple key providers for flexible key management: +Multiple key providers are supported for flexible key management: - **KBS**: Key Broker Service with remote attestation - **KMS**: Alibaba Cloud Key Management Service -- **OIDC**: KMS with OpenID Connect authentication -- **Exec**: Custom executable providing keys +- **OIDC**: KMS using OpenID Connect authentication +- **Exec**: Custom executable that provides the key See [Key Providers](../docs/key-providers.md) for detailed configuration. @@ -150,5 +178,5 @@ Apache-2.0 ## See Also - [cryptpilot-crypt](../cryptpilot-crypt/) - Runtime volume encryption -- [cryptpilot-verity](../cryptpilot-verity/) - dm-verity utilities +- [cryptpilot-verity](../cryptpilot-verity/) - dm-verity tools - [Main Project README](../README.md) diff --git a/cryptpilot-fde/README_zh.md b/cryptpilot-fde/README_zh.md index b160334..beb87a1 100644 --- a/cryptpilot-fde/README_zh.md +++ b/cryptpilot-fde/README_zh.md @@ -4,13 +4,41 @@ `cryptpilot-fde` 为机密计算环境提供全盘加密(FDE)能力。它加密整个系统磁盘、保护启动完整性,并支持远程证明的安全启动。 +其使用方式如下图所示 + +```mermaid +graph LR + %% 受信任环境 + subgraph TrustedEnv [受信任环境] + User((用户)) -->|1.准备| Trustee[Trustee服务] + Trustee -->|2.制作| Image1[机密系统盘镜像] + end + + %% 云服务提供商环境 + subgraph CloudEnv [云服务提供商环境] + Image2[机密系统盘镜像] -->|4.创建| Instance[机密计算实例] + end + + %% 跨区域动作 + Image1 -->|3.导入| Image2 + + %% 虚线交互逻辑 + Instance -.->|启动时访问Trustee
远程证明并获取解密密钥| Trustee + + %% 样式调整 + style TrustedEnv fill:#f9f9f9,stroke:#333,stroke-width:1px + style CloudEnv fill:#eef3ff,stroke:#333,stroke-width:1px + style Instance fill:#fff,stroke:#0277bd,stroke-width:2px + +``` + ## 功能特性 - **全盘加密**:同时加密 rootfs 和数据分区 - **完整性保护**:使用 dm-verity 保护只读 rootfs - **度量与证明**:度量启动工件用于远程证明 - **灵活的密钥管理**:支持 KBS、KMS、OIDC、TPM2 和自定义 exec 提供者 -- **覆盖文件系统**:在只读加密 rootfs 上提供可写覆盖层 +- **差异层机制**:在只读加密 rootfs 上提供可写差异层(支持 overlayfs 或 dm-snapshot) ## 安装 @@ -55,7 +83,7 @@ cryptpilot-convert --in ./original.qcow2 --out ./encrypted.qcow2 \ 显示用于证明的加密参考值: ```sh -cryptpilot-fde show-reference-value --stage system --disk /path/to/disk.qcow2 +cryptpilot-fde show-reference-value --disk /path/to/disk.qcow2 ``` ### `cryptpilot-fde config check` @@ -121,8 +149,8 @@ cryptpilot-enhance --mode full --image ./disk.qcow2 - 解密并挂载数据分区 2. **Sysroot 挂载后**(`after-sysroot` 阶段): - - 在只读 rootfs 上设置可写覆盖层 - - 覆盖层存储在加密数据分区或 tmpfs 上 + - 在只读 rootfs 上设置可写差异层 + - 差异层存储在加密 delta 分区或内存中 - 为 switch_root 准备系统 详情请参阅[启动过程文档](docs/boot_zh.md)。 diff --git a/cryptpilot-fde/docs/boot.md b/cryptpilot-fde/docs/boot.md index 0931365..8b439d1 100644 --- a/cryptpilot-fde/docs/boot.md +++ b/cryptpilot-fde/docs/boot.md @@ -1,20 +1,242 @@ -# Boot Process of CryptPilot +# CryptPilot System Disk Encryption Boot Architecture -Some features of CryptPilot rely on inserting specific code at certain points in the system's boot process. This document will detail these contents, making it convenient for you to troubleshoot errors when encountering boot issues. +This document describes the boot process and implementation principles of the CryptPilot system disk encryption solution, covering the complete chain from image preparation to system startup. -A conventional Linux distribution boot process includes two stages: `Initrd` and `System Manager`. After the kernel completes initialization, components within the initrd begin running and prepare the final root file system. Subsequently, initrd hands over control to the System Manager component (usually systemd). The definitions of these two phases in this document are consistent with those in the [systemd documentation](https://www.freedesktop.org/software/systemd/man/latest/bootup.html). +## 1. Overview -## Initrd Stage +The CryptPilot system disk encryption solution provides boot-time integrity protection and runtime data encryption for Linux systems. It uses dm-verity technology to protect root filesystem integrity, LUKS2 for data encryption, and a difference layer mechanism (supporting overlayfs file-level overlay or dm-snapshot block-level snapshot) to build a writable runtime environment. -The work during the initrd stage relates to CryptPilot's system disk encryption feature; it is responsible for executing the disk decryption process. +### 1.1 Technical Principles -We have created a dracut module named [[cryptpilot](file:///root/cryptpilot/target/debug/cryptpilot)](/dist/dracut/modules.d/91cryptpilot/module-setup.sh). Each time `/boot/initramfs*.img` is updated (for example, by updating the kernel or manually running `dracut -v -f`), the CryptPilot executable will also be copied into it. +The CryptPilot system disk encryption solution is implemented through Linux kernel dm-verity and LUKS2 technologies, providing both measurability and data encryption capabilities for confidential system disks. -> [!NOTE] -> If the system disk encryption feature is not used (i.e., the `/etc/cryptpilot/fde.toml` file is not created), this dracut module will not be enabled. +- **Measurability**: Based on the dm-verity mechanism, the system builds a complete hash tree structure for the rootfs volume during boot, ensuring filesystem integrity through layer-by-layer verification. This mechanism combines with memory encryption technology to store the root hash value of the root filesystem in secure memory. During system startup, the kernel verifies whether the hash value of each data block in the rootfs volume matches the pre-stored root hash value. Any unauthorized modifications are detected in real-time and prevent system boot, thereby ensuring trusted measurement and tamper-proof protection for the root filesystem. -As described in the [systemd documentation](https://www.freedesktop.org/software/systemd/man/latest/bootup.html#Bootup%20in%20the%20initrd), the system startup process within the initrd is very similar to that of the System Manager phase, being controlled by systemd services as well. CryptPilot creates two services, placed both before and after `initrd-root-device.target`: +- **Data Encryption**: Through the LUKS2 standard, using AES-256 algorithm for full disk encryption. The encryption process adopts a hierarchical key system: the user-managed Master Key is used to encrypt data, while the Master Key itself is protected by another Key Encryption Key (KEK). The KEK is delivered through the remote attestation mechanism after the instance is verified. All data is automatically encrypted before being written to disk, and decrypted in the secure memory of the confidential instance when read, ensuring that encrypted data remains encrypted during cloud disk storage and meeting the requirements for autonomous control throughout the key lifecycle. -- [[cryptpilot-fde-before-sysroot.service](file:///root/cryptpilot/dist/dracut/modules.d/91cryptpilot/cryptpilot-fde-before-sysroot.service)](/dist/dracut/modules.d/91cryptpilot/cryptpilot-fde-before-sysroot.service): Starts before `initrd-root-device.target`, which decrypts the rootfs volume (if necessary) and performs measurement on its content. Additionally, it checks the data volume; if the data volume does not exist, it initializes a new data volume using the remaining space on the disk, which usually happens during the first system boot. If the data volume already exists, it will decrypt it. +### 1.2 Volume Structure -- [[cryptpilot-fde-after-sysroot.service](file:///root/cryptpilot/dist/dracut/modules.d/91cryptpilot/cryptpilot-fde-after-sysroot.service)](/dist/dracut/modules.d/91cryptpilot/cryptpilot-fde-after-sysroot.service): Starts after `initrd-root-device.target`, responsible for mounting the data volume to the `/data` directory and handling some trivial mount tasks. +In CryptPilot, data is encrypted in units of volumes. The confidential system disk contains two key volumes: + +```txt + +-------------------------------------------+ + | Combined Root Filesystem | + +----------------------+--------------------+ + | | | + | Root Filesystem | Difference Data | + | (Read-only Part) | | + | | | + +----------------------+--------------------+ + | | | + | Read-only rootfs | Read-write data | + | (Measured, Optional | (Encrypted, | + | Encryption) | Integrity) | + | | | + +----------------------+--------------------+ ++--------------+ | +--------+ +--------+ | +| Trustee | <---- 1. Remote Attestation Carries ----> | | Kernel | | initrd | System Disk | +| Trust Mgmt | Root Filesystem Measurement | +--------+ +----^---+ | ++--------------+ +------------------+------------------------+ + | | + +----------------------------------------------------------------------+ 2. Obtain Volume Decryption Key +``` + +- **rootfs Volume**: The rootfs volume stores the read-only root filesystem. + + - Measurement: During boot, the content of this volume is measured, and a hash tree is built for the rootfs volume based on the kernel's dm-verity mechanism. Since the measurement value is stored in memory, data modification is prevented. To maintain compatibility with business applications in the system, a writable overlay layer is overlaid on the read-only root filesystem during the startup phase, allowing temporary write modifications to the root filesystem. These write modifications will not destroy the read-only layer nor affect the measurement of the read-only root filesystem. + + - Encryption: Encryption of this volume is optional, depending on your business requirements. If you need to encrypt the rootfs volume data, you can configure encryption options during the confidential system disk creation process. + +- **delta Volume**: The delta volume is an encrypted volume composed of the remaining available space on the system disk, containing a read-write Ext4 filesystem. + + - Encryption: During system startup, this volume is decrypted, and after entering the system, it is mounted to the `/data` location. Any data written to the delta volume is encrypted before being written to disk. Users can write their data files here, and the data will not be lost after instance restart. + +### 1.3 Core Components + +The core components of the system disk encryption solution include: +- **cryptpilot-convert**: Prepares encrypted disk layout during the image conversion phase +- **cryptpilot-fde**: Performs decryption and mounting operations during system boot + +The entire process is divided into the image preparation phase and the system startup phase. The image preparation phase completes disk layout conversion in an offline environment, while the system startup phase completes device activation and filesystem mounting on each boot. + +## 2. Image Preparation Phase + +The image preparation phase is executed by `cryptpilot-convert` in an offline environment, responsible for converting ordinary disk images into layouts that support system disk encryption. The core tasks of this phase are to prepare data structures for dm-verity integrity protection and plan metadata required for startup. + +The conversion process first shrinks the original rootfs partition to its minimum size to reduce the storage overhead of the hash tree. Then the dm-verity hash tree is calculated, outputting hash tree data and root_hash values. The hash tree data is stored in a separate logical volume, while root_hash is recorded in the `metadata.toml` file. + +The converted disk uses LVM to manage storage layout, creating three logical volumes in a volume group named "cryptpilot": the rootfs logical volume stores the shrunk rootfs data, the rootfs_hash logical volume stores the dm-verity hash tree, and the delta logical volume serves as difference layer storage space. When using the overlayfs mechanism, the delta volume is mounted as a filesystem to store the writable layer; when using the dm-snapshot mechanism, the delta volume is used directly as a block device for COW (Copy-On-Write) storage. + +The `metadata.toml` file contains metadata format version and dm-verity root_hash values. This file is embedded into the initrd image during the conversion process and can be accessed in the initrd environment during startup. + +## 3. Boot Modes and Boot Configuration + +The system disk encryption solution supports two boot modes, selected through the `--uki` parameter of `cryptpilot-convert`. + +### 3.1 GRUB Mode (Default) + +GRUB mode uses GRUB2 as the bootloader, suitable for scenarios requiring multi-kernel version management and boot menus. + +**Partition Layout**: + +| Partition | Mount Point | Purpose | +|-----------|-------------|---------| +| EFI System Partition | `/boot/efi` | Stores shim, GRUB bootloader | +| Boot Partition | `/boot` | Stores kernel, initrd, grub.cfg | +| LVM Physical Volume | - | Contains rootfs, rootfs_hash, delta logical volumes | + +**Boot Flow**: + +```mermaid +flowchart LR + A[EFI Firmware] --> B[shim] + B --> C[GRUB] + C --> D{Boot Menu} + D --> E[Load Kernel+initrd] + E --> F[Kernel Startup] + F --> G[Initrd Stage] + G --> H[System Startup] +``` + +GRUB mode supports multi-kernel version management, allowing users to select different kernel versions through the menu during boot. `cryptpilot-fde` parses saved_entry in grubenv when calculating reference values, reading the corresponding kernel version for measurement. + +### 3.2 UKI Mode (`--uki`) + +UKI mode uses Unified Kernel Image, packaging the kernel, initrd, and kernel command line into a single EFI executable file. + +**Partition Layout**: + +| Partition | Mount Point | Purpose | +|-----------|-------------|---------| +| EFI System Partition | `/boot/efi` | Stores UKI image (BOOTX64.EFI) | +| LVM Physical Volume | - | Contains rootfs, rootfs_hash, delta logical volumes | + +UKI mode does not require an independent boot partition, resulting in a more concise partition layout. + +**Boot Flow**: + +```mermaid +flowchart LR + A[EFI Firmware] --> B[UKI] + B --> C[Self-extract and Start Kernel] + C --> D[Kernel Startup] + D --> E[Initrd Stage] + E --> F[System Startup] +``` + +UKI generation uses dracut's `--uefi` parameter. The default kernel command line is `console=tty0 console=ttyS0,115200n8`, and custom parameters can be appended via `--uki-append-cmdline`. `cryptpilot-fde` parses segments in the UKI image directly when calculating reference values. + +### 3.3 Mode Comparison + +| Feature | GRUB Mode | UKI Mode | +|---------|-----------|----------| +| Partition Count | 3 (EFI, boot, LVM) | 2 (EFI, LVM) | +| Boot Files | Multiple (shim, GRUB, kernel, initrd) | Single UKI file | +| Multi-kernel Support | Yes | No | +| Boot Menu | Yes | No | +| Command Line Customization | Through grub.cfg | Through `--uki-append-cmdline` | + +Both modes have identical processing flows after the Initrd stage, with differences mainly in partition layout and boot file generation during the image conversion phase. + +## 4. Initrd Stage + +The Initrd stage is the core execution phase of the system disk encryption solution, with device activation and filesystem mounting orchestrated by systemd services. This stage operates through two services working together: `cryptpilot-fde-before-sysroot` prepares block devices, and `cryptpilot-fde-after-sysroot` builds the writable runtime environment. + +### 4.1 Pre-boot Preparation (cryptpilot-fde-before-sysroot) + +This service executes before `initrd-root-device.target` and is the key stage for device preparation. The service first activates the LVM volume group, making all logical volumes visible. Then it reads root_hash from `metadata.toml`, which serves as the integrity verification basis for dm-verity device activation. + +The construction of the rootfs device chain varies depending on encryption configuration. If rootfs encryption is configured, the service first obtains the passphrase through a key provider and opens the LUKS2 encrypted volume, then establishes dm-verity on the decrypted device. If encryption is not configured, dm-verity is established directly on the rootfs logical volume. + +#### 4.1.1 Delta Volume Initialization and Expansion + +The service first checks whether the disk where the LVM physical volume resides has unallocated space, and if so, extends the partition table and expands the LVM physical volume. This mechanism enables the system to automatically adapt to disk expansion scenarios in cloud environments. + +Delta volume initialization is also completed at this stage. The service checks whether the delta logical volume exists, creating it if it does not and occupying all remaining space in the volume group. If the delta volume already exists, it is expanded to the remaining space in the volume group. + +**Delta Volume Decryption Flow**: + +```text +delta_lv → dm-crypt → dm-integrity(optional) → decrypted delta volume block device +``` + +After the delta logical volume is created or expanded, the service obtains the passphrase for the delta volume, determines whether reinitialization is needed, formats the LUKS2 volume, and obtains the decrypted delta volume block device. The decrypted delta volume block device will be used to carry the difference layer data. The system supports two difference layer implementation mechanisms, selected through the `delta_backend` configuration, with dm-snapshot as the default. + +#### 4.1.2 Overlayfs Delta Volume Preparation (When Using Overlayfs Mechanism) + +When configured with `delta_backend = "overlayfs"`, the service creates an ext4 filesystem on the decrypted delta volume block device for storing overlay upperdir and workdir. + +**Device Chain Structure**: + +```text +rootfs_lv → [dm-crypt] → dm-verity ──┬─→ overlayfs ──→ /sysroot + │ + upper layer + (decrypted delta volume or tmpfs) +``` + +#### 4.1.3 dm-snapshot Device Chain Preparation (When Using dm-snapshot Mechanism) + +When configured with `delta_backend = "dm-snapshot"` (default), the service uses the decrypted delta volume block device directly as COW storage. It then continues to build the dm-snapshot device chain, enabling dracut to mount a writable root filesystem directly. + +**Device Chain Structure**: + +```text +rootfs_lv → [dm-crypt] → dm-verity ──┬─→ dm-linear ──→ dm-snapshot ──→ /sysroot + │ ↑ + dm-zero │ + │ COW device + (size equals COW device) (decrypted delta volume or zram) +``` + +The build process is divided into the following steps: + +1. **Prepare COW Device**: Prepare copy-on-write storage according to the `delta_location` configuration + - `delta_location = "ram"`: Create zram memory block device with size equal to system memory + - `delta_location = "disk"` or `"disk-persist"`: Use delta logical volume as COW device + +2. **Create dm-zero Device**: Create a dm-zero device of the same size as the COW device to extend the origin device size + +3. **Create dm-linear Device**: Linearly concatenate the dm-verity device and dm-zero device to obtain a virtual device larger than the original rootfs + +4. **Create dm-snapshot Device**: Establish a snapshot on the dm-linear device, using the COW device to store change data + +**Persistence Strategy**: + +- `disk` mode: Reinitialize COW device on each boot, discarding all changes +- `disk-persist` mode: Retain COW device content, restoring changes after reboot + +The dm-snapshot mechanism requires no special handling for container runtime directories; all write operations are completed directly at the block layer. + +### 4.2 dracut Mounts Sysroot + +After `cryptpilot-fde-before-sysroot` completes, dracut takes over execution. dracut identifies the dm-verity device as the root device and mounts it to `/sysroot`. Due to the nature of dm-verity, this mount is read-only. At this point, `/sysroot` presents the original rootfs content protected by integrity verification. + +### 4.3 Difference Layer Establishment (cryptpilot-fde-after-sysroot) + +This service executes after `sysroot.mount`, resolving the conflict between dm-verity read-only restrictions and system runtime write requirements. + +#### 4.3.1 Overlayfs Delta Volume Enablement (When Using Overlayfs Mechanism) + +When configured with `delta_backend = "overlayfs"`, a file-level overlay mechanism is used. The service uses the directory mounted by the original dm-verity device as the lowerdir for overlayfs. + +Writable layer preparation is performed according to the `delta_location` configuration. When configured as `ram`, tmpfs in memory is used as upperdir, with data lost after reboot. When configured as `disk` or `disk-persist`, the overlay directory in the delta volume is used as upperdir, with `disk` mode clearing on each boot while `disk-persist` mode retains data. + +Overlayfs mounting combines lowerdir, upperdir, and workdir, mounting the unified view to `/sysroot` to override the original read-only mount. After mounting, read operations are passed through to the dm-verity protected lowerdir, while write operations are redirected to the writable upperdir. + +For container runtime scenarios, the following directories are bind mounted to independent subdirectories within the writable layer, with original content copied from lowerdir on first boot: +- `/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/` +- `/var/lib/containers/` +- `/var/lib/docker/` + +Failure to bind mount these directories will not prevent system startup, only error logs are recorded. + +#### 4.3.2 dm-snapshot Delta Volume Enablement (When Using dm-snapshot Mechanism) + +When configured with `delta_backend = "dm-snapshot"` (default), the dm-snapshot device chain has already been prepared during the `cryptpilot-fde-before-sysroot` stage. dracut mounts the dm-snapshot device directly to `/sysroot`, and this mount is already writable. + +No additional operations are required at this stage; all write operations are handled at the block device layer through the dm-snapshot COW mechanism. + +## 5. System Switch Stage + +After `cryptpilot-fde-after-sysroot` completes, the initrd stage ends. dracut performs cleanup work and hands over control to systemd. systemd switches `/sysroot` as the real root filesystem, and the system enters the normal System Manager stage. At this point, the running root filesystem may be either the overlayfs unified view (overlayfs mechanism) or the dm-snapshot device (dm-snapshot mechanism), protected by dm-verity integrity while supporting normal write operations. diff --git a/cryptpilot-fde/docs/boot_zh.md b/cryptpilot-fde/docs/boot_zh.md index fc0c407..f9dc582 100644 --- a/cryptpilot-fde/docs/boot_zh.md +++ b/cryptpilot-fde/docs/boot_zh.md @@ -1,22 +1,245 @@ -# CryptPilot的启动流程 +# CryptPilot系统盘加密启动架构 -CryptPilot的部分功能依赖于在系统的启动过程的某些位置插入特定的代码。本文将详细介绍这部分内容,方便您在遇到启动问题时排查错误。 +本文档详细阐述CryptPilot系统盘加密方案的启动流程与实现原理,涵盖从镜像准备到系统启动的完整链路。 -常规的Linux发行版启动包含`Initrd`和`System Manager`两个阶段。当kernel完成初始化后,initrd中的组件开始运行,并准备好最终的根文件系统。随后initrd将控制权交给system manager组件(通常是systemd)。本文中对于两个阶段的定义与[systemd文档](https://www.freedesktop.org/software/systemd/man/latest/bootup.html)一致。 +## 1. 概述 -## Initrd阶段 +CryptPilot系统盘加密方案旨在为Linux系统提供启动时完整性保护和运行时数据加密能力。该方案通过dm-verity技术保护根文件系统完整性,结合LUKS2实现数据加密,并通过差异层机制(支持overlayfs文件级覆盖或dm-snapshot块级快照)构建可写的运行时环境。 -在initrd阶段的工作与CryptPilot的系统盘加密特性有关,它会负责执行的磁盘的解密过程。 +### 1.1 技术原理 -我们创建了一个名为[`cryptpilot`](/dist/dracut/modules.d/91cryptpilot/module-setup.sh)的dracut module,每次更新`/boot/initramfs*.img`时(比如更新内核或者手动运行`dracut -v -f`),会将CryptPilot可执行文件也拷贝到其中。 +CryptPilot系统盘加密方案通过Linux内核的dm-verity和LUKS2技术实现,能够为机密实例的系统盘提供可度量和数据加密两种能力。 -> [!NOTE] -> 如果未使用系统盘加密特性(即没有创建`/etc/cryptpilot/fde.toml`文件),则不会启用该dracut module。 +- **可度量**:基于dm-verity机制,系统在启动时将对rootfs卷构建完整的哈希树结构,通过逐层校验确保文件系统的完整性。该机制将与内存加密技术相结合,将根文件系统的根哈希值存储在安全的内存中。在系统启动过程中,内核将验证rootfs卷每个数据块的哈希值是否与预存的根哈希值一致,任何未经授权的修改都会被实时检测并阻止系统启动,从而实现对根文件系统的可信度量及防篡改保障。 -正如[systemd的文档](https://www.freedesktop.org/software/systemd/man/latest/bootup.html#Bootup%20in%20the%20initrd)中所描述,系统在initrd中的启动过程和System Manager阶段非常类似,也是由systemd服务来控制的。CryptPilot创建了两个服务,分别位于`initrd-root-device.target`的前面和后面: +- **数据加密**:通过LUKS2标准,采用AES-256算法实现全盘加密。加密过程采用分层密钥体系:用户自主管理的主密钥(Master Key)用于加密数据,而主密钥本身则由另一个密钥加密密钥(KEK)进行保护。KEK通过远程证明机制在对实例进行验证后下发。所有数据在写入磁盘之前会自动进行加密,而在读取时则在机密实例的安全内存中进行解密,从而确保加密数据在云盘存储期间始终处于加密状态,并满足密钥全生命周期的自主可控需求。 -- [`cryptpilot-fde-before-sysroot.service`](/dist/dracut/modules.d/91cryptpilot/cryptpilot-fde-before-sysroot.service):在`initrd-root-device.target`之前启动,它将对rootfs卷进行解密(如果需要的话),并完成对其内容的度量。此外,它还将检查data卷,如果data卷不存在,它会用磁盘剩余的空间来初始化一个新的data卷,这通常发生在系统首次启动时。如果data卷已经存在,则它将解密data卷。 +### 1.2 卷结构 -- [`cryptpilot-fde-after-sysroot.service`](/dist/dracut/modules.d/91cryptpilot/cryptpilot-fde-after-sysroot.service):在`initrd-root-device.target`之后启动,它会负责将data卷挂载到`/data`目录中,并处理一些琐碎的挂载任务。 +在CryptPilot中,数据是以卷(Volume)为单元进行加密,机密系统盘中包含两个关键卷: + +```txt + +-------------------------------------------+ + | 组合后的根文件系统 | + +----------------------+--------------------+ + | | | + | 根文件系统 | 差异数据 | + | (只读部分) | | + | | | + +----------------------+--------------------+ + | | | + | 只读rootfs卷 | 可读写delta卷 | + | (被度量、可选加密保护) | (加密、完整性保护) | + | | | + +----------------------+--------------------+ ++--------------+ | +--------+ +--------+ | +| Trustee | <-- 1. 远程证明携带 ---> | | Kernel | | initrd | 系统盘 | +| 信任管理服务 | 根文件系统度量值 | +--------+ +----^---+ | ++--------------+ +------------------+------------------------+ + | | + +--------------------------------------------------+ 2. 获取卷解密密钥 +``` + +- **rootfs卷**:rootfs卷存放了只读的根文件系统。 + + - 度量:在启动时该卷的内容会被度量,并基于内核的dm-verity机制对rootfs卷建立哈希树。由于度量值被存储在内存中,可以防止数据被修改。为了保持系统中业务程序的兼容性,在启动阶段,一个可写差异层将被应用到只读的根文件系统上,从而允许您在根文件系统上做临时性的写入修改。这些写入修改将不会破坏只读层,也不会影响只读根文件系统的度量。 + + - 加密:对该卷的加密是一个可选的操作,这取决于您的业务需求。如果您需要加密rootfs卷的数据,可以在创建机密系统盘的过程中配置加密选项。 + +- **delta卷**:delta卷是系统盘上剩余可用空间组成的一个加密卷,包含一个可读写的Ext4文件系统。 + + - 加密:在系统启动过程中,该卷会被解密,并且在进入系统后,该卷会被挂载到/data位置上。任何delta卷上写入的数据,都会被加密后落盘。用户可以将其数据文件写入到此处,在实例重新启动后,数据不会丢失。 + + +### 1.3 核心组件 + +系统盘加密方案的核心组件包括: +- **cryptpilot-convert**:在镜像转换阶段准备加密磁盘布局 +- **cryptpilot-fde**:在系统启动时执行解密和挂载操作 + +整个流程分为镜像准备阶段和系统启动阶段。镜像准备阶段在离线环境中完成磁盘布局转换,系统启动阶段在每次开机时完成设备激活和文件系统挂载。 + +## 2. 镜像准备阶段 + +镜像准备阶段由`cryptpilot-convert`在离线环境中执行,负责将普通磁盘镜像转换为支持系统盘加密的布局。该阶段的核心任务是为dm-verity完整性保护准备数据结构,并规划启动时所需的元数据。 + +转换过程首先对原始rootfs分区进行收缩,压缩至最小尺寸以减少哈希树的存储开销。随后计算dm-verity哈希树,输出包括哈希树数据和root_hash值。哈希树数据被存储到独立的逻辑卷,而root_hash则记录到`metadata.toml`文件中。 + +转换后的磁盘采用LVM管理存储布局,在名为cryptpilot的卷组中创建三个逻辑卷:rootfs逻辑卷存储收缩后的rootfs数据,rootfs_hash逻辑卷存储dm-verity哈希树,delta逻辑卷作为差异层存储空间。当使用overlayfs机制时,delta卷挂载为文件系统存储可写层;当使用dm-snapshot机制时,delta卷作为块设备直接作为COW(Copy-On-Write)存储。 + +`metadata.toml`文件包含元数据格式版本和dm-verity的root_hash值,该文件在转换过程中被嵌入initrd镜像,启动时可在initrd环境中访问。 + +## 3. 启动模式与引导配置 + +系统盘加密方案支持两种启动模式,通过`cryptpilot-convert`的`--uki`参数选择。 + +### 3.1 GRUB模式(默认) + +GRUB模式使用GRUB2作为引导加载程序,适用于需要多内核版本管理和启动菜单的场景。 + +**分区布局**: + +| 分区 | 挂载点 | 用途 | +|------|--------|------| +| EFI系统分区 | `/boot/efi` | 存储shim、GRUB引导程序 | +| boot分区 | `/boot` | 存储内核、initrd、grub.cfg | +| LVM物理卷 | - | 包含rootfs、rootfs_hash、delta逻辑卷 | + +**引导流程**: + +```mermaid +flowchart LR + A[EFI固件] --> B[shim] + B --> C[GRUB] + C --> D{启动菜单} + D --> E[加载内核+initrd] + E --> F[内核启动] + F --> G[Initrd阶段] + G --> H[系统启动] +``` + +GRUB模式支持多内核版本管理,用户可在启动时通过菜单选择不同内核版本。`cryptpilot-fde`在计算参考值时解析grubenv中的saved_entry,读取对应内核版本进行度量。 + +### 3.2 UKI模式(`--uki`) + +UKI模式使用统一内核镜像(Unified Kernel Image),将内核、initrd、内核命令行打包为单个EFI可执行文件。 + +**分区布局**: + +| 分区 | 挂载点 | 用途 | +|------|--------|------| +| EFI系统分区 | `/boot/efi` | 存储UKI镜像(BOOTX64.EFI) | +| LVM物理卷 | - | 包含rootfs、rootfs_hash、delta逻辑卷 | + +UKI模式无需独立的boot分区,分区布局更简洁。 + +**引导流程**: + +```mermaid +flowchart LR + A[EFI固件] --> B[UKI] + B --> C[自解压启动内核] + C --> D[内核启动] + D --> E[Initrd阶段] + E --> F[系统启动] +``` + +UKI生成使用dracut的`--uefi`参数,默认内核命令行为`console=tty0 console=ttyS0,115200n8`,可通过`--uki-append-cmdline`追加自定义参数。`cryptpilot-fde`在计算参考值时直接解析UKI镜像中的各段进行度量。 + +### 3.3 模式对比 + +| 特性 | GRUB模式 | UKI模式 | +|------|---------|---------| +| 分区数量 | 3个(EFI、boot、LVM) | 2个(EFI、LVM) | +| 引导文件 | 多个(shim、GRUB、内核、initrd) | 单个UKI文件 | +| 多内核支持 | 是 | 否 | +| 启动菜单 | 有 | 无 | +| 命令行定制 | 通过grub.cfg | 通过`--uki-append-cmdline` | + +两种模式在Initrd阶段之后的处理流程完全相同,差异主要体现在镜像转换阶段的分区布局和引导文件生成方式。 + +## 4. Initrd阶段 + +Initrd阶段是系统盘加密方案的核心执行阶段,由systemd服务编排完成设备激活和文件系统挂载。该阶段通过两个服务协同工作:`cryptpilot-fde-before-sysroot`负责准备块设备,`cryptpilot-fde-after-sysroot`负责构建可写的运行时环境。 + +### 3.1 启动前准备(cryptpilot-fde-before-sysroot) + +该服务在`initrd-root-device.target`之前执行,是设备准备的关键阶段。服务首先激活LVM卷组,使所有逻辑卷可见。随后从`metadata.toml`读取root_hash,该值是dm-verity设备激活时的完整性校验依据。 + +rootfs设备链的构建根据加密配置有所不同。若配置了rootfs加密,服务首先通过密钥提供者获取passphrase并打开LUKS2加密卷,再在该解密设备上建立dm-verity。若未配置加密,则直接在rootfs逻辑卷上建立dm-verity。 + + +#### 3.1.1 delta卷的初始化和扩容 + +服务会先检查LVM物理卷所在磁盘是否有未分配空间,若有则扩展分区表,并扩容LVM物理卷,这一机制使系统能够自动适应云环境中磁盘扩容的场景。 + +delta卷的初始化也在此一阶段完成。服务会先检查delta逻辑卷是否存在,若不存在则创建并占用卷组全部剩余空间。若delta卷已存在,则将其扩容至卷组剩余空间。 + +**delta卷解密流程**: + +```text +delta_lv → dm-crypt → dm-integrity(可选) → 解密后的delta卷块设备 +``` + +在delta逻辑卷创建或扩容后,服务获取delta卷的passphrase,判断是否需要重新初始化,格式化LUKS2卷,得到解密后的delta卷块设备。解密后的delta卷块设备将用于承载差异层数据。系统支持两种差异层实现机制,通过`delta_backend`配置选择,默认为dm-snapshot。 + +#### 3.1.2 overlayfs delta卷准备(当使用overlayfs机制时) + +当配置`delta_backend = "overlayfs"`时,服务会在解密后的delta卷块设备上创建ext4文件系统,用于存储overlay upperdir和workdir。 + +**设备链结构**: + +```text +rootfs_lv → [dm-crypt] → dm-verity ──┬─→ overlayfs ──→ /sysroot + │ + upper layer + (解密后的delta卷 或 tmpfs) +``` + +#### 3.1.3 dm-snapshot设备链准备(当使用dm-snapshot机制时) + +当配置`delta_backend = "dm-snapshot"`(默认)时,服务会将解密后的delta卷块设备直接作为COW存储使用。随后继续构建dm-snapshot设备链,使dracut能够直接挂载可写的根文件系统。 + +**设备链结构**: + +```text +rootfs_lv → [dm-crypt] → dm-verity ──┬─→ dm-linear ──→ dm-snapshot ──→ /sysroot + │ ↑ + dm-zero │ + │ COW设备 + (大小等于COW设备) (解密后的delta卷 或 zram) +``` + +构建过程分为以下步骤: + +1. **准备COW设备**:根据`delta_location`配置准备写时复制存储 + - `delta_location = "ram"`:创建zram内存块设备,大小等于系统内存 + - `delta_location = "disk"`或`"disk-persist"`:使用delta逻辑卷作为COW设备 + +2. **创建dm-zero设备**:创建与COW设备等大的dm-zero设备,用于扩展origin设备大小 + +3. **创建dm-linear设备**:将dm-verity设备和dm-zero设备线性拼接,得到一个比原始rootfs更大的虚拟设备 + +4. **创建dm-snapshot设备**:在dm-linear设备上建立快照,使用COW设备存储变更数据 + +**持久化策略**: + +- `disk`模式:每次启动重新初始化COW设备,丢弃所有变更 +- `disk-persist`模式:保留COW设备内容,重启后恢复变更 + +dm-snapshot机制无需容器运行时目录的特殊处理,所有写入操作直接在块层完成。 + +### 3.2 dracut挂载sysroot + +`cryptpilot-fde-before-sysroot`完成后,dracut接管执行。dracut识别dm-verity设备为root设备,将其挂载到`/sysroot`。由于dm-verity的特性,此挂载为只读。此时`/sysroot`呈现的是受完整性保护的原始rootfs内容。 + +### 3.3 差异层建立(cryptpilot-fde-after-sysroot) + +该服务在`sysroot.mount`之后执行,解决dm-verity只读限制与系统运行时写入需求的矛盾。 + +#### 3.3.1 overlayfs delta卷启用(当使用overlayfs机制时) + +当配置`delta_backend = "overlayfs"`时,使用文件级覆盖机制。服务将原始dm-verity设备挂载的目录作为overlayfs的lowerdir。 + +可写层的准备根据配置`delta_location`进行。配置为`ram`时使用内存中的tmpfs作为upperdir,数据在重启后丢失。配置为`disk`或`disk-persist`时使用delta卷中的overlay目录作为upperdir,`disk`模式在每次启动时清空而`disk-persist`模式保留数据。 + +overlayfs挂载将lowerdir、upperdir和workdir组合,将联合视图挂载到`/sysroot`覆盖原有的只读挂载。挂载后读取操作透传到dm-verity保护的lowerdir,写入操作重定向到可写的upperdir。 + +对于容器运行时等场景,以下目录会被bind mount到可写层内的独立子目录,首次启动时从lowerdir复制原始内容: +- `/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/` +- `/var/lib/containers/` +- `/var/lib/docker/` + +这些目录的bind mount失败不会阻止系统启动,仅记录错误日志。 + +#### 3.3.2 dm-snapshot delta卷启用(当使用dm-snapshot机制时) + +当配置`delta_backend = "dm-snapshot"`(默认)时,dm-snapshot设备链已在`cryptpilot-fde-before-sysroot`阶段准备完成。dracut直接挂载dm-snapshot设备到`/sysroot`,该挂载即为可写状态。 + +此阶段无需额外操作,所有写入操作已在块设备层通过dm-snapshot的COW机制处理。 + +## 5. 系统切换阶段 + +`cryptpilot-fde-after-sysroot`完成后,initrd阶段结束。dracut执行清理工作,将控制权交给systemd。systemd将`/sysroot`作为真实根文件系统切换,系统进入正常的System Manager阶段。此时运行的根文件系统根据配置可能是overlayfs联合视图(overlayfs机制)或dm-snapshot设备(dm-snapshot机制),既受dm-verity完整性保护又支持正常写入操作。 diff --git a/cryptpilot-fde/docs/configuration.md b/cryptpilot-fde/docs/configuration.md index 5bcbb3d..e3dba47 100644 --- a/cryptpilot-fde/docs/configuration.md +++ b/cryptpilot-fde/docs/configuration.md @@ -7,7 +7,7 @@ This guide covers configuration options for Full Disk Encryption (FDE) with cryp The default configuration directory is `/etc/cryptpilot/`: - **`global.toml`**: Global configuration (optional), see [global.toml.template](../../dist/etc/global.toml.template) -- **`fde.toml`**: FDE configuration for rootfs and data volumes +- **`fde.toml`**: FDE configuration for rootfs and delta volumes ## FDE Configuration @@ -15,13 +15,13 @@ System disk encryption (Full Disk Encryption) encrypts the entire system disk, p An encrypted system disk contains two main volumes: - **Rootfs volume**: Read-only root filesystem -- **Data volume**: Writable data partition +- **Delta volume**: Writable delta partition ### Configuration File Structure Reference template: [fde.toml.template](../../dist/etc/fde.toml.template) -A basic FDE configuration must contain `[rootfs]` and `[data]` sections. +A basic FDE configuration must contain `[rootfs]` and `[delta]` sections. ### Rootfs Volume Configuration @@ -34,10 +34,10 @@ An overlayfs layer provides write capability on top of the read-only rootfs. ```toml [rootfs] # Storage location for the overlay layer: "disk", "disk-persist", or "ram" -# - "disk": Stored on data volume but cleared on boot (default, recommended for security) -# - "disk-persist": Stored on data volume (persistent, but depends on data volume type) +# - "disk": Stored on delta volume but cleared on boot (default, recommended for security) +# - "disk-persist": Stored on delta volume (persistent, but depends on delta volume type) # - "ram": Stored in memory (cleared on reboot) -rw_overlay = "disk" +delta_location = "disk" # Encryption configuration (optional) # If omitted, rootfs will not be encrypted (but still protected by dm-verity) @@ -48,9 +48,9 @@ resource_path = "/secrets/rootfs-key" **Available fields:** -- **`rw_overlay`** (optional, default: `"disk"`): Overlay storage location - - `"disk"`: Store on data volume but forcibly cleared on boot (**default**, recommended for security) - - `"disk-persist"`: Store on data volume (persistent across reboots, but depends on data volume configuration: if data volume is temporary, it will still be lost on reboot) +- **`delta_location`** (optional, default: `"disk"`): Overlay storage location + - `"disk"`: Store on delta volume but forcibly cleared on boot (**default**, recommended for security) + - `"disk-persist"`: Store on delta volume (persistent across reboots, but depends on delta volume configuration: if delta volume is temporary, it will still be lost on reboot) - `"ram"`: Store in tmpfs (cleared on reboot, no disk space used) - **`encrypt`** (optional): Key provider configuration for rootfs encryption @@ -72,19 +72,19 @@ cryptpilot-fde uses Remote Attestation to measure the root filesystem: When using `kbs` as the key provider, measurement information is automatically included when fetching decryption keys from KBS. The KBS owner can configure [Remote Attestation Policies](https://github.com/openanolis/trustee/blob/main/attestation-service/docs/policy.md) to validate the measurements, establishing a full trust chain for confidential VM boot. -### Data Volume Configuration +### Delta Volume Configuration -The data volume uses the remaining disk space and contains an encrypted, writable filesystem. During boot, this volume is decrypted and mounted at `/data`. +The delta volume uses the remaining disk space and contains an encrypted, writable filesystem. During boot, this volume is decrypted and mounted at `/data`. **Configuration options:** ```toml -[data] -# Enable data integrity protection +[delta] +# Enable delta integrity protection integrity = true # Encryption configuration (required) -[data.encrypt.kbs] +[delta.encrypt.kbs] url = "https://kbs.example.com" resource_path = "/secrets/data-key" ``` @@ -95,7 +95,7 @@ resource_path = "/secrets/data-key" - When enabled, data is verified on every read - Prevents data tampering (but not replay attacks) -- **`encrypt`** (required): Key provider configuration for data volume encryption +- **`encrypt`** (required): Key provider configuration for delta volume encryption - See [Key Providers](../../docs/key-providers.md) for provider details ## Configuration Validation diff --git a/cryptpilot-fde/docs/configuration_zh.md b/cryptpilot-fde/docs/configuration_zh.md index e397de1..d22e582 100644 --- a/cryptpilot-fde/docs/configuration_zh.md +++ b/cryptpilot-fde/docs/configuration_zh.md @@ -21,23 +21,23 @@ 参考模板:[fde.toml.template](../../dist/etc/fde.toml.template) -一个基础的 FDE 配置文件必须包含 `[rootfs]` 和 `[data]` 两个配置项。 +一个基础的 FDE 配置文件必须包含 `[rootfs]` 和 `[delta]` 两个配置项。 ### Rootfs 卷配置 rootfs 卷存放只读的根分区文件系统。对该文件系统的加密是可选的,但不管是否开启加密,在启动时该卷都会被度量,并基于 dm-verity 防止数据被修改。 -在启动阶段,一个基于 overlayfs 的覆盖层将被覆盖在只读的根文件系统上,允许在根分区上做临时性的写入修改。 +在启动阶段,一个差异层(支持 overlayfs 或 dm-snapshot)将被应用到只读的根文件系统上,允许在根分区上做临时性的写入修改。 **配置选项:** ```toml [rootfs] -# 覆盖层的存储位置:"disk"、"disk-persist" 或 "ram" +# 差异层的存储位置:"disk"、"disk-persist" 或 "ram" # - "disk": 存储到 data 卷上但每次启动时清空(默认,推荐用于安全性) # - "disk-persist": 存储到 data 卷上(重启后保留,具体持久性取决于 data 卷配置) # - "ram": 存储在内存中(重启后清除) -rw_overlay = "disk" +delta_location = "disk" # 加密配置(可选) # 如不指定,则根分区不加密(但仍受 dm-verity 保护) @@ -48,7 +48,7 @@ resource_path = "/secrets/rootfs-key" **字段说明:** -- **`rw_overlay`**(可选,默认:`"disk"`):覆盖层存储位置 +- **`delta_location`**(可选,默认:`"disk"`):差异层存储位置 - `"disk"`:存储到 data 卷,但每次启动时强制清空(**默认值**,推荐用于提高安全性) - `"disk-persist"`:存储到 data 卷(重启后保留,但持久性取决于 data 卷的配置:如果 data 卷本身是临时卷,重启后仍会丢失) - `"ram"`:存储在内存中(重启后清除,不占用磁盘空间) @@ -79,12 +79,12 @@ data 卷使用系统盘上剩余可用空间,包含一个加密的可读写文 **配置选项:** ```toml -[data] +[delta] # 开启数据完整性保护 integrity = true # 加密配置(必需) -[data.encrypt.kbs] +[delta.encrypt.kbs] url = "https://kbs.example.com" resource_path = "/secrets/data-key" ``` diff --git a/cryptpilot-fde/docs/quick-start.md b/cryptpilot-fde/docs/quick-start.md index 65be836..256f33a 100644 --- a/cryptpilot-fde/docs/quick-start.md +++ b/cryptpilot-fde/docs/quick-start.md @@ -20,16 +20,16 @@ For this demo, we'll use the `exec` key provider with a hardcoded passphrase: mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" [rootfs.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] -[data] +[delta] integrity = true -[data.encrypt.exec] +[delta.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] EOF @@ -47,9 +47,9 @@ The configuration directory structure: **Configuration Explanation:** - `[rootfs]`: Root filesystem configuration - - `rw_overlay = "disk"`: Store writable overlay on data partition (survives reboot) + - `delta_location = "disk"`: Store writable overlay on delta partition (survives reboot) - `encrypt.exec`: Use exec provider with passphrase "AAAaaawewe222" -- `[data]`: Data partition configuration +- `[delta]`: Delta partition configuration - `integrity = true`: Enable dm-integrity for data authenticity - `encrypt.exec`: Use exec provider with passphrase "AAAaaawewe222" @@ -88,7 +88,7 @@ cryptpilot-convert --in ./aliyun_3_x64_20G_nocloud_alibase_20251030.qcow2 \ 1. Reads the original disk image 2. Creates encrypted rootfs partition with dm-verity -3. Creates encrypted data partition with dm-integrity +3. Creates encrypted delta partition with dm-integrity 4. Installs cryptpilot-fde into initrd 5. Configures boot loader for encrypted boot 6. Writes the encrypted disk to output file @@ -131,7 +131,7 @@ wget https://alinux3.oss-cn-hangzhou.aliyuncs.com/seed.img For attestation purposes, calculate cryptographic reference values: ```sh -cryptpilot-fde show-reference-value --stage system --disk ./encrypted.qcow2 +cryptpilot-fde show-reference-value --disk ./encrypted.qcow2 ``` This outputs measurement values that can be uploaded to [Reference Value Provider Service (RVPS)](https://github.com/confidential-containers/trustee/tree/main/rvps). @@ -158,13 +158,13 @@ Create configuration without rootfs encryption: mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" # Note: No encrypt configuration in rootfs section -[data] +[delta] integrity = true -[data.encrypt.exec] +[delta.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] EOF @@ -173,13 +173,13 @@ EOF **Configuration Explanation:** - `[rootfs]`: Root filesystem configuration - - `rw_overlay = "disk"`: Store writable overlay on data partition + - `delta_location = "disk"`: Store writable overlay on delta partition - **No `encrypt` configuration**: rootfs is not encrypted, only dm-verity integrity protection -- `[data]`: Data partition configuration +- `[delta]`: Delta partition configuration - `integrity = true`: Enable dm-integrity - - `encrypt.exec`: Data partition is still encrypted + - `encrypt.exec`: Delta partition is still encrypted -### Encrypt Data Partition (rootfs Not Encrypted) +### Encrypt Delta Partition (rootfs Not Encrypted) Use the `--rootfs-no-encryption` parameter: @@ -193,7 +193,7 @@ cryptpilot-convert --in ./aliyun_3_x64_20G_nocloud_alibase_20251030.qcow2 \ **What happens:** 1. rootfs uses dm-verity for integrity protection (not encrypted) -2. Data partition is encrypted normally +2. Delta partition is encrypted normally 3. System still performs measurement and attestation during boot 4. rootfs is mounted read-only with writable overlay layer @@ -228,16 +228,16 @@ For production systems, you need to encrypt a real disk. mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" [rootfs.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] -[data] +[delta] integrity = true -[data.encrypt.exec] +[delta.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] EOF @@ -269,16 +269,16 @@ For production environments, use Key Broker Service with remote attestation. mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" [rootfs.encrypt.kbs] url = "https://kbs.example.com" resource_path = "/secrets/rootfs-key" -[data] +[delta] integrity = true -[data.encrypt.kbs] +[delta.encrypt.kbs] url = "https://kbs.example.com" resource_path = "/secrets/data-key" EOF @@ -316,17 +316,17 @@ For Alibaba Cloud users, use KMS for centralized key management. mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" [rootfs.encrypt.kms] kms_instance_id = "kst-****" client_key_id = "LTAI****" client_key_password_from_kms = "alias/ClientKey_****" -[data] +[delta] integrity = true -[data.encrypt.kms] +[delta.encrypt.kms] kms_instance_id = "kst-****" client_key_id = "LTAI****" client_key_password_from_kms = "alias/ClientKey_****" @@ -406,7 +406,7 @@ If `cryptpilot-convert` fails: 1. **Check disk format**: Only qcow2 images are supported for disk images 2. **Check disk size**: Ensure enough space for encryption overhead 3. **For real disks**: Ensure the disk is unmounted and not in use -4. **Device already exists error**: If you see errors like `/dev/system: already exists in filesystem`, it may be leftover from a previous failed convert. Try `dmsetup remove_all` to clean up +4. **Device already exists error**: If you see errors like `/dev/cryptpilot: already exists in filesystem`, it may be leftover from a previous failed convert. Try `dmsetup remove_all` to clean up 5. **Check logs**: The last convert's detailed log is saved at `/tmp/.cryptpilot-convert.log` ### Boot Failed @@ -426,5 +426,5 @@ If the encrypted system fails to boot: ## See Also -- [cryptpilot-crypt Quick Start](../../cryptpilot-crypt/docs/quick-start.md) - Encrypt data volumes +- [cryptpilot-crypt Quick Start](../../cryptpilot-crypt/docs/quick-start.md) - Encrypt delta volumes - [Development Guide](../../docs/development.md) - Build and test instructions diff --git a/cryptpilot-fde/docs/quick-start_zh.md b/cryptpilot-fde/docs/quick-start_zh.md index 9b64e7b..19fd5b1 100644 --- a/cryptpilot-fde/docs/quick-start_zh.md +++ b/cryptpilot-fde/docs/quick-start_zh.md @@ -20,16 +20,16 @@ mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" [rootfs.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] -[data] +[delta] integrity = true -[data.encrypt.exec] +[delta.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] EOF @@ -47,9 +47,9 @@ tree ./config_dir **配置说明:** - `[rootfs]`:根文件系统配置 - - `rw_overlay = "disk"`:将可写覆盖层存储在数据分区上(重启后保留) + - `delta_location = "disk"`:将可写差异层存储在 delta 分区上(重启后保留) - `encrypt.exec`:使用 exec 提供者,密码为 "AAAaaawewe222" -- `[data]`:数据分区配置 +- `[delta]`:delta 分区配置 - `integrity = true`:启用 dm-integrity 数据完整性保护 - `encrypt.exec`:使用 exec 提供者,密码为 "AAAaaawewe222" @@ -88,7 +88,7 @@ cryptpilot-convert --in ./aliyun_3_x64_20G_nocloud_alibase_20251030.qcow2 \ 1. 读取原始磁盘镜像 2. 创建带有 dm-verity 的加密 rootfs 分区 -3. 创建带有 dm-integrity 的加密数据分区 +3. 创建带有 dm-integrity 的加密 delta 分区 4. 将 cryptpilot-fde 安装到 initrd 5. 配置引导加载程序以支持加密启动 6. 将加密磁盘写入输出文件 @@ -131,7 +131,7 @@ wget https://alinux3.oss-cn-hangzhou.aliyuncs.com/seed.img 为了证明目的,计算加密参考值: ```sh -cryptpilot-fde show-reference-value --stage system --disk ./encrypted.qcow2 +cryptpilot-fde show-reference-value --disk ./encrypted.qcow2 ``` 这将输出可上传到[参考值提供服务(RVPS)](https://github.com/confidential-containers/trustee/tree/main/rvps)的度量值。 @@ -158,13 +158,13 @@ cryptpilot-fde show-reference-value --stage system --disk ./encrypted.qcow2 mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" # 注意:rootfs 段不包含 encrypt 配置 -[data] +[delta] integrity = true -[data.encrypt.exec] +[delta.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] EOF @@ -173,13 +173,13 @@ EOF **配置说明:** - `[rootfs]`:根文件系统配置 - - `rw_overlay = "disk"`:将可写覆盖层存储在数据分区上 + - `delta_location = "disk"`:将可写差异层存储在 delta 分区上 - **不包含** `encrypt` 配置:rootfs 不加密,仅使用 dm-verity 完整性保护 -- `[data]`:数据分区配置 +- `[delta]`:delta 分区配置 - `integrity = true`:启用 dm-integrity - - `encrypt.exec`:数据分区仍然加密 + - `encrypt.exec`:delta 分区仍然加密 -### 加密数据分区(rootfs 不加密) +### 加密 delta 分区(rootfs 不加密) 使用 `--rootfs-no-encryption` 参数: @@ -193,7 +193,7 @@ cryptpilot-convert --in ./aliyun_3_x64_20G_nocloud_alibase_20251030.qcow2 \ **发生的事情:** 1. rootfs 使用 dm-verity 进行完整性保护(不加密) -2. 数据分区正常加密 +2. delta 分区正常加密 3. 系统启动时仍会进行度量和证明 4. rootfs 以只读方式挂载,通过 overlay 提供可写层 @@ -228,16 +228,16 @@ cryptpilot-convert --in ./aliyun_3_x64_20G_nocloud_alibase_20251030.qcow2 \ mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" [rootfs.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] -[data] +[delta] integrity = true -[data.encrypt.exec] +[delta.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] EOF @@ -269,16 +269,16 @@ cryptpilot-convert --device /dev/nvme2n1 \ mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" [rootfs.encrypt.kbs] url = "https://kbs.example.com" resource_path = "/secrets/rootfs-key" -[data] +[delta] integrity = true -[data.encrypt.kbs] +[delta.encrypt.kbs] url = "https://kbs.example.com" resource_path = "/secrets/data-key" EOF @@ -316,17 +316,17 @@ cryptpilot-convert --device /dev/nvme2n1 \ mkdir -p ./config_dir cat << EOF > ./config_dir/fde.toml [rootfs] -rw_overlay = "disk" +delta_location = "disk" [rootfs.encrypt.kms] kms_instance_id = "kst-****" client_key_id = "LTAI****" client_key_password_from_kms = "alias/ClientKey_****" -[data] +[delta] integrity = true -[data.encrypt.kms] +[delta.encrypt.kms] kms_instance_id = "kst-****" client_key_id = "LTAI****" client_key_password_from_kms = "alias/ClientKey_****" @@ -406,7 +406,7 @@ cryptpilot-fde -c ./config_dir/ config check --keep-checking 1. **检查磁盘格式**:磁盘镜像仅支持 qcow2 格式 2. **检查磁盘大小**:确保有足够空间用于加密开销 3. **对于真实磁盘**:确保磁盘未挂载且不在使用中 -4. **设备已存在错误**:如果出现类似 `/dev/system: already exists in filesystem` 的错误,可能是上次 convert 失败遗留的,尝试 `dmsetup remove_all` 清除 +4. **设备已存在错误**:如果出现类似 `/dev/cryptpilot: already exists in filesystem` 的错误,可能是上次 convert 失败遗留的,尝试 `dmsetup remove_all` 清除 5. **查看日志**:最后一次 convert 的详细日志保存在 `/tmp/.cryptpilot-convert.log` ### 启动失败 diff --git a/cryptpilot-fde/src/bin/gen-template/main.rs b/cryptpilot-fde/src/bin/gen-template/main.rs index d0250ce..33a7a89 100644 --- a/cryptpilot-fde/src/bin/gen-template/main.rs +++ b/cryptpilot-fde/src/bin/gen-template/main.rs @@ -2,8 +2,11 @@ use anyhow::{Context, Result}; use clap::{command, Parser}; use cryptpilot::config::encrypt::{EncryptConfig, KeyProviderConfig}; use cryptpilot::provider::kbs::{CdhType, KbsConfig}; +use cryptpilot_fde::config::{ + BootServiceConfig, DeltaBackend, DeltaConfig, DeltaLocation, FdeConfig, GlobalConfig, + RootFsConfig, +}; use documented::DocumentedFields; -use serde::{Deserialize, Serialize}; use shadow_rs::shadow; use toml_edit::{Decor, DocumentMut, RawString, Table}; @@ -11,72 +14,6 @@ shadow!(build); use crate::build::CLAP_LONG_VERSION; -// FDE Configuration structures -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, DocumentedFields)] -#[serde(deny_unknown_fields)] -pub struct FdeConfig { - /// Configuration related to the read-only root filesystem. - pub rootfs: RootFsConfig, - - /// Configuration related to the data partition. - pub data: DataConfig, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, DocumentedFields)] -#[serde(deny_unknown_fields)] -pub struct RootFsConfig { - /// The type of read-write overlay layer over the underhood read-only rootfs. Can be "disk", "disk-persist", or "ram". Default value is "disk". - #[serde(skip_serializing_if = "Option::is_none")] - pub rw_overlay: Option, - - /// Encryption configuration for root filesystem. If not set, the rootfs partition WOULD NOT be encrypted. - #[serde(skip_serializing_if = "Option::is_none")] - pub encrypt: Option, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, DocumentedFields)] -#[serde(deny_unknown_fields)] -pub struct DataConfig { - /// Whether or not to enable integrity check. - #[serde(default = "Default::default")] - pub integrity: bool, - - /// Encryption configuration for data partition. If not set, the data partition WOULD NOT be encrypted. - pub encrypt: EncryptConfig, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(deny_unknown_fields)] -pub enum RwOverlayType { - /// The overlay will be placed on disk but will be cleared on every boot. - /// This is the default and recommended option for security. - #[serde(rename = "disk")] - Disk, - /// The overlay will be placed on disk, and be persistent across reboots. - /// Note: persistence depends on the data volume configuration. - #[serde(rename = "disk-persist")] - DiskPersist, - /// The overlay will be placed on tmpfs (in RAM), and be cleared on reboot. - #[serde(rename = "ram")] - Ram, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, DocumentedFields)] -#[serde(deny_unknown_fields)] -pub struct GlobalConfig { - /// Configuration related to cryptpilot boot service. - #[serde(skip_serializing_if = "Option::is_none")] - pub boot: Option, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, DocumentedFields)] -#[serde(deny_unknown_fields)] -pub struct BootServiceConfig { - /// Enable this option if you want to see more log when running cryptpilot boot service in initrd stage and in system stage. - #[serde(default = "Default::default")] - pub verbose: bool, -} - #[derive(Parser, Debug)] #[command(version, about, long_about = None)] #[clap(long_version = CLAP_LONG_VERSION)] @@ -179,8 +116,8 @@ impl AsAnnotatedToml for FdeConfig { annotate_toml_table::(item) .context("Failed to annotate `RootFsConfig`")?; }; - if let Some(item) = toml.get_mut("data").and_then(|item| item.as_table_mut()) { - annotate_toml_table::(item).context("Failed to annotate `DataConfig`")?; + if let Some(item) = toml.get_mut("delta").and_then(|item| item.as_table_mut()) { + annotate_toml_table::(item).context("Failed to annotate `DeltaConfig`")?; }; Ok(toml) } @@ -189,7 +126,8 @@ impl AsAnnotatedToml for FdeConfig { pub fn get_fde_config() -> FdeConfig { FdeConfig { rootfs: RootFsConfig { - rw_overlay: Some(RwOverlayType::Disk), + delta_location: Some(DeltaLocation::Disk), + delta_backend: Some(DeltaBackend::DmSnapshot), encrypt: Some(EncryptConfig { key_provider: KeyProviderConfig::Kbs(KbsConfig { cdh_type: CdhType::OneShot { @@ -207,7 +145,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX }), }), }, - data: DataConfig { + delta: DeltaConfig { integrity: true, encrypt: EncryptConfig { key_provider: KeyProviderConfig::Kbs(KbsConfig { diff --git a/cryptpilot-fde/src/cmd/boot_service/stage/after_sysroot.rs b/cryptpilot-fde/src/cmd/boot_service/stage/after_sysroot.rs index 87dd208..a907c6f 100644 --- a/cryptpilot-fde/src/cmd/boot_service/stage/after_sysroot.rs +++ b/cryptpilot-fde/src/cmd/boot_service/stage/after_sysroot.rs @@ -6,10 +6,10 @@ use tokio::{ process::Command, }; -use crate::cmd::boot_service::stage::{DATA_LAYER_DEVICE, ROOTFS_LAYER_DEVICE}; +use crate::cmd::boot_service::stage::{DELTA_DEVICE, ROOTFS_DEVICE}; use cryptpilot::fs::cmd::CheckCommandOutput; -use crate::config::RwOverlayType; +use crate::config::{DeltaBackend, DeltaLocation}; pub async fn setup_mounts_required_by_fde() -> Result<()> { tracing::info!("Setting up mounts required by FDE"); @@ -23,26 +23,44 @@ pub async fn setup_mounts_required_by_fde() -> Result<()> { return Ok(()); }; + let backend = fde_config.rootfs.delta_backend.unwrap_or_default(); + tracing::info!("Using overlay backend: {:?}", backend); + check_sysroot().await?; - // 1. Mount the data volume to filesystem - tracing::info!("[ 1/4 ] Mounting data volume"); + match backend { + DeltaBackend::Overlayfs => { + setup_overlayfs_mounts(fde_config).await?; + } + DeltaBackend::DmSnapshot => { + tracing::info!( + "dm-snapshot device already mounted at /sysroot, no additional setup needed" + ); + } + } + + Ok(()) +} + +async fn setup_overlayfs_mounts(fde_config: crate::config::FdeConfig) -> Result<()> { + // 1. Mount the delta volume to filesystem + tracing::info!("[ 1/3 ] Mounting delta volume"); async { - tokio::fs::create_dir_all("/data_volume").await?; + tokio::fs::create_dir_all("/delta_volume").await?; Command::new("mount") - .arg(DATA_LAYER_DEVICE) - .arg("/data_volume") + .arg(DELTA_DEVICE) + .arg("/delta_volume") .run() .await?; Ok::<_, anyhow::Error>(()) } .await - .context("Failed to mount data volume on /data_volume")?; + .context("Failed to mount delta volume on /delta_volume")?; // 2. Setup the rootfs-overlay. If on ram, create it first. If on disk, just use it to setup overlayfs. - tracing::info!("[ 2/4 ] Setting up rootfs overlay"); + tracing::info!("[ 2/3 ] Setting up rootfs overlay"); // Setup a backup of /sysroot at /sysroot_bak before mount overlay fs on it let sysroot_bak = Path::new("/sysroot_bak"); @@ -62,7 +80,10 @@ pub async fn setup_mounts_required_by_fde() -> Result<()> { .await .with_context(|| format!("Failed to setup backup of /sysroot at {sysroot_bak:?}"))?; - let overlay_type = fde_config.rootfs.rw_overlay.unwrap_or(RwOverlayType::Disk); + let delta_location = fde_config + .rootfs + .delta_location + .unwrap_or(DeltaLocation::Disk); // Load overlay module if not loaded Command::new("modprobe") @@ -71,8 +92,8 @@ pub async fn setup_mounts_required_by_fde() -> Result<()> { .await .context("Failed to load kernel module 'overlay'")?; - let overlay_dir = match overlay_type { - RwOverlayType::Ram => { + let overlay_dir = match delta_location { + DeltaLocation::Ram => { tracing::info!("Using tmpfs as rootfs overlay"); async { tokio::fs::create_dir_all("/ram_overlay").await?; @@ -88,7 +109,7 @@ pub async fn setup_mounts_required_by_fde() -> Result<()> { Command::new("mount") .args(["-t", "overlay"]) - .arg(ROOTFS_LAYER_DEVICE) + .arg(ROOTFS_DEVICE) .args([ "-o", "lowerdir=/sysroot,upperdir=/ram_overlay/upper,workdir=/ram_overlay/work", @@ -105,38 +126,17 @@ pub async fn setup_mounts_required_by_fde() -> Result<()> { Path::new("/ram_overlay") } - RwOverlayType::Disk | RwOverlayType::DiskPersist => { - let should_clear = matches!(overlay_type, RwOverlayType::Disk); - if should_clear { - tracing::info!( - "Using data-volume:/overlay as rootfs overlay (ephemeral mode, will be cleared on boot)" - ); - } else { - tracing::info!("Using data-volume:/overlay as rootfs overlay (persistent mode)"); - } + DeltaLocation::Disk | DeltaLocation::DiskPersist => { async { - let overlay_path = Path::new("/data_volume/overlay"); - - // If disk mode (default), clear the overlay directory on boot - if should_clear && overlay_path.exists() { - tracing::info!("Clearing overlay directory for ephemeral mode"); - if let Err(e) = tokio::fs::remove_dir_all(overlay_path).await { - tracing::warn!( - "Failed to clear overlay directory: {:#}. Continuing anyway.", - e - ); - } - } - - tokio::fs::create_dir_all("/data_volume/overlay/upper").await?; - tokio::fs::create_dir_all("/data_volume/overlay/work").await?; + tokio::fs::create_dir_all("/delta_volume/overlay/upper").await?; + tokio::fs::create_dir_all("/delta_volume/overlay/work").await?; Command::new("mount") .args(["-t", "overlay"]) - .arg(ROOTFS_LAYER_DEVICE) + .arg(ROOTFS_DEVICE) .args([ "-o", - "lowerdir=/sysroot,upperdir=/data_volume/overlay/upper,workdir=/data_volume/overlay/work", + "lowerdir=/sysroot,upperdir=/delta_volume/overlay/upper,workdir=/delta_volume/overlay/work", "/sysroot", ]) .run() @@ -148,12 +148,12 @@ pub async fn setup_mounts_required_by_fde() -> Result<()> { .await .context("Failed to setup overlayfs on /sysroot")?; - Path::new("/data_volume/overlay") + Path::new("/delta_volume/overlay") } }; // Setting up mount bind for some special dirs - tracing::info!("[ 3/4 ] Setting up mount bind"); + tracing::info!("[ 3/3 ] Setting up mount bind"); let dirs = [ "/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/", "/var/lib/containers/", @@ -225,36 +225,22 @@ pub async fn setup_mounts_required_by_fde() -> Result<()> { } } - // 4. mount --bind the /data folder - tracing::info!("[ 4/4 ] Setting up user-data dir: /data"); - async { - tokio::fs::create_dir_all("/data_volume/data").await?; - tokio::fs::create_dir_all("/sysroot/data").await?; - - Command::new("mount") - .args(["--bind", "/data_volume/data", "/sysroot/data"]) - .run() - .await?; - - Ok::<_, anyhow::Error>(()) - } - .await - .context("Failed to setup mount bind on /sysroot/data")?; - Ok(()) } async fn check_sysroot() -> Result<()> { + tracing::info!("Checking mount source of /sysroot"); + // The mount of /sysroot is not done by cryptpilot. It is intentional, because we do not want to take over the job of /etc/fstab. So we have to check if sysroot is mounted from ROOTFS_LAYER_DEVICE. let mtab_content = fs::read_to_string("/etc/mtab").await?; for line in mtab_content.lines() { let mut fields = line.split(' '); match (fields.next(), fields.next()) { (Some(device), Some("/sysroot")) => { - if device == ROOTFS_LAYER_DEVICE { + if device == ROOTFS_DEVICE { return Ok(()); } else { - bail!("Rootfs mounted at /sysroot is not expected and could be a security risk. Expected: {ROOTFS_LAYER_DEVICE}, got: {device}"); + bail!("Rootfs mounted at /sysroot is not expected and could be a security risk. Expected: {ROOTFS_DEVICE}, got: {device}"); } } _ => continue, diff --git a/cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs b/cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs index edfbf7a..02ab7d1 100644 --- a/cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs +++ b/cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs @@ -1,16 +1,21 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::{bail, Context, Result}; use tokio::{fs::File, io::AsyncWriteExt, process::Command}; -use crate::cmd::boot_service::{ - metadata::{load_metadata_from_file, Metadata}, - stage::{ - DATA_LAYER_DEVICE, DATA_LAYER_NAME, DATA_LOGICAL_VOLUME, ROOTFS_DECRYPTED_LAYER_DEVICE, - ROOTFS_DECRYPTED_LAYER_NAME, ROOTFS_HASH_LOGICAL_VOLUME, ROOTFS_LAYER_NAME, - ROOTFS_LOGICAL_VOLUME, +use crate::{ + cmd::boot_service::{ + metadata::{load_metadata_from_file, Metadata}, + stage::{ + DELTA_DEVICE, DELTA_LOGICAL_VOLUME, DELTA_NAME, ROOTFS_DECRYPTED_LAYER_DEVICE, + ROOTFS_DECRYPTED_NAME, ROOTFS_DEVICE, ROOTFS_EXTENDED_DEVICE, ROOTFS_EXTENDED_NAME, + ROOTFS_HASH_LOGICAL_VOLUME, ROOTFS_LOGICAL_VOLUME, ROOTFS_NAME, ROOTFS_VERITY_DEVICE, + ROOTFS_VERITY_NAME, VOLUME_GROUP_NAME, + }, }, + config::{DeltaBackend, DeltaLocation}, }; +use block_devs::BlckExt; use cryptpilot::{ fs::cmd::CheckCommandOutput, provider::{IntoProvider as _, KeyProvider as _, VolumeType}, @@ -32,13 +37,16 @@ pub async fn setup_volumes_required_by_fde() -> Result<()> { tracing::info!("Setting up volumes required by FDE"); - // 1. Checking and activating LVM volume group 'system' - tracing::info!("[ 1/4 ] Checking and activating LVM volume group 'system'"); + // 1. Checking and activating LVM volume group + tracing::info!( + volume_group_name = VOLUME_GROUP_NAME, + "[ 1/4 ] Checking and activating LVM volume group" + ); Command::new("vgchange") - .args(["-a", "y", "system"]) + .args(["-a", "y", VOLUME_GROUP_NAME]) .run() .await - .context("Failed to activate LVM volume group 'system'")?; + .with_context(|| format!("Failed to activate LVM volume group '{VOLUME_GROUP_NAME}'"))?; // 2. Load the root-hash and add it to the AAEL tracing::info!("[ 2/4 ] Loading root-hash"); @@ -73,7 +81,7 @@ pub async fn setup_volumes_required_by_fde() -> Result<()> { tracing::info!("Setting up dm-crypt for rootfs volume"); cryptpilot::fs::luks2::open_with_check_passphrase( - ROOTFS_DECRYPTED_LAYER_NAME, + ROOTFS_DECRYPTED_NAME, Path::new(ROOTFS_LOGICAL_VOLUME), &passphrase, IntegrityType::None, @@ -84,107 +92,146 @@ pub async fn setup_volumes_required_by_fde() -> Result<()> { } tracing::info!("Setting up dm-verity for rootfs volume"); + + let backend = fde_config.rootfs.delta_backend.unwrap_or_default(); + + let (dm_verity_output_name, dm_verity_output_device) = match backend { + DeltaBackend::Overlayfs => (ROOTFS_NAME, Path::new(ROOTFS_DEVICE)), + DeltaBackend::DmSnapshot => (ROOTFS_VERITY_NAME, Path::new(ROOTFS_VERITY_DEVICE)), + }; + setup_rootfs_dm_verity( + dm_verity_output_name, &metadata.root_hash, - if fde_config.rootfs.encrypt.is_some() { + Path::new(if fde_config.rootfs.encrypt.is_some() { ROOTFS_DECRYPTED_LAYER_DEVICE } else { ROOTFS_LOGICAL_VOLUME - }, + }), ) .await?; - // Now we have the rootfs ro part - // 4. Open the data logical volume with dm-crypt and dm-integrity on it - tracing::info!("[ 4/4 ] Setting up data volume"); - - tracing::info!("Expanding system PV partition"); - if let Err(error) = expand_system_pv_partition().await { - tracing::warn!(?error, "Failed to expend the system PV partition"); - } - + // 4. Setup delta volume and overlay backend { - // Check if the data logical volume exists - if !Path::new(DATA_LOGICAL_VOLUME).exists() { - tracing::info!( - "Data logical volume does not exist, assume it is first time boot and create it." - ); - - // Due to there is no udev in initrd, the lvcreate will complain that /dev/system/data not exist. A workaround is to set '--zero n' and zeroing the first 4k of logical volume manually. - // See https://serverfault.com/a/1059400 - async { - Command::new("lvcreate") - .args(["-n", "data", "--zero", "n", "-l", "100%FREE", "system"]) - .env("LVM_SYSTEM_DIR", CRYPTPILOT_LVM_SYSTEM_DIR) - .run() - .await?; - File::options() - .write(true) - .open("/dev/mapper/system-data") - .await? - .write_all(&[0u8; 4096]) + let delta_location = fde_config + .rootfs + .delta_location + .unwrap_or(DeltaLocation::Disk); + + tracing::info!( + ?backend, + ?delta_location, + "[ 4/4 ] Setting up delta volume if required" + ); + + if matches!( + delta_location, + DeltaLocation::Disk | DeltaLocation::DiskPersist + ) { + tracing::info!("Expanding system PV partition"); + if let Err(error) = expand_system_pv_partition().await { + tracing::warn!(?error, "Failed to expend the system PV partition"); + } + + // Ensure delta logical volume exists + ensure_delta_volume_exist_and_expanded().await?; + + let (recreate, integrity) = + setup_delta_volume_luks2(&fde_config.delta, delta_location).await?; + + // Setup delta volume based on backend type + match backend { + DeltaBackend::Overlayfs => { + let delta_device = Path::new(DELTA_DEVICE); + if recreate { + tracing::info!("Creating ext4 fs on delta volume"); + cryptpilot::fs::mkfs::force_mkfs( + delta_device, + &MakeFsType::Ext4, + integrity, + ) + .await?; + } else { + // Resize existing filesystem to fill the expanded device + resize_ext4_filesystem(delta_device).await?; + } + } + DeltaBackend::DmSnapshot => { + // Build dm-snapshot device chain + setup_dm_snapshot_device_chain( + dm_verity_output_device, + Path::new(DELTA_DEVICE), + matches!(delta_location, DeltaLocation::DiskPersist), + ) .await?; - Ok::<_, anyhow::Error>(()) + + // Resize rootfs filesystem to fill the expanded device after building snapshot chain + resize_ext4_filesystem(Path::new(ROOTFS_DEVICE)).await?; + } } - .await - .context("Failed to create data logical volume")?; } else { - tracing::info!("Expanding data logical volume"); - if let Err(error) = expand_system_data_lv().await { - tracing::warn!(?error, "Failed to expend data logical volume"); + // No need to set up delta volume + match backend { + DeltaBackend::Overlayfs => { + // Nothing to do + } + DeltaBackend::DmSnapshot => { + tracing::info!("Creating zram device for COW storage"); + let cow_device = create_zram_cow_device().await?; + // Build dm-snapshot device chain + setup_dm_snapshot_device_chain(dm_verity_output_device, &cow_device, false) + .await?; + // Resize rootfs filesystem to fill the expanded device after building snapshot chain + resize_ext4_filesystem(Path::new(ROOTFS_DEVICE)).await?; + } } } + } - tracing::info!("Fetching passphrase for data volume"); - let provider = fde_config.data.encrypt.key_provider.into_provider(); - let passphrase = provider - .get_key() - .await - .context("Failed to get passphrase")?; + tracing::info!("Both rootfs volume and delta volume are ready"); - let integrity = if fde_config.data.integrity { - IntegrityType::Journal // Select Journal mode since it is persistent storage - } else { - IntegrityType::None - }; - - let data_logical_volume_dev = Path::new(DATA_LOGICAL_VOLUME); - let recreate_data_lv_content = matches!(provider.volume_type(), VolumeType::Temporary) - || !cryptpilot::fs::luks2::is_initialized(data_logical_volume_dev).await?; - if recreate_data_lv_content { - // Create a LUKS volume on it - tracing::info!("Creating LUKS2 on data volume"); - cryptpilot::fs::luks2::format(data_logical_volume_dev, &passphrase, integrity).await?; - } - - // TODO: support change size of the LUKS2 volume and inner ext4 file system - tracing::info!("Opening data volume"); - cryptpilot::fs::luks2::open_with_check_passphrase( - DATA_LAYER_NAME, - data_logical_volume_dev, - &passphrase, - integrity, - ) - .await?; + Ok(()) +} - if recreate_data_lv_content { - // Create a Ext4 fs on it - tracing::info!("Creating ext4 fs on data volume"); - cryptpilot::fs::mkfs::force_mkfs( - Path::new(DATA_LAYER_DEVICE), - &MakeFsType::Ext4, - integrity, - ) - .await?; +async fn ensure_delta_volume_exist_and_expanded() -> Result<(), anyhow::Error> { + if !Path::new(DELTA_LOGICAL_VOLUME).exists() { + tracing::info!( + "Delta logical volume does not exist, assume it is first time boot and create it." + ); + + // Due to there is no udev in initrd, the lvcreate will complain that /dev/cryptpilot/delta not exist. A workaround is to set '--zero n' and zeroing the first 4k of logical volume manually. + // See https://serverfault.com/a/1059400 + async { + Command::new("lvcreate") + .args([ + "-n", + DELTA_NAME, + "--zero", + "n", + "-l", + "100%FREE", + "cryptpilot", + ]) + .env("LVM_SYSTEM_DIR", CRYPTPILOT_LVM_SYSTEM_DIR) + .run() + .await?; + File::options() + .write(true) + .open(DELTA_LOGICAL_VOLUME) + .await? + .write_all(&[0u8; 4096]) + .await?; + Ok::<_, anyhow::Error>(()) + } + .await + .context("Failed to create delta logical volume")?; + } else { + tracing::info!("Expanding delta logical volume"); + if let Err(error) = expand_system_delta_lv().await { + tracing::warn!(?error, "Failed to expend delta logical volume"); } - - // Mark the volume as fully initialized - cryptpilot::fs::luks2::mark_volume_as_initialized(Path::new(DATA_LOGICAL_VOLUME)).await?; } - - tracing::info!("Both rootfs volume and data volume are ready"); - Ok(()) } @@ -192,7 +239,11 @@ async fn load_metadata() -> Result { load_metadata_from_file(Path::new(METADATA_PATH_IN_INITRD)).await } -async fn setup_rootfs_dm_verity(root_hash: &str, lower_dm_device: &str) -> Result<()> { +async fn setup_rootfs_dm_verity( + dm_verity_output_name: &str, + root_hash: &str, + lower_dm_device: &Path, +) -> Result<()> { async { Command::new("modprobe") .arg("dm-verity") @@ -203,7 +254,7 @@ async fn setup_rootfs_dm_verity(root_hash: &str, lower_dm_device: &str) -> Resul Command::new("veritysetup") .arg("open") .arg(lower_dm_device) - .arg(ROOTFS_LAYER_NAME) + .arg(dm_verity_output_name) .arg(ROOTFS_HASH_LOGICAL_VOLUME) .arg(root_hash) .run() @@ -222,7 +273,7 @@ async fn expand_system_pv_partition() -> Result<()> { r#" set -euo pipefail -VG_NAME="system" +VG_NAME="cryptpilot" # Find any PV belonging to the volume group PV_DEV=$(pvs --noheadings -o pv_name,vg_name | awk "\$2==\"$VG_NAME\" {print \$1; exit}") @@ -259,7 +310,7 @@ echo "Last partition number: $LAST_PART_NUM" echo "Expanding partition and physical volume ..." if growpart "$DISK_PATH" "$LAST_PART_NUM"; then - # the growpart command fill also call lvm pvresize to resize the related data volume + # the growpart command fill also call lvm pvresize to resize the related delta volume echo "Physical volume resized successfully" elif [[ $? -eq 1 ]]; then @@ -278,11 +329,11 @@ fi Ok::<_, anyhow::Error>(()) } -async fn expand_system_data_lv() -> Result<()> { +async fn expand_system_delta_lv() -> Result<()> { Command::new("lvextend") .arg("-l") .arg("+100%FREE") - .arg(DATA_LOGICAL_VOLUME) + .arg(DELTA_LOGICAL_VOLUME) .env("LVM_SYSTEM_DIR", CRYPTPILOT_LVM_SYSTEM_DIR) .run_with_status_checker(|code, _, _| match code { 0 | 5 => Ok(()), @@ -294,3 +345,199 @@ async fn expand_system_data_lv() -> Result<()> { Ok::<_, anyhow::Error>(()) } + +/// Setup delta volume LUKS2 encryption and return whether content should be recreated +async fn setup_delta_volume_luks2( + delta_config: &crate::config::DeltaConfig, + delta_location: DeltaLocation, +) -> Result<(bool, IntegrityType)> { + tracing::info!("Fetching passphrase for delta volume"); + let provider = delta_config.encrypt.key_provider.clone().into_provider(); + let passphrase = provider + .get_key() + .await + .context("Failed to get passphrase")?; + + let integrity = if delta_config.integrity { + IntegrityType::Journal // Select Journal mode since it is persistent storage + } else { + IntegrityType::None + }; + + let delta_logical_volume_dev = Path::new(DELTA_LOGICAL_VOLUME); + + let recreate = if matches!(provider.volume_type(), VolumeType::Temporary) { + tracing::info!("Key provider is temporary, will recreate delta volume content"); + true + } else if !cryptpilot::fs::luks2::is_initialized(delta_logical_volume_dev).await? { + tracing::info!("Delta volume is not initialized, will create new content"); + true + } else if matches!(delta_location, DeltaLocation::Disk) { + tracing::info!("Overlay type is disk (non-persistent), will recreate delta volume content"); + true + } else { + tracing::info!("Delta volume is initialized and overlay type is persistent, will reuse existing content"); + false + }; + + if recreate { + // Create a LUKS volume on it + tracing::info!("Creating LUKS2 on delta volume"); + cryptpilot::fs::luks2::format(delta_logical_volume_dev, &passphrase, integrity).await?; + } + + // TODO: support change size of the LUKS2 volume and inner ext4 file system + tracing::info!("Opening delta volume"); + cryptpilot::fs::luks2::open_with_check_passphrase( + DELTA_NAME, + delta_logical_volume_dev, + &passphrase, + integrity, + ) + .await?; + + Ok((recreate, integrity)) +} + +async fn create_zram_cow_device() -> Result { + // Load zram module + if !Path::new("/sys/class/zram-control").exists() { + Command::new("modprobe") + .arg("zram") + .run() + .await + .context("Failed to load zram module")?; + } + + // Get total memory in KB + let mem_info = tokio::fs::read_to_string("/proc/meminfo").await?; + let mem_total_kb = mem_info + .lines() + .find(|line| line.starts_with("MemTotal:")) + .and_then(|line| line.split_whitespace().nth(1)) + .and_then(|s| s.parse::().ok()) + .context("Failed to parse MemTotal from /proc/meminfo")?; + + // Add a zram device + let zram_id = tokio::fs::read_to_string("/sys/class/zram-control/hot_add") + .await + .context("Adding zram device")? + .trim_end() + .parse::() + .context("Allocate new zram device number")?; + + // Set zram size equal to total memory + let zram_size = format!("{}K", mem_total_kb); + tokio::fs::write(format!("/sys/block/zram{}/disksize", zram_id), &zram_size) + .await + .context("Failed to set zram disksize")?; + tracing::info!(size = %zram_size, "Created zram{}", zram_id); + + Ok(PathBuf::from(format!("/dev/zram{}", zram_id))) +} + +async fn setup_dm_snapshot_device_chain( + rootfs_device: &Path, + cow_device: &Path, + persistent: bool, +) -> Result<()> { + tracing::info!( + ?rootfs_device, + ?cow_device, + "Building dm-snapshot device chain" + ); + + // Load required kernel modules + Command::new("modprobe") + .arg("dm-snapshot") + .run() + .await + .context("Failed to load dm-snapshot module")?; + + // Get device sizes (in sectors, 512 bytes each) + let verity_size = get_device_size_bytes(rootfs_device).await? / 512; + let cow_size = get_device_size_bytes(cow_device).await? / 512; + + // Create dm-linear device combining dm-verity and zero target + // The zero target is used directly in the table, no need to create a separate dm-zero device + let linear_size = verity_size + cow_size; + tracing::info!( + "Creating dm-linear device with {} sectors (verity:{} + zero:{})", + linear_size, + verity_size, + cow_size + ); + Command::new("dmsetup") + .arg("create") + .arg(ROOTFS_EXTENDED_NAME) + .arg("--table") + .arg(format!( + "0 {} linear {} 0\n{} {} zero", + verity_size, + rootfs_device.to_string_lossy(), + verity_size, + cow_size + )) + .run() + .await + .context("Failed to create dm-linear device")?; + + // Create dm-snapshot device + tracing::info!("Creating dm-snapshot device"); + Command::new("dmsetup") + .arg("create") + .arg(ROOTFS_NAME) + .arg("--table") + .arg(format!( + "0 {} snapshot {} {} {} 16", // chunk size is 16 sectors (8KB) + linear_size, + ROOTFS_EXTENDED_DEVICE, + cow_device.to_string_lossy(), + if persistent { "PO" } else { "N" } + )) + .run() + .await + .context("Failed to create dm-snapshot device")?; + + tracing::info!("dm-snapshot device chain created successfully"); + Ok(()) +} + +async fn get_device_size_bytes(device: &Path) -> Result { + let file = File::open(device) + .await + .context(format!("Failed to open device {:?}", device))? + .into_std() + .await; + + file.get_block_device_size().context(format!( + "Failed to get block device size in bytes {:?}", + device + )) +} + +async fn resize_ext4_filesystem(device: &Path) -> Result<()> { + tracing::info!(device = %device.display(), "Resizing ext4 filesystem to fill device"); + + // Clear the read-only feature flag before resizing + Command::new("tune2fs") + .args(["-O", "^read-only"]) + .arg(device) + .run() + .await + .context(format!( + "Failed to clear read-only flag on {}", + device.display() + ))?; + + Command::new("resize2fs") + .arg(device) + .run() + .await + .context(format!( + "Failed to resize ext4 filesystem on {}", + device.display() + ))?; + tracing::info!("ext4 filesystem resized successfully"); + Ok(()) +} diff --git a/cryptpilot-fde/src/cmd/boot_service/stage/mod.rs b/cryptpilot-fde/src/cmd/boot_service/stage/mod.rs index 4d47532..11dfea8 100644 --- a/cryptpilot-fde/src/cmd/boot_service/stage/mod.rs +++ b/cryptpilot-fde/src/cmd/boot_service/stage/mod.rs @@ -1,19 +1,32 @@ pub mod after_sysroot; pub mod before_sysroot; -// Rootfs encryption layer - LUKS2 encrypted device for root filesystem -pub const ROOTFS_LAYER_NAME: &str = "rootfs"; -pub const ROOTFS_LAYER_DEVICE: &str = "/dev/mapper/rootfs"; +// LVM related constants +// Volume group name in LVM +pub const VOLUME_GROUP_NAME: &str = "cryptpilot"; // Rootfs logical volume in LVM -pub const ROOTFS_LOGICAL_VOLUME: &str = "/dev/mapper/system-rootfs"; -// Rootfs decrypted layer with dm-verity for integrity verification -pub const ROOTFS_DECRYPTED_LAYER_NAME: &str = "rootfs_decrypted"; -pub const ROOTFS_DECRYPTED_LAYER_DEVICE: &str = "/dev/mapper/rootfs_decrypted"; +pub const ROOTFS_LOGICAL_VOLUME: &str = "/dev/mapper/cryptpilot-rootfs"; // Rootfs dm-verity hash device -pub const ROOTFS_HASH_LOGICAL_VOLUME: &str = "/dev/mapper/system-rootfs_hash"; +pub const ROOTFS_HASH_LOGICAL_VOLUME: &str = "/dev/mapper/cryptpilot-rootfs_hash"; +// Delta logical volume in LVM +pub const DELTA_LOGICAL_VOLUME: &str = "/dev/mapper/cryptpilot-delta"; + +// The final rootfs device - Used for mounting as root filesystem +pub const ROOTFS_NAME: &str = "rootfs"; +pub const ROOTFS_DEVICE: &str = "/dev/mapper/rootfs"; + +// Rootfs decrypted which will be used as backend for dm-verity +pub const ROOTFS_DECRYPTED_NAME: &str = "rootfs_decrypted"; +pub const ROOTFS_DECRYPTED_LAYER_DEVICE: &str = "/dev/mapper/rootfs_decrypted"; + +// The final delta partition device - LUKS2 encrypted device for delta partition +pub const DELTA_NAME: &str = "delta"; +pub const DELTA_DEVICE: &str = "/dev/mapper/delta"; -// Data partition encryption layer - LUKS2 encrypted device for data partition -pub const DATA_LAYER_NAME: &str = "data"; -pub const DATA_LAYER_DEVICE: &str = "/dev/mapper/data"; -// Data logical volume in LVM -pub const DATA_LOGICAL_VOLUME: &str = "/dev/mapper/system-data"; +// dm-snapshot related constants +// dm-verity device name for dm-snapshot backend +pub const ROOTFS_VERITY_NAME: &str = "rootfs_verity"; +pub const ROOTFS_VERITY_DEVICE: &str = "/dev/mapper/rootfs_verity"; +// dm-linear device combining dm-verity and zero target (extended rootfs for snapshot) +pub const ROOTFS_EXTENDED_NAME: &str = "rootfs_extended"; +pub const ROOTFS_EXTENDED_DEVICE: &str = "/dev/mapper/rootfs_extended"; diff --git a/cryptpilot-fde/src/cmd/config/check.rs b/cryptpilot-fde/src/cmd/config/check.rs index 98e5ec8..e5690c7 100644 --- a/cryptpilot-fde/src/cmd/config/check.rs +++ b/cryptpilot-fde/src/cmd/config/check.rs @@ -62,7 +62,7 @@ impl super::super::Command for ConfigCheckCommand { .encrypt .map(|encrypt| ("rootfs", encrypt)) .into_iter() - .chain(std::iter::once(("data", fde.data.encrypt))); + .chain(std::iter::once(("delta", fde.delta.encrypt))); for (volume_debug_name, encrypt) in encrypt_configs { tracing::info!("Checking config for FDE \"{}\" volume", volume_debug_name); diff --git a/cryptpilot-fde/src/config/cloud_init.rs b/cryptpilot-fde/src/config/cloud_init.rs index 6f1e4c4..4495399 100644 --- a/cryptpilot-fde/src/config/cloud_init.rs +++ b/cryptpilot-fde/src/config/cloud_init.rs @@ -75,16 +75,16 @@ pub mod tests { verbose = true [fde.rootfs] -rw_overlay = "disk" +delta_location = "disk" [fde.rootfs.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"] -[fde.data] +[fde.delta] integrity = true -[fde.data.encrypt.exec] +[fde.delta.encrypt.exec] command = "echo" args = ["-n", "AAAaaawewe222"]"#, )?; diff --git a/cryptpilot-fde/src/config/fde.rs b/cryptpilot-fde/src/config/fde.rs index 0936ec6..ff796f7 100644 --- a/cryptpilot-fde/src/config/fde.rs +++ b/cryptpilot-fde/src/config/fde.rs @@ -9,16 +9,21 @@ pub struct FdeConfig { /// Configuration related to the read-only root filesystem. pub rootfs: RootFsConfig, - /// Configuration related to the data partition. - pub data: DataConfig, + /// Configuration related to the writeable delta volume on disk. + #[serde(alias = "data")] + pub delta: DeltaConfig, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, DocumentedFields)] #[serde(deny_unknown_fields)] pub struct RootFsConfig { - /// The type of read-write overlay layer over the underhood read-only rootfs. Can be "disk", "disk-persist", or "ram". Default value is "disk". + /// The locaton for storing the delta data over the underhood read-only rootfs. Can be "disk", "disk-persist", or "ram". Default value is "disk". + #[serde(skip_serializing_if = "Option::is_none", alias = "rw_overlay")] + pub delta_location: Option, + + /// The backend implementation for the delta data layer. Can be "overlayfs" or "dm-snapshot". Default value is "dm-snapshot". #[serde(skip_serializing_if = "Option::is_none")] - pub rw_overlay: Option, + pub delta_backend: Option, /// Encryption configuration for root filesystem. If not set, the rootfs partition WOULD NOT be encrypted. #[serde(skip_serializing_if = "Option::is_none")] @@ -27,34 +32,46 @@ pub struct RootFsConfig { #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, DocumentedFields)] #[serde(deny_unknown_fields)] -pub struct DataConfig { +pub struct DeltaConfig { /// Whether or not to enable integrity check. #[serde(default = "Default::default")] pub integrity: bool, - /// Encryption configuration for data partition. If not set, the data partition WOULD NOT be encrypted. + /// Encryption configuration for delta partition. If not set, the delta partition WOULD NOT be encrypted. pub encrypt: EncryptConfig, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Default)] #[serde(deny_unknown_fields)] -pub enum RwOverlayType { - /// The overlay will be placed on disk but will be cleared on every boot. +pub enum DeltaLocation { + /// The delta data will be placed on disk but will be cleared on every boot. /// This is the default and recommended option for security. #[default] #[serde(rename = "disk")] Disk, - /// The overlay will be placed on disk, and be persistent across reboots. - /// Note: persistence depends on the data volume configuration. + /// The delta data will be placed on disk, and be persistent across reboots. + /// Note: persistence depends on the delta volume configuration. #[serde(rename = "disk-persist")] DiskPersist, - /// The overlay will be placed on tmpfs (in RAM), and be cleared on reboot. + /// The delta data will be placed on tmpfs (in RAM), and be cleared on reboot. #[serde(rename = "ram")] Ram, } +#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Default)] +#[serde(deny_unknown_fields)] +pub enum DeltaBackend { + /// Use overlayfs for file-level copy-on-write. + #[serde(rename = "overlayfs")] + Overlayfs, + /// Use dm-snapshot for block-level copy-on-write. This is the default. + #[default] + #[serde(rename = "dm-snapshot")] + DmSnapshot, +} + #[cfg(test)] -pub mod tests { +mod tests { use cryptpilot::{ config::encrypt::KeyProviderConfig, @@ -72,7 +89,8 @@ pub mod tests { let raw = r#" [rootfs] -rw_overlay = "disk" +delta_location = "disk" +delta_backend = "dm-snapshot" [rootfs.encrypt.kbs] kbs_url = "https://1.2.3.4:8080" @@ -84,10 +102,10 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -----END CERTIFICATE----- """ -[data] +[delta] integrity = true -[data.encrypt.kbs] +[delta.encrypt.kbs] kbs_url = "https://1.2.3.4:8080" key_uri = "kbs:///default/test/data_partition" kbs_root_cert = """ @@ -102,7 +120,8 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX config, FdeConfig { rootfs: RootFsConfig { - rw_overlay: Some(RwOverlayType::Disk), + delta_location: Some(DeltaLocation::Disk), + delta_backend: Some(DeltaBackend::DmSnapshot), encrypt: Some(EncryptConfig { key_provider: KeyProviderConfig::Kbs(KbsConfig { cdh_type: CdhType::OneShot { @@ -120,7 +139,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX }) }) }, - data: DataConfig { + delta: DeltaConfig { integrity: true, encrypt: EncryptConfig { key_provider: KeyProviderConfig::Kbs(KbsConfig { diff --git a/cryptpilot-fde/src/config/global.rs b/cryptpilot-fde/src/config/global.rs index fb6fab6..ac14bb7 100644 --- a/cryptpilot-fde/src/config/global.rs +++ b/cryptpilot-fde/src/config/global.rs @@ -18,7 +18,7 @@ pub struct BootServiceConfig { } #[cfg(test)] -pub mod tests { +mod tests { #[allow(unused_imports)] use super::*; diff --git a/cryptpilot-fde/src/lib.rs b/cryptpilot-fde/src/lib.rs new file mode 100644 index 0000000..4aa682d --- /dev/null +++ b/cryptpilot-fde/src/lib.rs @@ -0,0 +1,13 @@ +// Library exports for cryptpilot-crypt + +use shadow_rs::shadow; + +shadow!(build); + +pub mod cli; +pub mod cmd; +pub mod config; +pub mod disk; + +// Re-export async_defer from cryptpilot core +pub use cryptpilot::async_defer; diff --git a/dist/dracut/modules.d/91cryptpilot/module-setup.sh b/dist/dracut/modules.d/91cryptpilot/module-setup.sh index 9cf7051..062c634 100755 --- a/dist/dracut/modules.d/91cryptpilot/module-setup.sh +++ b/dist/dracut/modules.d/91cryptpilot/module-setup.sh @@ -10,12 +10,19 @@ install() { set -e set -u # TODO: simplify this - inst_multiple veritysetup mkfs.ext4 mkfs.vfat mkfs.xfs mkswap base64 + inst_multiple veritysetup base64 inst_multiple vgchange lvcreate inst_multiple blkid lsblk findmnt - inst_multiple dd tail grep sort + inst_multiple tail grep inst_multiple awk sed pvs growpart sfdisk lvm lvextend mountpoint bash inst_multiple modprobe + + # We need mkfs.ext4 for creating delta volume here + inst_multiple mkfs.ext4 + + # For resize ext4 filesystem + inst_multiple tune2fs resize2fs + # For debug only # inst_multiple curl nc ip find systemctl journalctl ifconfig lsblk df # Install cryptpilot-fde for FDE boot-time decryption diff --git a/dist/etc/fde.toml.template b/dist/etc/fde.toml.template index 8ed56c1..3a80dff 100644 --- a/dist/etc/fde.toml.template +++ b/dist/etc/fde.toml.template @@ -1,7 +1,9 @@ # Configuration related to the read-only root filesystem. [rootfs] -# The type of read-write overlay layer over the underhood read-only rootfs. Can be "disk", "disk-persist", or "ram". Default value is "disk". -rw_overlay = "disk" +# The locaton for storing the delta data over the underhood read-only rootfs. Can be "disk", "disk-persist", or "ram". Default value is "disk". +delta_location = "disk" +# The backend implementation for the delta data layer. Can be "overlayfs" or "dm-snapshot". Default value is "dm-snapshot". +delta_backend = "dm-snapshot" [rootfs.encrypt.kbs] cdh_type = "one-shot" @@ -14,12 +16,11 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX """ key_uri = "kbs:///default/mykey/rootfs_partition" -# Configuration related to the data partition. -[data] -# Whether or not to enable integrity check. +# Configuration related to the writeable delta volume on disk. +[delta] integrity = true -[data.encrypt.kbs] +[delta.encrypt.kbs] cdh_type = "one-shot" kbs_url = "https://1.2.3.4:8080" kbs_root_cert = """ diff --git a/dist/usr/lib/udev/rules.d/12-cryptpilot-hide-intermediate-devices.rules b/dist/usr/lib/udev/rules.d/12-cryptpilot-hide-intermediate-devices.rules index 2ff32be..48b7fa5 100644 --- a/dist/usr/lib/udev/rules.d/12-cryptpilot-hide-intermediate-devices.rules +++ b/dist/usr/lib/udev/rules.d/12-cryptpilot-hide-intermediate-devices.rules @@ -2,14 +2,38 @@ # # This rule should be placed after /usr/lib/udev/rules.d/10-dm.rules, /usr/lib/udev/rules.d/11-dm-lvm.rules and before /usr/lib/udev/rules.d/13-dm-disk.rules, /etc/udev/rules.d/61-persistent-storage.rules -# Prevent /dev/system/rootfs from creating /dev/disk/by-uuid/... symlinks -ENV{DM_VG_NAME}=="system", ENV{DM_LV_NAME}=="rootfs", \ +# +# The following are used when rootfs encryption is not enabled +# + +# Prevent /dev/cryptpilot/rootfs from creating /dev/disk/by-uuid/... symlinks +ENV{DM_VG_NAME}=="cryptpilot", ENV{DM_LV_NAME}=="rootfs", \ ENV{UDISKS_IGNORE}="1", \ ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1", \ ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1" +# +# The following are used when rootfs encryption is enabled +# + # Prevent /dev/mapper/rootfs_decrypted from creating /dev/disk/by-uuid/... symlinks ENV{DM_NAME}=="rootfs_decrypted", \ ENV{UDISKS_IGNORE}="1", \ ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1", \ ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1" + +# +# The following are used in dm-snapshot backend +# + +# Prevent /dev/mapper/rootfs_verity from creating /dev/disk/by-uuid/... symlinks +ENV{DM_NAME}=="rootfs_verity", \ + ENV{UDISKS_IGNORE}="1", \ + ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1", \ + ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1" + +# Prevent /dev/mapper/rootfs_extended from creating /dev/disk/by-uuid/... symlinks +ENV{DM_NAME}=="rootfs_extended", \ + ENV{UDISKS_IGNORE}="1", \ + ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1", \ + ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1"