use super::{Authority, MachineConfig, PassRefusal};
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex, Weak};
use zeroize::Zeroizing;
pub(super) struct HeldToken {
pub(super) token: Zeroizing<String>,
authority: Weak<Authority>,
}
type PassCacheToken = Mutex<HeldToken>;
type PassCache = BTreeMap<Vec<u8>, Weak<PassCacheToken>>;
static PASSES: Mutex<PassCache> = Mutex::new(BTreeMap::new());
#[derive(Clone)]
pub struct MachinePass(pub(super) Arc<PassCacheToken>);
impl std::fmt::Debug for MachinePass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MachinePass").finish_non_exhaustive()
}
}
impl MachinePass {
pub(crate) fn configured(machine: &MachineConfig) -> Result<Self, PassRefusal> {
Self::hold(machine, None)
}
pub(super) fn hold(
machine: &MachineConfig,
token: Option<Zeroizing<String>>,
) -> Result<Self, PassRefusal> {
let source = serde_json::to_vec(&(
&machine.identity_path,
&machine.pass_path,
&machine.agent_id,
&machine.audience,
))
.map_err(|_| PassRefusal("machine_pass_source_refused"))?;
let mut passes = PASSES
.lock()
.map_err(|_| PassRefusal("pass_holder_refused"))?;
passes.retain(|_, pass| pass.strong_count() > 0);
if let Some(pass) = passes.get(&source).and_then(Weak::upgrade) {
return Ok(Self(pass));
}
let token = match token {
Some(token) => token,
None if machine.agent_id.is_empty() && !machine.pass_path.as_os_str().is_empty() => {
Zeroizing::new(
std::fs::read_to_string(&machine.pass_path)
.map_err(|_| PassRefusal("machine_pass_path_refused"))?,
)
}
None if !machine.agent_id.is_empty() => Zeroizing::new(String::new()),
None => return Err(PassRefusal("machine_pass_not_configured")),
};
let pass = Arc::new(Mutex::new(HeldToken {
token: Zeroizing::new(token.trim().to_owned()),
authority: Weak::new(),
}));
passes.insert(source, Arc::downgrade(&pass));
drop(passes);
Ok(Self(pass))
}
pub(super) fn attach(&self, authority: &Arc<Authority>) -> Result<(), PassRefusal> {
let mut held = self
.0
.lock()
.map_err(|_| PassRefusal("pass_holder_refused"))?;
if held.authority.upgrade().is_some() {
return Err(PassRefusal("machine_pass_already_installed"));
}
held.authority = Arc::downgrade(authority);
drop(held);
Ok(())
}
pub(crate) fn for_open(&self) -> Result<Zeroizing<String>, PassRefusal> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_| PassRefusal("machine_pass_clock_refused"))?
.as_secs();
self.for_open_at(now)
}
pub(super) fn for_open_at(&self, now: u64) -> Result<Zeroizing<String>, PassRefusal> {
let authority = self
.0
.lock()
.map_err(|_| PassRefusal("pass_holder_refused"))?
.authority
.upgrade();
if let Some(authority) = authority {
authority.refresh_expired(now)?;
}
self.current()
}
pub(crate) fn issued(&self) -> Result<bool, PassRefusal> {
self.0
.lock()
.map(|held| !held.token.is_empty())
.map_err(|_| PassRefusal("pass_holder_refused"))
}
pub(crate) fn current(&self) -> Result<Zeroizing<String>, PassRefusal> {
self.0
.lock()
.map(|held| held.token.clone())
.map_err(|_| PassRefusal("pass_holder_refused"))
}
}