1use std::{2 collections::BTreeMap,3 io::{self, Cursor},4};56use age::Recipient;7use chrono::{DateTime, Utc};8use fleet_shared::SecretData;9use itertools::Itertools;10use serde::{de::Error, Deserialize, Serialize};11use serde_json::Value;1213#[derive(Serialize, Deserialize, Default)]14#[serde(rename_all = "camelCase")]15pub struct HostData {16 #[serde(default)]17 #[serde(skip_serializing_if = "String::is_empty")]18 pub encryption_key: String,19}2021const VERSION: &str = "0.1.0";22pub struct FleetDataVersion;23impl Serialize for FleetDataVersion {24 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>25 where26 S: serde::Serializer,27 {28 VERSION.serialize(serializer)29 }30}31impl<'de> Deserialize<'de> for FleetDataVersion {32 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>33 where34 D: serde::Deserializer<'de>,35 {36 let version = String::deserialize(deserializer)?;37 if version != VERSION {38 return Err(D::Error::custom(format!(39 "fleet.nix data version mismatch, expected {VERSION}, got {version}.\nFollow the docs for migration instruction"40 )));41 }42 Ok(Self)43 }44}4546#[derive(Serialize, Deserialize)]47#[serde(rename_all = "camelCase")]48pub struct FleetData {49 pub version: FleetDataVersion,5051 #[serde(default)]52 pub hosts: BTreeMap<String, HostData>,53 #[serde(default)]54 #[serde(skip_serializing_if = "BTreeMap::is_empty")]55 pub shared_secrets: BTreeMap<String, FleetSharedSecret>,56 #[serde(default)]57 #[serde(skip_serializing_if = "BTreeMap::is_empty")]58 pub host_secrets: BTreeMap<String, BTreeMap<String, FleetSecret>>,5960 61 #[serde(default)]62 #[serde(skip_serializing_if = "BTreeMap::is_empty")]63 pub extra: BTreeMap<String, Value>,64}6566#[derive(Serialize, Deserialize, Clone)]67#[serde(rename_all = "camelCase")]68#[must_use]69pub struct FleetSharedSecret {70 pub owners: Vec<String>,71 #[serde(flatten)]72 pub secret: FleetSecret,73}747576pub fn encrypt_secret_data(77 recipients: impl IntoIterator<Item = impl Recipient + Send + 'static>,78 data: Vec<u8>,79) -> Option<SecretData> {80 let mut encrypted = vec![];81 let recipients = recipients82 .into_iter()83 .map(|v| Box::new(v) as Box<dyn Recipient + Send>)84 .collect_vec();85 let mut encryptor = age::Encryptor::with_recipients(recipients)?86 .wrap_output(&mut encrypted)87 .expect("in memory write");88 io::copy(&mut Cursor::new(data), &mut encryptor).expect("in memory copy");89 encryptor.finish().expect("in memory flush");90 Some(SecretData {91 data: encrypted,92 encrypted: true,93 })94}9596#[derive(Serialize, Deserialize, Clone)]97pub struct FleetSecretPart {98 pub raw: SecretData,99}100101#[derive(Serialize, Deserialize, Clone)]102#[serde(rename_all = "camelCase")]103#[must_use]104pub struct FleetSecret {105 #[serde(default = "Utc::now")]106 pub created_at: DateTime<Utc>,107 #[serde(default)]108 #[serde(skip_serializing_if = "Option::is_none", alias = "expire_at")]109 pub expires_at: Option<DateTime<Utc>>,110111 #[serde(flatten)]112 pub parts: BTreeMap<String, FleetSecretPart>,113}