Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for Secure Boot #3097

Merged
merged 18 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
/build/rpms/*-debuginfo-*.rpm
/build/rpms/*-debugsource-*.rpm
**/target/*
/tests
/sbkeys
/tests
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
/*.pem
/keys
/roles
/sbkeys/**/
bcressey marked this conversation as resolved.
Show resolved Hide resolved
/Licenses.toml
/licenses
*.run
Expand Down
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ ARG VARIANT_FAMILY
ARG VARIANT_FLAVOR
ARG REPO
ARG GRUB_SET_PRIVATE_VAR
ARG UEFI_SECURE_BOOT
ARG SYSTEMD_NETWORKD
ARG UNIFIED_CGROUP_HIERARCHY
ARG XFS_DATA_PARTITION
Expand Down Expand Up @@ -101,6 +102,7 @@ RUN rpmdev-setuptree \
&& echo "%bcond_without $(V=${VARIANT_FAMILY,,}; echo ${V//-/_})_family" >> .bconds \
&& echo "%bcond_without $(V=${VARIANT_FLAVOR:-no}; V=${V,,}; echo ${V//-/_})_flavor" >> .bconds \
&& echo -e -n "${GRUB_SET_PRIVATE_VAR:+%bcond_without grub_set_private_var\n}" >> .bconds \
&& echo -e -n "${UEFI_SECURE_BOOT:+%bcond_without uefi_secure_boot\n}" >> .bconds \
&& echo -e -n "${SYSTEMD_NETWORKD:+%bcond_without systemd_networkd\n}" >> .bconds \
&& echo -e -n "${UNIFIED_CGROUP_HIERARCHY:+%bcond_without unified_cgroup_hierarchy\n}" >> .bconds \
&& echo -e -n "${XFS_DATA_PARTITION:+%bcond_without xfs_data_partition\n}" >> .bconds \
Expand Down Expand Up @@ -200,13 +202,27 @@ ARG DATA_IMAGE_PUBLISH_SIZE_GIB
ARG KERNEL_PARAMETERS
ARG GRUB_SET_PRIVATE_VAR
ARG XFS_DATA_PARTITION
ARG UEFI_SECURE_BOOT
ENV VARIANT=${VARIANT} VERSION_ID=${VERSION_ID} BUILD_ID=${BUILD_ID} \
PRETTY_NAME=${PRETTY_NAME} IMAGE_NAME=${IMAGE_NAME} \
KERNEL_PARAMETERS=${KERNEL_PARAMETERS}
WORKDIR /root

USER root
RUN --mount=target=/host \
--mount=type=secret,id=PK.crt,target=/root/sbkeys/PK.crt \
bcressey marked this conversation as resolved.
Show resolved Hide resolved
--mount=type=secret,id=KEK.crt,target=/root/sbkeys/KEK.crt \
--mount=type=secret,id=db.crt,target=/root/sbkeys/db.crt \
--mount=type=secret,id=vendor.crt,target=/root/sbkeys/vendor.crt \
--mount=type=secret,id=shim-sign.key,target=/root/sbkeys/shim-sign.key \
--mount=type=secret,id=shim-sign.crt,target=/root/sbkeys/shim-sign.crt \
--mount=type=secret,id=code-sign.key,target=/root/sbkeys/code-sign.key \
--mount=type=secret,id=code-sign.crt,target=/root/sbkeys/code-sign.crt \
--mount=type=secret,id=config-sign.key,target=/root/sbkeys/config-sign.key \
--mount=type=secret,id=kms-sign.json,target=/root/.config/aws-kms-pkcs11/config.json \
--mount=type=secret,id=aws-access-key-id.env,target=/root/.aws/aws-access-key-id.env \
--mount=type=secret,id=aws-secret-access-key.env,target=/root/.aws/aws-secret-access-key.env \
--mount=type=secret,id=aws-session-token.env,target=/root/.aws/aws-session-token.env \
/host/tools/rpm2img \
--package-dir=/local/rpms \
--output-dir=/local/output \
Expand All @@ -219,6 +235,7 @@ RUN --mount=target=/host \
--ovf-template="/host/variants/${VARIANT}/template.ovf" \
${XFS_DATA_PARTITION:+--xfs-data-partition=yes} \
${GRUB_SET_PRIVATE_VAR:+--with-grub-set-private-var=yes} \
${UEFI_SECURE_BOOT:+--with-uefi-secure-boot=yes} \
&& echo ${NOCACHE}

