From f7086420f281f52f0b206ae71b5967e1757e8431 Mon Sep 17 00:00:00 2001 From: Jarrett Tierney Date: Tue, 6 Feb 2024 22:17:08 +0000 Subject: [PATCH] prairiedog: adjust to leverage a rendered config file Changes prairiedog to read from /etc/prairiedog.toml which is generated automatically from the settings. --- Release.toml | 2 + packages/os/os.spec | 4 +- packages/os/prairiedog-toml | 22 ++++++ sources/Cargo.lock | 18 ++++- sources/Cargo.toml | 2 + .../prairiedog-config-file-v0-1-0/Cargo.toml | 13 ++++ .../prairiedog-config-file-v0-1-0/src/main.rs | 16 ++++ .../prairiedog-services-cfg-v0-1-0/Cargo.toml | 13 ++++ .../src/main.rs | 18 +++++ sources/api/prairiedog/Cargo.toml | 6 +- sources/api/prairiedog/src/bootconfig.rs | 77 ++++++++++--------- sources/api/prairiedog/src/error.rs | 9 +-- sources/api/prairiedog/src/main.rs | 27 +++---- sources/models/shared-defaults/boot.toml | 6 +- 14 files changed, 174 insertions(+), 59 deletions(-) create mode 100644 packages/os/prairiedog-toml create mode 100644 sources/api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0/Cargo.toml create mode 100644 sources/api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0/src/main.rs create mode 100644 sources/api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0/Cargo.toml create mode 100644 sources/api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0/src/main.rs diff --git a/Release.toml b/Release.toml index 6a0e6fa93e3..a7846f29589 100644 --- a/Release.toml +++ b/Release.toml @@ -273,4 +273,6 @@ version = "1.19.2" "(1.19.1, 1.19.2)" = [ "migrate_v1.19.2_certdog-config-file-v0-1-0.lz4", "migrate_v1.19.2_certdog-service-cfg-v0-1-0.lz4", + "migrate_v1.19.2_prairiedog-config-file-v0-1-0.lz4", + "migrate_v1.19.2_prairiedog-services-cfg-v0-1-0.lz4" ] diff --git a/packages/os/os.spec b/packages/os/os.spec index 5f562c68128..ea949aca563 100644 --- a/packages/os/os.spec +++ b/packages/os/os.spec @@ -32,6 +32,7 @@ Source12: 00-resolved.conf Source13: cis-checks-k8s-metadata-json %endif Source14: certdog-toml +Source15: prairiedog-toml # 1xx sources: systemd units Source100: apiserver.service @@ -473,7 +474,7 @@ install -d %{buildroot}%{_cross_datadir}/updog install -p -m 0644 %{_cross_repo_root_json} %{buildroot}%{_cross_datadir}/updog install -d %{buildroot}%{_cross_templatedir} -install -p -m 0644 %{S:5} %{S:6} %{S:7} %{S:8} %{S:14} %{buildroot}%{_cross_templatedir} +install -p -m 0644 %{S:5} %{S:6} %{S:7} %{S:8} %{S:14} %{S:15} %{buildroot}%{_cross_templatedir} install -d %{buildroot}%{_cross_unitdir} install -p -m 0644 \ @@ -682,6 +683,7 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir} %files -n %{_cross_os}prairiedog %{_cross_bindir}/prairiedog %{_cross_unitdir}/reboot-if-required.service +%{_cross_templatedir}/prairiedog-toml %files -n %{_cross_os}certdog %{_cross_bindir}/certdog diff --git a/packages/os/prairiedog-toml b/packages/os/prairiedog-toml new file mode 100644 index 00000000000..48379cc5981 --- /dev/null +++ b/packages/os/prairiedog-toml @@ -0,0 +1,22 @@ +[required-extensions] +boot = "v1" ++++ +{{#if settings.boot}} +{{#if settings.boot.reboot-to-reconcile}} +reboot-to-reconcile = {{settings.boot.reboot-to-reconcile}} + +{{/if}} +{{#if settings.boot.kernel}} +[kernel] +{{#each settings.boot.kernel}} +"{{@key}}" = [ {{#each this}}"{{{this}}}",{{/each}} ] +{{/each}} + +{{/if}} +{{#if settings.boot.init}} +[init] +{{#each settings.boot.init}} +"{{@key}}" = [ {{#each this}}"{{{this}}}",{{/each}} ] +{{/each}} +{{/if}} +{{/if}} diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 1d30304ccf2..8574d43c9d6 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -3101,14 +3101,30 @@ dependencies = [ "generate-readme", "log", "maplit", + "modeled-types", "models", "nix", "schnauzer", + "serde", "serde_json", "signpost", "simplelog", "snafu", - "tokio", + "toml 0.8.8", +] + +[[package]] +name = "prairiedog-config-file-v0-1-0" +version = "0.1.0" +dependencies = [ + "migration-helpers", +] + +[[package]] +name = "prairiedog-services-cfg-v0-1-0" +version = "0.1.0" +dependencies = [ + "migration-helpers", ] [[package]] diff --git a/sources/Cargo.toml b/sources/Cargo.toml index cda13db3699..883ee023f57 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -63,6 +63,8 @@ members = [ "api/migration/migrations/v1.19.1/public-control-container-v0-7-8", "api/migration/migrations/v1.19.2/certdog-config-file-v0-1-0", "api/migration/migrations/v1.19.2/certdog-service-cfg-v0-1-0", + "api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0", + "api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0", "bloodhound", diff --git a/sources/api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0/Cargo.toml b/sources/api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0/Cargo.toml new file mode 100644 index 00000000000..524c7e687cd --- /dev/null +++ b/sources/api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "prairiedog-config-file-v0-1-0" +version = "0.1.0" +authors = ["Jarrett Tierney "] +license = "Apache-2.0 OR MIT" +edition = "2021" +publish = false +exclude = ["README.md"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" } diff --git a/sources/api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0/src/main.rs b/sources/api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0/src/main.rs new file mode 100644 index 00000000000..7d89c43fb38 --- /dev/null +++ b/sources/api/migration/migrations/v1.19.2/prairiedog-config-file-v0-1-0/src/main.rs @@ -0,0 +1,16 @@ +use migration_helpers::common_migrations::AddPrefixesMigration; +use migration_helpers::{migrate, Result}; +use std::process; + +fn run() -> Result<()> { + migrate(AddPrefixesMigration(vec![ + "configuration-files.prairiedog-toml", + ])) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0/Cargo.toml b/sources/api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0/Cargo.toml new file mode 100644 index 00000000000..44d43d76497 --- /dev/null +++ b/sources/api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "prairiedog-services-cfg-v0-1-0" +version = "0.1.0" +authors = ["Jarrett Tierney "] +license = "Apache-2.0 OR MIT" +edition = "2021" +publish = false +exclude = ["README.md"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" } diff --git a/sources/api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0/src/main.rs b/sources/api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0/src/main.rs new file mode 100644 index 00000000000..391529368df --- /dev/null +++ b/sources/api/migration/migrations/v1.19.2/prairiedog-services-cfg-v0-1-0/src/main.rs @@ -0,0 +1,18 @@ +use migration_helpers::common_migrations::{ListReplacement, ReplaceListsMigration}; +use migration_helpers::{migrate, Result}; +use std::process; + +fn run() -> Result<()> { + migrate(ReplaceListsMigration(vec![ListReplacement { + setting: "services.bootconfig.configuration-files", + old_vals: &[], + new_vals: &["prairiedog-toml"], + }])) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/prairiedog/Cargo.toml b/sources/api/prairiedog/Cargo.toml index 173afd5b3fc..0d0cc4920e4 100644 --- a/sources/api/prairiedog/Cargo.toml +++ b/sources/api/prairiedog/Cargo.toml @@ -15,12 +15,14 @@ constants = { path = "../../constants", version = "0.1" } log = "0.4" nix = "0.26" models = { path = "../../models", version = "0.1" } +modeled-types = { path = "../../models/modeled-types", version = "0.1"} schnauzer = { path = "../schnauzer", version = "0.1" } signpost = { path = "../../updater/signpost", version = "0.1" } simplelog = "0.12" snafu = "0.7" -serde_json = "1" -tokio = { version = "~1.32", default-features = false, features = ["macros", "rt-multi-thread"] } # LTS +serde = { version = "1.0", features = ["derive"]} +serde_json = "1.0" +toml = "0.8" [dev-dependencies] maplit = "1" diff --git a/sources/api/prairiedog/src/bootconfig.rs b/sources/api/prairiedog/src/bootconfig.rs index 0c3a9846a46..ab7ffe88920 100644 --- a/sources/api/prairiedog/src/bootconfig.rs +++ b/sources/api/prairiedog/src/bootconfig.rs @@ -1,13 +1,13 @@ use crate::error; use crate::error::Result; use crate::initrd::generate_initrd; -use model::modeled_types::{BootConfigKey, BootConfigValue}; -use model::BootSettings; +use modeled_types::{BootConfigKey, BootConfigValue}; +use serde::{Deserialize, Serialize}; use snafu::{ensure, ResultExt}; use std::collections::HashMap; use std::convert::TryInto; use std::path::Path; -use tokio::io; +use std::{fs, io}; // Boot config related consts const BOOTCONFIG_INITRD_PATH: &str = "/var/lib/bottlerocket/bootconfig.data"; @@ -22,6 +22,26 @@ const DEFAULT_BOOT_SETTINGS: BootSettings = BootSettings { init_parameters: None, }; +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "kebab-case")] +struct BootSettings { + reboot_to_reconcile: Option, + #[serde( + alias = "kernel", + rename(serialize = "kernel"), + default, + skip_serializing_if = "Option::is_none" + )] + kernel_parameters: Option>>, + #[serde( + alias = "init", + rename(serialize = "init"), + default, + skip_serializing_if = "Option::is_none" + )] + init_parameters: Option>>, +} + fn append_boot_config_value_list(values: &[BootConfigValue], output: &mut String) { for (i, v) in values.iter().enumerate() { if i > 0 { @@ -66,11 +86,11 @@ fn serialize_boot_settings_to_boot_config(boot_settings: &BootSettings) -> Resul } /// Queries Bottlerocket boot settings and generates initrd image file with boot config as the only data -pub(crate) async fn generate_boot_config

