difftreelog
refactor use generator helper for built-in secret generators
in: trunk
11 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -808,10 +808,12 @@
dependencies = [
"age",
"anyhow",
+ "base64 0.22.1",
"clap",
"ed25519-dalek",
"fleet-shared",
"rand",
+ "x25519-dalek",
]
[[package]]
cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth40 /// Secret public part40 /// Secret public part41 #[clap(long)]41 #[clap(long)]42 public: Option<String>,42 public: Option<String>,43 /// How to name public secret part44 #[clap(long, default_value = "public")]45 public_name: String,46 /// Load public part from specified file43 /// Load public part from specified file47 #[clap(long)]44 #[clap(long)]48 public_file: Option<PathBuf>,45 public_file: Option<PathBuf>,55 #[clap(long)]52 #[clap(long)]56 re_add: bool,53 re_add: bool,575455 /// How to name public secret part56 #[clap(long, short = 'p', default_value = "public")]57 public_part: String,58 /// How to name private secret part58 #[clap(default_value = "secret")]59 #[clap(short = 's', long, default_value = "secret")]59 part_name: String,60 part: String,60 },61 },61 /// Add secret, data should be provided in stdin62 /// Add secret, data should be provided in stdin62 Add {63 Add {63 /// Secret name64 /// Secret name64 name: String,65 name: String,65 /// Secret owners66 /// Secret owner67 #[clap(short = 'm', long)]66 machine: String,68 machine: String,67 /// Override secret if already present69 /// Override secret if already present68 #[clap(long)]70 #[clap(long)]69 force: bool,71 force: bool,70 /// Secret public part72 /// Secret public part71 #[clap(long)]73 #[clap(long)]72 public: Option<String>,74 public: Option<String>,73 /// How to name public secret part75 /// Load public part from specified file74 #[clap(long, default_value = "public")]76 #[clap(long)]75 public_name: String,77 public_file: Option<PathBuf>,76 /// Load public part from specified file7879 /// How to name public secret part77 #[clap(long)]80 #[clap(short = 'p', long, default_value = "public")]78 public_file: Option<PathBuf>,81 public_part: String,7982 /// How to name private secret part80 #[clap(default_value = "secret")]83 #[clap(short = 's', long, default_value = "secret")]81 part_name: String,84 part: String,82 },85 },83 /// Read secret from remote host, requires sudo on said host86 /// Read secret from remote host, requires sudo on said host84 Read {87 Read {85 name: String,88 name: String,89 #[clap(short = 'm', long)]86 machine: String,90 machine: String,879192 /// Which private secret part to read88 #[clap(default_value = "secret")]93 #[clap(short = 'p', long, default_value = "secret")]89 part_name: String,94 part: String,90 },95 },91 UpdateShared {96 UpdateShared {92 name: String,97 name: String,939894 #[clap(long)]99 #[clap(short = 'm', long)]95 machines: Option<Vec<String>>,100 machine: Option<Vec<String>>,9610197 #[clap(long)]102 #[clap(long)]98 add_machines: Vec<String>,103 add_machine: Vec<String>,99 #[clap(long)]104 #[clap(long)]100 remove_machines: Vec<String>,105 remove_machine: Vec<String>,101106102 /// Which host should we use to decrypt107 /// Which host should we use to decrypt103 #[clap(long)]108 #[clap(long)]104 prefer_identities: Vec<String>,109 prefer_identities: Vec<String>,105106 #[clap(default_value = "secret")]107 part_name: String,108 },110 },109 Regenerate {111 Regenerate {110 /// Which host should we use to decrypt, in case if reencryption is required, without112 /// Which host should we use to decrypt, in case if reencryption is required, without115 List {},117 List {},116 Edit {118 Edit {117 name: String,119 name: String,118 machine: String,119120 #[clap(default_value = "secret")]120 #[clap(short = 'm', long)]121 part: String,121 machine: String,122122123 #[clap(long)]123 #[clap(long)]124 add: bool,124 add: bool,125126 /// Which private secret part to read127 #[clap(short = 'p', long, default_value = "secret")]128 part: String,125 },129 },126}130}127131220 };224 };221 let on_pkgs = host.pkgs().await?;225 let on_pkgs = host.pkgs().await?;222 let call_package = nix_go!(on_pkgs.callPackage);226 let call_package = nix_go!(on_pkgs.callPackage);223 let mk_encrypt_secret = nix_go!(on_pkgs.mkEncryptSecret);227 let mk_secret_generators = nix_go!(on_pkgs.mkSecretGenerators);224228225 let mut recipients = Vec::new();229 let mut recipients = Vec::new();226 for owner in owners {230 for owner in owners {227 let key = config.key(owner).await?;231 let key = config.key(owner).await?;228 recipients.push(key);232 recipients.push(key);229 }233 }230 let encrypt = nix_go!(mk_encrypt_secret(Obj {234 let generators = nix_go!(mk_secret_generators(Obj {231 recipients: { recipients },235 recipients: { recipients },232 }));236 }));233237234 let generator = nix_go!(call_package(generator)(Obj {238 let generator = nix_go!(call_package(generator)(generators));235 encrypt,236 // rustfmt_please_newline237 }));238239239 let generator = generator.build().await?;240 let generator = generator.build().await?;240 let generator = generator241 let generator = generator305 }306 }306 let default_pkgs = &config.default_pkgs;307 let default_pkgs = &config.default_pkgs;307 let default_call_package = nix_go!(default_pkgs.callPackage);308 let default_call_package = nix_go!(default_pkgs.callPackage);309 let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);308 // Generators provide additional information in passthru, to access310 // Generators provide additional information in passthru, to access309 // passthru we should call generator, but information about where this generator is supposed to build311 // passthru we should call generator, but information about where this generator is supposed to build310 // is located in passthru... Thus evaluating generator on host.312 // is located in passthru... Thus evaluating generator on host.313 //315 //314 // I don't want to make modules always responsible for additional secret data anyway,316 // I don't want to make modules always responsible for additional secret data anyway,315 // so it should be in derivation, and not in the secret data itself.317 // so it should be in derivation, and not in the secret data itself.316 let default_generator = nix_go!(default_call_package(generator)(Obj {318 let generators = nix_go!(default_mk_secret_generators(Obj {317 encrypt: { "exit 1" },319 recipients: { <Vec<String>>::new() },318 // rustfmt_please_newline319 }));320 }));321 let default_generator = nix_go!(default_call_package(generator)(generators));320322321 let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);323 let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);322324442 name,444 name,443 force,445 force,444 public,446 public,445 public_name,447 public_part: public_name,446 public_file,448 public_file,447 expires_at,449 expires_at,448 re_add,450 re_add,449 part_name,451 part: part_name,450 } => {452 } => {451 // TODO: Forbid updating secrets with set expectedOwners (= not user-managed).453 // TODO: Forbid updating secrets with set expectedOwners (= not user-managed).452454500 name,502 name,501 force,503 force,502 public,504 public,503 public_name,505 public_part: public_name,504 public_file,506 public_file,505 part_name,507 part: part_name,506 } => {508 } => {507 if config.has_secret(&machine, &name) && !force {509 if config.has_secret(&machine, &name) && !force {508 bail!("secret already defined");510 bail!("secret already defined");535 Secret::Read {537 Secret::Read {536 name,538 name,537 machine,539 machine,538 part_name,540 part: part_name,539 } => {541 } => {540 let secret = config.host_secret(&machine, &name)?;542 let secret = config.host_secret(&machine, &name)?;541 let Some(secret) = secret.parts.get(&part_name) else {543 let Some(secret) = secret.parts.get(&part_name) else {552 }554 }553 Secret::UpdateShared {555 Secret::UpdateShared {554 name,556 name,555 machines,557 machine,556 add_machines,558 add_machine,557 remove_machines,559 remove_machine,558 prefer_identities,560 prefer_identities,559 part_name,560 } => {561 } => {561 // TODO: Forbid updating secrets with set expectedOwners (= not user-managed).562 // TODO: Forbid updating secrets with set expectedOwners (= not user-managed).562563563 let secret = config.shared_secret(&name)?;564 let secret = config.shared_secret(&name)?;564 if secret.secret.parts.get(&part_name).is_none() {565 if secret.secret.parts.values().all(|v| !v.raw.encrypted) {565 bail!("no secret");566 bail!("no secret");566 }567 }567568568 let initial_machines = secret.owners.clone();569 let initial_machines = secret.owners.clone();569 let target_machines = parse_machines(570 let target_machines = parse_machines(570 initial_machines.clone(),571 initial_machines.clone(),571 machines,572 machine,572 add_machines,573 add_machine,573 remove_machines,574 remove_machine,574 )?;575 )?;575576576 if target_machines.is_empty() {577 if target_machines.is_empty() {cmds/fleet/src/host.rsdiffbeforeafterboth--- a/cmds/fleet/src/host.rs
+++ b/cmds/fleet/src/host.rs
@@ -95,7 +95,7 @@
let out = cmd.run_string().await?;
let mut lines = out.split('\n');
if let Some(last) = lines.next_back() {
- ensure!(last == "", "output of ls should end with newline");
+ ensure!(last.is_empty(), "output of ls should end with newline");
}
Ok(lines.map(ToOwned::to_owned).collect())
}
cmds/generator-helper/Cargo.tomldiffbeforeafterboth--- a/cmds/generator-helper/Cargo.toml
+++ b/cmds/generator-helper/Cargo.toml
@@ -6,7 +6,9 @@
[dependencies]
age.workspace = true
anyhow.workspace = true
+base64 = "0.22.1"
clap.workspace = true
ed25519-dalek = { version = "2.1", features = ["rand_core"] }
fleet-shared.workspace = true
rand = "0.8.5"
+x25519-dalek = "2.0.1"
cmds/generator-helper/src/main.rsdiffbeforeafterboth--- a/cmds/generator-helper/src/main.rs
+++ b/cmds/generator-helper/src/main.rs
@@ -1,52 +1,161 @@
use std::{
- fs,
- io::{self, stdout, Cursor, Read, Write},
- path::PathBuf,
+ env,
+ fs::{File, OpenOptions},
+ io::{copy, Read, Write},
str::FromStr,
};
-use age::Recipient;
+use age::{
+ ssh::{ParseRecipientKeyError, Recipient as SshRecipient},
+ Encryptor, Recipient,
+};
use anyhow::{anyhow, bail, ensure, Context, Result};
-use clap::Parser;
-use ed25519_dalek::SigningKey;
+use clap::{Parser, ValueEnum};
use fleet_shared::SecretData;
use rand::{
distributions::{Alphanumeric, DistString, Distribution, Uniform},
- rngs::OsRng,
- thread_rng, Rng,
+ thread_rng,
};
-fn write_output(out: &str, data: impl AsRef<[u8]>, stdout_marker: &mut bool) -> Result<()> {
- let data = data.as_ref();
- if out == "-" {
- let mut stdout = stdout();
- if *stdout_marker {
- stdout.write_all(&[b'\n'])?;
+fn write_output_file(out: &str) -> Result<File> {
+ let file = OpenOptions::new()
+ .create_new(true)
+ .write(true)
+ .open(out)
+ .with_context(|| format!("failed to open output {out:?}"))?;
+ Ok(file)
+}
+fn write_public(out: &str, mut input: impl Read, encoding: OutputEncoding) -> Result<()> {
+ let mut output = write_output_file(out)?;
+
+ let mut data = Vec::new();
+ copy(&mut input, &mut wrap_encoder(&mut data, encoding))?;
+
+ output.write_all(
+ SecretData {
+ data,
+ encrypted: false,
}
- *stdout_marker = true;
- stdout.write_all(data)?;
- } else {
- fs::write(out, data)?;
+ .to_string()
+ .as_bytes(),
+ )?;
+ Ok(())
+}
+fn write_private(
+ identities: &Identities,
+ out: &str,
+ mut input: impl Read,
+ encoding: OutputEncoding,
+) -> Result<()> {
+ let mut output = write_output_file(out)?;
+ let encryptor = make_encryptor(identities)?;
+
+ let mut data = Vec::new();
+ {
+ let mut encrypted_writer = encryptor.wrap_output(&mut data)?;
+ copy(
+ &mut input,
+ &mut wrap_encoder(&mut encrypted_writer, encoding),
+ )?;
+ encrypted_writer.finish()?;
};
+
+ output.write_all(
+ SecretData {
+ data,
+ encrypted: true,
+ }
+ .to_string()
+ .as_bytes(),
+ )?;
Ok(())
}
+type Identities = Vec<SshRecipient>;
+fn load_identities() -> Result<Identities> {
+ let list = env::var("GENERATOR_HELPER_IDENTITIES");
+ let list = match list {
+ Ok(v) => v,
+ Err(env::VarError::NotPresent) => {
+ bail!("gh is only intended to be used from secret generator scripts, but if you really want to use it somewhere else - set GENERATOR_HELPER_IDENTITIES to list of newline-delimited ssh identities");
+ }
+ Err(e) => bail!("somehow, identities list is not utf-8: {e}"),
+ };
+ let list = list.trim();
+ ensure!(!list.is_empty(), "no identities passed, can't encrypt data");
+ list.lines()
+ .map(age::ssh::Recipient::from_str)
+ .collect::<Result<Identities, ParseRecipientKeyError>>()
+ .map_err(|e| anyhow!("parse recipients: {e:?}"))
+}
+fn make_encryptor(r: &Identities) -> Result<Encryptor> {
+ Ok(Encryptor::with_recipients(
+ r.iter()
+ .map(|v| {
+ let coerced: Box<dyn Recipient + Send> = Box::new(v.clone());
+ coerced
+ })
+ .collect(),
+ )
+ .expect("list is not empty"))
+}
+fn wrap_encoder<'t>(w: impl Write + 't, encoding: OutputEncoding) -> impl Write + 't {
+ fn coerce<'t>(w: impl Write + 't) -> Box<dyn Write + 't> {
+ Box::new(w)
+ }
+ match encoding {
+ OutputEncoding::Raw => coerce(w),
+ OutputEncoding::Base64 => {
+ use base64::engine::general_purpose::STANDARD;
+ let writer = base64::write::EncoderWriter::new(w, &STANDARD);
+ coerce(writer)
+ }
+ }
+}
+
+#[derive(Clone, Copy, ValueEnum, Default)]
+enum OutputEncoding {
+ /// Do not encode data, store as is.
+ #[default]
+ Raw,
+ /// Encode as base64 (with padding).
+ Base64,
+}
+
#[derive(Parser)]
enum Generate {
/// Generate public, private keys without wrapping, in standard ed25519 schema
/// (64 bytes private (due to merge with private), 32 bytes public)
Ed25519 {
+ #[arg(long, short = 'p')]
public: String,
+ #[arg(long, short = 's')]
private: String,
/// Private key should be just the private key (32 bytes), not standard private+public.
#[arg(long)]
no_embed_public: bool,
+ #[arg(long, short = 'e', value_enum, default_value_t)]
+ encoding: OutputEncoding,
+ },
+ /// Generate public, private keys without wrapping, in standard x25519 schema
+ /// (32 bytes private, 32 bytes public)
+ X25519 {
+ #[arg(long, short = 'p')]
+ public: String,
+ #[arg(long, short = 's')]
+ private: String,
+ #[arg(long, short = 'e', value_enum, default_value_t)]
+ encoding: OutputEncoding,
},
Password {
+ #[arg(long, short = 'o')]
output: String,
+ #[arg(long)]
size: usize,
#[arg(long, short = 'n')]
no_symbols: bool,
+ #[arg(long, short = 'e', value_enum, default_value_t)]
+ encoding: OutputEncoding,
},
}
@@ -54,15 +163,17 @@
enum Opts {
/// Encode public part from stdin.
Public {
- #[arg(long)]
- allow_empty: bool,
+ #[arg(long, short = 'o')]
+ output: String,
+ #[arg(long, short = 'e', value_enum, default_value_t)]
+ encoding: OutputEncoding,
},
/// Encrypt private part from stdin.
Private {
- #[arg(long)]
- allow_empty: bool,
- #[arg(short = 'r')]
- recipient: Vec<String>,
+ #[arg(long, short = 'o')]
+ output: String,
+ #[arg(long, short = 'e', value_enum, default_value_t)]
+ encoding: OutputEncoding,
},
/// Generate keys in well-known schemas.
///
@@ -70,99 +181,34 @@
/// otherwise you should ensure noone is able to read generated files, they don't have any mode set by default.
#[command(subcommand)]
Generate(Generate),
- // Generate {
- // kind: GenerateKind,
- // /// Different generators generate different number of files, you need to specify number of outputs corresponding to the generator.
- // #[arg(short = 'o')]
- // outputs: Vec<String>,
- // },
}
-fn parse_stdin() -> Result<Option<Vec<u8>>> {
- let mut input = vec![];
- io::stdin().read_to_end(&mut input)?;
- if input.is_empty() {
- Ok(None)
- } else {
- Ok(Some(input))
- }
-}
-pub fn encrypt_secret_data(
- recipients: impl IntoIterator<Item = impl Recipient + Send + 'static>,
- data: Vec<u8>,
-) -> Option<SecretData> {
- let mut encrypted = vec![];
- let recipients = recipients
- .into_iter()
- .map(|v| Box::new(v) as Box<dyn Recipient + Send>)
- .collect::<Vec<_>>();
- let mut encryptor = age::Encryptor::with_recipients(recipients)?
- .wrap_output(&mut encrypted)
- .expect("in memory write");
- io::copy(&mut Cursor::new(data), &mut encryptor).expect("in memory copy");
- encryptor.finish().expect("in memory flush");
- Some(SecretData {
- data: encrypted,
- encrypted: true,
- })
-}
-
fn main() -> Result<()> {
let opts = Opts::parse();
// Assumed to be secure, seeded from secure OsRng+reseeded.
let mut rng = thread_rng();
match opts {
- Opts::Public { allow_empty } => {
- let stdin = parse_stdin()?;
- if stdin.is_none() && !allow_empty {
- bail!("empty stdin input is not allowed unless --allow-empty is set");
- }
- let stdin = stdin.unwrap_or_default();
- io::stdout().write_all(
- SecretData {
- data: stdin,
- encrypted: false,
- }
- .to_string()
- .as_bytes(),
- )?;
+ Opts::Public { output, encoding } => {
+ write_public(&output, std::io::stdin(), encoding)?;
}
- Opts::Private {
- allow_empty,
- recipient,
- } => {
- let stdin = parse_stdin()?;
- if stdin.is_none() && !allow_empty {
- bail!("empty stdin input is not allowed unless --allow-empty is set");
- }
- let stdin = stdin.unwrap_or_default();
- if recipient.is_empty() {
- bail!("recipient list is empty");
- }
- let out = encrypt_secret_data(
- recipient
- .into_iter()
- .map(|r| age::ssh::Recipient::from_str(&r))
- .collect::<Result<Vec<age::ssh::Recipient>, age::ssh::ParseRecipientKeyError>>()
- .map_err(|e| anyhow!("parse recipients: {e:?}"))?,
- stdin,
- )
- .expect("got recipients");
- io::stdout().write_all(out.to_string().as_bytes())?;
+ Opts::Private { output, encoding } => {
+ let recipients = load_identities()?;
+ write_private(&recipients, &output, std::io::stdin(), encoding)?;
}
Opts::Generate(gen) => {
- let mut stdout_marker: bool = false;
match gen {
Generate::Ed25519 {
public,
private,
no_embed_public,
+ encoding,
} => {
- let key = SigningKey::generate(&mut rng).to_keypair_bytes();
-
- write_output(&public, &key[32..], &mut stdout_marker).context("public")?;
- write_output(
+ let recipients = load_identities()?;
+ let key = ed25519_dalek::SigningKey::generate(&mut rng).to_keypair_bytes();
+ write_public(&public, &key[32..], encoding)?;
+ write_private(
+ &recipients,
&private,
&key[..{
if no_embed_public {
@@ -171,19 +217,31 @@
64
}
}],
- &mut stdout_marker,
- )
- .context("private")?;
+ encoding,
+ )?;
+ }
+ Generate::X25519 {
+ public,
+ private,
+ encoding,
+ } => {
+ let recipients = load_identities()?;
+ let key = x25519_dalek::StaticSecret::random_from_rng(rng);
+ let public_key: x25519_dalek::PublicKey = (&key).into();
+ write_public(&public, public_key.as_bytes().as_slice(), encoding)?;
+ write_private(&recipients, &private, key.as_bytes().as_slice(), encoding)?;
}
Generate::Password {
size,
no_symbols,
output,
+ encoding,
} => {
ensure!(
size >= 6,
"misconfiguration? password is shorter than 6 chars"
);
+ let recipients = load_identities()?;
let out = if no_symbols {
Alphanumeric.sample_string(&mut rng, size)
} else {
@@ -195,7 +253,7 @@
.map(|i| GEN_ASCII_SYMBOLS[i] as char)
.collect::<String>()
};
- write_output(&output, out, &mut stdout_marker)?;
+ write_private(&recipients, &output, out.as_bytes(), encoding)?;
}
}
}
flake.nixdiffbeforeafterboth--- a/flake.nix
+++ b/flake.nix
@@ -67,6 +67,7 @@
perSystem = {
config,
system,
+ pkgs,
...
}: let
# Can also be built for darwin, through it is not usual to deploy nixos systems from macos machines.
@@ -75,14 +76,14 @@
# It is not possible to deploy any host from armv6/armv7 hardware, and I don't think it even makes sense.
deployerSystems = ["aarch64-linux" "x86_64-linux"];
deployerSystem = builtins.elem system deployerSystems;
- pkgs = import nixpkgs {
- inherit system;
- overlays = [(rust-overlay.overlays.default)];
- };
lib = pkgs.lib;
rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
craneLib = (crane.mkLib pkgs).overrideToolchain rust;
in {
+ _module.args.pkgs = import nixpkgs {
+ inherit system;
+ overlays = [(rust-overlay.overlays.default)];
+ };
# Reference fleet package should be built with nightly rust, specified in rust-toolchain.toml.
packages = lib.mkIf deployerSystem (let
packages = import ./pkgs {
lib/fleetLib.nixdiffbeforeafterboth--- a/lib/fleetLib.nix
+++ b/lib/fleetLib.nix
@@ -42,23 +42,46 @@
mkPassword = {size ? 32}: {
coreutils,
- encrypt,
mkSecretGenerator,
+ ...
}:
mkSecretGenerator {
script = ''
mkdir $out
+ gh generate password -o $out/secret --size ${toString size}
+ '';
+ };
- ${coreutils}/bin/tr -dc 'A-Za-z0-9!?%=' < /dev/random \
- | ${coreutils}/bin/head -c ${toString size} \
- | ${encrypt} > $out/secret
+ mkEd25519 = {
+ noEmbedPublic ? false,
+ encoding ? null,
+ }: {mkSecretGenerator, ...}:
+ mkSecretGenerator {
+ script = ''
+ mkdir $out
+ gh generate ed25519 -p $out/public -s $out/secret \
+ ${lib.optionalString noEmbedPublic "--no-embed-public"} \
+ ${lib.optionalString (encoding != null) "--encoding=${encoding}"}
'';
};
+ mkGarage = {}: mkEd25519 {noEmbedPublic = true;};
+
+ mkX25519 = {encoding ? null}: {mkSecretGenerator, ...}:
+ mkSecretGenerator {
+ script = ''
+ mkdir $out
+ gh generate x25519 -p $out/public -s $out/secret \
+ ${lib.optionalString (encoding != null) "--encoding=${encoding}"}
+ '';
+ };
+
+ mkWireguard = {}: mkX25519 {encoding = "base64";};
+
mkRsa = {size ? 4096}: {
openssl,
- encrypt,
mkSecretGenerator,
+ ...
}:
mkSecretGenerator {
script = ''
@@ -67,8 +90,8 @@
${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}
${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key
- sudo cat rsa_private.key | ${encrypt} > $out/secret
- sudo cat rsa_public.key > $out/public
+ cat rsa_private.key | gh private -o $out/secret
+ cat rsa_public.key | gh public -o $out/public
'';
};
}
modules/fleet/secrets.nixdiffbeforeafterboth--- a/modules/fleet/secrets.nix
+++ b/modules/fleet/secrets.nix
@@ -130,85 +130,81 @@
overlays = [
(final: prev: let
lib = final.lib;
- inherit (lib) strings concatMap;
- inherit (strings) escapeShellArgs;
+ inherit (lib) strings;
+ inherit (strings) concatStringsSep;
in {
- mkEncryptSecret = {
- rage ? prev.rage,
- recipients,
- }:
- prev.writeShellScript "encryptor" ''
- #!/bin/sh
- exec ${rage}/bin/rage ${escapeShellArgs (concatMap (r: ["-r" r]) recipients)} -e "$@"
- '';
- # TODO: Move to fleet
- # TODO: Merge both generators to one with consistent options syntax?
- # Impure generator is built on local machine, then built closure is copied to remote machine,
- # and then it is ran in inpure context, so that this generator may access HSMs and other things.
- mkImpureSecretGenerator = {
- script,
- # If set - script will be run on remote machine, otherwise it will be run with fleet project in CWD
- # (Some secrets-encryption-in-git/managed PKI solution is expected)
- impureOn ? null,
- }:
- (prev.writeShellScript "impureGenerator.sh" ''
- #!/bin/sh
- set -eu
+ mkSecretGenerators = {recipients}: rec {
+ # TODO: Merge both generators to one with consistent options syntax?
+ # Impure generator is built on local machine, then built closure is copied to remote machine,
+ # and then it is ran in inpure context, so that this generator may access HSMs and other things.
+ mkImpureSecretGenerator = {
+ script,
+ # If set - script will be run on remote machine, otherwise it will be run with fleet project in CWD
+ # (Some secrets-encryption-in-git/managed PKI solution is expected)
+ impureOn ? null,
+ }:
+ (prev.writeShellScript "impureGenerator.sh" ''
+ #!/bin/sh
+ set -eu
+
+ export GENERATOR_HELPER_IDENTITIES="${concatStringsSep "\n" recipients}";
+ export PATH=${final.fleet-generator-helper}/bin:$PATH
- # TODO: Provide tempdir from outside, to make it securely erasurable as needed?
- tmp=$(mktemp -d)
- cd $tmp
- # cd /var/empty
+ # TODO: Provide tempdir from outside, to make it securely erasurable as needed?
+ tmp=$(mktemp -d)
+ cd $tmp
+ # cd /var/empty
- created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")
+ created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")
- ${script}
+ ${script}
- if ! test -d $out; then
- echo "impure generator script did not produce expected \$out output"
- exit 1
- fi
+ if ! test -d $out; then
+ echo "impure generator script did not produce expected \$out output"
+ exit 1
+ fi
- echo -n $created_at > $out/created_at
- echo -n SUCCESS > $out/marker
- '')
- .overrideAttrs (old: {
- passthru = {
- inherit impureOn;
- generatorKind = "impure";
- };
- });
- # Pure generators are disabled for now
- mkSecretGenerator = {script}: final.mkImpureSecretGenerator {inherit script;};
+ echo -n $created_at > $out/created_at
+ echo -n SUCCESS > $out/marker
+ '')
+ .overrideAttrs (old: {
+ passthru = {
+ inherit impureOn;
+ generatorKind = "impure";
+ };
+ });
+ # Pure generators are disabled for now
+ mkSecretGenerator = {script}: mkImpureSecretGenerator {inherit script;};
- # TODO: Implement consistent naming
- # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...
- # But for now, it is ran the same way as `impureSecretGenerator`, but on the local machine.
- # mkSecretGenerator = {script}:
- # (prev.writeShellScript "generator.sh" ''
- # #!/bin/sh
- # set -eu
- # # TODO: make nix daemon build secret, not just the script.
- # cd /var/empty
- #
- # created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")
- #
- # ${script}
- # if ! test -d $out; then
- # echo "impure generator script did not produce expected \$out output"
- # exit 1
- # fi
- #
- # echo -n $created_at > $out/created_at
- # echo -n SUCCESS > $out/marker
- # '')
- # .overrideAttrs (old: {
- # passthru = {
- # generatorKind = "pure";
- # };
- # # TODO: make nix daemon build secret, not just the script.
- # # __impure = true;
- # });
+ # TODO: Implement consistent naming
+ # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...
+ # But for now, it is ran the same way as `impureSecretGenerator`, but on the local machine.
+ # mkSecretGenerator = {script}:
+ # (prev.writeShellScript "generator.sh" ''
+ # #!/bin/sh
+ # set -eu
+ # # TODO: make nix daemon build secret, not just the script.
+ # cd /var/empty
+ #
+ # created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")
+ #
+ # ${script}
+ # if ! test -d $out; then
+ # echo "impure generator script did not produce expected \$out output"
+ # exit 1
+ # fi
+ #
+ # echo -n $created_at > $out/created_at
+ # echo -n SUCCESS > $out/marker
+ # '')
+ # .overrideAttrs (old: {
+ # passthru = {
+ # generatorKind = "pure";
+ # };
+ # # TODO: make nix daemon build secret, not just the script.
+ # # __impure = true;
+ # });
+ };
})
];
};
pkgs/default.nixdiffbeforeafterboth--- a/pkgs/default.nix
+++ b/pkgs/default.nix
@@ -2,6 +2,7 @@
callPackage,
craneLib,
}: {
+ fleet = callPackage ./fleet.nix {inherit craneLib;};
fleet-install-secrets = callPackage ./fleet-install-secrets.nix {inherit craneLib;};
- fleet = callPackage ./fleet.nix {inherit craneLib;};
+ fleet-generator-helper = callPackage ./fleet-generator-helper.nix {inherit craneLib;};
}
pkgs/fleet-generator-helper.nixdiffbeforeafterboth--- /dev/null
+++ b/pkgs/fleet-generator-helper.nix
@@ -0,0 +1,13 @@
+{craneLib}:
+craneLib.buildPackage rec {
+ pname = "fleet-generator-helper";
+
+ src = craneLib.cleanCargoSource (craneLib.path ../.);
+ strictDeps = true;
+
+ cargoExtraArgs = "--locked -p ${pname}";
+
+ postInstall = ''
+ ln -s $out/bin/${pname} $out/bin/gh
+ '';
+}
pkgs/generator-helper.nixdiffbeforeafterboth--- a/pkgs/generator-helper.nix
+++ /dev/null
@@ -1,13 +0,0 @@
-{craneLib}:
-craneLib.buildPackage rec {
- pname = "fleet-generator-helper";
-
- src = craneLib.cleanCargoSource (craneLib.path ../.);
- strictDeps = true;
-
- cargoExtraArgs = "--locked -p ${pname}";
-
- postInstall = ''
- mv bin/${pname} bin/genhelper
- '';
-}