mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Issue an error if the specified boot loader entry cannot be found. Reviewed-by: Holger Dengler <dengler@linux.ibm.com> Signed-off-by: Finn Callies <fcallies@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1596 lines
35 KiB
Bash
Executable File
1596 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
|
|
--version Show version information"
|
|
if [[ "${action}" == "[ACTION]" ]]; then
|
|
echo -e "\t-c, --config\t\tConfiguration yaml file"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
return 0
|
|
}
|
|
|
|
print_version() {
|
|
cat <<-EOD
|
|
$0: version %S390_TOOLS_VERSION%
|
|
Copyright IBM Corp.
|
|
EOD
|
|
}
|
|
|
|
|
|
#
|
|
# 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,version,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
|
|
;;
|
|
'--version')
|
|
print_version
|
|
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
|
|
kernel_parameter=""
|
|
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 [[ -z "${kernel_parameter}" ]]; then
|
|
log_error "No boot loader entry with title \"${ble}\" found"
|
|
fi
|
|
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 env -S LIBGUESTFS_BACKEND=direct 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
|