(socket_path: P) -> Result<()> +pub(crate) fn generate_boot_config

(config_path: P) -> Result<()> where P: AsRef, { - let bootconfig_bytes = match get_boot_config_settings(socket_path).await? { + let bootconfig_bytes = match get_boot_config_settings(config_path)? { Some(boot_settings) => { info!("Generating initrd boot config from boot settings"); trace!("Boot settings: {:?}", boot_settings); @@ -86,36 +106,25 @@ where }; let initrd = generate_initrd(&bootconfig_bytes)?; trace!("Writing initrd image file: {:?}", initrd); - tokio::fs::write(BOOTCONFIG_INITRD_PATH, &initrd) - .await - .context(error::WriteInitrdSnafu)?; + fs::write(BOOTCONFIG_INITRD_PATH, &initrd).context(error::WriteInitrdSnafu)?; Ok(()) } /// Retrieves boot config related Bottlerocket settings. If they don't exist in the settings model, /// we return `None` instead. -async fn get_boot_config_settings

(socket_path: P) -> Result> +fn get_boot_config_settings

(config_path: P) -> Result> where P: AsRef, { - let uri = "/settings"; - let settings: serde_json::Value = - schnauzer::v1::get_json(socket_path, uri, Some(("prefix", "boot"))) - .await - .context(error::RetrieveSettingsSnafu)?; - - match settings.get("boot") { - None => Ok(None), - Some(boot_settings_val) => Ok(Some( - serde_json::from_value(boot_settings_val.to_owned()) - .context(error::BootSettingsFromJsonValueSnafu)?, - )), - } + let config_str = fs::read_to_string(config_path.as_ref()).context(error::ReadFileSnafu { + path: config_path.as_ref().to_path_buf(), + })?; + toml::from_str(config_str.as_str()).context(error::InputTomlSnafu) } /// Reads `/proc/bootconfig`. Not having any boot config is ignored. -async fn read_proc_bootconfig() -> Result> { - match tokio::fs::read_to_string(PROC_BOOTCONFIG).await { +fn read_proc_bootconfig() -> Result> { + match fs::read_to_string(PROC_BOOTCONFIG) { Ok(s) => Ok(Some(s)), Err(e) => { // If there's no `/proc/bootconfig`, then the user hasn't provisioned any kernel boot configuration. @@ -131,8 +140,8 @@ async fn read_proc_bootconfig() -> Result> { } /// Reads `/proc/bootconfig` and populates the Bottlerocket boot settings based on the existing boot config data -pub(crate) async fn generate_boot_settings() -> Result<()> { - if let Some(proc_bootconfig) = read_proc_bootconfig().await? { +pub(crate) fn generate_boot_settings() -> Result<()> { + if let Some(proc_bootconfig) = read_proc_bootconfig()? { debug!( "Generating kernel boot config settings from `{}`", PROC_BOOTCONFIG @@ -265,7 +274,7 @@ fn parse_boot_config_to_boot_settings(bootconfig: &str) -> Result }) } -/// Given a boot config string, deserialize it to `model::BootSettings` and then serialize it back +/// Given a boot config string, deserialize it to `BootSettings` and then serialize it back /// out as a JSON string for sundog consumption fn boot_config_to_boot_settings_json(bootconfig_str: &str) -> Result { // We'll only send the setting if the existing boot config file fits our settings model @@ -275,18 +284,16 @@ fn boot_config_to_boot_settings_json(bootconfig_str: &str) -> Result { } /// Decides whether the host should be rebooted to have its boot settings take effect -pub(crate) async fn is_reboot_required

(socket_path: P) -> Result +pub(crate) fn is_reboot_required

(config_path: P) -> Result where P: AsRef, { - let old_boot_settings = match read_proc_bootconfig().await? { + let old_boot_settings = match read_proc_bootconfig()? { Some(proc_bootconfig) => parse_boot_config_to_boot_settings(&proc_bootconfig)?, None => DEFAULT_BOOT_SETTINGS, }; - let new_boot_settings = get_boot_config_settings(socket_path) - .await? - .unwrap_or(DEFAULT_BOOT_SETTINGS); + let new_boot_settings = get_boot_config_settings(config_path)?.unwrap_or(DEFAULT_BOOT_SETTINGS); let reboot_required = if new_boot_settings.reboot_to_reconcile.unwrap_or(false) { boot_settings_change_requires_reboot(&old_boot_settings, &new_boot_settings) @@ -297,7 +304,7 @@ where Ok(reboot_required) } -/// Check whether `model::BootSettings` changed in a way to warrant a reboot +/// Check whether `BootSettings` changed in a way to warrant a reboot fn boot_settings_change_requires_reboot( old_boot_settings: &BootSettings, new_boot_settings: &BootSettings, @@ -328,13 +335,13 @@ fn boot_settings_change_requires_reboot( #[cfg(test)] mod boot_settings_tests { + use super::BootSettings; use crate::bootconfig::{ boot_config_to_boot_settings_json, boot_settings_change_requires_reboot, serialize_boot_settings_to_boot_config, DEFAULT_BOOTCONFIG_STR, }; use maplit::hashmap; - use model::modeled_types::{BootConfigKey, BootConfigValue}; - use model::BootSettings; + use modeled_types::{BootConfigKey, BootConfigValue}; use serde_json::json; use serde_json::value::Value; use std::collections::HashMap; diff --git a/sources/api/prairiedog/src/error.rs b/sources/api/prairiedog/src/error.rs index 690d364f960..c1c733dcdc1 100644 --- a/sources/api/prairiedog/src/error.rs +++ b/sources/api/prairiedog/src/error.rs @@ -56,9 +56,6 @@ pub(super) enum Error { path: PathBuf, }, - #[snafu(display("Failed to retrieve settings: {}", source))] - RetrieveSettings { source: schnauzer::v1::Error }, - #[snafu(display("Failed to convert usize to u32: {}", source))] UsizeToU32 { source: std::num::TryFromIntError }, @@ -68,12 +65,12 @@ pub(super) enum Error { #[snafu(display("Failed to write initrd image file: {}", source))] WriteInitrd { source: std::io::Error }, + #[snafu(display("Error deserializing `BootSettings` from TOML: {}", source))] + InputToml { source: toml::de::Error }, + #[snafu(display("Error serializing `BootSettings` to JSON: {}", source))] OutputJson { source: serde_json::error::Error }, - #[snafu(display("Failed to deserialize `BootSettings` from JSON value: {}", source))] - BootSettingsFromJsonValue { source: serde_json::error::Error }, - #[snafu(display( "Invalid boot config file, expected key-value, or key entries for each line" ))] diff --git a/sources/api/prairiedog/src/main.rs b/sources/api/prairiedog/src/main.rs index 0d22ace7cd9..bae875f4786 100644 --- a/sources/api/prairiedog/src/main.rs +++ b/sources/api/prairiedog/src/main.rs @@ -20,7 +20,7 @@ use simplelog::{Config as LogConfig, LevelFilter, SimpleLogger, WriteLogger}; use snafu::{ensure, ResultExt}; use std::ffi::OsStr; use std::fs::{self, File}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{self, Command}; use std::thread; use std::time::Duration; @@ -29,6 +29,8 @@ mod bootconfig; mod error; mod initrd; +// Prairie dog config path +const DEFAULT_CONFIG_FILE: &str = "/etc/prairiedog.toml"; // Kdump related binary paths const MAKEDUMPFILE_PATH: &str = "/sbin/makedumpfile"; const KEXEC_PATH: &str = "/sbin/kexec"; @@ -60,9 +62,9 @@ struct Args { #[argh(option, default = "LevelFilter::Info", short = 'l')] /// log-level trace|debug|info|warn|error log_level: LevelFilter, - #[argh(option, default = "constants::API_SOCKET.to_string()", short = 's')] - /// socket-path path to apiserver socket - socket_path: String, + #[argh(option, default = "PathBuf::from(DEFAULT_CONFIG_FILE)", short = 'c')] + /// config-path to provide alternate config file path + config_path: PathBuf, #[argh(subcommand)] subcommand: Subcommand, } @@ -283,11 +285,11 @@ fn load_crash_kernel() -> Result<()> { Ok(()) } -async fn reboot_if_required

(socket_path: P) -> Result<()> +fn reboot_if_required

(config_path: P) -> Result<()> where P: AsRef, { - if is_reboot_required(socket_path).await? { + if is_reboot_required(config_path)? { info!("Boot settings changed and require a reboot to take effect. Initiating reboot..."); command("/usr/bin/systemctl", ["reboot"])?; // The "systemctl reboot" process will not block until the host does @@ -333,7 +335,7 @@ fn setup_logger(args: &Args) -> Result<()> { Ok(()) } -async fn run() -> Result<()> { +fn run() -> Result<()> { let args: Args = argh::from_env(); setup_logger(&args)?; @@ -341,15 +343,14 @@ async fn run() -> Result<()> { Subcommand::CaptureDump(_) => capture_dump(), Subcommand::PrepareBoot(_) => prepare_boot(), Subcommand::LoadCrashKernel(_) => load_crash_kernel(), - Subcommand::GenerateBootConfig(_) => generate_boot_config(args.socket_path).await, - Subcommand::GenerateBootSettings(_) => generate_boot_settings().await, - Subcommand::RebootIfRequired(_) => reboot_if_required(args.socket_path).await, + Subcommand::GenerateBootConfig(_) => generate_boot_config(args.config_path), + Subcommand::GenerateBootSettings(_) => generate_boot_settings(), + Subcommand::RebootIfRequired(_) => reboot_if_required(args.config_path), } } -#[tokio::main] -async fn main() { - if let Err(e) = run().await { +fn main() { + if let Err(e) = run() { error!("{}", e); process::exit(1); } diff --git a/sources/models/shared-defaults/boot.toml b/sources/models/shared-defaults/boot.toml index f3d39dd4778..abf4ad53535 100644 --- a/sources/models/shared-defaults/boot.toml +++ b/sources/models/shared-defaults/boot.toml @@ -5,5 +5,9 @@ affected-services = ["bootconfig"] setting-generator = "/usr/bin/prairiedog generate-boot-settings" [services.bootconfig] -configuration-files = [] +configuration-files = ["prairiedog-toml"] restart-commands = ["/usr/bin/prairiedog generate-boot-config"] + +[configuration-files.prairiedog-toml] +path = "/etc/prairiedog.toml" +template-path = "/usr/share/templates/prairiedog-toml"