diff --git a/jenkins/image_building/initrd_sdk/README.md b/jenkins/image_building/initrd_sdk/README.md new file mode 100644 index 00000000..25b56e04 --- /dev/null +++ b/jenkins/image_building/initrd_sdk/README.md @@ -0,0 +1,9 @@ +# Initrd SDK + +The Initrd SDK folder contains scripts and documentation to help integrate +features e.g. LUKS and TPM support into the linux "initrd/initfs" of disk +disk images built as part of the Metal3 project. + +This directory is just a loose collection of scripts and information that +can be injected at different stages of the image building and would be +eventually executed during the boot process of a machine. diff --git a/jenkins/image_building/initrd_sdk/unlock-mount-luks.sh b/jenkins/image_building/initrd_sdk/unlock-mount-luks.sh new file mode 100755 index 00000000..3515ff54 --- /dev/null +++ b/jenkins/image_building/initrd_sdk/unlock-mount-luks.sh @@ -0,0 +1,140 @@ +#!/bin/bash + +_is_luks(){ + # check blkid record of a device and determine if it is luks encrypted + # arguments - any path to device file e.g. /dev/sd*, + # /dev/disk/by-label/,by-uuid/ etc.. + local _record _half_type _full_type _device_path + _device_path="$1" + _record="$(blkid "${_device_path}")" + _half_type="${_record##*TYPE=\"}" + _full_type="${_half_type%%\"*}" + printf "TYPE IS %s\n" "${_full_type}" + if [[ "crypto_LUKS" == "${_full_type}" ]]; then + return 0 + fi + return 1 +} + +_get_partition_from_blkid(){ + # remove all but the device name of the blkid record + # arguments - any path to deevice file e.g. /dev/sd*, + # /dev/disk/by-label/,by-uuid/ etc.. + local _record _partition_device_path _partition _uuid _blkid + _partition_device_path="$1" + _record="$(blkid "${_partition_device_path}")" + # take the UUID then, run regular blkid w/o arguments and match the UUID + _record="${_record#*UUID=}" + _uuid="${_record%% *}" + + blkid | while read -r _pname _iter_uuid _ _; do + if [[ "${_uuid}" == "${_iter_uuid#*UUID=}" ]]; then + _partition="${_pname%%\:*}" + printf "%s" "${_partition##*/}" + break + fi + done +} + +_get_part_prefix_for_device(){ + # some block device type will have "p" or "part" partition number + # prefix associated with it, this function return's the partition prefix + # arguments - name of a partition that can be found in /proc/partitions + # and belongs to the device/disk under analysis + local _partition + _partition="$1" + if [[ "${_partition}" =~ nvme|loop|mmcblk ]]; then + if [[ "${_partition}" =~ .*part.* ]]; then + printf "part" + else + printf "p" + fi + fi +} + +_count_parts_for_disk(){ + # Match how many partitions belong to a given disk based on + # data available in /proc/partitions + # arguments - name of the disk under /dev (not path just name) + local _count _disk + _disk="$1" + # both the disk and the partitions are listed in the /proc/partitions so + # there will be an extra record that has to be accounted for + _count=-1 + # read command will also cut up the columns + while read -r _ _ _ _part; do + # if the major device number matches then we have match + if [[ "${_part}" =~ ${_disk} ]]; then + _count=$((_count + 1)) + fi + done < "/proc/partitions" + printf "%s" "${_count}" +} + +# Start of the execution + +# The script has 1 mandatory and 3 optional positional arguments +# Such as: +# - path to the device file of the root partition (mandatory) +# - path to the script that provides the encryption key in plain text format +# - the partition number of the config-drive +# - flag to run in dry run mode (nothig will get created/unlocked/mounted +root_device_path="${1:?}" +key_script="${2:-/etc/tpm2-unseal-key.sh}" +config_drive_part_num="${3:-}" +dry_run="${4:-false}" + +printf "INFO: unlock script has been started with the following arguments:\n" +printf "Root device:%s, key script:%s, dry run:%s\n" "${root_device_path}" \ + "${key_script}" "${dry_run}" + +# create the mount point for the root file system +if [[ "${dry_run}" == "false" ]]; then + mkdir "/realroot" +fi + +# different workflows depending on the presence of encryption +# -------------------------------------------------------------------- +# nvme, mmc/sd card and loop devices have a partition number prefix +# e.g./dev/nvme0n1 <--> /dev/nvme0n1p1 +# -------------------------------------------------------------------- +# It is expected that the last partition on the root device is the +# config drive partition thus that is the default logic. In case the user +# specified a value for `config_drive_part_num` the partition count will +# be discarded and the value of `config_drive_part_num` will be used instead. +if [[ $(_is_luks "${root_device_path}") ]]; then + printf "Mounting encrypted %s\n" "${root_device_path}" + if [[ "${dry_run}" == "false" ]]; then + /usr/lib/systemd/systemd-cryptsetup attach realroot \ + "${root_device_path}" <("${key_script}") luks + mount "/dev/mapper/realroot" "/realroot" + fi + root_partition="$(_get_partition_from_blkid "${root_device_path}")" + part_prefix="$(_get_part_prefix_for_device "${root_partition}")" + root_device="${root_partition%"${part_prefix}"*}" + part_count="$(_count_parts_for_disk "${root_device}")" + config_drive_common="/dev/${root_device}${part_prefix}" + if [[ -z "${config_drive_part_num}" ]]; then + config_drive_path="${config_drive_common}${part_count}" + else + config_drive_path="${config_drive_common}${config_drive_part_num}" + fi + if [[ $(_is_luks "${config_drive_path}") ]]; then + printf "Unlocking config drive %s\n" "${config_drive_path}" + printf "INFO: config-drive:%s disk:%s part_count:%s " \ + "${config_drive_path}" "${root_device}" "${part_count}" + printf "root_partition:%s prefix:%s\n" "${root_partition}" \ + "${part_prefix}" + if [[ "${dry_run}" == "false" ]]; then + /usr/lib/systemd/systemd-cryptsetup attach "config-2" \ + "${config_drive_path}" <("${key_script}") luks + fi + # At this stage the config drive does not need to be mounted, after + # that it is unlocked cloud-init can identify and mount the config + # drive on its own + fi + +else + printf "Mounting NON encrypted volume!!!\n" + mount "${root_device_path}" "/realroot" +fi