git.delta.rocks / jrsonnet / refs/commits / 67bf612dbfd3

difftreelog

refactor use generator helper for built-in secret generators

Yaroslav Bolyukin2024-06-28parent: #b56a5a3.patch.diff
in: trunk

11 files changed

modifiedCargo.lockdiffbeforeafterboth
808dependencies = [808dependencies = [
809 "age",809 "age",
810 "anyhow",810 "anyhow",
811 "base64 0.22.1",
811 "clap",812 "clap",
812 "ed25519-dalek",813 "ed25519-dalek",
813 "fleet-shared",814 "fleet-shared",
814 "rand",815 "rand",
816 "x25519-dalek",
815]817]
816818
817[[package]]819[[package]]
modifiedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
40 /// Secret public part40 /// Secret public part
41 #[clap(long)]41 #[clap(long)]
42 public: Option<String>,42 public: Option<String>,
43 /// How to name public secret part
44 #[clap(long, default_value = "public")]
45 public_name: String,
46 /// Load public part from specified file43 /// Load public part from specified file
47 #[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,
5754
55 /// How to name public secret part
56 #[clap(long, short = 'p', default_value = "public")]
57 public_part: String,
58 /// How to name private secret part
58 #[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 stdin
62 Add {63 Add {
63 /// Secret name64 /// Secret name
64 name: String,65 name: String,
65 /// Secret owners66 /// Secret owner
67 #[clap(short = 'm', long)]
66 machine: String,68 machine: String,
67 /// Override secret if already present69 /// Override secret if already present
68 #[clap(long)]70 #[clap(long)]
69 force: bool,71 force: bool,
70 /// Secret public part72 /// Secret public part
71 #[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 file
74 #[clap(long, default_value = "public")]76 #[clap(long)]
75 public_name: String,77 public_file: Option<PathBuf>,
76 /// Load public part from specified file78
79 /// How to name public secret part
77 #[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 part
80 #[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 host
84 Read {87 Read {
85 name: String,88 name: String,
89 #[clap(short = 'm', long)]
86 machine: String,90 machine: String,
8791
92 /// Which private secret part to read
88 #[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,
9398
94 #[clap(long)]99 #[clap(short = 'm', long)]
95 machines: Option<Vec<String>>,100 machine: Option<Vec<String>>,
96101
97 #[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>,
101106
102 /// Which host should we use to decrypt107 /// Which host should we use to decrypt
103 #[clap(long)]108 #[clap(long)]
104 prefer_identities: Vec<String>,109 prefer_identities: Vec<String>,
105
106 #[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, without
115 List {},117 List {},
116 Edit {118 Edit {
117 name: String,119 name: String,
118 machine: String,
119
120 #[clap(default_value = "secret")]120 #[clap(short = 'm', long)]
121 part: String,121 machine: String,
122122
123 #[clap(long)]123 #[clap(long)]
124 add: bool,124 add: bool,
125
126 /// Which private secret part to read
127 #[clap(short = 'p', long, default_value = "secret")]
128 part: String,
125 },129 },
126}130}
127131
220 };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);
224228
225 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 }));
233237
234 let generator = nix_go!(call_package(generator)(Obj {238 let generator = nix_go!(call_package(generator)(generators));
235 encrypt,
236 // rustfmt_please_newline
237 }));
238239
239 let generator = generator.build().await?;240 let generator = generator.build().await?;
240 let generator = generator241 let generator = generator
305 }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 access
309 // 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 build
310 // 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_newline
319 }));320 }));
321 let default_generator = nix_go!(default_call_package(generator)(generators));
320322
321 let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);323 let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);
322324
442 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).
452454
500 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).
562563
563 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 }
567568
568 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 )?;
575576
576 if target_machines.is_empty() {577 if target_machines.is_empty() {
modifiedcmds/fleet/src/host.rsdiffbeforeafterboth
95 let out = cmd.run_string().await?;95 let out = cmd.run_string().await?;
96 let mut lines = out.split('\n');96 let mut lines = out.split('\n');
97 if let Some(last) = lines.next_back() {97 if let Some(last) = lines.next_back() {
98 ensure!(last == "", "output of ls should end with newline");98 ensure!(last.is_empty(), "output of ls should end with newline");
99 }99 }
100 Ok(lines.map(ToOwned::to_owned).collect())100 Ok(lines.map(ToOwned::to_owned).collect())
101 }101 }
modifiedcmds/generator-helper/Cargo.tomldiffbeforeafterboth
6[dependencies]6[dependencies]
7age.workspace = true7age.workspace = true
8anyhow.workspace = true8anyhow.workspace = true
9base64 = "0.22.1"
9clap.workspace = true10clap.workspace = true
10ed25519-dalek = { version = "2.1", features = ["rand_core"] }11ed25519-dalek = { version = "2.1", features = ["rand_core"] }
11fleet-shared.workspace = true12fleet-shared.workspace = true
12rand = "0.8.5"13rand = "0.8.5"
14x25519-dalek = "2.0.1"
1315
modifiedcmds/generator-helper/src/main.rsdiffbeforeafterboth
1use 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};
77
8use 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};
1819
20fn 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)?;
30
31 let mut data = Vec::new();
32 copy(&mut input, &mut wrap_encoder(&mut data, encoding))?;
33
34 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)?;
52
22 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 };
62
29 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}
73
74type 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 coerced
97 })
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}
115
116#[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}
33124
34#[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 schema
37 /// (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 schema
141 /// (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}
52161
53#[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}
80
81fn 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 = recipients
96 .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}
109185
110fn 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();
114190
115 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 recipient
145 .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();
163
164 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 64
172 }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 }
modifiedflake.nixdiffbeforeafterboth
67 perSystem = {67 perSystem = {
68 config,68 config,
69 system,69 system,
70 pkgs,
70 ...71 ...
71 }: let72 }: let
72 # Can also be built for darwin, through it is not usual to deploy nixos systems from macos machines.73 # Can also be built for darwin, through it is not usual to deploy nixos systems from macos machines.
75 # It is not possible to deploy any host from armv6/armv7 hardware, and I don't think it even makes sense.76 # It is not possible to deploy any host from armv6/armv7 hardware, and I don't think it even makes sense.
76 deployerSystems = ["aarch64-linux" "x86_64-linux"];77 deployerSystems = ["aarch64-linux" "x86_64-linux"];
77 deployerSystem = builtins.elem system deployerSystems;78 deployerSystem = builtins.elem system deployerSystems;
78 pkgs = import nixpkgs {
79 inherit system;
80 overlays = [(rust-overlay.overlays.default)];
81 };
82 lib = pkgs.lib;79 lib = pkgs.lib;
83 rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;80 rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
84 craneLib = (crane.mkLib pkgs).overrideToolchain rust;81 craneLib = (crane.mkLib pkgs).overrideToolchain rust;
85 in {82 in {
83 _module.args.pkgs = import nixpkgs {
84 inherit system;
85 overlays = [(rust-overlay.overlays.default)];
86 };
86 # Reference fleet package should be built with nightly rust, specified in rust-toolchain.toml.87 # Reference fleet package should be built with nightly rust, specified in rust-toolchain.toml.
87 packages = lib.mkIf deployerSystem (let88 packages = lib.mkIf deployerSystem (let
88 packages = import ./pkgs {89 packages = import ./pkgs {
modifiedlib/fleetLib.nixdiffbeforeafterboth
4242
43 mkPassword = {size ? 32}: {43 mkPassword = {size ? 32}: {
44 coreutils,44 coreutils,
45 encrypt,
46 mkSecretGenerator,45 mkSecretGenerator,
46 ...
47 }:47 }:
48 mkSecretGenerator {48 mkSecretGenerator {
49 script = ''49 script = ''
50 mkdir $out50 mkdir $out
5151 gh generate password -o $out/secret --size ${toString size}
52 ${coreutils}/bin/tr -dc 'A-Za-z0-9!?%=' < /dev/random \
53 | ${coreutils}/bin/head -c ${toString size} \
54 | ${encrypt} > $out/secret
55 '';52 '';
56 };53 };
54
55 mkEd25519 = {
56 noEmbedPublic ? false,
57 encoding ? null,
58 }: {mkSecretGenerator, ...}:
59 mkSecretGenerator {
60 script = ''
61 mkdir $out
62 gh generate ed25519 -p $out/public -s $out/secret \
63 ${lib.optionalString noEmbedPublic "--no-embed-public"} \
64 ${lib.optionalString (encoding != null) "--encoding=${encoding}"}
65 '';
66 };
67
68 mkGarage = {}: mkEd25519 {noEmbedPublic = true;};
69
70 mkX25519 = {encoding ? null}: {mkSecretGenerator, ...}:
71 mkSecretGenerator {
72 script = ''
73 mkdir $out
74 gh generate x25519 -p $out/public -s $out/secret \
75 ${lib.optionalString (encoding != null) "--encoding=${encoding}"}
76 '';
77 };
78
79 mkWireguard = {}: mkX25519 {encoding = "base64";};
5780
58 mkRsa = {size ? 4096}: {81 mkRsa = {size ? 4096}: {
59 openssl,82 openssl,
60 encrypt,
61 mkSecretGenerator,83 mkSecretGenerator,
84 ...
62 }:85 }:
63 mkSecretGenerator {86 mkSecretGenerator {
64 script = ''87 script = ''
65 mkdir $out88 mkdir $out
6689
67 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}90 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}
68 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key91 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key
6992
70 sudo cat rsa_private.key | ${encrypt} > $out/secret93 cat rsa_private.key | gh private -o $out/secret
71 sudo cat rsa_public.key > $out/public94 cat rsa_public.key | gh public -o $out/public
72 '';95 '';
73 };96 };
74}97}
7598
modifiedmodules/fleet/secrets.nixdiffbeforeafterboth
130 overlays = [130 overlays = [
131 (final: prev: let131 (final: prev: let
132 lib = final.lib;132 lib = final.lib;
133 inherit (lib) strings concatMap;133 inherit (lib) strings;
134 inherit (strings) escapeShellArgs;134 inherit (strings) concatStringsSep;
135 in {135 in {
136 mkEncryptSecret = {136 mkSecretGenerators = {recipients}: rec {
137 rage ? prev.rage,
138 recipients,
139 }:
140 prev.writeShellScript "encryptor" ''
141 #!/bin/sh
142 exec ${rage}/bin/rage ${escapeShellArgs (concatMap (r: ["-r" r]) recipients)} -e "$@"
143 '';
144 # TODO: Move to fleet
145 # TODO: Merge both generators to one with consistent options syntax?137 # TODO: Merge both generators to one with consistent options syntax?
146 # Impure generator is built on local machine, then built closure is copied to remote machine,138 # Impure generator is built on local machine, then built closure is copied to remote machine,
147 # and then it is ran in inpure context, so that this generator may access HSMs and other things.139 # and then it is ran in inpure context, so that this generator may access HSMs and other things.
151 # (Some secrets-encryption-in-git/managed PKI solution is expected)143 # (Some secrets-encryption-in-git/managed PKI solution is expected)
152 impureOn ? null,144 impureOn ? null,
153 }:145 }:
154 (prev.writeShellScript "impureGenerator.sh" ''146 (prev.writeShellScript "impureGenerator.sh" ''
155 #!/bin/sh147 #!/bin/sh
156 set -eu148 set -eu
157149
158 # TODO: Provide tempdir from outside, to make it securely erasurable as needed?150 export GENERATOR_HELPER_IDENTITIES="${concatStringsSep "\n" recipients}";
151 export PATH=${final.fleet-generator-helper}/bin:$PATH
152
153 # TODO: Provide tempdir from outside, to make it securely erasurable as needed?
159 tmp=$(mktemp -d)154 tmp=$(mktemp -d)
160 cd $tmp155 cd $tmp
161 # cd /var/empty156 # cd /var/empty
162157
163 created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")158 created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")
164159
165 ${script}160 ${script}
166161
167 if ! test -d $out; then162 if ! test -d $out; then
168 echo "impure generator script did not produce expected \$out output"163 echo "impure generator script did not produce expected \$out output"
169 exit 1164 exit 1
170 fi165 fi
171166
172 echo -n $created_at > $out/created_at167 echo -n $created_at > $out/created_at
173 echo -n SUCCESS > $out/marker168 echo -n SUCCESS > $out/marker
174 '')169 '')
175 .overrideAttrs (old: {170 .overrideAttrs (old: {
176 passthru = {171 passthru = {
177 inherit impureOn;172 inherit impureOn;
178 generatorKind = "impure";173 generatorKind = "impure";
179 };174 };
180 });175 });
181 # Pure generators are disabled for now176 # Pure generators are disabled for now
182 mkSecretGenerator = {script}: final.mkImpureSecretGenerator {inherit script;};177 mkSecretGenerator = {script}: mkImpureSecretGenerator {inherit script;};
183178
184 # TODO: Implement consistent naming179 # TODO: Implement consistent naming
185 # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...180 # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...
186 # But for now, it is ran the same way as `impureSecretGenerator`, but on the local machine.181 # But for now, it is ran the same way as `impureSecretGenerator`, but on the local machine.
187 # mkSecretGenerator = {script}:182 # mkSecretGenerator = {script}:
188 # (prev.writeShellScript "generator.sh" ''183 # (prev.writeShellScript "generator.sh" ''
189 # #!/bin/sh184 # #!/bin/sh
190 # set -eu185 # set -eu
191 # # TODO: make nix daemon build secret, not just the script.186 # # TODO: make nix daemon build secret, not just the script.
192 # cd /var/empty187 # cd /var/empty
193 #188 #
194 # created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")189 # created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")
195 #190 #
196 # ${script}191 # ${script}
197 # if ! test -d $out; then192 # if ! test -d $out; then
198 # echo "impure generator script did not produce expected \$out output"193 # echo "impure generator script did not produce expected \$out output"
199 # exit 1194 # exit 1
200 # fi195 # fi
201 #196 #
202 # echo -n $created_at > $out/created_at197 # echo -n $created_at > $out/created_at
203 # echo -n SUCCESS > $out/marker198 # echo -n SUCCESS > $out/marker
204 # '')199 # '')
205 # .overrideAttrs (old: {200 # .overrideAttrs (old: {
206 # passthru = {201 # passthru = {
207 # generatorKind = "pure";202 # generatorKind = "pure";
208 # };203 # };
209 # # TODO: make nix daemon build secret, not just the script.204 # # TODO: make nix daemon build secret, not just the script.
210 # # __impure = true;205 # # __impure = true;
211 # });206 # });
207 };
212 })208 })
213 ];209 ];
214 };210 };
modifiedpkgs/default.nixdiffbeforeafterboth
2 callPackage,2 callPackage,
3 craneLib,3 craneLib,
4}: {4}: {
5 fleet = callPackage ./fleet.nix {inherit craneLib;};
5 fleet-install-secrets = callPackage ./fleet-install-secrets.nix {inherit craneLib;};6 fleet-install-secrets = callPackage ./fleet-install-secrets.nix {inherit craneLib;};
6 fleet = callPackage ./fleet.nix {inherit craneLib;};7 fleet-generator-helper = callPackage ./fleet-generator-helper.nix {inherit craneLib;};
7}8}
89
addedpkgs/fleet-generator-helper.nixdiffbeforeafterboth

no changes

deletedpkgs/generator-helper.nixdiffbeforeafterboth

no changes