--- a/cmds/fleet/src/cmds/secrets/mod.rs +++ b/cmds/fleet/src/cmds/secrets/mod.rs @@ -1,6 +1,12 @@ -use crate::{fleetdata::FleetSecret, host::Config}; +use crate::{ + fleetdata::{FleetSecret, FleetSharedSecret}, + host::Config, +}; use anyhow::{bail, Result}; -use std::io::{self, Cursor, Read}; +use std::{ + io::{self, Cursor, Read}, + path::PathBuf, +}; use structopt::StructOpt; #[derive(StructOpt)] @@ -8,7 +14,7 @@ /// Force load keys for all defined hosts ForceKeys, /// Add secret, data should be provided in stdin - Add { + AddShared { /// Secret name name: String, /// Secret owners @@ -18,7 +24,23 @@ force: bool, #[structopt(long)] public: Option, + #[structopt(long)] + public_file: Option, }, + /// Add secret, data should be provided in stdin + Add { + /// Secret name + name: String, + /// Secret owners + machine: String, + /// Override secret if already present + #[structopt(long)] + force: bool, + #[structopt(long)] + public: Option, + #[structopt(long)] + public_file: Option, + }, } impl Secrets { @@ -32,11 +54,12 @@ config.key(&host)?; } } - Secrets::Add { + Secrets::AddShared { machines, name, force, public, + public_file, } => { let recipients = machines .iter() @@ -61,16 +84,66 @@ }; let mut data = config.data_mut(); - if data.secrets.contains_key(&name) && !force { + if data.shared_secrets.contains_key(&name) && !force { bail!("secret already defined"); } - data.secrets.insert( + data.shared_secrets.insert( name, - FleetSecret { + FleetSharedSecret { owners: machines, + secret: FleetSecret { + expire_at: None, + secret, + public: match (public, public_file) { + (Some(v), None) => Some(v), + (None, Some(v)) => Some(std::fs::read_to_string(&v)?), + (Some(_), Some(_)) => { + bail!("only public or public_file should be set") + } + (None, None) => None, + }, + }, + }, + ); + } + Secrets::Add { + machine, + name, + force, + public, + public_file, + } => { + let recipient = config.recipient(&machine)?; + + let secret = { + let mut input = vec![]; + io::stdin().read_to_end(&mut input)?; + + let mut encrypted = vec![]; + let recipient = Box::new(recipient) as Box; + let mut encryptor = age::Encryptor::with_recipients(vec![recipient]) + .wrap_output(&mut encrypted)?; + io::copy(&mut Cursor::new(input), &mut encryptor)?; + encryptor.finish()?; + encrypted + }; + + let mut data = config.data_mut(); + let host_secrets = data.host_secrets.entry(machine).or_default(); + if host_secrets.contains_key(&name) && !force { + bail!("secret already defined"); + } + host_secrets.insert( + name, + FleetSecret { expire_at: None, secret, - public, + public: match (public, public_file) { + (Some(v), None) => Some(v), + (None, Some(v)) => Some(std::fs::read_to_string(&v)?), + (Some(_), Some(_)) => bail!("only public or public_file should be set"), + (None, None) => None, + }, }, ); } --- a/cmds/fleet/src/fleetdata.rs +++ b/cmds/fleet/src/fleetdata.rs @@ -11,18 +11,29 @@ } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct FleetData { #[serde(default)] pub hosts: BTreeMap, #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] - pub secrets: BTreeMap, + pub shared_secrets: BTreeMap, + #[serde(default)] + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub host_secrets: BTreeMap>, } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FleetSecret { +pub struct FleetSharedSecret { pub owners: Vec, + #[serde(flatten)] + pub secret: FleetSecret, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FleetSecret { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub expire_at: Option>,