1use std::{2 collections::BTreeMap,3 io::{self, Cursor},4};56use age::Recipient;7use chrono::{DateTime, Utc};8use fleet_shared::SecretData;9use serde::{de::Error, Deserialize, Serialize};10use serde_json::Value;1112#[derive(Serialize, Deserialize, Default)]13#[serde(rename_all = "camelCase")]14pub struct HostData {15 #[serde(default)]16 #[serde(skip_serializing_if = "String::is_empty")]17 pub encryption_key: String,18}1920const VERSION: &str = "0.1.0";21pub struct FleetDataVersion;22impl Serialize for FleetDataVersion {23 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>24 where25 S: serde::Serializer,26 {27 VERSION.serialize(serializer)28 }29}30impl<'de> Deserialize<'de> for FleetDataVersion {31 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>32 where33 D: serde::Deserializer<'de>,34 {35 let version = String::deserialize(deserializer)?;36 if version != VERSION {37 return Err(D::Error::custom(format!(38 "fleet.nix data version mismatch, expected {VERSION}, got {version}.\nFollow the docs for migration instruction"39 )));40 }41 Ok(Self)42 }43}4445#[derive(Serialize, Deserialize)]46#[serde(rename_all = "camelCase")]47pub struct FleetData {48 pub version: FleetDataVersion,4950 #[serde(default)]51 pub hosts: BTreeMap<String, HostData>,52 #[serde(default)]53 #[serde(skip_serializing_if = "BTreeMap::is_empty")]54 pub shared_secrets: BTreeMap<String, FleetSharedSecret>,55 #[serde(default)]56 #[serde(skip_serializing_if = "BTreeMap::is_empty")]57 pub host_secrets: BTreeMap<String, BTreeMap<String, FleetSecret>>,5859 60 #[serde(default)]61 #[serde(skip_serializing_if = "BTreeMap::is_empty")]62 pub extra: BTreeMap<String, Value>,63}6465#[derive(Serialize, Deserialize, Clone)]66#[serde(rename_all = "camelCase")]67#[must_use]68pub struct FleetSharedSecret {69 pub owners: Vec<String>,70 #[serde(flatten)]71 pub secret: FleetSecret,72}737475pub fn encrypt_secret_data<'a>(76 recipients: impl IntoIterator<Item = &'a dyn Recipient>,77 data: Vec<u8>,78) -> Option<SecretData> {79 let mut encrypted = vec![];80 let mut encryptor = age::Encryptor::with_recipients(recipients.into_iter())81 .ok()?82 .wrap_output(&mut encrypted)83 .expect("in memory write");84 io::copy(&mut Cursor::new(data), &mut encryptor).expect("in memory copy");85 encryptor.finish().expect("in memory flush");86 Some(SecretData {87 data: encrypted,88 encrypted: true,89 })90}9192#[derive(Serialize, Deserialize, Clone)]93pub struct FleetSecretPart {94 pub raw: SecretData,95}9697#[derive(Serialize, Deserialize, Clone)]98#[serde(rename_all = "camelCase")]99#[must_use]100pub struct FleetSecret {101 #[serde(default = "Utc::now")]102 pub created_at: DateTime<Utc>,103 #[serde(default)]104 #[serde(skip_serializing_if = "Option::is_none", alias = "expire_at")]105 pub expires_at: Option<DateTime<Utc>>,106107 #[serde(flatten)]108 pub parts: BTreeMap<String, FleetSecretPart>,109}