git.delta.rocks / jrsonnet / refs/commits / e862c17d93fb

difftreelog

feat support secrets without secret data

Yaroslav Bolyukin2021-11-13parent: #4ab80d6.patch.diff
in: trunk

4 files changed

modifiedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
before · cmds/fleet/src/cmds/secrets/mod.rs
1use crate::{2	fleetdata::{FleetSecret, FleetSharedSecret},3	host::Config,4};5use anyhow::{bail, Result};6use std::{7	io::{self, Cursor, Read},8	path::PathBuf,9};10use structopt::StructOpt;1112#[derive(StructOpt)]13pub enum Secrets {14	/// Force load keys for all defined hosts15	ForceKeys,16	/// Add secret, data should be provided in stdin17	AddShared {18		/// Secret name19		name: String,20		/// Secret owners21		machines: Vec<String>,22		/// Override secret if already present23		#[structopt(long)]24		force: bool,25		#[structopt(long)]26		public: Option<String>,27		#[structopt(long)]28		public_file: Option<PathBuf>,29	},30	/// Add secret, data should be provided in stdin31	Add {32		/// Secret name33		name: String,34		/// Secret owners35		machine: String,36		/// Override secret if already present37		#[structopt(long)]38		force: bool,39		#[structopt(long)]40		public: Option<String>,41		#[structopt(long)]42		public_file: Option<PathBuf>,43	},44}4546impl Secrets {47	pub fn run(self, config: &Config) -> Result<()> {48		match self {49			Secrets::ForceKeys => {50				for host in config.list_hosts()? {51					if config.should_skip(&host) {52						continue;53					}54					config.key(&host)?;55				}56			}57			Secrets::AddShared {58				machines,59				name,60				force,61				public,62				public_file,63			} => {64				let recipients = machines65					.iter()66					.map(|m| config.recipient(m))67					.collect::<Result<Vec<_>>>()?;6869				let secret = {70					let mut input = vec![];71					io::stdin().read_to_end(&mut input)?;7273					let mut encrypted = vec![];74					let recipients = recipients75						.iter()76						.cloned()77						.map(|r| Box::new(r) as Box<dyn age::Recipient>)78						.collect();79					let mut encryptor =80						age::Encryptor::with_recipients(recipients).wrap_output(&mut encrypted)?;81					io::copy(&mut Cursor::new(input), &mut encryptor)?;82					encryptor.finish()?;83					encrypted84				};8586				let mut data = config.data_mut();87				if data.shared_secrets.contains_key(&name) && !force {88					bail!("secret already defined");89				}90				data.shared_secrets.insert(91					name,92					FleetSharedSecret {93						owners: machines,94						secret: FleetSecret {95							expire_at: None,96							secret,97							public: match (public, public_file) {98								(Some(v), None) => Some(v),99								(None, Some(v)) => Some(std::fs::read_to_string(&v)?),100								(Some(_), Some(_)) => {101									bail!("only public or public_file should be set")102								}103								(None, None) => None,104							},105						},106					},107				);108			}109			Secrets::Add {110				machine,111				name,112				force,113				public,114				public_file,115			} => {116				let recipient = config.recipient(&machine)?;117118				let secret = {119					let mut input = vec![];120					io::stdin().read_to_end(&mut input)?;121122					let mut encrypted = vec![];123					let recipient = Box::new(recipient) as Box<dyn age::Recipient>;124					let mut encryptor = age::Encryptor::with_recipients(vec![recipient])125						.wrap_output(&mut encrypted)?;126					io::copy(&mut Cursor::new(input), &mut encryptor)?;127					encryptor.finish()?;128					encrypted129				};130131				let mut data = config.data_mut();132				let host_secrets = data.host_secrets.entry(machine).or_default();133				if host_secrets.contains_key(&name) && !force {134					bail!("secret already defined");135				}136				host_secrets.insert(137					name,138					FleetSecret {139						expire_at: None,140						secret,141						public: match (public, public_file) {142							(Some(v), None) => Some(v),143							(None, Some(v)) => Some(std::fs::read_to_string(&v)?),144							(Some(_), Some(_)) => bail!("only public or public_file should be set"),145							(None, None) => None,146						},147					},148				);149			}150		}151		Ok(())152	}153}
after · cmds/fleet/src/cmds/secrets/mod.rs
1use crate::{2	fleetdata::{FleetSecret, FleetSharedSecret},3	host::Config,4};5use anyhow::{bail, Result};6use std::{7	io::{self, Cursor, Read},8	path::PathBuf,9};10use structopt::StructOpt;1112#[derive(StructOpt)]13pub enum Secrets {14	/// Force load keys for all defined hosts15	ForceKeys,16	/// Add secret, data should be provided in stdin17	AddShared {18		/// Secret name19		name: String,20		/// Secret owners21		machines: Vec<String>,22		/// Override secret if already present23		#[structopt(long)]24		force: bool,25		#[structopt(long)]26		public: Option<String>,27		#[structopt(long)]28		public_file: Option<PathBuf>,29	},30	/// Add secret, data should be provided in stdin31	Add {32		/// Secret name33		name: String,34		/// Secret owners35		machine: String,36		/// Override secret if already present37		#[structopt(long)]38		force: bool,39		#[structopt(long)]40		public: Option<String>,41		#[structopt(long)]42		public_file: Option<PathBuf>,43	},44}4546impl Secrets {47	pub fn run(self, config: &Config) -> Result<()> {48		match self {49			Secrets::ForceKeys => {50				for host in config.list_hosts()? {51					if config.should_skip(&host) {52						continue;53					}54					config.key(&host)?;55				}56			}57			Secrets::AddShared {58				machines,59				name,60				force,61				public,62				public_file,63			} => {64				let recipients = machines65					.iter()66					.map(|m| config.recipient(m))67					.collect::<Result<Vec<_>>>()?;6869				let secret = {70					let mut input = vec![];71					io::stdin().read_to_end(&mut input)?;7273					if input.is_empty() {74						input75					} else {76						let mut encrypted = vec![];77						let recipients = recipients78							.iter()79							.cloned()80							.map(|r| Box::new(r) as Box<dyn age::Recipient>)81							.collect();82						let mut encryptor = age::Encryptor::with_recipients(recipients)83							.wrap_output(&mut encrypted)?;84						io::copy(&mut Cursor::new(input), &mut encryptor)?;85						encryptor.finish()?;86						encrypted87					}88				};8990				let mut data = config.data_mut();91				if data.shared_secrets.contains_key(&name) && !force {92					bail!("secret already defined");93				}94				data.shared_secrets.insert(95					name,96					FleetSharedSecret {97						owners: machines,98						secret: FleetSecret {99							expire_at: None,100							secret,101							public: match (public, public_file) {102								(Some(v), None) => Some(v),103								(None, Some(v)) => Some(std::fs::read_to_string(&v)?),104								(Some(_), Some(_)) => {105									bail!("only public or public_file should be set")106								}107								(None, None) => None,108							},109						},110					},111				);112			}113			Secrets::Add {114				machine,115				name,116				force,117				public,118				public_file,119			} => {120				let recipient = config.recipient(&machine)?;121122				let secret = {123					let mut input = vec![];124					io::stdin().read_to_end(&mut input)?;125126					let mut encrypted = vec![];127					let recipient = Box::new(recipient) as Box<dyn age::Recipient>;128					let mut encryptor = age::Encryptor::with_recipients(vec![recipient])129						.wrap_output(&mut encrypted)?;130					io::copy(&mut Cursor::new(input), &mut encryptor)?;131					encryptor.finish()?;132					encrypted133				};134135				let mut data = config.data_mut();136				let host_secrets = data.host_secrets.entry(machine).or_default();137				if host_secrets.contains_key(&name) && !force {138					bail!("secret already defined");139				}140				host_secrets.insert(141					name,142					FleetSecret {143						expire_at: None,144						secret,145						public: match (public, public_file) {146							(Some(v), None) => Some(v),147							(None, Some(v)) => Some(std::fs::read_to_string(&v)?),148							(Some(_), Some(_)) => bail!("only public or public_file should be set"),149							(None, None) => None,150						},151					},152				);153			}154		}155		Ok(())156	}157}
modifiedcmds/fleet/src/fleetdata.rsdiffbeforeafterboth
--- a/cmds/fleet/src/fleetdata.rs
+++ b/cmds/fleet/src/fleetdata.rs
@@ -39,7 +39,12 @@
 	pub expire_at: Option<DateTime<Utc>>,
 	#[serde(skip_serializing_if = "Option::is_none")]
 	pub public: Option<String>,
