difftreelog
secret management
in: trunk
11 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1092,6 +1092,7 @@
"time",
"tokio",
"tokio-util",
+ "toml_edit",
"tracing",
]
@@ -2744,6 +2745,13 @@
checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298"
[[package]]
+name = "repl-plugin-unstable"
+version = "0.1.0"
+dependencies = [
+ "fleet-base",
+]
+
+[[package]]
name = "reqwest"
version = "0.12.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3618,6 +3626,43 @@
]
[[package]]
+name = "toml_datetime"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.23.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
+dependencies = [
+ "indexmap 2.11.4",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
+
+[[package]]
name = "tonic"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4469,6 +4514,15 @@
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
+name = "winnow"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@
resolver = "2"
package.version = "0.1.0"
package.edition = "2024"
-package.rust-version = "1.86.0"
+package.rust-version = "1.89.0"
[workspace.dependencies]
better-command = { path = "./crates/better-command" }
cmds/repl-plugin-unstable/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/cmds/repl-plugin-unstable/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "repl-plugin-unstable"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+fleet-base = { version = "0.1.0", path = "../../crates/fleet-base" }
cmds/repl-plugin-unstable/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/cmds/repl-plugin-unstable/src/lib.rs
@@ -0,0 +1,6 @@
+use fleet_base::primops::init_primops;
+
+#[unsafe(no_mangle)]
+fn nix_plugin_entry() {
+ init_primops();
+}
crates/fleet-base/Cargo.tomldiffbeforeafterboth--- a/crates/fleet-base/Cargo.toml
+++ b/crates/fleet-base/Cargo.toml
@@ -8,7 +8,7 @@
age.workspace = true
anyhow.workspace = true
better-command.workspace = true
-chrono = "0.4.41"
+chrono = { version = "0.4.41", features = ["serde"] }
clap = { workspace = true, features = ["derive"] }
fleet-shared.workspace = true
futures = "0.3.31"
@@ -27,5 +27,6 @@
thiserror.workspace = true
time = { version = "0.3.41", features = ["parsing"] }
tokio.workspace = true
-tokio-util = "0.7.15"
+tokio-util = { version = "0.7.15", features = ["codec"] }
+toml_edit = "0.23.7"
tracing.workspace = true
crates/fleet-base/src/lib.rsdiffbeforeafterboth--- a/crates/fleet-base/src/lib.rs
+++ b/crates/fleet-base/src/lib.rs
@@ -4,4 +4,6 @@
pub mod host;
mod keys;
pub mod opts;
+pub mod primops;
pub mod secret;
+pub mod secret_storage;
crates/fleet-base/src/primops.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/fleet-base/src/primops.rs
@@ -0,0 +1,45 @@
+use nix_eval::NativeFn;
+
+#[derive(thiserror::Error, Debug)]
+enum Error {}
+
+struct Parts {
+ encrypted: Vec<String>,
+ public: Vec<String>,
+}
+
+trait SecretsBackend {
+ fn has_shared(&self, name: &str);
+ fn has_host(&self, host: &str, name: &str);
+ fn shared_parts(&self, name: &str) -> Parts;
+ fn host_parts(&self, host: &str, name: &str) -> Parts;
+}
+
+struct FsSecretsBackend {
+
+}
+
+pub fn init_primops() {
+ NativeFn::new(
+ c"fleet_ensure_secret",
+ c"Ensure secret existence for a host, regenerating it in case of some mismatch",
+ [
+ c"host",
+ c"secret",
+ c"expected_parts",
+ c"expected_encrypted_parts",
+ c"generator",
+ ],
+ |[
+ host,
+ secret,
+ expected_parts,
+ expected_encrypted_parts,
+ generator,
+ ]| {
+
+ todo!()
+ },
+ )
+ .register();
+}
crates/fleet-base/src/secret_storage.rsdiffbeforeafterboth1use anyhow::{Result, bail, ensure};2use itertools::Itertools;3use std::fs::{File, metadata};4use std::io::{self, ErrorKind, Read, Write};5use std::path::PathBuf;6use std::str::FromStr;7use std::{env, fs};89use tempfile::{TempPath, tempfile_in};10use toml_edit::{Document, DocumentMut, Formatted, Item, Value};1112struct Name(String);1314fn encode_name(name: &str) -> Name {15 assert!(16 !name.starts_with(['_', '.']),17 "groups should not start with _ or ."18 );19 assert!(20 !name.chars().any(|c| c == '/'),21 "group name should not contain internal slash"22 );23 Name(name.to_owned())24}2526enum RewriteError {27 ConcurrentCreate,28 ConcurrentDelete,29 ConcurrentModify,30 ConcurrentWrite,31 Io(io::Error),32 Persist(tempfile::PersistError),33}3435fn safe_rewrite(36 path: &PathBuf,37 old_content: Option<Vec<u8>>,38 new_content: Option<Vec<u8>>,39) -> Result<(), RewriteError> {40 let mut f = match (old_content.is_some(), new_content.is_some()) {41 (false, true) => match File::create_new(path) {42 Ok(v) => v,43 Err(e) if e.kind() == ErrorKind::AlreadyExists => {44 return Err(RewriteError::ConcurrentCreate);45 }46 Err(e) => return Err(RewriteError::Io(e)),47 },48 (true, _) => match File::open(&path) {49 Ok(v) => v,50 Err(_e) => return Err(RewriteError::ConcurrentDelete),51 },52 (false, false) => match metadata(&path) {53 Err(e) if e.kind() == ErrorKind::NotFound => {54 return Ok(());55 }56 Ok(_) => return Err(RewriteError::ConcurrentCreate),57 Err(e) => return Err(RewriteError::Io(e)),58 },59 };60 f.lock().map_err(RewriteError::Io)?;61 let mut check_content = vec![];62 f.read_to_end(&mut check_content)63 .map_err(RewriteError::Io)?;64 match &old_content {65 Some(old) => {66 if old != &check_content {67 return Err(RewriteError::ConcurrentModify);68 }69 }70 None => {71 if !check_content.is_empty() {72 return Err(RewriteError::ConcurrentDelete);73 }74 }75 }76 if let Some(new_content) = new_content {77 if Some(&new_content) == old_content.as_ref() {78 return Ok(());79 }80 let dir = path.parent().expect("file is in directory, thus not root");81 let mut tempfile = tempfile::Builder::new()82 .prefix(".rewrite-")83 .tempfile_in(dir)84 .map_err(RewriteError::Io)?;85 tempfile.write_all(&new_content).map_err(RewriteError::Io)?;86 tempfile.flush().map_err(RewriteError::Io)?;87 tempfile.persist(path).map_err(RewriteError::Persist)?;88 } else {89 fs::remove_file(path).map_err(RewriteError::Io)?;90 }91 let _ = f.unlock();92 Ok(())93}94fn update_string(path: PathBuf, modify: impl Fn(&mut Option<String>) -> Result<()>) -> Result<()> {95 loop {96 let orig = match fs::read_to_string(&path) {97 Ok(v) => Some(v),98 Err(e) if e.kind() == ErrorKind::NotFound => None,99 Err(e) => return Err(e.into()),100 };101 let mut edit = orig.clone();102 modify(&mut edit);103104 match safe_rewrite(&path, orig.map(String::into), edit.map(String::into)) {105 Ok(()) => return Ok(()),106 Err(107 RewriteError::ConcurrentCreate108 | RewriteError::ConcurrentModify109 | RewriteError::ConcurrentWrite110 | RewriteError::ConcurrentDelete,111 ) => {112 continue;113 }114 Err(RewriteError::Io(io)) => return Err(io.into()),115 Err(RewriteError::Persist(io)) => return Err(io.into()),116 }117 }118}119fn update_toml(path: PathBuf, modify: impl Fn(&mut DocumentMut) -> Result<()>) -> Result<()> {120 update_string(path, |str| {121 let mut doc = match str {122 None => DocumentMut::new(),123 Some(v) => DocumentMut::from_str(v)?,124 };125 modify(&mut doc)?;126 if doc.is_empty() {127 *str = None128 } else {129 *str = Some(doc.to_string())130 }131 Ok(())132 })133}134fn update_lines(path: PathBuf, modify: impl Fn(&mut Vec<String>) -> Result<()>) -> Result<()> {135 update_string(path, |str| {136 let mut list = if let Some(str) = str {137 str.split('\n').map(|s| s.to_owned()).collect_vec()138 } else {139 vec![]140 };141 let had_end_newline = if list.last().map(|v| v.as_str()) == Some("") {142 list.pop();143 true144 } else {145 false146 };147 modify(&mut list)?;148 if list.is_empty() {149 *str = None150 } else {151 if had_end_newline {152 list.push("".to_owned())153 }154 *str = Some(list.join("\n"));155 }156 Ok(())157 })158}159fn update_section(160 data: &mut Vec<String>,161 start: &str,162 end: &str,163 modify: impl Fn(&mut Vec<String>) -> Result<()>,164) -> Result<()> {165 let first = data166 .iter()167 .enumerate()168 .filter(|(_, v)| *v == start)169 .at_most_one()170 .map_err(|_| anyhow::anyhow!("there should be at most one section start"))?171 .map(|(v, _)| v);172 let last = data173 .iter()174 .enumerate()175 .filter(|(_, v)| *v == end)176 .at_most_one()177 .map_err(|_| anyhow::anyhow!("there should be at most one section end"))?178 .map(|(v, _)| v);179180 match (first, last) {181 (None, None) => {182 let mut out = Vec::new();183 modify(&mut out)?;184 if out.is_empty() {185 return Ok(());186 }187 data.push(start.to_owned());188 data.extend(out);189 data.push(end.to_owned());190 Ok(())191 }192 (None, Some(_)) | (Some(_), None) => {193 bail!("mismatched section start/end")194 }195 (Some(first), Some(last)) => {196 ensure!(first < last, "section end should come after start");197 let mut out = data[first + 1..last]198 .iter()199 .map(|v| v.to_owned())200 .collect_vec();201 modify(&mut out)?;202 if out.is_empty() {203 data.drain(first..=last);204 } else {205 data.splice(first + 1..last, out);206 }207 Ok(())208 }209 }210}211212struct Group {213 path: PathBuf,214}215impl Group {216 fn new(path: PathBuf) -> Self {217 Self { path }218 }219 fn manage(&self, manager: &str) {}220 fn ensure_managing(&self, manager: &str) {221 if !self.has_stored() {222 return;223 }224 let managed = match fs::read_to_string(self.path.join(".managed_by")) {225 Ok(found_manager) => found_manager.lines().any(|line| line == manager),226 Err(e) if e.kind() == ErrorKind::NotFound => true,227 Err(e) => panic!("{e}"),228 };229 assert!(managed);230 }231 fn has_stored(&self) -> bool {232 match fs::metadata(&self.path) {233 Ok(d) => d.is_dir(),234 Err(e) if e.kind() == ErrorKind::NotFound => false,235 Err(e) => panic!("{e}"),236 }237 }238}239240struct Root {241 path: PathBuf,242}243impl Root {244 fn new(path: PathBuf) -> Self {245 Self { path }246 }247 fn subgroup(&self, name: &str) -> Group {248 Group::new(self.path.join(name))249 }250}251252#[test]253fn test() {254 let mut data = vec![255 "a".to_owned(),256 "b".to_owned(),257 "start".to_owned(),258 "c".to_owned(),259 "d".to_owned(),260 "end".to_owned(),261 "e".to_owned(),262 "f".to_owned(),263 ];264 update_section(&mut data, "start", "end", |a| {265 a.push("vv".to_owned());266 Ok(())267 })268 .unwrap();269 dbg!(&data);270 // for v in 0..1000 {271 // update_toml(PathBuf::from("./test.toml"), |e| {272 // e.as_table_mut()273 // .insert("hello", Item::Value(Value::Integer(Formatted::new(v))));274 // })275 // .expect("update")276 // }277 // v.subgroup(name)278}crates/fleet-base/test.tomldiffbeforeafterboth--- /dev/null
+++ b/crates/fleet-base/test.toml
@@ -0,0 +1 @@
+hello = 999
crates/nix-eval/src/lib.rsdiffbeforeafterboth--- a/crates/nix-eval/src/lib.rs
+++ b/crates/nix-eval/src/lib.rs
@@ -952,7 +952,7 @@
type UserClosure<const N: usize> = Box<dyn Fn([&Value; N]) -> Result<Value>>;
-struct NativeFn(*mut PrimOp);
+pub struct NativeFn(*mut PrimOp);
impl NativeFn {
pub fn new<const N: usize>(
name: &'static CStr,
flake.nixdiffbeforeafterboth--- a/flake.nix
+++ b/flake.nix
@@ -185,6 +185,7 @@
inputs'.nix.packages.nix-flake-c
inputs'.nix.packages.nix-fetchers-c
inputs'.nix.packages.nix-store-c
+ inputs'.nix.packages.nix
(rage.overrideAttrs { cargoFeatures = [ "plugin" ]; })
];