From ec2bd25edfea87166969623659e385f1976e1b6c Mon Sep 17 00:00:00 2001 From: Nikita Dubrovskii Date: Thu, 16 Nov 2023 17:17:13 +0100 Subject: [PATCH] s390x: add zVM Secure IPL support --- data/example-config.yaml | 2 + docs/cmd/install.md | 3 + docs/customizing-install.md | 2 + docs/release-notes.md | 1 + man/coreos-installer-install.8 | 5 +- scripts/coreos-installer-service | 5 + src/cmdline/install.rs | 7 ++ src/install.rs | 3 + src/s390x/mod.rs | 1 + src/s390x/zipl.rs | 116 +++++++++++++++++- systemd/coreos-installer-post.target | 1 + systemd/coreos-installer-reboot.service | 1 + ...coreos-installer-secure-ipl-reboot.service | 15 +++ 13 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 systemd/coreos-installer-secure-ipl-reboot.service diff --git a/data/example-config.yaml b/data/example-config.yaml index 640a1dd91..1d48fde75 100644 --- a/data/example-config.yaml +++ b/data/example-config.yaml @@ -43,5 +43,7 @@ stream-base-url: URL preserve-on-error: true # Fetch retries, or string "infinite" fetch-retries: N +# Enable IBM Secure IPL +secure-ipl: true # Destination device dest-device: path diff --git a/docs/cmd/install.md b/docs/cmd/install.md index be4a55fa8..240e55edf 100644 --- a/docs/cmd/install.md +++ b/docs/cmd/install.md @@ -163,4 +163,7 @@ Advanced Options: indefinitely. [default: 0] + + --secure-ipl + Enable IBM Secure IPL ``` diff --git a/docs/customizing-install.md b/docs/customizing-install.md index 6ae305ddb..a8cf73cac 100644 --- a/docs/customizing-install.md +++ b/docs/customizing-install.md @@ -168,6 +168,8 @@ stream-base-url: URL preserve-on-error: true # Fetch retries, or string "infinite" fetch-retries: N +# Enable IBM Secure IPL +secure-ipl: true # Destination device dest-device: path ``` diff --git a/docs/release-notes.md b/docs/release-notes.md index 7696a3f7d..35d8e717f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,7 @@ nav_order: 8 Major changes: +- install: Add z/VM Secure IPL support Minor changes: diff --git a/man/coreos-installer-install.8 b/man/coreos-installer-install.8 index 721196e4f..1d76f4c1d 100644 --- a/man/coreos-installer-install.8 +++ b/man/coreos-installer-install.8 @@ -4,7 +4,7 @@ .SH NAME coreos\-installer\-install \- Install Fedora CoreOS or RHEL CoreOS .SH SYNOPSIS -\fBcoreos\-installer\-install\fR [\fB\-c\fR|\fB\-\-config\-file\fR] [\fB\-s\fR|\fB\-\-stream\fR] [\fB\-u\fR|\fB\-\-image\-url\fR] [\fB\-f\fR|\fB\-\-image\-file\fR] [\fB\-i\fR|\fB\-\-ignition\-file\fR] [\fB\-I\fR|\fB\-\-ignition\-url\fR] [\fB\-\-ignition\-hash\fR] [\fB\-a\fR|\fB\-\-architecture\fR] [\fB\-p\fR|\fB\-\-platform\fR] [\fB\-\-console\fR] [\fB\-\-append\-karg\fR] [\fB\-\-delete\-karg\fR] [\fB\-n\fR|\fB\-\-copy\-network\fR] [\fB\-\-network\-dir\fR] [\fB\-\-save\-partlabel\fR] [\fB\-\-save\-partindex\fR] [\fB\-\-offline\fR] [\fB\-\-insecure\fR] [\fB\-\-insecure\-ignition\fR] [\fB\-\-stream\-base\-url\fR] [\fB\-\-preserve\-on\-error\fR] [\fB\-\-fetch\-retries\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIDEST_DEVICE\fR] +\fBcoreos\-installer\-install\fR [\fB\-c\fR|\fB\-\-config\-file\fR] [\fB\-s\fR|\fB\-\-stream\fR] [\fB\-u\fR|\fB\-\-image\-url\fR] [\fB\-f\fR|\fB\-\-image\-file\fR] [\fB\-i\fR|\fB\-\-ignition\-file\fR] [\fB\-I\fR|\fB\-\-ignition\-url\fR] [\fB\-\-ignition\-hash\fR] [\fB\-a\fR|\fB\-\-architecture\fR] [\fB\-p\fR|\fB\-\-platform\fR] [\fB\-\-console\fR] [\fB\-\-append\-karg\fR] [\fB\-\-delete\-karg\fR] [\fB\-n\fR|\fB\-\-copy\-network\fR] [\fB\-\-network\-dir\fR] [\fB\-\-save\-partlabel\fR] [\fB\-\-save\-partindex\fR] [\fB\-\-offline\fR] [\fB\-\-insecure\fR] [\fB\-\-insecure\-ignition\fR] [\fB\-\-stream\-base\-url\fR] [\fB\-\-preserve\-on\-error\fR] [\fB\-\-fetch\-retries\fR] [\fB\-\-secure\-ipl\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIDEST_DEVICE\fR] .SH DESCRIPTION Install Fedora CoreOS or RHEL CoreOS .SH OPTIONS @@ -123,6 +123,9 @@ Fetch retries, or "infinite" Number of times to retry network fetches, or the string "infinite" to retry indefinitely. .TP +\fB\-\-secure\-ipl\fR +Enable IBM Secure IPL +.TP \fB\-h\fR, \fB\-\-help\fR Print help (see a summary with \*(Aq\-h\*(Aq) .TP diff --git a/scripts/coreos-installer-service b/scripts/coreos-installer-service index 71c636a5c..aaa452f4b 100755 --- a/scripts/coreos-installer-service +++ b/scripts/coreos-installer-service @@ -121,6 +121,11 @@ if karg_bool coreos.inst.insecure; then args+=("--insecure") fi +# zVM Secure IPL support +if karg_bool coreos.inst.secure_ipl; then + args+=("--secure-ipl") +fi + # Always retry HTTP requests; we've got nothing to lose since we fail anyway. args+=("--fetch-retries" "infinite") diff --git a/src/cmdline/install.rs b/src/cmdline/install.rs index 000479f69..885ca73db 100644 --- a/src/cmdline/install.rs +++ b/src/cmdline/install.rs @@ -252,6 +252,10 @@ pub struct InstallConfig { #[serde(skip_serializing_if = "is_default")] #[arg(long, value_name = "N", default_value_t, help_heading = ADVANCED)] pub fetch_retries: FetchRetries, + /// Enable IBM Secure IPL + #[serde(skip_serializing_if = "is_default")] + #[arg(long, help_heading = ADVANCED)] + pub secure_ipl: bool, // positional args /// Destination device @@ -361,6 +365,7 @@ mod test { stream_base_url: Some(Url::parse("http://example.com/t").unwrap()), preserve_on_error: true, fetch_retries: FetchRetries::from_str("3").unwrap(), + secure_ipl: true, dest_device: Some("u".into()), }; let expected = vec![ @@ -412,6 +417,7 @@ mod test { "--preserve-on-error", "--fetch-retries", "3", + "--secure-ipl", "u", ]; assert_eq!(config.to_args().unwrap(), expected); @@ -484,6 +490,7 @@ dest-device: u stream_base_url: Some(Url::parse("http://example.com/t").unwrap()), preserve_on_error: true, fetch_retries: FetchRetries::from_str("3").unwrap(), + secure_ipl: false, dest_device: Some("u".into()), }; let config = InstallConfig::from_args(&["--config-file", f.path().to_str().unwrap()]) diff --git a/src/install.rs b/src/install.rs index 71c116e75..c2a8cc56b 100644 --- a/src/install.rs +++ b/src/install.rs @@ -454,6 +454,9 @@ fn write_disk( None, )?; s390x::chreipl(device)?; + if config.secure_ipl { + s390x::set_loaddev(device)?; + } } } diff --git a/src/s390x/mod.rs b/src/s390x/mod.rs index 8f39c4c75..7a707637f 100644 --- a/src/s390x/mod.rs +++ b/src/s390x/mod.rs @@ -19,6 +19,7 @@ pub mod zipl; pub use dasd::{dasd_try_get_sector_size, image_copy_s390x, prepare_dasd}; pub use zipl::chreipl; +pub use zipl::set_loaddev; pub use zipl::zipl; mod eckd; mod fba; diff --git a/src/s390x/zipl.rs b/src/s390x/zipl.rs index 4b3d8064e..213b08133 100644 --- a/src/s390x/zipl.rs +++ b/src/s390x/zipl.rs @@ -14,10 +14,11 @@ use crate::blockdev::Mount; use crate::io::{visit_bls_entry, visit_bls_entry_options, Initrd, KargsEditor}; -use crate::runcmd; use crate::s390x::ZiplSecexMode; use crate::util::cmd_output; -use anyhow::{anyhow, Context, Result}; +use crate::{runcmd, runcmd_output}; +use anyhow::{anyhow, bail, Context, Result}; +use lazy_static::lazy_static; use nix::mount::MsFlags; use regex::Regex; use std::fs::{copy, create_dir_all, read_dir, DirEntry, File}; @@ -33,11 +34,116 @@ pub fn chreipl>(dev: P) -> Result<()> { Ok(()) } +/// Secure boot (Secure IPL) includes support of SCSI and ECKD DASD boot devices +enum Loaddev { + Eckd(String), + Scsi(String, String, String), +} + +fn parse_lszdev_eckd(line: &str) -> Result { + // ECKD ID looks like: 0.0.5223, we need only last part of it (5223) + lazy_static! { + static ref REGEX: Regex = Regex::new(r#"[[:digit:]].[[:digit:]].([[:xdigit:]]+)"#).unwrap(); + } + if let Some(cap) = REGEX.captures_iter(line).next() { + return Ok(Loaddev::Eckd(cap[1].to_string())); + } + bail!("bad ECKD id: {}", line); +} + +fn parse_lszdev_zfcp(line: &str) -> Result { + // SCSI ID looks like: 0.0.8000:0x500507630400d1e3:0x4000401d00000000 + // So here is regex to parse required ids: 8000,500507630400d1e3,4000401d00000000 + lazy_static! { + static ref REGEX: Regex = Regex::new( + r#"[[:digit:]].[[:digit:]].([[:xdigit:]]+):0x([[:xdigit:]]+):0x([[:xdigit:]]+)"# + ) + .unwrap(); + } + if let Some(cap) = REGEX.captures_iter(line).next() { + return Ok(Loaddev::Scsi( + cap[1].to_string(), + cap[2].to_string(), + cap[3].to_string(), + )); + } + bail!("bad zFCP id: {}", line); +} + +fn parse_lszdev>(dev: P) -> Result { + // We don't want to traverse sysfs and do same stuff lszdev does, + // so just call it to get required info. Here is sample output: + // $ lszdev -c TYPE,ID -n + // dasd-eckd 0.0.0190 + // zfcp-lun 0.0.8007:0x500507630400d1e3:0x4001404c00000000 + // qeth 0.0.bdd0:0.0.bdd1:0.0.bdd2 + // generic-ccw 0.0.000c + let output = runcmd_output!( + "lszdev", + "-n", + "--columns", + "TYPE,ID", + "--by-node", + dev.as_ref() + )?; + let (devtype, id) = output + .trim() + .split_once(' ') + .with_context(|| format!("parsing lszdev {output}"))?; + match devtype { + "dasd-eckd" => parse_lszdev_eckd(id), + "zfcp-lun" => parse_lszdev_zfcp(id), + _ => bail!("unsupported device: {} id: {}", devtype, id), + } +} + +/// Sets zVM Secure Boot (Secure IPL) boot device to `dev`. +pub fn set_loaddev>(dev: P) -> Result<()> { + if !secure_ipl_is_supported()? { + bail!("Secure IPL is not supported"); + } + // check if system is zVM guest + if !Path::new("/dev/vmcp").exists() { + return Ok(()); + } + eprintln!("Setting LOADDEV"); + let mut cmd = Command::new("vmcp"); + cmd.arg("set").arg("loaddev"); + match parse_lszdev(dev)? { + Loaddev::Eckd(d) => cmd.arg("eckd").arg("dev").arg(d), + Loaddev::Scsi(d, p, l) => + // CP tool wants portname/lun to be splitted at 8th character: + // $ vmcp set loaddev dev 8007 portname 500507630400d1e3 lun 4001404c00000000 secure + // HCPZPM002E Invalid operand - 500507630400D1E3 + // $ vmcp set loaddev dev 8007 portname 50050763 0400d1e3 lun 4001404c 00000000 secure + { + cmd.arg("dev") + .arg(d) + .arg("portname") + .arg(&p[0..8]) + .arg(&p[8..]) + .arg("lun") + .arg(&l[0..8]) + .arg(&l[8..]) + } + }; + cmd.arg("secure"); + cmd_output(&mut cmd)?; + Ok(()) +} + fn secure_execution_is_enabled() -> Result { - let sysfs_flag = "/sys/firmware/uv/prot_virt_guest"; - match File::open(sysfs_flag) { + sysfs_flag_enabled("/sys/firmware/uv/prot_virt_guest") +} + +fn secure_ipl_is_supported() -> Result { + sysfs_flag_enabled("/sys/firmware/ipl/has_secure") +} + +fn sysfs_flag_enabled>(path: P) -> Result { + match File::open(&path) { Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false), - Err(e) => Err(e).with_context(|| format!("reading {sysfs_flag}")), + Err(e) => Err(e).with_context(|| format!("reading {}", path.as_ref().display())), Ok(mut f) => { let mut buffer = String::new(); f.read_to_string(&mut buffer)?; diff --git a/systemd/coreos-installer-post.target b/systemd/coreos-installer-post.target index 8013eb6f8..b67be37b4 100644 --- a/systemd/coreos-installer-post.target +++ b/systemd/coreos-installer-post.target @@ -5,3 +5,4 @@ AllowIsolate=yes Requires=coreos-installer.target Requires=coreos-installer-reboot.service Requires=coreos-installer-noreboot.service +Requires=coreos-installer-secure-ipl-reboot.service diff --git a/systemd/coreos-installer-reboot.service b/systemd/coreos-installer-reboot.service index 18fdb4bee..b0866b55c 100644 --- a/systemd/coreos-installer-reboot.service +++ b/systemd/coreos-installer-reboot.service @@ -5,6 +5,7 @@ After=coreos-installer.target OnFailure=emergency.target OnFailureJobMode=replace-irreversibly ConditionPathExists=/run/coreos-installer-reboot +ConditionKernelCommandLine=!coreos.inst.secure_ipl [Service] Type=simple diff --git a/systemd/coreos-installer-secure-ipl-reboot.service b/systemd/coreos-installer-secure-ipl-reboot.service new file mode 100644 index 000000000..d2de242b1 --- /dev/null +++ b/systemd/coreos-installer-secure-ipl-reboot.service @@ -0,0 +1,15 @@ +[Unit] +Description=Reboot in Secure IPL mode after CoreOS Installer +Requires=coreos-installer.target +After=coreos-installer.target +OnFailure=emergency.target +OnFailureJobMode=replace-irreversibly +ConditionPathExists=/run/coreos-installer-reboot +ConditionKernelCommandLine=coreos.inst.secure_ipl + +[Service] +Type=simple +# On z/VM, we need to use 'vmcp ipl loaddev'. Otherwise, for the LPAR case, we can just reboot as usual. +ExecStart=/bin/sh -c '[ -e /dev/vmcp ] && vmcp ipl loaddev || systemctl --no-block reboot' +StandardOutput=kmsg+console +StandardError=kmsg+console