-	#[serde(serialize_with = "as_z85", deserialize_with = "from_z85")]
+	#[serde(
+		default,
+		skip_serializing_if = "Vec::is_empty",
+		serialize_with = "as_z85",
+		deserialize_with = "from_z85"
+	)]
 	pub secret: Vec<u8>,
 }
 
modifiedcmds/install-secrets/src/main.rsdiffbeforeafterboth
--- a/cmds/install-secrets/src/main.rs
+++ b/cmds/install-secrets/src/main.rs
@@ -29,16 +29,21 @@
 	mode: String,
 	owner: String,
 	#[serde(deserialize_with = "from_z85")]
-	secret: Vec<u8>,
+	secret: Option<Vec<u8>>,
 }
 
-fn from_z85<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
+fn from_z85<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
 where
 	D: Deserializer<'de>,
 {
 	use serde::de::Error;
-	String::deserialize(deserializer)
-		.and_then(|string| z85::decode(&string).map_err(|err| Error::custom(err.to_string())))
+	if let Some(v) = <Option<String>>::deserialize(deserializer)? {
+		Ok(Some(
+			z85::decode(&v).map_err(|err| Error::custom(err.to_string()))?,
+		))
+	} else {
+		Ok(None)
+	}
 }
 
 type Data = HashMap<String, DataItem>;
