difftreelog
feat support secrets without secret data
in: trunk
4 files changed
cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth1use 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}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}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.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,
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 {