Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added LUKS reencryption support #2703

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions build-tests/x86/tumbleweed/test-image-luks/appliance.kiwi
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>

<!-- OBS-Profiles: @BUILD_FLAVOR@ -->
<image schemaversion="7.5" name="kiwi-test-image-luks">
<description type="system">
<author>Marcus Schäfer</author>
<contact>[email protected]</contact>
<specification>Disk full encryption disk test build</specification>
</description>
<profiles>
<profile name="Insecure" description="Encrypted no reencryption"/>
<profile name="ReEncryptExtraBootEmptyPass" description="Run reencryption with extra boot partition and empty passphrase"/>
<profile name="ReEncryptExtraBootWithPass" description="Run reencryption with extra boot partition and passphrase"/>
<profile name="ReEncryptFullDisk" description="Run full disk reencryption with passphrase"/>
</profiles>
<preferences>
<version>1.15.1</version>
<packagemanager>zypper</packagemanager>
Expand All @@ -16,13 +22,51 @@
<rpm-check-signatures>false</rpm-check-signatures>
<bootsplash-theme>breeze</bootsplash-theme>
<bootloader-theme>openSUSE</bootloader-theme>
</preferences>
<preferences profiles="Insecure">
<type image="oem" filesystem="ext4" kernelcmdline="console=ttyS0" firmware="uefi" luks="linux" luks_version="luks2" luks_pbkdf="pbkdf2" bootpartition="false">
<luksformat>
<option name="--cipher" value="aes-xts-plain64"/>
<option name="--key-size" value="256"/>
</luksformat>
<oemconfig>
<oem-resize>false</oem-resize>
<oem-resize>true</oem-resize>
</oemconfig>
<bootloader name="grub2" console="serial" timeout="10"/>
</type>
</preferences>
<preferences profiles="ReEncryptExtraBootEmptyPass">
<type image="oem" filesystem="ext4" kernelcmdline="console=ttyS0 rd.kiwi.oem.luks.reencrypt" firmware="uefi" luks="" luks_version="luks2" luks_pbkdf="pbkdf2" bootpartition="true">
<luksformat>
<option name="--cipher" value="aes-xts-plain64"/>
<option name="--key-size" value="256"/>
</luksformat>
<oemconfig>
<oem-resize>true</oem-resize>
</oemconfig>
<bootloader name="grub2" console="serial" timeout="10"/>
</type>
</preferences>
<preferences profiles="ReEncryptExtraBootWithPass">
<type image="oem" filesystem="ext4" kernelcmdline="console=ttyS0 rd.kiwi.oem.luks.reencrypt" firmware="uefi" luks="linux" luks_version="luks2" luks_pbkdf="pbkdf2" bootpartition="true">
<luksformat>
<option name="--cipher" value="aes-xts-plain64"/>
<option name="--key-size" value="256"/>
</luksformat>
<oemconfig>
<oem-resize>true</oem-resize>
</oemconfig>
<bootloader name="grub2" console="serial" timeout="10"/>
</type>
</preferences>
<preferences profiles="ReEncryptFullDisk">
<type image="oem" filesystem="ext4" kernelcmdline="console=ttyS0 rd.kiwi.oem.luks.reencrypt" firmware="uefi" luks="linux" luks_version="luks2" luks_pbkdf="pbkdf2" bootpartition="false">
<luksformat>
<option name="--cipher" value="aes-xts-plain64"/>
<option name="--key-size" value="256"/>
</luksformat>
<oemconfig>
<oem-resize>true</oem-resize>
</oemconfig>
<bootloader name="grub2" console="serial" timeout="10"/>
</type>
Expand Down Expand Up @@ -61,6 +105,7 @@
<package name="shim"/>
<package name="timezone"/>
<package name="cryptsetup"/>
<package name="dracut-kiwi-oem-repart"/>
</packages>
<packages type="bootstrap">
<package name="gawk"/>
Expand Down
34 changes: 33 additions & 1 deletion build-tests/x86/tumbleweed/test-image-luks/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
# :
# STATUS : BETA
#----------------
declare kiwi_iname=${kiwi_iname}
declare kiwi_profiles=${kiwi_profiles}

#======================================
# Functions...
#--------------------------------------
# shellcheck disable=SC1091
test -f /.kconfig && . /.kconfig
test -f /.profile && . /.profile

