diff --git a/Dockerfile b/Dockerfile
index 1293f72..21ffad4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,20 +14,18 @@ RUN apt-get update && \
bison \
build-essential \
cpio \
- curl \
debhelper \
fakeroot \
flex \
gcc-12-plugin-dev \
- gettext-base \
git \
kmod \
libelf-dev \
liblz4-tool \
libssl-dev \
- lsb-release \
ncurses-dev \
python3 \
+ python3-jinja2 \
python3-requests \
rsync \
wget \
@@ -35,9 +33,10 @@ RUN apt-get update && \
RUN groupadd -g ${GID} ${USERNAME} && useradd -m -d /home/${USERNAME} -g ${GID} -u ${UID} ${USERNAME}
-COPY build-kernel.sh /usr/local/bin/build-kernel.sh
+COPY build-kernel.py /usr/local/bin/build-kernel.py
COPY grsecurity-urls.py /usr/local/bin/grsecurity-urls.py
COPY debian /debian
+COPY pubkeys/ /pubkeys
RUN mkdir -p -m 0755 /kernel /patches-grsec /output
RUN chown ${USERNAME}:${USERNAME} /kernel /patches-grsec /output
@@ -46,6 +45,5 @@ WORKDIR /kernel
# VOLUME ["/kernel"]
USER ${USERNAME}
-COPY pubkeys/ /pubkeys
-CMD ["/usr/local/bin/build-kernel.sh"]
+CMD ["/usr/local/bin/build-kernel.py"]
diff --git a/build-kernel.py b/build-kernel.py
new file mode 100755
index 0000000..adc7e0c
--- /dev/null
+++ b/build-kernel.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+import os
+import re
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+import jinja2
+import requests
+
+
+def render_template(filename, context):
+ template_content = Path(f"{filename}.j2").read_text()
+ template = jinja2.Template(template_content)
+ rendered_content = template.render(context)
+ Path(filename).write_text(rendered_content)
+
+
+def main():
+ # Whether to use grsecurity patches
+ grsecurity = os.environ.get("GRSECURITY") == "1"
+ # Desired specific Linux version to download (e.g. "5.15.100")
+ linux_version = os.environ.get("LINUX_VERSION")
+ # Desired Linux major version to download (e.g. "5.15", "6.6")
+ linux_major_version = os.environ.get("LINUX_MAJOR_VERSION")
+ # Local version flavor, e.g. "securedrop", "workstation", "tiny"
+ local_version = os.environ["LOCALVERSION"]
+ # Increment if we need to build the same specific version again (e.g. metapackage changes)
+ build_version = os.environ.get("BUILD_VERSION", "1")
+
+ source_date_epoch = os.environ["SOURCE_DATE_EPOCH"]
+ source_date_epoch_formatted = subprocess.check_output(
+ ["date", "-R", "-d", f"@{source_date_epoch}"], text=True
+ ).strip()
+ for line in Path("/etc/os-release").read_text().splitlines():
+ if line.startswith("VERSION_CODENAME="):
+ version_codename = line.split("=")[1].strip()
+ break
+
+ # Check output directory
+ if not os.path.isdir("/output") or not os.access("/output", os.W_OK):
+ print("WARNING: Output directory /output not found or not writable")
+ print("WARNING: To save packages, you must mount /output as a volume")
+ sys.exit(1)
+
+ # Fetch grsecurity patchset if desired
+ if grsecurity:
+ # TODO: invoke this through normal Python means
+ linux_version = subprocess.check_output(
+ ["/usr/local/bin/grsecurity-urls.py", "--print-version"], text=True
+ ).strip()
+ print("Will include grsecurity patch for kernel", linux_version)
+ with open("/patches-grsec/grsec", "wb") as f:
+ # TODO: invoke this through normal Python means
+ subprocess.run(["/usr/local/bin/grsecurity-urls.py"], stdout=f)
+ else:
+ print("Skipping grsecurity patch set")
+
+ # If we're don't have a kernel version yet, look it up
+ if not linux_version:
+ if not linux_major_version:
+ print("ERROR: $LINUX_MAJOR_VERSION must be set")
+ sys.exit(1)
+ print(f"Looking up latest release of {linux_major_version} from kernel.org")
+ response = requests.get("https://www.kernel.org/")
+ response.raise_for_status()
+ linux_version = re.search(
+ rf"({re.escape(linux_major_version)}\.(\d+?))",
+ response.text,
+ ).group(1)
+
+ # Fetch Linux kernel source
+ folder = linux_version.split(".")[0] + ".x"
+ print(f"Fetching Linux kernel source {linux_version}")
+ subprocess.check_call(
+ [
+ "wget",
+ f"https://cdn.kernel.org/pub/linux/kernel/v{folder}/linux-{linux_version}.tar.xz",
+ ]
+ )
+ subprocess.check_call(
+ [
+ "wget",
+ f"https://cdn.kernel.org/pub/linux/kernel/v{folder}/linux-{linux_version}.tar.sign",
+ ]
+ )
+ print(f"Extracting Linux kernel source {linux_version}")
+ subprocess.check_call(
+ ["xz", "-d", "-T", "0", "-v", f"linux-{linux_version}.tar.xz"]
+ )
+ subprocess.check_call(
+ [
+ "gpgv",
+ "--keyring",
+ "/pubkeys/kroah_hartman.gpg",
+ f"linux-{linux_version}.tar.sign",
+ f"linux-{linux_version}.tar",
+ ]
+ )
+ shutil.unpack_archive(
+ f"linux-{linux_version}.tar"
+ )
+ # Copy custom /config
+ print("Copying custom config for kernel source", linux_version)
+ shutil.copy("/config", f"linux-{linux_version}/.config")
+
+ # Apply grsec patches
+ if grsecurity:
+ print(f"Applying grsec patches for kernel source {linux_version}")
+ subprocess.check_call(
+ ["patch", "-p", "1", "-i", "/patches-grsec/grsec"],
+ cwd=f"linux-{linux_version}",
+ )
+
+ # Generate orig tarball
+ print("Generating orig tarball")
+ linux_build_version = f"{linux_version}-{build_version}"
+ version_suffix = ("grsec-" if grsecurity else "") + local_version
+ subprocess.check_call(
+ [
+ "tar",
+ "--use-compress-program=xz -T 0",
+ "-cf",
+ f"linux-upstream_{linux_build_version}-{version_suffix}.orig.tar.xz",
+ f"linux-{linux_version}",
+ ]
+ )
+
+ os.chdir(f"linux-{linux_version}")
+ # Copy debian/
+ print("Setting up our debian/ tree")
+ shutil.copytree("/debian", "debian")
+
+ template_variables = {
+ "linux_build_version": linux_build_version,
+ "source_date_epoch_formatted": source_date_epoch_formatted,
+ "version_suffix": version_suffix,
+ "build_version": build_version,
+ "version_codename": version_codename,
+ "debarch": "amd64",
+ "kernelarch": "x86",
+ "local_version": local_version,
+ }
+
+ render_template("debian/control", template_variables)
+ render_template("debian/changelog", template_variables)
+ render_template("debian/rules.vars", template_variables)
+
+ # Building Linux kernel source
+ print("Building Linux kernel source", linux_version)
+ subprocess.check_call(
+ ["dpkg-buildpackage", "-uc", "-us"],
+ )
+
+ os.chdir("..")
+ # Storing build artifacts
+ print("Storing build artifacts for", linux_version)
+ # Because Python doesn't support brace-fnmatch globbing
+ extensions = ['buildinfo', 'changes', 'dsc', 'deb', 'tar.xz']
+ artifacts = []
+ for extension in extensions:
+ artifacts.extend(Path(".").glob(f"*.{extension}"))
+ for artifact in artifacts:
+ print(f"Copying {artifact}...")
+ shutil.copy(str(artifact), "/output")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/build-kernel.sh b/build-kernel.sh
deleted file mode 100755
index ded6fd3..0000000
--- a/build-kernel.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/bin/bash
-set -euxo pipefail
-
-
-# Patching with grsecurity is disabled by default.
-# Can be renabled vai env var or cli flag.
-GRSECURITY="${GRSECURITY:-}"
-LINUX_VERSION="${LINUX_VERSION:-}"
-LINUX_MAJOR_VERSION="${LINUX_MAJOR_VERSION:-}"
-LINUX_CUSTOM_CONFIG="${LINUX_CUSTOM_CONFIG:-/config}"
-# "securedrop" or "workstation" (or "tiny" in CI)
-LOCALVERSION="${LOCALVERSION:-}"
-# Increment this if we need to rebuild the same kernel version for whatever reason
-export BUILD_VERSION="${BUILD_VERSION:-1}"
-export SOURCE_DATE_EPOCH
-export SOURCE_DATE_EPOCH_FORMATTED=$(date -R -d @$SOURCE_DATE_EPOCH)
-export KBUILD_BUILD_TIMESTAMP
-export DEB_BUILD_TIMESTAMP
-# Get the current Debian codename so we can vary based on version
-eval "export $(cat /etc/os-release | grep CODENAME)"
-export VERSION_CODENAME
-
-if [[ $# > 0 ]]; then
- x="$1"
- shift
- if [[ "$x" = "--grsecurity" ]]; then
- GRSECURITY=1
- else
- echo "Usage: $0 [--grsecurity]"
- exit 1
- fi
-fi
-
-# If there's no output directory, then deb packages will be
-# lost in the ephemeral container.
-if [[ ! -d /output && ! -w /output ]]; then
- echo "WARNING: Output directory /output not found" >&2
- echo "WARNING: to save packages, you must mount /output as a volume" >&2
- exit 1
-fi
-
-if [[ -n "$GRSECURITY" && "$GRSECURITY" = "1" ]]; then
- LINUX_VERSION="$(/usr/local/bin/grsecurity-urls.py --print-version)"
- echo "Will include grsecurity patch for kernel $LINUX_VERSION"
- /usr/local/bin/grsecurity-urls.py > /patches-grsec/grsec
-else
- echo "Skipping grsecurity patch set"
-fi
-
-if [[ -z "$LINUX_VERSION" ]]; then
- if [[ -z "$LINUX_MAJOR_VERSION" ]]; then
- echo "ERROR: \$LINUX_MAJOR_VERSION must be set"
- exit 1
- fi
- # Get the latest patch version of this version series from kernel.org
- echo "Looking up latest release of $LINUX_MAJOR_VERSION from kernel.org"
- LINUX_VERSION="$(curl -s https://www.kernel.org/ | grep -m1 -F "$LINUX_MAJOR_VERSION" -A1 | head -n1 | grep -oP '[\d\.]+')"
-fi
-export LINUX_VERSION
-
-# 5.15.120 -> 5
-FOLDER="$(cut -d. -f1 <<< "$LINUX_VERSION").x"
-echo "Fetching Linux kernel source $LINUX_VERSION"
-wget https://cdn.kernel.org/pub/linux/kernel/v${FOLDER}/linux-${LINUX_VERSION}.tar.{xz,sign}
-
-echo "Extracting Linux kernel source $LINUX_VERSION"
-xz -d -T 0 -v linux-${LINUX_VERSION}.tar.xz
-gpgv --keyring /pubkeys/kroah_hartman.gpg linux-${LINUX_VERSION}.tar.sign linux-${LINUX_VERSION}.tar
-tar -xf linux-${LINUX_VERSION}.tar
-cd linux-${LINUX_VERSION}
-
-if [[ -e "$LINUX_CUSTOM_CONFIG" ]]; then
- echo "Copying custom config for kernel source $LINUX_VERSION"
- cp "$LINUX_CUSTOM_CONFIG" .config
-fi
-
-if [[ -e /patches-grsec && -n "$GRSECURITY" && "$GRSECURITY" = "1" ]]; then
- echo "Applying grsec patches for kernel source $LINUX_VERSION"
- find /patches-grsec -maxdepth 1 -type f -exec patch -p 1 -i {} \;
-fi
-
-export LINUX_BUILD_VERSION="${LINUX_VERSION}-${BUILD_VERSION}"
-if [[ -n "$GRSECURITY" && "$GRSECURITY" = "1" ]]; then
- export VERSION_SUFFIX="grsec-${LOCALVERSION}"
-else
- export VERSION_SUFFIX="${LOCALVERSION}"
-fi
-
-# Generate the orig tarball
-tar --use-compress-program="xz -T 0" -cf ../linux-upstream_${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}.orig.tar.xz .
-
-echo "Copying in our debian/"
-cp -R /debian debian
-
-export DEBARCH="amd64"
-
-cat debian/control.in | envsubst > debian/control
-echo "" >> debian/control
-if [[ "$LOCALVERSION" = "workstation" ]]; then
- echo "Generating d/control for workstation"
- cat debian/control.workstation | envsubst >> debian/control
-else
- echo "Generating d/control for server"
- cat debian/control.server | envsubst >> debian/control
-fi
-cat debian/changelog.in | envsubst > debian/changelog
-
-cat < debian/rules.vars
-ARCH := x86
-KERNELRELEASE := ${LINUX_VERSION}
-EOF
-
-echo "Building Linux kernel source $LINUX_VERSION"
-
-# TODO set parallel build here
-dpkg-buildpackage -uc -us
-
-echo "Storing build artifacts for $LINUX_VERSION"
-if [[ -d /output ]]; then
- rsync -a --info=progress2 ../*.{buildinfo,changes,dsc,deb,tar.xz} /output/
-fi
diff --git a/debian/changelog.in b/debian/changelog.in
deleted file mode 100644
index 0c6177d..0000000
--- a/debian/changelog.in
+++ /dev/null
@@ -1,5 +0,0 @@
-linux-upstream (${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}-${BUILD_VERSION}) ${VERSION_CODENAME}; urgency=low
-
- * Custom built Linux kernel.
-
- -- SecureDrop Team ${SOURCE_DATE_EPOCH_FORMATTED}
diff --git a/debian/changelog.j2 b/debian/changelog.j2
new file mode 100644
index 0000000..fc9f392
--- /dev/null
+++ b/debian/changelog.j2
@@ -0,0 +1,5 @@
+linux-upstream ({{linux_build_version}}-{{version_suffix}}-{{build_version}}) {{version_codename}}; urgency=low
+
+ * Custom built Linux kernel.
+
+ -- SecureDrop Team {{source_date_epoch_formatted}}
diff --git a/debian/control.in b/debian/control.in
deleted file mode 100644
index a39b55f..0000000
--- a/debian/control.in
+++ /dev/null
@@ -1,36 +0,0 @@
-Source: linux-upstream
-Section: kernel
-Priority: optional
-Maintainer: SecureDrop Team
-Rules-Requires-Root: no
-Build-Depends: bc, debhelper, rsync, kmod, cpio, bison, flex, libelf-dev, libssl-dev
-Homepage: https://securedrop.org/
-
-Package: linux-image-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}
-Architecture: ${DEBARCH}
-Description: Linux kernel, version ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}
- This package contains the Linux kernel, modules and corresponding other
- files, version: ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}.
-
-Package: linux-libc-dev
-Section: devel
-Provides: linux-kernel-headers
-Architecture: ${DEBARCH}
-Description: Linux support headers for userspace development
- This package provides userspaces headers from the Linux kernel. These headers
- are used by the installed headers for GNU glibc and other system libraries.
-Multi-Arch: same
-
-Package: linux-headers-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}
-Architecture: ${DEBARCH}
-Description: Linux kernel headers for ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX} on ${DEBARCH}
- This package provides kernel header files for ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX} on ${DEBARCH}
- .
- This is useful for people who need to build external modules
-
-Package: linux-image-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}-dbg
-Section: debug
-Architecture: ${DEBARCH}
-Description: Linux kernel debugging symbols for ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}
- This package will come in handy if you need to debug the kernel. It provides
- all the necessary debug symbols for the kernel and its modules.
diff --git a/debian/control.j2 b/debian/control.j2
new file mode 100644
index 0000000..3a673ec
--- /dev/null
+++ b/debian/control.j2
@@ -0,0 +1,56 @@
+Source: linux-upstream
+Section: kernel
+Priority: optional
+Maintainer: SecureDrop Team
+Rules-Requires-Root: no
+Build-Depends: bc, debhelper, rsync, kmod, cpio, bison, flex, libelf-dev, libssl-dev
+Homepage: https://securedrop.org/
+
+Package: linux-image-{{linux_build_version}}-{{version_suffix}}
+Architecture: {{debarch}}
+Description: Linux kernel, version {{linux_build_version}}-{{version_suffix}}
+ This package contains the Linux kernel, modules and corresponding other
+ files, version: {{linux_build_version}}-{{version_suffix}}.
+
+Package: linux-libc-dev
+Section: devel
+Provides: linux-kernel-headers
+Architecture: {{debarch}}
+Description: Linux support headers for userspace development
+ This package provides userspaces headers from the Linux kernel. These headers
+ are used by the installed headers for GNU glibc and other system libraries.
+Multi-Arch: same
+
+Package: linux-headers-{{linux_build_version}}-{{version_suffix}}
+Architecture: {{debarch}}
+Description: Linux kernel headers for {{linux_build_version}}-{{version_suffix}} on {{debarch}}
+ This package provides kernel header files for {{linux_build_version}}-{{version_suffix}} on {{debarch}}
+ .
+ This is useful for people who need to build external modules
+
+Package: linux-image-{{linux_build_version}}-{{version_suffix}}-dbg
+Section: debug
+Architecture: {{debarch}}
+Description: Linux kernel debugging symbols for {{linux_build_version}}-{{version_suffix}}
+ This package will come in handy if you need to debug the kernel. It provides
+ all the necessary debug symbols for the kernel and its modules.
+
+{% if local_version == "workstation" %}
+Package: securedrop-workstation-grsec
+Section: admin
+Architecture: {{debarch}}
+Pre-Depends: qubes-kernel-vm-support (>=4.0.31)
+Depends: linux-image-{{linux_build_version}}-{{version_suffix}}, libelf-dev, paxctld
+Description: Linux for SecureDrop Workstation template (meta-package)
+ Metapackage providing a grsecurity-patched Linux kernel for use in SecureDrop
+ Workstation Qubes templates. Depends on the most recently built patched kernel
+ maintained by FPF.
+{% else %}
+Package: securedrop-grsec
+Section: admin
+Architecture: {{debarch}}
+Depends: linux-image-{{linux_build_version}}-{{version_suffix}}, intel-microcode, amd64-microcode, paxctld
+Description: Metapackage providing a grsecurity-patched Linux kernel for use
+ with SecureDrop. Depends on the most recently built patched kernel maintained
+ by FPF. Package also includes sysctl and PaX flags calls for GRUB.
+{% endif %}
diff --git a/debian/control.server b/debian/control.server
deleted file mode 100644
index 9e518a4..0000000
--- a/debian/control.server
+++ /dev/null
@@ -1,7 +0,0 @@
-Package: securedrop-grsec
-Section: admin
-Architecture: ${DEBARCH}
-Depends: linux-image-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}, intel-microcode, amd64-microcode, paxctld
-Description: Metapackage providing a grsecurity-patched Linux kernel for use
- with SecureDrop. Depends on the most recently built patched kernel maintained
- by FPF. Package also includes sysctl and PaX flags calls for GRUB.
diff --git a/debian/control.workstation b/debian/control.workstation
deleted file mode 100644
index 982e634..0000000
--- a/debian/control.workstation
+++ /dev/null
@@ -1,9 +0,0 @@
-Package: securedrop-workstation-grsec
-Section: admin
-Architecture: ${DEBARCH}
-Pre-Depends: qubes-kernel-vm-support (>=4.0.31)
-Depends: linux-image-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}, libelf-dev, paxctld
-Description: Linux for SecureDrop Workstation template (meta-package)
- Metapackage providing a grsecurity-patched Linux kernel for use in SecureDrop
- Workstation Qubes templates. Depends on the most recently built patched kernel
- maintained by FPF.
diff --git a/debian/rules.vars.j2 b/debian/rules.vars.j2
new file mode 100644
index 0000000..e4979a5
--- /dev/null
+++ b/debian/rules.vars.j2
@@ -0,0 +1,2 @@
+ARCH := {{kernelarch}}
+KERNELRELEASE := {{linux_build_version}}-{{version_suffix}}