difftreelog
feat support secrets without secret data
in: trunk
4 files changed
cmds/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();
cmds/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>,
}
cmds/install-secrets/src/main.rsdiffbeforeafterboth1use 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}modules/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 {