git.delta.rocks / fleet / refs/commits / 087938500f2e

difftreelog

source

crates/fleet-base/src/keys.rs2.3 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	fn cached_host_key(&self, host: &str) -> Option<String> {13		let hosts = self.data.hosts.read().expect("no poisoning");14		let key = hosts.get(host).map(|h| &h.encryption_key);15		if let Some(key) = key16			&& key.is_empty()17		{18			return None;19		}20		key.cloned()21	}22	pub fn update_key(&self, host: &str, key: String) {23		let mut hosts = self.data.hosts.write().expect("no poisoning");24		let host = hosts.entry(host.to_string()).or_default();25		host.encryption_key = key.trim().to_string();26	}2728	pub async fn host_key(&self, host: &str) -> anyhow::Result<String> {29		if let Some(key) = self.cached_host_key(host) {30			Ok(key)31		} else {32			warn!("Loading key for {}", host);33			let host = self.host(host)?;34			let remowt = host.remowt().await?;35			let mut cmd = remowt.cmd("cat");36			cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");37			let key = cmd.run_string().await?;38			self.update_key(&host.name, key.clone());39			Ok(key)40		}41	}42	pub async fn key(&self, owner: &SecretOwner) -> anyhow::Result<String> {43		if let Some(host) = owner.as_host() {44			self.host_key(host).await45		} else {46			bail!("only host keys supported for now")47		}48	}49	/// Insecure, requires root50	pub async fn recipient(&self, host: &SecretOwner) -> anyhow::Result<Box<dyn Recipient>> {51		let key = self.key(host).await?;52		age::ssh::Recipient::from_str(&key)53			.map_err(|e| anyhow!("parse recipient error: {e:?}"))54			.map(|v| Box::new(v) as Box<dyn Recipient>)55	}5657	pub async fn recipients(&self, hosts: Vec<SecretOwner>) -> Result<Vec<Box<dyn Recipient>>> {58		futures::stream::iter(hosts.iter())59			.then(|m| self.recipient(m))60			.try_collect::<Vec<_>>()61			.await62	}6364	#[allow(dead_code)]65	pub async fn orphaned_data(&self) -> Result<Vec<String>> {66		let mut out = Vec::new();67		let host_names = self.list_hosts()?.into_iter().map(|h| h.name).collect_vec();68		let hosts = self.data.hosts.read().expect("no poisoning");69		for hostname in hosts70			.iter()71			.filter(|(_, host)| !host.encryption_key.is_empty())72			.map(|(n, _)| n)73		{74			if !host_names.contains(hostname) {75				out.push(hostname.to_owned())76			}77		}7879		Ok(out)80	}81}