diff --git a/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor.bb b/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor.bb index 584b97660f..5ed0385517 100644 --- a/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor.bb +++ b/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor.bb @@ -20,6 +20,7 @@ SRC_URI += " \ file://balena-supervisor-healthcheck \ file://tmpfiles-supervisor.conf \ file://migrate-supervisor-state.service \ + file://balena-supervisor-security-status.service \ " SYSTEMD_SERVICE:${PN} = " \ @@ -27,6 +28,7 @@ SYSTEMD_SERVICE:${PN} = " \ update-balena-supervisor.service \ update-balena-supervisor.timer \ migrate-supervisor-state.service \ + balena-supervisor-security-status \ " FILES:${PN} += " \ @@ -48,6 +50,9 @@ RDEPENDS:${PN} = " \ balena-config-vars \ systemd \ os-helpers-api \ + os-helpers-supervisor \ + os-helpers-fs \ + os-helpers-sb \ " python () { @@ -104,6 +109,7 @@ do_install () { install -c -m 0644 ${WORKDIR}/update-balena-supervisor.service ${D}${systemd_unitdir}/system install -c -m 0644 ${WORKDIR}/update-balena-supervisor.timer ${D}${systemd_unitdir}/system install -c -m 0644 ${WORKDIR}/migrate-supervisor-state.service ${D}${systemd_unitdir}/system + install -c -m 0644 ${WORKDIR}/balena-supervisor-security-status.service ${D}${systemd_unitdir}/system # symlinks to legacy resin-supervisor systemd unit files ln -s balena-supervisor.service ${D}${systemd_unitdir}/system/resin-supervisor.service ln -s update-balena-supervisor.service ${D}${systemd_unitdir}/system/update-resin-supervisor.service diff --git a/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/balena-supervisor-security-status.service b/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/balena-supervisor-security-status.service new file mode 100644 index 0000000000..46261b51fc --- /dev/null +++ b/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/balena-supervisor-security-status.service @@ -0,0 +1,10 @@ +[Unit] +Description=Expose Secureboot & FDE status to the supervisor + +[Service] +Type=oneshot +ExecStart=/bin/sh -c ". /usr/libexec/os-helpers-supervisor && writeSecurityStatus" +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/balena-supervisor.service b/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/balena-supervisor.service index 6f3ac36876..a7aafea35f 100644 --- a/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/balena-supervisor.service +++ b/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/balena-supervisor.service @@ -16,9 +16,11 @@ After=\ bind-etc-systemd-system-resin.target.wants.service \ bind-etc-balena-supervisor.service \ migrate-supervisor-state.service \ - extract-balena-ca.service + extract-balena-ca.service \ + balena-supervisor-security-status.service Wants=balena.service ConditionPathExists=/etc/balena-supervisor/supervisor.conf +ConditionPathExists=/var/run/supervisor [Service] Type=simple @@ -28,6 +30,7 @@ WatchdogSec=180 SyslogIdentifier=balena-supervisor EnvironmentFile=/etc/balena-supervisor/supervisor.conf EnvironmentFile=-/tmp/update-supervisor.conf +ExecStartPre=mkdir -p /var/run/supervisor ExecStartPre=-@BINDIR@/balena stop resin_supervisor ExecStartPre=-@BINDIR@/balena stop balena_supervisor ExecStartPre=/bin/systemctl is-active balena.service diff --git a/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/start-balena-supervisor b/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/start-balena-supervisor index b05415a842..ca1737fbfe 100644 --- a/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/start-balena-supervisor +++ b/meta-balena-common/recipes-containers/balena-supervisor/balena-supervisor/start-balena-supervisor @@ -71,6 +71,7 @@ runSupervisor() { --mount type=bind,source=/var/log/supervisor-log,target=/var/log \ --mount type=bind,source=/etc/ssl/certs,target=/etc/ssl/certs,readonly \ --mount type=bind,source=/usr/share/ca-certificates,target=/usr/share/ca-certificates,readonly \ + --mount type=bind,source=/var/run/supervisor/,target=/var/run/supervisor/,readonly \ --mount type=bind,source=/,target=/mnt/root \ -e DOCKER_ROOT=/mnt/root/var/lib/docker \ -e DOCKER_SOCKET=/var/run/balena-engine.sock \ diff --git a/meta-balena-common/recipes-support/os-helpers/os-helpers.bb b/meta-balena-common/recipes-support/os-helpers/os-helpers.bb index 9d3f4461f2..a434802d06 100644 --- a/meta-balena-common/recipes-support/os-helpers/os-helpers.bb +++ b/meta-balena-common/recipes-support/os-helpers/os-helpers.bb @@ -23,6 +23,7 @@ SRC_URI = " \ file://os-helpers-api \ file://os-helpers-efi \ file://os-helpers-sb \ + file://os-helpers-supervisor \ file://safe_reboot \ " S = "${WORKDIR}" @@ -39,6 +40,7 @@ PACKAGES = " \ ${PN}-reboot \ ${PN}-efi \ ${PN}-sb \ + ${PN}-supervisor \ " do_install() { @@ -52,6 +54,7 @@ do_install() { ${WORKDIR}/os-helpers-api \ ${WORKDIR}/os-helpers-efi \ ${WORKDIR}/os-helpers-sb \ + ${WORKDIR}/os-helpers-supervisor \ ${WORKDIR}/safe_reboot \ ${D}${libexecdir} sed -i "s,@@BALENA_CONF_UNIT_STORE@@,${BALENA_CONF_UNIT_STORE},g" ${D}${libexecdir}/os-helpers-config @@ -67,6 +70,7 @@ FILES:${PN}-api = "${libexecdir}/os-helpers-api" FILES:${PN}-reboot = "${libexecdir}/safe_reboot" FILES:${PN}-efi = "${libexecdir}/os-helpers-efi" FILES:${PN}-sb = "${libexecdir}/os-helpers-sb" +FILES:${PN}-sb = "${libexecdir}/os-helpers-supervisor" do_test_api() { if [ "${BB_NO_NETWORK}" = "1" ]; then diff --git a/meta-balena-common/recipes-support/os-helpers/os-helpers/os-helpers-efi b/meta-balena-common/recipes-support/os-helpers/os-helpers/os-helpers-efi index 1ef3f8205a..d7352a0295 100644 --- a/meta-balena-common/recipes-support/os-helpers/os-helpers/os-helpers-efi +++ b/meta-balena-common/recipes-support/os-helpers/os-helpers/os-helpers-efi @@ -44,3 +44,54 @@ setup_mode_enabled() { user_mode_enabled() { secureboot_enabled && ! setup_mode_enabled } + +# return PK and KEK keys metadata as a json array +get_SB_keys() { + tmp_dir="/tmp/efi_extract_$(openssl rand -hex 6)" + mkdir -p "$tmp_dir" + + # Function to extract and process certificates + extract_and_process() { + local efi_var="$1" + local efi_readvar_output="${tmp_dir}/${efi_var}" + local certs_output_prefix="${efi_readvar_output}-certs" + local json_entries=() + + # Read EFI variable and extract certificates + efi-readvar -v "$efi_var" -o "$efi_readvar_output" > /dev/null 2>&1 + sig-list-to-certs "$efi_readvar_output" "$certs_output_prefix" > /dev/null 2>&1 + + # Process all extracted certificates + for cert_file in "${certs_output_prefix}"-*.der; do + if [[ -f "$cert_file" ]]; then + # Convert DER to PEM + pem_file="${cert_file%.der}.pem" + openssl x509 -inform der -in "$cert_file" -out "$pem_file" > /dev/null 2>&1 + + # Extract certificate details + issuer=$(openssl x509 -in "$pem_file" -noout -issuer | sed 's/issuer=//') + subject=$(openssl x509 -in "$pem_file" -noout -subject | sed 's/subject=//') + validity=$(openssl x509 -in "$pem_file" -noout -dates | tr '\n' ' ' | sed 's/ *$//') + serial=$(openssl x509 -in "$pem_file" -noout -serial | sed 's/serial=//') + sha256_fingerprint=$(openssl x509 -in "$pem_file" -noout -fingerprint -sha256 | cut -d'=' -f2 | tr -d ':') + + # Construct JSON object for this certificate + json_entry="{\"type\":\"$efi_var\",\"issuer\":\"$issuer\",\"subject\":\"$subject\",\"validity\":\"$validity\",\"serial\":\"$serial\",\"sha256\":\"$sha256_fingerprint\"}" + + # Add JSON entry to the list + json_entries+=("$json_entry") + fi + done + + # Join JSON entries into a valid JSON array and return + echo "${json_entries[@]}" | jq -s . + } + + pk_json=$(extract_and_process "PK") + kek_json=$(extract_and_process "KEK") + + rm -rf "$tmp_dir" + + # Merge PK and KEK JSON arrays + echo "$pk_json" "$kek_json" | jq -s 'add' +} \ No newline at end of file diff --git a/meta-balena-common/recipes-support/os-helpers/os-helpers/os-helpers-supervisor b/meta-balena-common/recipes-support/os-helpers/os-helpers/os-helpers-supervisor new file mode 100644 index 0000000000..413bce563e --- /dev/null +++ b/meta-balena-common/recipes-support/os-helpers/os-helpers/os-helpers-supervisor @@ -0,0 +1,59 @@ +#!/bin/sh +# Copyright 2025 Balena Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# security.json is a file that is read by the supervisor to get the secureboot and encryption status +# it can be extended in the future to include more security related information +# altenratively, we could replace it with a 2-way communication channel between the supervisor and the host (gRPC or ...) + +# note that the /var/run/supervisor folder is mounted in the SV container + +# vi: ft=sh + +# shellcheck disable=SC1091 +[ -f "/usr/libexec/os-helpers-sb" ] && . /usr/libexec/os-helpers-sb +# shellcheck disable=SC1091 +[ -f "/usr/libexec/os-helpers-fs" ] && . /usr/libexec/os-helpers-fs +# shellcheck disable=SC1091 +[ -f "/usr/libexec/os-helpers-fs" ] && . /usr/libexec/os-helpers-efi + +SUPERVISOR_RUN_DIR="/var/run/supervisor" + +writeSecurityStatus() { + mkdir -p "$SUPERVISOR_RUN_DIR" + + # is_secured is a function from os-helpers-sb which returns 0 if secureboot is enabled + if is_secured; then + secureboot=true + else + secureboot=false + fi + + # determine if the data partition is encrypted + data_partition=$(get_dev_path_from_label "resin-data") + if is_part_encrypted ${data_partition}; then + fullDiskEncryption=true + else + fullDiskEncryption=false + fi + + # get_SB_keys is a function from os-helpers-efi that returns the PK and KEK keys metadata as a json array + cat > "$SUPERVISOR_RUN_DIR/security.json" <