@@ -49,6 +54,11 @@
 	name: &str,
 	value: DataItem,
 ) -> Result<()> {
+	if value.secret.is_none() {
+		return Ok(());
+	}
+	let secret = value.secret.as_ref().unwrap();
+
 	let mut path = dir.to_path_buf();
 	path.push(name);
 	if path.strip_prefix(&dir).is_err() {
@@ -88,7 +98,7 @@
 	// File is owned by root, and only root can modify it
 
 	let decrypted = {
-		let mut input = Cursor::new(&value.secret);
+		let mut input = Cursor::new(&secret);
 		let decryptor = Decryptor::new(&mut input).context("failed to init decryptor")?;
 		let decryptor = match decryptor {
 			Decryptor::Recipients(r) => r,
modifiedmodules/nixos/secrets.nixdiffbeforeafterboth
--- a/modules/nixos/secrets.nix
+++ b/modules/nixos/secrets.nix
@@ -3,7 +3,9 @@
   sysConfig = config;
   secretType = types.submodule ({ config, ... }: {
     config = {
-      path = mkOptionDefault "/run/secrets/${config._module.args.name}";
+      path = mkOptionDefault (if config.secret == null then (error "secret is not set") else "/run/secrets/${config._module.args.name}");
+      publicPath = mkOptionDefault (pkgs.writeText "pub-${config._module.args.name}" config.public);
+      secret = mkIf (config.public != null) "";
     };
     options = {
       public = mkOption {
@@ -12,7 +14,7 @@
         default = null;
       };
       secret = mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
         description = "Encrypted secret data";
       };
       mode = mkOption {
@@ -36,6 +38,11 @@
         readOnly = true;
         description = "Path to the decrypted secret";
       };
+      publicPath = mkOption {
+        type = types.package;
+        readOnly = true;
+        description = "Path to the public part of secret";
+      };
     };
   });
   secretsFile = pkgs.writeTextFile {