use std::{collections::HashSet, fmt::Display, str::FromStr};
use nethsm::{Credentials, Passphrase, UserId, UserRole};
use serde::{Deserialize, Serialize};
use ssh_key::{PublicKey, authorized_keys::Entry};
use zeroize::Zeroize;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("The SSH authorized key is used multiple times: {ssh_authorized_key}")]
DuplicateAuthorizedKeys {
ssh_authorized_key: AuthorizedKeyEntry,
},
#[error("Invalid system user name: {0}")]
InvalidSystemUserName(String),
#[error("The SSH authorized key is not valid: {entry}")]
InvalidAuthorizedKeyEntry { entry: String },
#[error("No SSH authorized key provided!")]
NoAuthorizedKeys,
#[error("SSH key error: {0}")]
SshKey(#[from] ssh_key::Error),
#[error("The system-wide User ID has a namespace: {0}")]
SystemWideUserIdWithNamespace(UserId),
#[error("NetHSM user error: {0}")]
NetHsmUser(#[from] nethsm::UserError),
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Zeroize)]
pub struct ConfigCredentials {
#[zeroize(skip)]
role: UserRole,
#[zeroize(skip)]
name: UserId,
passphrase: Option<String>,
}
impl ConfigCredentials {
pub fn new(role: UserRole, name: UserId, passphrase: Option<String>) -> Self {
Self {
role,
name,
passphrase,
}
}
pub fn get_name(&self) -> UserId {
self.name.clone()
}
pub fn get_role(&self) -> UserRole {
self.role
}
pub fn get_passphrase(&self) -> Option<&str> {
self.passphrase.as_deref()
}
pub fn set_passphrase(&mut self, passphrase: String) {
self.passphrase = Some(passphrase)
}
pub fn has_passphrase(&self) -> bool {
self.passphrase.is_some()
}
}
impl From<ConfigCredentials> for Credentials {
fn from(value: ConfigCredentials) -> Self {
Self::new(value.name, value.passphrase.map(Passphrase::new))
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Zeroize)]
#[serde(into = "String", try_from = "String")]
pub struct SystemUserId(String);
impl SystemUserId {
pub fn new(user: String) -> Result<Self, Error> {
if user.is_empty()
|| !(user
.chars()
.all(|char| char.is_alphanumeric() || char == '_' || char == '-'))
{
return Err(Error::InvalidSystemUserName(user));
}
Ok(Self(user))
}
}
impl AsRef<str> for SystemUserId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for SystemUserId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<SystemUserId> for String {
fn from(value: SystemUserId) -> Self {
value.to_string()
}
}
impl FromStr for SystemUserId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s.to_string())
}
}
impl TryFrom<String> for SystemUserId {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Zeroize)]
#[serde(into = "String", try_from = "String")]
pub struct AuthorizedKeyEntry(String);
impl AuthorizedKeyEntry {
pub fn new(entry: String) -> Result<Self, Error> {
if Entry::from_str(&entry).is_err() {
return Err(Error::InvalidAuthorizedKeyEntry { entry });
}
Ok(Self(entry))
}
}
impl AsRef<str> for AuthorizedKeyEntry {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for AuthorizedKeyEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<AuthorizedKeyEntry> for String {
fn from(value: AuthorizedKeyEntry) -> Self {
value.to_string()
}
}
impl FromStr for AuthorizedKeyEntry {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s.to_string())
}
}
impl TryFrom<&AuthorizedKeyEntry> for Entry {
type Error = Error;
fn try_from(value: &AuthorizedKeyEntry) -> Result<Self, Error> {
Entry::from_str(&value.0).map_err(Error::SshKey)
}
}
impl TryFrom<String> for AuthorizedKeyEntry {
type Error = Error;
fn try_from(value: String) -> Result<Self, Error> {
Self::new(value)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(into = "Vec<String>", try_from = "Vec<String>")]
pub struct AuthorizedKeyEntryList(Vec<AuthorizedKeyEntry>);
impl AuthorizedKeyEntryList {
pub fn new(ssh_authorized_keys: Vec<AuthorizedKeyEntry>) -> Result<Self, Error> {
if ssh_authorized_keys.is_empty() {
return Err(Error::NoAuthorizedKeys);
}
let mut set = HashSet::new();
for (ssh_authorized_key, pub_key) in ssh_authorized_keys
.iter()
.filter_map(|ssh_authorized_key| {
if let Ok(entry) = Entry::try_from(ssh_authorized_key) {
Some((ssh_authorized_key.clone(), entry.public_key().clone()))
} else {
None
}
})
.collect::<Vec<(AuthorizedKeyEntry, PublicKey)>>()
{
if !set.insert(pub_key) {
return Err(Error::DuplicateAuthorizedKeys { ssh_authorized_key });
}
}
Ok(Self(ssh_authorized_keys))
}
}
impl AsRef<[AuthorizedKeyEntry]> for AuthorizedKeyEntryList {
fn as_ref(&self) -> &[AuthorizedKeyEntry] {
&self.0
}
}
impl From<AuthorizedKeyEntryList> for Vec<String> {
fn from(value: AuthorizedKeyEntryList) -> Self {
value
.0
.iter()
.map(|authorized_key| authorized_key.to_string())
.collect()
}
}
impl From<&AuthorizedKeyEntryList> for Vec<AuthorizedKeyEntry> {
fn from(value: &AuthorizedKeyEntryList) -> Self {
value.0.to_vec()
}
}
impl TryFrom<Vec<String>> for AuthorizedKeyEntryList {
type Error = Error;
fn try_from(value: Vec<String>) -> Result<Self, Self::Error> {
let authorized_keys = {
let mut authorized_keys: Vec<AuthorizedKeyEntry> = vec![];
for authorized_key in value {
authorized_keys.push(AuthorizedKeyEntry::new(authorized_key)?)
}
authorized_keys
};
Self::new(authorized_keys)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(into = "String", try_from = "String")]
pub struct SystemWideUserId(UserId);
impl SystemWideUserId {
pub fn new(user_id: String) -> Result<Self, Error> {
let user_id = UserId::new(user_id)?;
if user_id.is_namespaced() {
return Err(Error::SystemWideUserIdWithNamespace(user_id));
}
Ok(Self(user_id))
}
}
impl Display for SystemWideUserId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl FromStr for SystemWideUserId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s.to_string())
}
}
impl From<SystemWideUserId> for String {
fn from(value: SystemWideUserId) -> Self {
value.to_string()
}
}
impl From<SystemWideUserId> for UserId {
fn from(value: SystemWideUserId) -> Self {
value.0
}
}
impl TryFrom<String> for SystemWideUserId {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}