git.delta.rocks / jrsonnet / refs/commits / 7a2e42ede362

difftreelog

refactor implement new secret storage schema

Yaroslav Bolyukin2022-08-31parent: #590ae3f.patch.diff
in: trunk

5 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13,34 +13,34 @@
 
 [[package]]
 name = "aes"
-version = "0.7.5"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
+checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba"
 dependencies = [
  "cfg-if",
- "cipher",
+ "cipher 0.4.3",
  "cpufeatures",
- "ctr",
- "opaque-debug",
 ]
 
 [[package]]
 name = "age"
-version = "0.7.1"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23100453ca2a1bbda9bfc6deac1bebb828d7e66ba481ebccfedfddf29321b6b9"
+checksum = "f066ce1514d24201eab31e0831e9333d2e9b06d698b25f705ef0697fee8256a2"
 dependencies = [
  "aes",
  "age-core",
  "base64",
  "bcrypt-pbkdf",
  "bech32",
- "block-modes",
+ "cbc",
  "chacha20poly1305",
+ "cipher 0.4.3",
  "cookie-factory",
+ "ctr",
  "curve25519-dalek",
  "hkdf",
- "hmac 0.11.0",
+ "hmac",
  "i18n-embed",
  "i18n-embed-fl",
  "lazy_static",
@@ -52,6 +52,7 @@
  "rsa",
  "rust-embed",
  "scrypt",
+ "sha2 0.10.3",
  "sha2 0.9.9",
  "subtle",
  "x25519-dalek",
@@ -60,18 +61,19 @@
 
 [[package]]
 name = "age-core"
-version = "0.7.1"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70afa630ef12a4fc666277713efbe6da2bc87bb3f3af0f1149415b701362c615"
+checksum = "00a5c8d8a33abc74ad393896a6305351dd159d0e184788f4729e3c80e397fa45"
 dependencies = [
  "base64",
  "chacha20poly1305",
  "cookie-factory",
  "hkdf",
+ "io_tee",
  "nom",
  "rand 0.8.5",
  "secrecy",
- "sha2 0.9.9",
+ "sha2 0.10.3",
 ]
 
 [[package]]
@@ -149,14 +151,13 @@
 
 [[package]]
 name = "bcrypt-pbkdf"
-version = "0.7.2"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bde65b3c84000288c0abe8aa601a4b7c40b0dbbb7d144dd6c712ed9796e1fd5"
+checksum = "f4ef233ffa9cb9c7820b2b0e9efd0821ed180e866c9120ec9f45518659742074"
 dependencies = [
  "blowfish",
- "hex-literal",
  "pbkdf2",
- "sha2 0.10.1",
+ "sha2 0.10.3",
 ]
 
 [[package]]
@@ -190,30 +191,22 @@
 ]
 
 [[package]]
-name = "block-modes"
-version = "0.8.1"
+name = "block-padding"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
+checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78"
 dependencies = [
- "block-padding",
- "cipher",
+ "generic-array",
 ]
 
 [[package]]
-name = "block-padding"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
-
-[[package]]
 name = "blowfish"
-version = "0.8.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe3ff3fc1de48c1ac2e3341c4df38b0d1bfb8fdf04632a187c8b75aaa319a7ab"
+checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
 dependencies = [
  "byteorder",
- "cipher",
- "opaque-debug",
+ "cipher 0.4.3",
 ]
 
 [[package]]
@@ -235,10 +228,13 @@
 checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
 
 [[package]]
-name = "cc"
-version = "1.0.73"
+name = "cbc"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher 0.4.3",
+]
 
 [[package]]
 name = "cfg-if"
@@ -253,7 +249,7 @@
 checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91"
 dependencies = [
  "cfg-if",
- "cipher",
+ "cipher 0.3.0",
  "cpufeatures",
  "zeroize",
 ]
@@ -266,7 +262,7 @@
 dependencies = [
  "aead",
  "chacha20",
- "cipher",
+ "cipher 0.3.0",
  "poly1305",
  "zeroize",
 ]
@@ -295,6 +291,16 @@
 ]
 
 [[package]]
+name = "cipher"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
 name = "clap"
 version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -366,25 +372,15 @@
 dependencies = [
  "generic-array",
  "typenum",
-]
-
-[[package]]
-name = "crypto-mac"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
-dependencies = [
- "generic-array",
- "subtle",
 ]
 
 [[package]]
 name = "ctr"
