git.delta.rocks / fleet / refs/commits / 9766610438d2

difftreelog

source

crates/fleet-base/src/keys.rs2.4 KiBsourcehistory
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	/// Insecure, requires root53	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}