1use std::str::FromStr as _;23use age::Recipient;4use anyhow::{Result, anyhow, bail};5use futures::{StreamExt as _, TryStreamExt as _};6use itertools::Itertools as _;7use tracing::warn;89use crate::{fleetdata::SecretOwner, host::Config};1011impl Config {12 pub fn gc_root_prefix(&self) -> String {13 self.data.gc_root_prefix.clone()14 }15 fn cached_host_key(&self, host: &str) -> Option<String> {16 let hosts = self.data.hosts.read().expect("no poisoning");17 let key = hosts.get(host).map(|h| &h.encryption_key);18 if let Some(key) = key19 && key.is_empty()20 {21 return None;22 }23 key.cloned()24 }25 pub fn update_key(&self, host: &str, key: String) {26 let mut hosts = self.data.hosts.write().expect("no poisoning");27 let host = hosts.entry(host.to_string()).or_default();28 host.encryption_key = key.trim().to_string();29 }3031 pub async fn host_key(&self, host: &str) -> anyhow::Result<String> {32 if let Some(key) = self.cached_host_key(host) {33 Ok(key)34 } else {35 warn!("Loading key for {}", host);36 let host = self.host(host)?;37 let remowt = host.remowt().await?;38 let mut cmd = remowt.cmd("cat");39 cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");40 let key = cmd.run_string().await?;41 self.update_key(&host.name, key.clone());42 Ok(key)43 }44 }45 pub async fn key(&self, owner: &SecretOwner) -> anyhow::Result<String> {46 if let Some(host) = owner.as_host() {47 self.host_key(host).await48 } else {49 bail!("only host keys supported for now")50 }51 }52 53 pub async fn recipient(&self, host: &SecretOwner) -> anyhow::Result<Box<dyn Recipient>> {54 let key = self.key(host).await?;55 age::ssh::Recipient::from_str(&key)56 .map_err(|e| anyhow!("parse recipient error: {e:?}"))57 .map(|v| Box::new(v) as Box<dyn Recipient>)58 }5960 pub async fn recipients(&self, hosts: Vec<SecretOwner>) -> Result<Vec<Box<dyn Recipient>>> {61 futures::stream::iter(hosts.iter())62 .then(|m| self.recipient(m))63 .try_collect::<Vec<_>>()64 .await65 }6667 #[allow(dead_code)]68 pub async fn orphaned_data(&self) -> Result<Vec<String>> {69 let mut out = Vec::new();70 let host_names = self.list_hosts()?.into_iter().map(|h| h.name).collect_vec();71 let hosts = self.data.hosts.read().expect("no poisoning");72 for hostname in hosts73 .iter()74 .filter(|(_, host)| !host.encryption_key.is_empty())75 .map(|(n, _)| n)76 {77 if !host_names.contains(hostname) {78 out.push(hostname.to_owned())79 }80 }8182 Ok(out)83 }84}