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.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/secrets/mod.rs
+++ b/cmds/fleet/src/cmds/secrets/mod.rs
@@ -40,9 +40,6 @@
/// Secret public part
#[clap(long)]
public: Option<String>,
- /// How to name public secret part
- #[clap(long, default_value = "public")]
- public_name: String,
/// Load public part from specified file
#[clap(long)]
public_file: Option<PathBuf>,
@@ -55,14 +52,19 @@
#[clap(long)]
re_add: bool,
- #[clap(default_value = "secret")]
- part_name: String,
+ /// How to name public secret part
+ #[clap(long, short = 'p', default_value = "public")]
+ public_part: String,
+ /// How to name private secret part
+ #[clap(short = 's', long, default_value = "secret")]
+ part: String,
},
/// Add secret, data should be provided in stdin
Add {
/// Secret name
name: String,
- /// Secret owners
+ /// Secret owner
+ #[clap(short = 'm', long)]
machine: String,
/// Override secret if already present
#[clap(long)]
@@ -70,41 +72,41 @@
/// Secret public part
#[clap(long)]
public: Option<String>,
- /// How to name public secret part
- #[clap(long, default_value = "public")]
- public_name: String,
/// Load public part from specified file
#[clap(long)]
public_file: Option<PathBuf>,
- #[clap(default_value = "secret")]
- part_name: String,
+ /// How to name public secret part
+ #[clap(short = 'p', long, default_value = "public")]
+ public_part: String,
+ /// How to name private secret part
+ #[clap(short = 's', long, default_value = "secret")]
+ part: String,
},
/// Read secret from remote host, requires sudo on said host
Read {
name: String,
+ #[clap(short = 'm', long)]
machine: String,
- #[clap(default_value = "secret")]
- part_name: String,
+ /// Which private secret part to read
+ #[clap(short = 'p', long, default_value = "secret")]
+ part: String,
},
UpdateShared {
name: String,
- #[clap(long)]
- machines: Option<Vec<String>>,
+ #[clap(short = 'm', long)]
+ machine: Option<Vec<String>>,
#[clap(long)]
- add_machines: Vec<String>,
+ add_machine: Vec<String>,
#[clap(long)]
- remove_machines: Vec<String>,
+ remove_machine: Vec<String>,
/// Which host should we use to decrypt
#[clap(long)]
prefer_identities: Vec<String>,
-
- #[clap(default_value = "secret")]
- part_name: String,
},
Regenerate {
/// Which host should we use to decrypt, in case if reencryption is required, without
@@ -115,13 +117,15 @@
List {},
Edit {
name: String,
+ #[clap(short = 'm', long)]
machine: String,
-
- #[clap(default_value = "secret")]
- part: String,
#[clap(long)]
add: bool,
+
+ /// Which private secret part to read
+ #[clap(short = 'p', long, default_value = "secret")]
+ part: String,
},
}
@@ -220,21 +224,18 @@
};
let on_pkgs = host.pkgs().await?;
let call_package = nix_go!(on_pkgs.callPackage);
- let mk_encrypt_secret = nix_go!(on_pkgs.mkEncryptSecret);
+ let mk_secret_generators = nix_go!(on_pkgs.mkSecretGenerators);
let mut recipients = Vec::new();
for owner in owners {
let key = config.key(owner).await?;
recipients.push(key);
}
- let encrypt = nix_go!(mk_encrypt_secret(Obj {
+ let generators = nix_go!(mk_secret_generators(Obj {
recipients: { recipients },
}));
- let generator = nix_go!(call_package(generator)(Obj {
- encrypt,
- // rustfmt_please_newline
- }));
+ let generator = nix_go!(call_package(generator)(generators));
let generator = generator.build().await?;
let generator = generator
@@ -305,6 +306,7 @@
}
let default_pkgs = &config.default_pkgs;
let default_call_package = nix_go!(default_pkgs.callPackage);
+ let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);
// Generators provide additional information in passthru, to access
// passthru we should call generator, but information about where this generator is supposed to build
// is located in passthru... Thus evaluating generator on host.
@@ -313,10 +315,10 @@
//
// I don't want to make modules always responsible for additional secret data anyway,
// so it should be in derivation, and not in the secret data itself.
- let default_generator = nix_go!(default_call_package(generator)(Obj {
- encrypt: { "exit 1" },
- // rustfmt_please_newline
+ let generators = nix_go!(default_mk_secret_generators(Obj {
+ recipients: { <Vec<String>>::new() },
}));
+ let default_generator = nix_go!(default_call_package(generator)(generators));
let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);
@@ -442,11 +444,11 @@
name,
force,
public,
- public_name,
+ public_part: public_name,
public_file,
expires_at,
re_add,
- part_name,
+ part: part_name,
} => {
// TODO: Forbid updating secrets with set expectedOwners (= not user-managed).
@@ -500,9 +502,9 @@
name,
force,
public,
- public_name,
+ public_part: public_name,
public_file,
- part_name,
+ part: part_name,
} => {
if config.has_secret(&machine, &name) && !force {
bail!("secret already defined");
@@ -535,7 +537,7 @@
Secret::Read {
name,
machine,
- part_name,
+ part: part_name,
} => {
let secret = config.host_secret(&machine, &name)?;
let Some(secret) = secret.parts.get(&part_name) else {
@@ -552,25 +554,24 @@
}
Secret::UpdateShared {
name,
- machines,
- add_machines,
- remove_machines,
+ machine,
+ add_machine,
+ remove_machine,
prefer_identities,
- part_name,
} => {
// TODO: Forbid updating secrets with set expectedOwners (= not user-managed).
let secret = config.shared_secret(&name)?;
- if secret.secret.parts.get(&part_name).is_none() {
+ if secret.secret.parts.values().all(|v| !v.raw.encrypted) {
bail!("no secret");
}
let initial_machines = secret.owners.clone();
let target_machines = parse_machines(
initial_machines.clone(),
- machines,
- add_machines,
- remove_machines,
+ machine,
+ add_machine,
+ remove_machine,
)?;
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.rsdiffbeforeafterboth1use std::{1use std::{2 env,2 fs,3 fs::{File, OpenOptions},3 io::{self, stdout, Cursor, Read, Write},4 io::{copy, Read, Write},4 path::PathBuf,5 str::FromStr,5 str::FromStr,6};6};778use age::Recipient;8use age::{9 ssh::{ParseRecipientKeyError, Recipient as SshRecipient},10 Encryptor, Recipient,11};9use anyhow::{anyhow, bail, ensure, Context, Result};12use anyhow::{anyhow, bail, ensure, Context, Result};10use clap::Parser;13use clap::{Parser, ValueEnum};11use ed25519_dalek::SigningKey;12use fleet_shared::SecretData;14use fleet_shared::SecretData;13use rand::{15use rand::{14 distributions::{Alphanumeric, DistString, Distribution, Uniform},16 distributions::{Alphanumeric, DistString, Distribution, Uniform},15 rngs::OsRng,16 thread_rng, Rng,17 thread_rng,17};18};181920fn write_output_file(out: &str) -> Result<File> {21 let file = OpenOptions::new()22 .create_new(true)23 .write(true)24 .open(out)25 .with_context(|| format!("failed to open output {out:?}"))?;26 Ok(file)27}28fn write_public(out: &str, mut input: impl Read, encoding: OutputEncoding) -> Result<()> {29 let mut output = write_output_file(out)?;3031 let mut data = Vec::new();32 copy(&mut input, &mut wrap_encoder(&mut data, encoding))?;3334 output.write_all(35 SecretData {36 data,37 encrypted: false,38 }39 .to_string()40 .as_bytes(),41 )?;42 Ok(())43}19fn write_output(out: &str, data: impl AsRef<[u8]>, stdout_marker: &mut bool) -> Result<()> {44fn write_private(45 identities: &Identities,46 out: &str,47 mut input: impl Read,48 encoding: OutputEncoding,49) -> Result<()> {20 let data = data.as_ref();50 let mut output = write_output_file(out)?;21 if out == "-" {51 let encryptor = make_encryptor(identities)?;5222 let mut stdout = stdout();53 let mut data = Vec::new();23 if *stdout_marker {54 {24 stdout.write_all(&[b'\n'])?;55 let mut encrypted_writer = encryptor.wrap_output(&mut data)?;25 }56 copy(57 &mut input,58 &mut wrap_encoder(&mut encrypted_writer, encoding),26 *stdout_marker = true;59 )?;27 stdout.write_all(data)?;60 encrypted_writer.finish()?;28 } else {61 };6229 fs::write(out, data)?;63 output.write_all(30 };64 SecretData {65 data,66 encrypted: true,67 }68 .to_string()69 .as_bytes(),70 )?;31 Ok(())71 Ok(())32}72}7374type Identities = Vec<SshRecipient>;75fn load_identities() -> Result<Identities> {76 let list = env::var("GENERATOR_HELPER_IDENTITIES");77 let list = match list {78 Ok(v) => v,79 Err(env::VarError::NotPresent) => {80 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");81 }82 Err(e) => bail!("somehow, identities list is not utf-8: {e}"),83 };84 let list = list.trim();85 ensure!(!list.is_empty(), "no identities passed, can't encrypt data");86 list.lines()87 .map(age::ssh::Recipient::from_str)88 .collect::<Result<Identities, ParseRecipientKeyError>>()89 .map_err(|e| anyhow!("parse recipients: {e:?}"))90}91fn make_encryptor(r: &Identities) -> Result<Encryptor> {92 Ok(Encryptor::with_recipients(93 r.iter()94 .map(|v| {95 let coerced: Box<dyn Recipient + Send> = Box::new(v.clone());96 coerced97 })98 .collect(),99 )100 .expect("list is not empty"))101}102fn wrap_encoder<'t>(w: impl Write + 't, encoding: OutputEncoding) -> impl Write + 't {103 fn coerce<'t>(w: impl Write + 't) -> Box<dyn Write + 't> {104 Box::new(w)105 }106 match encoding {107 OutputEncoding::Raw => coerce(w),108 OutputEncoding::Base64 => {109 use base64::engine::general_purpose::STANDARD;110 let writer = base64::write::EncoderWriter::new(w, &STANDARD);111 coerce(writer)112 }113 }114}115116#[derive(Clone, Copy, ValueEnum, Default)]117enum OutputEncoding {118 /// Do not encode data, store as is.119 #[default]120 Raw,121 /// Encode as base64 (with padding).122 Base64,123}3312434#[derive(Parser)]125#[derive(Parser)]35enum Generate {126enum Generate {36 /// Generate public, private keys without wrapping, in standard ed25519 schema127 /// Generate public, private keys without wrapping, in standard ed25519 schema37 /// (64 bytes private (due to merge with private), 32 bytes public)128 /// (64 bytes private (due to merge with private), 32 bytes public)38 Ed25519 {129 Ed25519 {130 #[arg(long, short = 'p')]39 public: String,131 public: String,132 #[arg(long, short = 's')]40 private: String,133 private: String,41 /// Private key should be just the private key (32 bytes), not standard private+public.134 /// Private key should be just the private key (32 bytes), not standard private+public.42 #[arg(long)]135 #[arg(long)]43 no_embed_public: bool,136 no_embed_public: bool,137 #[arg(long, short = 'e', value_enum, default_value_t)]138 encoding: OutputEncoding,44 },139 },140 /// Generate public, private keys without wrapping, in standard x25519 schema141 /// (32 bytes private, 32 bytes public)142 X25519 {143 #[arg(long, short = 'p')]144 public: String,145 #[arg(long, short = 's')]146 private: String,147 #[arg(long, short = 'e', value_enum, default_value_t)]148 encoding: OutputEncoding,149 },45 Password {150 Password {151 #[arg(long, short = 'o')]46 output: String,152 output: String,153 #[arg(long)]47 size: usize,154 size: usize,48 #[arg(long, short = 'n')]155 #[arg(long, short = 'n')]49 no_symbols: bool,156 no_symbols: bool,157 #[arg(long, short = 'e', value_enum, default_value_t)]158 encoding: OutputEncoding,50 },159 },51}160}5216153#[derive(Parser)]162#[derive(Parser)]54enum Opts {163enum Opts {55 /// Encode public part from stdin.164 /// Encode public part from stdin.56 Public {165 Public {57 #[arg(long)]166 #[arg(long, short = 'o')]58 allow_empty: bool,167 output: String,168 #[arg(long, short = 'e', value_enum, default_value_t)]169 encoding: OutputEncoding,59 },170 },60 /// Encrypt private part from stdin.171 /// Encrypt private part from stdin.61 Private {172 Private {62 #[arg(long)]173 #[arg(long, short = 'o')]63 allow_empty: bool,174 output: String,64 #[arg(short = 'r')]175 #[arg(long, short = 'e', value_enum, default_value_t)]65 recipient: Vec<String>,176 encoding: OutputEncoding,66 },177 },67 /// Generate keys in well-known schemas.178 /// Generate keys in well-known schemas.68 ///179 ///69 /// Note that this command is only intended to be used in fleet secret generator,180 /// Note that this command is only intended to be used in fleet secret generator,70 /// otherwise you should ensure noone is able to read generated files, they don't have any mode set by default.181 /// otherwise you should ensure noone is able to read generated files, they don't have any mode set by default.71 #[command(subcommand)]182 #[command(subcommand)]72 Generate(Generate),183 Generate(Generate),73 // Generate {74 // kind: GenerateKind,75 // /// Different generators generate different number of files, you need to specify number of outputs corresponding to the generator.76 // #[arg(short = 'o')]77 // outputs: Vec<String>,78 // },79}184}8081fn parse_stdin() -> Result<Option<Vec<u8>>> {82 let mut input = vec![];83 io::stdin().read_to_end(&mut input)?;84 if input.is_empty() {85 Ok(None)86 } else {87 Ok(Some(input))88 }89}90pub fn encrypt_secret_data(91 recipients: impl IntoIterator<Item = impl Recipient + Send + 'static>,92 data: Vec<u8>,93) -> Option<SecretData> {94 let mut encrypted = vec![];95 let recipients = recipients96 .into_iter()97 .map(|v| Box::new(v) as Box<dyn Recipient + Send>)98 .collect::<Vec<_>>();99 let mut encryptor = age::Encryptor::with_recipients(recipients)?100 .wrap_output(&mut encrypted)101 .expect("in memory write");102 io::copy(&mut Cursor::new(data), &mut encryptor).expect("in memory copy");103 encryptor.finish().expect("in memory flush");104 Some(SecretData {105 data: encrypted,106 encrypted: true,107 })108}109185110fn main() -> Result<()> {186fn main() -> Result<()> {111 let opts = Opts::parse();187 let opts = Opts::parse();112 // Assumed to be secure, seeded from secure OsRng+reseeded.188 // Assumed to be secure, seeded from secure OsRng+reseeded.113 let mut rng = thread_rng();189 let mut rng = thread_rng();114190115 match opts {191 match opts {116 Opts::Public { allow_empty } => {192 Opts::Public { output, encoding } => {117 let stdin = parse_stdin()?;118 if stdin.is_none() && !allow_empty {119 bail!("empty stdin input is not allowed unless --allow-empty is set");120 }121 let stdin = stdin.unwrap_or_default();122 io::stdout().write_all(193 write_public(&output, std::io::stdin(), encoding)?;123 SecretData {124 data: stdin,125 encrypted: false,126 }127 .to_string()128 .as_bytes(),129 )?;130 }194 }131 Opts::Private {195 Opts::Private { output, encoding } => {132 allow_empty,133 recipient,134 } => {135 let stdin = parse_stdin()?;196 let recipients = load_identities()?;136 if stdin.is_none() && !allow_empty {137 bail!("empty stdin input is not allowed unless --allow-empty is set");138 }139 let stdin = stdin.unwrap_or_default();140 if recipient.is_empty() {141 bail!("recipient list is empty");142 }143 let out = encrypt_secret_data(197 write_private(&recipients, &output, std::io::stdin(), encoding)?;144 recipient145 .into_iter()146 .map(|r| age::ssh::Recipient::from_str(&r))147 .collect::<Result<Vec<age::ssh::Recipient>, age::ssh::ParseRecipientKeyError>>()148 .map_err(|e| anyhow!("parse recipients: {e:?}"))?,149 stdin,150 )151 .expect("got recipients");152 io::stdout().write_all(out.to_string().as_bytes())?;153 }198 }154 Opts::Generate(gen) => {199 Opts::Generate(gen) => {155 let mut stdout_marker: bool = false;156 match gen {200 match gen {157 Generate::Ed25519 {201 Generate::Ed25519 {158 public,202 public,159 private,203 private,160 no_embed_public,204 no_embed_public,205 encoding,161 } => {206 } => {207 let recipients = load_identities()?;162 let key = SigningKey::generate(&mut rng).to_keypair_bytes();208 let key = ed25519_dalek::SigningKey::generate(&mut rng).to_keypair_bytes();163164 write_output(&public, &key[32..], &mut stdout_marker).context("public")?;209 write_public(&public, &key[32..], encoding)?;165 write_output(210 write_private(211 &recipients,166 &private,212 &private,167 &key[..{213 &key[..{168 if no_embed_public {214 if no_embed_public {171 64217 64172 }218 }173 }],219 }],174 &mut stdout_marker,220 encoding,175 )176 .context("private")?;221 )?;177 }222 }223 Generate::X25519 {224 public,225 private,226 encoding,227 } => {228 let recipients = load_identities()?;229 let key = x25519_dalek::StaticSecret::random_from_rng(rng);230 let public_key: x25519_dalek::PublicKey = (&key).into();231 write_public(&public, public_key.as_bytes().as_slice(), encoding)?;232 write_private(&recipients, &private, key.as_bytes().as_slice(), encoding)?;233 }178 Generate::Password {234 Generate::Password {179 size,235 size,180 no_symbols,236 no_symbols,181 output,237 output,238 encoding,182 } => {239 } => {183 ensure!(240 ensure!(184 size >= 6,241 size >= 6,185 "misconfiguration? password is shorter than 6 chars"242 "misconfiguration? password is shorter than 6 chars"186 );243 );244 let recipients = load_identities()?;187 let out = if no_symbols {245 let out = if no_symbols {188 Alphanumeric.sample_string(&mut rng, size)246 Alphanumeric.sample_string(&mut rng, size)189 } else {247 } else {195 .map(|i| GEN_ASCII_SYMBOLS[i] as char)253 .map(|i| GEN_ASCII_SYMBOLS[i] as char)196 .collect::<String>()254 .collect::<String>()197 };255 };198 write_output(&output, out, &mut stdout_marker)?;256 write_private(&recipients, &output, out.as_bytes(), encoding)?;199 }257 }200 }258 }201 }259 }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
- '';
-}