difftreelog
feat mkAskPass generator
in: trunk
8 files changed
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,3 +23,8 @@
tokio = { version = "1.45.1", features = ["fs", "macros", "rt", "rt-multi-thread", "sync", "time"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
+
+[profile.dev]
+panic = "abort"
+[profile.release]
+panic = "abort"
crates/fleet-base/src/fleetdata.rsdiffbeforeafterboth1use std::{2 collections::{3 BTreeMap, BTreeSet,4 btree_map::{self, Entry},5 },6 io::{self, Cursor},7 ops::Deref,8};910use age::Recipient;11use chrono::{DateTime, Utc};12use fleet_shared::SecretData;13use rand::{14 distr::{Alphanumeric, SampleString as _},15 rng,16};17use serde::{18 Deserialize, Serialize,19 de::{self, Error},20};21use serde_json::Value;22use tracing::info;2324#[derive(Serialize, Deserialize, Default)]25#[serde(rename_all = "camelCase")]26pub struct HostData {27 #[serde(default)]28 #[serde(skip_serializing_if = "String::is_empty")]29 pub encryption_key: String,30}3132const VERSION: &str = "0.1.0";33pub struct FleetDataVersion;34impl Serialize for FleetDataVersion {35 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>36 where37 S: serde::Serializer,38 {39 VERSION.serialize(serializer)40 }41}42impl<'de> Deserialize<'de> for FleetDataVersion {43 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>44 where45 D: serde::Deserializer<'de>,46 {47 let version = String::deserialize(deserializer)?;48 if version != VERSION {49 return Err(D::Error::custom(format!(50 "fleet.nix data version mismatch, expected {VERSION}, got {version}.\nFollow the docs for migration instruction"51 )));52 }53 Ok(Self)54 }55}5657fn generate_gc_prefix() -> String {58 let id = Alphanumeric.sample_string(&mut rng(), 8);59 format!("fleet-gc-{id}")60}6162#[derive(Serialize, Deserialize)]63#[serde(rename_all = "camelCase")]64pub struct ManagerKey {65 pub name: String,66 pub key: String,67}6869#[derive(Serialize, Deserialize)]70#[serde(rename_all = "camelCase")]71pub struct FleetData {72 pub version: FleetDataVersion,73 #[serde(default = "generate_gc_prefix")]74 pub gc_root_prefix: String,7576 #[serde(default, skip_serializing_if = "Vec::is_empty")]77 pub manager_keys: Vec<ManagerKey>,7879 #[serde(default)]80 pub hosts: BTreeMap<String, HostData>,8182 #[serde(default, alias = "shared_secrets")]83 pub secrets: FleetSecrets,8485 // extra_name => anything86 #[serde(default)]87 #[serde(skip_serializing_if = "BTreeMap::is_empty")]88 pub extra: BTreeMap<String, Value>,8990 #[serde(default)]91 #[serde(skip_serializing_if = "BTreeMap::is_empty")]92 host_secrets: BTreeMap<String, BTreeMap<String, FleetSecretDistribution>>,93}94impl FleetData {95 pub fn from_str(s: &str) -> anyhow::Result<Self> {96 let mut data: Self = nixlike::parse_str(s)?;97 if !data.host_secrets.is_empty() {98 info!("migrating host secrets into shared secrets structure");99 data.secrets100 .merge_from_hosts(std::mem::take(&mut data.host_secrets));101 }102 Ok(data)103 }104}105106/// Returns None if recipients.is_empty()107pub fn encrypt_secret_data<'r>(108 recipients: impl IntoIterator<Item = &'r Box<dyn Recipient>>,109 data: Vec<u8>,110) -> Option<SecretData> {111 let mut encrypted = vec![];112 let mut encryptor = age::Encryptor::with_recipients(recipients.into_iter().map(|v| &**v))113 .ok()?114 .wrap_output(&mut encrypted)115 .expect("in memory write");116 io::copy(&mut Cursor::new(data), &mut encryptor).expect("in memory copy");117 encryptor.finish().expect("in memory flush");118 Some(SecretData {119 data: encrypted,120 encrypted: true,121 })122}123124#[derive(Serialize, Deserialize, Clone)]125pub struct FleetSecretPart {126 pub raw: SecretData,127}128129#[derive(Serialize, Deserialize, Clone)]130#[serde(rename_all = "camelCase")]131#[must_use]132pub struct FleetSecretData {133 #[serde(default = "Utc::now")]134 pub created_at: DateTime<Utc>,135 #[serde(default)]136 #[serde(skip_serializing_if = "Option::is_none", alias = "expire_at")]137 pub expires_at: Option<DateTime<Utc>>,138139 #[serde(flatten)]140 pub parts: BTreeMap<String, FleetSecretPart>,141142 #[serde(default)]143 #[serde(skip_serializing_if = "Value::is_null")]144 pub generation_data: Value,145}146147#[derive(Serialize, Deserialize, Clone)]148#[serde(rename_all = "camelCase")]149#[must_use]150pub struct FleetSecretDistribution {151 #[serde(default)]152 pub owners: BTreeSet<String>,153 #[serde(flatten)]154 pub secret: FleetSecretData,155156 #[serde(default, skip_serializing, alias="managed")]157 pub _deprecated_managed: bool,158}159160#[derive(Clone)]161#[must_use]162pub struct FleetSecretDistributions(Vec<FleetSecretDistribution>);163164impl Deref for FleetSecretDistributions {165 type Target = [FleetSecretDistribution];166167 fn deref(&self) -> &Self::Target {168 self.0.as_slice()169 }170}171172impl FleetSecretDistributions {173 pub fn owners(&self) -> impl Iterator<Item = &String> {174 self.0.iter().flat_map(|v| v.owners.iter())175 }176 #[allow(177 clippy::len_without_is_empty,178 reason = "should not be empty for a long time"179 )]180 pub fn len(&self) -> usize {181 self.0.len()182 }183184 pub fn get(&self, owner: &str) -> Option<&FleetSecretDistribution> {185 self.0.iter().find(|d| d.owners.contains(owner))186 }187 fn entry(&mut self, owner: String) -> DistEntry<'_> {188 let Some(idx) = self.0.iter().position(|d| d.owners.contains(&owner)) else {189 return DistEntry::Vacant(VacantDistEntry {190 distributions: self,191 owner,192 });193 };194 DistEntry::Occupied(OccupiedDistEntry {195 distributions: self,196 idx,197 owner,198 })199 }200 fn extend(&mut self, dist: FleetSecretDistribution) {201 for owner in &dist.owners {202 self.entry(owner.to_owned()).remove();203 }204 self.0.push(dist);205 }206 pub fn contains(&self, owner: &str) -> bool {207 self.0.iter().any(|d| d.owners.contains(owner))208 }209}210211struct OccupiedDistEntry<'d> {212 distributions: &'d mut FleetSecretDistributions,213 idx: usize,214 owner: String,215}216impl<'d> OccupiedDistEntry<'d> {217 fn remove(self) -> VacantDistEntry<'d> {218 let dist = &mut self.distributions.0[self.idx];219 assert!(220 dist.owners.remove(&self.owner),221 "entry exists, as we have its reference"222 );223 if dist.owners.is_empty() {224 self.distributions.0.remove(self.idx);225 }226 VacantDistEntry {227 distributions: self.distributions,228 owner: self.owner,229 }230 }231 fn set(self, secret: FleetSecretData) -> Self {232 self.remove().set(secret)233 }234}235struct VacantDistEntry<'d> {236 distributions: &'d mut FleetSecretDistributions,237 owner: String,238}239impl<'d> VacantDistEntry<'d> {240 fn set(self, secret: FleetSecretData) -> OccupiedDistEntry<'d> {241 let Self {242 distributions,243 owner,244 } = self;245 let idx = distributions.0.len();246 distributions.0.push(FleetSecretDistribution {247 owners: BTreeSet::from_iter([owner.clone()]),248 secret,249250 _deprecated_managed: true,251 });252 OccupiedDistEntry {253 distributions,254 owner,255 idx,256 }257 }258}259260enum DistEntry<'d> {261 Vacant(VacantDistEntry<'d>),262 Occupied(OccupiedDistEntry<'d>),263}264impl DistEntry<'_> {265 fn remove(self) -> Self {266 match self {267 DistEntry::Vacant(_) => self,268 DistEntry::Occupied(o) => Self::Vacant(o.remove()),269 }270 }271 fn set(self, secret: FleetSecretData) -> Self {272 Self::Occupied(match self {273 DistEntry::Vacant(e) => e.set(secret),274 DistEntry::Occupied(e) => e.set(secret),275 })276 }277}278279impl Serialize for FleetSecretDistributions {280 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>281 where282 S: serde::Serializer,283 {284 let mut found_hosts = BTreeSet::new();285 for ele in self.0.iter() {286 if ele.owners.is_empty() {287 panic!("consistency: secret distribution has no defined owners");288 }289 for ele in ele.owners.iter() {290 if !found_hosts.insert(ele) {291 panic!(292 "consistency: secret distribution contains duplicate entry for the same host",293 );294 }295 }296 }297 match self.0.len() {298 0 => panic!("consistency: empty distributions"),299 1 => self.0[0].serialize(serializer),300 _ => self.0.serialize(serializer),301 }302 }303}304impl<'de> Deserialize<'de> for FleetSecretDistributions {305 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>306 where307 D: serde::Deserializer<'de>,308 {309 #[derive(Deserialize)]310 #[serde(untagged)]311 enum Distributions {312 One(FleetSecretDistribution),313 Many(Vec<FleetSecretDistribution>),314 }315 let d = Distributions::deserialize(deserializer)?;316 let ds = match d {317 Distributions::One(d) => vec![d],318 Distributions::Many(ds) => ds,319 };320 if ds.is_empty() {321 return Err(de::Error::custom("consistency: empty distributions"));322 }323 let mut found_hosts = BTreeSet::new();324 for ele in ds.iter() {325 if ele.owners.is_empty() {326 return Err(de::Error::custom(327 "consistency: secret distribution has no defined owners",328 ));329 }330 for ele in ele.owners.iter() {331 if !found_hosts.insert(ele) {332 return Err(de::Error::custom(333 "consistency: secret distribution contains duplicate entry for the same host",334 ));335 }336 }337 }338 Ok(Self(ds))339 }340}341342#[derive(Serialize, Deserialize, Default)]343pub struct FleetSecrets(BTreeMap<String, FleetSecretDistributions>);344345impl FleetSecrets {346 pub fn keys(&self) -> btree_map::Keys<String, FleetSecretDistributions> {347 self.0.keys()348 }349350 pub fn keys_for_owner(&self, owner: &str) -> impl Iterator<Item = &String> {351 self.0352 .iter()353 .filter(|(_, d)| d.contains(owner))354 .map(|(n, _)| n)355 }356357 pub fn drop_owner_no_reencrypt(&mut self, secret: &str, owner: &str) -> bool {358 let Entry::Occupied(mut dists) = self.0.entry(secret.to_owned()) else {359 return false;360 };361 let DistEntry::Occupied(dist) = dists.get_mut().entry(owner.to_owned()) else {362 return false;363 };364365 dist.remove();366367 if dists.get().0.is_empty() {368 dists.remove();369 };370371 true372 }373 pub fn set_single_data(&mut self, secret: String, owner: String, data: FleetSecretData) {374 let e = self375 .0376 .entry(secret.to_owned())377 .or_insert_with(|| FleetSecretDistributions(Default::default()));378 e.entry(owner.to_owned()).set(data);379 }380 pub fn set_data(&mut self, secret: String, data: FleetSecretDistribution) {381 match self.0.entry(secret) {382 Entry::Vacant(e) => {383 e.insert(FleetSecretDistributions(vec![data]));384 }385 Entry::Occupied(mut e) => {386 let dists = e.get_mut();387 dists.extend(data)388 }389 }390 }391 pub fn get_single(&self, secret: &str, owner: &str) -> Option<&FleetSecretDistribution> {392 let secret = self.0.get(secret)?;393 secret.get(owner)394 }395 pub fn get(&self, secret: &str) -> Option<&FleetSecretDistributions> {396 self.0.get(secret)397 }398399 pub fn contains_for_owner(&self, secret: &str, owner: &str) -> bool {400 let Some(secret) = self.0.get(secret) else {401 return false;402 };403 secret.contains(owner)404 }405 pub fn contains(&self, secret: &str) -> bool {406 self.0.contains_key(secret)407 }408 pub fn remove(&mut self, secret: &str) {409 self.0.remove(secret);410 }411412 fn merge_from_hosts(413 &mut self,414 host_secrets: BTreeMap<String, BTreeMap<String, FleetSecretDistribution>>,415 ) {416 for (host, host_secrets) in host_secrets {417 for (secret_name, mut secret_data) in host_secrets {418 secret_data.owners.insert(host.clone());419 self.set_data(secret_name, secret_data);420 }421 }422 }423}crates/fleet-base/src/opts.rsdiffbeforeafterboth--- a/crates/fleet-base/src/opts.rs
+++ b/crates/fleet-base/src/opts.rs
@@ -22,7 +22,8 @@
use crate::{
fleetdata::FleetData,
- host::{Config, ConfigHost, FleetConfigInternals}, primops::init_primops,
+ host::{Config, ConfigHost, FleetConfigInternals},
+ primops::{PRIMOPS_DATA, init_primops},
};
#[derive(Clone)]
@@ -213,7 +214,7 @@
std::fs::read_to_string(&fleet_data_path).context("reading fleet state (fleet.nix)")?;
let data = Arc::new(Mutex::new(FleetData::from_str(&bytes)?));
- init_primops(data.clone());
+ init_primops();
let mut fetch_settings = FetchSettings::new();
fetch_settings.set(c"warn-dirty", c"false");
@@ -264,8 +265,7 @@
if cfg!(debug_assertions) {
gc_now();
}
-
- Ok(Config(Arc::new(FleetConfigInternals {
+ let config = Config(Arc::new(FleetConfigInternals {
directory,
data,
flake_outputs: flake,
@@ -275,6 +275,12 @@
default_pkgs,
nixpkgs,
localhost: self.localhost.to_owned(),
- })))
+ }));
+
+ PRIMOPS_DATA
+ .set(config.clone())
+ .map_err(|_| ())
+ .expect("only one fleet config may exist per process");
+ Ok(config)
}
}
crates/fleet-base/src/primops.rsdiffbeforeafterboth--- a/crates/fleet-base/src/primops.rs
+++ b/crates/fleet-base/src/primops.rs
@@ -1,10 +1,15 @@
-use std::collections::HashMap;
-use std::sync::{Arc, Mutex};
+use std::cell::OnceCell;
+use std::collections::{BTreeMap, HashMap};
+use std::sync::{Arc, Mutex, OnceLock};
-use nix_eval::{NativeFn, Value};
-use tracing::info;
+use anyhow::{Context, bail};
+use itertools::Itertools;
+use nix_eval::{NativeFn, Value, nix_go, nix_go_json};
+use serde::Deserialize;
+use tracing::{info, warn};
use crate::fleetdata::{FleetData, FleetSecrets};
+use crate::host::Config;
#[derive(thiserror::Error, Debug)]
enum Error {}
@@ -23,18 +28,76 @@
struct FsSecretsBackend {}
-pub fn init_primops(secrets: Arc<Mutex<FleetData>>) {
+pub static PRIMOPS_DATA: OnceLock<Config> = OnceLock::new();
+
+#[derive(Deserialize, Debug)]
+struct GeneratorPart {
+ encrypted: bool,
+}
+
+pub fn init_primops() {
info!("initializing primops");
NativeFn::new(
c"__fleetEnsureHostSecret",
c"Ensure secret existence for a host, regenerating it in case of some mismatch",
[c"host", c"secret", c"generator"],
- |[host, secret, generator]| {
- todo!("ensure secret");
- Ok(Value::new_attrs(HashMap::from_iter([(
- "raw",
- Value::new_str("rawData"),
- )])))
+ |es, [host, secret, generator]| {
+ info!("get host");
+ let host = host.to_string()?;
+ info!("get secret");
+ let secret = secret.to_string()?;
+
+ info!("get config");
+ let config = PRIMOPS_DATA
+ .get()
+ .expect("primops data should be set on init");
+
+ info!("get pkgs");
+ let nixpkgs = &config.nixpkgs;
+ let default_pkgs = &config.default_pkgs;
+ let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);
+ let generators = nix_go!(default_mk_secret_generators(Obj {
+ recipients: <Vec<String>>::new(),
+ }));
+ let pkgs_and_generators = default_pkgs.clone().attrs_update(generators)?;
+
+ info!("call package");
+ let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));
+ let default_generator = call_package
+ .call(generator.clone())
+ .context("calling callPackage with generator")?
+ .call(Value::new_attrs(HashMap::new()))
+ .context("providing extra callPackage args")?;
+
+ info!("get parts");
+ let mut parts: BTreeMap<String, GeneratorPart> = nix_go_json!(default_generator.parts);
+ info!("got parts: {parts:?}");
+
+ let Some(existing) = config
+ .host_secret(&host, &secret) else {
+ bail!("missing secret {secret} for host {host}; secret needs regeneration")
+ };
+
+ info!("got existing: {existing:?}");
+
+ let mut out = HashMap::new();
+
+ for (part_name, part) in &existing.secret.parts {
+ let Some(definition) = parts.remove(part_name) else {
+ warn!("secret {secret} part {part_name} is stored, but not defined in nixos config, it will not be passed to nix");
+ continue;
+ };
+ if definition.encrypted != part.raw.encrypted {
+ bail!("secret {secret} part {part_name} is supposed to be {}, but it is {}; secret needs regeneration", if definition.encrypted {"encrypted"} else {"unencrypted"}, if part.raw.encrypted {"encrypted"} else {"unencrypted"});
+ }
+ out.insert(part_name.as_str(), Value::new_attrs(HashMap::from_iter([("raw", Value::new_str(&part.raw.to_string()))])));
+ }
+ if !parts.is_empty(){
+ let defs = parts.keys().collect_vec();
+ bail!("secret parts are defined, but not stored: {defs:?}, secret needs regeneration")
+ }
+
+ Ok(Value::new_attrs(out))
},
)
.register();
crates/nix-eval/src/lib.rsdiffbeforeafterboth--- a/crates/nix-eval/src/lib.rs
+++ b/crates/nix-eval/src/lib.rs
@@ -12,7 +12,7 @@
use serde::de::DeserializeOwned;
pub use anyhow::Result;
-use tracing::instrument;
+use tracing::{info, instrument, warn};
use self::logging::{ErrorInfoBuilder, nix_logging_cxx};
use self::nix_cxx::set_fetcher_setting;
@@ -22,7 +22,8 @@
GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,
PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,
bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,
- clear_err, copy_value, err_code, err_info_msg, err_msg, eval_state_build,
+ clear_err, copy_value, err_NIX_ERR_KEY, err_NIX_ERR_NIX_ERROR, err_NIX_ERR_OVERFLOW,
+ err_NIX_ERR_UNKNOWN, err_code, err_info_msg, err_msg, eval_state_build,
eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,
expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,
flake_lock, flake_lock_flags, flake_lock_flags_free, flake_lock_flags_new, flake_reference,
@@ -36,9 +37,8 @@
make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,
realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,
realised_string_get_store_path, realised_string_get_store_path_count, register_primop,
- set_err_msg, setting_set, state_free, store_copy_closure, store_get_fs_closure, store_open,
- store_parse_path, store_path_free, store_path_name, string_realise, value, value_call,
- value_decref, value_incref,
+ set_err_msg, setting_set, state_free, store_open, store_parse_path, store_path_free,
+ store_path_name, string_realise, value, value_call, value_decref, value_force, value_incref,
};
// Contains macros helpers
@@ -62,6 +62,7 @@
type nix_fetchers_settings;
include!("nix-eval/src/lib.hh");
+ #[allow(clippy::missing_safety_doc)]
unsafe fn set_fetcher_setting(
settings: *mut nix_fetchers_settings,
setting: *const c_char,
@@ -111,19 +112,19 @@
#[derive(Debug)]
#[repr(i32)]
pub enum NixErrorKind {
- Unknown = 1,
- Overflow = 2,
- Key = 3,
- Generic = 4,
+ Unknown = err_NIX_ERR_UNKNOWN,
+ Overflow = err_NIX_ERR_OVERFLOW,
+ Key = err_NIX_ERR_KEY,
+ Generic = err_NIX_ERR_NIX_ERROR,
}
impl NixErrorKind {
fn from_int(v: c_int) -> Option<Self> {
Some(match v {
0 => return None,
- -1 => Self::Unknown,
- -2 => Self::Overflow,
- -3 => Self::Key,
- -4 => Self::Generic,
+ nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,
+ nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,
+ nix_raw::err_NIX_ERR_KEY => Self::Key,
+ nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,
_ => {
debug_assert!(false, "unexpected nix error kind: {v}");
Self::Unknown
@@ -298,8 +299,10 @@
}
}
-static GLOBAL_STATE: LazyLock<GlobalState> =
- LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));
+static GLOBAL_STATE: LazyLock<GlobalState> = LazyLock::new(|| {
+ info!("initializing nix global state");
+ GlobalState::new().expect("global state init shouldn't fail")
+});
thread_local! {
static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));
@@ -424,7 +427,8 @@
}
}
-struct EvalState(*mut c_eval_state);
+#[repr(transparent)]
+pub struct EvalState(*mut c_eval_state);
unsafe impl Send for EvalState {}
unsafe impl Sync for EvalState {}
@@ -697,7 +701,15 @@
let builtin = Self::eval("builtins.toString")?;
builtin.call(self.clone())
}
+ fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {
+ with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;
+ Ok(())
+ }
pub fn to_string(&self) -> Result<String> {
+ let ty = self.type_of();
+ if !matches!(ty, NixType::String) {
+ bail!("unexpected type: {ty:?}, expected string");
+ }
let mut str_out = String::new();
with_default_context(|c, _| unsafe {
get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())
@@ -931,17 +943,39 @@
ret: *mut value,
) {
let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };
+ let mut e = None;
let args: [&Value; N] = array::from_fn(|i| {
- let v: &Value = unsafe { &*args.add(i).cast_const().cast() };
- v
+ let v: &mut Value = unsafe { &mut *args.add(i).cast() };
+
+ info!("forcing arg");
+ if matches!(v.type_of(), NixType::Thunk)
+ && let Err(err) = v.force(state)
+ {
+ e = Some(err);
+ };
+ v as &Value
});
+ info!("args forced");
let ctx: &mut NixContext = unsafe { &mut *context.cast() };
- match user_closure(args) {
+ if let Some(e) = e {
+ warn!("set err = {e}");
+ unsafe { init_int(context, ret, 0) };
+ return ctx.set_err(
+ NixErrorKind::Unknown,
+ &CString::new(e.to_string()).expect("forcing argument value failed"),
+ );
+ }
+
+ let state: &EvalState = unsafe { std::mem::transmute(&state) };
+
+ match user_closure(state, args) {
Ok(v) => {
unsafe { copy_value(context, ret, v.0) };
}
Err(e) => {
+ unsafe { init_int(context, ret, 0) };
+ warn!("set err = {e:#?}");
ctx.set_err(
NixErrorKind::Unknown,
&CString::new(e.to_string()).expect("error should not contain internal nuls"),
@@ -950,7 +984,7 @@
}
}
-type UserClosure<const N: usize> = Box<dyn Fn([&Value; N]) -> Result<Value>>;
+type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;
pub struct NativeFn(*mut PrimOp);
impl NativeFn {
@@ -958,7 +992,7 @@
name: &'static CStr,
doc: &'static CStr,
args: [&'static CStr; N],
- f: impl Fn([&Value; N]) -> Result<Value> + 'static,
+ f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,
) -> Self {
// Double-boxing to make it thin pointer, as vtable gets outside of first Box
let closure: Box<UserClosure<N>> = Box::new(Box::new(f));
@@ -1003,7 +1037,7 @@
c"__uppercaseSuffix2",
c"make string uppercase and add suffix",
[c"str", c"suffix"],
- |[str, suffix]: [&Value; 2]| {
+ |_, [str, suffix]: [&Value; 2]| {
let str = str.to_string()?;
let suffix = suffix.to_string()?;
Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))
@@ -1038,7 +1072,7 @@
c"uppercase_suffix",
c"make string uppercase and add suffix",
[c"str", c"suffix"],
- |[str, suffix]: [&Value; 2]| {
+ |es, [str, suffix]: [&Value; 2]| {
let str = str.to_string()?;
let suffix = suffix.to_string()?;
Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))
crates/nix-eval/src/macros.rsdiffbeforeafterboth--- a/crates/nix-eval/src/macros.rs
+++ b/crates/nix-eval/src/macros.rs
@@ -16,7 +16,12 @@
$(nix_expr_inner!(@obj($o) $($tt)*);)?
}};
(@obj($o:ident)) => {{}};
- (Obj { $($tt:tt)* }) => {{
+ (Obj { }) => {{
+ use $crate::{nix_expr_inner};
+ let out = std::collections::hash_map::HashMap::new();
+ Value::new_attrs(out)
+ }};
+ (Obj { $($tt:tt)+ }) => {{
use $crate::{nix_expr_inner};
let mut out = std::collections::hash_map::HashMap::new();
nix_expr_inner!(@obj(out) $($tt)*);
lib/default.nixdiffbeforeafterboth--- a/lib/default.nix
+++ b/lib/default.nix
@@ -149,6 +149,23 @@
}
);
+ mkAskPass =
+ { prompt ? "Secret value", part ? "secret" }:
+ (
+ {
+ kdePackages,
+ mkImpureSecretGenerator,
+ }:
+ mkImpureSecretGenerator {
+ # TODO: Escape prompt?
+ script = ''
+ ${kdePackages.kdialog}/bin/kdialog --inputbox "${prompt}" | gh private -o $out/${part}
+ '';
+
+ parts.${part}.encrypted = true;
+ }
+ );
+
/**
Generate a random RSA keypair
@@ -251,6 +268,7 @@
mkBytes
mkHexBytes
mkBase64Bytes
+ mkAskPass
;
strings =
modules/nixos/secrets.nixdiffbeforeafterboth--- a/modules/nixos/secrets.nix
+++ b/modules/nixos/secrets.nix
@@ -19,7 +19,6 @@
submodule
str
attrsOf
- nullOr
unspecified
uniq
functionTo
@@ -77,13 +76,12 @@
{
options = {
parts = mkOption {
- type = attrsOf (secretPartType secretName);
+ type = uniq (attrsOf (secretPartType secretName));
description = "Definition of secret parts";
};
generator = mkOption {
- type = uniq (nullOr (functionTo package));
+ type = uniq (functionTo package);
description = "Derivation to evaluate for secret generation";
- default = null;
};
mode = mkOption {
type = str;
@@ -103,14 +101,25 @@
};
};
config = {
- parts = builtins.fleetEnsureHostSecret sysConfig.networking.hostName secretName config.generator;
+ # C api is broken in regard to thunks
+ # https://github.com/NixOS/nix/issues/12800
+ parts = let
+ hostName = sysConfig.networking.hostName;
+ generator = config.generator;
+ in builtins.deepSeq [
+ hostName
+ secretName
+ generator
+ ] (builtins.fleetEnsureHostSecret
+ hostName
+ secretName
+ generator);
};
}
);
- secretsData = (mapAttrs (_: s: s.definition) config.secrets);
secretsFile = pkgs.writeTextFile {
name = "secrets.json";
- text = toJSON secretsData;
+ text = toJSON config.system.secretsData;
};
useSysusers =
(config.systemd ? sysusers && config.systemd.sysusers.enable)
@@ -121,17 +130,20 @@
secrets = mkOption {
type = attrsOf secretType;
default = { };
- apply = v: (mapAttrs (_: secret: secret.parts // { definition = secret; }) v);
+ apply = mapAttrs (_: secret: secret.parts // {definition = secret;});
description = "Host-local secrets";
};
system.secretsData = mkOption {
type = unspecified;
- default = { };
+ default = mapAttrs (_: s:
+ (removeAttrs s.definition ["generator"]) // {
+ parts = mapAttrs (_: part: removeAttrs part ["data"]) s.definition.parts;
+ }
+ ) config.secrets;
description = "secrets.json contents";
};
};
config = {
- system = { inherit secretsData; };
environment.systemPackages = [ pkgs.fleet-install-secrets ];
systemd.services.fleet-install-secrets = mkIf useSysusers {