-version = "0.8.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
+checksum = "0d14f329cfbaf5d0e06b5e87fff7e265d2673c5ea7d2c27691a2c107db1442a0"
 dependencies = [
- "cipher",
+ "cipher 0.4.3",
 ]
 
 [[package]]
@@ -713,38 +709,21 @@
 dependencies = [
  "libc",
 ]
-
-[[package]]
-name = "hex-literal"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
 
 [[package]]
 name = "hkdf"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
-dependencies = [
- "digest 0.9.0",
- "hmac 0.11.0",
-]
-
-[[package]]
-name = "hmac"
-version = "0.11.0"
+version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
 dependencies = [
- "crypto-mac",
- "digest 0.9.0",
+ "hmac",
 ]
 
 [[package]]
 name = "hmac"
-version = "0.12.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
  "digest 0.10.3",
 ]
@@ -846,6 +825,16 @@
 ]
 
 [[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
+
+[[package]]
 name = "instant"
 version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -875,6 +864,12 @@
 ]
 
 [[package]]
+name = "io_tee"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304"
+
+[[package]]
 name = "itoa"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -891,9 +886,9 @@
 
 [[package]]
 name = "libc"
-version = "0.2.118"
+version = "0.2.132"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94"
+checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
 
 [[package]]
 name = "libm"
@@ -985,15 +980,16 @@
 
 [[package]]
 name = "nix"
-version = "0.23.1"
+version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
+checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
 dependencies = [
+ "autocfg 1.1.0",
  "bitflags",
- "cc",
  "cfg-if",
  "libc",
  "memoffset",
+ "pin-utils",
 ]
 
 [[package]]
@@ -1141,9 +1137,9 @@
 
 [[package]]
 name = "pbkdf2"
-version = "0.10.0"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4628cc3cf953b82edcd3c1388c5715401420ce5524fedbab426bd5aba017434"
+checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7"
 dependencies = [
  "digest 0.10.3",
 ]
@@ -1482,11 +1478,11 @@
 
 [[package]]
 name = "salsa20"
-version = "0.9.0"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c0fbb5f676da676c260ba276a8f43a8dc67cf02d1438423aeb1c677a7212686"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
 dependencies = [
- "cipher",
+ "cipher 0.4.3",
 ]
 
 [[package]]
@@ -1506,14 +1502,14 @@
 
 [[package]]
 name = "scrypt"
-version = "0.8.1"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e73d6d7c6311ebdbd9184ad6c4447b2f36337e327bda107d3ba9e3c374f9d325"
+checksum = "ba0aaf3911fff0d942c10a49779de7754699810fc7dbe3df515613b2ecc8195a"
 dependencies = [
- "hmac 0.12.0",
+ "hmac",
  "pbkdf2",
  "salsa20",
- "sha2 0.10.1",
+ "sha2 0.10.3",
 ]
 
 [[package]]
@@ -1577,9 +1573,9 @@
 
 [[package]]
 name = "sha2"
-version = "0.10.1"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
+checksum = "899bf02746a2c92bf1053d9327dadb252b01af1f81f90cdb902411f518bc7215"
 dependencies = [
  "cfg-if",
  "cpufeatures",
modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
--- a/cmds/fleet/Cargo.toml
+++ b/cmds/fleet/Cargo.toml
@@ -13,14 +13,19 @@
 tempfile = "3.2"
 once_cell = "1.5"
 hostname = "0.3.1"
-age-core = "0.7.0"
+age-core = "0.8.0"
 peg = "0.8.0"
-nixlike = {path = "../../crates/nixlike"}
-age = { version = "0.7.0", features = ["ssh", "armor"] }
+nixlike = { path = "../../crates/nixlike" }
+age = { version = "0.8.1", features = ["ssh", "armor"] }
 base64 = "0.13.0"
 chrono = { version = "0.4.19", features = ["serde"] }
 z85 = "3.0.3"
-clap = { version = "3.1.0", features = ["derive", "env", "wrap_help", "unicode"] }
+clap = { version = "3.1.0", features = [
+	"derive",
+	"env",
+	"wrap_help",
+	"unicode",
+] }
 tokio = { version = "1.14.0", features = ["full"] }
 tracing = "0.1.29"
 tracing-subscriber = { version = "0.3.3", features = ["fmt", "env-filter"] }
modifiedcmds/install-secrets/Cargo.tomldiffbeforeafterboth
--- a/cmds/install-secrets/Cargo.toml
+++ b/cmds/install-secrets/Cargo.toml
@@ -4,13 +4,18 @@
 edition = "2021"
 
 [dependencies]
-age = { version = "0.7.1", features = ["ssh"] }
+age = { version = "0.8.1", features = ["ssh"] }
 anyhow = "1.0.44"
 env_logger = "0.9.0"
 log = "0.4.14"
-nix = "0.23.1"
+nix = "0.25.0"
 serde = "1.0.130"
 serde_json = "1.0.68"
-clap = { version = "3.1.0", features = ["derive", "env", "wrap_help", "unicode"] }
+clap = { version = "3.1.0", features = [
+	"derive",
+	"env",
+	"wrap_help",
+	"unicode",
+] }
 tempfile = "3.2.0"
 z85 = "3.0.3"
modifiedcmds/install-secrets/src/main.rsdiffbeforeafterboth
before · cmds/install-secrets/src/main.rs
1use age::Decryptor;2use anyhow::{anyhow, bail, Context, Result};3use clap::Parser;4use log::{error, warn};5use nix::fcntl::{renameat2, RenameFlags};6use nix::sys::stat::Mode;7use nix::unistd::{chown, Group, User};8use serde::{Deserialize, Deserializer};9use std::fs::{self, DirBuilder};10use std::io::{self, Cursor, Read};11use std::iter;12use std::os::unix::prelude::PermissionsExt;13use std::str::from_utf8;14use std::{15	collections::HashMap,16	os::unix::fs::DirBuilderExt,17	path::{Path, PathBuf},18};1920#[derive(Parser)]21#[clap(author)]22struct Opts {23	data: PathBuf,24}2526#[derive(Deserialize)]27#[serde(rename_all = "camelCase")]28struct DataItem {29	group: String,30	mode: String,31	owner: String,3233	#[serde(deserialize_with = "from_z85")]34	secret: Option<Vec<u8>>,35	public: String,3637	secret_hash: String,38	public_path: String,39}4041fn from_z85<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>42where43	D: Deserializer<'de>,44{45	use serde::de::Error;46	if let Some(v) = <Option<String>>::deserialize(deserializer)? {47		Ok(Some(48			z85::decode(&v).map_err(|err| Error::custom(err.to_string()))?,49		))50	} else {51		Ok(None)52	}53}5455type Data = HashMap<String, DataItem>;5657fn init_secret(58	identity: &age::ssh::Identity,59	dir: &Path,60	name: &str,61	value: DataItem,62) -> Result<()> {63	if value.secret.is_none() {64		return Ok(());65	}66	let secret = value.secret.as_ref().unwrap();6768	let mut path = dir.to_path_buf();69	path.push(name);70	if path.strip_prefix(&dir).is_err() {71		bail!("found escaping name");72	}7374	let secret_dir = path75		.parent()76		.expect("path is in tempdir, so it should have parent");7778	if secret_dir != dir {79		DirBuilder::new()80			.recursive(true)81			// o: xrw82			// g: xr83			// a: xr84			.mode(0o755)85			.create(86				path.parent()87					.expect("path is in tempdir, so it should have parent"),88			)89			.context("failed to create secret directory")?;90	}9192	let mode = Mode::from_bits(93		u32::from_str_radix(&value.mode, 8).context("failed to parse mode as octal")?,94	)95	.context("failed to parse mode")?;96	let user = User::from_name(&value.owner)97		.context("failed to get user")?98		.ok_or_else(|| anyhow!("user not found"))?;99	let group = Group::from_name(&value.group)100		.context("failed to get group")?101		.ok_or_else(|| anyhow!("group not found"))?;102	let mut tempfile =103		tempfile::NamedTempFile::new_in(secret_dir).context("failed to create tempfile")?;104	// File is owned by root, and only root can modify it105106	let decrypted = {107		let mut input = Cursor::new(&secret);108		let decryptor = Decryptor::new(&mut input).context("failed to init decryptor")?;109		let decryptor = match decryptor {110			Decryptor::Recipients(r) => r,111			Decryptor::Passphrase(_) => bail!("should be recipients"),112		};113		let mut decryptor = decryptor114			.decrypt(iter::once(identity as &dyn age::Identity))115			.context("failed to decrypt, wrong key?")?;116117		let mut decrypted = Vec::new();118		decryptor119			.read_to_end(&mut decrypted)120			.context("failed to decrypt")?;121		decrypted122	};123124	io::copy(&mut Cursor::new(decrypted), &mut tempfile)125		.context("failed to write decrypted file")?;126127	// Make file owned by specified user and group, then change mode128	chown(tempfile.path(), Some(user.uid), Some(group.gid))129		.context("failed to apply user/group")?;130	fs::set_permissions(tempfile.path(), fs::Permissions::from_mode(mode.bits())).unwrap();131	tempfile.persist(path).context("failed to persist")?;132133	Ok(())134}135136fn main() -> anyhow::Result<()> {137	env_logger::Builder::new()138		.filter_level(log::LevelFilter::Info)139		.init();140141	let opts = Opts::parse();142	let data = fs::read(&opts.data).context("failed to read secrets data")?;143	let data_str = from_utf8(&data).context("failed to read data to string")?;144	let data: Data = serde_json::from_str(data_str).context("failed to parse data")?;145146	let tempdir = tempfile::tempdir_in("/run/").context("failed to create secrets tempdir")?;147148	let identity = age::ssh::Identity::from_buffer(149		&mut Cursor::new(150			fs::read("/etc/ssh/ssh_host_ed25519_key").context("failed to read host private key")?,151		),152		None,153	)154	.context("failed to parse identity")?;155156	let mut failed = false;157	for (name, value) in data {158		if let Err(e) = init_secret(&identity, tempdir.path(), &name, value) {159			error!(160				"{:?}",161				e.context(format!("failed to initialize secret {}", name))162			);163			failed = true;164		}165	}166	if failed {167		bail!("one or more secrets failed");168	}169170	if fs::metadata("/run/secrets")171		.map(|m| m.is_dir())172		.unwrap_or(false)173	{174		// Already linked175		renameat2(176			None,177			tempdir.path(),178			None,179			"/run/secrets",180			RenameFlags::RENAME_EXCHANGE,181		)182		.context("failed to exchange secret directories")?;183		if tempdir.close().is_err() {184			warn!("failed to unlink old secrets");185		}186	} else {187		// Link now188		let persisted = tempdir.into_path();189		fs::rename(&persisted, "/run/secrets").context("failed to link secret directory")?;190	}191	Ok(())192}
after · cmds/install-secrets/src/main.rs
1use age::Decryptor;2use anyhow::{anyhow, bail, Context, Result};3use clap::Parser;4use log::{error, warn};5use nix::fcntl::{renameat2, RenameFlags};6use nix::sys::stat::Mode;7use nix::unistd::{chown, Group, User};8use serde::{Deserialize, Deserializer};9use std::fs::{self, DirBuilder, File};10use std::io::{self, Cursor, Read, Write};11use std::iter;12use std::os::unix::prelude::PermissionsExt;13use std::str::from_utf8;14use std::{15	collections::HashMap,16	os::unix::fs::DirBuilderExt,17	path::{Path, PathBuf},18};1920#[derive(Parser)]21#[clap(author)]22struct Opts {23	data: PathBuf,24}2526#[derive(Deserialize)]27#[serde(rename_all = "camelCase")]28struct DataItem {29	group: String,30	mode: String,31	owner: String,3233	#[serde(deserialize_with = "from_z85")]34	secret: Option<Vec<u8>>,35	public: Option<String>,3637	public_path: PathBuf,38	stable_public_path: PathBuf,3940	secret_path: PathBuf,41	stable_secret_path: PathBuf,42}4344fn from_z85<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>45where46	D: Deserializer<'de>,47{48	use serde::de::Error;49	if let Some(v) = <Option<String>>::deserialize(deserializer)? {50		Ok(Some(51			z85::decode(&v).map_err(|err| Error::custom(err.to_string()))?,52		))53	} else {54		Ok(None)55	}56}5758type Data = HashMap<String, DataItem>;5960fn init_secret(identity: &age::ssh::Identity, value: DataItem) -> Result<()> {61	if let Some(public) = &value.public {62		let mut hashed = File::create(&value.public_path)?;63		let mut stable_dir = value.stable_public_path.parent().expect("not root");64		let mut stable_temp =65			tempfile::NamedTempFile::new_in(stable_dir).context("failed to create tempfile")?;66		hashed.write_all(public.as_bytes())?;67		stable_temp.write_all(public.as_bytes())?;68		stable_temp.flush()?;69		fs::set_permissions(stable_temp.path(), fs::Permissions::from_mode(0o444))70			.context("perm")?;71		fs::set_permissions(&value.public_path, fs::Permissions::from_mode(0o444))72			.context("perm")?;7374		stable_temp75			.persist(value.stable_public_path)76			.context("failed to persist")?;77	}78	if value.secret.is_none() {79		return Ok(());80	}81	let secret = value.secret.as_ref().unwrap();8283	let mode = Mode::from_bits(84		u32::from_str_radix(&value.mode, 8).context("failed to parse mode as octal")?,85	)86	.context("failed to parse mode")?;87	let user = User::from_name(&value.owner)88		.context("failed to get user")?89		.ok_or_else(|| anyhow!("user not found"))?;90	let group = Group::from_name(&value.group)91		.context("failed to get group")?92		.ok_or_else(|| anyhow!("group not found"))?;9394	let mut stable_dir = value.stable_secret_path.parent().expect("not root");95	let mut stable_temp =96		tempfile::NamedTempFile::new_in(stable_dir).context("failed to create tempfile")?;97	let mut hashed = File::create(&value.secret_path)?;9899	// File is owned by root, and only root can modify it100	let decrypted = {101		let mut input = Cursor::new(&secret);102		let decryptor = Decryptor::new(&mut input).context("failed to init decryptor")?;103		let decryptor = match decryptor {104			Decryptor::Recipients(r) => r,105			Decryptor::Passphrase(_) => bail!("should be recipients"),106		};107		let mut decryptor = decryptor108			.decrypt(iter::once(identity as &dyn age::Identity))109			.context("failed to decrypt, wrong key?")?;110111		let mut decrypted = Vec::new();112		decryptor113			.read_to_end(&mut decrypted)114			.context("failed to decrypt")?;115		decrypted116	};117118	io::copy(&mut Cursor::new(&decrypted), &mut stable_temp)119		.context("failed to write decrypted file")?;120	io::copy(&mut Cursor::new(decrypted), &mut hashed).context("failed to write decrypted file")?;121122	// Make file owned by specified user and group, then change mode123	chown(stable_temp.path(), Some(user.uid), Some(group.gid))124		.context("failed to apply user/group")?;125	chown(&value.secret_path, Some(user.uid), Some(group.gid))126		.context("failed to apply user/group")?;127	fs::set_permissions(stable_temp.path(), fs::Permissions::from_mode(mode.bits())).unwrap();128	fs::set_permissions(&value.secret_path, fs::Permissions::from_mode(mode.bits())).unwrap();129	stable_temp130		.persist(value.stable_secret_path)131		.context("failed to persist")?;132133	Ok(())134}135136fn main() -> anyhow::Result<()> {137	env_logger::Builder::new()138		.filter_level(log::LevelFilter::Info)139		.init();140141	let opts = Opts::parse();142	let data = fs::read(&opts.data).context("failed to read secrets data")?;143	let data_str = from_utf8(&data).context("failed to read data to string")?;144	let data: Data = serde_json::from_str(data_str).context("failed to parse data")?;145146	if !fs::metadata("/run/secrets")147		.map(|m| m.is_dir())148		.unwrap_or(false)149	{150		fs::create_dir("/run/secrets").context("failed to create secrets directory")?;151	}152153	let identity = age::ssh::Identity::from_buffer(154		&mut Cursor::new(155			fs::read("/etc/ssh/ssh_host_ed25519_key").context("failed to read host private key")?,156		),157		None,158	)159	.context("failed to parse identity")?;160161	let mut failed = false;162	for (name, value) in data {163		if let Err(e) = init_secret(&identity, value) {164			error!(165				"{:?}",166				e.context(format!("failed to initialize secret {}", name))167			);168			failed = true;169		}170	}171	if failed {172		bail!("one or more secrets failed");173	}174175	Ok(())176}
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -22,6 +22,7 @@
       devShell = (pkgs.mkShell.override { stdenv = llvmPkgs.stdenv; }) {
         nativeBuildInputs = with pkgs; [
           rust
+          lld
           cargo-edit
           cargo-udeps
           cargo-fuzz