Merge pull request #26 from nix-community/upstream-bootspec

project: support upstream bootspec
This commit is contained in:
nikstur 2022-12-25 18:11:28 +01:00 committed by GitHub
commit 14c6c413f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 144 additions and 106 deletions

View File

@ -31,6 +31,7 @@ to take something up:
- Experimenting with `fwupd` / Green Checkmark in GNOME Device Security - Experimenting with `fwupd` / Green Checkmark in GNOME Device Security
- https://github.com/fwupd/fwupd/issues/5284 - https://github.com/fwupd/fwupd/issues/5284
- Experimenting with TPM2 measurements - Experimenting with TPM2 measurements
- Support bootspec with no initrd
- Studying the initrd secrets feature in NixOS wrt SecureBoot & TPM2 - Studying the initrd secrets feature in NixOS wrt SecureBoot & TPM2
- ... - ...

View File

@ -14,11 +14,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1669605882, "lastModified": 1670900067,
"narHash": "sha256-TiQtL5sUI5rp28S63v+VX25qNjcrc8Xeu+shf3g7Tj4=", "narHash": "sha256-VXVa+KBfukhmWizaiGiHRVX/fuk66P8dgSFfkVN4/MY=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "24591d5f8cc979f7b243b88a2d39da09976970ad", "rev": "59b31b41a589c0a65e4a1f86b0e5eac68081468b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -30,11 +30,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1650374568, "lastModified": 1668681692,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8", "rev": "009399224d5e398d03b22badca40a37ac85412a1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -60,32 +60,32 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1669535121, "lastModified": 1671755972,
"narHash": "sha256-koZLM7oWVGrjyHnYDo7/w5qlmUn9UZUKSFNfmIjueE8=", "narHash": "sha256-X977apvpqBqqRf2XBNorfunZmQNn3cQYGEnQE4L90Fo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b45ec953794bb07922f0468152ad1ebaf8a084b3", "rev": "e8ee153b1717dca9c6aa38d5cf198329480d5b41",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixpkgs-unstable", "ref": "nixos-unstable-small",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs-test": { "nixpkgs-test": {
"locked": { "locked": {
"lastModified": 1669495095, "lastModified": 1671812130,
"narHash": "sha256-wasu8T7ac+LVm4aXuAYkH76Rr98VW0Cp9oZvNPuKiiU=", "narHash": "sha256-GALBK+qB9rhnB+lVnxdgtMoXCySXughZZ3+qGO1Ke/k=",
"owner": "RaitoBezarius", "owner": "RaitoBezarius",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8bbe1bb1f7352dd9c2e448e8d68846a66d0c2aca", "rev": "e51bf8cc8e2c75192e930ad83ed272938729e7be",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "RaitoBezarius", "owner": "RaitoBezarius",
"ref": "experimental-secureboot", "ref": "simplified-qemu-boot-disks",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -109,11 +109,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1669602829, "lastModified": 1671243596,
"narHash": "sha256-I3LBvBiVui4Rf0iQvTqUIgBovaLDzpOzsoNEzCsDowg=", "narHash": "sha256-vQ1q6uwx2gKsHbQVhkq17nT8HwUmRbIG8cJVFafNb5s=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "b9da8e68a08707115be750c0cf7ade33f49d8ec4", "rev": "905db21103d646ddc1eb81920e05180e6e2b6734",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -2,7 +2,8 @@
description = "Lanzaboot Secure Boot Madness"; description = "Lanzaboot Secure Boot Madness";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small";
nixpkgs-test.url = "github:RaitoBezarius/nixpkgs/simplified-qemu-boot-disks";
crane = { crane = {
url = "github:ipetkov/crane"; url = "github:ipetkov/crane";
@ -11,7 +12,6 @@
inputs.flake-utils.follows = "flake-utils"; inputs.flake-utils.follows = "flake-utils";
}; };
nixpkgs-test.url = "github:RaitoBezarius/nixpkgs/experimental-secureboot";
rust-overlay = { rust-overlay = {
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@ -21,7 +21,7 @@
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = { self, nixpkgs, crane, nixpkgs-test, rust-overlay, ... }: outputs = { self, nixpkgs, nixpkgs-test, crane, rust-overlay, ... }:
let let
pkgs = import nixpkgs { pkgs = import nixpkgs {
system = "x86_64-linux"; system = "x86_64-linux";
@ -140,7 +140,6 @@
}; };
boot.loader.efi = { boot.loader.efi = {
enable = true;
canTouchEfiVariables = true; canTouchEfiVariables = true;
}; };
boot.lanzaboote = { boot.lanzaboote = {
@ -166,7 +165,8 @@
return f'/boot/EFI/nixos/{store_dir}-{filename}.efi' return f'/boot/EFI/nixos/{store_dir}-{filename}.efi'
machine.start() machine.start()
bootspec = json.loads(machine.succeed("cat /run/current-system/bootspec/boot.v1.json")) bootspec = json.loads(machine.succeed("cat /run/current-system/boot.json")).get('v1')
assert bootspec is not None, "Unsupported bootspec version!"
src_path = ${path.src} src_path = ${path.src}
dst_path = ${path.dst} dst_path = ${path.dst}
machine.succeed(f"cp -rf {src_path} {dst_path}") machine.succeed(f"cp -rf {src_path} {dst_path}")
@ -263,7 +263,7 @@
testScript = '' testScript = ''
machine.start() machine.start()
print(machine.succeed("ls -lah /boot/EFI/Linux")) print(machine.succeed("ls -lah /boot/EFI/Linux"))
print(machine.succeed("cat /run/current-system/bootspec/boot.v1.json")) print(machine.succeed("cat /run/current-system/boot.json"))
# TODO: make it more reliable to find this filename, i.e. read it from somewhere? # TODO: make it more reliable to find this filename, i.e. read it from somewhere?
machine.succeed("bootctl set-default nixos-generation-1-specialisation-variant.efi") machine.succeed("bootctl set-default nixos-generation-1-specialisation-variant.efi")
machine.succeed("sync") machine.succeed("sync")

View File

@ -32,9 +32,11 @@ in
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
# bootspec is putting at false boot.bootspec = {
# until we fix this upstream, we will mkForce it. enable = true;
boot.loader.supportsInitrdSecrets = mkForce true; extensions."lanzaboote"."osRelease" = config.environment.etc."os-release".source;
};
boot.loader.supportsInitrdSecrets = true;
boot.loader.external = { boot.loader.external = {
enable = true; enable = true;
installHook = pkgs.writeShellScript "bootinstall" '' installHook = pkgs.writeShellScript "bootinstall" ''

View File

@ -66,6 +66,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "bootspec"
version = "0.1.0"
source = "git+https://github.com/DeterminateSystems/bootspec#41cf62bb985ccf2f34b96a6c0129481d56b86bfe"
dependencies = [
"serde",
"serde_json",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.77" version = "1.0.77"
@ -208,6 +217,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
"bootspec",
"clap", "clap",
"goblin", "goblin",
"nix", "nix",
@ -349,18 +359,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.147" version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.147" version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -392,9 +402,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.103" version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -10,7 +10,9 @@ anyhow = "1.0.66"
clap = { version = "4.0.26", features = ["derive"] } clap = { version = "4.0.26", features = ["derive"] }
goblin = "0.6.0" goblin = "0.6.0"
nix = { version = "0.25.0", default-features = false, features = [ "fs" ] } nix = { version = "0.25.0", default-features = false, features = [ "fs" ] }
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.151", features = ["derive"] }
serde_json = "1.0.89" serde_json = "1.0.89"
tempfile = "3.3.0" tempfile = "3.3.0"
blake3 = "1.3.3" blake3 = "1.3.3"
# TODO: wait for a upstream release and pin it.
bootspec = { git = "https://github.com/DeterminateSystems/bootspec" }

View File

@ -1,4 +1,5 @@
{ {
"v1": {
"init": "/run/current-system/init", "init": "/run/current-system/init",
"initrd": "/run/current-system/initrd", "initrd": "/run/current-system/initrd",
"kernel": "/run/current-system/kernel", "kernel": "/run/current-system/kernel",
@ -15,7 +16,8 @@
"label": "LanzaOS", "label": "LanzaOS",
"toplevel": "/run/current-system", "toplevel": "/run/current-system",
"specialisation": {}, "specialisation": {},
"extension": { "extensions": {
"osRelease": "/etc/os-release" "lanzaboote": { "osRelease": "/etc/os-release" }
}
} }
} }

View File

@ -1,32 +0,0 @@
use std::collections::HashMap;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Bootspec {
/// Label for the system closure
pub label: String,
/// Path to kernel (bzImage) -- $toplevel/kernel
pub kernel: PathBuf,
/// list of kernel parameters
pub kernel_params: Vec<String>,
/// Path to the init script
pub init: PathBuf,
/// Path to initrd -- $toplevel/initrd
pub initrd: PathBuf,
/// Path to "append-initrd-secrets" script -- $toplevel/append-initrd-secrets
pub initrd_secrets: Option<PathBuf>,
/// config.system.build.toplevel path
pub toplevel: PathBuf,
/// Mapping of specialisation names to their boot.json
pub specialisation: HashMap<String, Bootspec>,
pub extension: Extension,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Extension {
pub os_release: PathBuf,
}

View File

@ -24,13 +24,19 @@ impl EspPaths {
let esp_systemd = esp.join("EFI/systemd"); let esp_systemd = esp.join("EFI/systemd");
let esp_efi_fallback_dir = esp.join("EFI/BOOT"); let esp_efi_fallback_dir = esp.join("EFI/BOOT");
let bootspec = &generation.bootspec; let bootspec = &generation.spec.bootspec;
Ok(Self { Ok(Self {
esp: esp.to_path_buf(), esp: esp.to_path_buf(),
nixos: esp_nixos.clone(), nixos: esp_nixos.clone(),
kernel: esp_nixos.join(nixos_path(&bootspec.kernel, "bzImage")?), kernel: esp_nixos.join(nixos_path(&bootspec.kernel, "bzImage")?),
initrd: esp_nixos.join(nixos_path(&bootspec.initrd, "initrd")?), initrd: esp_nixos.join(nixos_path(
bootspec
.initrd
.as_ref()
.context("Lanzaboote does not support missing initrd yet")?,
"initrd",
)?),
linux: esp_linux.clone(), linux: esp_linux.clone(),
lanzaboote_image: esp_linux.join(generation_path(generation)), lanzaboote_image: esp_linux.join(generation_path(generation)),
efi_fallback_dir: esp_efi_fallback_dir.clone(), efi_fallback_dir: esp_efi_fallback_dir.clone(),

View File

@ -1,42 +1,82 @@
use serde::de::IntoDeserializer;
use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::fs; use std::fs;
use std::path::Path; use std::path::{Path, PathBuf};
use anyhow::{Context, Result}; use anyhow::{anyhow, Context, Result};
use bootspec::generation::Generation as BootspecGeneration;
use bootspec::BootJson;
use bootspec::SpecialisationName;
use crate::bootspec::Bootspec; #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecureBootExtension {
#[serde(rename = "osRelease")]
pub os_release: PathBuf,
}
#[derive(Debug, Clone)]
pub struct ExtendedBootJson {
pub bootspec: BootJson,
pub extensions: SecureBootExtension,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Generation { pub struct Generation {
/// Profile symlink index
version: u64, version: u64,
specialisation_name: Option<String>, /// Top-level specialisation name
pub bootspec: Bootspec, specialisation_name: Option<SpecialisationName>,
/// Top-level extended boot specification
pub spec: ExtendedBootJson,
} }
impl Generation { impl Generation {
fn extract_extensions(bootspec: &BootJson) -> Result<SecureBootExtension> {
Ok(Deserialize::deserialize(
bootspec.extensions.get("lanzaboote")
.context("Failed to extract Lanzaboote-specific extension from Bootspec, missing lanzaboote field in `extensions`")?
.clone()
.into_deserializer()
)?)
}
pub fn from_toplevel(toplevel: impl AsRef<Path>) -> Result<Self> { pub fn from_toplevel(toplevel: impl AsRef<Path>) -> Result<Self> {
let bootspec_path = toplevel.as_ref().join("bootspec/boot.v1.json"); let bootspec_path = toplevel.as_ref().join("boot.json");
let bootspec: Bootspec = serde_json::from_slice( let generation: BootspecGeneration = serde_json::from_slice(
&fs::read(bootspec_path).context("Failed to read bootspec file")?, &fs::read(bootspec_path).context("Failed to read bootspec file")?,
) )
.context("Failed to parse bootspec json")?; .context("Failed to parse bootspec json")?;
let bootspec: BootJson = generation
.try_into()
.map_err(|err: &'static str| anyhow!(err))?;
let extensions = Self::extract_extensions(&bootspec)?;
Ok(Self { Ok(Self {
version: parse_version(toplevel)?, version: parse_version(toplevel)?,
specialisation_name: None, specialisation_name: None,
spec: ExtendedBootJson {
bootspec, bootspec,
extensions,
},
}) })
} }
pub fn specialise(&self, name: &str, bootspec: &Bootspec) -> Self { pub fn specialise(&self, name: &SpecialisationName, bootspec: &BootJson) -> Result<Self> {
Self { Ok(Self {
version: self.version, version: self.version,
specialisation_name: Some(String::from(name)), specialisation_name: Some(name.clone()),
spec: ExtendedBootJson {
bootspec: bootspec.clone(), bootspec: bootspec.clone(),
} extensions: Self::extract_extensions(bootspec)?,
},
})
} }
pub fn is_specialized(&self) -> Option<String> { pub fn is_specialized(&self) -> Option<SpecialisationName> {
self.specialisation_name.clone() self.specialisation_name.clone()
} }
} }

View File

@ -45,8 +45,8 @@ impl Installer {
self.install_generation(&generation) self.install_generation(&generation)
.context("Failed to install generation")?; .context("Failed to install generation")?;
for (name, bootspec) in &generation.bootspec.specialisation { for (name, bootspec) in &generation.spec.bootspec.specialisation {
let specialised_generation = generation.specialise(name, bootspec); let specialised_generation = generation.specialise(name, bootspec)?;
println!("Installing specialisation: {name} of generation: {generation}"); println!("Installing specialisation: {name} of generation: {generation}");
@ -59,7 +59,8 @@ impl Installer {
} }
fn install_generation(&self, generation: &Generation) -> Result<()> { fn install_generation(&self, generation: &Generation) -> Result<()> {
let bootspec = &generation.bootspec; let bootspec = &generation.spec.bootspec;
let secureboot_extensions = &generation.spec.extensions;
let esp_paths = EspPaths::new(&self.esp, generation)?; let esp_paths = EspPaths::new(&self.esp, generation)?;
@ -77,13 +78,20 @@ impl Installer {
println!("Appending secrets to initrd..."); println!("Appending secrets to initrd...");
let initrd_location = secure_temp_dir.path().join("initrd"); let initrd_location = secure_temp_dir.path().join("initrd");
copy(&bootspec.initrd, &initrd_location)?; copy(
bootspec
.initrd
.as_ref()
.context("Lanzaboote does not support missing initrd yet")?,
&initrd_location,
)?;
if let Some(initrd_secrets_script) = &bootspec.initrd_secrets { if let Some(initrd_secrets_script) = &bootspec.initrd_secrets {
append_initrd_secrets(initrd_secrets_script, &initrd_location)?; append_initrd_secrets(initrd_secrets_script, &initrd_location)?;
} }
let systemd_boot = bootspec let systemd_boot = bootspec
.toplevel .toplevel
.0
.join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi"); .join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi");
[ [
@ -102,7 +110,7 @@ impl Installer {
let lanzaboote_image = pe::lanzaboote_image( let lanzaboote_image = pe::lanzaboote_image(
&secure_temp_dir, &secure_temp_dir,
&self.lanzaboote_stub, &self.lanzaboote_stub,
&bootspec.extension.os_release, &secureboot_extensions.os_release,
&kernel_cmdline, &kernel_cmdline,
&esp_paths.kernel, &esp_paths.kernel,
&esp_paths.initrd, &esp_paths.initrd,

View File

@ -1,4 +1,3 @@
mod bootspec;
mod cli; mod cli;
mod esp; mod esp;
mod generation; mod generation;