1use crate::{2 fleetdata::{FleetSecret, FleetSharedSecret},3 host::Config,4};5use anyhow::{bail, Result};6use futures::{StreamExt, TryStreamExt};7use std::{8 io::{self, Cursor, Read},9 path::PathBuf,10};11use structopt::StructOpt;1213#[derive(StructOpt)]14pub enum Secrets {15 16 ForceKeys,17 18 AddShared {19 20 name: String,21 22 machines: Vec<String>,23 24 #[structopt(long)]25 force: bool,26 #[structopt(long)]27 public: Option<String>,28 #[structopt(long)]29 public_file: Option<PathBuf>,30 },31 32 Add {33 34 name: String,35 36 machine: String,37 38 #[structopt(long)]39 force: bool,40 #[structopt(long)]41 public: Option<String>,42 #[structopt(long)]43 public_file: Option<PathBuf>,44 },45}4647impl Secrets {48 pub async fn run(self, config: &Config) -> Result<()> {49 match self {50 Secrets::ForceKeys => {51 for host in config.list_hosts().await? {52 if config.should_skip(&host) {53 continue;54 }55 config.key(&host).await?;56 }57 }58 Secrets::AddShared {59 machines,60 name,61 force,62 public,63 public_file,64 } => {65 let recipients = futures::stream::iter(machines.iter())66 .then(|m| config.recipient(m))67 .try_collect::<Vec<_>>()68 .await?;6970 let secret = {71 let mut input = vec![];72 io::stdin().read_to_end(&mut input)?;7374 if input.is_empty() {75 input76 } else {77 let mut encrypted = vec![];78 let recipients = recipients79 .iter()80 .cloned()81 .map(|r| Box::new(r) as Box<dyn age::Recipient>)82 .collect();83 let mut encryptor = age::Encryptor::with_recipients(recipients)84 .wrap_output(&mut encrypted)?;85 io::copy(&mut Cursor::new(input), &mut encryptor)?;86 encryptor.finish()?;87 encrypted88 }89 };9091 let mut data = config.data_mut();92 if data.shared_secrets.contains_key(&name) && !force {93 bail!("secret already defined");94 }95 data.shared_secrets.insert(96 name,97 FleetSharedSecret {98 owners: machines,99 secret: FleetSecret {100 expire_at: None,101 secret,102 public: match (public, public_file) {103 (Some(v), None) => Some(v),104 (None, Some(v)) => Some(std::fs::read_to_string(&v)?),105 (Some(_), Some(_)) => {106 bail!("only public or public_file should be set")107 }108 (None, None) => None,109 },110 },111 },112 );113 }114 Secrets::Add {115 machine,116 name,117 force,118 public,119 public_file,120 } => {121 let recipient = config.recipient(&machine).await?;122123 let secret = {124 let mut input = vec![];125 io::stdin().read_to_end(&mut input)?;126127 let mut encrypted = vec![];128 let recipient = Box::new(recipient) as Box<dyn age::Recipient>;129 let mut encryptor = age::Encryptor::with_recipients(vec![recipient])130 .wrap_output(&mut encrypted)?;131 io::copy(&mut Cursor::new(input), &mut encryptor)?;132 encryptor.finish()?;133 encrypted134 };135136 let mut data = config.data_mut();137 let host_secrets = data.host_secrets.entry(machine).or_default();138 if host_secrets.contains_key(&name) && !force {139 bail!("secret already defined");140 }141 host_secrets.insert(142 name,143 FleetSecret {144 expire_at: None,145 secret,146 public: match (public, public_file) {147 (Some(v), None) => Some(v),148 (None, Some(v)) => Some(std::fs::read_to_string(&v)?),149 (Some(_), Some(_)) => bail!("only public or public_file should be set"),150 (None, None) => None,151 },152 },153 );154 }155 }156 Ok(())157 }158}