#======================================
# Greeting...
Expand All @@ -46,3 +49,32 @@ baseSetRunlevel 3
#------------------------------------------
rm -rf /usr/share/doc/packages/*
rm -rf /usr/share/doc/manual/*


# For image tests with an extra boot partition the
# kernel must not be a symlink to another area of
# the filesystem. Latest changes on SUSE changed the
# layout of the kernel which breaks every image with
# an extra boot partition
#
# All of the following is more than a hack and I
# don't like it all
#
# Complains and discussions about this please with
# the SUSE kernel team as we in kiwi can just live
# with the consequences of this change
#
for profile in ${kiwi_profiles//,/ }; do
Fixed Show fixed Hide fixed
if [ "${profile}" = "ReEncryptExtraBootEmptyPass" ] || [ "${profile}" = "ReEncryptExtraBootWithPass" ]; then
pushd /

for file in /boot/* /boot/.*; do
if [ -L "${file}" ];then
link_target=$(readlink "${file}")
if [[ "${link_target}" =~ usr/lib/modules ]];then
mv "${link_target}" "${file}"
fi
fi
done
fi
done
12 changes: 12 additions & 0 deletions doc/source/concept_and_workflow/customize_the_boot_process.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,18 @@ the available kernel boot parameters for these modules:
Note that options starting with `rd.kiwi` are not passed to avoid
side effects.

``rd.kiwi.oem.luks.reencrypt``
For OEM LUKS2 encrypted disk images. If set, reencrypts the disk
prior an eventual resize and therefore creates a new key pool and
master key. The reencryption is advisable if the image binary is
not protected. With access to the image binary it's possible to
extract the luks header which then allows to decrypt the data
unless it was reencrypted. The reencryption process only runs if
the checksum of the luks header still matches the one from the
original disk image. Be aware that the reencryption will ask
for the passphrase if the image has been built with an initial
luks passphrase.

``rd.kiwi.oem.maxdisk=size[KMGT]``
Specifies the maximum disk size an unattended OEM installation uses for image
deployment. Unattended OEM deployments default to deploying on `/dev/sda` (or
Expand Down
21 changes: 18 additions & 3 deletions dracut/modules.d/90kiwi-repart/kiwi-repart-disk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ function initialize {
test -f ${profile} && import_file ${profile}
import_file ${partition_ids}

# Optional env TERM setup
term=$(getarg "rd.kiwi.term=")
[ -n "${term}" ] && export TERM="${term}"

# Create dialog profile from current env
# Used in the systemd dialog unit
env >/dialog_profile

disk=$(lookup_disk_device_from_root)
export disk

Expand Down Expand Up @@ -248,6 +256,16 @@ mask_fsck_root_service
# initialize for disk repartition
initialize

# reencrypt luks device
if luks_system "${disk}";then
if getargbool 0 rd.kiwi.oem.luks.reencrypt; then
reencrypt_luks "${disk}"
fi
fi

# wait for the root device to appear
wait_for_storage_device "${root_device}"

# check if repart/resize is wanted
if ! resize_wanted "${root_device}" "${disk}"; then
return
Expand All @@ -258,9 +276,6 @@ if [ "$(get_partition_table_type "${disk}")" = 'gpt' ];then
relocate_gpt_at_end_of_disk "${disk}"
fi

# wait for the root device to appear
wait_for_storage_device "${root_device}"

# resize disk partition table
if lvm_system;then
repart_lvm_disk || return
Expand Down
5 changes: 5 additions & 0 deletions dracut/modules.d/99kiwi-lib/kiwi-dialog-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,8 @@ function ask_and_shutdown {
systemctl halt
fi
}

function ask_for_credentials {
local text_message="$1"
run_dialog --insecure --passwordbox "\"${text_message}\"" 7 60
}
56 changes: 56 additions & 0 deletions dracut/modules.d/99kiwi-lib/kiwi-luks-lib.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#!/bin/bash

# shellcheck disable=SC1091
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
type set_root_map >/dev/null 2>&1 || . /lib/kiwi-lib.sh
type wait_for_storage_device >/dev/null 2>&1 || . /lib/kiwi-partitions-lib.sh
type ask_for_password >/dev/null 2>&1 || . /lib/dracut-crypt-lib.sh
type run_progress_dialog >/dev/null 2>&1 || . /lib/kiwi-dialog-lib.sh
Fixed Show fixed Hide fixed

function luks_system {
declare kiwi_RootPart=${kiwi_RootPart}
Expand All @@ -20,6 +22,60 @@ function deactivate_luks {
/usr/lib/systemd/systemd-cryptsetup detach luks
}

function reencrypt_luks {
declare kiwi_RootPart=${kiwi_RootPart}
local disk=$1
local header_checksum_origin=/root/.luks.header
local header_checksum_cur=/root/.luks.header.cur
local keyfile=/root/.root.keyfile
local passphrase_file=/root/.slot0
local progress=/dev/install_progress
local load_text="Reencrypting..."
local title_text="LUKS"
local device
device=$(get_partition_node_name "${disk}" "${kiwi_RootPart}")
read -r header_checksum_origin < "${header_checksum_origin}"
if [ "${kiwi_luks_empty_passphrase}" = "true" ];then
cryptsetup \
--key-file /dev/zero \
--keyfile-size 32 \
luksHeaderBackup "${device}" \
--header-backup-file "${header_checksum_cur}"
else
cryptsetup \
--key-file "${keyfile}" \
luksHeaderBackup "${device}" \
--header-backup-file "${header_checksum_cur}"
fi
header_checksum_cur=$(
sha256sum "${header_checksum_cur}" |\
cut -f1 -d" "; rm -f "${header_checksum_cur}"
)
if [ "${header_checksum_origin}" == "${header_checksum_cur}" ];then
if [ "${kiwi_luks_empty_passphrase}" = "true" ];then
echo -n > "${passphrase_file}"
else
ask_for_credentials "Enter Credentials for Key Slot(0)"
get_dialog_result > "${passphrase_file}"
fi
setup_progress_fifo ${progress}
(
# reencrypt slot0, this will wipe all key slots
cryptsetup reencrypt \
--progress-frequency 1 \
--key-file "${passphrase_file}" \
--key-slot 0 \
"${device}" 2>&1 | sed -u 's/.* \([0-9]*\)[0-9.]*%.*/\1/'
) >"${progress}" &
run_progress_dialog "${load_text}" "${title_text}"
if [ -e "${keyfile}" ];then
# re-add keyfile if present
cryptsetup --key-file "${passphrase_file}" luksAddKey \
"${device}" "${keyfile}"
fi
fi
}

