1use std::{2 collections::{BTreeMap, BTreeSet},3 io::{self, Cursor},4};56use age::Recipient;7use chrono::{DateTime, Utc};8use fleet_shared::SecretData;9use rand::{10 distr::{Alphanumeric, SampleString as _},11 rng,12};13use serde::{Deserialize, Serialize, de::Error};14use serde_json::Value;1516use crate::secret::{Expectations, RegenerationReason, secret_needs_regeneration};1718#[derive(Serialize, Deserialize, Default)]19#[serde(rename_all = "camelCase")]20pub struct HostData {21 #[serde(default)]22 #[serde(skip_serializing_if = "String::is_empty")]23 pub encryption_key: String,24}2526const VERSION: &str = "0.1.0";27pub struct FleetDataVersion;28impl Serialize for FleetDataVersion {29 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>30 where31 S: serde::Serializer,32 {33 VERSION.serialize(serializer)34 }35}36impl<'de> Deserialize<'de> for FleetDataVersion {37 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>38 where39 D: serde::Deserializer<'de>,40 {41 let version = String::deserialize(deserializer)?;42 if version != VERSION {43 return Err(D::Error::custom(format!(44 "fleet.nix data version mismatch, expected {VERSION}, got {version}.\nFollow the docs for migration instruction"45 )));46 }47 Ok(Self)48 }49}5051fn generate_gc_prefix() -> String {52 let id = Alphanumeric.sample_string(&mut rng(), 8);53 format!("fleet-gc-{id}")54}5556#[derive(Serialize, Deserialize)]57#[serde(rename_all = "camelCase")]58pub struct ManagerKey {59 pub name: String,60 pub key: String,61}6263#[derive(Serialize, Deserialize)]64#[serde(rename_all = "camelCase")]65pub struct FleetData {66 pub version: FleetDataVersion,67 #[serde(default = "generate_gc_prefix")]68 pub gc_root_prefix: String,6970 #[serde(default)]71 pub manager_keys: Vec<ManagerKey>,7273 #[serde(default)]74 pub hosts: BTreeMap<String, HostData>,75 #[serde(default)]76 #[serde(skip_serializing_if = "BTreeMap::is_empty")]77 pub shared_secrets: BTreeMap<String, FleetSharedSecret>,78 #[serde(default)]79 #[serde(skip_serializing_if = "BTreeMap::is_empty")]80 pub host_secrets: BTreeMap<String, BTreeMap<String, FleetHostSecret>>,8182 83 #[serde(default)]84 #[serde(skip_serializing_if = "BTreeMap::is_empty")]85 pub extra: BTreeMap<String, Value>,86}878889pub fn encrypt_secret_data<'r>(90 recipients: impl IntoIterator<Item = &'r Box<dyn Recipient>>,91 data: Vec<u8>,92) -> Option<SecretData> {93 let mut encrypted = vec![];94 let mut encryptor = age::Encryptor::with_recipients(recipients.into_iter().map(|v| &**v))95 .ok()?96 .wrap_output(&mut encrypted)97 .expect("in memory write");98 io::copy(&mut Cursor::new(data), &mut encryptor).expect("in memory copy");99 encryptor.finish().expect("in memory flush");100 Some(SecretData {101 data: encrypted,102 encrypted: true,103 })104}105106#[derive(Serialize, Deserialize, Clone)]107pub struct FleetSecretPart {108 pub raw: SecretData,109}110111#[derive(Serialize, Deserialize, Clone)]112#[serde(rename_all = "camelCase")]113#[must_use]114pub struct FleetSecretData {115 #[serde(default = "Utc::now")]116 pub created_at: DateTime<Utc>,117 #[serde(default)]118 #[serde(skip_serializing_if = "Option::is_none", alias = "expire_at")]119 pub expires_at: Option<DateTime<Utc>>,120121 #[serde(flatten)]122 pub parts: BTreeMap<String, FleetSecretPart>,123124 #[serde(default)]125 #[serde(skip_serializing_if = "Value::is_null")]126 pub generation_data: Value,127}128129#[derive(Serialize, Deserialize, Clone)]130#[serde(rename_all = "camelCase")]131#[must_use]132pub struct FleetHostSecret {133 #[serde(default)]134 #[serde(skip_serializing_if = "Option::is_none")]135 pub managed: Option<bool>,136 #[serde(flatten)]137 pub secret: FleetSecretData,138}139impl FleetHostSecret {140 pub fn needs_regeneration(&self, expectations: &Expectations) -> Option<RegenerationReason> {141 secret_needs_regeneration(&self.secret, &expectations.owners, expectations)142 }143}144145#[derive(Serialize, Deserialize, Clone)]146#[serde(rename_all = "camelCase")]147#[must_use]148pub struct FleetSharedSecret {149 #[serde(default)]150 #[serde(skip_serializing_if = "Option::is_none")]151 pub managed: Option<bool>,152 pub owners: BTreeSet<String>,153 #[serde(flatten)]154 pub secret: FleetSecretData,155}