difftreelog
feat fleet secret read command reimplementation
in: trunk
2 files changed
cmds/fleet/src/cmds/secrets.rsdiffbeforeafterboth1use std::{1use std::io::{Write as _, stdout};2 collections::{BTreeSet, HashSet},3 io::{Read, stdin},4 path::PathBuf,5};627use anyhow::{Context as _, Result, anyhow, bail, ensure};3use anyhow::{Context as _, Result, anyhow, bail};8use clap::Parser;4use clap::Parser;9use fleet_base::{fleetdata::SecretOwner, host::Config, opts::FleetOpts};5use fleet_base::{fleetdata::SecretOwner, host::Config, opts::FleetOpts};10use fleet_shared::SecretData;11use itertools::{ExactlyOneError, Itertools as _};6use itertools::Itertools as _;12use tokio::fs::read;13use tracing::{info, warn};7use tracing::warn;14815#[derive(Parser)]9#[derive(Parser)]16pub enum Secret {10pub enum Secret {27 machine: Option<String>,21 machine: Option<String>,282229 /// Which private secret part to read23 /// Which private secret part to read24 /// If not specified - only one existing part is read30 #[clap(short = 'p', long, default_value = "secret")]25 #[clap(short = 'p', long)]31 part: Option<String>,26 part: Option<String>,3233 /// Which host should we use to decrypt, in case if reencryption is required, without34 /// regeneration35 #[clap(long)]36 prefer_identities: Vec<String>,37 },27 },38 /// Prune (remove, mark for regeneration) secrets28 /// Prune (remove, mark for regeneration) secrets39 Prune {29 Prune {54 machine: Vec<String>,44 machine: Vec<String>,55 },45 },56 List {},46 List {},57 Edit {58 name: String,59 #[clap(short = 'm', long)]60 machine: String,6162 #[clap(long)]63 add: bool,6465 /// Which private secret part to read66 #[clap(short = 'p', long, default_value = "secret")]67 part: String,68 },69}47}704871impl Secret {49impl Secret {83 name,61 name,84 machine,62 machine,85 part: part_name,63 part: part_name,86 mut prefer_identities,87 } => {64 } => {65 let (owners, secret_data) = {88 let secret = config.data.secrets.read().expect("not poisoned");66 let secret = config.data.secrets.read().expect("not poisoned");896790 let Some(dist) = secret.get("name") else {68 let Some(dist) = secret.get(&name) else {91 bail!("secret doesn't exists");69 bail!("secret doesn't exists");92 };70 };9371103 )?81 )?104 };82 };10583106 let part_name = part_name.unwrap_or_else(|| "secret".to_string());84 let part = if let Some(part_name) = &part_name {85 dist.secret.parts.get(part_name).ok_or_else(|| {86 anyhow!("secret {name} does not have part named {part_name}")87 })?88 } else {89 dist.secret90 .parts91 .iter()92 .exactly_one()93 .map_err(|e| anyhow!("{e}"))94 .context("with no part specified, there should be exactly one part")?95 .196 };107 let Some(part) = dist.secret.parts.get(&part_name) else {97 let owners = dist.owners().cloned().collect::<Vec<_>>();98 let secret_data = part.raw.clone();99 (owners, secret_data)100 };101102 for host in config103 .preferred_hosts(|h| owners.iter().any(|o| o.as_host() == Some(h)))104 .context("failed to list hosts")?105 {106 let host = match host {107 Ok(h) => h,108 Err(e) => {109 warn!("failed to use host: {e}");110 continue;111 }112 };113 match host.decrypt(secret_data.clone()).await {114 Ok(data) => {115 let mut w = stdout();116 w.write_all(&data)?;117 return Ok(());118 }108 bail!("secret part {part_name:?} is not defined");119 Err(e) => warn!("failed to decrypt on {}: {e}", host.name),109 };120 };110121 }111 // dist.get(SecretOwner(name));112113 todo!();122 bail!("failed to find suitable decrypting host");114 /*115 let Some(secret) = config.shared_secret(&name) else {116 bail!("secret doesn't exists");117 };118119 let dist = if secret.len() == 1 {120 &secret[0]121 } else if let Some(machine) = machine {122 let dist = secret.get(&machine);123 let Some(dist) = dist else {124 bail!("machine {machine} has no distribution of secret {name}");125 };126 prefer_identities.push(machine);127 dist128 } else {129 bail!(130 "secret {name} has shares, but no --machine specified for specifing which do you need"131 )132 };133134 let Some(part) = dist.secret.parts.get(&part_name) else {135 bail!("no part {part_name} in secret {name}");136 };137 let data = if part.raw.encrypted {138 let identity_holder = if !prefer_identities.is_empty() {139 prefer_identities140 .iter()141 .find(|i| dist.owners.iter().any(|s| s == *i))142 } else {143 dist.owners.first()144 };145 let Some(identity_holder) = identity_holder else {146 bail!("no available holder found");147 };148 let host = config.host(identity_holder)?;149 host.decrypt(part.raw.clone()).await?150 } else {151 part.raw.data.clone()152 };153 stdout().write_all(&data)?;154 */155 todo!()156 }123 }157 Secret::List {} => {124 Secret::List {} => {158 /*125 /*192 */159 */193 todo!()160 todo!()194 }161 }195 Secret::Edit {196 name,197 machine,198 part,199 add,200 } => {201 /*let secret = config202 .host_secret(&machine, &name)203 .context("secret not found")?;204 if let Some(data) = secret.secret.parts.get(&part) {205 let host = config.host(&machine)?;206 let secret = host.decrypt(data.raw.clone()).await?;207 String::from_utf8(secret).context("secret is not utf8")?208 } else if add {209 String::new()210 } else {211 bail!("part {part} not found in secret {name}. Did you mean to `--add` it?");212 };*/213 todo!()214 }215 Secret::Prune { name, machine } => todo!(),162 Secret::Prune { name, machine } => todo!(),216 Secret::Ensure { name, machine } => todo!(),163 Secret::Ensure { name, machine } => todo!(),217 }164 }crates/fleet-base/src/host.rsdiffbeforeafterboth1use std::{1use std::{2 collections::{BTreeMap, BTreeSet},2 collections::{BTreeMap, BTreeSet, HashSet},3 ffi::{OsStr, OsString},3 ffi::{OsStr, OsString},4 fmt::Display,4 fmt::Display,5 io::Write,5 io::Write,652 }652 }653 }653 }654655 pub fn preferred_hosts(656 &self,657 filter: impl Fn(&str) -> bool,658 ) -> Result<impl Iterator<Item = Result<ConfigHost>>> {659 let prefer = self660 .prefer_identities661 .iter()662 .filter_map(|v| v.as_host())663 .collect::<HashSet<_>>();664 let config = &self.config_field;665 let mut names = nix_go!(config.hosts).list_fields()?;666 names.retain(|s| filter(s));667 names.sort_by_key(|h| prefer.contains(h.as_str()));668669 Ok(names.into_iter().map(|h| self.host(&h)))670 }654671655 pub fn host(&self, name: &str) -> Result<ConfigHost> {672 pub fn host(&self, name: &str) -> Result<ConfigHost> {656 let config = &self.config_field;673 let config = &self.config_field;