diff --git a/95-akmods.preset b/95-akmods.preset new file mode 100644 index 0000000..2b2b8a8 --- /dev/null +++ b/95-akmods.preset @@ -0,0 +1,8 @@ +# Also see: +# https://fedoraproject.org/wiki/Starting_services_by_default + +# Installing presets is not the preferred solution but until another one +# presents itself: +# https://bugzilla.rpmfusion.org/show_bug.cgi?id=3713 +enable akmods.service +#enable akmods-shutdown.service diff --git a/95-akmodsposttrans.install b/95-akmodsposttrans.install new file mode 100755 index 0000000..407e9ac --- /dev/null +++ b/95-akmodsposttrans.install @@ -0,0 +1,59 @@ +#!/bin/bash - +# +# 95-akmodposttrans.install - Calls akmods for newly installed kernels +# +# Copyright (c) 2019 Nicolas Viéville +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +COMMAND="$1" +KERNEL_VERSION="$2" +BOOT_DIR_ABS="$3" +KERNEL_IMAGE="$4" + +# just check in case a user calls this directly +if [[ ! -w /var ]] ; then + echo "Needs to run as root to be able to install rpms." >&2 + exit 4 +fi + +if [[ ! -n "${KERNEL_VERSION}" ]] ; then + exit 1 +fi + +case "${COMMAND}" in + add) + # Exit early if system-update.target is active - rhbz#1518401 + /usr/bin/systemctl -q is-active system-update-pre.target system-update.target + RET=$? + + [[ $RET == 0 ]] && exit 0 + + /bin/systemctl restart "akmods@${KERNEL_VERSION}.service" --no-block >/dev/null 2>&1 + ;; + remove) + # Nothing to do + ;; + *) + ;; +esac + +exit 0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c9b44cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..d4a9b47 --- /dev/null +++ b/README @@ -0,0 +1,12 @@ +Akmods startup script will rebuild akmod packages during system +boot, while its background daemon will build them for kernels right +after they were installed. + +The akmods systemd service provides both, and is enabled by default. + +The akmods-shutdown service is disabled by default but can, in some +circumstances, provide an additional chance to build and install a kernel +module. Users who would prefer longer shutdowns over delayed startups +may wish to consider enabling it with the following command: + + sudo systemctl enable --now akmods-shutdown.service diff --git a/README.secureboot b/README.secureboot new file mode 100644 index 0000000..908b282 --- /dev/null +++ b/README.secureboot @@ -0,0 +1,51 @@ +Secure boot is a setup using UEFI firmware to check cryptographic +signatures on the bootloader and associated OS kernel to ensure they +have not been tampered with or bypassed in the boot process. + +This verification can be extended to Kernel and its modules. +It's default case in Fedora with UEFI and Secure boot enabled. + +Fedora Project have signed kernels and also main modules with Fedora +Key, but 3rd party modules as NVidia, VirtualBox, etc. need to be signed +to load. + +Akmods provides an enroll process to sign third party modules with your +own keypair. + +At the first run of the akmods.service, certificate and keypair will be +created with default value using the '/usr/sbin/kmodgenca' script. + +You may also wish to manually create your own certificate and keypair +with `/usr/sbin/kmodgenca` command. +If '/usr/sbin/kmodgenca' is launched with the '-a' parameter, it will +use default values to complete the cacert.config file, and to generate +automatically the cert and the private key. +If '/usr/sbin/kmodgenca' is launched without parameters, user will be +prompted to complete manually the cacert.config file, then the cert and +the private key will be automatically generated. +If the cert and the private key files already exist, +'/usr/sbin/kmodgenca' will exit unless the '-f' parameter is used. + +The cert and the private key are stored respectively in +/etc/pki/akmods/certs and /etc/pki/akmods/private/ directories. + +Now you need to enroll the public key in MOK, this process is described +below. +- Ask MOK to enroll new keypair with certificate with the command + `mokutil --import /etc/pki/akmods/certs/public_key.der`. +- mokutil asks to generate a password to enroll the public key. +- Rebooting the system is needed for MOK to enroll the new public key. +- On next boot MOK Management is launched and you have to choose + "Enroll MOK". +- Choose "Continue" to enroll the key or "View key 0" to show the keys + already enrolled. +- Confirm enrollment by selecting "Yes". +- You will be invited to enter the password generated above. + WARNING: keyboard is mapped to QWERTY! +- The new key is enrolled, and system ask you to reboot. + +You can confirm the enrollment of the new keypair once the system +rebooted with: + `mokutil --list-enrolled +or with: + `mokutil --test-key /etc/pki/akmods/certs/public_key.der` diff --git a/akmods b/akmods new file mode 100644 index 0000000..9af6046 --- /dev/null +++ b/akmods @@ -0,0 +1,700 @@ +#!/usr/bin/bash - +######################################################################## +# +# akmods - Rebuilds and install akmod RPMs +# Copyright (c) 2007, 2008 Thorsten Leemhuis +# Copyright (c) 2018 Nicolas Chauvet +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +######################################################################## +# +# ToDo: +# - use yum/dnf to install required kernel-devel packages? +# - better way to detect if a earlier build failed or succeeded +# - special kernel "all" (all that are installed with a matching -devel package; could be called from posttrans in akmods packages) +# - manpage +# - make it configurable if kmod building is done with nohup +# - check on shutdown if akmods is still running and let it finish before continuing +# - make it configurable if kmods from the repo replace local ones + +# global vars +myprog="akmods" +myver="0.6.2" +kmodlogfile= +continue_line="" +tmpdir= +kernels= +verboselevel=2 +# We cannot differenciate from a code failure to shutdown kill9 oom etc +# So we always retry anyway +alwaystry=1 + +# Check Running plymouth +no_plymouth=1 +last_message="" + +function check_plymouth() { + which plymouth > /dev/null 2> /dev/null + if [[ "$?" -eq 1 ]] + then + no_plymouth=1 + return 0 + fi + + plymouth --ping + no_plymouth=$? +} + +# new or del, msg +akmods_echo_plymouth(){ + if [[ "$no_plymouth" -eq 0 ]] + then + if [[ "$1" -eq 1 ]] + then + plymouth display-message --text="$2" + last_message=$2 + else + if [ -z "${last_message}" ]; then + plymouth hide-message --text="$last_message" & + last_message="" + fi + fi + fi +} + +akmods_echo() +{ + # where to output + local this_fd=${1} + shift + + # verboselevel + local this_verbose=${1} + shift + + # output to console + if (( verboselevel >= this_verbose )) ; then + if [[ "${1}" == "--success" ]] ; then + echo_success + continue_line="" + echo + return 0 + elif [[ "${1}" == "--failure" ]] ; then + echo_failure + echo + continue_line="" + return 0 + elif [[ "${1}" == "--warning" ]] ; then + echo_warning + echo + continue_line="" + return 0 + elif [[ "${1}" == "-n" ]] ; then + continue_line="true" + fi + echo "$@" >&"${this_fd}" + fi + + # no need to print the status flags in the logs + if [[ "${1}" == "--success" ]] || [[ "${1}" == "--failure" ]] || [[ "${1}" == "--warning" ]] ; then + return 0 + fi + + # no need to continues in the log + if [[ "${1}" == "-n" ]] ; then + shift + fi + + # global logfile + echo "$(date +%Y/%m/%d\ %H:%M:%S) akmods: $*" >> "/var/log/akmods/akmods.log" + + # the kmods logfile as well, if we work on a kmod + if [[ -n "${kmodlogfile}" ]] ; then + echo "$(date +%Y/%m/%d\ %H:%M:%S) akmods: $*" >> "${kmodlogfile}" + fi +} + +finally() +{ + # remove tmpfiles + remove_tmpdir + + # remove lockfile + rm -f /var/cache/akmods/.lockfile + + exit "${1:-128}" +} + +# Make sure finally() is run regardless of reason for exiting. +trap "finally" ABRT HUP INT QUIT + +create_tmpdir() +{ + if ! tmpdir="$(mktemp -d -p /tmp "${myprog}.XXXXXXXX")/" ; then + akmods_echo 2 1 "ERROR: failed to create tmpdir." + akmods_echo 2 1 --failure ; return 1 + fi + if ! mkdir "${tmpdir}"results ; then + akmods_echo 2 1 "ERROR: failed to create result tmpdir." + akmods_echo 2 1 --failure ; return 1 + fi +} + +remove_tmpdir() +{ + # remove tmpfiles + if [[ -n "${tmpdir}" ]] && [[ -d "${tmpdir}" ]] ; then + rm -f "${tmpdir}"results/* "${tmpdir}"*.log + rmdir "${tmpdir}"results/ "${tmpdir}" + fi +} + +cleanup_cachedir () +{ + local excluded + excluded=$(find /boot -name 'vmlinuz-*' '!' -name '*rescue*' 2>/dev/null | sed 's/.*vmlinuz-//') + local -a file_list + mapfile -t file_list < <(find /var/cache/akmods -mindepth 2 -type f -not -name .last.log 2>/dev/null | grep -Fv -f <(echo "${excluded}")) + for one_file in "${file_list[@]}"; do + if grep -q ".*\.rpm$" <<< "${one_file}" ; then + if ! rpm -q "$(basename "${one_file%.rpm}")" >/dev/null ; then + rm -f "${one_file}" + fi + else + rm -f "${one_file}" + fi + done +} + +init () +{ + # some security provisions + \export PATH='/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin' + \unalias -a + hash -r + # https://bugzilla.rpmfusion.org/show_bug.cgi?id=4023 + #ulimit -H -c 0 -- + IFS=$' \t\n' + UMASK=022 + umask ${UMASK} + + # we get the echo_{success,failure} stuff from there + if [[ -r /etc/rc.d/init.d/functions ]] ; then + source /etc/rc.d/init.d/functions + else + # Use our own simple replacements + echo_success() { + echo -ne " [ OK ]\r" + return 0 + } + echo_failure() { + echo -ne " [FAILED]\r" + return 1 + } + echo_warning() { + echo -ne " [WARNING]\r" + return 1 + } + fi + + # needs root permissions + if [[ ! -w /var ]] ; then + echo -n "Needs to run as root to be able to install rpms." >&2 + echo_failure ; echo ; exit 1 + fi + + # no akmods + if [[ ! -d "/usr/src/akmods/" ]] ; then + echo -n "/usr/src/akmods/ not found." >&2 + echo_failure ; echo ; exit 1 + fi + + # if there are no akmod packages installed there is nothing to do for us + if ! ls /usr/src/akmods/*-kmod.latest &> /dev/null ; then + echo -n "No akmod packages found, nothing to do." >&2 + echo_success ; echo ; exit 0 + fi + + + # now that we know that we're root make sure our dir for logging and results is available + if [[ ! -d "/var/cache/akmods/" ]] ; then + if ! mkdir -p "/var/cache/akmods/" ; then + echo -n "/var/cache/akmods/ not found and could not be created" >&2 + echo_failure ; echo ; exit 1 + fi + fi + if [[ ! -w "/var/cache/akmods/" ]] ; then + echo -n "/var/cache/akmods/ not writable" >&2 + echo_failure ; echo ; exit 1 + fi + + # tools needed + for tool in akmodsbuild chown flock sed rpmdev-vercmp ; do + if ! command -v "${tool}" &> /dev/null ; then + echo -n "${tool} not found" >&2 + echo_failure ; echo ; exit 1 + fi + done + + # create lockfile and wait till we get it + exec 99>/run/akmods/akmods.lock + flock -w 900 99 +} + + +check_kernel_devel() +{ + if [[ ! -r /usr/src/kernels/"${1}"/Makefile ]] && \ + [[ ! -r /lib/modules/"${1}"/build/Makefile ]] ; then + echo "Could not find files needed to compile modules for ${1}" + echo "Are the development files for kernel ${1} or the appropriate kernel-devel package installed?" + return 1 + elif [[ -r /usr/src/kernels/"${1}"/Makefile ]] && \ + [[ ! -d /lib/modules/"${1}" ]] ; then + # this is a red hat / fedora kernel-devel package, but the kernel for it is not installed + # kmodtool would add a dep on that kernel when building; thus when we'd try to install the + # rpms we'd run into a missing-dep problem. Thus we prevent that case + echo "Kernel ${1} not installed" + return 1 + fi + return 0 +} + +check_default_kernel() +{ + # Ensure to build for grub or systemd-boot default kernel + # + # IMPORTANT: "bootctl is-installed" check that systemd-boot is installed only. + # It doesn't check if systemd-boot is the default loader. + # So we assume grubby results if available + if command -v grubby >/dev/null 2>&1 ; then + default_kernel=$(grubby --default-kernel | sed -e 's/^.*vmlinuz-//') + elif bootctl is-installed >/dev/null 2>&1 ; then + # Leave jq as optional - isDefault requires systemd 253 + if command -v jq >/dev/null ; then + default_kernel="$(bootctl list --json=short | jq -r '.[] | select(.isDefault).version')" + # Validate the result or discard - rhbz#2270414 + if [[ ! -f /boot/vmlinuz-"${default_kernel}" ]] ; then + default_kernel="" + fi + fi + else # They use neither systemd-boot nor grub2 + echo -n "Unable to figure out the default kernel" >&2 + echo_warning ; echo + default_kernel="" + fi + + local _kernels + if [[ "${default_kernel}" == "$(uname -r)" ]] ; then + _kernels="${default_kernel}" + else + _kernels="${default_kernel} $(uname -r)" + fi + + for _kernel in ${_kernels} ; do + if check_kernel_devel "${_kernel}" ; then + kernels="${kernels} ${_kernel}" + fi + done + +} + +buildinstall_kmod() +{ + local this_kernelver=${1} + local this_kmodname=${2} + local this_kmodsrpm=${3} + local this_kmodverrel=${4} + + if [[ ! -r "${this_kmodsrpm}" ]] ; then + akmods_echo 2 1 "ERROR: ${this_kmodsrpm} not found." + akmods_echo 2 1 --failure ; return 1 + fi + + + # result and logdir + if [[ ! -d "/var/cache/akmods/${this_kmodname}" ]] ; then + if ! mkdir "/var/cache/akmods/${this_kmodname}" ; then + akmods_echo 2 1 "ERROR: could not create /var/cache/akmods/${this_kmodname}." + akmods_echo 2 1 --failure ; return 1 + fi + fi + + ## preparations + # tmpdir + create_tmpdir + + # akmods needs to write there (and nobody else, but mktemp takes care of that!) + chown akmods "${tmpdir}" "${tmpdir}"results + + # remove old logfiles if they exist + rm -f "/var/cache/akmods/${this_kmodname}/${this_kmodverrel}-for-${this_kernelver}.log" "/var/cache/akmods/${this_kmodname}/.last.log" + + # create a per kmod logfile + if ! touch "/var/cache/akmods/${this_kmodname}/.last.log" ; then + akmods_echo 2 1 "ERROR: failed to create kmod specific logfile." + return 1 + fi + + # akmods_echo will log to this file from now on as well + kmodlogfile="/var/cache/akmods/${this_kmodname}/.last.log" + + # Unset TMPDIR since it is misused by "runuser" + # https://bugzilla.rpmfusion.org/show_bug.cgi?id=2596 + unset TMPDIR + + # build module using akmod + akmods_echo_plymouth 1 "akmod: Building ${this_kmodsrpm}..." + akmods_echo 1 4 "Building RPM using the command '/usr/sbin/akmodsbuild --kernels ${this_kernelver} ${this_kmodsrpm}'" + /sbin/runuser -s /bin/bash -c "/usr/sbin/akmodsbuild --quiet --kernels ${this_kernelver} --outputdir ${tmpdir}results --logfile ${tmpdir}/akmodsbuild.log ${this_kmodsrpm}" akmods >> "${kmodlogfile}" 2>&1 + local returncode=$? + + # copy rpmbuild log to kmod specific logfile + if [[ -s "${tmpdir}"/akmodsbuild.log ]] ; then + sed -e "s|^|$(date +%Y/%m/%d\ %H:%M:%S) akmodsbuild: |" "${tmpdir}"/akmodsbuild.log >> "${kmodlogfile}" + fi + + # result + if (( returncode != 0 )) ; then + if [[ -n "${continue_line}" ]] ; then + akmods_echo 1 2 --failure + fi + + akmods_echo_plymouth 0 "" + akmods_echo_plymouth 1 "akmod: Building ${this_kmodsrpm} failed!" + sleep 5 + akmods_echo 2 1 "Building rpms failed; see /var/cache/akmods/${this_kmodname}/${this_kmodverrel}-for-${this_kernelver}.failed.log for details" + cp -fl "${kmodlogfile}" "/var/cache/akmods/${this_kmodname}/${this_kmodverrel}-for-${this_kernelver}.failed.log" + kmodlogfile="" + remove_tmpdir + return 4 + fi + + # dnf/yum install - repository disabled on purpose see rfbz#3350 + + akmods_echo_plymouth 0 "" + akmods_echo_plymouth 1 "akmod: Installing ${this_kmodsrpm}..." + akmods_echo 1 4 "Installing newly built rpms" + local -a rpm_paths + mapfile -t rpm_paths < <(find "${tmpdir}results" -type f -name '*.rpm' | grep -v debuginfo) + if [[ -f /usr/bin/dnf ]] ; then + akmods_echo 1 4 "DNF detected" + dnf -y "${pkg_install:-install}" --nogpgcheck --disablerepo='*' "${rpm_paths[@]}" >> "${kmodlogfile}" 2>&1 + else + akmods_echo 1 4 "DNF not found, using YUM instead." + yum -y "${pkg_install:-install}" --nogpgcheck --disablerepo='*' "${rpm_paths[@]}" >> "${kmodlogfile}" 2>&1 + fi + local returncode=$? + + # place the newly built rpms where user expects them + cp "${tmpdir}results/"* "/var/cache/akmods/${this_kmodname}/" + + # everything fine? + if (( returncode != 0 )) ; then + if [[ -n "${continue_line}" ]] ; then + akmods_echo 1 2 --failure + fi + + akmods_echo_plymouth 0 "" + akmods_echo_plymouth 1 "akmod: Installing ${this_kmodsrpm} failed!" + sleep 5 + akmods_echo 2 1 "Could not install newly built RPMs. You can find them and the logfile in:" + akmods_echo 2 1 "/var/cache/akmods/${this_kmodname}/${this_kmodverrel}-for-${this_kernelver}.failed.log" + cp -fl "${kmodlogfile}" "/var/cache/akmods/${this_kmodname}/${this_kmodverrel}-for-${this_kernelver}.failed.log" + kmodlogfile="" + remove_tmpdir + return 8 + fi + + # finish + akmods_echo 1 4 "Successful." + cp -fl "${kmodlogfile}" "/var/cache/akmods/${this_kmodname}/${this_kmodverrel}-for-${this_kernelver}.log" + kmodlogfile="" + remove_tmpdir + + akmods_echo_plymouth 0 "" + + return 0 +} + +check_kmod_up2date() +{ + local this_kernelver=${1} + local this_kmodname=${2} + + # with --rebuild we should always build + if [[ -n "${rebuild}" ]]; then + return 1 + fi + + local kmodpackage_file + kmodpackage_file="$(modinfo "${this_kmodname}" -k "${this_kernelver}" -n 2>/dev/null)" + + # kmod present, even with weak-modules? + if [[ ! -n "${kmodpackage_file}" ]] && [[ ! -d /lib/modules/${this_kernelver}/extra/${this_kmodname}/ ]] ; then + # build it + return 1 + fi + + # special case where part of the kmod is mainlined using $this_kmodname + # making $kmodpackage_file non zero when the kmod is not install yet + if [[ "${kmodpackage_file}" == "/lib/modules/${this_kernelver}/"* ]] && \ + [[ ! -d /lib/modules/${this_kernelver}/extra/${this_kmodname}/ ]] ; then + # build it + return 1 + fi + + # kmod up2date? + local kmodpackage + # Weak module symlink case + if [ -n "${kmodpackage_file}" ] && [ -h "${kmodpackage_file}" ] && echo "${kmodpackage_file}" | grep -q "weak-updates" ; then + kmodpackage="$(rpm -qf "$(readlink -e "${kmodpackage_file}")" 2> /dev/null)" + # Regular module file case + else + kmodpackage="$(rpm -qf "/lib/modules/${this_kernelver}/extra/${this_kmodname}/" 2> /dev/null)" + fi + if [[ ! -n "${kmodpackage}" ]] ; then + # seems we didn't get what we wanted + # well, better to do nothing in this case + akmods_echo 1 2 -n "Warning: Could not determine what package owns /lib/modules/${this_kernelver}/extra/${this_kmodname}/" + return 0 + fi + local kmodver + kmodver=$(rpm -q --qf '%{EPOCH}:%{VERSION}-%{RELEASE}\n' "${kmodpackage}" | sed 's|(none)|0|; s!\.\(fc\|el\|lvn\)[0-9]*!!g') + local akmodver + akmodver=$(rpm -qp --qf '%{EPOCH}:%{VERSION}-%{RELEASE}\n' /usr/src/akmods/"${this_kmodname}"-kmod.latest | sed 's|(none)|0|; s!\.\(fc\|el\|lvn\)[0-9]*!!g') + + rpmdev-vercmp "${kmodver}" "${akmodver}" &>/dev/null + local retvalue=$? + if [[ "$retvalue" == 0 ]] ; then + # Versions are the same. Nothing to do. + return 0 + elif [[ "$retvalue" == 11 ]] ; then + # kmod is newer, nothing to do. + return 0 + elif [[ "$retvalue" == 12 ]] ; then + # akmod is newer, need to build kmod. + return 1 + else + # Something went wrong + akmods_echo 1 2 -n "Error: Could not determine if akmod is newer than the installed kmod" + akmods_echo 1 2 --failure + return 0 + fi +} + +check_kmods() +{ + local this_kernelver="${1}" + + akmods_echo 1 2 -n "Checking kmods exist for ${this_kernelver}" + for akmods_kmodfile in /usr/src/akmods/*-kmod.latest ; do + local this_kmodname + this_kmodname="$(basename "${akmods_kmodfile%%-kmod.latest}")" + + # actually check this akmod? + if [[ -n "${akmods}" ]] ; then + for akmod in ${akmods} ; do + if [[ "${this_kmodname}" != "${akmod}" ]] ; then + # ignore this one + continue 2 + fi + done + fi + + # go + if ! check_kmod_up2date "${this_kernelver}" "${this_kmodname}" ; then + # okay, kmod wasn't found or is not up2date + if [[ -n "${continue_line}" ]] ; then + akmods_echo 1 2 --success + # if the files for building modules are not available don't even try to build modules + if [[ ! -r /usr/src/kernels/"${this_kernelver}"/Makefile ]] && \ + [[ ! -r /lib/modules/"${this_kernelver}"/build/Makefile ]] ; then + akmods_echo 1 2 "Files needed for building modules against kernel" + akmods_echo 1 2 "${this_kernelver} could not be found as the following" + akmods_echo 1 2 "directories are missing:" + akmods_echo 1 2 "/usr/src/kernels/${this_kernelver}/" + akmods_echo 1 2 -n "/lib/modules/${this_kernelver}/build/" + akmods_echo 1 2 -n "Is the correct kernel-devel package installed?" + akmods_echo 1 2 --failure + return 1 + fi + fi + + local this_kmodverrel + this_kmodverrel="$(rpm -qp --qf '%{VERSION}-%{RELEASE}' "${akmods_kmodfile}" | sed 's!\.\(fc\|el\|lvn\)[0-9]*!!g' )" + if [[ ! -n "${alwaystry}" ]] && [[ -e "/var/cache/akmods/${this_kmodname}/${this_kmodverrel}-for-${this_kernelver}".failed.log ]] ; then + akmods_echo 1 2 -n "Ignoring ${this_kmodname}-kmod as it failed earlier" + akmods_echo 1 2 --warning + local someignored="true" + else + akmods_echo 1 2 -n "Building and installing ${this_kmodname}-kmod" + buildinstall_kmod "${this_kernelver}" "${this_kmodname}" "${akmods_kmodfile}" "${this_kmodverrel}" + local returncode=$? + if [[ "$returncode" == "0" ]] ; then + akmods_echo 1 2 --success + local somesucceeded="true" + elif [[ "$returncode" == "8" ]] ; then + akmods_echo 1 2 --failure "New kmod RPM was built but could not be installed." + else + local somefailed="true" + fi + fi + fi + done + + if [[ -n "${continue_line}" ]] ; then + akmods_echo 1 2 --success + elif [[ -n "${someignored}" ]] || [[ -n "${somefailed}" ]] ; then + echo + akmods_echo 1 2 "Hint: Some kmods were ignored or failed to build or install." + akmods_echo 1 2 "You can try to rebuild and install them by by calling" + akmods_echo 1 2 "'/usr/sbin/akmods --force' as root." + echo + sleep 2 + fi + + # akmods for newly installed akmod rpms as wells as akmods.service run + # after udev and systemd-modules-load.service have tried to load modules + if [[ -n "${somesucceeded}" ]] && [[ "${this_kernelver}" == "$(uname -r)" ]] ; then + find /sys/devices -name modalias -print0 | xargs -0 cat | xargs modprobe -a -b -q + if [ -f /usr/bin/systemctl ] ; then + systemctl restart systemd-modules-load.service + fi + fi +} + +myprog_help () +{ + echo "Checks the akmod packages and rebuilds them if needed" + echo $'\n'"Usage: ${myprog} [OPTIONS]" + echo $'\n'"Options:" + echo " --force -- try all, even if they failed earlier" + echo " --kernels -- build and install only for kernel " + echo " (formatted the same as 'uname -r' would produce)" + echo " --rebuild -- rebuild all, even if they are up to date" + echo " --akmod -- build and install only akmod " +} + + +# first parse command line options +while [ "${1}" ] ; do + case "${1}" in + --kernel|--kernels) + shift + if [[ ! -n "${1}" ]] ; then + echo "ERROR: Please provide the kernel-version to build for together with --kernel" >&2 + exit 1 + fi + + if ! check_kernel_devel "${1}" ; then + echo "ERROR: kernel or kernel-devel required for ${1}" >&2 + exit 1 + fi + + # overwrites the default: + if [[ ! -n "${kernels}" ]] ; then + kernels="${1}" + else + kernels="${kernels} ${1}" + fi + # an try to build, even if we tried already + alwaystry=true + shift + ;; + --akmod|--kmod) + shift + if [[ ! -n "${1}" ]] ; then + echo "ERROR: Please provide a name of a akmod package together with --akmods" >&2 + exit 1 + elif [[ -r /usr/src/akmods/"${1}"-kmod.latest ]] ; then + akmods="${akmods}${1} " + elif [[ -r /usr/src/akmods/"${1}".latest ]] ; then + akmods="${akmods}${1%%-kmod} " + else + echo "Could not find akmod ${1}" + exit 1 + fi + shift + ;; + --force) + alwaystry=true + shift + ;; + --from-init) + # just in case: remove stale lockfile if it exists: + rm -f /var/cache/akmods/.lockfile + # Clean old logs and rpm files from no more installed kmod + # packages. + cleanup_cachedir + # akmods --from-init only operates on current kernel + kernels="$(uname -r)" + shift + ;; + --from-posttrans|--from-kernel-posttrans|--from-akmod-posttrans) + # ignored + shift + ;; + --rebuild) + rebuild=true + pkg_install=reinstall + shift + ;; + --verbose) + (( verboselevel++ )) + shift + ;; + --quiet) + (( verboselevel-- )) + shift + ;; + --help) + myprog_help + exit 0 + ;; + --version) + echo "${myprog} ${myver}" + exit 0 + ;; + *) + echo "Error: Unknown option '${1}'." >&2 + myprog_help >&2 + exit 2 + ;; + esac +done + +check_plymouth +# sanity checks +init + +# only check for default_kernel if no value have been parsed +if [ -z "${kernels}" ] ; then + check_default_kernel +fi + +# go +for kernel in ${kernels} ; do + check_kmods "${kernel}" +done + +# finished :) +finally 0 diff --git a/akmods-keygen.target b/akmods-keygen.target new file mode 100644 index 0000000..5df7494 --- /dev/null +++ b/akmods-keygen.target @@ -0,0 +1,3 @@ +[Unit] +Wants=akmods-keygen@.service +PartOf=akmods.service diff --git a/akmods-keygen@.service b/akmods-keygen@.service new file mode 100644 index 0000000..8ae60ee --- /dev/null +++ b/akmods-keygen@.service @@ -0,0 +1,11 @@ +[Unit] +Description=Akmods Secure boot MOK Key Generation +ConditionFileNotEmpty=|!/etc/pki/akmods/certs/public_key.der +ConditionFileNotEmpty=|!/etc/pki/akmods/private/private_key.priv + +[Service] +Type=oneshot +ExecStart=/usr/sbin/kmodgenca -a + +[Install] +WantedBy=akmods-keygen.target diff --git a/akmods-kmodgenca b/akmods-kmodgenca new file mode 100644 index 0000000..eab0882 --- /dev/null +++ b/akmods-kmodgenca @@ -0,0 +1,588 @@ +#!/bin/bash + +# NAME: 'kmodgenca' +# PURPOSE: Helper script to create CA/key pair to sign modules. +# Copyright (c) 2017 Stanislas Leduc +# Copyright (c) 2018-2019 Nicolas Viéville +# Copyright (c) 2024 Rohan Barar + +################################################################################ +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +################################################################################ + +# EXIT STATUS CODES AND DESCRIPTIONS +# 0 - SUCCESS +# 1 - INSUFFICIENT PRIVILEGES +# 2 - INVALID COMMAND LINE ARGUMENT +# 3 - BROKEN SYMLINKS TO DEFAULT KEY PAIR +# 4 - MISSING CACERT CONFIGURATION TEMPLATE +# 5 - FAILED TO READ CA CERTIFICATE CONFIGURATION TEMPLATE +# 6 - FAILED TO WRITE CA CERTIFICATE CONFIGURATION FILE +# 7 - UNSUCCESSFUL OPENSSL KEY PAIR CREATION COMMAND +# 8 - FAILED TO CREATE KEY PAIR FILES + +# ENFORCE STRICT ERROR HANDLING +# - Exit script on error. +# - Ensure pipelines fail on the first error. +set -eo pipefail + +# DECLARE CONSTANTS +# Script Information +readonly SCRIPT_NAME="kmodgenca" +readonly SCRIPT_VERSION="0.6.0" + +# Directories +readonly AKMODS_DIR="/etc/pki/akmods" +readonly PRIVATE_KEY_DIR="${AKMODS_DIR}/private" +readonly PUBLIC_KEY_DIR="${AKMODS_DIR}/certs" + +# Paths +readonly PRIVATE_KEY_PATH="${PRIVATE_KEY_DIR}/private_key.priv" +readonly PUBLIC_KEY_PATH="${PUBLIC_KEY_DIR}/public_key.der" +readonly CACERT_CONFIG_PATH="${AKMODS_DIR}/cacert.config" +readonly RESTORECON_PATH="/usr/sbin/restorecon" + +# ANSI +readonly BOLD_RED_TEXT="\e[1;31m" +readonly BOLD_YELLOW_TEXT="\e[1;33m" +readonly BOLD_GREEN_TEXT="\033[1;32m" +readonly BOLD_BLUE_TEXT="\e[1;34m" +readonly BOLD_GREY_TEXT="\e[1;37m" +readonly CLEAR_TEXT="\e[0m" + +# DECLARE VARIABLES +# Command Line Argument Flags +FORCE_BUILD=0 +AUTOMATIC_BUILD=0 +SHOW_HELP=0 +SHOW_VER=0 +BAD_ARGS=0 + +# Unique New Key Pair Name (Hostname + UNIX/POSIX Timestamp + Dashless UUID) +cert_hostname="${HOSTNAME}" +KEYNAME="${cert_hostname:0:44}_$(date +%s)_$(uuidgen | awk -F '-' '{print $1}')" + +# Other +AUTOMATIC_BUILD_OPTION="" + +# FUNCTIONS +function help() { + echo -e "${BOLD_GREY_TEXT}KMODGENCA HELP${CLEAR_TEXT}" + echo "Creates a Certificate Authority (CA) and key pair for module signing." + echo "Private keys are created in: '${PRIVATE_KEY_DIR}'." + echo "Public keys (certificates) are created in: '${PUBLIC_KEY_DIR}'." + echo -e "\nUsage: ${SCRIPT_NAME} [OPTIONS]" + echo -e "\nOptions:" + echo " -a, --auto Utilise default values for 'cacert.config'." + echo " -f, --force Create CA/key pair even if one already exists." + echo " -h, --help Display this help message." + echo " -V, --version Display script version information." + echo "" +} + +function check_root() { + # Notify user. + echo -e "${BOLD_BLUE_TEXT}INFO:${CLEAR_TEXT} CHECKING FOR ELEVATED PRIVILEGES..." + + if [ "$EUID" -ne 0 ]; then + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} INSUFFICIENT PRIVILEGES!" >&2 + echo "Please run the command using 'sudo' or as root." >&2 + echo "Quitting." >&2 + exit 1 + fi +} + +function parse_arguments() { + if [ $# -gt 0 ]; then + while [ "$1" ] ; do + case "$1" in + -a|--auto) + AUTOMATIC_BUILD=1 + shift + ;; + -f|--force) + FORCE_BUILD=1 + shift + ;; + -h|--help) + SHOW_HELP=1 + shift + ;; + -V|--version) + SHOW_VER=1 + shift + ;; + -*) + # Handle combined single-letter options. + for (( i=1; i<${#1}; i++ )); do + case "${1:$i:1}" in + a) + AUTOMATIC_BUILD=1 + ;; + f) + FORCE_BUILD=1 + ;; + h) + SHOW_HELP=1 + ;; + V) + SHOW_VER=1 + ;; + *) + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} INVALID OPTION '${1:$i:1}' in '${1}'." >&2 + BAD_ARGS=1 + ;; + esac + done + shift + ;; + *) + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} INVALID OPTION '${1}'." >&2 + BAD_ARGS=1 + shift + ;; + esac + done + fi + + # Display help message and then exit in the event of invalid argument(s). + if [[ "$BAD_ARGS" -eq 1 ]]; then + echo "" >&2 + help >&2 + echo "Quitting." >&2 + exit 2 + fi + + # Display script help information if requested. + if [[ "$SHOW_HELP" -eq 1 ]]; then + help + fi + + # Display script version information if requested. + if [[ "$SHOW_VER" -eq 1 ]]; then + echo "${SCRIPT_NAME} v${SCRIPT_VERSION}" + fi + + # Exit script if version and/or help information requested. + if [ "$SHOW_VER" -eq 1 ] || [ "$SHOW_HELP" -eq 1 ]; then + if [ "$AUTOMATIC_BUILD" -eq 1 ]; then + echo -e "${BOLD_YELLOW_TEXT}WARNING:${CLEAR_TEXT} IGNORING '-a' (--auto)." >&2 + fi + if [ "$FORCE_BUILD" -eq 1 ]; then + echo -e "${BOLD_YELLOW_TEXT}WARNING:${CLEAR_TEXT} IGNORING '-f' (--force)." >&2 + fi + exit 0 + fi + + # Warn user regarding forced builds. + if [[ "$FORCE_BUILD" -eq 1 ]]; then + echo -e "${BOLD_YELLOW_TEXT}WARNING:${CLEAR_TEXT} FORCED BUILD SELECTED. KEY PAIR OVERWRITE MAY OCCUR!" >&2 + fi + + # Warn user regarding automatic builds. + if [[ "$AUTOMATIC_BUILD" -eq 1 ]]; then + echo -e "${BOLD_YELLOW_TEXT}WARNING:${CLEAR_TEXT} AUTOMATIC BUILD SELECTED. USING DEFAULT VALUES FOR CA/KEY PAIR CREATION." >&2 + fi +} + +function check_broken_key_pair() { + # Check for broken non-selected key pairs. + local unmatched_public_key_paths=() + local unmatched_private_key_paths=() + + # Store paths of public and private keys. + local public_key_paths=() + local private_key_paths=() + # Note: Requires superuser permissions (i.e., sudo). + mapfile -t public_key_paths < <(find "$PUBLIC_KEY_DIR" -maxdepth 1 -name "*.der") + mapfile -t private_key_paths < <(find "$PRIVATE_KEY_DIR" -maxdepth 1 -name "*.priv") + + # Find public/private keys without corresponding private/public keys. + local key_file_path + for key_file_path in "${public_key_paths[@]}"; do + # Skip symlink. + if [[ "$key_file_path" == "$PUBLIC_KEY_PATH" ]]; then + continue + fi + + # Remove file extension. + local public_key_name + public_key_name="$(basename "$key_file_path")" + public_key_name="${public_key_name%.*}" + + # Check if the corresponding private key exists. + local found=0 + for private_key_path in "${private_key_paths[@]}"; do + if [[ "$private_key_path" == "${PRIVATE_KEY_DIR}/${public_key_name}.priv" ]]; then + found=1 + break + fi + done + + # Store public key file name (with extension) if unpaired. + if [[ "$found" -eq 0 ]]; then + unmatched_public_key_paths+=("$key_file_path") + fi + done + + for key_file_path in "${private_key_paths[@]}"; do + # Skip symlink. + if [[ "$key_file_path" == "$PRIVATE_KEY_PATH" ]]; then + continue + fi + + # Remove file extension. + local private_key_name + private_key_name="$(basename "$key_file_path")" + private_key_name="${private_key_name%.*}" + + # Check if the corresponding public key exists. + local found=0 + for public_key_path in "${public_key_paths[@]}"; do + if [[ "$public_key_path" == "${PUBLIC_KEY_DIR}/${private_key_name}.der" ]]; then + found=1 + break + fi + done + + # Store private key file name (with extension) if unpaired. + if [[ "$found" -eq 0 ]]; then + unmatched_private_key_paths+=("$key_file_path") + fi + done + + # Check if isolated keys were detected. + if [[ ${#unmatched_private_key_paths[@]} -gt 0 || ${#unmatched_public_key_paths[@]} -gt 0 ]]; then + echo -e "${BOLD_YELLOW_TEXT}WARNING:${CLEAR_TEXT} SOME KEY PAIRS ARE BROKEN!" >&2 + + # Notify user regarding isolated public keys. + if [[ ${#unmatched_public_key_paths[@]} -gt 0 ]]; then + echo "Isolated Public Keys:" >&2 + local isolated_pub_key_path + for isolated_pub_key_path in "${unmatched_public_key_paths[@]}"; do + echo " ${isolated_pub_key_path}" >&2 + done + echo "" >&2 + fi + + # Notify user regarding isolated private keys. + if [[ ${#unmatched_private_key_paths[@]} -gt 0 ]]; then + echo "Isolated Private Keys:" >&2 + local isolated_pri_key_path + for isolated_pri_key_path in "${unmatched_private_key_paths[@]}"; do + echo " ${isolated_pri_key_path}" >&2 + done + echo "" >&2 + fi + fi + + # Terminate the script when: + # 1. A certificate (public key) OR private key exists (but not both), AND + # 2. A forced rebuild was not requested (i.e., 'FORCE_BUILD' is NOT '1') + + # Check for broken symlinks to the currently selected pair of keys. + # Note: Requires superuser permissions (i.e. sudo). + # shellcheck disable=SC2155 + local pub_key_exists=$(readlink -e "$PUBLIC_KEY_PATH" &>/dev/null && echo 1 || echo 0) + + # Note: Requires superuser permissions (i.e. sudo). + # shellcheck disable=SC2155 + local pri_key_exists=$(readlink -e "$PRIVATE_KEY_PATH" &>/dev/null && echo 1 || echo 0) + + if [[ "$pub_key_exists" -ne "$pri_key_exists" && "$FORCE_BUILD" -eq 0 ]]; then + # Notify user. + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} BROKEN SYMLINK(S) TO THE DEFAULT KEY PAIR!" >&2 + echo "Valid symlinks to a public and private key must exist." >&2 + echo "" >&2 + + # Dynamic status output with colours. + echo -e "${PUBLIC_KEY_PATH}: $( [[ $pub_key_exists -eq 1 ]] && echo -e "${BOLD_GREEN_TEXT}WORKING${CLEAR_TEXT}" || echo -e "${BOLD_RED_TEXT}BROKEN${CLEAR_TEXT}" )" >&2 + echo -e "${PRIVATE_KEY_PATH}: $( [[ $pri_key_exists -eq 1 ]] && echo -e "${BOLD_GREEN_TEXT}WORKING${CLEAR_TEXT}" || echo -e "${BOLD_RED_TEXT}BROKEN${CLEAR_TEXT}" )" >&2 + echo "" >&2 + echo "Quitting." >&2 + + # Exit script. + exit 3 + fi +} + +function check_existing_key_pair() { + # Notify user. + echo -e "${BOLD_BLUE_TEXT}INFO:${CLEAR_TEXT} CHECKING FOR AN EXISTING KEY PAIR..." + + # Terminate the script when: + # 1. Both a certificate (public key) and private key already exist, AND + # 2. A forced rebuild was not requested (i.e., 'FORCE_BUILD' is NOT '1') + + # Note: This approach will return '1' in the event of a broken symlink. + # Note: Requires superuser permissions (i.e. sudo). + if readlink -e "$PUBLIC_KEY_PATH" &>/dev/null && \ + readlink -e "$PRIVATE_KEY_PATH" &>/dev/null && \ + [ "$FORCE_BUILD" -eq 0 ]; then + + # Notify user. + echo -e "${BOLD_YELLOW_TEXT}WARNING:${CLEAR_TEXT} EXISTING KEY PAIR." >&2 + echo "Please specify argument '--force' to overwrite the existing key pair." >&2 + echo "Quitting." >&2 + + # Exit script. + exit 0 + fi +} + +function set_key_pair_name() { + if [ "$AUTOMATIC_BUILD" -eq 0 ]; then + while true; do + local key_pair_file_name="" + local valid_name=1 + + # Request key pair name from user. + # shellcheck disable=SC2162 + read -p "Key Pair Name: " key_pair_file_name + + # Check for empty string. + if [[ -z $(echo "$key_pair_file_name" | xargs) ]]; then + valid_name=0 + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} NAME MUST NOT BE EMPTY.\n" >&2 + fi + + # Ensure name is not '.' or '..'. + if [[ $(echo "$key_pair_file_name" | xargs) == "." ]] || [[ $(echo "$key_pair_file_name" | xargs) == ".." ]]; then + valid_name=0 + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} NAME MUST NOT BE '.' OR '..'.\n" >&2 + fi + + # Ensure name is not longer than 255 characters. + if [ "$(echo "$key_pair_file_name" | xargs | awk '{print length}')" -gt 255 ]; then + valid_name=0 + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} NAME MUST NOT BE LONGER THAN 255 CHARACTERS.\n" >&2 + fi + + # Ensure name only contains valid characters. + # - Letters (A-Z) (a-z) + # - Numbers (0-9) + # - Special + # - Period ('.') + # - Underscore ('_') + # - Hyphen ('-') + if ! [[ $(echo "$key_pair_file_name" | xargs) =~ ^[0-9a-zA-Z._-]+$ ]]; then + # Avoid triggering on an empty string. + if [[ -n $(echo "$key_pair_file_name" | xargs) ]]; then + valid_name=0 + + # Inform user of illegal characters within provided name. + local illegal_chars + illegal_chars=$(echo "$key_pair_file_name" | awk -F '' '{for(i=1;i<=NF;i++) if ($i !~ /^[0-9a-zA-Z._-]$/) print $i}' | sort -u | tr -d '\n') + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} NAME MUST NOT CONTAIN ILLEGAL CHARACTERS." >&2 + echo -e "Illegal characters in provided name:" >&2 + for (( i=0; i<${#illegal_chars}; i++ )); do + echo "- '${illegal_chars:i:1}'" >&2 + done + echo -e "\nPlease ensure the name only contains letters, numbers, periods, underscores and hyphens.\n" >&2 + fi + fi + + # Ensure key pair with same name does not exist. + if [ -f "${PUBLIC_KEY_DIR}/${key_pair_file_name}.der" ] || [ -f "${PRIVATE_KEY_DIR}/${key_pair_file_name}.priv" ]; then + valid_name=0 + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} EXISTING KEY PAIR WITH SAME NAME.\n" >&2 + fi + + # Break the loop if a valid name was provided. + if [ "$valid_name" -eq 1 ]; then + break + fi + done + + # Update global key pair name variable. + KEYNAME="$key_pair_file_name" + else + # Handle the extremely unlikely occurrence of a key pair name conflict with an existing key pair. + while [ -f "${PUBLIC_KEY_DIR}/${KEYNAME}.der" ]; do + KEYNAME="${cert_hostname:0:44}_$(date +%s)_$(uuidgen | awk -F '-' '{print $1}')" + done + fi +} + +function create_cacert_config() { + # Notify user. + echo -e "${BOLD_BLUE_TEXT}INFO:${CLEAR_TEXT} UPDATING CACERT CONFIGURATION FILE AT '${CACERT_CONFIG_PATH}'..." + + # Check if the cacert configuration template exists. + if [[ -f "${CACERT_CONFIG_PATH}.in" ]]; then + local sed_output="" + local sed_exit_status=0 + + if [ "$AUTOMATIC_BUILD" -eq 1 ]; then + # Set '-batch' argument. + AUTOMATIC_BUILD_OPTION="-batch" + + local cert_country_code=$(locale country_ab2) + if [[ -z ${cert_country_code} ]]; then + echo -e "${BOLD_YELLOW_TEXT}WARNING:${CLEAR_TEXT} COULD NOT DETECT COUNTRY CODE FROM LOCALE; USING FALLBACK VALUE: US" >&2 + cert_country_code=US + fi + + # Utilise default values if 'AUTOMATIC_BUILD' is equal to '1'. + # - Set OpenSSL field values. + # - Comment default and min/max values. + sed_output=$(sed -e "s#\(0.organizationName *= \).*#\1${cert_hostname}#" \ + -e "s#\(organizationalUnitName *= \).*#\1${cert_hostname}#" \ + -e "s#\(emailAddress *= \).*#\1akmods@${cert_hostname}#" \ + -e "s#\(localityName *= \).*#\1None#" \ + -e "s#\(stateOrProvinceName *= \).*#\1None#" \ + -e "s#\(countryName *= \).*#\1${cert_country_code}#" \ + -e "s#\(commonName *= \).*#\1${KEYNAME}#" \ + -e "s/^[^#]*_default *= /#&/" \ + -e "s/^[^#]*_min/#&/" \ + -e "s/^[^#]*_max/#&/" "${CACERT_CONFIG_PATH}.in") + sed_exit_status=$? + else + # Request user enter values manually if 'AUTOMATIC_BUILD' is equal to '0'. + # Request OpenSSL prompt user for values later. + sed_output=$(sed -e "s#\(prompt *= \).*#\1yes#" "${CACERT_CONFIG_PATH}.in") + sed_exit_status=$? + fi + + # Check if 'sed' command failed. + if [ "$sed_exit_status" -ne 0 ]; then + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} FAILED TO READ CACERT CONFIGURATION TEMPLATE at '${CACERT_CONFIG_PATH}.in'." >&2 + echo "Quitting." >&2 + exit 5 + else + # Note: Requires superuser permissions (i.e. sudo). + if ! echo "$sed_output" > "$CACERT_CONFIG_PATH"; then + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} FAILED TO WRITE CACERT CONFIGURATION FILE to '${CACERT_CONFIG_PATH}'." >&2 + echo "Quitting." >&2 + exit 6 + fi + fi + else + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} MISSING CACERT CONFIGURATION TEMPLATE!" >&2 + echo "Failed to locate the CAcert configuration template at '${CACERT_CONFIG_PATH}.in'." >&2 + echo "Quitting." >&2 + exit 4 + fi +} + +function create_new_key_pair() { + # Notify user. + echo -e "${BOLD_BLUE_TEXT}INFO:${CLEAR_TEXT} CREATING NEW KEY PAIR..." + + # Prepare an OpenSSL command to generate the key pair. + local key_pair_generation_command=( + "openssl req" # Request new certificate + "-x509" # X.509 certificate type + "-new" # New key pair + "-nodes" # No DES + "-utf8" # UTF-8 encoding + "-sha256" # SHA-256 hash algorithm + "-days" "3650" # 10 year cert validity + "${AUTOMATIC_BUILD_OPTION}" # Empty or "-batch" + "-config" "${CACERT_CONFIG_PATH}" # Configuration file path + "-outform" "DER" # DER output format + "-out" "${PUBLIC_KEY_DIR}/${KEYNAME}.der" # Public key output path + "-keyout" "${PRIVATE_KEY_DIR}/${KEYNAME}.priv" # Private key output path + ) + + # Execute the key pair generation command within the 'akmods' group context. + # Ensure 'rw-rwx---' permissions. + # Note: Requires superuser permissions (i.e. sudo). + if sg akmods -c "umask 037 && ${key_pair_generation_command[*]}"; then + # Check if both a public and a private key file were created. + if [[ ! -f "${PUBLIC_KEY_DIR}/${KEYNAME}.der" || ! -f "${PRIVATE_KEY_DIR}/${KEYNAME}.priv" ]]; then + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} KEY PAIR CREATION FAILED!" >&2 + echo "The OpenSSL key pair generation command ran, but key files were not created." >&2 + echo "Quitting." >&2 + exit 8 + fi + else + echo -e "${BOLD_RED_TEXT}ERROR:${CLEAR_TEXT} KEY PAIR CREATION FAILED!" >&2 + echo "The OpenSSL key pair generation command did not complete successfully." >&2 + echo "Quitting." >&2 + exit 7 + fi +} + +function set_key_permissions() { + # Notify user. + echo -e "${BOLD_BLUE_TEXT}INFO:${CLEAR_TEXT} SETTING KEY PAIR PERMISSIONS..." + + # Ensure that akmods group can read keys. + # Note: Requires superuser permissions (i.e. sudo). + chmod g+r "${PUBLIC_KEY_DIR}/${KEYNAME}.der" + chmod g+r "${PRIVATE_KEY_DIR}/${KEYNAME}.priv" + + # Sanitise permissions. + # Note: Requires superuser permissions (i.e. sudo). + if [[ -x "$RESTORECON_PATH" ]] ; then + $RESTORECON_PATH "${PUBLIC_KEY_DIR}/${KEYNAME}.der" + $RESTORECON_PATH "${PRIVATE_KEY_DIR}/${KEYNAME}.priv" + fi +} + +function update_key_symlinks() { + # Notify user. + echo -e "${BOLD_BLUE_TEXT}INFO:${CLEAR_TEXT} UPDATING KEY PAIR SYMLINKS..." + + # Note: Requires superuser permissions (i.e. sudo). + ln -nsf "${PUBLIC_KEY_DIR}/${KEYNAME}.der" "$PUBLIC_KEY_PATH" + ln -nsf "${PRIVATE_KEY_DIR}/${KEYNAME}.priv" "$PRIVATE_KEY_PATH" + chown -h root:akmods "$PUBLIC_KEY_PATH" + chown -h root:akmods "$PRIVATE_KEY_PATH" +} + +# SCRIPT MAINLINE +# Parse any supplied arguments. +parse_arguments "$@" + +# Check for elevated privileges. +check_root + +# Check for broken key pairs. +check_broken_key_pair + +# Check for existing key pair. +check_existing_key_pair + +# Set key pair name. +set_key_pair_name + +# Create 'cacert.config' using template file 'cacert.config.in'. +create_cacert_config + +# Create new key pair. +create_new_key_pair + +# Set permissions and sanitise keys. +set_key_permissions + +# Update symlink to use new key pair. +update_key_symlinks + +# Print completion messages. +echo -e "\n${BOLD_GREEN_TEXT}SUCCESS!${CLEAR_TEXT}" +echo "Public Key (Certificate) created at: ${PUBLIC_KEY_DIR}/${KEYNAME}.der" +echo "Private Key created at: ${PRIVATE_KEY_DIR}/${KEYNAME}.priv" +echo -e "\nSymlinks:" +echo "${KEYNAME}.der -> ${PUBLIC_KEY_PATH}" +echo "${KEYNAME}.priv -> ${PRIVATE_KEY_PATH}" + +# Exit script. +exit 0 diff --git a/akmods-ostree-post b/akmods-ostree-post new file mode 100644 index 0000000..747d0ed --- /dev/null +++ b/akmods-ostree-post @@ -0,0 +1,102 @@ +#!/bin/bash - +############################################################################ +# +# akmods - Rebuilds and install akmod RPMs +# Copyright (c) 2007, 2008 Thorsten Leemhuis +# Copyright (c) 2018 Nicolas Chauvet +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +############################################################################ + +myprog="akmods-post" +tmpdir= + +# Only do %post builds in ostree +if ! grep -q OSTREE_VERSION= /etc/os-release && ! test -f /run/ostree-booted; then + exit 0 +fi + +kmodname=$1 +srpm=$2 + + +finally() +{ + # remove tmpfiles + remove_tmpdir + + exit "${1:-128}" +} + +# Make sure finally() is run regardless of reason for exiting. +trap "finally" ABRT HUP INT QUIT + +create_tmpdir() +{ + if ! tmpdir="$(mktemp -d -p /tmp "${myprog}.XXXXXXXX")/" ; then + echo "ERROR: failed to create tmpdir." >&2 + finally 1 + fi + if ! mkdir "${tmpdir}"results ; then + echo "ERROR: failed to create result tmpdir." >&2 + finally 1 + fi +} + +remove_tmpdir() +{ + # remove tmpfiles + if [[ -n "${tmpdir}" ]] && [[ -d "${tmpdir}" ]]; then + rm -rf "${tmpdir}" + fi +} + +# This is an ostree build, so do build for all +# deployed kernels in the %post +kernels="$(ls /lib/modules)" + +create_tmpdir + +for kernel in ${kernels} ; do + echo "Building ${srpm} for kernel ${kernel}" + # Note: This builds as root, but this is pretty safe because its happening in the ostree %post sandbox. + # In fact, given that /usr is a rofiles-fuse mount no other user can access /usr in this sandbox anyway. + akmodsbuild --quiet --kernels "${kernel}" --outputdir "${tmpdir}results" --logfile "${tmpdir}/akmodsbuild.log" "${srpm}" 2>&1 + returncode=$? + if (( returncode != 0 )); then + finally 1 + fi +done + +for f in $(find "${tmpdir}results" -type f -name '*.rpm' | grep -v debuginfo) ; do + rpm2cpio "${f}" | cpio --quiet -D / -id + returncode=$? + if (( returncode != 0 )); then + echo "Extracting $f failed:" 2>&1 + finally 1 + fi +done + +for kernel in ${kernels} ; do + depmod -v "${kernel}" 2>&1 +done + +finally 0 diff --git a/akmods-shutdown b/akmods-shutdown new file mode 100644 index 0000000..ac97e8f --- /dev/null +++ b/akmods-shutdown @@ -0,0 +1,31 @@ +#!/bin/bash +# +# akmods-shutdown - Helper script to build kernel modules on shutdown +# Copyright (c) 2012 Richard shaw +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +echo "This akmods-shutdown script is deprecated and will be removed in the future" +echo "Using akmods instead ..." + +sleep 6 + +/usr/sbin/akmods diff --git a/akmods-shutdown.service b/akmods-shutdown.service new file mode 100644 index 0000000..10e8782 --- /dev/null +++ b/akmods-shutdown.service @@ -0,0 +1,14 @@ +[Unit] +Description=Builds and install new kmods from akmod packages +Before=shutdown.service reboot.service halt.service +Conflicts=shutdown.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/true +ExecStop=-/usr/sbin/akmods +TimeoutStopSec=5min + +[Install] +WantedBy=multi-user.target diff --git a/akmods-tmpfiles.conf b/akmods-tmpfiles.conf new file mode 100644 index 0000000..5197d7e --- /dev/null +++ b/akmods-tmpfiles.conf @@ -0,0 +1,2 @@ +# See tmpfiles.d(5) for details +d /run/akmods 0770 root akmods - diff --git a/akmods.h2m b/akmods.h2m new file mode 100644 index 0000000..214d28d --- /dev/null +++ b/akmods.h2m @@ -0,0 +1,12 @@ +[BUGS] +https://bugz.fedoraproject.org/akmods +[REPORTING BUGS] +Submit a bug against the akmods component at: +.br +https://bugzilla.redhat.com/enter_bug.cgi?product=Fedora +[AUTHOR] +Thorsten Leemhuis +[MAINTAINER] +Richard Shaw +[SEE ALSO] +http://rpmfusion.org/Packaging/KernelModules/Akmods diff --git a/akmods.log b/akmods.log new file mode 100644 index 0000000..884c151 --- /dev/null +++ b/akmods.log @@ -0,0 +1,8 @@ +/var/log/akmods/akmods.log { + monthly + rotate 12 + missingok + notifempty + create 644 root root + su root akmods +} diff --git a/akmods.rpmlintrc b/akmods.rpmlintrc new file mode 100644 index 0000000..38e9b58 --- /dev/null +++ b/akmods.rpmlintrc @@ -0,0 +1,3 @@ +addFilter("W: non-standard-.*") +addFilter("W: no-manual-page-for-binary .*") +addFilter("W: empty-%postun") diff --git a/akmods.service b/akmods.service new file mode 100644 index 0000000..587913d --- /dev/null +++ b/akmods.service @@ -0,0 +1,14 @@ +[Unit] +Description=Builds and install new kmods from akmod packages +ConditionPathExists=!/run/ostree-booted +Before=display-manager.service +After=akmods-keygen.target +Wants=akmods-keygen.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/sbin/akmods --from-init + +[Install] +WantedBy=multi-user.target diff --git a/akmods.spec b/akmods.spec new file mode 100644 index 0000000..88d3cef --- /dev/null +++ b/akmods.spec @@ -0,0 +1,202 @@ +Name: akmods +Version: 0.6.2 +Release: %autorelease +Summary: Automatic kmods build and install tool + +License: MIT +URL: http://rpmfusion.org/Packaging/KernelModules/Akmods + +# We are upstream, these files are maintained directly in pkg-git +Source0: 95-akmods.preset +Source1: akmods +Source2: akmodsbuild +Source3: akmods.h2m +Source6: akmods.service +Source7: akmods-shutdown +Source8: akmods-shutdown.service +Source9: README +Source10: LICENSE +Source11: akmods@.service +Source12: akmods-ostree-post +Source13: 95-akmodsposttrans.install +Source14: akmods.log +Source15: README.secureboot +Source16: cacert.config.in +Source17: akmods-kmodgenca +Source18: akmods-keygen.target +Source19: akmods-keygen@.service +Source20: %{name}-tmpfiles.conf +Source21: akmods.sysusers.conf + +BuildArch: noarch + +BuildRequires: help2man + +# Needed for older branches el8+, noop on f43+ +%{?sysusers_requires_compat} + +# not picked up automatically +Requires: %{_bindir}/flock +Requires: %{_bindir}/time + +# needed for actually building kmods: +Requires: %{_bindir}/rpmdev-vercmp +Requires: kmodtool >= 1.1-1 + +# needed to create CA/Keypair to sign modules +Requires: openssl + +# this should track in all stuff that is normally needed to compile modules: +Requires: bzip2 coreutils diffutils file findutils gawk gcc grep +Requires: gzip make sed tar unzip util-linux rpm-build + +# On EL, kABI list was renamed +%if 0%{?rhel} +Requires: (kernel-abi-stablelists if kernel-core) +%endif + +# We use a virtual provide that would match either +# kernel-devel or kernel-PAE-devel +Requires: kernel-devel-uname-r +# kernel-devel-matched enforces the same kernel version as the -devel +%if 0%{?fedora} || 0%{?rhel} >= 9 +Requires: (kernel-debug-devel-matched if kernel-debug-core) +Requires: (kernel-devel-matched if kernel-core) +%else +Suggests: (kernel-debug-devel if kernel-debug-core) +Suggests: (kernel-devel if kernel-core) +%endif +Suggests: (kernel-rt-devel if kernel-rt) + +# we create a special user that used by akmods to build kmod packages + +# systemd unit requirements. +BuildRequires: systemd +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +# Optional but good to have on recent kernel +Requires: pkgconfig(libelf) + +# We need grubby or systemd-boot to know the default kernel +# On EL7 assumes grubby is there by default - rhbz#2124086 +%if 0%{?fedora} || 0%{?rhel} > 7 +Requires: (grubby or sdubby) +%endif + +%description +Akmods startup script will rebuild akmod packages during system +boot, while its background daemon will build them for kernels right +after they were installed. + + +%prep +%setup -q -c -T +cp -p %{SOURCE9} %{SOURCE10} %{SOURCE15} . + + +%build +# Nothing to build + + +%install +mkdir -p %{buildroot}%{_usrsrc}/%{name} \ + %{buildroot}%{_sbindir} \ + %{buildroot}%{_sysconfdir}/rpm \ + %{buildroot}%{_sysconfdir}/pki/%{name}/certs \ + %{buildroot}%{_sysconfdir}/pki/%{name}/private \ + %{buildroot}%{_sysconfdir}/kernel/postinst.d \ + %{buildroot}%{_sysconfdir}/logrotate.d \ + %{buildroot}%{_localstatedir}/cache/%{name} \ + %{buildroot}%{_localstatedir}/log/%{name} \ + %{buildroot}%{_tmpfilesdir} + +install -pm 0755 %{SOURCE1} %{buildroot}%{_sbindir}/ +install -pm 0755 %{SOURCE2} %{buildroot}%{_sbindir}/ +install -pm 0755 %{SOURCE12} %{buildroot}%{_sbindir}/ +install -pm 0644 %{SOURCE14} %{buildroot}%{_sysconfdir}/logrotate.d/%{name} +install -pm 0640 %{SOURCE16} %{buildroot}%{_sysconfdir}/pki/%{name}/ +install -pm 0755 %{SOURCE17} %{buildroot}%{_sbindir}/kmodgenca +install -pm 0644 %{SOURCE20} %{buildroot}%{_tmpfilesdir}/%{name}.conf +install -dpm 0770 %{buildroot}%{_rundir}/%{name}/ + +mkdir -p %{buildroot}%{_prefix}/lib/kernel/install.d +install -pm 0755 %{SOURCE13} %{buildroot}%{_prefix}/lib/kernel/install.d/ +mkdir -p \ + %{buildroot}%{_unitdir} \ + %{buildroot}%{_presetdir} + +install -pm 0644 %{SOURCE0} %{buildroot}%{_presetdir}/ +install -pm 0644 %{SOURCE6} %{buildroot}%{_unitdir}/ +install -pm 0755 %{SOURCE7} %{buildroot}%{_sbindir}/ +install -pm 0644 %{SOURCE8} %{buildroot}%{_unitdir}/ +install -pm 0644 %{SOURCE11} %{buildroot}%{_unitdir}/ +install -pm 0644 %{SOURCE18} %{buildroot}%{_unitdir}/ +install -pm 0644 %{SOURCE19} %{buildroot}%{_unitdir}/ + +# Generate and install man pages. +mkdir -p %{buildroot}%{_mandir}/man1 +help2man -N -i %{SOURCE3} -s 1 \ + -o %{buildroot}%{_mandir}/man1/akmods.1 \ + %{buildroot}%{_sbindir}/akmods +help2man -N -i %{SOURCE3} -s 1 \ + -o %{buildroot}%{_mandir}/man1/akmodsbuild.1 \ + %{buildroot}%{_sbindir}/akmodsbuild + +install -m0644 -D %{SOURCE21} %{buildroot}%{_sysusersdir}/akmods.conf + + +%pre +%sysusers_create_compat %{SOURCE21} + +%post +%systemd_post akmods.service +%systemd_post akmods@.service +%systemd_post akmods-shutdown.service + +%preun +%systemd_preun akmods.service +%systemd_preun akmods@.service +%systemd_preun akmods-shutdown.service + +%postun +%systemd_postun akmods.service +%systemd_postun akmods@.service +%systemd_postun akmods-shutdown.service + + +%files +%doc README README.secureboot +%license LICENSE +%{_sbindir}/akmodsbuild +%{_sbindir}/akmods +%{_sbindir}/akmods-ostree-post +%{_sbindir}/kmodgenca +%dir %attr(750,root,akmods) %{_sysconfdir}/pki/%{name}/certs +%dir %attr(750,root,akmods) %{_sysconfdir}/pki/%{name}/private +%config(noreplace) %attr(640,root,akmods) %{_sysconfdir}/pki/%{name}/cacert.config.in +%config(noreplace) %{_sysconfdir}/logrotate.d/%{name} +%{_unitdir}/akmods.service +%{_unitdir}/akmods@.service +%{_sbindir}/akmods-shutdown +%{_unitdir}/akmods-shutdown.service +%{_prefix}/lib/kernel/install.d/95-akmodsposttrans.install +%attr(0644,root,root) %{_unitdir}/akmods-keygen.target +%attr(0644,root,root) %{_unitdir}/akmods-keygen@.service +%dir %attr(0770,root,akmods) %{_rundir}/%{name} +%{_tmpfilesdir}/%{name}.conf +# akmods was enabled in the default preset by f28 +%if 0%{?rhel} +%{_presetdir}/95-akmods.preset +%else +%exclude %{_presetdir}/95-akmods.preset +%endif +%{_usrsrc}/akmods +%dir %attr(-,akmods,akmods) %{_localstatedir}/cache/akmods +%dir %attr(0775,root,akmods) %{_localstatedir}/log/%{name} +%{_mandir}/man1/* +%{_sysusersdir}/akmods.conf + + +%changelog +%autochangelog diff --git a/akmods.sysusers.conf b/akmods.sysusers.conf new file mode 100644 index 0000000..6dd7280 --- /dev/null +++ b/akmods.sysusers.conf @@ -0,0 +1,3 @@ +#Type Name ID GECOS Home directory Shell +g akmods - - - - +u akmods - 'User is used by akmods to build akmod packages' /var/cache/akmods/ - diff --git a/akmods@.service b/akmods@.service new file mode 100644 index 0000000..32103ab --- /dev/null +++ b/akmods@.service @@ -0,0 +1,12 @@ +[Unit] +Description=Builds and install new kmods from akmod for a given kernel +Wants=akmods-keygen.target +After=akmods-keygen.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/systemd-inhibit --mode=block --what=idle:sleep:shutdown --who="akmods" --why="Akmods Transaction running" /usr/sbin/akmods --from-kernel-posttrans --kernels %i + +[Install] +WantedBy=multi-user.target diff --git a/akmodsbuild b/akmodsbuild new file mode 100644 index 0000000..23b0783 --- /dev/null +++ b/akmodsbuild @@ -0,0 +1,365 @@ +#!/bin/bash +# +# akmodbuild - Helper script for building kernel module SRPMs +# Copyright (c) 2007 Thorsten Leemhuis +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +myprog="akmodsbuild" +myver="0.6.2" + +# defaults that might get overwritten by user: +kernels="$(uname -r)" +numberofjobs=$(grep -c processor /proc/cpuinfo 2> /dev/null) +verboselevel=2 +outputdir="${PWD}" +srpms= + +init () +{ + ## startup checks + # prevent root-usage + if [[ -w /var ]] ; then + echo "ERROR: Not to be used as root; start as user or '${myprog}' instead." >&2 + exit 1 + fi + + # do we have everything we need to build for the kernels in question? + for kernel in ${kernels}; do + if [[ ! -e /usr/src/kernels/${kernel}/Makefile ]] && [[ ! -e /usr/lib/modules/${kernel}/build/Makefile ]] ; then + echo "ERROR: Files needed for building modules against kernel" >&2 + echo " ${kernel} could not be found as the following" >&2 + echo " directories are missing:" + echo " /usr/src/kernels/${kernel}/" >&2 + echo " /usr/lib/modules/${kernel}/build/" >&2 + exit 2 + fi + done + + if [[ ! -n "${srpms}" ]] ; then + echo "ERROR: Please provide a list of SRPM-files to build." + exit 2 + fi + + # SRPMS available? + for srpm in ${srpms}; do + if [[ ! -r "${srpm}" ]] ; then + echo "ERROR: Can't find SRPM ${srpm}" + exit 1 + fi + done + + # room to save things + if [[ ! -d "${outputdir}" ]] ; then + echo "ERROR: ${outputdir} is not a directory" >&2 + exit 1 + elif [[ ! -w "${outputdir}" ]] ; then + echo "ERROR: ${outputdir} is not a writable" >&2 + exit 1 + fi + + + # make sure this is a number + if ! (( numberofjobs > 0 )) ; then + echo "Warning: using hardcoded defaut value for number of jobs" + numberofjobs=2 + fi + + ## preparations + # tmpdir + if ! tmpdir="$(mktemp -d -p /tmp "${myprog}.XXXXXXXX")" ; then + echo "ERROR: Could create tempdir." + exit 1 + fi + + if [ -z "${target}" ] ; then + case "${kernels}" in + *x86_64_v4) target=x86_64_v4;; + *x86_64_v3) target=x86_64_v3;; + *x86_64_v2) target=x86_64_v2;; + *armv7hl) target=armv7hl;; + *) target="$(uname -m)" ;; + esac + fi + # buildtree + mkdir "${tmpdir}"/{BUILD,SOURCES,SPECS,SRPMS,RPMS,RPMS/"${target}"} + + # logfile + if [[ ! -n "${logfile}" ]] ; then + logfile="${tmpdir}/logfile" + fi + + if { [[ -e "${logfile}" ]] && [[ ! -w "${logfile}" ]] ; } || ! touch "${logfile}" ; then + echo "ERROR: Could not write logfile." + finally + exit 1 + fi +} + + +finally() +{ + # kill background jobs if needed + if [[ -n "${watch_jobid}" ]] ; then + kill "${watch_jobid}" + fi + if [[ -n "${rpmbuild_jobid}" ]] ; then + kill "${rpmbuild_jobid}" + fi + + # remove tmpfiles + if [[ -d "${tmpdir}" ]] ; then + rm -rf "${tmpdir}" + fi +} +trap "finally" 2 + + +akmods_echo() +{ + # where to output + local this_fd=${1} + shift + + # verboselevel + local this_verbose=${1} + shift + + if [[ "${1}" == "--not-logfile" ]] ; then + local notlogfile=true + shift + fi + + # output to console + if (( verboselevel >= this_verbose )) ; then + echo "$@" >&"${this_fd}" + fi + + # global logfile + if [[ ! -n "${notlogfile}" ]] ; then + echo "$@" >> "${logfile}" + fi +} + + +watch_rpmbuild() +{ + # background function to show rpmbuild progress + # does't use akmods_echo here; this stage handles the output on its own + # (seperate process and there is no need to log this) + if (( verboselevel == 2 )) ; then + tail --pid "${1}" -n +1 -s 0.1 -f "${2}" 2>/dev/null | grep --line-buffered -e '%prep' -e '%build' -e '%install' -e '%clean' | while read -r line ; do + if [[ "${line}" != "${line##*prep}" ]] ; then + echo -n "prep " + elif [[ "${line}" != "${line##*build}" ]] ; then + echo -n "build " + elif [[ "${line}" != "${line##*install}" ]] ; then + echo -n "install " + elif [[ "${line}" != "${line##*clean}" ]] ; then + echo -n "clean; " + # last linefeed is done by the caller + fi + done + elif (( verboselevel > 2 )) ; then + tail --pid "${1}" -n +1 -s 0.1 -f "${2}" + fi +} + +process_srpm() +{ + local source_rpm="${1}" + + # status info + akmods_echo 1 2 -n "* Rebuilding ${source_rpm} for kernel(s) ${kernels}: " + + # kick off rebuild into background + /usr/bin/time --format='%x' --output="${tmpdir}/.jobexit" rpmbuild \ + --define "_topdir ${tmpdir}/" \ + --define "_buildtree ${tmpdir}/BUILD" \ + --define "_specdir ${tmpdir}/SPECS" \ + --define "_sourcedir ${tmpdir}/SOURCES" \ + --define "_srcrpmdir ${tmpdir}/SRPMS" \ + --define "_rpmdir ${tmpdir}/RPMS" \ + --define "_smp_mflags -j${numberofjobs}" \ + --define "kernels ${kernels}" \ + --target "${target}" \ + --rebuild "${source_rpm}" 2>&1 | tee -a "${logfile}" > "${tmpdir}/.joblog" & + + local rpmbuild_jobid=$! + + # show progress + if (( verboselevel >= 2 )) ; then + watch_rpmbuild "${rpmbuild_jobid}" "${tmpdir}/.joblog" 2> /dev/null & + local watch_jobid=$! + fi + + # wait for rpmbuild + wait "${rpmbuild_jobid}" + local rpmbuild_returncode + rpmbuild_returncode=$(tail -n 1 "${tmpdir}/.jobexit") + unset rpmbuild_jobid + + # give watch_rpmbuild a moment to catch up; kill it if it does not + if (( verboselevel >= 2 )) ; then + sleep 0.5 + kill "${watch_jobid}" &> /dev/null + unset watch_jobid + fi + + # did rpmbuild succeed? + if (( rpmbuild_returncode != 0 )) ; then + # linefeed: + akmods_echo 1 2 "" + + akmods_echo 2 2 --not-logfile "rpmbuild failed with errorcode ${rpmbuild_returncode}; last 35 Lines of log:" + akmods_echo 2 2 --not-logfile "--- " + tail -n 35 "${tmpdir}/.joblog" >&2 + akmods_echo 2 2 --not-logfile "---" + return "${rpmbuild_returncode}" + fi + + # finish status for watch_rpmbuild + if (( verboselevel >= 2 )) ; then + akmods_echo 1 2 -n "Successful; " + fi + + local rpms_built + rpms_built="$(cd "${tmpdir}"/RPMS/"${target}" || exit ; echo *)" + + if ! mv "${tmpdir}/RPMS/${target}/"* "${outputdir}" ; then + # linefeed: + akmods_echo 1 2 "" + + akmods_echo 2 2 "Failed to move ${tmpdir}/RPMS/${target}/"* "to ${outputdir}" + return 128 + fi + + if (( verboselevel == 1 )) ; then + for rpm in ${rpms_built}; do + echo "${outputdir%%/}/${rpm}" + done + elif (( verboselevel >= 2 )) ; then + akmods_echo 1 2 "Saved ${rpms_built} in ${outputdir%%/}/" + fi + + + # finished + return 0 +} + +myprog_help () +{ + echo "Rebuilds kmod SRPM(s)" + echo $'\n'"Usage: ${myprog} [OPTIONS] " + echo $'\n'"Options:" + echo " -k, --kernels -- build for kernel-versions (output from 'uname -r')" + echo " -l, --logfile -- save rpmbuild output to " + echo " -o, --outputdir -- save rpms and logs here (current directory)" + echo " -t, --target -- target-arch (output from 'uname -m')" + echo " -v, --verbose -- increase verboseness" + echo " -q, --quiet -- be more quiet" + echo " -h, --help -- show usage" + echo " -V, --version -- show version" +} + +while [ "${1}" ] ; do + case "${1}" in + -k|--kernels) + shift + if [[ ! -n "${1}" ]] ; then + echo "ERROR: Please provide kernel-version(s) to build for together with --kernel" >&2 + exit 1 + fi + kernels="${1}" + shift + ;; + -l|--logfile) + shift + if [[ ! -n "${1}" ]] ; then + echo "ERROR: Please provide a filename together with --logfile" >&2 + exit 1 + fi + logfile="${1}" + shift + ;; + -o|--outputdir) + shift + if [[ ! -n "${1}" ]] ; then + echo "ERROR: Please provide the output directory together with --outputdir" >&2 + exit 1 + fi + outputdir="${1}" + shift + ;; + -t|--target) + shift + if [[ ! -n "${1}" ]] ; then + echo "ERROR: Please provide the target-arch together with --target" >&2 + exit 1 + fi + target="${1}" + shift + ;; + -v|--verbose) + (( verboselevel++ )) + shift + ;; + -q|--quiet) + (( verboselevel-- )) + shift + ;; + -h|--help) + myprog_help + exit 0 + ;; + -V|--version) + echo "${myprog} ${myver}" + exit 0 + ;; + --*) + echo "Error: Unknown option '${1}'." >&2 + myprog_help >&2 + exit 2 + ;; + *) + srpms="${srpms} ${1}" + shift + ;; + esac +done + +# sanity checks +init + +# go +for srpm in ${srpms}; do + process_srpm "${srpm}" + returncode=$? + + if (( returncode != 0 )) ; then + finally + exit "${returncode}" + fi +done + +# finished +finally + +exit 0 diff --git a/cacert.config.in b/cacert.config.in new file mode 100644 index 0000000..20a2098 --- /dev/null +++ b/cacert.config.in @@ -0,0 +1,41 @@ +# Default OpenSSL settings and configuration file for kmodgenca +# shell-script. +# +[ req ] +default_bits = 4096 +distinguished_name = req_distinguished_name +prompt = no +utf8 = yes +string_mask = utf8only +x509_extensions = req_exts + +[ req_distinguished_name ] +# Values settings +# +0.organizationName = Organization Name (eg, company) +organizationalUnitName = Organizational Unit Name (eg, section) +emailAddress = Email Address +emailAddress_max = 64 +localityName = Locality Name (eg, city) +stateOrProvinceName = State or Province Name (full name) +countryName = Country Name (2 letter code) +countryName_min = 2 +countryName_max = 2 +commonName = Common Name (eg, your name or your server\'s hostname) +commonName_max = 64 + +# Default values +# +0.organizationName_default = akmods local +organizationalUnitName_default = akmods +emailAddress_default = akmods@localhost.localdomain +localityName_default = None +stateOrProvinceName_default = None +countryName_default = XX +commonName_default = akmods local signing CA + +[ req_exts ] +basicConstraints = critical,CA:FALSE +keyUsage = digitalSignature +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid diff --git a/changelog b/changelog new file mode 100644 index 0000000..6bcb2f2 --- /dev/null +++ b/changelog @@ -0,0 +1,178 @@ +* Fri May 02 2025 Marcel Hetzendorfer - 0.6.0-11 +- Show building and installing on plymouth boot screen + +* Tue Feb 11 2025 Zbigniew Jędrzejewski-Szmek - 0.6.0-10 +- Add sysusers.d config file to allow rpm to create users/groups + automatically + +* Thu Jan 16 2025 Fedora Release Engineering - 0.6.0-9 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild + +* Wed Dec 11 2024 Nicolas Chauvet - 0.6.0-8 +- Update others hostname occurences + +* Tue Dec 10 2024 Nicolas Chauvet - 0.6.0-7 +- Drop hostname deps - rhbz#2330137 + +* Thu Nov 28 2024 Nicolas Chauvet - 0.6.0-6 +- Validate or discard default_kernel - rhbz#2270414 + +* Fri Nov 08 2024 Nicolas Chauvet - 0.6.0-5 +- Fix KEYNAME lengh - rhbz#2323702 + +* Wed Oct 02 2024 Rohan Barar - 0.6.0-4 +- Add robust missing key pair logic + +* Wed Oct 02 2024 Rohan Barar - 0.6.0-3 +- Improved error handling + Bug fixes + +* Tue Oct 01 2024 Rohan Barar - 0.6.0-2 +- Add check for elevated privileges + +* Tue Oct 01 2024 Nicolas Chauvet - 0.6.0-1 +- Bump akmods version + +* Tue Oct 01 2024 Nicolas Chauvet - 0.5.10-30 +- Remove duplicate akmodsposttrans call - rhbz#2011120 + +* Thu Sep 26 2024 Rohan Barar - 0.5.10-29 +- Avoid double error on empty user-provided key pair name. + +* Thu Sep 26 2024 Rohan Barar - 0.5.10-28 +- Corrected erroneous code introduced in previous commits. + +* Thu Sep 26 2024 Rohan Barar - 0.5.10-27 +- Fixed typo 'if' to 'fi'. + +* Thu Sep 26 2024 Rohan Barar - 0.5.10-26 +- Added check for existing key pair with same name as user-specified new + key pair name. + +* Thu Sep 26 2024 Rohan Barar - 0.5.10-25 +- Added ability for user to name key pair. + +* Sun Sep 22 2024 Rohan Barar - 0.5.10-24 +- Introduced loop to gracefully handle extremely rare key pair name + collision events. + +* Sat Sep 21 2024 Rohan Barar - 0.5.10-23 +- Refactor key pair naming scheme to enhance robustness + Removed collision + check and key pair backup function due to bug with ':' in file names + alongside superfluous nature of function given improved naming scheme. + +* Sat Sep 21 2024 Rohan Barar - 0.5.10-22 +- Removed 'sudo' prefixes as per request in PR #23. + +* Sat Sep 21 2024 Rohan Barar - 0.5.10-21 +- Further improvements to argument parsing logic. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-20 +- Improved clarity of exit status code comments. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-19 +- Revert "Utilise robust shebang." as per request on PR #23. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-18 +- Added support for combined single-letter arguments + Chowned symlinks. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-17 +- Improved mokutil error handling + Added sudo prefixes. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-16 +- Added error handling for failed cacert modification. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-15 +- Whitespace changes for consistency. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-14 +- Extract functions to enhance readability + Set 'commonName' to match + 'KEYNAME'. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-13 +- Added logic to detect broken existing key pairs. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-12 +- Improved user feedback in event of existing key pair. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-11 +- Updated copyright information. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-10 +- Various changes to avoid ShellCheck warnings. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-9 +- Align license to 80 character width. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-8 +- Utilise robust shebang. + +* Fri Sep 20 2024 Rohan Barar - 0.5.10-7 +- Removed hard-coded paths. + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.10-6 +- Fix parsing multiple kernel + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.10-5 +- Use check_kernel_devel return code as appropriate + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.10-4 +- Change check_kernel_devel() to return instead of exit + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.10-3 +- akmods --from-init only operates on current kernel + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.10-2 +- Deprecate akmods-shutdown script + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.10-1 +- Bump to akmods 0.5.10 + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.9-8 +- Only check for default_kernel is no value - rhbz#2293047 + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.9-7 +- Revert "Call Init before the argument parser" + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.9-6 +- Switch to use sdubby alternatives to grubby + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.9-5 +- Drop older rhel and use -core + +* Fri Aug 23 2024 Nicolas Chauvet - 0.5.9-4 +- Drop older rhel cases + +* Mon Aug 19 2024 Jonathan Wakely - 0.5.9-3 +- Fix bug URLs in man page + +* Wed Jul 17 2024 Fedora Release Engineering - 0.5.9-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild + +* Thu Jul 04 2024 Nicolas Chauvet - 0.5.9-1 +- akmods release 0.5.9 + +* Thu Jul 04 2024 Hans de Goede - 0.5.8-10 +- Fix intel-ipu6-kmod installation with kernel >= 6.10 + +* Thu Jul 04 2024 Marius Schwarz - 0.5.8-9 +- Call Init before the argument parser + +* Mon Jan 22 2024 Fedora Release Engineering - 0.5.8-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Fri Jan 19 2024 Fedora Release Engineering - 0.5.8-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Tue Dec 05 2023 Nicolas Chauvet - 0.5.8-6 +- Workaround for rhbz#1889136 when localpkg_gpgcheck=True + +* Wed Jul 19 2023 Fedora Release Engineering - 0.5.8-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Fri May 05 2023 Nicolas Chauvet - 0.5.8-1 +- Don't emit weak-deps from deprecated arches on all +- Allow akmods --rebuild to force rebuild+reinstall - rhbz#2140012 +- ensure to build for grub or systemd-boot default kernel - rhbz#2124086 +- Drop "which" as akmods dependency + +