mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
pvics (PV Image Customization Support) is a comprehensive tool for converting existing qcow2 KVM guest images to IBM Secure Execution for Linux (SEL) images with Early Boot Customization (EBC) support and encrypted root filesystems. The tool provides four main actions: - list: Retrieve information about boot loader entries in a base image - convert: Convert a base image to an EBC-ready SEL image - encrypt: Encrypt the root filesystem and prepare EBC resources - full: Perform both conversion and encryption in one operation Assisted-by: IBM Bob:1.0.1 Reviewed-by: Holger Dengler <dengler@linux.ibm.com> Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Signed-off-by: Finn Callies <fcallies@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1580 lines
35 KiB
Bash
Executable File
1580 lines
35 KiB
Bash
Executable File
#!/bin/bash
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# Copyright IBM Corp.
|
|
|
|
|
|
#
|
|
# CLEANUP STACK FUNCTIONS
|
|
#
|
|
|
|
# Initialize the cleanup stack as an array
|
|
declare -a CLEANUP_STACK=()
|
|
|
|
cleanup_push() {
|
|
local cmd="$*"
|
|
if [[ -z "$cmd" ]]; then
|
|
log_error "pvics: no command provided"
|
|
return 1
|
|
fi
|
|
CLEANUP_STACK+=("$cmd")
|
|
log_debug "registered cleanup: ${cmd}"
|
|
return 0
|
|
}
|
|
|
|
cleanup_pop() {
|
|
local stack_size=${#CLEANUP_STACK[@]}
|
|
|
|
if [[ $stack_size -eq 0 ]]; then
|
|
log_warn "cleanup_pop: Warning - cleanup stack is empty"
|
|
return 1
|
|
fi
|
|
|
|
# Get the last command (top of stack)
|
|
local last_idx=$((stack_size - 1))
|
|
local cmd="${CLEANUP_STACK[$last_idx]}"
|
|
|
|
# Remove it from the stack
|
|
unset 'CLEANUP_STACK[$last_idx]'
|
|
|
|
# Execute the command
|
|
log_debug "cleanup_pop: Executing: $cmd"
|
|
execute "${cmd}"
|
|
local ret=$?
|
|
|
|
if [[ $ret -ne 0 ]]; then
|
|
log_warn "cleanup_pop: Warning - command failed with exit code $ret"
|
|
fi
|
|
|
|
return $ret
|
|
}
|
|
|
|
cleanup_run() {
|
|
local stack_size=${#CLEANUP_STACK[@]}
|
|
local failed=0
|
|
|
|
if [[ "${stack_size}" -eq 0 ]]; then
|
|
log_debug "cleanup_run: No cleanup commands to execute"
|
|
return 0
|
|
fi
|
|
|
|
log_debug "cleanup_run: Executing ${stack_size} cleanup command(s)"
|
|
|
|
# Execute commands in reverse order (LIFO)
|
|
while [[ ${#CLEANUP_STACK[@]} -gt 0 ]]; do
|
|
if ! cleanup_pop; then
|
|
((failed++))
|
|
fi
|
|
done
|
|
|
|
if [[ "${failed}" -gt 0 ]]; then
|
|
log_warn "cleanup_run: Warning - ${failed} command(s) failed"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
cleanup_clear() {
|
|
local stack_size=${#CLEANUP_STACK[@]}
|
|
CLEANUP_STACK=()
|
|
log_debug "cleanup_clear: Cleared $stack_size command(s) from stack"
|
|
return 0
|
|
}
|
|
|
|
cleanup_show() {
|
|
local stack_size=${#CLEANUP_STACK[@]}
|
|
|
|
if [[ $stack_size -eq 0 ]]; then
|
|
log_info "Cleanup stack is empty"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Cleanup stack ($stack_size command(s)):"
|
|
local idx=0
|
|
for cmd in "${CLEANUP_STACK[@]}"; do
|
|
log_info " [$idx] $cmd"
|
|
((idx++))
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
cleanup() {
|
|
local rc=${1:-1}
|
|
local stack_size=${#CLEANUP_STACK[@]}
|
|
|
|
trap - ERR EXIT PIPE
|
|
|
|
if [[ "${stack_size}" -gt 0 ]]; then
|
|
cleanup_run
|
|
log_info "Cleanup finished"
|
|
fi
|
|
|
|
if [[ "${rc}" -ne 0 ]]; then
|
|
exit "${rc}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# LOGGING FUNCTIONS
|
|
#
|
|
|
|
# Configuration for logger utility
|
|
LOGGER_TAG="pvics"
|
|
LOGGER_FACILITY="user"
|
|
|
|
log_init() {
|
|
logger -t "${LOGGER_TAG}" -p "${LOGGER_FACILITY}.info" "Logging initialized"
|
|
return 0
|
|
}
|
|
|
|
log_debug() {
|
|
local msg="$*"
|
|
logger -t "${LOGGER_TAG}" -p "${LOGGER_FACILITY}.debug" "${msg}"
|
|
return 0
|
|
}
|
|
|
|
log_info() {
|
|
local msg="$*"
|
|
echo "[ INFO ] ${msg}"
|
|
logger -t "${LOGGER_TAG}" -p "${LOGGER_FACILITY}.info" "${msg}"
|
|
return 0
|
|
}
|
|
|
|
log_warn() {
|
|
local msg="$*"
|
|
echo "[ WARN ] ${msg}"
|
|
logger -t "${LOGGER_TAG}" -p "${LOGGER_FACILITY}.warning" "${msg}"
|
|
return 0
|
|
}
|
|
|
|
log_error() {
|
|
local msg="$*"
|
|
echo "[ ERR ] ${msg}" >&2
|
|
logger -t "${LOGGER_TAG}" -p "${LOGGER_FACILITY}.err" "${msg}"
|
|
return 1
|
|
}
|
|
|
|
log_cleanup() {
|
|
logger -t "${LOGGER_TAG}" -p "${LOGGER_FACILITY}.info" "Logging cleanup complete"
|
|
return 0
|
|
}
|
|
|
|
setup_logger() {
|
|
log_init
|
|
cleanup_push log_cleanup
|
|
return 0
|
|
}
|
|
|
|
warn_overwrite() {
|
|
local file
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
log_error "internal error"
|
|
fi
|
|
|
|
file=$1
|
|
|
|
if [[ -e "${file}" ]]; then
|
|
log_warn "${file} already exists and may be overwritten"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
err_noexist() {
|
|
local file
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
file=$1
|
|
|
|
if [[ ! -e "${file}" ]]; then
|
|
log_error "${file} does not exist"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# PREREQ FUNCTIONS
|
|
#
|
|
|
|
check_pvsecret_ebc() {
|
|
local pvsecret_version
|
|
|
|
# Run pvsecret with --version --verbose to check for EBC support
|
|
pvsecret_version=$(pvsecret --version --verbose 2>&1 || true)
|
|
|
|
if ! echo "${pvsecret_version}" | grep -q "+ebc"; then
|
|
log_error "pvsecret does not support EBC feature. Please use a version compiled with EBC support."
|
|
fi
|
|
|
|
log_info "pvsecret supports EBC feature"
|
|
|
|
return 0
|
|
}
|
|
|
|
check_prereqs_convert() {
|
|
local tools
|
|
|
|
tools=(
|
|
"blkid"
|
|
"lsblk"
|
|
"lsinitrd"
|
|
"modprobe"
|
|
"mount"
|
|
"mountpoint"
|
|
"pvextract-hdr"
|
|
"pvimg"
|
|
"printf"
|
|
"qemu-nbd"
|
|
"systemctl"
|
|
"umount"
|
|
)
|
|
|
|
for tool in "${tools[@]}"; do
|
|
require_command "${tool}"
|
|
done
|
|
|
|
# Check for virtualization tools based on architecture
|
|
if [[ "${ARCH}" == "s390x" ]]; then
|
|
require_command "virt-install"
|
|
require_command "virsh"
|
|
else
|
|
require_command "qemu-system-s390x"
|
|
require_command "pgrep"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
check_prereqs_encrypt() {
|
|
local tools
|
|
|
|
tools=(
|
|
"blkid"
|
|
"cryptsetup"
|
|
"hexdump"
|
|
"lsblk"
|
|
"mktemp"
|
|
"modprobe"
|
|
"mount"
|
|
"mountpoint"
|
|
"pvebc"
|
|
"pvextract-hdr"
|
|
"pvsecret"
|
|
"qemu-img"
|
|
"qemu-nbd"
|
|
"sha256sum"
|
|
"strings"
|
|
"umount"
|
|
"xxd"
|
|
"virt-resize"
|
|
)
|
|
|
|
for tool in "${tools[@]}"; do
|
|
require_command "${tool}"
|
|
done
|
|
|
|
# Check if pvsecret supports EBC feature
|
|
check_pvsecret_ebc
|
|
|
|
return 0
|
|
}
|
|
|
|
check_prereqs_list() {
|
|
local tools
|
|
|
|
tools=(
|
|
"blkid"
|
|
"lsblk"
|
|
"lsinitrd"
|
|
"modprobe"
|
|
"mountpoint"
|
|
"mount"
|
|
"qemu-nbd"
|
|
"sha256sum"
|
|
"umount"
|
|
)
|
|
|
|
for tool in "${tools[@]}"; do
|
|
require_command "${tool}"
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
check_prereqs() {
|
|
local tools
|
|
log_info "checking for required commands"
|
|
|
|
# always needed prereqs
|
|
tools=(
|
|
"grep"
|
|
"mkdir"
|
|
"uname"
|
|
"uuidgen"
|
|
"yq"
|
|
)
|
|
for tool in "${tools[@]}"; do
|
|
require_command "${tool}"
|
|
done
|
|
|
|
if [[ "${ACTION}" == "convert" ]]; then
|
|
check_prereqs_convert
|
|
elif [[ "${ACTION}" == "encrypt" ]]; then
|
|
check_prereqs_encrypt
|
|
elif [[ "${ACTION}" == "full" ]]; then
|
|
check_prereqs_convert
|
|
check_prereqs_encrypt
|
|
elif [[ "${ACTION}" == "list" ]]; then
|
|
check_prereqs_list
|
|
else
|
|
log_error "internal error"
|
|
fi
|
|
|
|
log_info "all required commands found, continuing..."
|
|
|
|
return 0
|
|
}
|
|
|
|
require_command() {
|
|
local cmd
|
|
|
|
cmd="$1"
|
|
|
|
command -v "${cmd}" >/dev/null 2>&1 || \
|
|
log_error "${cmd} required but not installed."
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# HELPER FUNCTIONS
|
|
#
|
|
|
|
get_free_nbd() {
|
|
local free
|
|
|
|
test -b /dev/nbd0 || modprobe nbd
|
|
|
|
free=$(lsblk --output NAME,SIZE \
|
|
| grep nbd \
|
|
| grep 0B \
|
|
| grep -oE "nbd[0-9]+" \
|
|
| head -n 1)
|
|
|
|
if [[ -z "${free}" ]]; then
|
|
log_error "no free NBD found"
|
|
fi
|
|
|
|
free="/dev/${free}"
|
|
|
|
log_info "found free NBD: ${free}"
|
|
|
|
NBD="${free}"
|
|
|
|
return 0
|
|
}
|
|
|
|
execute() {
|
|
log_debug "$@"
|
|
# shellcheck disable=SC2294
|
|
eval "$@"
|
|
|
|
return $?
|
|
}
|
|
|
|
get_arch() {
|
|
ARCH=$(uname -m)
|
|
if [[ "${ARCH}" == "" ]]; then
|
|
log_error "Architecture not officially supported"
|
|
return 1
|
|
else
|
|
if [[ "${ARCH}" != "s390x" && "${ARCH}" != "x86_64" ]]; then
|
|
log_warn "the detected host architecture ${ARCH} is not one of the officially supported architectures (s390x, x86_64)"
|
|
else
|
|
log_info "found supported architecture ${ARCH}"
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# $1 path to image
|
|
# Returns: Sets global NBD variable
|
|
connect_nbd() {
|
|
local image i nbd_size
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
log_error "internal error"
|
|
fi
|
|
|
|
image="$1"
|
|
|
|
get_free_nbd
|
|
|
|
execute qemu-nbd --connect="${NBD}" "${image}"
|
|
|
|
log_info "waiting for qemu-nbd:"
|
|
for i in {1..10}; do
|
|
test -b "${NBD}p1" && break
|
|
echo -n "."
|
|
sleep 0.1
|
|
done
|
|
echo ""
|
|
|
|
if [[ -b "${NBD}p1" ]]; then
|
|
# Verify the NBD device is actually connected to our image
|
|
nbd_size=$(lsblk --bytes --noheadings --output SIZE "${NBD}" 2>/dev/null || echo "0")
|
|
if [[ "${nbd_size}" == "0" ]]; then
|
|
log_error "NBD device ${NBD} appears to be claimed by another process"
|
|
fi
|
|
cleanup_push disconnect_nbd
|
|
else
|
|
log_error "unable to connect ${image} to ${NBD}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
disconnect_nbd() {
|
|
test -z "${NBD}" && return 0
|
|
|
|
execute "qemu-nbd --disconnect '${NBD}' 2>/dev/null"
|
|
unset NBD
|
|
|
|
return 0
|
|
}
|
|
|
|
# $1 partition device (e.g., "${NBD}p${BOOT_PARTNUM}")
|
|
# $2 mount point path
|
|
# $3 optional: mount options (e.g., "-r" for read-only)
|
|
mount_partition() {
|
|
local partition mount_point mount_opts
|
|
|
|
if [[ $# -lt 2 ]]; then
|
|
log_error "internal error"
|
|
fi
|
|
|
|
partition="$1"
|
|
mount_point="$2"
|
|
mount_opts="${3:-}"
|
|
|
|
execute mount "${mount_opts}" "${partition}" "${mount_point}"
|
|
|
|
if mountpoint -q "${mount_point}"; then
|
|
cleanup_push umount_partition "${mount_point}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# $1 mountpoint
|
|
umount_partition() {
|
|
local mntp="${1:?no mountpoint specified}"
|
|
|
|
if mountpoint -q "${mntp}"; then
|
|
umount "${mntp}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# $1 path to asr
|
|
# $2 path to toc.pol
|
|
add_asr_to_toc() {
|
|
local toc asr
|
|
|
|
if [[ $# -lt 2 ]]; then
|
|
log_error "internal error"
|
|
fi
|
|
|
|
asr="$1"
|
|
toc="$2"
|
|
|
|
log_info "adding Add-Secret-Request ${asr} to TOC policy ${toc}"
|
|
|
|
xxd -p -s -16 "${asr}" >> "${toc}"
|
|
|
|
return 0
|
|
}
|
|
|
|
# $1 partition label
|
|
# $2 path to image
|
|
get_partnum() {
|
|
local partition image p partnum
|
|
|
|
if [[ $# -lt 2 ]]; then
|
|
log_error "internal error"
|
|
fi
|
|
|
|
partition="$1"
|
|
image="$2"
|
|
|
|
connect_nbd "${image}"
|
|
|
|
# get partitions
|
|
for p in "${NBD}"p*; do
|
|
|
|
if [[ $(blkid | grep -E "^${p}" | grep -oE "LABEL=\"[a-zA-Z]*\"" | cut -d '"' -f 2) == "${partition}" ]]; then
|
|
partnum="$(blkid | grep -oE "^${p}" | cut -d 'p' -f 2)"
|
|
log_info "Found partition with label ${partition} at partition ${partnum}"
|
|
|
|
cleanup_pop # Disconnect and remove from stack
|
|
PARTNUM="${partnum}"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
cleanup_pop # Disconnect and remove from stack
|
|
PARTNUM=""
|
|
|
|
return 0
|
|
}
|
|
|
|
# Build pv_cmd array for pvimg/pvsecret commands
|
|
# $1: hkds - space-separated list of HKD paths
|
|
# $2: no_verify - "true" or "false"
|
|
# $3: certs - space-separated list of certificate paths (optional if no_verify=true)
|
|
# $4: crls - space-separated list of CRL paths (optional if no_verify=true)
|
|
# $5: rootca - path to root CA (optional if no_verify=true)
|
|
# $6: offline - "true" or "false" (optional if no_verify=true)
|
|
# Returns: Sets global pv_cmd array
|
|
build_pv_cmd() {
|
|
local hkds no_verify certs crls rootca offline
|
|
local h c files f
|
|
|
|
if [[ $# -lt 2 ]]; then
|
|
log_error "internal error: build_pv_cmd requires at least 2 arguments"
|
|
fi
|
|
|
|
hkds="$1"
|
|
no_verify="$2"
|
|
certs="${3:-}"
|
|
crls="${4:-}"
|
|
rootca="${5:-}"
|
|
offline="${6:-false}"
|
|
|
|
# Initialize pv_cmd array
|
|
pv_cmd=()
|
|
|
|
# Add HKD files
|
|
for h in ${hkds}; do
|
|
files=$(ls "${h}")
|
|
for f in ${files}; do
|
|
pv_cmd+=("--host-key-document" "${f}")
|
|
done
|
|
done
|
|
|
|
# If verification is enabled
|
|
if [[ "${no_verify}" == "false" ]]; then
|
|
# Add certificates
|
|
for c in ${certs}; do
|
|
files=$(ls "${c}")
|
|
for f in ${files}; do
|
|
pv_cmd+=("--cert" "${f}")
|
|
done
|
|
done
|
|
|
|
# Add CRLs
|
|
for c in ${crls}; do
|
|
files=$(ls "${c}")
|
|
for f in ${files}; do
|
|
pv_cmd+=("--crl" "${f}")
|
|
done
|
|
done
|
|
|
|
# Add root CA
|
|
pv_cmd+=("--root-ca" "${rootca}")
|
|
|
|
# Add offline flag if requested
|
|
if [[ "${offline}" == "true" ]]; then
|
|
pv_cmd+=("--offline")
|
|
fi
|
|
else
|
|
# Verification disabled
|
|
pv_cmd+=("--no-verify")
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# $1 kernel
|
|
# $2 initramfs
|
|
# $3 kernel parameter
|
|
# $4 output
|
|
# $5 yaml configuration
|
|
#
|
|
# If this function is called by another script that sourced this one the yaml configuration has to
|
|
# be supplied as many variables will not be set otherwise
|
|
build_sel_image() {
|
|
if [[ $# -lt 4 ]]; then
|
|
return 1
|
|
fi
|
|
|
|
i="$1"
|
|
r="$2"
|
|
p="$3"
|
|
o="$4"
|
|
err_noexist "${i}"
|
|
err_noexist "${r}"
|
|
err_noexist "${p}"
|
|
|
|
if [[ $# -gt 4 ]]; then
|
|
CONFIG_FILE="$5"
|
|
err_noexist "${CONFIG_FILE}"
|
|
parse_yaml_pv
|
|
fi
|
|
|
|
log_info "The following configuration is used to generate the SEL image:"
|
|
log_info "kernel: ${i}"
|
|
log_info "initramfs: ${r}"
|
|
log_info "kernel parameter: $(cat "${p}")"
|
|
|
|
# Build pv_cmd array using helper function
|
|
build_pv_cmd "${hkds}" "${NO_VERIFY}" "${certs}" "${crls}" "${rootca}" "${offline}"
|
|
|
|
# create SEL image
|
|
pvimg_cmd=("pvimg" "create")
|
|
pvimg_cmd+=("--kernel" "${i}")
|
|
pvimg_cmd+=("--ramdisk" "${r}")
|
|
pvimg_cmd+=("--parmfile" "${p}")
|
|
pvimg_cmd+=("--output" "${o}")
|
|
pvimg_cmd+=("${pv_cmd[@]}")
|
|
if [[ "${NO_EBC:-false}" != "true" ]]; then
|
|
pvimg_cmd+=("--enable-cck-update" "--quiet" "--disable-image-encryption")
|
|
fi
|
|
pvimg_cmd+=("${PVIMG_USER_OPTS}")
|
|
|
|
execute "${pvimg_cmd[@]}"
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# USAGE FUNCTIONS
|
|
#
|
|
|
|
usage() {
|
|
local action required_options
|
|
|
|
action="${ACTION}"
|
|
required_options="--image <FILE>"
|
|
|
|
if [[ "${action}" == "list" ]]; then
|
|
echo "
|
|
List all available boot loader entries of a s390x guest image and its partitions."
|
|
elif [[ "${action}" == "convert" ]]; then
|
|
echo "
|
|
Convert a s390x guest to a Secure Execution for Linux guest."
|
|
elif [[ "${action}" == "encrypt" ]]; then
|
|
echo "
|
|
Encrypt the root filesystem of a s390x Secure Execution for Linux guest."
|
|
elif [[ "${action}" == "" || "${action}" == "full" ]]; then
|
|
if [[ "${action}" == "" ]]; then
|
|
action="[ACTION]"
|
|
fi
|
|
echo "
|
|
Convert a s390x guest to a Secure Execution for Linux guest with encrypted root
|
|
filesystem."
|
|
fi
|
|
|
|
if [[ "${action}" == "convert" || "${action}" == "encrypt" || "${action}" == "full" ]]; then
|
|
required_options="${required_options} --config <FILE>"
|
|
fi
|
|
|
|
echo "
|
|
Usage: $0 ${action} [OPTIONS] ${required_options}"
|
|
|
|
if [[ "${action}" == "[ACTION]" ]]; then
|
|
echo "
|
|
Actions:
|
|
list List boot entries of a guest
|
|
convert Convert the guest to Secure Execution for Linux guest
|
|
encrypt Encrypt the root filesystem of the guest
|
|
full Convert the guest to SEL guest and encrypt the root fs"
|
|
fi
|
|
|
|
echo "
|
|
Options:
|
|
-h, --help Show this help text"
|
|
if [[ "${action}" == "[ACTION]" ]]; then
|
|
echo -e "\t-c, --config\t\tConfiguration yaml file"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# CLI FUNCTIONS
|
|
#
|
|
|
|
cli() {
|
|
local temp
|
|
|
|
if [[ "$#" -lt 1 ]]; then
|
|
usage
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$1" == "convert" ]]; then
|
|
ACTION="convert"
|
|
shift
|
|
elif [[ "$1" == "encrypt" ]]; then
|
|
ACTION="encrypt"
|
|
shift
|
|
elif [[ "$1" == "full" ]]; then
|
|
ACTION="full"
|
|
shift
|
|
elif [[ "$1" == "list" ]]; then
|
|
ACTION="list"
|
|
shift
|
|
fi
|
|
|
|
temp=$(getopt -o 'hc:i:' --long 'help,config:,image:' -- "$@")
|
|
if ! eval set -- "$temp"; then
|
|
usage
|
|
return 1
|
|
fi
|
|
|
|
eval set -- "$temp"
|
|
unset temp
|
|
|
|
while true; do
|
|
case "$1" in
|
|
'-h'|'--help')
|
|
usage
|
|
exit 0
|
|
;;
|
|
'-c'|'--config')
|
|
case "$2" in
|
|
'')
|
|
usage
|
|
exit 1
|
|
;;
|
|
*)
|
|
CONFIG_FILE="$2"
|
|
;;
|
|
esac
|
|
shift 2
|
|
continue
|
|
;;
|
|
'-i'|'--image')
|
|
case "$2" in
|
|
'')
|
|
usage
|
|
exit 1
|
|
;;
|
|
*)
|
|
IMAGE="$2"
|
|
;;
|
|
esac
|
|
shift 2
|
|
continue
|
|
;;
|
|
'--')
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z ${IMAGE} ]]; then
|
|
usage
|
|
exit 1
|
|
elif [[ ! -f ${IMAGE} ]]; then
|
|
log_error "base image ${IMAGE} does not exist"
|
|
|
|
return 1
|
|
fi
|
|
|
|
if [[ -z ${CONFIG_FILE} && ${ACTION} != "list" ]]; then
|
|
usage
|
|
exit 1
|
|
elif [[ -n ${CONFIG_FILE} && ! -f ${CONFIG_FILE} ]]; then
|
|
log_error "config file ${CONFIG_FILE} does not exist"
|
|
|
|
return 1
|
|
fi
|
|
|
|
if [[ ${ACTION} != "convert" && ${ACTION} != "encrypt" && ${ACTION} != "full" && ${ACTION} != "list" ]]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# YAML FUNCTIONS
|
|
#
|
|
|
|
parse_yaml_pv() {
|
|
local files f hkd cert crl
|
|
|
|
NO_VERIFY=$(yaml_get_opt ".no-verify")
|
|
if [[ "${NO_VERIFY}" == "unset" ]]; then
|
|
NO_VERIFY=false
|
|
fi
|
|
|
|
PVIMG_USER_OPTS="$(yaml_get_opt ".convert.pvimg-create-options")"
|
|
if [[ "${PVIMG_USER_OPTS}" == "unset" && "${NO_EBC}" != "true" ]]; then
|
|
PVIMG_USER_OPTS="--enable-pckmo-hmac"
|
|
elif [[ "${PVIMG_USER_OPTS}" == "unset" ]]; then
|
|
PVIMG_USER_OPTS=""
|
|
fi
|
|
|
|
HKDs="$(yaml_get_req ".hkds[]")"
|
|
for hkd in ${HKDs}; do
|
|
files=$(ls "${hkd}")
|
|
for f in ${files}; do
|
|
err_noexist "${f}"
|
|
done
|
|
done
|
|
|
|
if [[ "${NO_VERIFY}" == "false" ]]; then
|
|
CC_CERTS="$(yaml_get_req ".certificate-chain.certs[]")"
|
|
for cert in ${CC_CERTS}; do
|
|
files=$(ls "${cert}")
|
|
for f in ${files}; do
|
|
err_noexist "${f}"
|
|
done
|
|
done
|
|
CC_CRLS="$(yaml_get_req ".certificate-chain.crls[]")"
|
|
for crl in ${CC_CRLS}; do
|
|
files=$(ls "${crl}")
|
|
for f in ${files}; do
|
|
err_noexist "${f}"
|
|
done
|
|
done
|
|
CC_ROOTCA=$(yaml_get_req ".certificate-chain.root-ca")
|
|
err_noexist "${CC_ROOTCA}"
|
|
|
|
CC_OFFLINE=$(yaml_get_opt ".certificate-chain.offline")
|
|
if [[ "${CC_OFFLINE}" == "unset" ]]; then
|
|
CC_OFFLINE="false"
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
parse_yaml() {
|
|
# general required configuration settings
|
|
NO_EBC=$(yaml_get_opt ".no-ebc")
|
|
if [[ ${NO_EBC} == "unset" ]]; then
|
|
NO_EBC=false
|
|
fi
|
|
|
|
parse_yaml_pv
|
|
|
|
OUT=$(yaml_get_req ".out")
|
|
err_noexist "${OUT}"
|
|
|
|
if [[ ${ACTION} == "convert" || ${ACTION} == "full" ]]; then
|
|
C_BOOT_LOADER_ENTRY=$(yaml_get_req ".convert.boot-loader-entry")
|
|
C_SEL_KERNEL_PARAMETER=$(yaml_get_opt ".convert.sel-kernel-parameter")
|
|
fi
|
|
|
|
if [[ ${ACTION} == "encrypt" || ${ACTION} == "full" ]]; then
|
|
E_CCK=$(yaml_get_opt ".encrypt.cck")
|
|
if [[ ${E_CCK} != "unset" ]]; then
|
|
err_noexist "${E_CCK}"
|
|
fi
|
|
E_LUKS_RFS_KEY=$(yaml_get_opt ".encrypt.luks-key")
|
|
if [[ ${E_LUKS_RFS_KEY} != "unset" ]]; then
|
|
err_noexist "${E_LUKS_RFS_KEY}"
|
|
fi
|
|
E_LUKS_PASSPHRASE=$(yaml_get_opt ".encrypt.luks-passphrase")
|
|
if [[ ${E_LUKS_PASSPHRASE} != "unset" ]]; then
|
|
err_noexist "${E_LUKS_PASSPHRASE}"
|
|
fi
|
|
E_LUKS_RFS_KEY_ASR_NAME=$(yaml_get_opt ".encrypt.luks-key-asr-name")
|
|
E_LUKS_KEY_SIZE=$(yaml_get_opt ".encrypt.luks-key-size")
|
|
if [[ "${E_LUKS_KEY_SIZE}" == "unset" ]]; then
|
|
E_LUKS_KEY_SIZE="512"
|
|
elif [[ "${E_LUKS_KEY_SIZE}" != "256" && "${E_LUKS_KEY_SIZE}" != "512" ]]; then
|
|
log_error "invalid .encrypt.luks-key-size of ${E_LUKS_KEY_SIZE} != 256 or 512"
|
|
fi
|
|
E_ADD_SECRET_REQUESTS="$(yaml_get_opt ".encrypt.add-secret-requests[]")"
|
|
if [[ "${E_ADD_SECRET_REQUESTS}" != "unset" ]]; then
|
|
for asr in ${E_ADD_SECRET_REQUESTS}; do
|
|
files=$(ls "${asr}")
|
|
for f in ${files}; do
|
|
err_noexist "${f}"
|
|
done
|
|
done
|
|
|
|
# extension secret is now required
|
|
E_EXTENSION_SECRET=$(yaml_get_req ".encrypt.extension-secret")
|
|
err_noexist "${E_EXTENSION_SECRET}"
|
|
else
|
|
E_EXTENSION_SECRET=$(yaml_get_opt ".encrypt.extension-secret")
|
|
if [[ ${E_EXTENSION_SECRET} != "unset" ]]; then
|
|
err_noexist "${E_EXTENSION_SECRET}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
yaml_get_req() {
|
|
yaml_get "$@" true
|
|
|
|
return 0
|
|
}
|
|
|
|
yaml_get_opt() {
|
|
yaml_get "$@" false
|
|
|
|
return 0
|
|
}
|
|
|
|
yaml_get() {
|
|
local key required ret
|
|
|
|
if [[ $# -lt 2 ]]; then
|
|
log_error "internal error"
|
|
fi
|
|
|
|
key="$1"
|
|
required="$2"
|
|
|
|
ret="$(yq e -r "${key} // \"unset\"" "${CONFIG_FILE}")"
|
|
|
|
if [[ "${required}" == "true" && "${ret}" == "unset" ]]; then
|
|
log_error "key ${key} not specified in ${CONFIG_FILE} but required"
|
|
fi
|
|
|
|
echo "${ret}"
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# ACTION FUNCTIONS
|
|
#
|
|
|
|
list() {
|
|
local image temp_boot ble_dir f title kernel initramfs kernel_params valid
|
|
|
|
image="${IMAGE}"
|
|
|
|
# mount guest
|
|
connect_nbd "${image}"
|
|
|
|
temp_boot="${TEMP_DIR}/boot"
|
|
mount_partition "${NBD}p${BOOT_PARTNUM}" "${temp_boot}" "-r"
|
|
|
|
ble_dir="${temp_boot}/loader/entries"
|
|
if [[ ! -d "${ble_dir}" ]]; then
|
|
log_error "directory ${ble_dir} does not exist, unable to find boot loader entries"
|
|
fi
|
|
|
|
log_info "Found the following boot loader entries:"
|
|
for f in "${ble_dir}"/*.conf; do
|
|
|
|
title="$(grep -E "^title" "${f}" | grep -oE " .*" | grep -oE "[^ ]*")"
|
|
kernel="$(grep -E "^linux" "${f}" | cut -d ' ' -f 2)"
|
|
initramfs="$(grep -E "^initrd" "${f}" | cut -d ' ' -f 2)"
|
|
kernel_params="$(grep -E "^options" "${f}" | cut -d ' ' -f 2-)"
|
|
|
|
if echo "${kernel_params}" | grep -q "root=LABEL=root"; then
|
|
valid="valid"
|
|
elif echo "${kernel_params}" | grep -q "root=/dev/disk/by-label/root"; then
|
|
valid="valid"
|
|
else
|
|
valid="invalid"
|
|
fi
|
|
|
|
if ! lsinitrd "${TEMP_DIR}${initramfs}" | grep -q "sel-ebc.target"; then
|
|
valid="invalid"
|
|
fi
|
|
|
|
echo "
|
|
title: \"${title}\" [ ${valid} ]:
|
|
kernel: ${kernel}
|
|
SHA256 ($(sha256sum "${TEMP_DIR}${kernel}" | cut -d ' ' -f 1))
|
|
initramfs: ${initramfs}
|
|
SHA256 ($(sha256sum "${TEMP_DIR}${initramfs}" | cut -d ' ' -f 1))
|
|
kernel_params: ${kernel_params}
|
|
"
|
|
done
|
|
|
|
cleanup_pop # Unmount temp_boot
|
|
cleanup_pop # Disconnect NBD
|
|
|
|
return 0
|
|
}
|
|
|
|
# Generate SEL guest from existing guest
|
|
convert() {
|
|
# setup variables
|
|
local rootca offline crls certs hkds kernel_parameter
|
|
local kernel_parameter_file sel_kernel_parameter initramfs kernel dest
|
|
local image temp_boot temp_root sel_header guest_name
|
|
local pvimg_cmd pv_cmd ble_array b
|
|
|
|
# initialize
|
|
image=${IMAGE}
|
|
ble=${C_BOOT_LOADER_ENTRY}
|
|
kernel_parameter_file="${TEMP_DIR}/kernel_parameter"
|
|
hkds=${HKDs}
|
|
# optional
|
|
sel_kernel_parameter=${C_SEL_KERNEL_PARAMETER}
|
|
certs="${CC_CERTS}"
|
|
crls="${CC_CRLS}"
|
|
rootca="${CC_ROOTCA}"
|
|
offline="${CC_OFFLINE}"
|
|
|
|
# check for files
|
|
err_noexist "${image}"
|
|
|
|
if [[ ${sel_kernel_parameter} == "unset" ]]; then
|
|
sel_kernel_parameter=""
|
|
fi
|
|
|
|
# create copy for safety
|
|
log_info "copying image; depending on size this may take some time"
|
|
execute cp "${image}" "${TEMP_DIR}/image.copy"
|
|
image="${TEMP_DIR}/image.copy"
|
|
|
|
# mount guest
|
|
connect_nbd "${image}"
|
|
temp_boot="${TEMP_DIR}/boot"
|
|
temp_root="${TEMP_DIR}/root"
|
|
mount_partition "${NBD}p${BOOT_PARTNUM}" "${temp_boot}"
|
|
mount_partition "${NBD}p${ROOT_PARTNUM}" "${temp_root}"
|
|
|
|
# get kernel, initramfs, and kernel parameter from boot loader entry
|
|
for f in "${temp_boot}"/loader/entries/*.conf; do
|
|
|
|
title="$(grep -E "^title" "${f}" | cut -d ' ' -f 3)"
|
|
if [[ "${title}" != "${ble}" ]]; then
|
|
continue
|
|
fi
|
|
|
|
kernel="$(grep -E "^linux" "${f}" | cut -d ' ' -f 2)"
|
|
initramfs="$(grep -E "^initrd" "${f}" | cut -d ' ' -f 2)"
|
|
if [[ "${NO_EBC}" != "true" ]]; then
|
|
kernel_parameter="rd.sel-ebc "
|
|
fi
|
|
kernel_parameter+="$(grep -E "^options" "${f}" | cut -d ' ' -f 2-) ${sel_kernel_parameter}"
|
|
break
|
|
done
|
|
if [[ "${NO_EBC}" != "true" ]]; then
|
|
if [[ $(echo "${kernel_parameter}" | grep "root=LABEL=root") == "" \
|
|
&& $? == "1" \
|
|
&& $(echo "${kernel_parameter}" | grep "root=/dev/disk/by-label/root") == "" \
|
|
&& $? == "1" ]]; then
|
|
log_error "root partition has to be identified using the LABEL root in the kernel parameter line"
|
|
fi
|
|
if [[ $(lsinitrd "${TEMP_DIR}${initramfs}" | grep "/usr/lib/systemd/system/sel-ebc.target") == "" \
|
|
&& $? == "1" ]]; then
|
|
log_error "initramfs ${initramfs} does not contain IBMs SEL EBC dracut module from s390-tools"
|
|
fi
|
|
fi
|
|
|
|
# check existence
|
|
echo "${kernel_parameter}" > "${kernel_parameter_file}"
|
|
|
|
build_sel_image "${TEMP_DIR}${kernel}" "${TEMP_DIR}${initramfs}" "${kernel_parameter_file}" "${temp_boot}/sel-ebc.img"
|
|
|
|
# extract header from SEL image
|
|
sel_header="${OUT}/selhdr.bin"
|
|
execute pvextract-hdr -o "${sel_header}" "${temp_boot}/sel-ebc.img"
|
|
|
|
# make old boot loader entries invalid (zipl will only find files ending in .conf)
|
|
mapfile -t ble_array < <(ls "${temp_boot}/loader/entries/")
|
|
for b in "${ble_array[@]}"; do
|
|
execute mv "${temp_boot}/loader/entries/${b}" "${temp_boot}/loader/entries/${b}.old"
|
|
done
|
|
|
|
# create boot entry for SEL
|
|
printf 'title sel-ebc\nlinux /boot/sel-ebc.img\n' > "${temp_boot}/loader/entries/sel-ebc.conf"
|
|
|
|
# inject systemd service to run zipl
|
|
local service_name="ebczipl.service"
|
|
local systemd_dir="/usr/lib/systemd/system"
|
|
local link
|
|
cat > "${temp_root}${systemd_dir}/${service_name}" << EOF
|
|
[Unit]
|
|
Description=call zipl and shutdown system
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=zipl
|
|
ExecStartPost=shutdown now
|
|
RemainAfterExit=yes
|
|
StandardOutput=file:/var/log/sel-ebc-zipl.log
|
|
StandardError=file:/var/log/sel-ebc-zipl.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
systemctl --root="${temp_root}" --no-reload enable "${service_name}"
|
|
|
|
# unmount mounted image
|
|
cleanup_pop # Unmount temp_root
|
|
cleanup_pop # Unmount temp_boot
|
|
cleanup_pop # Disconnect NBD
|
|
|
|
guest_name="sel-ebc-${UUID}"
|
|
|
|
# start guest
|
|
log_info "starting guest to run zipl"
|
|
if [[ "${ARCH}" == "s390x" ]]; then
|
|
execute virt-install \
|
|
--name "${guest_name}" \
|
|
--memory 2048 \
|
|
--vcpus 2 \
|
|
--disk path="${image}",format=qcow2 \
|
|
--import \
|
|
--network network=default,model=virtio \
|
|
--graphics none \
|
|
--console pty,target_type=serial \
|
|
--noautoconsole \
|
|
--cpu model=host-model,-deflate \
|
|
--osinfo detect=on,require=off > /dev/null
|
|
else
|
|
execute qemu-system-s390x \
|
|
-name "${guest_name}" \
|
|
-machine s390-ccw-virtio \
|
|
-cpu max \
|
|
-m 2048 \
|
|
-drive file="${image}",if=virtio,format=qcow2 \
|
|
-serial none \
|
|
-display none \
|
|
-daemonize \
|
|
-netdev user,id=net0 \
|
|
-device virtio-net-ccw,netdev=net0
|
|
fi
|
|
|
|
# shutdown and remove guest
|
|
if [[ "${ARCH}" == "s390x" ]]; then
|
|
log_info "waiting for ${guest_name} to shut down:"
|
|
for _ in {1..200}; do
|
|
execute virsh list --all | grep "${guest_name}" | grep "shut off" > /dev/null && break
|
|
echo -n "."
|
|
sleep 0.2
|
|
done
|
|
echo ""
|
|
|
|
execute virsh undefine "${guest_name}"
|
|
else
|
|
log_info "waiting for ${guest_name} to shut down:"
|
|
for _ in {1..200}; do
|
|
execute ! pgrep -af "qemu-system-s390x.*${guest_name}" >/dev/null && break
|
|
echo -n "."
|
|
sleep 2
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
# mount guest
|
|
connect_nbd "${image}"
|
|
temp_root="${TEMP_DIR}/root"
|
|
mount_partition "${NBD}p${ROOT_PARTNUM}" "${temp_root}"
|
|
|
|
# eject systemd service to run zipl
|
|
if [[ -f "${temp_root}${systemd_dir}/${service_name}" ]]; then
|
|
rm -f "${temp_root}${systemd_dir}/${service_name}"
|
|
fi
|
|
link="${temp_root}${systemd_dir}/multi-user.target.wants/${service_name}"
|
|
if [[ -h "${link}" ]]; then
|
|
rm -f "${link}"
|
|
fi
|
|
|
|
# unmount mounted image
|
|
cleanup_pop # Unmount temp_root
|
|
cleanup_pop # Disconnect NBD
|
|
|
|
# copy image to output directory
|
|
IMAGE="${OUT}/image.qcow2"
|
|
execute mv "${image}" "${IMAGE}"
|
|
|
|
return 0
|
|
}
|
|
|
|
# Encrypt root partition of SEL guest
|
|
encrypt() {
|
|
local image dest luks_dev_name passphrase luks_segment_key_size segment_key
|
|
local asr_name hkds cck asrs certs crls rootca offline extension_secret
|
|
local pvsecret_cmd_static pvsecret_cmd pv_cmd
|
|
local retr_sec_type luks_segment_key_bytes blob blob_file
|
|
local sector_size metadata_size keyslots_size temp_boot sics header
|
|
local image_resized root_part retr_sec_id dump key_size pv_out pv_in
|
|
local kcmdline files f asr
|
|
|
|
image="${IMAGE}"
|
|
dest="${OUT}/image.qcow2"
|
|
luks_dev_name="cryptroot"
|
|
passphrase="${E_LUKS_PASSPHRASE}"
|
|
luks_segment_key_size="${E_LUKS_KEY_SIZE}"
|
|
segment_key="${E_LUKS_RFS_KEY}"
|
|
asr_name="${E_LUKS_RFS_KEY_ASR_NAME}"
|
|
hkds="${HKDs}"
|
|
cck="${E_CCK}"
|
|
asrs="${E_ADD_SECRET_REQUESTS}"
|
|
certs="${CC_CERTS}"
|
|
crls="${CC_CRLS}"
|
|
rootca="${CC_ROOTCA}"
|
|
offline="${CC_OFFLINE}"
|
|
extension_secret="${E_EXTENSION_SECRET}"
|
|
pvsecret_cmd_static="pvsecret create "
|
|
|
|
if [[ "${NO_EBC}" == "true" ]]; then
|
|
log_info "EBC disabled, skipping encrypt"
|
|
return 0
|
|
fi
|
|
|
|
# set default LUKS key ASR name
|
|
if [[ "${asr_name}" == "unset" ]]; then
|
|
asr_name="rfs-luks-key"
|
|
fi
|
|
|
|
# generate random extension secret if none supplied
|
|
if [[ "${extension_secret}" == "unset" ]]; then
|
|
extension_secret="${OUT}/extension.secret"
|
|
execute dd if=/dev/random of="${extension_secret}" bs=32 count=1
|
|
log_info "generated new extension secret to ${extension_secret}"
|
|
fi
|
|
|
|
# generate random cck if none supplied
|
|
if [[ "${cck}" == "unset" ]]; then
|
|
cck="${OUT}/cck.key"
|
|
execute dd if=/dev/random of="${cck}" bs=32 count=1
|
|
log_info "generated new Customer-Communication-Key to ${cck}"
|
|
fi
|
|
|
|
# generate random luks passphrase if none supplied
|
|
if [[ "${passphrase}" == "unset" ]]; then
|
|
passphrase="${OUT}/passphrase"
|
|
execute dd if=/dev/random of="${passphrase}" bs=32 count=1
|
|
log_info "generated new LUKS passphrase to ${passphrase}"
|
|
fi
|
|
|
|
# check or set default LUKS key size
|
|
if [[ "${luks_segment_key_size}" == "unset" ]]; then
|
|
if [[ "${segment_key}" == "unset" ]]; then
|
|
luks_segment_key_size="512"
|
|
else
|
|
key_size="$(wc --bytes < "${segment_key}")"
|
|
if [[ "${key_size}" -lt "256" ]]; then
|
|
log_error "${segment_key} has to be at least 32 bytes"
|
|
elif [[ "${key_size}" -lt "512" ]]; then
|
|
luks_segment_key_size="256"
|
|
else
|
|
luks_segment_key_size="256"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# set retrievable secret type
|
|
if [[ "${luks_segment_key_size}" == "256" ]]; then
|
|
retr_sec_type="7"
|
|
elif [[ "${luks_segment_key_size}" == "512" ]]; then
|
|
retr_sec_type="8"
|
|
else
|
|
log_error "Cipher aes-xts-plain64 requires a key size of 256 or 512 bit!"
|
|
fi
|
|
|
|
# generate random LUKS key if none supplied
|
|
if [[ "${segment_key}" == "unset" ]]; then
|
|
luks_segment_key_bytes=$((luks_segment_key_size / 8))
|
|
|
|
segment_key="${OUT}/rfs.key"
|
|
execute dd if=/dev/random of="${segment_key}" bs="${luks_segment_key_bytes}" count=1
|
|
log_info "generated new LUKS key for root filesystem to ${segment_key}"
|
|
fi
|
|
|
|
# Increase QCOW2 disk size by 32MiB to allow additional space for LUKS2 header
|
|
log_info "increase size of qcow2 to fit the LUKS header"
|
|
execute qemu-img resize "${image}" +32M
|
|
# Increase partition size by 32MiB as well, but not the file system, as the LUKS2 encryption is around the filesystem
|
|
image_resized="${TEMP_DIR}/resized.qcow2"
|
|
execute cp "${image}" "${image_resized}"
|
|
execute virt-resize --expand "/dev/vda${ROOT_PARTNUM}" --no-expand-content "${image}" "${image_resized}"
|
|
# Remove smaller image, as we already made a copy
|
|
execute rm -f "${image}"
|
|
image="${image_resized}"
|
|
|
|
# move image to output and compress
|
|
# the longopt --compress to the short opt -c is broken
|
|
execute qemu-img convert --target-format qcow2 -c "${image}" "${dest}" --quiet
|
|
execute rm -f "${image}"
|
|
image="${dest}"
|
|
|
|
# mount guest image
|
|
connect_nbd "${image}"
|
|
root_part="${NBD}p${ROOT_PARTNUM}"
|
|
|
|
# mount boot partition
|
|
temp_boot="${TEMP_DIR}/boot"
|
|
mount_partition "${NBD}p${BOOT_PARTNUM}" "${temp_boot}"
|
|
|
|
# print kernel cmdline
|
|
if [[ -f "${temp_boot}/sel-ebc.img" ]]; then
|
|
kcmdline="$(strings "${temp_boot}/sel-ebc.img" | grep -E "^rd.sel-ebc")"
|
|
|
|
log_info "found the following kernel cmdline:"
|
|
log_info "${kcmdline}"
|
|
else
|
|
log_error "${temp_boot}/sel-ebc.img does not exist, unable to retrieve kernel cmdline"
|
|
fi
|
|
|
|
# Encrypt the root partition of the mounted guest
|
|
log_info "encrypting partition"
|
|
execute cryptsetup reencrypt --encrypt --reduce-device-size 32M \
|
|
--cipher aes-xts-plain64 --key-size "${luks_segment_key_size}" \
|
|
--volume-key-file "${segment_key}" --key-file "${passphrase}" "${root_part}" \
|
|
--batch-mode --label=${luks_dev_name}
|
|
log_info "done encrypting"
|
|
|
|
log_info "reformat LUKS device from aes to paes"
|
|
retr_sec_id=$(echo -n "${asr_name}" | sha256sum | cut -d " " -f 1)
|
|
|
|
blob="00000000 09000000 000${retr_sec_type}0000 ${retr_sec_id}"
|
|
blob_file="$(mktemp)"
|
|
echo "${blob}" | xxd -r -p > "${blob_file}"
|
|
printf "key blob: \n%s\n" "$(hexdump "${blob_file}")"
|
|
if [[ "$(wc -c "${blob_file}" | cut -d " " -f 1 )" != "44" ]]; then
|
|
log_error "size of ${blob_file} != 44"
|
|
fi
|
|
|
|
# parse LUKS header
|
|
dump=$(mktemp)
|
|
execute cryptsetup luksDump "${root_part}" > "${dump}"
|
|
sector_size=$(grep "sector:" "${dump}" | awk 'NR==1{print $2}')
|
|
metadata_size=$(grep "Metadata area:" "${dump}" | awk 'NR==1{print $3}')
|
|
keyslots_size=$(grep "Keyslots area:" "${dump}" | awk 'NR==1{print $3}')
|
|
|
|
# Reformat LUKS header of guest root fs from AES to PAES
|
|
execute cryptsetup luksFormat "${root_part}" --batch-mode \
|
|
--key-file "${passphrase}" \
|
|
--uuid "${UUID}" --cipher paes-xts-plain64 \
|
|
--sector-size "${sector_size}" \
|
|
--luks2-metadata-size "${metadata_size}" \
|
|
--luks2-keyslots-size "${keyslots_size}" \
|
|
--volume-key-file "${blob_file}" --key-size 352 \
|
|
--label=${luks_dev_name}
|
|
|
|
sics="${temp_boot}/sics"
|
|
if [[ ! -d "${sics}" ]]; then
|
|
execute mkdir "${sics}"
|
|
fi
|
|
|
|
# Build pv_cmd array using helper function
|
|
build_pv_cmd "${hkds}" "${NO_VERIFY}" "${certs}" "${crls}" "${rootca}" "${offline}"
|
|
|
|
# prepare pvsecret command
|
|
pvsecret_cmd_static=("pvsecret" "create")
|
|
pvsecret_cmd_static+=("${pv_cmd[@]}" "--extension-secret" "${extension_secret}" "--quiet")
|
|
header="${TEMP_DIR}/selhdr.bin"
|
|
execute pvextract-hdr -o "${header}" "${temp_boot}/sel-ebc.img"
|
|
pvsecret_cmd_static+=("--hdr" "${header}")
|
|
|
|
# generate ASR for LUKS segment key
|
|
pv_in="${segment_key}"
|
|
pv_out="${sics}/${asr_name}.asr"
|
|
pvsecret_cmd=("${pvsecret_cmd_static[@]}" "--toc-policy" "${sics}/toc.pol" "--output" "${pv_out}" "retrievable" "--secret" "${pv_in}" "--type" "aes-xts" "${asr_name}")
|
|
execute "${pvsecret_cmd[@]}"
|
|
log_info "generated Add-Secret-Request ${pv_out} from ${pv_in} containing the LUKS encryption key for the root filesystem"
|
|
|
|
# generate ASR for CCK
|
|
pv_in="${cck}"
|
|
pv_out="${sics}/cck.asr"
|
|
pvsecret_cmd=("${pvsecret_cmd_static[@]}" "--toc-policy" "${sics}/toc.pol" "--output" "${pv_out}" "update-cck" "--secret" "${pv_in}")
|
|
execute "${pvsecret_cmd[@]}"
|
|
log_info "generated Add-Secret-Request ${pv_out} from ${pv_in} containing the Customer-Communication-Key"
|
|
|
|
# generate ASR for LUKS passphrase
|
|
pv_out="${sics}/luks-rfs-passphrase.asr"
|
|
pv_in="${passphrase}"
|
|
pvsecret_cmd=("${pvsecret_cmd_static[@]}" "--toc-policy" "${sics}/toc.pol" "--output" "${pv_out}" "retrievable" "--secret" "${pv_in}" "--type" "plain" "luks-rfs-passphrase")
|
|
execute "${pvsecret_cmd[@]}"
|
|
log_info "generated Add-Secret-Request ${pv_out} from ${pv_in} containing the LUKS passphrase for the root filesystem"
|
|
|
|
# add additional ASRs to toc.pol
|
|
if [[ "${asrs}" != "unset" ]]; then
|
|
for asr in ${asrs}; do
|
|
files=$(ls "${asr}")
|
|
for f in ${files}; do
|
|
add_asr_to_toc "${f}" "${sics}/toc.pol"
|
|
execute cp "${f}" "${sics}/"
|
|
log_info "added user provided Add-Secret-Request ${f}"
|
|
done
|
|
done
|
|
fi
|
|
|
|
# generate toc.asr
|
|
pvsecret_cmd=("${pvsecret_cmd_static[@]}" "--output" "${sics}/toc.asr" "--policy" "toc.pol" "meta")
|
|
execute cp "${sics}/toc.pol" "toc.pol"
|
|
execute "${pvsecret_cmd[@]}"
|
|
execute rm -f toc.pol
|
|
|
|
# check sanity of SICS
|
|
pv_cmd=("pvebc" "--dry-run" "--toc" "${sics}/toc.asr")
|
|
execute "${pv_cmd[@]}"
|
|
log_info "${sics} is sane"
|
|
|
|
# unmount image
|
|
cleanup_pop # Unmount temp_boot
|
|
cleanup_pop # Disconnect NBD
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# MAIN
|
|
#
|
|
|
|
main() {
|
|
# Set strict error handling when running as script
|
|
local -
|
|
set -Eu
|
|
|
|
# call cleanup on error
|
|
# PIPE is needed to allow out=$(main list -i <image> | grep -m 1 <filter>)
|
|
# bash will close the subshell executing the main function as soon as grep has found a match
|
|
# cleanup will not be executed anymore
|
|
trap 'cleanup 1' ERR EXIT PIPE
|
|
|
|
# general
|
|
ACTION=""
|
|
NBD=""
|
|
ARCH=""
|
|
CONFIG_FILE=""
|
|
OUT=""
|
|
UUID=""
|
|
TEMP_DIR=""
|
|
NO_VERIFY=""
|
|
NO_EBC=""
|
|
BOOT_PARTNUM=""
|
|
ROOT_PARTNUM=""
|
|
HKDs=""
|
|
IMAGE=""
|
|
PARTNUM=""
|
|
|
|
# certificate chain related
|
|
CC_CERTS=""
|
|
CC_CRLS=""
|
|
CC_ROOTCA=""
|
|
CC_OFFLINE=""
|
|
|
|
# conversion related
|
|
C_BOOT_LOADER_ENTRY=""
|
|
C_SEL_KERNEL_PARAMETER=""
|
|
|
|
# encryption related
|
|
E_CCK=""
|
|
E_EXTENSION_SECRET=""
|
|
E_LUKS_RFS_KEY=""
|
|
E_LUKS_PASSPHRASE=""
|
|
E_LUKS_RFS_KEY_ASR_NAME=""
|
|
E_LUKS_KEY_SIZE=""
|
|
E_ADD_SECRET_REQUESTS=""
|
|
|
|
# parse command line
|
|
cli "$@"
|
|
|
|
# set up logger
|
|
setup_logger
|
|
|
|
# get architecture
|
|
get_arch
|
|
|
|
# check for prereqs
|
|
check_prereqs
|
|
|
|
if [[ "${ACTION}" != "list" ]]; then
|
|
# parse yaml configuration
|
|
parse_yaml
|
|
|
|
# setup output directory
|
|
if [[ ! -d ${OUT} ]]; then
|
|
execute mkdir "${OUT}"
|
|
if [[ ! -d ${OUT} ]]; then
|
|
log_error "unable to create ${OUT}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# get partitions
|
|
get_partnum "root" "${IMAGE}"
|
|
ROOT_PARTNUM="${PARTNUM}"
|
|
test -z "${ROOT_PARTNUM}" && log_error "unable to find root partition on ${IMAGE}"
|
|
get_partnum "boot" "${IMAGE}"
|
|
BOOT_PARTNUM="${PARTNUM}"
|
|
test -z "${BOOT_PARTNUM}" && log_error "unable to find boot partition on ${IMAGE}"
|
|
|
|
# setup temporary directory
|
|
UUID=$(uuidgen)
|
|
TEMP_DIR=/opt/sel-ebc-${UUID}
|
|
execute mkdir "${TEMP_DIR}"
|
|
if [[ ! -d ${TEMP_DIR} ]]; then
|
|
log_error "unable to create ${TEMP_DIR}"
|
|
fi
|
|
# Register cleanup for TEMP_DIR (will be removed on any exit)
|
|
if [[ -d "${TEMP_DIR}" ]]; then
|
|
cleanup_push rm -rf "${TEMP_DIR}"
|
|
fi
|
|
execute mkdir "${TEMP_DIR}/boot"
|
|
execute mkdir "${TEMP_DIR}/root"
|
|
|
|
log_info "all temporary resources will be placed in ${TEMP_DIR}"
|
|
|
|
if [[ "${ACTION}" == "list" ]]; then
|
|
list
|
|
fi
|
|
|
|
if [[ "${ACTION}" == "convert" || "${ACTION}" == "full" ]]; then
|
|
convert
|
|
fi
|
|
|
|
if [[ "${ACTION}" == "encrypt" || "${ACTION}" == "full" ]]; then
|
|
encrypt
|
|
fi
|
|
|
|
# Done - run cleanup explicitly to support sourcing
|
|
log_info "done"
|
|
|
|
# Run cleanup stack to reset logging and clean up resources
|
|
# This ensures cleanup happens even when script is sourced
|
|
cleanup 0
|
|
return $?
|
|
}
|
|
|
|
|
|
#
|
|
# SOURCE WRAPPER
|
|
#
|
|
|
|
if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then
|
|
main "${@}"
|
|
fi
|