From bcfb7156231faac8afb4a2f1eaf649e4060d172a Mon Sep 17 00:00:00 2001 From: Jarrett Tierney Date: Fri, 19 Jan 2024 20:52:34 +0000 Subject: [PATCH] bootstrap-containers: migrate to using configuration file This migrates the bootstrap-containers tool to fetch its settings via a generated toml file. log: * add migrations and package changes * migrate bootstrap-containers to load from config file * add mount with secret_t --- Release.toml | 2 + packages/os/bootstrap-containers-toml | 17 ++ packages/os/os.spec | 4 +- sources/Cargo.lock | 21 ++- sources/Cargo.toml | 2 + sources/api/bootstrap-containers/Cargo.toml | 9 +- sources/api/bootstrap-containers/src/main.rs | 158 +++++++----------- .../Cargo.toml | 13 ++ .../src/main.rs | 16 ++ .../Cargo.toml | 13 ++ .../src/main.rs | 18 ++ sources/models/shared-defaults/defaults.toml | 6 +- 12 files changed, 171 insertions(+), 108 deletions(-) create mode 100644 packages/os/bootstrap-containers-toml create mode 100644 sources/api/migration/migrations/v1.20.0/bootstrap-containers-config-file-v0-1-0/Cargo.toml create mode 100644 sources/api/migration/migrations/v1.20.0/bootstrap-containers-config-file-v0-1-0/src/main.rs create mode 100644 sources/api/migration/migrations/v1.20.0/bootstrap-containers-services-cfg-v0-1-0/Cargo.toml create mode 100644 sources/api/migration/migrations/v1.20.0/bootstrap-containers-services-cfg-v0-1-0/src/main.rs diff --git a/Release.toml b/Release.toml index 1a3bd7b8f1c..dc63d508ef5 100644 --- a/Release.toml +++ b/Release.toml @@ -290,4 +290,6 @@ version = "1.20.0" "migrate_v1.20.0_host-containers-config-list-v0-1-0.lz4", "migrate_v1.20.0_corndog-config-file-v0-1-0.lz4", "migrate_v1.20.0_corndog-services-cfg-v0-1-0.lz4", + "migrate_v1.20.0_bootstrap-containers-config-file-v0-1-0.lz4", + "migrate_v1.20.0_bootstrap-containers-services-cfg-v0-1-0.lz4", ] diff --git a/packages/os/bootstrap-containers-toml b/packages/os/bootstrap-containers-toml new file mode 100644 index 00000000000..3988328f453 --- /dev/null +++ b/packages/os/bootstrap-containers-toml @@ -0,0 +1,17 @@ +[required-extensions] +bootstrap-containers = "v1" +std = { version = "v1", helpers = ["if_not_null"] } ++++ +{{#if_not_null settings.bootstrap-containers}} +{{#each settings.bootstrap-containers}} +["{{@key}}"] +source = "{{{this.source}}}" +mode = "{{{this.mode}}}" +{{#if_not_null this.user-data}} +user-data = "{{{this.user-data}}}" +{{/if_not_null}} +{{#if_not_null this.essential}} +essential = {{this.essential}} +{{/if_not_null}} +{{/each}} +{{/if_not_null}} diff --git a/packages/os/os.spec b/packages/os/os.spec index 11d40c5c1fc..53a9937b9d7 100644 --- a/packages/os/os.spec +++ b/packages/os/os.spec @@ -35,6 +35,7 @@ Source14: certdog-toml Source15: prairiedog-toml Source16: thar-be-updates-toml Source17: corndog-toml +Source18: bootstrap-containers-toml Source19: host-containers-toml # 1xx sources: systemd units @@ -477,7 +478,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} %{S:15} %{S:16} %{S:17} %{S:19} \ +install -p -m 0644 %{S:5} %{S:6} %{S:7} %{S:8} %{S:14} %{S:15} %{S:16} %{S:17} %{S:18} %{S:19} \ %{buildroot}%{_cross_templatedir} install -d %{buildroot}%{_cross_unitdir} @@ -700,6 +701,7 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir} %{_cross_bindir}/bootstrap-containers %{_cross_unitdir}/bootstrap-containers@.service %{_cross_tmpfilesdir}/bootstrap-containers.conf +%{_cross_templatedir}/bootstrap-containers-toml %files -n %{_cross_os}bloodhound %{_cross_bindir}/bloodhound diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 6493c83058e..4b8707e9842 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -1068,17 +1068,30 @@ dependencies = [ name = "bootstrap-containers" version = "0.1.0" dependencies = [ - "apiclient", "base64", "constants", - "datastore", "generate-readme", "log", - "models", + "modeled-types", + "serde", "serde_json", "simplelog", "snafu 0.8.2", - "tokio", + "toml", +] + +[[package]] +name = "bootstrap-containers-config-file-v0-1-0" +version = "0.1.0" +dependencies = [ + "migration-helpers", +] + +[[package]] +name = "bootstrap-containers-services-cfg-v0-1-0" +version = "0.1.0" +dependencies = [ + "migration-helpers", ] [[package]] diff --git a/sources/Cargo.toml b/sources/Cargo.toml index a252344a4fa..e78bce3d627 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -76,6 +76,8 @@ members = [ "api/migration/migrations/v1.20.0/host-containers-config-list-v0-1-0", "api/migration/migrations/v1.20.0/corndog-config-file-v0-1-0", "api/migration/migrations/v1.20.0/corndog-services-cfg-v0-1-0", + "api/migration/migrations/v1.20.0/bootstrap-containers-config-file-v0-1-0", + "api/migration/migrations/v1.20.0/bootstrap-containers-services-cfg-v0-1-0", "bloodhound", diff --git a/sources/api/bootstrap-containers/Cargo.toml b/sources/api/bootstrap-containers/Cargo.toml index 202a4466c21..e4c638c05f9 100644 --- a/sources/api/bootstrap-containers/Cargo.toml +++ b/sources/api/bootstrap-containers/Cargo.toml @@ -10,16 +10,15 @@ build = "build.rs" exclude = ["README.md"] [dependencies] -apiclient = { path = "../apiclient", version = "0.1" } -constants = { path = "../../constants", version = "0.1" } -datastore = { path = "../datastore", version = "0.1" } base64 = "0.21" +constants = { path = "../../constants", version = "0.1" } log = "0.4" -models = { path = "../../models", version = "0.1" } +modeled-types = { path = "../../models/modeled-types", version = "0.1" } +serde = { version = "1.0", features = ["derive"] } serde_json = "1" simplelog = "0.12" snafu = "0.8" -tokio = { version = "~1.32", default-features = false, features = ["macros", "rt-multi-thread"] } # LTS +toml = "0.8" [build-dependencies] generate-readme = { version = "0.1", path = "../../generate-readme" } diff --git a/sources/api/bootstrap-containers/src/main.rs b/sources/api/bootstrap-containers/src/main.rs index 39fd9ddcb1b..6bc2d0f5c1c 100644 --- a/sources/api/bootstrap-containers/src/main.rs +++ b/sources/api/bootstrap-containers/src/main.rs @@ -74,7 +74,7 @@ journalctl -u bootstrap-containers@bear.service extern crate log; use base64::Engine; -use datastore::{serialize_scalar, Key, KeyType}; +use serde::{Deserialize, Serialize}; use simplelog::{Config as LogConfig, LevelFilter, SimpleLogger}; use snafu::{ensure, OptionExt, ResultExt}; use std::collections::HashMap; @@ -83,29 +83,43 @@ use std::env; use std::ffi::OsStr; use std::fmt::Write; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{self, Command}; use std::str::FromStr; -use model::modeled_types::{BootstrapContainerMode, Identifier}; +use modeled_types::{BootstrapContainerMode, Identifier, Url, ValidBase64}; const ENV_FILE_DIR: &str = "/etc/bootstrap-containers"; const DROPIN_FILE_DIR: &str = "/etc/systemd/system"; const PERSISTENT_STORAGE_DIR: &str = "/local/bootstrap-containers"; const DROP_IN_FILENAME: &str = "overrides.conf"; +const DEFAULT_CONFIG_PATH: &str = "/etc/bootstrap-containers/bootstrap-containers.toml"; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +struct BootstrapContainer { + #[serde(default, skip_serializing_if = "Option::is_none")] + source: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + mode: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + user_data: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + essential: Option, +} /// Stores user-supplied global arguments #[derive(Debug)] struct Args { log_level: LevelFilter, - socket_path: String, + config_path: PathBuf, } impl Default for Args { fn default() -> Self { Self { log_level: LevelFilter::Info, - socket_path: constants::API_SOCKET.to_string(), + config_path: PathBuf::from_str(DEFAULT_CONFIG_PATH).unwrap(), } } } @@ -134,16 +148,15 @@ fn usage() { mark-bootstrap Global arguments: - [ --socket-path PATH ] + [ --config-path PATH ] [ --log-level trace|debug|info|warn|error ] Mark bootstrap arguments: --container-id CONTAINER-ID --mode MODE - Socket path defaults to {}", - program_name, - constants::API_SOCKET, + Config path defaults to {}", + program_name, DEFAULT_CONFIG_PATH, ); } @@ -165,10 +178,11 @@ fn parse_args(args: env::Args) -> Result<(Args, Subcommand)> { .context(error::LogLevelSnafu { log_level })?; } - "-s" | "--socket-path" => { - global_args.socket_path = iter.next().context(error::UsageSnafu { - message: "Did not give argument to --socket-path", - })? + "-c" | "--config-path" => { + let config_str = iter.next().context(error::UsageSnafu { + message: "Did not give argument to --config-path", + })?; + global_args.config_path = PathBuf::from(config_str.as_str()); } // Subcommands @@ -242,10 +256,7 @@ fn parse_mark_bootstrap_args(args: Vec) -> Result { } /// Handles how the bootstrap containers' systemd units are created -fn handle_bootstrap_container( - name: S, - container_details: &model::BootstrapContainer, -) -> Result<()> +fn handle_bootstrap_container(name: S, container_details: &BootstrapContainer) -> Result<()> where S: AsRef, { @@ -382,27 +393,17 @@ where } /// Query the API for the currently defined bootstrap containers -async fn get_bootstrap_containers

( - socket_path: P, -) -> Result> +fn get_bootstrap_containers

(config_path: P) -> Result> where P: AsRef, { - debug!("Querying the API for settings"); - - let method = "GET"; - let uri = constants::API_SETTINGS_URI; - let (_code, response_body) = apiclient::raw_request(&socket_path, uri, method, None) - .await - .context(error::APIRequestSnafu { method, uri })?; - - // Build a Settings struct from the response string - debug!("Deserializing response"); - let settings: model::Settings = - serde_json::from_str(&response_body).context(error::ResponseJsonSnafu { method, uri })?; + let config_str = fs::read_to_string(config_path.as_ref()).context(error::ReadConfigSnafu { + config_path: config_path.as_ref(), + })?; - // If bootstrap containers aren't defined, return an empty map - Ok(settings.bootstrap_containers.unwrap_or_default()) + toml::from_str(&config_str).context(error::DeserializationSnafu { + config_path: config_path.as_ref(), + }) } /// SystemdUnit stores the systemd unit being manipulated @@ -492,12 +493,12 @@ where } /// Handles the `create-containers` subcommand -async fn create_containers

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

(config_path: P) -> Result<()> where P: AsRef, { let mut failed = 0usize; - let bootstrap_containers = get_bootstrap_containers(socket_path).await?; + let bootstrap_containers = get_bootstrap_containers(config_path)?; for (name, container_details) in bootstrap_containers.iter() { // Continue to handle other bootstrap containers if we fail one if let Err(e) = handle_bootstrap_container(name, container_details) { @@ -520,38 +521,24 @@ where /// Handles the `mark-bootstrap` subcommand, which is called by the bootstrap /// container's systemd unit, which could potentially cause a concurrent invocation /// in this binary after the API setting finalizes. -async fn mark_bootstrap

(args: MarkBootstrapArgs, socket_path: P) -> Result<()> -where - P: AsRef, -{ +fn mark_bootstrap(args: MarkBootstrapArgs) -> Result<()> { let container_id: &str = args.container_id.as_ref(); let mode = args.mode.as_ref(); info!("Mode for '{}' is '{}'", container_id, mode); // When 'mode' is 'once', the container is marked as 'off' once it - // finishes. This guarantees that the the container is only started in + // finishes. This guarantees that the container is only started in // the boot where it was created. if mode != "always" { - let formatted = format!("settings.bootstrap-containers.{}.mode", container_id); - let key = Key::new(KeyType::Data, &formatted) - .context(error::KeyFormatSnafu { key: formatted })?; - let value = serialize_scalar(&"off".to_string()).context(error::SerializeSnafu)?; - - let mut map = HashMap::new(); - map.insert(key, value); - let settings: model::Settings = datastore::deserialization::from_map(&map) - .context(error::SettingsDeserializeSnafu { settings: map })?; - + let formatted = format!("settings.bootstrap-containers.{}.mode=off", container_id); info!("Turning off container '{}'", container_id); - apiclient::set::set(socket_path, &settings) - .await - .context(error::SetSnafu)?; + command("apiclient", ["set", formatted.as_str()])?; } Ok(()) } -async fn run() -> Result<()> { +fn run() -> Result<()> { let (args, subcommand) = parse_args(env::args())?; // SimpleLogger will send errors to stderr and anything less to stdout. @@ -560,19 +547,16 @@ async fn run() -> Result<()> { info!("bootstrap-containers started"); match subcommand { - Subcommand::CreateContainers => create_containers(args.socket_path).await, - Subcommand::MarkBootstrap(mark_bootstrap_args) => { - mark_bootstrap(mark_bootstrap_args, args.socket_path).await - } + Subcommand::CreateContainers => create_containers(args.config_path), + Subcommand::MarkBootstrap(mark_bootstrap_args) => mark_bootstrap(mark_bootstrap_args), } } // Returning a Result from main makes it print a Debug representation of the error, but with Snafu // we have nice Display representations of the error, so we wrap "main" (run) and print any error. // https://github.com/shepmaster/snafu/issues/110 -#[tokio::main] -async fn main() { - if let Err(e) = run().await { +fn main() { + if let Err(e) = run() { match e { error::Error::Usage { .. } => { eprintln!("{}", e); @@ -590,9 +574,7 @@ async fn main() { // =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= mod error { - use datastore::Key; use snafu::Snafu; - use std::collections::HashMap; use std::fmt; use std::io; use std::path::PathBuf; @@ -601,12 +583,16 @@ mod error { #[derive(Debug, Snafu)] #[snafu(visibility(pub(super)))] pub(super) enum Error { - #[snafu(display("Error sending {} to {}: {}", method, uri, source))] - APIRequest { - method: String, - uri: String, - #[snafu(source(from(apiclient::Error, Box::new)))] - source: Box, + #[snafu(display("Failed to read settings from config at {}: {}", config_path.display(), source))] + ReadConfig { + config_path: PathBuf, + source: std::io::Error, + }, + + #[snafu(display("Failed to deserialize settings from config at {}: {}", config_path.display(), source))] + Deserialization { + config_path: PathBuf, + source: toml::de::Error, }, #[snafu(display( @@ -621,9 +607,7 @@ mod error { // `try_from` in `BootstrapContainerMode` already returns a useful error message #[snafu(display("Failed to parse mode: {}", source))] - BootstrapContainerMode { - source: model::modeled_types::error::Error, - }, + BootstrapContainerMode { source: modeled_types::error::Error }, #[snafu(display("'{}' failed - stderr: {}", bin_path, String::from_utf8_lossy(&output.stderr)))] @@ -635,23 +619,6 @@ mod error { source: std::io::Error, }, - #[snafu(display("Failed to deserialize key '{}': '{}'", key, source))] - KeyDeserialize { - key: String, - source: datastore::deserialization::Error, - }, - - #[snafu(display( - "Adding container name to key '{}' resulted in invalid format: {}", - key, - source - ))] - KeyFormat { - key: String, - #[snafu(source(from(datastore::Error, Box::new)))] - source: Box, - }, - #[snafu(display("Logger setup error: {}", source))] Logger { source: log::SetLoggerError }, @@ -688,14 +655,11 @@ mod error { #[snafu(display("Unable to serialize data: {}", source))] Serialize { source: serde_json::Error }, - #[snafu(display("Failed to change settings: {}", source))] - Set { source: apiclient::set::Error }, + #[snafu(display("Failed to change settings via apiclient: {}", source))] + Set { source: io::Error }, - #[snafu(display("Failed to deserialize settings '{:#?}': '{}'", settings, source))] - SettingsDeserialize { - settings: HashMap, - source: datastore::deserialization::Error, - }, + #[snafu(display("Failed to change settings, apiclient returned an error"))] + SetClient, #[snafu(display("{}", message))] Usage { message: String }, diff --git a/sources/api/migration/migrations/v1.20.0/bootstrap-containers-config-file-v0-1-0/Cargo.toml b/sources/api/migration/migrations/v1.20.0/bootstrap-containers-config-file-v0-1-0/Cargo.toml new file mode 100644 index 00000000000..9f2a1a6fb34 --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/bootstrap-containers-config-file-v0-1-0/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bootstrap-containers-config-file-v0-1-0" +version = "0.1.0" +edition = "2021" +authors = ["Jarrett Tierney "] +license = "Apache-2.0 OR MIT" +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.20.0/bootstrap-containers-config-file-v0-1-0/src/main.rs b/sources/api/migration/migrations/v1.20.0/bootstrap-containers-config-file-v0-1-0/src/main.rs new file mode 100644 index 00000000000..1c47e6ca1b0 --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/bootstrap-containers-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.bootstrap-containers-toml", + ])) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/migration/migrations/v1.20.0/bootstrap-containers-services-cfg-v0-1-0/Cargo.toml b/sources/api/migration/migrations/v1.20.0/bootstrap-containers-services-cfg-v0-1-0/Cargo.toml new file mode 100644 index 00000000000..cf6d433eae2 --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/bootstrap-containers-services-cfg-v0-1-0/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bootstrap-containers-services-cfg-v0-1-0" +version = "0.1.0" +edition = "2021" +authors = ["Jarrett Tierney "] +license = "Apache-2.0 OR MIT" +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.20.0/bootstrap-containers-services-cfg-v0-1-0/src/main.rs b/sources/api/migration/migrations/v1.20.0/bootstrap-containers-services-cfg-v0-1-0/src/main.rs new file mode 100644 index 00000000000..4c45018e5fb --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/bootstrap-containers-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.bootstrap-containers.configuration-files", + old_vals: &["host-ctr-toml"], + new_vals: &["host-ctr-toml", "bootstrap-containers-toml"], + }])) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/models/shared-defaults/defaults.toml b/sources/models/shared-defaults/defaults.toml index d50acc4488c..6d4f0847a9c 100644 --- a/sources/models/shared-defaults/defaults.toml +++ b/sources/models/shared-defaults/defaults.toml @@ -169,12 +169,16 @@ template-path = "/usr/share/templates/corndog-toml" # Bootstrap Containers [services.bootstrap-containers] -configuration-files = ["host-ctr-toml"] +configuration-files = ["host-ctr-toml", "bootstrap-containers-toml"] restart-commands = ["/usr/bin/bootstrap-containers create-containers"] [metadata.settings.bootstrap-containers] affected-services = ["bootstrap-containers"] +[configuration-files.bootstrap-containers-toml] +path = "/etc/bootstrap-containers/bootstrap-containers.toml" +template-path = "/usr/share/templates/bootstrap-containers-toml" + # Certdog [services.pki]