# =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=
Expand Down
54 changes: 53 additions & 1 deletion Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ BUILDSYS_STATE_DIR = "${BUILDSYS_BUILD_DIR}/state"
BUILDSYS_IMAGES_DIR = "${BUILDSYS_BUILD_DIR}/images"
BUILDSYS_TOOLS_DIR = "${BUILDSYS_ROOT_DIR}/tools"
BUILDSYS_SOURCES_DIR = "${BUILDSYS_ROOT_DIR}/sources"
BUILDSYS_SBKEYS_DIR = "${BUILDSYS_ROOT_DIR}/sbkeys"
BUILDSYS_SBKEYS_PROFILE = { script = ['echo "${BUILDSYS_SBKEYS_PROFILE:-local}"'] }
BUILDSYS_TIMESTAMP = { script = ["date +%s"] }
BUILDSYS_VERSION_BUILD = { script = ["git describe --always --dirty --exclude '*' || echo 00000000"] }
# For now, release config path can't be overridden with -e, because it's used
Expand Down Expand Up @@ -157,6 +159,9 @@ BUILDSYS_NAME_FRIENDLY = "${BUILDSYS_NAME_VARIANT}-v${BUILDSYS_VERSION_IMAGE}"
# For variant build artifacts.
BUILDSYS_VARIANT_DIR = "${BUILDSYS_OUTPUT_DIR}/${BUILDSYS_VERSION_FULL}"

# Depends on ${BUILDSYS_SBKEYS_DIR} and ${BUILDSYS_SBKEYS_PROFILE}.
BUILDSYS_SBKEYS_PROFILE_DIR = "${BUILDSYS_SBKEYS_DIR}/${BUILDSYS_SBKEYS_PROFILE}"

# Path to repo-specific root role.
PUBLISH_REPO_ROOT_JSON = "${BUILDSYS_ROOT_DIR}/roles/${PUBLISH_REPO}.root.json"
# If you don't specify a signing key in Infra.toml, we generate one at this path.
Expand Down Expand Up @@ -680,6 +685,52 @@ cargo install \
'''
]

[tasks.build-sbkeys]
dependencies = ["fetch"]
script_runner = "bash"
script = [
'''
# Check the profile for all files needed for Secure Boot signing.
profile="${BUILDSYS_SBKEYS_PROFILE_DIR}"

found=0
# A local profile has signing keys and certificates, while an AWS profile
# has a config for the aws-kms-pkcs11 helper. Either type is supported.
if [ -s "${profile}/shim-sign.key" ] && \
bcressey marked this conversation as resolved.
Show resolved Hide resolved
[ -s "${profile}/shim-sign.crt" ] && \
[ -s "${profile}/code-sign.key" ] && \
[ -s "${profile}/code-sign.crt" ] ; then
let found+=1
elif [ -s "${profile}/kms-sign.json" ] ; then
let found+=1
fi

expected=1
for f in {PK,KEK,db,vendor}.crt config-sign.key efi-vars.{json,aws} ; do
let expected+=1
[ -s "${profile}/${f}" ] && let found+=1
done

if [ "${found}" -eq "${expected}" ] ; then
exit 0
fi

if [ "${found}" -gt 0 ] ; then
echo "Incomplete Secure Boot signing profile found in ${profile}." >&2
echo "Missing $(( expected - found )) files." >&2
exit 1
fi

echo "No Secure Boot signing profile found in ${profile}." >&2
echo "Generating local keys." >&2

