git.delta.rocks / jrsonnet / refs/commits / d866c975e1c7

difftreelog

source

src/db/secret.rs5.1 KiBsourcehistory
1use crate::nix::{NixBuild, NixEval, SECRETS_ATTRIBUTE};2use anyhow::{bail, Result};3use log::info;4use serde::{Deserialize, Deserializer, Serialize, Serializer};5use std::{6	collections::{BTreeMap, BTreeSet, HashMap},7	time::Instant,8	time::SystemTime,9};10use time::{Duration, PrimitiveDateTime};1112use super::{db::DbData, keys::KeyDb};1314#[derive(Serialize, Deserialize, Debug)]15pub struct SecretListData {16	pub owners: BTreeSet<String>,17	#[serde(rename = "expireIn")]18	renew_in: Option<u64>,19}20pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {21	NixEval::new(format!("{}", SECRETS_ATTRIBUTE))22		.apply(23			r#"24				s: (builtins.mapAttrs (n: {owners, expireIn, ...}: {25					inherit owners expireIn;26				}) s)27			"#28			.into(),29		)30		.run_json()31}3233struct ReadableDate(PrimitiveDateTime);34impl Serialize for ReadableDate {35	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>36	where37		S: Serializer,38	{39		serializer.serialize_str(&self.0.to_string())40	}41}42impl<'de> Deserialize<'de> for ReadableDate {43	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>44	where45		D: Deserializer<'de>,46	{47		Ok(Self(48			PrimitiveDateTime::parse(String::deserialize(deserializer)?, "%F %T").unwrap(),49		))50	}51}52impl From<PrimitiveDateTime> for ReadableDate {53	fn from(d: PrimitiveDateTime) -> Self {54		Self(d)55	}56}57impl From<ReadableDate> for PrimitiveDateTime {58	fn from(d: ReadableDate) -> Self {59		d.060	}61}6263#[derive(serde::Serialize, serde::Deserialize)]64struct SecretData {65	created_at: ReadableDate,66	renew_at: Option<ReadableDate>,67	owners: BTreeSet<String>,6869	public_data: BTreeMap<String, String>,70	private_files: BTreeMap<String, String>,71}72impl SecretData {73	fn should_renew(&self) -> bool {74		if let Some(renew_at) = &self.renew_at {75			let now: PrimitiveDateTime = SystemTime::now().into();76			renew_at.0 <= now77		} else {78			false79		}80	}81	fn is_valid(&self, data: &SecretListData) -> bool {82		self.owners == data.owners83	}84}8586#[derive(serde::Serialize, serde::Deserialize)]87struct NixDataValue {88	data: BTreeMap<String, String>,89}9091#[derive(serde::Serialize, serde::Deserialize)]92struct NixData {93	secrets: BTreeMap<String, NixDataValue>,94}9596#[derive(serde::Serialize, serde::Deserialize, Default)]97pub struct SecretDb {98	secrets: BTreeMap<String, SecretData>,99}100impl DbData for SecretDb {101	const DB_NAME: &'static str = "secrets";102}103104impl SecretDb {105	// Secrets are generated on machine running fleet command106	pub fn generate_secret(107		&mut self,108		keys: &KeyDb,109		secret: &str,110		data: &SecretListData,111	) -> Result<()> {112		let mut rage_keys = String::new();113		for (i, owner) in data.owners.iter().enumerate() {114			if i != 0 {115				rage_keys.push(' ');116			}117			rage_keys.push_str("--recipient \"");118			rage_keys.push_str(&keys.get_host_key(&owner)?);119			rage_keys.push('"')120		}121		let created_at: PrimitiveDateTime = SystemTime::now().into();122		let renew_at = data123			.renew_in124			.map(|hours| created_at + Duration::hours(hours as i64));125		let built = NixBuild::new(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret))126			.env("RAGE_KEYS".into(), rage_keys)127			.env("IMPURITY_SOURCE".into(), format!("{:?}", Instant::now()))128			.run()?;129		let path = built.path().to_owned();130		let mut secret_data = SecretData {131			created_at: created_at.into(),132			renew_at: renew_at.map(|v| v.into()),133			owners: data.owners.clone(),134			public_data: BTreeMap::new(),135			private_files: BTreeMap::new(),136		};137		for file in std::fs::read_dir(path)? {138			let entry = file?;139			if !entry.file_type()?.is_file() {140				bail!("Secret generator should produce files, not directories");141			}142			let name = entry.file_name();143			let name = name144				.to_str()145				.ok_or(anyhow::anyhow!("file name should be utf-8"))?;146			let value = String::from_utf8(std::fs::read(entry.path())?)?;147			if let Some(name) = name.strip_prefix("pub_") {148				secret_data.public_data.insert(name.into(), value);149			} else {150				secret_data.private_files.insert(name.into(), value);151			}152		}153		self.secrets.insert(secret.into(), secret_data);154		Ok(())155	}156	pub fn need_to_generate(&self, secret: &str, data: &SecretListData) -> Result<bool> {157		let secret = self.secrets.get(secret);158		if secret.is_none() {159			return Ok(true);160		}161		let secret = secret.unwrap();162163		if secret.should_renew() {164			return Ok(true);165		}166167		if !secret.is_valid(&data) {168			return Ok(true);169		}170171		Ok(false)172	}173	pub fn ensure_generated(174		&mut self,175		keys: &KeyDb,176		secret: &str,177		data: &SecretListData,178	) -> Result<()> {179		if self.need_to_generate(secret, data)? {180			info!("Generating secret {}", secret);181			self.generate_secret(keys, secret, data)?;182		}183184		Ok(())185	}186	pub fn generate_nix_data(&self) -> Result<String> {187		let mut out = BTreeMap::new();188		for (host, secrets) in &self.secrets {189			out.insert(190				host.to_owned(),191				NixDataValue {192					data: secrets193						.public_data194						.clone()195						.iter()196						.map(|(k, v)| (k.to_owned(), v.trim().to_owned()))197						.collect(),198				},199			);200		}201		Ok(serde_json::to_string(&out)?)202	}203204	pub fn has_secret(&self, secret: &str) -> bool {205		self.secrets.contains_key(secret)206	}207208	pub fn remove_secret(&mut self, secret: &str) {209		self.secrets.remove(secret);210	}211}