git.delta.rocks / jrsonnet / refs/commits / 4973cbaf2611

difftreelog

feat secrets cmd update

tzzukmulYaroslav Bolyukin2026-04-18parent: #dffcb61.patch.diff
in: trunk

2 files changed

addedcmds/fleet/src/cmds/secrets.rsdiffbeforeafterboth

no changes

deletedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/secrets/mod.rs
+++ /dev/null
@@ -1,352 +0,0 @@
-use std::{
-	collections::{BTreeSet, HashSet},
-	io::{Read, stdin},
-	path::PathBuf,
-};
-
-use anyhow::{Result, bail, ensure};
-use clap::Parser;
-use fleet_base::{host::Config, opts::FleetOpts};
-use fleet_shared::SecretData;
-use tokio::fs::read;
-use tracing::{info, warn};
-
-#[derive(Parser)]
-pub enum Secret {
-	/// Force load host keys for all defined hosts
-	ForceKeys,
-	/// Read secret from remote host, requires sudo on one of the owning hosts
-	Read {
-		/// Secret name to read
-		name: String,
-
-		/// Distribution with what machine to read
-		/// If not shared between multiple - defaults to single owner
-		#[clap(short = 'm', long)]
-		machine: Option<String>,
-
-		/// Which private secret part to read
-		#[clap(short = 'p', long, default_value = "secret")]
-		part: Option<String>,
-
-		/// Which host should we use to decrypt, in case if reencryption is required, without
-		/// regeneration
-		#[clap(long)]
-		prefer_identities: Vec<String>,
-	},
-	/// Prune (remove, mark for regeneration) secrets
-	Prune {
-		/// Secret to prune
-		name: String,
-
-		/// Machines to prune - if specified, only the choosen machines will be pruned
-		#[clap(short = 'm', long)]
-		machine: Vec<String>,
-	},
-	/// Ensure secret is generated and not expired
-	Ensure {
-		/// Secret to ensure generated
-		name: String,
-
-		/// Machines to force secret for
-		#[clap(short = 'm', long)]
-		machine: Vec<String>,
-	},
-	List {},
-	Edit {
-		name: String,
-		#[clap(short = 'm', long)]
-		machine: String,
-
-		#[clap(long)]
-		add: bool,
-
-		/// Which private secret part to read
-		#[clap(short = 'p', long, default_value = "secret")]
-		part: String,
-	},
-}
-
-async fn parse_public(
-	public: Option<String>,
-	public_file: Option<PathBuf>,
-) -> Result<Option<SecretData>> {
-	Ok(match (public, public_file) {
-		(Some(v), None) => Some(SecretData {
-			data: v.into(),
-			encrypted: false,
-		}),
-		(None, Some(v)) => Some(SecretData {
-			data: read(v).await?,
-			encrypted: false,
-		}),
-		(Some(_), Some(_)) => {
-			bail!("only public or public_file should be set")
-		}
-		(None, None) => None,
-	})
-}
-
-async fn parse_secret() -> Result<Option<Vec<u8>>> {
-	let mut input = vec![];
-	stdin().read_to_end(&mut input)?;
-	if input.is_empty() {
-		Ok(None)
-	} else {
-		Ok(Some(input))
-	}
-}
-
-fn parse_machines(
-	initial: BTreeSet<String>,
-	machines: Option<Vec<String>>,
-	mut add_machines: Vec<String>,
-	mut remove_machines: Vec<String>,
-) -> Result<BTreeSet<String>> {
-	if machines.is_none() && add_machines.is_empty() && remove_machines.is_empty() {
-		bail!("no operation");
-	}
-
-	let initial_machines = initial.clone();
-	let mut target_machines = initial;
-	info!("Currently encrypted for {initial_machines:?}");
-
-	if let Some(machines) = machines {
-		ensure!(
-			add_machines.is_empty() && remove_machines.is_empty(),
-			"can't combine --machines and --add-machines/--remove-machines"
-		);
-		let target = initial_machines.iter().collect::<HashSet<_>>();
-		let source = machines.iter().collect::<HashSet<_>>();
-		for removed in target.difference(&source) {
-			remove_machines.push((*removed).clone());
-		}
-		for added in source.difference(&target) {
-			add_machines.push((*added).clone());
-		}
-	}
-
-	for machine in &remove_machines {
-		if !target_machines.remove(machine) {
-			warn!("secret is not enabled for {machine}");
-		}
-	}
-	for machine in &add_machines {
-		if !target_machines.insert(machine.to_owned()) {
-			warn!("secret is already added to {machine}");
-		}
-	}
-	if !remove_machines.is_empty() {
-		// TODO: maybe force secret regeneration?
-		// Not that useful without revokation.
-		warn!(
-			"secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret"
-		);
-	}
-	Ok(target_machines)
-}
-impl Secret {
-	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {
-		match self {
-			Secret::ForceKeys => {
-				for host in config.list_hosts()? {
-					if opts.should_skip(&host)? {
-						continue;
-					}
-					config.host_key(&host.name).await?;
-				}
-			}
-			Secret::Read {
-				name,
-				machine,
-				part: part_name,
-				mut prefer_identities,
-			} => {
-				/*
-				let Some(secret) = config.shared_secret(&name) else {
-					bail!("secret doesn't exists");
-				};
-
-				let dist = if secret.len() == 1 {
-					&secret[0]
-				} else if let Some(machine) = machine {
-					let dist = secret.get(&machine);
-					let Some(dist) = dist else {
-						bail!("machine {machine} has no distribution of secret {name}");
-					};
-					prefer_identities.push(machine);
-					dist
-				} else {
-					bail!(
-						"secret {name} has shares, but no --machine specified for specifing which do you need"
-					)
-				};
-
-				let Some(part) = dist.secret.parts.get(&part_name) else {
-					bail!("no part {part_name} in secret {name}");
-				};
-				let data = if part.raw.encrypted {
-					let identity_holder = if !prefer_identities.is_empty() {
-						prefer_identities
-							.iter()
-							.find(|i| dist.owners.iter().any(|s| s == *i))
-					} else {
-						dist.owners.first()
-					};
-					let Some(identity_holder) = identity_holder else {
-						bail!("no available holder found");
-					};
-					let host = config.host(identity_holder)?;
-					host.decrypt(part.raw.clone()).await?
-				} else {
-					part.raw.data.clone()
-				};
-				stdout().write_all(&data)?;
-				*/
-				todo!()
-			}
-			Secret::List {} => {
-				/*
-				let _span = info_span!("loading secrets").entered();
-				let configured = config.list_configured_shared()?;
-				#[derive(Tabled)]
-				struct SecretDisplay {
-					#[tabled(rename = "Name")]
-					name: String,
-					#[tabled(rename = "Owners")]
-					owners: String,
-				}
-				// let mut table = vec![];
-				for name in configured.iter().cloned() {
-					let config = config.clone();
-					let data = config.shared_secret(&name).expect("exists");
-					/*
-										let definition = config.shared_secret_definition(&name)?;
-										let expectations = definition.expectations()?;
-										let owners = data
-											.owners()
-											.map(|o| {
-												if expectations.owners.contains(o) {
-													o.green().to_string()
-												} else {
-													o.red().to_string()
-												}
-											})
-											.collect::<Vec<_>>();
-										table.push(SecretDisplay {
-											owners: owners.join(", "),
-											name,
-										})
-					*/
-				}
-				// info!("loaded\n{}", Table::new(table).to_string())
-				*/
-				todo!()
-			}
-			Secret::Edit {
-				name,
-				machine,
-				part,
-				add,
-			} => {
-				/*let secret = config
-					.host_secret(&machine, &name)
-					.context("secret not found")?;
-				if let Some(data) = secret.secret.parts.get(&part) {
-					let host = config.host(&machine)?;
-					let secret = host.decrypt(data.raw.clone()).await?;
-					String::from_utf8(secret).context("secret is not utf8")?
-				} else if add {
-					String::new()
-				} else {
-					bail!("part {part} not found in secret {name}. Did you mean to `--add` it?");
-				};*/
-				todo!()
-			}
-			Secret::Prune { name, machine } => todo!(),
-			Secret::Ensure { name, machine } => todo!(),
-		}
-		Ok(())
-	}
-}
-
-/*
-async fn edit_temp_file(
-	builder: tempfile::Builder<'_, '_>,
-	r: Vec<u8>,
-	header: &str,
-	comment: &str,
-) -> Result<(Vec<u8>, Option<String>), anyhow::Error> {
-	if !stdin().is_tty() {
-		// TODO: Also try to open /dev/tty directly?
-		bail!("stdin is not tty, can't open editor");
-	}
-
-	use std::fmt::Write;
-	let mut file = builder.tempfile()?;
-
-	let mut full_header = String::new();
-	let mut had = false;
-	for line in header.trim_end().lines() {
-		had = true;
-		writeln!(&mut full_header, "{comment}{line}")?;
-	}
-	if had {
-		writeln!(&mut full_header, "{}", comment.trim_end())?;
-	}
-	writeln!(
-		&mut full_header,
-		"{comment}Do not touch this header! It will be removed automatically"
-	)?;
-
-	file.write_all(full_header.as_bytes())?;
-	file.write_all(&r)?;
-
-	let abs_path = file.into_temp_path();
-	let editor = std::env::var_os("VISUAL")
-		.or_else(|| std::env::var_os("EDITOR"))
-		.unwrap_or_else(|| "vi".into());
-	let editor_args = shlex::bytes::split(editor.as_encoded_bytes())
-		.ok_or_else(|| anyhow!("EDITOR env var has wrong syntax"))?;
-	let editor_args = editor_args
-		.into_iter()
-		.map(|v| {
-			// Only ASCII subsequences are replaced
-			unsafe { OsString::from_encoded_bytes_unchecked(v) }
-		})
-		.collect_vec();
-	let Some((editor, args)) = editor_args.split_first() else {
-		bail!("EDITOR env var has no command");
-	};
-	let mut command = Command::new(editor);
-	command.args(args);
-
-	let path_arg = abs_path.canonicalize()?;
-
-	// TODO: Save full state, using tcget/_getmode/_setmode
-	let was_raw = terminal::is_raw_mode_enabled()?;
-	terminal::enable_raw_mode()?;
-
-	let status = command.arg(path_arg).status().await;
-
-	if !was_raw {
-		terminal::disable_raw_mode()?;
-	}
-
-	let success = match status {
-		Ok(s) => s.success(),
-		Err(e) if e.kind() == io::ErrorKind::NotFound => {
-			bail!("editor not found")
-		}
-		Err(e) => bail!("editor spawn error: {e}"),
-	};
-
-	let mut file = std::fs::read(&abs_path).context("read editor output")?;
-	let Some(v) = file.strip_prefix(full_header.as_bytes()) else {
-		todo!();
-	};
-	todo!();
-
-	// Ok((success, abs_path))
-}
-*/