1use std::collections::BTreeSet;23use anyhow::Result;4use chrono::{DateTime, Utc};5use nix_eval::{Value, nix_go, nix_go_json};67use crate::fleetdata::FleetSecretData;89#[derive(Debug)]10pub struct Expectations {11 pub owners: BTreeSet<String>,12 pub generation_data: serde_json::Value,13 pub public_parts: BTreeSet<String>,14 pub private_parts: BTreeSet<String>,15}1617pub struct HostSecretDefinition(pub(crate) String, pub(crate) Value);18impl HostSecretDefinition {19 pub fn is_managed(&self) -> Result<bool> {20 let def = self.definition_value()?;21 Ok(!nix_go!(def.generator).is_null())22 }23 pub fn is_shared(&self) -> Result<bool> {24 let def = self.definition_value()?;25 Ok(nix_go_json!(def.shared))26 }27 pub fn expectations(&self) -> Result<Expectations> {28 let def = self.definition_value()?;29 let parts = nix_go!(def.parts);3031 let mut public_parts = BTreeSet::new();32 let mut private_parts = BTreeSet::new();33 for part in parts.list_fields()? {34 if nix_go_json!(parts[&part].encrypted) {35 private_parts.insert(part.clone());36 } else {37 public_parts.insert(part.clone());38 }39 }4041 Ok(Expectations {42 owners: BTreeSet::from([self.0.clone()]),43 generation_data: nix_go_json!(def.expectedGenerationData),44 public_parts,45 private_parts,46 })47 }48 pub fn definition_value(&self) -> Result<Value> {49 let value = &self.1;50 Ok(nix_go!(value.definition))51 }52}5354pub struct SharedSecretDefinition(pub(crate) Value);55impl SharedSecretDefinition {56 pub fn is_managed(&self) -> Result<bool> {57 let value = &self.0;58 Ok(!nix_go!(value.generator).is_null())59 }60 pub fn expectations(&self) -> Result<Expectations> {61 let value = &self.0;62 Ok(Expectations {63 owners: nix_go_json!(value.expectedOwners),64 generation_data: nix_go_json!(value.expectedGenerationData),65 public_parts: nix_go_json!(value.expectedPublicParts),66 private_parts: nix_go_json!(value.expectedPrivateParts),67 })68 }69 pub fn definition_value(&self) -> Value {70 self.0.clone()71 }72}7374#[derive(thiserror::Error, Debug)]75pub enum RegenerationReason {76 #[error("owners added: {0:?}")]77 OwnersAdded(BTreeSet<String>),78 #[error("owners added: {0:?}")]79 OwnersRemoved(BTreeSet<String>),80 #[error("unexpected generation data, expected: {expected:?}, found: {found:?}")]81 GenerationData {82 expected: serde_json::Value,83 found: serde_json::Value,84 },85 #[error("unexpected part list, expected: {expected:?}, found: {found:?}")]86 PartList {87 expected: BTreeSet<String>,88 found: BTreeSet<String>,89 },90 #[error("part {0} is expected to be encrypted")]91 ExpectedPrivate(String),92 #[error("part {0} is not expected to be encrypted")]93 ExpectedPublic(String),94 #[error("secret is expired at {0}")]95 Expired(DateTime<Utc>),96}9798pub fn secret_needs_regeneration(99 secret: &FleetSecretData,100 owners: &BTreeSet<String>,101 expectations: &Expectations,102) -> Option<RegenerationReason> {103 if !owners.is_empty() {104 let added: BTreeSet<String> = expectations.owners.difference(owners).cloned().collect();105 if !added.is_empty() {106 return Some(RegenerationReason::OwnersAdded(added));107 }108109 let removed: BTreeSet<String> = owners.difference(&expectations.owners).cloned().collect();110 if !removed.is_empty() {111 return Some(RegenerationReason::OwnersRemoved(removed));112 }113 }114115 if secret.generation_data != expectations.generation_data {116 return Some(RegenerationReason::GenerationData {117 expected: expectations.generation_data.clone(),118 found: secret.generation_data.clone(),119 });120 }121122 if !expectations.public_parts.is_empty() || !expectations.private_parts.is_empty() {123 let expected: BTreeSet<String> = expectations124 .public_parts125 .union(&expectations.private_parts)126 .cloned()127 .collect();128 let found: BTreeSet<String> = secret.parts.keys().cloned().collect();129130 if found != expected {131 return Some(RegenerationReason::PartList { expected, found });132 }133134 for (name, value) in secret.parts.iter() {135 if value.raw.encrypted {136 if !expectations.private_parts.contains(name) {137 return Some(RegenerationReason::ExpectedPrivate(name.clone()));138 }139 } else if !expectations.public_parts.contains(name) {140 return Some(RegenerationReason::ExpectedPublic(name.clone()));141 }142 }143 }144145 if let Some(expiration) = secret.expires_at {146 147 if expiration < Utc::now() {148 return Some(RegenerationReason::Expired(expiration));149 }150 }151152 None153}