use std::{
cell::RefCell,
collections::{HashMap, HashSet, hash_map::Entry},
error::Error as StdError,
fmt::Display,
path::{Path, PathBuf},
str::FromStr,
};
use nethsm::{
Connection,
ConnectionSecurity,
Credentials,
KeyId,
NamespaceId,
NetHsm,
Passphrase,
Url,
UserId,
UserRole,
};
use serde::{Deserialize, Serialize};
use crate::{
ConfigCredentials,
ExtendedUserMapping,
PassphrasePrompt,
SystemUserId,
UserMapping,
UserPrompt,
};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Config file issue: {0}")]
ConfigFileLocation(#[source] confy::ConfyError),
#[error("Config loading issue: {source}\n{description}")]
Load {
source: confy::ConfyError,
description: String,
},
#[error("Config storing issue: {0}")]
Store(#[source] confy::ConfyError),
#[error("Credentials exist already: {0}")]
CredentialsExist(UserId),
#[error("Credentials do not exist: {0}")]
CredentialsMissing(UserId),
#[error("None of the provided users ({names:?}) map to one of the provided roles ({roles:?})")]
MatchingCredentialsMissing {
names: Vec<UserId>,
roles: Vec<UserRole>,
},
#[error("No user matching one of the requested roles ({0:?}) exists")]
NoMatchingCredentials(Vec<UserRole>),
#[error("No mapping found where a system user matches the name {name}")]
NoMatchingMappingForSystemUser { name: String },
#[error(
"Shamir's Secret Sharing not used for administrative secret handling, but the following users are setup to handle shares: {share_users:?}"
)]
NoSssButShareUsers { share_users: Vec<SystemUserId> },
#[error("Device exist already: {0}")]
DeviceExists(String),
#[error("Device does not exist: {0}")]
DeviceMissing(String),
#[error("The NetHsm user ID {nethsm_user_id} is used more than once!")]
DuplicateNetHsmUserId { nethsm_user_id: UserId },
#[error("The authorized SSH key {ssh_authorized_key} is used more than once!")]
DuplicateSshAuthorizedKey { ssh_authorized_key: String },
#[error("The key ID {key_id} is used more than once!")]
DuplicateKeyId { key_id: KeyId },
#[error("The key ID {key_id} is used more than once in namespace {namespace}!")]
DuplicateKeyIdInNamespace {
key_id: KeyId,
namespace: NamespaceId,
},
#[error("The system user ID {system_user_id} is used more than once!")]
DuplicateSystemUserId { system_user_id: SystemUserId },
#[error("The tag {tag} is used more than once!")]
DuplicateTag { tag: String },
#[error("The tag {tag} is used more than once in namespace {namespace}!")]
DuplicateTagInNamespace { tag: String, namespace: NamespaceId },
#[error("No system-wide user in the Administrator role exists.")]
MissingAdministrator,
#[error(
"No user in the Administrator role exist for the namespaces {}",
namespaces.iter().map(|id| id.to_string()).collect::<Vec<String>>().join(", ")
)]
MissingNamespaceAdministrators { namespaces: Vec<NamespaceId> },
#[error("No system user for downloading shares of a shared secret exists.")]
MissingShareDownloadUser,
#[error("No system user for uploading shares of a shared secret exists.")]
MissingShareUploadUser,
#[error("There is more than one device")]
MoreThanOneDevice,
#[error("There is no device")]
NoDevice,
#[error("The configuration can not be used interactively")]
NonInteractive,
#[error("NetHsm connection can not be created: {0}")]
NetHsm(#[from] nethsm::Error),
#[error("A prompt issue")]
Prompt(#[from] crate::prompt::Error),
#[error("User data invalid: {0}")]
User(#[from] nethsm::UserError),
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub enum ConfigInteractivity {
Interactive,
#[default]
NonInteractive,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConfigName(String);
impl Default for ConfigName {
fn default() -> Self {
Self("config".to_string())
}
}
impl Display for ConfigName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl FromStr for ConfigName {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string()))
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConfigSettings {
config_name: ConfigName,
app_name: String,
interactivity: ConfigInteractivity,
}
impl ConfigSettings {
pub fn new(
app_name: String,
interactivity: ConfigInteractivity,
config_name: Option<ConfigName>,
) -> Self {
Self {
app_name,
interactivity,
config_name: config_name.unwrap_or_default(),
}
}
pub fn config_name(&self) -> ConfigName {
self.config_name.to_owned()
}
pub fn app_name(&self) -> String {
self.app_name.clone()
}
pub fn interactivity(&self) -> ConfigInteractivity {
self.interactivity
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DeviceConfig {
connection: RefCell<Connection>,
credentials: RefCell<HashSet<ConfigCredentials>>,
#[serde(skip)]
interactivity: ConfigInteractivity,
}
impl DeviceConfig {
pub fn new(
connection: Connection,
credentials: Vec<ConfigCredentials>,
interactivity: ConfigInteractivity,
) -> Result<DeviceConfig, Error> {
let device_config = DeviceConfig {
connection: RefCell::new(connection),
credentials: RefCell::new(HashSet::new()),
interactivity,
};
if !credentials.is_empty() {
for creds in credentials.into_iter() {
device_config.add_credentials(creds)?
}
}
Ok(device_config)
}
pub fn set_config_interactivity(&mut self, config_type: ConfigInteractivity) {
self.interactivity = config_type;
}
pub fn add_credentials(&self, credentials: ConfigCredentials) -> Result<(), Error> {
if !self
.credentials
.borrow()
.iter()
.any(|creds| creds.get_name() == credentials.get_name())
{
self.credentials.borrow_mut().insert(credentials);
Ok(())
} else {
Err(Error::CredentialsExist(credentials.get_name()))
}
}
pub fn get_credentials(&self, name: &UserId) -> Result<ConfigCredentials, Error> {
if let Some(creds) = self
.credentials
.borrow()
.iter()
.find(|creds| &creds.get_name() == name)
{
Ok(creds.clone())
} else {
Err(Error::CredentialsMissing(name.to_owned()))
}
}
pub fn delete_credentials(&self, name: &UserId) -> Result<(), Error> {
let before = self.credentials.borrow().len();
self.credentials
.borrow_mut()
.retain(|creds| &creds.get_name() != name);
let after = self.credentials.borrow().len();
if before == after {
Err(Error::CredentialsMissing(name.to_owned()))
} else {
Ok(())
}
}
pub fn get_matching_credentials(
&self,
roles: &[UserRole],
names: &[UserId],
) -> Result<ConfigCredentials, Error> {
if names.is_empty() {
let creds = self
.credentials
.borrow()
.iter()
.filter_map(|creds| {
if roles.contains(&creds.get_role()) {
Some(creds.clone())
} else {
None
}
})
.collect::<Vec<ConfigCredentials>>();
return creds
.first()
.ok_or_else(|| Error::NoMatchingCredentials(roles.to_vec()))
.cloned();
}
for name in names {
if let Ok(creds) = &self.get_credentials(name) {
if roles.contains(&creds.get_role()) {
return Ok(creds.clone());
}
} else {
return Err(Error::CredentialsMissing(name.to_owned()));
}
}
Err(Error::MatchingCredentialsMissing {
names: names.to_vec(),
roles: roles.to_vec(),
})
}
pub fn nethsm_with_matching_creds(
&self,
roles: &[UserRole],
names: &[UserId],
passphrases: &[Passphrase],
) -> Result<NetHsm, Error> {
let nethsm: NetHsm = self.try_into()?;
if !roles.is_empty() {
let creds = if let Ok(creds) = self.get_matching_credentials(roles, names) {
creds
} else {
if self.interactivity == ConfigInteractivity::NonInteractive {
return Err(Error::NonInteractive);
}
let role = roles.first().expect("We have at least one user role");
ConfigCredentials::new(
role.to_owned(),
UserPrompt::new(role.to_owned()).prompt()?,
None,
)
};
let credentials = if !creds.has_passphrase() {
let name_index = names.iter().position(|name| name == &creds.get_name());
if let Some(name_index) = name_index {
if let Some(passphrase) = passphrases.get(name_index) {
Credentials::new(creds.get_name(), Some(passphrase.clone()))
} else {
if self.interactivity == ConfigInteractivity::NonInteractive {
return Err(Error::NonInteractive);
}
Credentials::new(
creds.get_name(),
Some(
PassphrasePrompt::User {
user_id: Some(creds.get_name()),
real_name: None,
}
.prompt()?,
),
)
}
} else {
if self.interactivity == ConfigInteractivity::NonInteractive {
return Err(Error::NonInteractive);
}
Credentials::new(
creds.get_name(),
Some(
PassphrasePrompt::User {
user_id: Some(creds.get_name()),
real_name: None,
}
.prompt()?,
),
)
}
} else {
creds.into()
};
let user_id = credentials.user_id.clone();
nethsm.add_credentials(credentials);
nethsm.use_credentials(&user_id)?;
}
Ok(nethsm)
}
}
impl TryFrom<DeviceConfig> for NetHsm {
type Error = Error;
fn try_from(value: DeviceConfig) -> Result<Self, Error> {
let nethsm = NetHsm::new(value.connection.borrow().clone(), None, None, None)?;
for creds in value.credentials.borrow().clone().into_iter() {
nethsm.add_credentials(creds.into())
}
Ok(nethsm)
}
}
impl TryFrom<&DeviceConfig> for NetHsm {
type Error = Error;
fn try_from(value: &DeviceConfig) -> Result<Self, Error> {
let nethsm = NetHsm::new(value.connection.borrow().clone(), None, None, None)?;
for creds in value.credentials.borrow().clone().into_iter() {
nethsm.add_credentials(creds.into())
}
Ok(nethsm)
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Config {
devices: RefCell<HashMap<String, DeviceConfig>>,
#[serde(skip)]
config_settings: ConfigSettings,
}
impl Config {
pub fn new(config_settings: ConfigSettings, path: Option<&Path>) -> Result<Self, Error> {
let mut config: Config = if let Some(path) = path {
confy::load_path(path).map_err(|error| Error::Load {
description: if let Some(error) = error.source() {
error.to_string()
} else {
"".to_string()
},
source: error,
})?
} else {
confy::load(
&config_settings.app_name,
Some(config_settings.config_name.0.as_str()),
)
.map_err(|error| Error::Load {
description: if let Some(error) = error.source() {
error.to_string()
} else {
"".to_string()
},
source: error,
})?
};
for (_label, device) in config.devices.borrow_mut().iter_mut() {
device.set_config_interactivity(config_settings.interactivity);
}
config.set_config_settings(config_settings);
Ok(config)
}
fn set_config_settings(&mut self, config_settings: ConfigSettings) {
self.config_settings = config_settings
}
pub fn add_device(
&self,
label: String,
url: Url,
tls_security: ConnectionSecurity,
) -> Result<(), Error> {
if let Entry::Vacant(entry) = self.devices.borrow_mut().entry(label.clone()) {
entry.insert(DeviceConfig::new(
Connection::new(url, tls_security),
vec![],
self.config_settings.interactivity,
)?);
Ok(())
} else {
Err(Error::DeviceExists(label))
}
}
pub fn delete_device(&self, label: &str) -> Result<(), Error> {
if self.devices.borrow_mut().remove(label).is_some() {
Ok(())
} else {
Err(Error::DeviceMissing(label.to_string()))
}
}
pub fn get_device(&self, label: Option<&str>) -> Result<DeviceConfig, Error> {
if let Some(label) = label {
if let Some(device_config) = self.devices.borrow().get(label) {
Ok(device_config.clone())
} else {
Err(Error::DeviceMissing(label.to_string()))
}
} else {
match self.devices.borrow().len() {
0 => Err(Error::NoDevice),
1 => Ok(self
.devices
.borrow()
.values()
.next()
.expect("there should be one")
.to_owned()),
_ => Err(Error::MoreThanOneDevice),
}
}
}
pub fn get_single_device_label(&self) -> Result<String, Error> {
if self.devices.borrow().keys().len() == 1 {
self.devices
.borrow()
.keys()
.next()
.map(|label| label.to_string())
.ok_or(Error::NoDevice)
} else {
Err(Error::MoreThanOneDevice)
}
}
pub fn add_credentials(
&self,
label: String,
credentials: ConfigCredentials,
) -> Result<(), Error> {
if let Some(device) = self.devices.borrow_mut().get_mut(&label) {
device.add_credentials(credentials)?
} else {
return Err(Error::DeviceMissing(label));
}
Ok(())
}
pub fn delete_credentials(&self, label: &str, name: &UserId) -> Result<(), Error> {
if let Some(device) = self.devices.borrow_mut().get_mut(label) {
device.delete_credentials(name)?
} else {
return Err(Error::DeviceMissing(label.to_string()));
}
Ok(())
}
pub fn get_config_settings(&self) -> ConfigSettings {
self.config_settings.clone()
}
pub fn get_default_config_file_path(&self) -> Result<PathBuf, Error> {
confy::get_configuration_file_path(
&self.config_settings.app_name,
Some(self.config_settings.config_name().0.as_str()),
)
.map_err(Error::ConfigFileLocation)
}
pub fn store(&self, path: Option<&Path>) -> Result<(), Error> {
if let Some(path) = path {
confy::store_path(path, self).map_err(Error::Store)
} else {
confy::store(&self.config_settings.app_name, "config", self).map_err(Error::Store)
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum AdministrativeSecretHandling {
Plaintext,
SystemdCreds,
#[default]
ShamirsSecretSharing,
}
#[derive(
Clone,
Copy,
Debug,
Default,
Deserialize,
strum::Display,
strum::EnumString,
Eq,
PartialEq,
Serialize,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum NonAdministrativeSecretHandling {
Plaintext,
#[default]
SystemdCreds,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct HermeticParallelConfig {
iteration: u32,
admin_secret_handling: AdministrativeSecretHandling,
non_admin_secret_handling: NonAdministrativeSecretHandling,
connections: HashSet<Connection>,
users: HashSet<UserMapping>,
#[serde(skip)]
settings: ConfigSettings,
}
impl HermeticParallelConfig {
pub fn new_from_file(
config_settings: ConfigSettings,
path: Option<&Path>,
) -> Result<Self, Error> {
let mut config: HermeticParallelConfig = if let Some(path) = path {
confy::load_path(path).map_err(|error| Error::Load {
description: if let Some(error) = error.source() {
error.to_string()
} else {
"".to_string()
},
source: error,
})?
} else {
confy::load(
&config_settings.app_name,
Some(config_settings.config_name.0.as_str()),
)
.map_err(|error| Error::Load {
description: if let Some(error) = error.source() {
error.to_string()
} else {
"".to_string()
},
source: error,
})?
};
config.settings = config_settings;
config.validate()?;
Ok(config)
}
pub fn new(
config_settings: ConfigSettings,
iteration: u32,
admin_secret_handling: AdministrativeSecretHandling,
non_admin_secret_handling: NonAdministrativeSecretHandling,
connections: HashSet<Connection>,
users: HashSet<UserMapping>,
) -> Result<Self, Error> {
let config = Self {
iteration,
admin_secret_handling,
non_admin_secret_handling,
connections,
users,
settings: config_settings,
};
config.validate()?;
Ok(config)
}
pub fn store(&self, path: Option<&Path>) -> Result<(), Error> {
if let Some(path) = path {
confy::store_path(path, self).map_err(Error::Store)
} else {
confy::store(&self.settings.app_name, "config", self).map_err(Error::Store)
}
}
pub fn iter_connections(&self) -> impl Iterator<Item = &Connection> {
self.connections.iter()
}
pub fn iter_user_mappings(&self) -> impl Iterator<Item = &UserMapping> {
self.users.iter()
}
pub fn get_iteration(&self) -> u32 {
self.iteration
}
pub fn get_administrative_secret_handling(&self) -> AdministrativeSecretHandling {
self.admin_secret_handling
}
pub fn get_non_administrative_secret_handling(&self) -> NonAdministrativeSecretHandling {
self.non_admin_secret_handling
}
pub fn get_extended_mapping_for_user(&self, name: &str) -> Result<ExtendedUserMapping, Error> {
for user_mapping in self.users.iter() {
if user_mapping
.get_system_user()
.is_some_and(|system_user| system_user.as_ref() == name)
{
return Ok(ExtendedUserMapping::new(
self.admin_secret_handling,
self.non_admin_secret_handling,
self.connections.clone(),
user_mapping.clone(),
));
}
}
Err(Error::NoMatchingMappingForSystemUser {
name: name.to_string(),
})
}
fn validate(&self) -> Result<(), Error> {
{
let mut system_users = HashSet::new();
for system_user_id in self
.users
.iter()
.filter_map(|mapping| mapping.get_system_user())
{
if !system_users.insert(system_user_id.clone()) {
return Err(Error::DuplicateSystemUserId {
system_user_id: system_user_id.clone(),
});
}
}
}
{
let mut nethsm_users = HashSet::new();
for nethsm_user_id in self
.users
.iter()
.flat_map(|mapping| mapping.get_nethsm_users())
{
if !nethsm_users.insert(nethsm_user_id.clone()) {
return Err(Error::DuplicateNetHsmUserId {
nethsm_user_id: nethsm_user_id.clone(),
});
}
}
}
if self
.users
.iter()
.filter_map(|mapping| {
if let UserMapping::NetHsmOnlyAdmin(user_id) = mapping {
if !user_id.is_namespaced() {
Some(user_id)
} else {
None
}
} else {
None
}
})
.next()
.is_none()
{
return Err(Error::MissingAdministrator);
}
{
let namespaces_users = self
.users
.iter()
.filter(|mapping| !matches!(mapping, UserMapping::NetHsmOnlyAdmin(_)))
.flat_map(|mapping| mapping.get_namespaces())
.collect::<HashSet<NamespaceId>>();
let namespaces_admins = self
.users
.iter()
.filter(|mapping| matches!(mapping, UserMapping::NetHsmOnlyAdmin(_)))
.flat_map(|mapping| mapping.get_namespaces())
.collect::<HashSet<NamespaceId>>();
let namespaces = namespaces_users
.difference(&namespaces_admins)
.cloned()
.collect::<Vec<NamespaceId>>();
if !namespaces.is_empty() {
return Err(Error::MissingNamespaceAdministrators { namespaces });
}
}
if self.admin_secret_handling == AdministrativeSecretHandling::ShamirsSecretSharing {
if !self
.users
.iter()
.any(|mapping| matches!(mapping, UserMapping::SystemOnlyShareDownload { .. }))
{
return Err(Error::MissingShareDownloadUser);
}
if !self
.users
.iter()
.any(|mapping| matches!(mapping, UserMapping::SystemOnlyShareUpload { .. }))
{
return Err(Error::MissingShareUploadUser);
}
} else {
let share_users: Vec<SystemUserId> = self
.users
.iter()
.filter_map(|mapping| match mapping {
UserMapping::SystemOnlyShareUpload {
system_user,
ssh_authorized_keys: _,
}
| UserMapping::SystemOnlyShareDownload {
system_user,
ssh_authorized_keys: _,
} => Some(system_user.clone()),
_ => None,
})
.collect();
if !share_users.is_empty() {
return Err(Error::NoSssButShareUsers { share_users });
}
}
{
let mut ssh_authorized_keys = HashSet::new();
for ssh_authorized_key in self
.users
.iter()
.filter(|mapping| {
!matches!(
mapping,
UserMapping::SystemOnlyShareDownload {
system_user: _,
ssh_authorized_keys: _,
}
)
})
.flat_map(|mapping| mapping.get_ssh_authorized_keys())
.filter_map(|authorized_key| {
ssh_key::authorized_keys::Entry::try_from(&authorized_key).ok()
})
{
if !ssh_authorized_keys.insert(ssh_authorized_key.public_key().clone()) {
return Err(Error::DuplicateSshAuthorizedKey {
ssh_authorized_key: ssh_authorized_key.public_key().to_string(),
});
}
}
}
{
let mut ssh_authorized_keys = HashSet::new();
for ssh_authorized_key in self
.users
.iter()
.filter(|mapping| {
!matches!(
mapping,
UserMapping::SystemOnlyShareUpload {
system_user: _,
ssh_authorized_keys: _,
}
)
})
.flat_map(|mapping| mapping.get_ssh_authorized_keys())
.filter_map(|authorized_key| {
ssh_key::authorized_keys::Entry::try_from(&authorized_key).ok()
})
{
if !ssh_authorized_keys.insert(ssh_authorized_key.public_key().clone()) {
return Err(Error::DuplicateSshAuthorizedKey {
ssh_authorized_key: ssh_authorized_key.public_key().to_string(),
});
}
}
}
{
let mut set = HashSet::new();
for key_id in self
.users
.iter()
.flat_map(|mapping| mapping.get_key_ids(None))
{
if !set.insert(key_id.clone()) {
return Err(Error::DuplicateKeyId { key_id });
}
}
for namespace in self
.users
.iter()
.flat_map(|mapping| mapping.get_namespaces())
{
let mut set = HashSet::new();
for key_id in self
.users
.iter()
.flat_map(|mapping| mapping.get_key_ids(Some(&namespace)))
{
if !set.insert(key_id.clone()) {
return Err(Error::DuplicateKeyIdInNamespace { key_id, namespace });
}
}
}
}
{
let mut set = HashSet::new();
for tag in self.users.iter().flat_map(|mapping| mapping.get_tags(None)) {
if !set.insert(tag) {
return Err(Error::DuplicateTag {
tag: tag.to_string(),
});
}
}
for namespace in self
.users
.iter()
.flat_map(|mapping| mapping.get_namespaces())
{
let mut set = HashSet::new();
for tag in self
.users
.iter()
.flat_map(|mapping| mapping.get_tags(Some(&namespace)))
{
if !set.insert(tag) {
return Err(Error::DuplicateTagInNamespace {
tag: tag.to_string(),
namespace,
});
}
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use core::panic;
use std::path::PathBuf;
use rstest::rstest;
use testdir::testdir;
use testresult::TestResult;
use super::*;
#[rstest]
fn create_and_store_empty_config() -> TestResult {
let config_file: PathBuf = testdir!().join("empty_config.toml");
let config = Config::new(
ConfigSettings::new("test".to_string(), ConfigInteractivity::Interactive, None),
Some(&config_file),
)?;
println!("{:#?}", config);
config.store(Some(&config_file))?;
println!("config file:\n{}", std::fs::read_to_string(config_file)?);
Ok(())
}
#[rstest]
fn roundtrip_config(
#[files("basic-config*.toml")]
#[base_dir = "tests/fixtures/roundtrip-config/"]
config_file: PathBuf,
) -> TestResult {
let output_config_file: PathBuf = testdir!().join(
config_file
.file_name()
.expect("the input config file should have a file name"),
);
let config = Config::new(
ConfigSettings::new("test".to_string(), ConfigInteractivity::Interactive, None),
Some(&config_file),
)?;
config.store(Some(&output_config_file))?;
assert_eq!(
std::fs::read_to_string(&output_config_file)?,
std::fs::read_to_string(&config_file)?
);
Ok(())
}
#[rstest]
fn basic_parallel_config_new_from_file(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/working/"]
config_file: PathBuf,
) -> TestResult {
HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)?;
Ok(())
}
#[rstest]
fn basic_parallel_config_duplicate_system_user(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-system-user/"]
config_file: PathBuf,
) -> TestResult {
println!("{config_file:?}");
match HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Err(Error::DuplicateSystemUserId { .. }) => Ok(()),
Ok(_) => panic!("Did not trigger any Error!"),
Err(error) => panic!("Did not trigger the correct Error: {:?}!", error),
}
}
#[rstest]
fn basic_parallel_config_duplicate_nethsm_user(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-nethsm-user/"]
config_file: PathBuf,
) -> TestResult {
if let Err(Error::DuplicateNetHsmUserId { .. }) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_missing_administrator(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/missing-administrator/"]
config_file: PathBuf,
) -> TestResult {
if let Err(Error::MissingAdministrator) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_missing_namespace_administrators(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/missing-namespace-administrator/"]
config_file: PathBuf,
) -> TestResult {
if let Err(Error::MissingNamespaceAdministrators { .. }) =
HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)
{
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_duplicate_authorized_keys_share_uploader(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-authorized-keys-share-uploader/"]
config_file: PathBuf,
) -> TestResult {
println!("Using configuration {:?}", config_file);
let config_file_string = config_file
.clone()
.into_os_string()
.into_string()
.map_err(|_x| format!("Can't convert {:?}", config_file))?;
if config_file_string.ends_with("admin-plaintext.toml")
|| config_file_string.ends_with("admin-systemd-creds.toml")
{
let _config = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)?;
Ok(())
} else if let Err(Error::DuplicateSshAuthorizedKey { .. }) =
HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)
{
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_duplicate_authorized_keys_share_downloader(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-authorized-keys-share-downloader/"]
config_file: PathBuf,
) -> TestResult {
println!("Using configuration {:?}", config_file);
let config_file_string = config_file
.clone()
.into_os_string()
.into_string()
.map_err(|_x| format!("Can't convert {:?}", config_file))?;
if config_file_string.ends_with("admin-plaintext.toml")
|| config_file_string.ends_with("admin-systemd-creds.toml")
{
let _config = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)?;
Ok(())
} else if let Err(Error::DuplicateSshAuthorizedKey { .. }) =
HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)
{
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_duplicate_authorized_keys_users(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-authorized-keys-users/"]
config_file: PathBuf,
) -> TestResult {
if let Err(Error::DuplicateSshAuthorizedKey { .. }) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_missing_share_download_user(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/missing-share-download-user/"]
config_file: PathBuf,
) -> TestResult {
println!("Using configuration {:?}", config_file);
let config_file_string = config_file
.clone()
.into_os_string()
.into_string()
.map_err(|_x| format!("Can't convert {:?}", config_file))?;
if config_file_string.ends_with("admin-plaintext.toml")
|| config_file_string.ends_with("admin-systemd-creds.toml")
{
let _config = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)?;
Ok(())
} else if let Err(Error::MissingShareDownloadUser) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_missing_share_upload_user(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/missing-share-upload-user/"]
config_file: PathBuf,
) -> TestResult {
println!("Using configuration {:?}", config_file);
let config_file_string = config_file
.clone()
.into_os_string()
.into_string()
.map_err(|_x| format!("Can't convert {:?}", config_file))?;
if config_file_string.ends_with("admin-plaintext.toml")
|| config_file_string.ends_with("admin-systemd-creds.toml")
{
let _config = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)?;
Ok(())
} else if let Err(Error::MissingShareUploadUser) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_no_sss_but_shares(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/no-sss-but-shares/"]
config_file: PathBuf,
) -> TestResult {
println!("Using configuration {:?}", config_file);
let config_file_string = config_file
.clone()
.into_os_string()
.into_string()
.map_err(|_x| format!("Can't convert {:?}", config_file))?;
if config_file_string.ends_with("admin-shamirs-secret-sharing.toml") {
let _config = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
)?;
Ok(())
} else if let Err(Error::NoSssButShareUsers { .. }) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_duplicate_key_id(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-key-id/"]
config_file: PathBuf,
) -> TestResult {
if let Err(Error::DuplicateKeyId { .. }) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_duplicate_key_id_in_namespace(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-key-id-in-namespace/"]
config_file: PathBuf,
) -> TestResult {
if let Err(Error::DuplicateKeyIdInNamespace { .. }) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_duplicate_tag(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-tag/"]
config_file: PathBuf,
) -> TestResult {
if let Err(Error::DuplicateTag { .. }) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
#[rstest]
fn basic_parallel_config_duplicate_tag_in_namespace(
#[files("basic-parallel-config-admin-*.toml")]
#[base_dir = "tests/fixtures/duplicate-tag-in-namespace/"]
config_file: PathBuf,
) -> TestResult {
if let Err(Error::DuplicateTagInNamespace { .. }) = HermeticParallelConfig::new_from_file(
ConfigSettings::new(
"test".to_string(),
ConfigInteractivity::NonInteractive,
None,
),
Some(&config_file),
) {
Ok(())
} else {
panic!("Did not trigger the correct Error!")
}
}
}