difftreelog
feat use builtin for getting secret
in: trunk
12 files changed
Cargo.lockdiffbeforeafterboth722source = "registry+https://github.com/rust-lang/crates.io-index"722source = "registry+https://github.com/rust-lang/crates.io-index"723checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"723checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"724725[[package]]726name = "convert_case"727version = "0.7.1"728source = "registry+https://github.com/rust-lang/crates.io-index"729checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"730dependencies = [731 "unicode-segmentation",732]733724734[[package]]725[[package]]735name = "cookie-factory"726name = "cookie-factory"764 "cfg-if",755 "cfg-if",765]756]766767[[package]]768name = "crossterm"769version = "0.29.0"770source = "registry+https://github.com/rust-lang/crates.io-index"771checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"772dependencies = [773 "bitflags",774 "crossterm_winapi",775 "derive_more",776 "document-features",777 "filedescriptor",778 "mio",779 "parking_lot",780 "rustix 1.1.2",781 "signal-hook",782 "signal-hook-mio",783 "winapi",784]785786[[package]]787name = "crossterm_winapi"788version = "0.9.1"789source = "registry+https://github.com/rust-lang/crates.io-index"790checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"791dependencies = [792 "winapi",793]794757795[[package]]758[[package]]796name = "crypto-common"759name = "crypto-common"934 "serde_core",897 "serde_core",935]898]936937[[package]]938name = "derive_more"939version = "2.0.1"940source = "registry+https://github.com/rust-lang/crates.io-index"941checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"942dependencies = [943 "derive_more-impl",944]945946[[package]]947name = "derive_more-impl"948version = "2.0.1"949source = "registry+https://github.com/rust-lang/crates.io-index"950checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"951dependencies = [952 "convert_case",953 "proc-macro2",954 "quote",955 "syn",956]957899958[[package]]900[[package]]959name = "digest"901name = "digest"978 "syn",920 "syn",979]921]980981[[package]]982name = "document-features"983version = "0.2.11"984source = "registry+https://github.com/rust-lang/crates.io-index"985checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"986dependencies = [987 "litrs",988]989922990[[package]]923[[package]]991name = "ed25519"924name = "ed25519"1072source = "registry+https://github.com/rust-lang/crates.io-index"1005source = "registry+https://github.com/rust-lang/crates.io-index"1073checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"1006checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"10741075[[package]]1076name = "filedescriptor"1077version = "0.8.3"1078source = "registry+https://github.com/rust-lang/crates.io-index"1079checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"1080dependencies = [1081 "libc",1082 "thiserror 1.0.69",1083 "winapi",1084]108510071086[[package]]1008[[package]]1087name = "find-crate"1009name = "find-crate"1128 "chrono",1050 "chrono",1129 "clap",1051 "clap",1130 "clap_complete",1052 "clap_complete",1131 "crossterm",1132 "fleet-base",1053 "fleet-base",1133 "fleet-shared",1054 "fleet-shared",1134 "futures",1055 "futures",1142 "openssh",1063 "openssh",1143 "opentelemetry",1064 "opentelemetry",1144 "opentelemetry_sdk",1065 "opentelemetry_sdk",1145 "owo-colors",1146 "peg",1066 "peg",1147 "regex",1067 "regex",1148 "serde",1068 "serde",1502source = "registry+https://github.com/rust-lang/crates.io-index"1422source = "registry+https://github.com/rust-lang/crates.io-index"1503checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"1423checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"15041505[[package]]1506name = "hermit-abi"1507version = "0.5.2"1508source = "registry+https://github.com/rust-lang/crates.io-index"1509checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"151014241511[[package]]1425[[package]]1512name = "hex"1426name = "hex"1963 "serde",1877 "serde",1964]1878]19651966[[package]]1967name = "is-terminal"1968version = "0.4.16"1969source = "registry+https://github.com/rust-lang/crates.io-index"1970checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"1971dependencies = [1972 "hermit-abi",1973 "libc",1974 "windows-sys 0.59.0",1975]19761977[[package]]1978name = "is_ci"1979version = "1.2.0"1980source = "registry+https://github.com/rust-lang/crates.io-index"1981checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"198218791983[[package]]1880[[package]]1984name = "is_terminal_polyfill"1881name = "is_terminal_polyfill"2084source = "registry+https://github.com/rust-lang/crates.io-index"1981source = "registry+https://github.com/rust-lang/crates.io-index"2085checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"1982checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"20862087[[package]]2088name = "litrs"2089version = "0.4.2"2090source = "registry+https://github.com/rust-lang/crates.io-index"2091checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"209219832093[[package]]1984[[package]]2094name = "lock_api"1985name = "lock_api"2161checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"2052checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"2162dependencies = [2053dependencies = [2163 "libc",2054 "libc",2164 "log",2165 "wasi 0.11.1+wasi-snapshot-preview1",2055 "wasi 0.11.1+wasi-snapshot-preview1",2166 "windows-sys 0.59.0",2056 "windows-sys 0.59.0",2167]2057]2427 "thiserror 2.0.17",2317 "thiserror 2.0.17",2428]2318]24292430[[package]]2431name = "owo-colors"2432version = "4.2.3"2433source = "registry+https://github.com/rust-lang/crates.io-index"2434checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"2435dependencies = [2436 "supports-color 2.1.0",2437 "supports-color 3.0.2",2438]243923192440[[package]]2320[[package]]2441name = "papergrid"2321name = "papergrid"3336source = "registry+https://github.com/rust-lang/crates.io-index"3216source = "registry+https://github.com/rust-lang/crates.io-index"3337checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"3217checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"33383339[[package]]3340name = "signal-hook"3341version = "0.3.18"3342source = "registry+https://github.com/rust-lang/crates.io-index"3343checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"3344dependencies = [3345 "libc",3346 "signal-hook-registry",3347]33483349[[package]]3350name = "signal-hook-mio"3351version = "0.2.4"3352source = "registry+https://github.com/rust-lang/crates.io-index"3353checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"3354dependencies = [3355 "libc",3356 "mio",3357 "signal-hook",3358]335932183360[[package]]3219[[package]]3361name = "signal-hook-registry"3220name = "signal-hook-registry"3448source = "registry+https://github.com/rust-lang/crates.io-index"3307source = "registry+https://github.com/rust-lang/crates.io-index"3449checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"3308checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"34503451[[package]]3452name = "supports-color"3453version = "2.1.0"3454source = "registry+https://github.com/rust-lang/crates.io-index"3455checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"3456dependencies = [3457 "is-terminal",3458 "is_ci",3459]34603461[[package]]3462name = "supports-color"3463version = "3.0.2"3464source = "registry+https://github.com/rust-lang/crates.io-index"3465checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6"3466dependencies = [3467 "is_ci",3468]346933093470[[package]]3310[[package]]3471name = "syn"3311name = "syn"4169source = "registry+https://github.com/rust-lang/crates.io-index"4009source = "registry+https://github.com/rust-lang/crates.io-index"4170checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"4010checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"41714172[[package]]4173name = "unicode-segmentation"4174version = "1.12.0"4175source = "registry+https://github.com/rust-lang/crates.io-index"4176checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"417740114178[[package]]4012[[package]]4179name = "unicode-width"4013name = "unicode-width"4450 "rustix 0.38.44",4284 "rustix 0.38.44",4451]4285]44524453[[package]]4454name = "winapi"4455version = "0.3.9"4456source = "registry+https://github.com/rust-lang/crates.io-index"4457checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"4458dependencies = [4459 "winapi-i686-pc-windows-gnu",4460 "winapi-x86_64-pc-windows-gnu",4461]44624463[[package]]4464name = "winapi-i686-pc-windows-gnu"4465version = "0.4.0"4466source = "registry+https://github.com/rust-lang/crates.io-index"4467checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"446842864469[[package]]4287[[package]]4470name = "winapi-util"4288name = "winapi-util"4475 "windows-sys 0.61.2",4293 "windows-sys 0.61.2",4476]4294]44774478[[package]]4479name = "winapi-x86_64-pc-windows-gnu"4480version = "0.4.0"4481source = "registry+https://github.com/rust-lang/crates.io-index"4482checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"448342954484[[package]]4296[[package]]4485name = "windows-core"4297name = "windows-core"README.adocdiffbeforeafterboth211 ];211 ];212 # And finally, I have secrets, which are shared between machines.212 # And finally, I have secrets, which are shared between machines.213 # Note that this example is somewhat wrong, as this goes not into the machine configuration, but to fleet configuration.213 # Note that this example is somewhat wrong, as this goes not into the machine configuration, but to fleet configuration.214 sharedSecrets = {214 secrets = {215 "ca.pem" = {215 "ca.pem" = {216 # This is just the public key, no need to regenerate it to change owner list216 # This is just the public key, no need to regenerate it to change owner list217 regenerateOnOwnerAdded = false;217 regenerateOnOwnerAdded = false;cmds/fleet/Cargo.tomldiffbeforeafterboth28async-trait = "0.1"28async-trait = "0.1"29base64 = "0.22.1"29base64 = "0.22.1"30chrono = { version = "0.4", features = ["serde"] }30chrono = { version = "0.4", features = ["serde"] }31crossterm = { version = "0.29.0", features = ["use-dev-tty"] }32futures = "0.3"31futures = "0.3"33hostname = "0.4.1"32hostname = "0.4.1"34itertools = "0.14"33itertools = "0.14"35openssh = "0.11"34openssh = "0.11"36owo-colors = { version = "4.2", features = ["supports-color", "supports-colors"] }37peg = "0.8"35peg = "0.8"38regex = "1.11"36regex = "1.11"39shlex = "1.3"37shlex = "1.3"cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth11 fleetdata::{FleetSecretData, FleetSecretDistribution, FleetSecretPart, encrypt_secret_data},11 fleetdata::{FleetSecretData, FleetSecretDistribution, FleetSecretPart, encrypt_secret_data},12 host::Config,12 host::Config,13 opts::FleetOpts,13 opts::FleetOpts,14 secret::{Expectations, RegenerationReason, SharedSecretDefinition, secret_needs_regeneration},14 secret::{Expectations, RegenerationReason, secret_needs_regeneration},15};15};16use fleet_shared::SecretData;16use fleet_shared::SecretData;17use nix_eval::{NixType, Value, nix_go, nix_go_json};17use nix_eval::{NixType, Value, nix_go, nix_go_json};18use owo_colors::OwoColorize;19use serde::Deserialize;18use serde::Deserialize;20use tabled::{Table, Tabled};19use tabled::{Table, Tabled};21use tokio::{fs::read, task::spawn_blocking};20use tokio::{fs::read, task::spawn_blocking};69 },68 },70}69}717072#[allow(clippy::too_many_arguments)]71/*73#[tracing::instrument(skip(config, secret, definition, prefer_identities))]72#[allow(clippy::too_many_arguments)]74async fn maybe_regenerate_shared_secret(73#[tracing::instrument(skip(config, secret, definition, prefer_identities))]75 secret_name: &str,74async fn maybe_regenerate_shared_secret(76 config: &Config,75 secret_name: &str,77 mut secret: FleetSecretDistribution,76 config: &Config,78 definition: SharedSecretDefinition,77 mut secret: FleetSecretDistribution,79 prefer_identities: &[String],78 definition: SharedSecretDefinition,80 expectations: &Expectations,79 prefer_identities: &[String],81) -> Result<FleetSecretDistribution> {80 expectations: &Expectations,82 let reason = secret_needs_regeneration(&secret.secret, &secret.owners, expectations);81) -> Result<FleetSecretDistribution> {83 let value = definition.definition_value();82 let reason = secret_needs_regeneration(&secret.secret, &secret.owners, expectations);8483 let value = definition.definition_value();85 let (should_reencrypt, reason) = match reason {8486 Some(RegenerationReason::OwnersAdded(_)) => {85 let (should_reencrypt, reason) = match reason {87 // Secret always needs to be reencrypted for new owners to be able to read it86 Some(RegenerationReason::OwnersAdded(_)) => {88 (87 // Secret always needs to be reencrypted for new owners to be able to read it89 true,88 (90 if nix_go_json!(value.regenerateOnOwnerAdded) {89 true,91 reason90 if nix_go_json!(value.regenerateOnOwnerAdded) {92 } else {91 reason93 None92 } else {94 },93 None95 )94 },96 }95 )97 Some(RegenerationReason::OwnersRemoved(_)) => {96 }98 // No need to reencrypt, we can just leave stanzas in place.97 Some(RegenerationReason::OwnersRemoved(_)) => {99 if nix_go_json!(value.regenerateOnOwnerRemoved) {98 // No need to reencrypt, we can just leave stanzas in place.100 (true, reason)99 if nix_go_json!(value.regenerateOnOwnerRemoved) {101 } else {100 (true, reason)102 (false, None)101 } else {103 }102 (false, None)104 }103 }105 Some(_) => (true, reason),104 }106 None => (false, None),105 Some(_) => (true, reason),107 };106 None => (false, None),108107 };109 if let Some(reason) = reason {108110 info!("secret needs to be regenerated: {reason}");109 if let Some(reason) = reason {111 let generated = generate_shared(config, secret_name, definition, expectations).await?;110 info!("secret needs to be regenerated: {reason}");112 Ok(generated)111 let generated = generate_shared(config, secret_name, definition, expectations).await?;113 } else if should_reencrypt {112 Ok(generated)114 info!("secret needs to be reencrypted");113 } else if should_reencrypt {115 let identity_holder = if !prefer_identities.is_empty() {114 info!("secret needs to be reencrypted");116 prefer_identities115 let identity_holder = if !prefer_identities.is_empty() {117 .iter()116 prefer_identities118 .find(|i| secret.owners.iter().any(|s| s == *i))117 .iter()119 } else {118 .find(|i| secret.owners.iter().any(|s| s == *i))120 secret.owners.first()119 } else {121 };120 secret.owners.first()122 let Some(identity_holder) = identity_holder else {121 };123 bail!("no available holder found");122 let Some(identity_holder) = identity_holder else {124 };123 bail!("no available holder found");125124 };126 for (part_name, part) in secret.secret.parts.iter_mut() {125127 let _span = info_span!("part reencryption", part_name);126 for (part_name, part) in secret.secret.parts.iter_mut() {128 if !part.raw.encrypted {127 let _span = info_span!("part reencryption", part_name);129 continue;128 if !part.raw.encrypted {130 }129 continue;131 let host = config.host(identity_holder).await?;130 }132 let encrypted = host131 let host = config.host(identity_holder).await?;133 .reencrypt(132 let encrypted = host134 part.raw.clone(),133 .reencrypt(135 expectations.owners.iter().cloned().collect(),134 part.raw.clone(),136 )135 expectations.owners.iter().cloned().collect(),137 .await?;136 )138 part.raw = encrypted;137 .await?;139 }138 part.raw = encrypted;140 secret.owners = expectations.owners.clone();139 }141 Ok(secret)140 secret.owners = expectations.owners.clone();142 } else {141 Ok(secret)143 Ok(secret)142 } else {144 }143 Ok(secret)145}144 }145}146*/146147147#[derive(Deserialize)]148#[derive(Deserialize)]148#[serde(rename_all = "camelCase")]149#[serde(rename_all = "camelCase")]314 }315 }315 }316 }316}317}317async fn generate_shared(318/*318 config: &Config,319async fn generate_shared(319 display_name: &str,320 config: &Config,320 secret: SharedSecretDefinition,321 display_name: &str,321 expectations: &Expectations,322 secret: SharedSecretDefinition,322) -> Result<FleetSecretDistribution> {323 expectations: &Expectations,323 // let owners: Vec<String> = nix_go_json!(secret.expectedOwners);324) -> Result<FleetSecretDistribution> {324 Ok(FleetSecretDistribution {325 // let owners: Vec<String> = nix_go_json!(secret.expectedOwners);325 managed: Some(true),326 Ok(FleetSecretDistribution {326 secret: generate(327 managed: Some(true),327 config,328 secret: generate(328 display_name,329 config,329 secret.definition_value(),330 display_name,330 expectations,331 secret.definition_value(),331 )332 expectations,332 .await?,333 )333 owners: expectations.owners.clone(),334 .await?,334 })335 owners: expectations.owners.clone(),335}336 })337}*/336338337async fn parse_public(339async fn parse_public(338 public: Option<String>,340 public: Option<String>,625 #[tabled(rename = "Owners")]627 #[tabled(rename = "Owners")]626 owners: String,628 owners: String,627 }629 }628 let mut table = vec![];630 // let mut table = vec![];629 for name in configured.iter().cloned() {631 for name in configured.iter().cloned() {630 let config = config.clone();632 let config = config.clone();631 let data = config.shared_secret(&name).expect("exists");633 let data = config.shared_secret(&name).expect("exists");632 let definition = config.shared_secret_definition(&name)?;634 /*633 let expectations = definition.expectations()?;635 let definition = config.shared_secret_definition(&name)?;634 let owners = data636 let expectations = definition.expectations()?;635 .owners()637 let owners = data636 .map(|o| {638 .owners()637 if expectations.owners.contains(o) {639 .map(|o| {638 o.green().to_string()640 if expectations.owners.contains(o) {639 } else {641 o.green().to_string()640 o.red().to_string()642 } else {641 }643 o.red().to_string()642 })644 }643 .collect::<Vec<_>>();645 })644 table.push(SecretDisplay {646 .collect::<Vec<_>>();645 owners: owners.join(", "),647 table.push(SecretDisplay {646 name,648 owners: owners.join(", "),647 })649 name,650 })651*/648 }652 }649 info!("loaded\n{}", Table::new(table).to_string())653 // info!("loaded\n{}", Table::new(table).to_string())650 }654 }651 Secret::Edit {655 Secret::Edit {652 name,656 name,crates/fleet-base/src/host.rsdiffbeforeafterboth23use crate::{23use crate::{24 command::MyCommand,24 command::MyCommand,25 fleetdata::{FleetData, FleetSecretData, FleetSecretDistribution, FleetSecretDistributions},25 fleetdata::{FleetData, FleetSecretData, FleetSecretDistribution, FleetSecretDistributions},26 secret::{HostSecretDefinition, SharedSecretDefinition},27};26};282729pub struct FleetConfigInternals {28pub struct FleetConfigInternals {30 /// Fleet project directory, containing fleet.nix file.29 /// Fleet project directory, containing fleet.nix file.31 pub directory: PathBuf,30 pub directory: PathBuf,32 /// builtins.currentSystem31 /// builtins.currentSystem33 pub local_system: String,32 pub local_system: String,34 pub data: Mutex<FleetData>,33 pub data: Arc<Mutex<FleetData>>,35 pub nix_args: Vec<OsString>,34 pub nix_args: Vec<OsString>,36 /// fleet_config.config35 /// fleet_config.config37 pub config_field: Value,36 pub config_field: Value,521 let secrets = nix_go!(nixos.secrets);520 let secrets = nix_go!(nixos.secrets);522 secrets.list_fields()521 secrets.list_fields()523 }522 }524 pub fn secret_definition(&self, name: &str) -> Result<HostSecretDefinition> {525 let nixos = self.nixos_unchecked_config()?;526 Ok(HostSecretDefinition(527 self.name.clone(),528 nix_go!(nixos.secrets[{ name }]),529 ))530 }531523532 /// Packages for this host, resolved with nixpkgs overlays524 /// Packages for this host, resolved with nixpkgs overlays533 pub async fn pkgs(&self) -> Result<Value> {525 pub async fn pkgs(&self) -> Result<Value> {666 let data = self.data();658 let data = self.data();667 data.secrets.get(secret).cloned()659 data.secrets.get(secret).cloned()668 }660 }669 pub fn shared_secret_definition(&self, secret: &str) -> Result<SharedSecretDefinition> {670 let config_field = &self.config_field;671 Ok(SharedSecretDefinition(nix_go!(672 config_field.sharedSecrets[{ secret }]673 )))674 }675661676 // TODO: Should this be something modifiable from other processes?662 // TODO: Should this be something modifiable from other processes?677 // E.g terraform provider might want to update FleetData (e.g secrets),663 // E.g terraform provider might want to update FleetData (e.g secrets),crates/fleet-base/src/opts.rsdiffbeforeafterboth211 }211 }212 let bytes =212 let bytes =213 std::fs::read_to_string(&fleet_data_path).context("reading fleet state (fleet.nix)")?;213 std::fs::read_to_string(&fleet_data_path).context("reading fleet state (fleet.nix)")?;214 let data = Mutex::new(FleetData::from_str(&bytes)?);214 let data = Arc::new(Mutex::new(FleetData::from_str(&bytes)?));215215216 let mut fetch_settings = FetchSettings::new();216 let mut fetch_settings = FetchSettings::new();217 fetch_settings.set(c"warn-dirty", c"false");217 fetch_settings.set(c"warn-dirty", c"false");239 let builtins_field = Value::eval("builtins")?;239 let builtins_field = Value::eval("builtins")?;240240241 let fleet_root = flake.get_field("fleetConfigurations")?;241 let fleet_root = flake.get_field("fleetConfigurations")?;242 let data_val = Value::serialized(&data)?;243 let fleet_field = nix_go!(fleet_root.default(data_val));242 let fleet_field = nix_go!(fleet_root.default(Obj {}));244243245 let config_field = nix_go!(fleet_field.config);244 let config_field = nix_go!(fleet_field.config);246245crates/fleet-base/src/primops.rsdiffbeforeafterboth1use std::collections::HashMap;2use std::sync::{Arc, Mutex};31use nix_eval::NativeFn;4use nix_eval::{NativeFn, Value};56use crate::fleetdata::{FleetData, FleetSecrets};273#[derive(thiserror::Error, Debug)]8#[derive(thiserror::Error, Debug)]4enum Error {}9enum Error {}1920}212422pub fn init_primops() {25pub fn init_primops(secrets: Arc<Mutex<FleetData>>) {23 NativeFn::new(26 NativeFn::new(24 c"fleet_ensure_secret",27 c"fleet_ensure_host_secret",25 c"Ensure secret existence for a host, regenerating it in case of some mismatch",28 c"Ensure secret existence for a host, regenerating it in case of some mismatch",26 [29 [c"host", c"secret", c"generator"],27 c"host",28 c"secret",29 c"expected_parts",30 c"expected_encrypted_parts",31 c"generator",32 ],33 |[30 |[host, secret, generator]| {34 host,35 secret,36 expected_parts,37 expected_encrypted_parts,38 generator,39 ]| { 4041 todo!()31 todo!("ensure secret");32 Ok(Value::new_attrs(HashMap::from_iter([(33 "raw",34 Value::new_str("rawData"),35 )])))42 },36 },43 )37 )44 .register();38 .register();39 NativeFn::new(40 c"fleet_ensure_host_secret",41 c"Ensure secret existence for a host, regenerating it in case of some mismatch",42 [c"host", c"secret", c"generator"],43 |[host, secret, generator]| {44 todo!("ensure secret");45 Ok(Value::new_attrs(HashMap::from_iter([(46 "raw",47 Value::new_str("rawData"),48 )])))49 },50 )51 .register();45}52}4653crates/fleet-base/src/secret.rsdiffbeforeafterboth1use std::collections::BTreeSet;1use std::collections::BTreeSet;223use anyhow::Result;4use chrono::{DateTime, Utc};3use chrono::{DateTime, Utc};5use nix_eval::{Value, nix_go, nix_go_json};647use crate::fleetdata::FleetSecretData;5use crate::fleetdata::FleetSecretData;8614 pub private_parts: BTreeSet<String>,12 pub private_parts: BTreeSet<String>,15}13}1617pub struct HostSecretDefinition(pub(crate) String, pub(crate) Value);18impl HostSecretDefinition {19 pub fn is_managed(&self) -> Result<bool> {20 let def = self.definition_value()?;21 Ok(!nix_go!(def.generator).is_null())22 }23 pub fn is_shared(&self) -> Result<bool> {24 let def = self.definition_value()?;25 Ok(nix_go_json!(def.shared))26 }27 pub fn expectations(&self) -> Result<Expectations> {28 let def = self.definition_value()?;29 let parts = nix_go!(def.parts);3031 let mut public_parts = BTreeSet::new();32 let mut private_parts = BTreeSet::new();33 for part in parts.list_fields()? {34 if nix_go_json!(parts[&part].encrypted) {35 private_parts.insert(part.clone());36 } else {37 public_parts.insert(part.clone());38 }39 }4041 Ok(Expectations {42 owners: BTreeSet::from([self.0.clone()]),43 generation_data: nix_go_json!(def.expectedGenerationData),44 public_parts,45 private_parts,46 })47 }48 pub fn definition_value(&self) -> Result<Value> {49 let value = &self.1;50 Ok(nix_go!(value.definition))51 }52}5354pub struct SharedSecretDefinition(pub(crate) Value);55impl SharedSecretDefinition {56 pub fn is_managed(&self) -> Result<bool> {57 let value = &self.0;58 Ok(!nix_go!(value.generator).is_null())59 }60 pub fn expectations(&self) -> Result<Expectations> {61 let value = &self.0;62 Ok(Expectations {63 owners: nix_go_json!(value.expectedOwners),64 generation_data: nix_go_json!(value.expectedGenerationData),65 public_parts: nix_go_json!(value.expectedPublicParts),66 private_parts: nix_go_json!(value.expectedPrivateParts),67 })68 }69 pub fn definition_value(&self) -> Value {70 self.0.clone()71 }72}731474#[derive(thiserror::Error, Debug)]15#[derive(thiserror::Error, Debug)]75pub enum RegenerationReason {16pub enum RegenerationReason {modules/module-list.nixdiffbeforeafterboth6 ./nixos.nix6 ./nixos.nix7 ./nixpkgs.nix7 ./nixpkgs.nix8 ./secrets.nix8 ./secrets.nix9 ./secrets-data.nix10]9]1110modules/nixos/secrets.nixdiffbeforeafterboth8let8let9 inherit (builtins)9 inherit (builtins)10 hashString10 hashString11 elemAt12 length13 toJSON11 toJSON14 filter15 ;12 ;16 inherit (lib.stringsWithDeps) stringAfter;13 inherit (lib.stringsWithDeps) stringAfter;17 inherit (lib.options) mkOption literalExpression;14 inherit (lib.options) mkOption literalExpression;18 inherit (lib.lists) optional;15 inherit (lib.lists) optional;19 inherit (lib.attrsets) mapAttrs mapAttrsToList;16 inherit (lib.attrsets) mapAttrs;20 inherit (lib.modules) mkIf mkMerge;17 inherit (lib.modules) mkIf;21 inherit (lib.types)18 inherit (lib.types)22 submodule19 submodule23 str20 str24 attrsOf21 attrsOf25 nullOr22 nullOr26 unspecified23 unspecified27 lazyAttrsOf28 uniq24 uniq29 functionTo25 functionTo30 package26 package31 listOf32 bool33 ;27 ;34 inherit (fleetLib.strings) decodeRawSecret;28 inherit (fleetLib.strings) decodeRawSecret;352936 sysConfig = config;30 sysConfig = config;37 secretPartDataType = submodule {38 options = {39 raw = mkOption {40 type = str;41 internal = true;42 description = "Encoded & Encrypted secret part data, passed from fleet.nix";43 };44 };45 };46 secretDataType = submodule {47 freeformType = lazyAttrsOf secretPartDataType;48 options = {49 shared = mkOption {50 description = "Is this secret owned by this machine, or propagated from shared secrets";51 default = false;52 };53 };54 };55 secretPartType =31 secretPartType =56 secretName:32 secretName:57 submodule (33 submodule (61 in37 in62 {38 {63 options = {39 options = {64 encrypted = mkOption {65 type = bool;66 description = "Is this secret part supposed to be encrypted?";67 };6869 hash = mkOption {40 hash = mkOption {70 type = str;41 type = str;82 type = str;53 type = str;83 description = "Secret public data (only available for plaintext)";54 description = "Secret public data (only available for plaintext)";84 };55 };56 raw = mkOption {57 type = str;58 description = "Raw (encoded/encrypted secret part data)";59 };85 };60 };86 config =61 config = {87 let88 raw = sysConfig.data.secrets.${secretName}.${partName}.raw;89 in90 {91 hash = hashString "sha1" raw;62 hash = hashString "sha1" config.raw;92 data = decodeRawSecret raw;63 data = decodeRawSecret config.raw;93 path = "/run/secrets/${secretName}/${config.hash}-${partName}";64 path = "/run/secrets/${secretName}/${config.hash}-${partName}";94 stablePath = "/run/secrets/${secretName}/${partName}";65 stablePath = "/run/secrets/${secretName}/${partName}";95 };66 };105 in76 in106 {77 {107 options = {78 options = {108 shared = mkOption {109 type = bool;110 description = "Was this secret propagated from a shared secret?";111 };112 parts = mkOption {79 parts = mkOption {113 type = lazyAttrsOf (secretPartType secretName);80 type = attrsOf (secretPartType secretName);114 description = "Definition of secret parts";81 description = "Definition of secret parts";115 default = { };116 };82 };117 generator = mkOption {83 generator = mkOption {118 type = uniq (nullOr (functionTo package));84 type = uniq (nullOr (functionTo package));135 default = sysConfig.users.users.${config.owner}.group;101 default = sysConfig.users.users.${config.owner}.group;136 defaultText = literalExpression "config.users.users.$${owner}.group";102 defaultText = literalExpression "config.users.users.$${owner}.group";137 };103 };138 expectedGenerationData = mkOption {139 type = unspecified;140 description = "Data that gets embedded into secret part";141 default = null;142 };143 };104 };144 config = {105 config = {145 shared = (sysConfig.data.secrets.${secretName} or { shared = false; }).shared;106 parts = builtins.fleet_ensure_host_secret sysConfig.networking.hostName secretName config.generator;146 parts = mkMerge [147 (mkIf (config.generator != null)148 (149 # Get fake derivation body, in future it should be implemented the same way as in Rust.150 lib.callPackageWith (151 pkgs152 // {153 mkSecretGenerator = pkgs.stdenv.mkDerivation;154 mkImpureSecretGenerator = pkgs.stdenv.mkDerivation;155 }156 ) config.generator { }157 ).parts158 )159 (mapAttrs (_: _: { }) (160 removeAttrs (sysConfig.data.secrets.${secretName} or { }) [161 "shared"162 "managed"163 ]164 ))165 ];166 };107 };167 }108 }168 );109 );169 processPart = secretName: partName: part: {170 inherit (part) path stablePath;171 raw = config.data.secrets.${secretName}.${partName}.raw;172 };173 processSecret = secretName: secret: {174 inherit (secret.definition) group mode owner;175 parts = (mapAttrs (processPart secretName) (secret.definition.parts));176 };177 secretsData = (mapAttrs (processSecret) config.secrets);110 secretsData = (mapAttrs (_: s: s.definition) config.secrets);178 secretsFile = pkgs.writeTextFile {111 secretsFile = pkgs.writeTextFile {179 name = "secrets.json";112 name = "secrets.json";180 text = toJSON secretsData;113 text = toJSON secretsData;185in118in186{119{187 options = {120 options = {188 data.secrets = mkOption {189 type = attrsOf secretDataType;190 default = { };191 description = "Host-local secret data";192 };193 secrets = mkOption {121 secrets = mkOption {194 type = attrsOf secretType;122 type = attrsOf secretType;195 default = { };123 default = { };modules/secrets-data.nixdiffbeforeafterbothno changes
modules/secrets.nixdiffbeforeafterboth5let5let6 inherit (lib.options) mkOption literalExpression;6 inherit (lib.options) mkOption literalExpression;7 inherit (lib.types)7 inherit (lib.types)8 unspecified9 nullOr8 nullOr10 listOf9 listOf11 str10 str68 '';67 '';69 default = null;68 default = null;70 };69 };71 expectedGenerationData = mkOption {72 type = unspecified;73 description = "Contextual metadata embedded within the secret part value";74 default = null;75 };76 expectedPrivateParts = mkOption {77 type = listOf str;78 default = [ ];79 description = "List of parts that are expected to be encrypted";80 };81 expectedPublicParts = mkOption {82 type = listOf str;83 default = [ ];84 description = "List of parts that are expected to be public";85 };86 };70 };87 };71 };88in72in