difftreelog
fix shared generator condition
in: trunk
10 files changed
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/build_systems.rs
+++ b/cmds/fleet/src/cmds/build_systems.rs
@@ -114,7 +114,8 @@
set.spawn_local(
(async move {
- let built = match build_task(config.clone(), hostname.clone(), "toplevel-fleet").await
+ let built = match build_task(config.clone(), hostname.clone(), "toplevel-fleet")
+ .await
{
Ok(path) => path,
Err(e) => {
cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth1use std::{2 collections::{BTreeMap, BTreeSet, HashSet},3 io::{self, Read, Write, stdin, stdout},4 path::PathBuf,5};67use anyhow::{Context, Result, anyhow, bail, ensure};8use chrono::{DateTime, Utc};9use clap::Parser;10use fleet_base::{11 fleetdata::{FleetSecretData, FleetSecretDistribution, FleetSecretPart, encrypt_secret_data},12 host::Config,13 opts::FleetOpts,14 secret::{Expectations, RegenerationReason, secret_needs_regeneration},15};16use fleet_shared::SecretData;17use nix_eval::{NixType, Value, nix_go, nix_go_json};18use serde::Deserialize;19use tabled::{Table, Tabled};20use tokio::{fs::read, task::spawn_blocking};21use tracing::{Instrument, error, info, info_span, warn};2223#[derive(Parser)]24pub enum Secret {25 AddManager,26 /// Force load host keys for all defined hosts27 ForceKeys,28 /// Read secret from remote host, requires sudo on one of the owning hosts29 Read {30 /// Secret name to read31 name: String,3233 /// Distribution with what machine to read34 /// If not shared between multiple - defaults to single owner35 #[clap(short = 'm', long)]36 machine: Option<String>,3738 /// Which private secret part to read39 #[clap(short = 'p', long, default_value = "secret")]40 part: String,4142 /// Which host should we use to decrypt, in case if reencryption is required, without43 /// regeneration44 #[clap(long)]45 prefer_identities: Vec<String>,46 },47 Regenerate {48 /// Which host should we use to decrypt, in case if reencryption is required, without49 /// regeneration50 #[clap(long)]51 prefer_identities: Vec<String>,52 /// Only regenerate shared secrets53 #[clap(long)]54 skip_hosts: bool,55 },56 List {},57 Edit {58 name: String,59 #[clap(short = 'm', long)]60 machine: String,6162 #[clap(long)]63 add: bool,6465 /// Which private secret part to read66 #[clap(short = 'p', long, default_value = "secret")]67 part: String,68 },69}7071/*72#[allow(clippy::too_many_arguments)]73#[tracing::instrument(skip(config, secret, definition, prefer_identities))]74async fn maybe_regenerate_shared_secret(75 secret_name: &str,76 config: &Config,77 mut secret: FleetSecretDistribution,78 definition: SharedSecretDefinition,79 prefer_identities: &[String],80 expectations: &Expectations,81) -> Result<FleetSecretDistribution> {82 let reason = secret_needs_regeneration(&secret.secret, &secret.owners, expectations);83 let value = definition.definition_value();8485 let (should_reencrypt, reason) = match reason {86 Some(RegenerationReason::OwnersAdded(_)) => {87 // Secret always needs to be reencrypted for new owners to be able to read it88 (89 true,90 if nix_go_json!(value.regenerateOnOwnerAdded) {91 reason92 } else {93 None94 },95 )96 }97 Some(RegenerationReason::OwnersRemoved(_)) => {98 // No need to reencrypt, we can just leave stanzas in place.99 if nix_go_json!(value.regenerateOnOwnerRemoved) {100 (true, reason)101 } else {102 (false, None)103 }104 }105 Some(_) => (true, reason),106 None => (false, None),107 };108109 if let Some(reason) = reason {110 info!("secret needs to be regenerated: {reason}");111 let generated = generate_shared(config, secret_name, definition, expectations).await?;112 Ok(generated)113 } else if should_reencrypt {114 info!("secret needs to be reencrypted");115 let identity_holder = if !prefer_identities.is_empty() {116 prefer_identities117 .iter()118 .find(|i| secret.owners.iter().any(|s| s == *i))119 } else {120 secret.owners.first()121 };122 let Some(identity_holder) = identity_holder else {123 bail!("no available holder found");124 };125126 for (part_name, part) in secret.secret.parts.iter_mut() {127 let _span = info_span!("part reencryption", part_name);128 if !part.raw.encrypted {129 continue;130 }131 let host = config.host(identity_holder).await?;132 let encrypted = host133 .reencrypt(134 part.raw.clone(),135 expectations.owners.iter().cloned().collect(),136 )137 .await?;138 part.raw = encrypted;139 }140 secret.owners = expectations.owners.clone();141 Ok(secret)142 } else {143 Ok(secret)144 }145}146*/147148#[derive(Deserialize)]149#[serde(rename_all = "camelCase")]150enum GeneratorKind {151 Impure,152 Pure,153}154155async fn generate_pure(156 _config: &Config,157 _display_name: &str,158 _secret: Value,159 _default_generator: Value,160 _expectations: &Expectations,161) -> Result<FleetSecretData> {162 bail!("pure generators are broken for now")163}164async fn generate_impure(165 config: &Config,166 _display_name: &str,167 secret: Value,168 default_generator: Value,169 expectations: &Expectations,170) -> Result<FleetSecretData> {171 let generator = nix_go!(secret.generator);172 let on: Option<String> = nix_go_json!(default_generator.impureOn);173174 let nixpkgs = &config.nixpkgs;175176 let host = if let Some(on) = &on {177 config.host(on).await?178 } else {179 config.local_host()180 };181 let on_pkgs = host.pkgs().await?;182 let mk_secret_generators = nix_go!(on_pkgs.mkSecretGenerators);183184 let mut recipients = Vec::new();185 for owner in &expectations.owners {186 let key = config.key(owner).await?;187 recipients.push(key);188 }189 let generators = nix_go!(mk_secret_generators(Obj { recipients }));190 let pkgs_and_generators = on_pkgs.attrs_update(generators)?;191192 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));193194 let generator = nix_go!(call_package(generator)(Obj {}));195196 let generator = spawn_blocking(move || generator.build("out"))197 .await198 .expect("nix build shouldn't fail")?;199 let generator = host.remote_derivation(&generator).await?;200201 let out_parent = host.mktemp_dir().await?;202 let out = format!("{out_parent}/out");203204 let mut r#gen = host.cmd(generator).await?;205 r#gen.env("out", &out);206 if on.is_none() {207 // This path is local, thus we can feed `OsString` directly to env var... But I don't think that's necessary to handle.208 let project_path: String = config209 .directory210 .clone()211 .into_os_string()212 .into_string()213 .map_err(|s| anyhow!("fleet project path is not utf-8: {s:?}"))?;214 r#gen.env("FLEET_PROJECT", project_path);215 }216 r#gen.run().await.context("impure generator")?;217218 {219 let marker = host.read_file_text(format!("{out}/marker")).await?;220 ensure!(marker == "SUCCESS", "generation not succeeded");221 }222223 let mut parts = BTreeMap::new();224 for part in host.read_dir(&out).await? {225 if part == "created_at" || part == "expires_at" || part == "marker" {226 continue;227 }228 let contents: SecretData = host229 .read_file_text(format!("{out}/{part}"))230 .await?231 .parse()232 .map_err(|e| anyhow!("failed to decode secret {out:?} part {part:?}: {e}"))?;233 parts.insert(part.to_owned(), FleetSecretPart { raw: contents });234 }235236 let created_at = host.read_file_value(format!("{out}/created_at")).await?;237 let expires_at = host.read_file_value(format!("{out}/expires_at")).await.ok();238239 let new_data = FleetSecretData {240 created_at,241 expires_at,242 parts,243 generation_data: expectations.generation_data.clone(),244 };245246 if let Some(reason) = secret_needs_regeneration(&new_data, &expectations.owners, expectations) {247 bail!("newly generated secret needs to be regenerated: {reason}")248 }249250 Ok(new_data)251}252253async fn generate(254 config: &Config,255 display_name: &str,256 secret: Value,257 expectations: &Expectations,258) -> Result<FleetSecretData> {259 let generator = nix_go!(secret.generator);260 // Can't properly check on nix module system level261 {262 let gen_ty = generator.type_of();263 if matches!(gen_ty, NixType::Null) {264 bail!("secret has no generator defined, can't automatically generate it.");265 }266 if matches!(gen_ty, NixType::Attrs) {267 if !generator.has_field("__functor")? {268 bail!("generator should be functor, got {gen_ty:?}");269 }270 } else if matches!(gen_ty, NixType::Function) {271 bail!("generator should be functor, got {gen_ty:?}");272 }273 }274 let nixpkgs = &config.nixpkgs;275 let default_pkgs = &config.default_pkgs;276 let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);277 // Generators provide additional information in passthru, to access278 // passthru we should call generator, but information about where this generator is supposed to build279 // is located in passthru... Thus evaluating generator on host.280 //281 // Maybe it is also possible to do some magic with __functor?282 //283 // I don't want to make modules always responsible for additional secret data anyway,284 // so it should be in derivation, and not in the secret data itself.285 let generators = nix_go!(default_mk_secret_generators(Obj {286 recipients: <Vec<String>>::new(),287 }));288 let pkgs_and_generators = default_pkgs.clone().attrs_update(generators)?;289290 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));291 let default_generator = nix_go!(call_package(generator)(Obj {}));292293 let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);294295 match kind {296 GeneratorKind::Impure => {297 generate_impure(298 config,299 display_name,300 secret,301 default_generator,302 expectations,303 )304 .await305 }306 GeneratorKind::Pure => {307 generate_pure(308 config,309 display_name,310 secret,311 default_generator,312 expectations,313 )314 .await315 }316 }317}318/*319async fn generate_shared(320 config: &Config,321 display_name: &str,322 secret: SharedSecretDefinition,323 expectations: &Expectations,324) -> Result<FleetSecretDistribution> {325 // let owners: Vec<String> = nix_go_json!(secret.expectedOwners);326 Ok(FleetSecretDistribution {327 managed: Some(true),328 secret: generate(329 config,330 display_name,331 secret.definition_value(),332 expectations,333 )334 .await?,335 owners: expectations.owners.clone(),336 })337}*/338339async fn parse_public(340 public: Option<String>,341 public_file: Option<PathBuf>,342) -> Result<Option<SecretData>> {343 Ok(match (public, public_file) {344 (Some(v), None) => Some(SecretData {345 data: v.into(),346 encrypted: false,347 }),348 (None, Some(v)) => Some(SecretData {349 data: read(v).await?,350 encrypted: false,351 }),352 (Some(_), Some(_)) => {353 bail!("only public or public_file should be set")354 }355 (None, None) => None,356 })357}358359async fn parse_secret() -> Result<Option<Vec<u8>>> {360 let mut input = vec![];361 stdin().read_to_end(&mut input)?;362 if input.is_empty() {363 Ok(None)364 } else {365 Ok(Some(input))366 }367}368369fn parse_machines(370 initial: BTreeSet<String>,371 machines: Option<Vec<String>>,372 mut add_machines: Vec<String>,373 mut remove_machines: Vec<String>,374) -> Result<BTreeSet<String>> {375 if machines.is_none() && add_machines.is_empty() && remove_machines.is_empty() {376 bail!("no operation");377 }378379 let initial_machines = initial.clone();380 let mut target_machines = initial;381 info!("Currently encrypted for {initial_machines:?}");382383 if let Some(machines) = machines {384 ensure!(385 add_machines.is_empty() && remove_machines.is_empty(),386 "can't combine --machines and --add-machines/--remove-machines"387 );388 let target = initial_machines.iter().collect::<HashSet<_>>();389 let source = machines.iter().collect::<HashSet<_>>();390 for removed in target.difference(&source) {391 remove_machines.push((*removed).clone());392 }393 for added in source.difference(&target) {394 add_machines.push((*added).clone());395 }396 }397398 for machine in &remove_machines {399 if !target_machines.remove(machine) {400 warn!("secret is not enabled for {machine}");401 }402 }403 for machine in &add_machines {404 if !target_machines.insert(machine.to_owned()) {405 warn!("secret is already added to {machine}");406 }407 }408 if !remove_machines.is_empty() {409 // TODO: maybe force secret regeneration?410 // Not that useful without revokation.411 warn!(412 "secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret"413 );414 }415 Ok(target_machines)416}417impl Secret {418 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {419 match self {420 Secret::AddManager => {421 todo!("part of fleet-pusher")422 }423 Secret::ForceKeys => {424 for host in config.list_hosts().await? {425 if opts.should_skip(&host).await? {426 continue;427 }428 config.key(&host.name).await?;429 }430 }431 Secret::Read {432 name,433 machine,434 part: part_name,435 mut prefer_identities,436 } => {437 let Some(secret) = config.shared_secret(&name) else {438 bail!("secret doesn't exists");439 };440441 let dist = if secret.len() == 1 {442 &secret[0]443 } else if let Some(machine) = machine {444 let dist = secret.get(&machine);445 let Some(dist) = dist else {446 bail!("machine {machine} has no distribution of secret {name}");447 };448 prefer_identities.push(machine);449 dist450 } else {451 bail!(452 "secret {name} has shares, but no --machine specified for specifing which do you need"453 )454 };455456 let Some(part) = dist.secret.parts.get(&part_name) else {457 bail!("no part {part_name} in secret {name}");458 };459 let data = if part.raw.encrypted {460 let identity_holder = if !prefer_identities.is_empty() {461 prefer_identities462 .iter()463 .find(|i| dist.owners.iter().any(|s| s == *i))464 } else {465 dist.owners.first()466 };467 let Some(identity_holder) = identity_holder else {468 bail!("no available holder found");469 };470 let host = config.host(identity_holder).await?;471 host.decrypt(part.raw.clone()).await?472 } else {473 part.raw.data.clone()474 };475 stdout().write_all(&data)?;476 }477 Secret::Regenerate {478 prefer_identities,479 skip_hosts,480 } => {481 /*482 info!("checking for secrets to regenerate");483 let expected_shared_set = config484 .list_configured_shared()485 .await?486 .into_iter()487 .collect::<HashSet<_>>();488 let stored_shared_set = config.list_secrets().into_iter().collect::<HashSet<_>>();489 {490 // Generate missing shared491 let _span = info_span!("shared").entered();492 for missing in expected_shared_set.difference(&stored_shared_set) {493 let definition = config.shared_secret_definition(missing)?;494 if !definition.is_managed()? {495 info!("skipping unmanaged secret: {missing}");496 continue;497 }498 let expectations = definition499 .expectations()500 .with_context(|| format!("expectations for shared {missing:?}"))?;501 info!("generating secret: {missing}");502 let shared = generate_shared(config, missing, definition, &expectations)503 .in_current_span()504 .await?;505 config.replace_shared(missing.to_string(), shared)506 }507 }508 if !skip_hosts {509 for host in config.list_hosts().await? {510 if opts.should_skip(&host).await? {511 continue;512 }513514 let _span = info_span!("host", host = host.name).entered();515 let expected_set = host516 .list_defined_secrets()?517 .into_iter()518 .collect::<HashSet<_>>();519 let stored_set = config520 .list_secrets_for_owner(&host.name)521 .into_iter()522 .collect::<HashSet<_>>();523 for missing_secret in expected_set.difference(&stored_set) {524 let secret = host.secret_definition(missing_secret)?;525 if secret.is_shared()? {526 continue;527 }528 info!("generating missing secret: {missing_secret}");529 let expectations = secret.expectations().with_context(|| {530 format!("expectations for {missing_secret:?} of {:?}", host.name)531 })?;532 let generated = match generate(533 config,534 missing_secret,535 secret.definition_value()?,536 &expectations,537 )538 .in_current_span()539 .await540 {541 Ok(v) => v,542 Err(e) => {543 error!("{e:?}");544 continue;545 }546 };547 config.insert_secret(host.name, missing_secret.to_string(), generated)548 }549 for known_secret in stored_set.intersection(&expected_set) {550 let secret = host.secret_definition(known_secret)?;551 if secret.is_shared()? {552 continue;553 }554 info!("updating secret: {known_secret}");555 let data = config.host_secret(&host.name, known_secret)?;556 let expectations = secret.expectations()?;557 if let Some(regen_reason) = data.needs_regeneration(&expectations) {558 info!("needs regeneration: {regen_reason}");559 let generated = match generate(560 config,561 known_secret,562 secret.definition_value()?,563 &expectations,564 )565 .in_current_span()566 .await567 {568 Ok(v) => v,569 Err(e) => {570 error!("{e:?}");571 continue;572 }573 };574 config.insert_secret(575 &host.name,576 known_secret.to_string(),577 FleetLegacyHostSecret {578 managed: Some(true),579 secret: generated,580 },581 )582 }583 }584 for removed_secret in stored_set.difference(&expected_set) {585 let definition = host.secret_definition(removed_secret)?;586 if definition.is_shared()? {587 continue;588 }589 info!("removing secret: {removed_secret}");590 config.remove_secret(&host.name, removed_secret);591 }592 }593 }594 for known_secret in stored_shared_set.intersection(&expected_shared_set) {595 info!("updating shared secret: {known_secret}");596 let data = config.shared_secret(known_secret)?.expect("exists");597598 let definition = config.shared_secret_definition(known_secret)?;599 let expectations = definition.expectations()?;600 config.replace_shared(601 known_secret.to_owned(),602 maybe_regenerate_shared_secret(603 known_secret,604 config,605 data,606 definition,607 &prefer_identities,608 &expectations,609 )610 .await?,611 );612 }613 for removed_secret in stored_shared_set.difference(&expected_shared_set) {614 info!("removing shared secret: {removed_secret}");615 config.remove_shared(removed_secret);616 }617 */618 todo!()619 }620 Secret::List {} => {621 let _span = info_span!("loading secrets").entered();622 let configured = config.list_configured_shared().await?;623 #[derive(Tabled)]624 struct SecretDisplay {625 #[tabled(rename = "Name")]626 name: String,627 #[tabled(rename = "Owners")]628 owners: String,629 }630 // let mut table = vec![];631 for name in configured.iter().cloned() {632 let config = config.clone();633 let data = config.shared_secret(&name).expect("exists");634 /*635 let definition = config.shared_secret_definition(&name)?;636 let expectations = definition.expectations()?;637 let owners = data638 .owners()639 .map(|o| {640 if expectations.owners.contains(o) {641 o.green().to_string()642 } else {643 o.red().to_string()644 }645 })646 .collect::<Vec<_>>();647 table.push(SecretDisplay {648 owners: owners.join(", "),649 name,650 })651*/652 }653 // info!("loaded\n{}", Table::new(table).to_string())654 }655 Secret::Edit {656 name,657 machine,658 part,659 add,660 } => {661 let secret = config662 .host_secret(&machine, &name)663 .context("secret not found")?;664 if let Some(data) = secret.secret.parts.get(&part) {665 let host = config.host(&machine).await?;666 let secret = host.decrypt(data.raw.clone()).await?;667 String::from_utf8(secret).context("secret is not utf8")?668 } else if add {669 String::new()670 } else {671 bail!("part {part} not found in secret {name}. Did you mean to `--add` it?");672 };673 }674 }675 Ok(())676 }677}678679/*680async fn edit_temp_file(681 builder: tempfile::Builder<'_, '_>,682 r: Vec<u8>,683 header: &str,684 comment: &str,685) -> Result<(Vec<u8>, Option<String>), anyhow::Error> {686 if !stdin().is_tty() {687 // TODO: Also try to open /dev/tty directly?688 bail!("stdin is not tty, can't open editor");689 }690691 use std::fmt::Write;692 let mut file = builder.tempfile()?;693694 let mut full_header = String::new();695 let mut had = false;696 for line in header.trim_end().lines() {697 had = true;698 writeln!(&mut full_header, "{comment}{line}")?;699 }700 if had {701 writeln!(&mut full_header, "{}", comment.trim_end())?;702 }703 writeln!(704 &mut full_header,705 "{comment}Do not touch this header! It will be removed automatically"706 )?;707708 file.write_all(full_header.as_bytes())?;709 file.write_all(&r)?;710711 let abs_path = file.into_temp_path();712 let editor = std::env::var_os("VISUAL")713 .or_else(|| std::env::var_os("EDITOR"))714 .unwrap_or_else(|| "vi".into());715 let editor_args = shlex::bytes::split(editor.as_encoded_bytes())716 .ok_or_else(|| anyhow!("EDITOR env var has wrong syntax"))?;717 let editor_args = editor_args718 .into_iter()719 .map(|v| {720 // Only ASCII subsequences are replaced721 unsafe { OsString::from_encoded_bytes_unchecked(v) }722 })723 .collect_vec();724 let Some((editor, args)) = editor_args.split_first() else {725 bail!("EDITOR env var has no command");726 };727 let mut command = Command::new(editor);728 command.args(args);729730 let path_arg = abs_path.canonicalize()?;731732 // TODO: Save full state, using tcget/_getmode/_setmode733 let was_raw = terminal::is_raw_mode_enabled()?;734 terminal::enable_raw_mode()?;735736 let status = command.arg(path_arg).status().await;737738 if !was_raw {739 terminal::disable_raw_mode()?;740 }741742 let success = match status {743 Ok(s) => s.success(),744 Err(e) if e.kind() == io::ErrorKind::NotFound => {745 bail!("editor not found")746 }747 Err(e) => bail!("editor spawn error: {e}"),748 };749750 let mut file = std::fs::read(&abs_path).context("read editor output")?;751 let Some(v) = file.strip_prefix(full_header.as_bytes()) else {752 todo!();753 };754 todo!();755756 // Ok((success, abs_path))757}758*/crates/fleet-base/src/fleetdata.rsdiffbeforeafterboth--- a/crates/fleet-base/src/fleetdata.rs
+++ b/crates/fleet-base/src/fleetdata.rs
@@ -153,7 +153,7 @@
#[serde(flatten)]
pub secret: FleetSecretData,
- #[serde(default, skip_serializing, alias="managed")]
+ #[serde(default, skip_serializing, alias = "managed")]
pub _deprecated_managed: bool,
}
crates/nix-eval/src/util.rsdiffbeforeafterboth--- a/crates/nix-eval/src/util.rs
+++ b/crates/nix-eval/src/util.rs
@@ -1,15 +1,23 @@
use std::time::Instant;
use anyhow::bail;
+use serde::Deserialize;
use tracing::{debug, warn};
use crate::{Value, nix_go_json};
+#[derive(Deserialize, Debug)]
+struct Assertion {
+ assertion: bool,
+ message: String,
+}
+
#[tracing::instrument(level = "info", skip(val))]
pub async fn assert_warn(action: &str, val: &Value) -> anyhow::Result<()> {
let before_errors = Instant::now();
let errors: Vec<String> = nix_go_json!(val.errors);
- debug!("errors evaluation took {:?}", before_errors.elapsed());
+ // let assertions: Vec<Assertion> = nix_go_json!(val.assertions);
+ debug!("errors evaluation took {:?} {errors:?} ", before_errors.elapsed());
if !errors.is_empty() {
bail!(
"failed with error{}{}",
crates/nixlike/Cargo.tomldiffbeforeafterboth--- a/crates/nixlike/Cargo.toml
+++ b/crates/nixlike/Cargo.toml
@@ -7,10 +7,10 @@
[dependencies]
thiserror.workspace = true
+itertools = "0.14.0"
linked-hash-map = "0.5.6"
peg = "0.8.5"
ron = "0.11.0"
serde = { version = "1.0.219", features = ["derive"] }
serde-transcode = "1.1.1"
serde_json = "1.0.140"
-itertools = "0.14.0"
lib/default.nixdiffbeforeafterboth--- a/lib/default.nix
+++ b/lib/default.nix
@@ -150,7 +150,10 @@
);
mkAskPass =
- { prompt ? "Secret value", part ? "secret" }:
+ {
+ prompt ? "Secret value",
+ part ? "secret",
+ }:
(
{
kdePackages,
lib/flakePart.nixdiffbeforeafterboth--- a/lib/flakePart.nix
+++ b/lib/flakePart.nix
@@ -34,7 +34,7 @@
# to do that, evaluate all the modules with only needed option declared.
bootstrapEval = lib.evalModules {
class = "fleet";
- prefix = ["fleetConfiguration"];
+ prefix = [ "fleetConfiguration" ];
modules = [
module
{
@@ -53,7 +53,7 @@
bootstrapNixpkgs = bootstrapEval.config.nixpkgs.buildUsing;
normalEval = bootstrapNixpkgs.lib.evalModules {
class = "fleet";
- prefix = ["fleetConfiguration"];
+ prefix = [ "fleetConfiguration" ];
modules = (import ../modules/module-list.nix) ++ [
module
(
modules/nixos.nixdiffbeforeafterboth--- a/modules/nixos.nix
+++ b/modules/nixos.nix
@@ -39,13 +39,23 @@
in
config.nixpkgs.buildUsing.lib.evalModules {
class = "nixos";
- prefix = ["fleetConfiguration" "hosts" hostArgs.config._module.args.name "nixos"];
+ prefix = [
+ "fleetConfiguration"
+ "hosts"
+ hostArgs.config._module.args.name
+ "nixos"
+ ];
modules = (import "${modulesPath}/module-list.nix") ++ [
(module // { key = "attr<host.nixos>"; })
(config.nixos // { key = "attr<fleet.nixos>"; })
];
specialArgs = {
- inherit fleetLib inputs self modulesPath;
+ inherit
+ fleetLib
+ inputs
+ self
+ modulesPath
+ ;
};
};
};
@@ -54,32 +64,34 @@
};
};
config = {
- nixos = let
- inherit (hostArgs.config) system;
- in {
- _module.args = {
- nixosHosts = mapAttrs (_: value: value.nixos_unchecked.config) config.hosts;
- hosts = config.hosts;
- host = hostArgs.config;
- fleetConfiguration = config;
+ nixos =
+ let
+ inherit (hostArgs.config) system;
+ in
+ {
+ _module.args = {
+ nixosHosts = mapAttrs (_: value: value.nixos_unchecked.config) config.hosts;
+ hosts = config.hosts;
+ host = hostArgs.config;
+ fleetConfiguration = config;
- inputs' = mapAttrs (
- inputName: input:
- builtins.addErrorContext
- "while retrieving system-dependent attributes for input ${escapeNixIdentifier inputName}"
- (
- if input._type or null == "flake" then
- _fleetFlakeRootConfig.perInput system input
- else
- "input is not a flake, perhaps flake = false was added to te input declaration?"
- )
- ) inputs;
- self' = builtins.addErrorContext "while retrieving system-dependent attributes for a flake's own outputs" (
- _fleetFlakeRootConfig.perInput system self
- );
+ inputs' = mapAttrs (
+ inputName: input:
+ builtins.addErrorContext
+ "while retrieving system-dependent attributes for input ${escapeNixIdentifier inputName}"
+ (
+ if input._type or null == "flake" then
+ _fleetFlakeRootConfig.perInput system input
+ else
+ "input is not a flake, perhaps flake = false was added to te input declaration?"
+ )
+ ) inputs;
+ self' = builtins.addErrorContext "while retrieving system-dependent attributes for a flake's own outputs" (
+ _fleetFlakeRootConfig.perInput system self
+ );
+ };
+ nixpkgs.hostPlatform = system;
};
- nixpkgs.hostPlatform = system;
- };
nixos_unchecked = hostArgs.config.nixos.extendModules {
modules = [
{
modules/nixos/secrets.nixdiffbeforeafterboth--- a/modules/nixos/secrets.nix
+++ b/modules/nixos/secrets.nix
@@ -77,7 +77,7 @@
}:
let
secretName = config._module.args.name;
- literal = l: enum [l];
+ literal = l: enum [ l ];
in
{
options = {
@@ -109,17 +109,16 @@
config = {
# C api is broken in regard to thunks
# https://github.com/NixOS/nix/issues/12800
- parts = let
- hostName = host._module.args.name;
- generator = config.generator;
- in builtins.deepSeq [
- hostName
- secretName
- generator
- ] (builtins.fleetEnsureHostSecret
- hostName
- secretName
- generator);
+ parts =
+ let
+ hostName = host._module.args.name;
+ generator = config.generator;
+ in
+ builtins.deepSeq [
+ hostName
+ secretName
+ generator
+ ] (builtins.fleetEnsureHostSecret hostName secretName generator);
};
}
);
@@ -136,14 +135,16 @@
secrets = mkOption {
type = attrsOf secretType;
default = { };
- apply = mapAttrs (_: secret: secret.parts // {definition = secret;});
+ apply = mapAttrs (_: secret: secret.parts // { definition = secret; });
description = "Host-local secrets";
};
system.secretsData = mkOption {
type = unspecified;
- default = mapAttrs (_: s:
- (removeAttrs s.definition ["generator"]) // {
- parts = mapAttrs (_: part: removeAttrs part ["data"]) s.definition.parts;
+ default = mapAttrs (
+ _: s:
+ (removeAttrs s.definition [ "generator" ])
+ // {
+ parts = mapAttrs (_: part: removeAttrs part [ "data" ]) s.definition.parts;
}
) config.secrets;
description = "secrets.json contents";
@@ -152,13 +153,25 @@
config = {
environment.systemPackages = [ pkgs.fleet-install-secrets ];
- assertions = mapAttrsToList (name: secret: let
- hasSharedDefinition = fleetConfiguration.secrets ? name;
- in {
- assertion = (secret.definition.generator == "shared") == hasSharedDefinition && hasSharedDefinition -> (elem host._module.args.name fleetConfiguration.secrets.${name}.expectedOwners);
- message = if hasSharedDefinition then"secret ${name} has host-specific secret generator, secrets with host-specific generators can not have shared generator in fleet configuration"
- else "secret ${name} is declared as shared, for shared secret fleet configuration should include shared secret generator, and expectedOwners should contain this host";
- }) config.secrets;
+ assertions = mapAttrsToList (
+ name: secret:
+ let
+ hasSharedDefinition = fleetConfiguration.secrets ? ${name};
+ in
+ {
+ assertion =
+ (secret.definition.generator == "shared") == hasSharedDefinition
+ && (
+ hasSharedDefinition
+ -> (elem host._module.args.name fleetConfiguration.secrets.${name}.expectedOwners)
+ );
+ message =
+ if hasSharedDefinition then
+ "secret ${name} has host-specific secret generator, secrets with host-specific generators can not have shared generator in fleet configuration"
+ else
+ "secret ${name} is declared as shared, for shared secret fleet configuration should include shared secret generator, and expectedOwners should contain this host";
+ }
+ ) config.secrets;
systemd.services.fleet-install-secrets = mkIf useSysusers {
wantedBy = [ "sysinit.target" ];
modules/nixos/top-level.nixdiffbeforeafterboth--- a/modules/nixos/top-level.nix
+++ b/modules/nixos/top-level.nix
@@ -2,6 +2,7 @@
pkgs,
config,
lib,
+ ...
}:
let
inherit (lib.strings) optionalString;