From c28d3f0e57e7a7017e081e459f3e5485b9addc11 Mon Sep 17 00:00:00 2001 From: David Runge <dvzrv@archlinux.org> Date: Tue, 14 Jan 2025 14:32:11 +0100 Subject: [PATCH 1/2] feat: Add `signstar-common` crate to provide common facilities - Adds all public data required for the `signstar-configure-build` crate, as it will be shared by other crates. - Adds NetHSM specific defaults. - Adds basic configuration file retrieval and directory handling for Signstar hosts. - Adds administrative credentials file retrieval and directory handling for Signstar hosts. Signed-off-by: David Runge <dvzrv@archlinux.org> --- .lycheeignore | 4 + Cargo.lock | 8 + Cargo.toml | 2 + signstar-common/Cargo.toml | 14 ++ signstar-common/README.md | 22 +++ signstar-common/src/admin_credentials.rs | 113 +++++++++++++ signstar-common/src/common.rs | 26 +++ signstar-common/src/config.rs | 205 +++++++++++++++++++++++ signstar-common/src/lib.rs | 8 + signstar-common/src/nethsm.rs | 26 +++ signstar-common/src/ssh.rs | 31 ++++ signstar-common/src/system_user.rs | 62 +++++++ 12 files changed, 521 insertions(+) create mode 100644 signstar-common/Cargo.toml create mode 100644 signstar-common/README.md create mode 100644 signstar-common/src/admin_credentials.rs create mode 100644 signstar-common/src/common.rs create mode 100644 signstar-common/src/config.rs create mode 100644 signstar-common/src/lib.rs create mode 100644 signstar-common/src/nethsm.rs create mode 100644 signstar-common/src/ssh.rs create mode 100644 signstar-common/src/system_user.rs diff --git a/.lycheeignore b/.lycheeignore index 26f8b06f..75cbd132 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -3,3 +3,7 @@ # an URL fragment that's used in format! macro # see: https://github.com/lycheeverse/lychee/issues/1492 https://raw.githubusercontent.com/Nitrokey/nethsm-sdk-py/main/tests/%7B%7D + +# URLs that only become available after release of a component +https://signstar.archlinux.page/rustdoc/signstar_common/ +https://docs.rs/signstar_common/latest/signstar_common/ diff --git a/Cargo.lock b/Cargo.lock index fa094f4d..d9cdcaef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3133,6 +3133,14 @@ dependencies = [ "rand_core", ] +[[package]] +name = "signstar-common" +version = "0.1.0" +dependencies = [ + "nethsm", + "thiserror 2.0.11", +] + [[package]] name = "signstar-configure-build" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 54407d2a..6b970a36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "nethsm-config", "nethsm-tests", "signstar-configure-build", + "signstar-common", "signstar-request-signature", ] @@ -31,6 +32,7 @@ serde = { version = "1.0.215", features = ["derive"] } # Then adjust the tests in signstar-request-signature/src/lib.rs to additionally test # inputs that reference the "old_sha2". sha2 = "0.11.0-pre.4" +signstar-common = { path = "signstar-common", version = "0.1.0" } signstar-request-signature = { path = "signstar-request-signature", version = "0.1.0" } strum = { version = "0.27.0", features = ["derive"] } testdir = "0.9.3" diff --git a/signstar-common/Cargo.toml b/signstar-common/Cargo.toml new file mode 100644 index 00000000..c07c559c --- /dev/null +++ b/signstar-common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors.workspace = true +description = "Common components for Signstar libraries and command-line interfaces" +edition.workspace = true +homepage.workspace = true +keywords = ["user", "signstar", "nethsm"] +license.workspace = true +name = "signstar-common" +repository.workspace = true +version = "0.1.0" + +[dependencies] +nethsm.workspace = true +thiserror.workspace = true diff --git a/signstar-common/README.md b/signstar-common/README.md new file mode 100644 index 00000000..9f04895b --- /dev/null +++ b/signstar-common/README.md @@ -0,0 +1,22 @@ +# Signstar common + +Shared components and data types for Signstar tools and libraries + +## Documentation + +- <https://signstar.archlinux.page/rustdoc/signstar_common/> for development version of the crate +- <https://docs.rs/signstar_common/latest/signstar_common/> for released versions of the crate + +## Contributing + +Please refer to the [contributing guidelines] to learn how to contribute to this project. + +## License + +This project may be used under the terms of the [Apache-2.0] or [MIT] license. + +Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses. + +[Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0 +[MIT]: https://opensource.org/licenses/MIT +[contributing guidelines]: ../CONTRIBUTING.md diff --git a/signstar-common/src/admin_credentials.rs b/signstar-common/src/admin_credentials.rs new file mode 100644 index 00000000..eedc834e --- /dev/null +++ b/signstar-common/src/admin_credentials.rs @@ -0,0 +1,113 @@ +//! Data and functions for using administrative credentials on a Signstar host. +//! +//! # Examples +//! +//! ``` +//! use signstar_common::admin_credentials::get_credentials_dir; +//! +//! // Get the directory path in which administrative credentials reside. +//! println!("{:?}", get_credentials_dir()); +//! ``` + +use std::{ + fs::{Permissions, create_dir_all, set_permissions}, + os::unix::fs::{PermissionsExt, chown}, + path::PathBuf, +}; + +use crate::common::{CREDENTIALS_DIR_MODE, get_data_home}; + +/// File name of plaintext administrative credentials. +const PLAINTEXT_CREDENTIALS_FILE: &str = "admin-credentials.toml"; + +/// File name of systemd-creds encrypted administrative credentials. +const SYSTEMD_CREDS_CREDENTIALS_FILE: &str = "admin-credentials.creds"; + +/// The directory for administrative credentials (encrypted and unencrypted). +const CREDENTIALS_DIR: &str = "creds/"; + +/// An error that may occur when handling credentials. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Applying permissions to a file failed. + #[error("Unable to apply permissions {permissions} to {path}:\n{source}")] + ApplyPermissions { + permissions: u32, + path: PathBuf, + source: std::io::Error, + }, + + /// No plaintext administrative credentials file can be found + #[error("Unable to create directory {dir}:\n{source}")] + CreateDirectory { + dir: &'static str, + source: std::io::Error, + }, + + /// The ownership of a directory can not be set. + #[error("Ownership of directory {dir} can not be changed to user {system_user}: {source}")] + DirChangeOwner { + dir: PathBuf, + system_user: String, + source: std::io::Error, + }, +} + +/// Returns the path of the directory in which administrative credentials reside. +pub fn get_credentials_dir() -> PathBuf { + get_data_home().join(PathBuf::from(CREDENTIALS_DIR)) +} + +/// Returns the file path for plaintext administrative credentials. +pub fn get_plaintext_credentials_file() -> PathBuf { + get_credentials_dir().join(PathBuf::from(PLAINTEXT_CREDENTIALS_FILE)) +} + +/// Returns the file path for systemd-creds encrypted administrative credentials. +pub fn get_systemd_creds_credentials_file() -> PathBuf { + get_credentials_dir().join(PathBuf::from(SYSTEMD_CREDS_CREDENTIALS_FILE)) +} + +/// Creates the directory for administrative credentials. +/// +/// # Errors +/// +/// Returns an error if the directory or one of its parents can not be created. +/// Refer to [`create_dir_all`] for further information on failure scenarios. +pub fn create_credentials_dir() -> Result<(), Error> { + let credentials_dir = get_credentials_dir(); + create_dir_all(credentials_dir.as_path()).map_err(|source| Error::CreateDirectory { + dir: CREDENTIALS_DIR, + source, + })?; + + // Set the permissions of the credentials directory to `CREDENTIALS_DIR_MODE`. + set_permissions( + credentials_dir.as_path(), + Permissions::from_mode(CREDENTIALS_DIR_MODE), + ) + .map_err(|source| Error::ApplyPermissions { + permissions: CREDENTIALS_DIR_MODE, + path: credentials_dir.clone(), + source, + })?; + + // Recursively chown all directories to root, until `DATA_HOME` is + // reached. + let data_home = get_data_home(); + let mut chown_dir = credentials_dir.clone(); + while chown_dir != data_home { + chown(&chown_dir, Some(0), Some(0)).map_err(|source| Error::DirChangeOwner { + dir: chown_dir.to_path_buf(), + system_user: "root".to_string(), + source, + })?; + if let Some(parent) = &chown_dir.parent() { + chown_dir = parent.to_path_buf() + } else { + break; + } + } + + Ok(()) +} diff --git a/signstar-common/src/common.rs b/signstar-common/src/common.rs new file mode 100644 index 00000000..8dd86eb9 --- /dev/null +++ b/signstar-common/src/common.rs @@ -0,0 +1,26 @@ +//! Common data for all Signstar functionalities. +//! +//! # Examples +//! +//! ``` +//! use signstar_common::common::get_data_home; +//! +//! // Get the directory below which all Signstar related data is stored. +//! println!("{:?}", get_data_home()); +//! ``` + +use std::path::PathBuf; + +/// The directory below which o store all Signstar related data. +const DATA_HOME: &str = "/var/lib/signstar/"; + +/// The file mode of directories containing credentials. +pub const CREDENTIALS_DIR_MODE: u32 = 0o100700; + +/// The file mode of secret files. +pub const SECRET_FILE_MODE: u32 = 0o100600; + +/// Get the directory below which all Signstar related data is stored. +pub fn get_data_home() -> PathBuf { + PathBuf::from(DATA_HOME) +} diff --git a/signstar-common/src/config.rs b/signstar-common/src/config.rs new file mode 100644 index 00000000..5cba4ce1 --- /dev/null +++ b/signstar-common/src/config.rs @@ -0,0 +1,205 @@ +//! Default locations for Signstar configuration files. +//! +//! # Examples +//! +//! ``` +//! use signstar_common::config::{ +//! get_config_file_or_default, +//! get_config_file_paths, +//! get_config_file, +//! get_default_config_dir_path, +//! get_default_config_file_path, +//! get_etc_override_config_file_path, +//! get_etc_override_dir_path, +//! get_run_override_config_file_path, +//! get_run_override_dir_path, +//! get_usr_local_override_config_file_path, +//! get_usr_local_override_dir_path, +//! }; +//! +//! // Get directory paths for Signstar configuration files. +//! println!("{:?}", get_etc_override_dir_path()); +//! println!("{:?}", get_run_override_dir_path()); +//! println!("{:?}", get_usr_local_override_dir_path()); +//! println!("{:?}", get_default_config_dir_path()); +//! +//! // Get file paths for Signstar configuration files. +//! println!("{:?}", get_etc_override_config_file_path()); +//! println!("{:?}", get_run_override_config_file_path()); +//! println!("{:?}", get_usr_local_override_config_file_path()); +//! println!("{:?}", get_default_config_file_path()); +//! +//! // Get the first config file found, according to directory precedence. +//! println!("{:?}", get_config_file()); +//! +//! // Get the first config file found, according to directory precedence, or the default if none are found. +//! println!("{:?}", get_config_file_or_default()); +//! +//! // Get all configuration file paths, sorted by directory precedence. +//! println!("{:?}", get_config_file_paths()); +//! ``` + +use std::{fs::create_dir_all, path::PathBuf}; + +/// The default config directory below "/usr" for Signstar hosts. +const DEFAULT_CONFIG_DIR: &str = "/usr/share/signstar/"; + +/// The override config directory below "/etc" for Signstar hosts. +const ETC_OVERRIDE_CONFIG_DIR: &str = "/etc/signstar/"; + +/// The override config directory below "/run" for Signstar hosts. +const RUN_OVERRIDE_CONFIG_DIR: &str = "/run/signstar/"; + +/// The override config directory below "/usr/local" for Signstar hosts. +const USR_LOCAL_OVERRIDE_CONFIG_DIR: &str = "/usr/local/share/signstar/"; + +/// The filename of a Signstar configuration file. +const CONFIG_FILE: &str = "config.toml"; + +/// An error that may occur when handling configuration directories or files. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// A directory can not be created. + #[error("Unable to create directory {dir}:\n{source}")] + CreateDirectory { dir: String, source: std::io::Error }, +} + +/// Returns the first Signstar configuration file available, or [`None`] if none found. +/// +/// Considers files named `config.toml` in the following directories in descending priority: +/// - `/etc/signstar` +/// - `/run/signstar` +/// - `/usr/local/share/signstar` +/// - `/usr/share/signstar` +/// +/// The first existing file is returned. +/// If no file is found [`None`] is returned. +pub fn get_config_file() -> Option<PathBuf> { + [ + get_etc_override_config_file_path(), + get_run_override_config_file_path(), + get_usr_local_override_config_file_path(), + get_default_config_file_path(), + ] + .into_iter() + .find(|file| file.is_file()) +} + +/// Returns the first Signstar configuration file available, or the default if none found. +/// +/// Considers files named `config.toml` in the following directories in descending priority: +/// - `/etc/signstar` +/// - `/run/signstar` +/// - `/usr/local/share/signstar` +/// - `/usr/share/signstar` +/// +/// The first existing file is returned. +/// If no file is found, the default location `/usr/share/signstar/config.toml` is returned. +pub fn get_config_file_or_default() -> PathBuf { + let Some(config) = get_config_file() else { + return get_default_config_file_path(); + }; + config +} + +/// Returns a list of all configuration file locations, sorted by precedence. +pub fn get_config_file_paths() -> Vec<PathBuf> { + vec![ + get_etc_override_config_file_path(), + get_run_override_config_file_path(), + get_usr_local_override_config_file_path(), + get_default_config_file_path(), + ] +} + +/// Returns the file path of the configuration file override below /etc. +pub fn get_etc_override_config_file_path() -> PathBuf { + PathBuf::from([ETC_OVERRIDE_CONFIG_DIR, CONFIG_FILE].concat()) +} + +/// Returns the directory path of the configuration override directory below /etc. +pub fn get_etc_override_dir_path() -> PathBuf { + PathBuf::from(ETC_OVERRIDE_CONFIG_DIR) +} + +/// Returns the file path of the configuration file override below /run. +pub fn get_run_override_config_file_path() -> PathBuf { + PathBuf::from([RUN_OVERRIDE_CONFIG_DIR, CONFIG_FILE].concat()) +} + +/// Returns the directory path of the configuration override directory below /run. +pub fn get_run_override_dir_path() -> PathBuf { + PathBuf::from(RUN_OVERRIDE_CONFIG_DIR) +} + +/// Returns the file path of the configuration file override below /usr/local. +pub fn get_usr_local_override_config_file_path() -> PathBuf { + PathBuf::from([USR_LOCAL_OVERRIDE_CONFIG_DIR, CONFIG_FILE].concat()) +} + +/// Returns the directory path of the configuration override directory below /usr/local. +pub fn get_usr_local_override_dir_path() -> PathBuf { + PathBuf::from(USR_LOCAL_OVERRIDE_CONFIG_DIR) +} + +/// Returns the file path of the default configuration file /usr. +pub fn get_default_config_file_path() -> PathBuf { + PathBuf::from([DEFAULT_CONFIG_DIR, CONFIG_FILE].concat()) +} + +/// Returns the directory path of the default configuration directory below /usr. +pub fn get_default_config_dir_path() -> PathBuf { + PathBuf::from(DEFAULT_CONFIG_DIR) +} + +/// Creates the default configuration directory below /usr. +/// +/// # Errors +/// +/// Returns an error if the directory or one of its parents can not be created. +/// Refer to [`create_dir_all`] for further information on failure scenarios. +pub fn create_default_config_dir() -> Result<(), Error> { + create_dir_all(get_default_config_dir_path()).map_err(|source| Error::CreateDirectory { + dir: DEFAULT_CONFIG_DIR.to_string(), + source, + }) +} + +/// Creates the configuration override dir below /etc. +/// +/// # Errors +/// +/// Returns an error if the directory or one of its parents can not be created. +/// Refer to [`create_dir_all`] for further information on failure scenarios. +pub fn create_etc_override_config_dir() -> Result<(), Error> { + create_dir_all(get_etc_override_dir_path()).map_err(|source| Error::CreateDirectory { + dir: ETC_OVERRIDE_CONFIG_DIR.to_string(), + source, + }) +} + +/// Creates the configuration override dir below /run. +/// +/// # Errors +/// +/// Returns an error if the directory or one of its parents can not be created. +/// Refer to [`create_dir_all`] for further information on failure scenarios. +pub fn create_run_override_config_dir() -> Result<(), Error> { + create_dir_all(get_run_override_dir_path()).map_err(|source| Error::CreateDirectory { + dir: RUN_OVERRIDE_CONFIG_DIR.to_string(), + source, + }) +} + +/// Creates the configuration override dir below /usr/local. +/// +/// # Errors +/// +/// Returns an error if the directory or one of its parents can not be created. +/// Refer to [`create_dir_all`] for further information on failure scenarios. +pub fn create_usr_local_override_config_dir() -> Result<(), Error> { + create_dir_all(get_usr_local_override_dir_path()).map_err(|source| Error::CreateDirectory { + dir: USR_LOCAL_OVERRIDE_CONFIG_DIR.to_string(), + source, + }) +} diff --git a/signstar-common/src/lib.rs b/signstar-common/src/lib.rs new file mode 100644 index 00000000..0f446ff0 --- /dev/null +++ b/signstar-common/src/lib.rs @@ -0,0 +1,8 @@ +//! Common components and data for Signstar crates. + +pub mod admin_credentials; +pub mod common; +pub mod config; +pub mod nethsm; +pub mod ssh; +pub mod system_user; diff --git a/signstar-common/src/nethsm.rs b/signstar-common/src/nethsm.rs new file mode 100644 index 00000000..a746d85c --- /dev/null +++ b/signstar-common/src/nethsm.rs @@ -0,0 +1,26 @@ +//! Defaults for NetHSM backends. + +use std::net::Ipv4Addr; + +use nethsm::LogLevel; + +/// The default admin user name of newly provisioned NetHSM. +pub const USER_DEFAULT_ADMIN: &str = "admin"; + +/// The default IP address of an unprovisioned NetHSM. +pub const NETWORK_DEFAULT_IP_ADDRESS: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 1); + +/// The default netmask of an unprovisioned NetHSM. +pub const NETWORK_DEFAULT_NETMASK: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0); + +/// The default gateway of an unprovisioned NetHSM. +pub const NETWORK_DEFAULT_GATEWAY: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); + +/// The default logging IP address of an unprovisioned NetHSM. +pub const LOGGING_DEFAULT_IP_ADDRESS: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); + +/// The default logging port of an unprovisioned NetHSM. +pub const LOGGING_DEFAULT_PORT: u32 = 514; + +/// The default logging level of an unprovisioned NetHSM. +pub const LOGGING_DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Info; diff --git a/signstar-common/src/ssh.rs b/signstar-common/src/ssh.rs new file mode 100644 index 00000000..9ca2d3fe --- /dev/null +++ b/signstar-common/src/ssh.rs @@ -0,0 +1,31 @@ +//! Defaults for SSH. +//! +//! # Examples +//! +//! ``` +//! use signstar_common::ssh::{get_ssh_authorized_key_base_dir, get_sshd_config_dropin_dir}; +//! +//! // Get directory path for SSH authorized_keys files for Signstar users. +//! println!("{:?}", get_ssh_authorized_key_base_dir()); +//! +//! // Get directory path for sshd_config drop-in files. +//! println!("{:?}", get_sshd_config_dropin_dir()); +//! ``` + +use std::path::PathBuf; + +/// The base directory below which SSH authorized_keys files for users are located. +const SSH_AUTHORIZED_KEY_BASE_DIR: &str = "/etc/ssh/"; + +/// The directory below which sshd_config drop-in files are located. +const SSHD_CONFIG_DROPIN_DIR: &str = "/etc/ssh/sshd_config.d/"; + +/// Returns the directory path below which SSH authorized_keys files for Signstar users are located. +pub fn get_ssh_authorized_key_base_dir() -> PathBuf { + PathBuf::from(SSH_AUTHORIZED_KEY_BASE_DIR) +} + +/// Returns the directory path below which SSH authorized_keys files for Signstar users are located. +pub fn get_sshd_config_dropin_dir() -> PathBuf { + PathBuf::from(SSHD_CONFIG_DROPIN_DIR) +} diff --git a/signstar-common/src/system_user.rs b/signstar-common/src/system_user.rs new file mode 100644 index 00000000..b0406e10 --- /dev/null +++ b/signstar-common/src/system_user.rs @@ -0,0 +1,62 @@ +//! Defaults for system users. +//! +//! ``` +//! use signstar_common::system_user::{get_home_base_dir_path, get_relative_user_secrets_dir}; +//! +//! // Get the base directory below which Signstar system user homes are located. +//! println!("{:?}", get_home_base_dir_path()); +//! +//! // Get the relative directory below which Signstar secrets are located per system user. +//! println!("{:?}", get_relative_user_secrets_dir()); +//! ``` + +use std::path::PathBuf; + +use crate::common::get_data_home; + +/// The relative base directory below which system user homes are located. +/// +/// This directory resides relative to the data home on the system. +const HOME_BASE_DIR: &str = "home/"; + +/// The directory name below which credentials files are stored. +/// +/// The directory is evaluated relative to a user's home. +const USER_SECRETS_DIR: &str = ".local/state/signstar/secrets/"; + +/// The file extension of plaintext credential files. +const PLAINTEXT_SECRETS_EXTENSION: &str = "txt"; + +/// The file extension of systemd-creds encrypted credential files. +const SYSTEMD_CREDS_SECRETS_EXTENSION: &str = "creds"; + +/// Returns the base directory below which Signstar system user homes are located. +pub fn get_home_base_dir_path() -> PathBuf { + get_data_home().join(PathBuf::from(HOME_BASE_DIR)) +} + +/// Returns the relative directory below which Signstar secrets are located per system user. +pub fn get_relative_user_secrets_dir() -> PathBuf { + PathBuf::from(USER_SECRETS_DIR) +} + +/// Returns the path to the secrets directory for a specific system user. +pub fn get_user_secrets_dir(system_user: &str) -> PathBuf { + get_home_base_dir_path() + .join(PathBuf::from(system_user)) + .join(get_relative_user_secrets_dir()) +} + +/// Returns the path to a plaintext secrets file for a system user and backend user. +pub fn get_plaintext_secret_file(system_user: &str, backend_user: &str) -> PathBuf { + get_user_secrets_dir(system_user).join(PathBuf::from( + [backend_user, ".", PLAINTEXT_SECRETS_EXTENSION].concat(), + )) +} + +/// Returns the path to a systemd-creds encrypted secrets file for a system user and backend user. +pub fn get_systemd_creds_secret_file(system_user: &str, backend_user: &str) -> PathBuf { + get_user_secrets_dir(system_user).join(PathBuf::from( + [backend_user, ".", SYSTEMD_CREDS_SECRETS_EXTENSION].concat(), + )) +} -- GitLab From 891dcbf937a3bc6428a96153ac23426dd0688dbe Mon Sep 17 00:00:00 2001 From: David Runge <dvzrv@archlinux.org> Date: Tue, 14 Jan 2025 17:07:30 +0100 Subject: [PATCH 2/2] feat: Rely on `signstar-common` crate for default locations Signed-off-by: David Runge <dvzrv@archlinux.org> --- Cargo.lock | 1 + signstar-configure-build/Cargo.toml | 1 + signstar-configure-build/src/cli.rs | 59 +++++++++------ signstar-configure-build/src/lib.rs | 112 ++++++++++++++-------------- 4 files changed, 95 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9cdcaef..8a00f64c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3148,6 +3148,7 @@ dependencies = [ "clap", "nethsm-config", "nix", + "signstar-common", "strum 0.27.1", "sysinfo 0.33.0", "thiserror 2.0.11", diff --git a/signstar-configure-build/Cargo.toml b/signstar-configure-build/Cargo.toml index 762d138b..f3da82e0 100644 --- a/signstar-configure-build/Cargo.toml +++ b/signstar-configure-build/Cargo.toml @@ -13,6 +13,7 @@ version = "0.1.2" clap = { workspace = true, features = ["cargo", "derive", "env"] } nethsm-config.workspace = true nix = { version = "0.29.0", features = ["user"] } +signstar-common.workspace = true strum.workspace = true sysinfo = "0.33.0" thiserror.workspace = true diff --git a/signstar-configure-build/src/cli.rs b/signstar-configure-build/src/cli.rs index 4f1808bd..95340eb9 100644 --- a/signstar-configure-build/src/cli.rs +++ b/signstar-configure-build/src/cli.rs @@ -1,17 +1,17 @@ use clap::{Parser, crate_name}; +use signstar_common::{ + config::{ + get_default_config_file_path, + get_etc_override_config_file_path, + get_run_override_config_file_path, + get_usr_local_override_config_file_path, + }, + ssh::{get_ssh_authorized_key_base_dir, get_sshd_config_dropin_dir}, + system_user::get_home_base_dir_path, +}; use strum::VariantNames; -use crate::{ - ConfigPath, - DEFAULT_CONFIG_FILE, - ETC_OVERRIDE_CONFIG_FILE, - HOME_BASE_DIR, - RUN_OVERRIDE_CONFIG_FILE, - SSH_AUTHORIZED_KEY_BASE_DIR, - SSHD_DROPIN_CONFIG_DIR, - SshForceCommand, - USR_LOCAL_OVERRIDE_CONFIG_FILE, -}; +use crate::{ConfigPath, SshForceCommand}; pub const BIN_NAME: &str = crate_name!(); const SSH_FORCE_COMMAND_VARIANTS: &[&str] = SshForceCommand::VARIANTS; @@ -30,25 +30,32 @@ It creates system users and their integration based on a central configuration f By default, one of the following configuration files is used if it exists, in the following order: -- \"{USR_LOCAL_OVERRIDE_CONFIG_FILE}\" +- {:?} -- \"{RUN_OVERRIDE_CONFIG_FILE}\" +- {:?} -- \"{ETC_OVERRIDE_CONFIG_FILE}\" +- {:?} -If none of the above are found, the default location \"{DEFAULT_CONFIG_FILE}\" is used. +If none of the above are found, the default location {:?} is used. Alternatively a custom configuration file location can be specified using the \"--config\"/ \"-c\" option. System users, if they don't exist already, are created with the help of `useradd`. -The users are created without a passphrase and setup with a home below \"{HOME_BASE_DIR}\". +The users are created without a passphrase and setup with a home below {:?}. However, their home directory is not created automatically. The system user accounts are then unlocked with the help of `usermod`. For each system user a tmpfiles.d integration is provided below \"/usr/lib/tmpfiles.d\", to allow automatic creation of their home directory. -If the used configuration file associates the system user with SSH public keys, a dedicated \"authorized_keys\" file containing the SSH public keys for the user is created below \"{SSH_AUTHORIZED_KEY_BASE_DIR}\". -Additionally, an \"sshd_config\" drop-in configuration is created below \"{SSHD_DROPIN_CONFIG_DIR}\". +If the used configuration file associates the system user with SSH public keys, a dedicated \"authorized_keys\" file containing the SSH public keys for the user is created below {:?}. +Additionally, an \"sshd_config\" drop-in configuration is created below {:?}. This \"sshd_config\" drop-in configuration enforces the use of the user's \"authorized_keys\" and the use of a specific command (i.e. one of {SSH_FORCE_COMMAND_VARIANTS:?}) depending on the user's role.", - ), + get_usr_local_override_config_file_path(), + get_run_override_config_file_path(), + get_etc_override_config_file_path(), + get_default_config_file_path(), + get_home_base_dir_path(), + get_ssh_authorized_key_base_dir(), + get_sshd_config_dropin_dir(), + ) )] pub struct Cli { #[arg( @@ -61,14 +68,18 @@ If specified, the custom configuration file is used instead of the default confi If unspecified, one of the following configuration files is used if it exists, in the following order: -- \"{USR_LOCAL_OVERRIDE_CONFIG_FILE}\" +- {:?} -- \"{RUN_OVERRIDE_CONFIG_FILE}\" +- {:?} -- \"{ETC_OVERRIDE_CONFIG_FILE}\" +- {:?} -If none of the above are found, the default location \"{DEFAULT_CONFIG_FILE}\" is used. -"), +If none of the above are found, the default location {:?} is used.", + get_usr_local_override_config_file_path(), + get_run_override_config_file_path(), + get_etc_override_config_file_path(), + get_default_config_file_path(), +), long, short )] diff --git a/signstar-configure-build/src/lib.rs b/signstar-configure-build/src/lib.rs index 219fec9c..cf840248 100644 --- a/signstar-configure-build/src/lib.rs +++ b/signstar-configure-build/src/lib.rs @@ -8,18 +8,15 @@ use std::{ use nethsm_config::{HermeticParallelConfig, SystemUserId, UserMapping}; use nix::unistd::User; +use signstar_common::{ + config::get_config_file_or_default, + ssh::{get_ssh_authorized_key_base_dir, get_sshd_config_dropin_dir}, + system_user::get_home_base_dir_path, +}; use sysinfo::{Pid, System}; pub mod cli; -pub static ETC_OVERRIDE_CONFIG_FILE: &str = "/etc/signstar/config.toml"; -pub static RUN_OVERRIDE_CONFIG_FILE: &str = "/run/signstar/config.toml"; -pub static USR_LOCAL_OVERRIDE_CONFIG_FILE: &str = "/usr/local/share/signstar/config.toml"; -pub static DEFAULT_CONFIG_FILE: &str = "/usr/share/signstar/config.toml"; -pub static SSH_AUTHORIZED_KEY_BASE_DIR: &str = "/etc/ssh"; -pub static SSHD_DROPIN_CONFIG_DIR: &str = "/etc/ssh/sshd_config.d"; -pub static HOME_BASE_DIR: &str = "/var/lib/signstar/home"; - #[derive(Debug, thiserror::Error)] pub enum Error { /// A config error @@ -64,6 +61,16 @@ pub enum Error { #[error("The string {0} could not be converted to a \"sysinfo::Uid\"")] SysUidFromStr(String), + /// A `Path` value for a tmpfiles.d integration is not valid. + #[error( + "The Path value {path} for the tmpfiles.d integration for {user} is not valid:\n{reason}" + )] + TmpfilesDPath { + path: String, + user: SystemUserId, + reason: &'static str, + }, + /// Adding a user failed #[error("Adding user {user} failed:\n{source}")] UserAdd { @@ -109,12 +116,7 @@ pub enum Error { /// The configuration file path for the application. /// -/// If the path exists and is a file, one of the following configuration file locations is used: -/// - [`ETC_OVERRIDE_CONFIG_FILE`] -/// - [`RUN_OVERRIDE_CONFIG_FILE`] -/// - [`USR_LOCAL_OVERRIDE_CONFIG_FILE`] -/// -/// If none of the above is found, [`DEFAULT_CONFIG_FILE`] is used (even if it doesn't exist!). +/// The configuration file location is defined by the behavior of [`get_config_file_or_default`]. #[derive(Clone, Debug)] pub struct ConfigPath(PathBuf); @@ -133,25 +135,10 @@ impl AsRef<Path> for ConfigPath { impl Default for ConfigPath { /// Returns the default [`ConfigPath`]. /// - /// If the path exists and is a file, one of the following configuration file locations is used: - /// - [`ETC_OVERRIDE_CONFIG_FILE`] - /// - [`RUN_OVERRIDE_CONFIG_FILE`] - /// - [`USR_LOCAL_OVERRIDE_CONFIG_FILE`] - /// - /// If none of the above is found, [`DEFAULT_CONFIG_FILE`] is used (even if it doesn't exist!). + /// Uses [`get_config_file_or_default`] to find the first usable configuration file path, or the + /// default if none is found. fn default() -> Self { - for config_file in [ - ETC_OVERRIDE_CONFIG_FILE, - RUN_OVERRIDE_CONFIG_FILE, - USR_LOCAL_OVERRIDE_CONFIG_FILE, - ] { - let config = PathBuf::from(config_file); - if config.is_file() { - return Self(config); - } - } - - Self(PathBuf::from(DEFAULT_CONFIG_FILE)) + Self(get_config_file_or_default()) } } @@ -308,8 +295,8 @@ pub fn ensure_root() -> Result<(), Error> { /// /// Works on the [`UserMapping`]s of the provided `config` and creates system users for all /// mappings, that define system users, if they don't exist on the system yet. -/// System users are created unlocked, without passphrase, with their homes located in -/// [`HOME_BASE_DIR`]. +/// System users are created unlocked, without passphrase, with their homes located in the directory +/// returned by [`get_home_base_dir_path`]. /// The home directories of users are not created upon user creation, but instead a [tmpfiles.d] /// configuration is added for them to automate their creation upon system boot. /// @@ -354,17 +341,17 @@ pub fn create_system_users(config: &HermeticParallelConfig) -> Result<(), Error> continue; } + let home_base_dir = get_home_base_dir_path(); + // add user, but do not create its home print!("Creating user \"{user}\"..."); let user_add = Command::new("useradd") - .args([ - "--base-dir", - HOME_BASE_DIR, - "--user-group", - "--shell", - "/usr/bin/bash", - user.as_ref(), - ]) + .arg("--base-dir") + .arg(home_base_dir.as_path()) + .arg("--user-group") + .arg("--shell") + .arg("/usr/bin/bash") + .arg(user.as_ref()) .output() .map_err(|error| Error::UserAdd { user: user.clone(), @@ -407,8 +394,25 @@ pub fn create_system_users(config: &HermeticParallelConfig) -> Result<(), Error> user: user.clone(), source, })?; + + // ensure that the `Path` component in the tmpfiles.d file + // - has whitespace replaced with a c-style escape + // - does not contain specifiers + let home_dir = { + let home_dir = + format!("{}/{user}", home_base_dir.to_string_lossy()).replace(" ", "\\x20"); + if home_dir.contains("%") { + return Err(Error::TmpfilesDPath { + path: home_dir.clone(), + user: user.clone(), + reason: "Specifiers (%) are not supported at this point.", + }); + } + home_dir + }; + buffer - .write_all(format!("d {HOME_BASE_DIR}/{user} 700 {user} {user}\n").as_bytes()) + .write_all(format!("d {home_dir} 700 {user} {user}\n",).as_bytes()) .map_err(|source| Error::WriteTmpfilesD { user: user.clone(), source, @@ -422,14 +426,14 @@ pub fn create_system_users(config: &HermeticParallelConfig) -> Result<(), Error> // add SSH authorized keys file user in system-wide location print!("Adding SSH authorized_keys file for user \"{user}\"..."); { - let filename = format!( - "{SSH_AUTHORIZED_KEY_BASE_DIR}/signstar-user-{user}.authorized_keys" - ); - let mut buffer = - File::create(filename).map_err(|source| Error::WriteAuthorizedKeys { - user: user.clone(), - source, - })?; + let mut buffer = File::create( + get_ssh_authorized_key_base_dir() + .join(format!("signstar-user-{user}.authorized_keys")), + ) + .map_err(|source| Error::WriteAuthorizedKeys { + user: user.clone(), + source, + })?; buffer .write_all( (authorized_keys @@ -450,9 +454,9 @@ pub fn create_system_users(config: &HermeticParallelConfig) -> Result<(), Error> // add sshd_config drop-in configuration for user print!("Adding sshd_config drop-in configuration for user \"{user}\"..."); { - let mut buffer = File::create(format!( - "{SSHD_DROPIN_CONFIG_DIR}/10-signstar-user-{user}.conf" - )) + let mut buffer = File::create( + get_sshd_config_dropin_dir().join(format!("10-signstar-user-{user}.conf")), + ) .map_err(|source| Error::WriteSshdConfig { user: user.clone(), source, -- GitLab