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 106 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}