function resize_luks {
cryptsetup resize luks
}
Expand Down
2 changes: 1 addition & 1 deletion dracut/modules.d/99kiwi-lib/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ install() {
e2fsck btrfsck xfs_repair \
vgs vgchange lvextend lvcreate lvresize pvresize \
mdadm cryptsetup dialog \
pv curl xz \
pv curl xz sha256sum sed \
dmsetup
inst_multiple -o dolly
if type partx &> /dev/null;then
Expand Down
10 changes: 9 additions & 1 deletion kiwi/boot/image/dracut.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,16 @@ def post_init(self) -> None:
# Initialize empty list of dracut caller options
self.dracut_options: List[str] = []
self.included_files: List[str] = []
self.delete_after_include_files: List[str] = []
self.modules: List[str] = []
self.add_modules: List[str] = []
self.omit_modules: List[str] = []
self.available_modules = self._get_modules()

def include_file(self, filename: str, install_media: bool = False) -> None:
def include_file(
self, filename: str, install_media: bool = False,
delete_after_include: bool = False
) -> None:
"""
Include file to dracut boot image

Expand All @@ -75,6 +79,8 @@ def include_file(self, filename: str, install_media: bool = False) -> None:
"""
self.included_files.append('--install')
self.included_files.append(filename)
if delete_after_include:
self.delete_after_include_files.append(filename)

def include_module(self, module: str, install_media: bool = False) -> None:
"""
Expand Down Expand Up @@ -238,6 +244,8 @@ def create_initrd(
Command.run(
['chmod', '644', self.initrd_filename]
)
for filename in self.delete_after_include_files:
os.unlink(f'{self.boot_root_directory}/{filename}')

def _get_modules(self) -> List[str]:
cmd = Command.run(
Expand Down
16 changes: 16 additions & 0 deletions kiwi/builder/disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,8 @@ def _build_main_system(

self._write_crypttab_to_system_image(luks_root)

self._write_luks_header_checksum_to_boot_image(luks_root)

self._write_integritytab_to_system_image(integrity_root)

self._write_generic_fstab_to_system_image(device_map, system)
Expand Down Expand Up @@ -1271,6 +1273,20 @@ def _write_crypttab_to_system_image(
os.sep + os.sep.join(['etc', os.path.basename(filename)])
)

def _write_luks_header_checksum_to_boot_image(
self, luks_root: Optional[LuksDevice]
) -> None:
if luks_root is not None:
log.info('Including origin LUKS header checksum')
filename = ''.join(
[self.root_dir, '/root/.luks.header']
)
self.boot_image.include_file(
filename=os.sep + os.sep.join(
['root', os.path.basename(filename)]
), delete_after_include=True
)

def _write_generic_fstab_to_system_image(
self, device_map: Dict,
system: Optional[Union[FileSystemBase, VolumeManagerBase]]
Expand Down
19 changes: 19 additions & 0 deletions kiwi/storage/luks_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

# project
from kiwi.utils.temporary import Temporary
from kiwi.utils.checksum import Checksum
from kiwi.path import Path
from kiwi.command import Command
from kiwi.defaults import Defaults
from kiwi.storage.device_provider import DeviceProvider
Expand Down Expand Up @@ -172,6 +174,23 @@ def create_crypto_luks(
'luksAddKey', storage_device, keyfile_path
]
)

# Create backup header checksum as reencryption reference
master_checksum = f'{root_dir}/root/.luks.header'
Path.wipe(master_checksum)
Command.run(
[
'cryptsetup', '--key-file', passphrase_file
] + extra_options + [
'luksHeaderBackup', storage_device,
'--header-backup-file', master_checksum
]
)
checksum = Checksum(master_checksum).sha256()
with open(master_checksum, 'w') as shasum:
shasum.write(checksum)

# open the pool
Command.run(
[
'cryptsetup', '--key-file', passphrase_file
Expand Down
Loading
Loading