mkdir -p "${BUILDSYS_SBKEYS_PROFILE_DIR}"
${BUILDSYS_SBKEYS_DIR}/generate-local-sbkeys \
bcressey marked this conversation as resolved.
Show resolved Hide resolved
--sdk-image "${BUILDSYS_SDK_IMAGE}" \
--output-dir "${BUILDSYS_SBKEYS_PROFILE_DIR}"
'''
]

# We need Cargo version 1.51 or higher in order to build a workspace's
# dependency during build-package
[tasks.check-cargo-version]
Expand Down Expand Up @@ -792,7 +843,7 @@ cargo build \
]

[tasks.build-variant]
dependencies = ["fetch-sdk", "build-tools", "publish-setup"]
dependencies = ["fetch-sdk", "build-tools", "build-sbkeys", "publish-setup"]
script = [
'''
export PATH="${BUILDSYS_TOOLS_DIR}/bin:${PATH}"
Expand Down Expand Up @@ -1177,6 +1228,7 @@ pubsys \
"${data_volume_args[@]}" \
\
--variant-manifest "${BUILDSYS_ROOT_DIR}/variants/${BUILDSYS_VARIANT}/Cargo.toml" \
--uefi-data "${BUILDSYS_SBKEYS_PROFILE_DIR}/efi-vars.aws" \
--arch "${BUILDSYS_ARCH}" \
--name "${ami_name}" \
--description "${PUBLISH_AMI_DESCRIPTION:-${ami_name}}" \
Expand Down
17 changes: 17 additions & 0 deletions PROVISIONING-METAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,20 @@ docker run --rm \
"${SDK_IMAGE}" \
bootconfig -l /tmp/bootconfig.data
```

### Enable Secure Boot

Starting with metal-k8s-1.28, the Bottlerocket images for bare metal support Secure Boot when used on a platform with UEFI firmware.
UEFI boot mode must be used, rather than legacy BIOS boot mode, and Secure Boot must be enabled.
bcressey marked this conversation as resolved.
Show resolved Hide resolved
The UEFI firmware may provide a Compatibility Support Module (CSM) option to enable legacy BIOS emulation.
The CSM option must not be enabled.
These options can be set in the firmware setup menu, which can be accessed during boot by pressing a certain key (such as F2 or F12).

Many Linux distros ship a copy of the [shim](https://github.com/rhboot/shim) bootloader signed by Microsoft with a key that is trusted by default.
Although Bottlerocket also uses `shim`, its copy is not signed by Microsoft and will not be trusted without additional configuration.
After installing Bottlerocket, the appropriate vendor certificate can be found on the EFI System Partition (ESP).
The firmware setup menu should provide an option to import a new vendor certificate by selecting a file on the ESP.
Either the PEM format (`db.crt`) or DER format (`db.cer`) certificate can be imported, depending on what the firmware supports.

The firmware setup menu should be password-protected to prevent unauthorized changes to the Secure Boot configuration.
Please refer to the documentation from your hardware vendor for more information on this procedure.
49 changes: 39 additions & 10 deletions SECURITY_FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ We will add and enhance security features over time based on these goals.

## Overview

| Feature | Version |
| :-------------------------------------------------------------------------------- | :-----: |
| [Automated security updates](#automated-security-updates) | 0.3.0 |
| [Immutable rootfs backed by dm-verity](#immutable-rootfs-backed-by-dm-verity) | 0.3.0 |
| [Stateless tmpfs for /etc](#stateless-tmpfs-for-etc) | 0.3.0 |
| [No shell or interpreters installed](#no-shell-or-interpreters-installed) | 0.3.0 |
| [Executables built with hardening flags](#executables-built-with-hardening-flags) | 0.3.0 |
| [SELinux enabled in enforcing mode](#selinux-enabled-in-enforcing-mode) | 0.3.0 |
| Feature | Version |
| :-------------------------------------------------------------------------------- | :------: |
| [Automated security updates](#automated-security-updates) | 0.3.0 |
| [Immutable rootfs backed by dm-verity](#immutable-rootfs-backed-by-dm-verity) | 0.3.0 |
| [Stateless tmpfs for /etc](#stateless-tmpfs-for-etc) | 0.3.0 |
| [No shell or interpreters installed](#no-shell-or-interpreters-installed) | 0.3.0 |
| [Executables built with hardening flags](#executables-built-with-hardening-flags) | 0.3.0 |
| [SELinux enabled in enforcing mode](#selinux-enabled-in-enforcing-mode) | 0.3.0 |
| [Kernel lockdown in integrity mode](#kernel-lockdown-in-integrity-mode) | 1.1.0 |
| [Secure Boot enabled](#secure-boot-enabled) | 1.15.0 |

The version listed indicates the first release of Bottlerocket that included the feature.
Features may evolve or improve over time.
Expand Down Expand Up @@ -71,8 +73,8 @@ The kernel is configured to restart if corruption is detected.
That allows the system to fail closed if the underlying block device is unexpectedly modified and the node is in an unknown state.
The uncontrolled reboot will disrupt running containers, which can trigger alarms and prompt administrators to investigate.

Although this provides a powerful layer of protection, it is **incomplete**.
An attacker with full access to the block device could alter both the verity metadata and the contents of the root filesystem.
Although this provides a powerful layer of protection, it is incomplete unless [Secure Boot is enabled](#secure-boot-enabled).
Otherwise, an attacker with full access to the block device could alter both the verity metadata and the contents of the root filesystem.

### Stateless tmpfs for /etc

Expand Down Expand Up @@ -151,3 +153,30 @@ The policy in Bottlerocket has the following objectives:

The policy is currently aimed at hardening the OS against persistent threats.
Future enhancements to the policy will focus on mitigating the impact of OS vulnerabilities, and protecting containers from other containers.

### Kernel lockdown in integrity mode

Bottlerocket enables Lockdown in "integrity" mode by default on most variants.

Lockdown is a Linux Security Module (LSM) that blocks certain actions which could compromise the Linux kernel.
As with SELinux, even processes that run as root with full capabilities are subject to these restrictions.

Certain variants such as `*-nvidia` need to load unsigned kernel modules at runtime.
This is prohibited by the "integrity" mode, but required for the hardware to work as expected.
On these variants, Lockdown is set to "none" instead.

### Secure Boot enabled

Bottlerocket enables Secure Boot for all new variants on platforms that support UEFI boot.
bcressey marked this conversation as resolved.
Show resolved Hide resolved

The goal is to prevent unsigned, untrusted code from running at any point until containers are started.
This is achieved by establishing the following chain of trust:
1) The trusted platform firmware verifies that shim is signed correctly, then loads it.
2) shim verifies that grub is signed correctly, then loads it.
3) grub verifies that its grub.cfg is signed correctly, then loads it.
4) grub verifies that the Linux kernel is signed correctly, then loads it.
5) The Linux kernel verifies that the [immutable root filesystem](#immutable-rootfs-backed-by-dm-verity) has not been altered.

Secure Boot only applies to platforms using UEFI firmware, and it is only enforced when the feature is enabled in the firmware.
Therefore, systems using the legacy BIOS boot mode cannot benefit from Secure Boot.
This includes Xen-based EC2 instance types, and bare metal machines configured to emulate the legacy BIOS boot mode.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
From 2dd65e321c9786dbec249e0826c58f530bcb5883 Mon Sep 17 00:00:00 2001
From: Markus Boehme <[email protected]>
Date: Fri, 28 Oct 2022 13:39:03 +0000
Subject: [PATCH] util/mkimage: Bump EFI PE header size to accommodate .sbat
section

With the --sbat option mkimage can embed SBAT metadata into a dedicated
.sbat section of the EFI image. However, no space was explicitly
reserved for this section in the section table of the PE header.

The miss has no adverse effects since there was enough padding in the
header anyway due to alignment constraints. An earlier commit,
a51f953f4ee8 ("mkimage: Align efi sections on 4k boundary"), increased
alignment to 4 KiB, so that the extra section table entry fit
comfortably inside the space reserved for the entire header anyway.

Fixes: b11547137703 ("util/mkimage: Add an option to import SBAT metadata into a .sbat section")
Signed-off-by: Markus Boehme <[email protected]>
---
util/mkimage.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/util/mkimage.c b/util/mkimage.c
index c3d33aa..2db1045 100644
--- a/util/mkimage.c
+++ b/util/mkimage.c
@@ -65,14 +65,14 @@
+ GRUB_PE32_SIGNATURE_SIZE \
+ sizeof (struct grub_pe32_coff_header) \
+ sizeof (struct grub_pe32_optional_header) \
- + 4 * sizeof (struct grub_pe32_section_table), \
+ + 5 * sizeof (struct grub_pe32_section_table), \
GRUB_PE32_FILE_ALIGNMENT)

#define EFI64_HEADER_SIZE ALIGN_UP (GRUB_PE32_MSDOS_STUB_SIZE \
+ GRUB_PE32_SIGNATURE_SIZE \
+ sizeof (struct grub_pe32_coff_header) \
+ sizeof (struct grub_pe64_optional_header) \
- + 4 * sizeof (struct grub_pe32_section_table), \
+ + 5 * sizeof (struct grub_pe32_section_table), \
GRUB_PE32_FILE_ALIGNMENT)

static const struct grub_install_image_target_desc image_targets[] =
--
2.39.0

Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
From 45468642affc3d641b5d73c5d6e63e1578bf9150 Mon Sep 17 00:00:00 2001
From: Markus Boehme <[email protected]>
Date: Fri, 28 Oct 2022 16:09:05 +0000
Subject: [PATCH] util/mkimage: avoid adding section table entry outside PE EFI
header

The number of sections in a PE EFI image can vary depending on the
options passed to mkimage, but their maximum number must be known at
compile time. Potentially adding a new section to a PE EFI image
therefore requires changes in at least two places of the code.

The prior commit fixed a situation where the maximum number of sections
was not bumped while implementing support for an new section. Catch
these situations at runtime rather than silently relying on sufficient
padding being available for the new section table entry or overwriting
part of the image.

Signed-off-by: Markus Boehme <[email protected]>
---
util/mkimage.c | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/util/mkimage.c b/util/mkimage.c
index 2db1045..1455c94 100644
--- a/util/mkimage.c
+++ b/util/mkimage.c
@@ -821,6 +821,7 @@ grub_install_get_image_targets_string (void)
*/
static struct grub_pe32_section_table *
init_pe_section(const struct grub_install_image_target_desc *image_target,
+ const char *pe_img,
struct grub_pe32_section_table *section,
const char * const name,
grub_uint32_t *vma, grub_uint32_t vsz, grub_uint32_t valign,
@@ -828,6 +829,15 @@ init_pe_section(const struct grub_install_image_target_desc *image_target,
grub_uint32_t characteristics)
{
size_t len = strlen (name);
+ const char *pe_header_end;
+
+ if (image_target->voidp_sizeof == 4)
+ pe_header_end = pe_img + EFI32_HEADER_SIZE;
+ else
+ pe_header_end = pe_img + EFI64_HEADER_SIZE;
+
+ if ((char *) section >= pe_header_end)
+ grub_util_error (_("section table space exhausted trying to add %s"), name);

if (len > sizeof (section->name))
grub_util_error (_("section name %s length is bigger than %lu"),
@@ -1438,7 +1448,7 @@ grub_install_generate_image (const char *dir, const char *prefix,
/* The sections. */
PE_OHDR (o32, o64, code_base) = grub_host_to_target32 (vma);
PE_OHDR (o32, o64, code_size) = grub_host_to_target32 (layout.exec_size);
- section = init_pe_section (image_target, section, ".text",
+ section = init_pe_section (image_target, pe_img, section, ".text",
&vma, layout.exec_size,
image_target->section_align,
&raw_data, layout.exec_size,
@@ -1452,7 +1462,7 @@ grub_install_generate_image (const char *dir, const char *prefix,
ALIGN_UP (total_module_size,
GRUB_PE32_FILE_ALIGNMENT));

- section = init_pe_section (image_target, section, ".data",
+ section = init_pe_section (image_target, pe_img, section, ".data",
&vma, scn_size, image_target->section_align,
&raw_data, scn_size,
GRUB_PE32_SCN_CNT_INITIALIZED_DATA |
@@ -1460,7 +1470,7 @@ grub_install_generate_image (const char *dir, const char *prefix,
GRUB_PE32_SCN_MEM_WRITE);

scn_size = pe_size - layout.reloc_size - sbat_size - raw_data;
- section = init_pe_section (image_target, section, "mods",
+ section = init_pe_section (image_target, pe_img, section, "mods",
&vma, scn_size, image_target->section_align,
&raw_data, scn_size,
GRUB_PE32_SCN_CNT_INITIALIZED_DATA |
@@ -1472,7 +1482,7 @@ grub_install_generate_image (const char *dir, const char *prefix,
pe_sbat = pe_img + raw_data;
grub_util_load_image (sbat_path, pe_sbat);

- section = init_pe_section (image_target, section, ".sbat",
+ section = init_pe_section (image_target, pe_img, section, ".sbat",
&vma, sbat_size,
image_target->section_align,
&raw_data, sbat_size,
@@ -1484,7 +1494,7 @@ grub_install_generate_image (const char *dir, const char *prefix,
PE_OHDR (o32, o64, base_relocation_table.rva) = grub_host_to_target32 (vma);
PE_OHDR (o32, o64, base_relocation_table.size) = grub_host_to_target32 (scn_size);
memcpy (pe_img + raw_data, layout.reloc_section, scn_size);
- init_pe_section (image_target, section, ".reloc",
+ init_pe_section (image_target, pe_img, section, ".reloc",
&vma, scn_size, image_target->section_align,
&raw_data, scn_size,
GRUB_PE32_SCN_CNT_INITIALIZED_DATA |
--
2.39.0

Loading