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
--- a/cmds/fleet/src/cmds/secrets/mod.rs
+++ b/cmds/fleet/src/cmds/secrets/mod.rs
@@ -70,17 +70,21 @@
 					let mut input = vec![];
 					io::stdin().read_to_end(&mut input)?;
 
-					let mut encrypted = vec![];
-					let recipients = recipients
-						.iter()
-						.cloned()
-						.map(|r| Box::new(r) as Box<dyn age::Recipient>)
-						.collect();
-					let mut encryptor =
-						age::Encryptor::with_recipients(recipients).wrap_output(&mut encrypted)?;
-					io::copy(&mut Cursor::new(input), &mut encryptor)?;
-					encryptor.finish()?;
-					encrypted
+					if input.is_empty() {
+						input
+					} else {
+						let mut encrypted = vec![];
+						let recipients = recipients
+							.iter()
+							.cloned()
+							.map(|r| Box::new(r) as Box<dyn age::Recipient>)
+							.collect();
+						let mut encryptor = age::Encryptor::with_recipients(recipients)
+							.wrap_output(&mut encrypted)?;
+						io::copy(&mut Cursor::new(input), &mut encryptor)?;
+						encryptor.finish()?;
+						encrypted
+					}
 				};
 
 				let mut data = config.data_mut();
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
before · cmds/install-secrets/src/main.rs
1use age::Decryptor;2use anyhow::{anyhow, bail, Context, Result};3use log::{error, warn};4use nix::fcntl::{renameat2, RenameFlags};5use nix::sys::stat::Mode;6use nix::unistd::{chown, Group, User};7use serde::{Deserialize, Deserializer};8use std::fs::{self, DirBuilder};9use std::io::{self, Cursor, Read};10use std::iter;11use std::os::unix::prelude::PermissionsExt;12use std::str::from_utf8;13use std::{14	collections::HashMap,15	os::unix::fs::DirBuilderExt,16	path::{Path, PathBuf},17};18use structopt::StructOpt;1920#[derive(StructOpt)]21#[structopt(author)]22struct Opts {23	data: PathBuf,24}2526#[derive(Deserialize)]27struct DataItem {28	group: String,29	mode: String,30	owner: String,31	#[serde(deserialize_with = "from_z85")]32	secret: Vec<u8>,33}3435fn from_z85<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>36where37	D: Deserializer<'de>,38{39	use serde::de::Error;40	String::deserialize(deserializer)41		.and_then(|string| z85::decode(&string).map_err(|err| Error::custom(err.to_string())))42}4344type Data = HashMap<String, DataItem>;4546fn init_secret(47	identity: &age::ssh::Identity,48	dir: &Path,49	name: &str,50	value: DataItem,51) -> Result<()> {52	let mut path = dir.to_path_buf();53	path.push(name);54	if path.strip_prefix(&dir).is_err() {55		bail!("found escaping name");56	}5758	let secret_dir = path59		.parent()60		.expect("path is in tempdir, so it should have parent");6162	if secret_dir != dir {63		DirBuilder::new()64			.recursive(true)65			// o: xrw66			// g: xr67			// a: xr68			.mode(0o755)69			.create(70				path.parent()71					.expect("path is in tempdir, so it should have parent"),72			)73			.context("failed to create secret directory")?;74	}7576	let mode = Mode::from_bits(77		u32::from_str_radix(&value.mode, 8).context("failed to parse mode as octal")?,78	)79	.context("failed to parse mode")?;80	let user = User::from_name(&value.owner)81		.context("failed to get user")?82		.ok_or_else(|| anyhow!("user not found"))?;83	let group = Group::from_name(&value.group)84		.context("failed to get group")?85		.ok_or_else(|| anyhow!("group not found"))?;86	let mut tempfile =87		tempfile::NamedTempFile::new_in(secret_dir).context("failed to create tempfile")?;88	// File is owned by root, and only root can modify it8990	let decrypted = {91		let mut input = Cursor::new(&value.secret);92		let decryptor = Decryptor::new(&mut input).context("failed to init decryptor")?;93		let decryptor = match decryptor {94			Decryptor::Recipients(r) => r,95			Decryptor::Passphrase(_) => bail!("should be recipients"),96		};97		let mut decryptor = decryptor98			.decrypt(iter::once(identity as &dyn age::Identity))99			.context("failed to decrypt, wrong key?")?;100101		let mut decrypted = Vec::new();102		decryptor103			.read_to_end(&mut decrypted)104			.context("failed to decrypt")?;105		decrypted106	};107108	io::copy(&mut Cursor::new(decrypted), &mut tempfile)109		.context("failed to write decrypted file")?;110111	// Make file owned by specified user and group, then change mode112	chown(tempfile.path(), Some(user.uid), Some(group.gid))113		.context("failed to apply user/group")?;114	fs::set_permissions(tempfile.path(), fs::Permissions::from_mode(mode.bits())).unwrap();115	tempfile.persist(path).context("failed to persist")?;116117	Ok(())118}119120fn main() -> anyhow::Result<()> {121	env_logger::Builder::new()122		.filter_level(log::LevelFilter::Info)123		.init();124125	let opts = Opts::from_args();126	let data = fs::read(&opts.data).context("failed to read secrets data")?;127	let data_str = from_utf8(&data).context("failed to read data to string")?;128	let data: Data = serde_json::from_str(data_str).context("failed to parse data")?;129130	let tempdir = tempfile::tempdir_in("/run/").context("failed to create secrets tempdir")?;131132	let identity = age::ssh::Identity::from_buffer(133		&mut Cursor::new(134			fs::read("/etc/ssh/ssh_host_ed25519_key").context("failed to read host private key")?,135		),136		None,137	)138	.context("failed to parse identity")?;139140	let mut failed = false;141	for (name, value) in data {142		if let Err(e) = init_secret(&identity, tempdir.path(), &name, value) {143			error!(144				"{:?}",145				e.context(format!("failed to initialize secret {}", name))146			);147			failed = true;148		}149	}150	if failed {151		bail!("one or more secrets failed");152	}153154	if fs::metadata("/run/secrets")155		.map(|m| m.is_dir())156		.unwrap_or(false)157	{158		// Already linked159		renameat2(160			None,161			tempdir.path(),162			None,163			"/run/secrets",164			RenameFlags::RENAME_EXCHANGE,165		)166		.context("failed to exchange secret directories")?;167		if tempdir.close().is_err() {168			warn!("failed to unlink old secrets");169		}170	} else {171		// Link now172		let persisted = tempdir.into_path();173		fs::rename(&persisted, "/run/secrets").context("failed to link secret directory")?;174	}175	Ok(())176}
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 {