From 18c666023157a06183b1504f147c6cf585ff3c98 Mon Sep 17 00:00:00 2001 From: oluceps Date: Thu, 12 Sep 2024 10:02:58 +0800 Subject: [PATCH] + init --- .envrc | 1 + .github/workflows/lint.yaml | 40 + .gitignore | 5 + Cargo.lock | 2228 ++++++++++++++++++++++++++++++++ Cargo.toml | 27 + LICENSE | 202 +++ README.md | 115 ++ TODO.md | 12 + apps/renc.nix | 30 + flake-module.nix | 69 + flake.lock | 172 +++ flake.nix | 128 ++ justfile | 15 + module/default.nix | 292 +++++ src/cmd/check.rs | 24 + src/cmd/deploy.rs | 214 +++ src/cmd/edit.rs | 0 src/cmd/mod.rs | 98 ++ src/cmd/renc.rs | 87 ++ src/helper/callback.rs | 105 ++ src/helper/mod.rs | 6 + src/helper/parse_identity.rs | 59 + src/helper/parse_permission.rs | 18 + src/helper/secret_buf.rs | 73 ++ src/helper/set_owner_group.rs | 48 + src/helper/stored.rs | 261 ++++ src/interop/mod.rs | 15 + src/main.rs | 25 + src/profile.rs | 50 + 29 files changed, 4419 insertions(+) create mode 100644 .envrc create mode 100644 .github/workflows/lint.yaml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 TODO.md create mode 100644 apps/renc.nix create mode 100644 flake-module.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 justfile create mode 100644 module/default.nix create mode 100644 src/cmd/check.rs create mode 100644 src/cmd/deploy.rs create mode 100644 src/cmd/edit.rs create mode 100644 src/cmd/mod.rs create mode 100644 src/cmd/renc.rs create mode 100644 src/helper/callback.rs create mode 100644 src/helper/mod.rs create mode 100644 src/helper/parse_identity.rs create mode 100644 src/helper/parse_permission.rs create mode 100644 src/helper/secret_buf.rs create mode 100644 src/helper/set_owner_group.rs create mode 100644 src/helper/stored.rs create mode 100644 src/interop/mod.rs create mode 100644 src/main.rs create mode 100644 src/profile.rs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..06ba980 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,40 @@ +name: Evaluate Flake +on: + pull_request: + branches: ["*"] + push: + branches: ["main"] +jobs: + check: + name: format check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: flake check + run: | + nix flake check + shell: bash + + test: + name: run tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: build vaultix cli + run: | + nix build .# + shell: bash + - name: cargo check + run: | + nix develop -c cargo check + shell: bash diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea525e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +/result +/secrets +/.direnv +test.json diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..59189fa --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2228 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "age" +version = "0.10.0" +source = "git+https://github.com/str4d/rage.git?rev=baf277a749c839e49f93bffb58d36734ac94be83#baf277a749c839e49f93bffb58d36734ac94be83" +dependencies = [ + "aes", + "aes-gcm", + "age-core", + "base64", + "bcrypt-pbkdf", + "bech32", + "cbc", + "chacha20poly1305", + "cipher", + "cookie-factory", + "ctr", + "curve25519-dalek", + "hmac", + "i18n-embed", + "i18n-embed-fl", + "lazy_static", + "nom", + "num-traits", + "pin-project", + "rand", + "rsa", + "rust-embed", + "scrypt", + "sha2", + "subtle", + "which", + "wsl", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "age-core" +version = "0.10.0" +source = "git+https://github.com/str4d/rage.git?rev=baf277a749c839e49f93bffb58d36734ac94be83#baf277a749c839e49f93bffb58d36734ac94be83" +dependencies = [ + "base64", + "chacha20poly1305", + "cookie-factory", + "hkdf", + "io_tee", + "nom", + "rand", + "secrecy", + "sha2", + "tempfile", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "argh" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "argh_shared" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.85", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + +[[package]] +name = "fluent" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +dependencies = [ + "thiserror", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "i18n-config" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e88074831c0be5b89181b05e6748c4915f77769ecc9a4c372f88b169a8509c9" +dependencies = [ + "basic-toml", + "log", + "serde", + "serde_derive", + "thiserror", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7839d8c7bb8da7bd58c1112d3a1aeb7f178ff3df4ae87783e758ca3bfb750b7" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "lazy_static", + "log", + "parking_lot", + "rust-embed", + "thiserror", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e9571c3cba9eba538eaa5ee40031b26debe76f0c7e17bafc97ea57a76cd82e" +dependencies = [ + "dashmap", + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "lazy_static", + "proc-macro-error2", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.85", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "intl-memoizer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "io_tee" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "loopdev-3" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90a97d7a5124296ee9124a815acdc3dc4a91f577b72812b3f1f99bb959b46e8d" +dependencies = [ + "bindgen", + "errno", + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinentry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72268b7db3a2075ea65d4b93b755d086e99196e327837e690db6559b393a8d69" +dependencies = [ + "log", + "nom", + "percent-encoding", + "secrecy", + "which", + "zeroize", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rpassword" +version = "7.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.48.0", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rtoolbox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.85", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.0.4", +] + +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "spdlog-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb877789e366d41c2afa9b764f638c5bf9cb23286969112bdb21b38a15de8d8d" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "spdlog-rs" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ba5130aa47fc05c2643857ab6f948078e8ac9b10fbb2ffbb10872ad690d63b" +dependencies = [ + "arc-swap", + "atomic", + "cfg-if", + "chrono", + "if_chain", + "is-terminal", + "libc", + "once_cell", + "rustc_version", + "spdlog-macros", + "spin", + "static_assertions", + "thiserror", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sys-mount" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6acb8bb63826062d5a44b68298cf2e25b84bc151bc0c31c35a83b61f818682a" +dependencies = [ + "bitflags", + "libc", + "loopdev-3", + "smart-default", + "thiserror", + "tracing", +] + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "serde", + "tinystr", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "vaultix" +version = "0.1.0" +dependencies = [ + "age", + "argh", + "blake3", + "console", + "eyre", + "libc", + "nom", + "pinentry", + "rpassword", + "serde", + "serde_json", + "sha2", + "spdlog-rs", + "subtle", + "sys-mount", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.85", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wsl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dab7ac864710bdea6594becbea5b5050333cf34fefb0dc319567eb347950d4" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e5c7f4e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "vaultix" +version = "0.1.0" +edition = "2021" + +[dependencies] +age = { git = "https://github.com/str4d/rage.git",rev = "baf277a749c839e49f93bffb58d36734ac94be83", features = ["ssh", "plugin"]} +argh = "0.1.12" +blake3 = "1.5.4" +console = "0.15.8" +eyre = "0.6.12" +libc = "0.2.158" +nom = "7.1.3" +pinentry = "0.5.1" +rpassword = "7.3.1" +serde = "1.0.210" +serde_json = "1.0.132" +sha2 = "0.10.8" +spdlog-rs = "0.3.13" +subtle = "2.6.1" +sys-mount = "3.0.1" + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ad941a9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Secirian + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8255d0 --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# Vaultix + +![built for nixos](https://img.shields.io/static/v1?logo=nixos&logoColor=white&label=&message=Built%20for%20Nix&color=41439a) +![CI state](https://github.com/oluceps/vaultix/actions/workflows/lint.yaml/badge.svg) + +Secret management for NixOS. + +Highly inspired by agenix-rekey. Based on rust age crate. + +> [!CAUTION] +> This project is in VERY early dev stage, NOT ready for production. + ++ AGE Support Only ++ PIV Card (Yubikey) Support ++ Age Plugin Compatible ++ No Bash + +## Prerequisite: + ++ `nix-command` feature enabled ++ `flake-parts` structured config ++ `self` as specialArgs, to `nixosSystem` ++ `systemd.sysusers` or `services.userborn` option enabled + +## Setup + +Adding flake-parts flakeModule: + +```nix +# flake.nix +# ... +outputs = inputs@{ flake-parts, self, ... }: + flake-parts.lib.mkFlake { inherit inputs; } ( + { ... }: + { + imports = [inputs.vaultix.flakeModules.default]; + perSystem = { + vaultix.nodes = self.nixosConfigurations; + }; + # ... + } +inputs.vaultix.url = "github:oluceps/vaultix"; +``` + +```nix +# configuration.nix +{ + imports = [ inputs.vaultix.nixosModules.default ]; + vaultix = { + settings = { + # relative to flake root, used for storing host public key - + # re-encrypted secrets. + storageDirRelative = "./secret/renc/${config.networking.hostName}"; + # extraRecipients = [ data.keys.ageKey ]; # not supported yet + masterIdentities = [ + # See https://github.com/str4d/age-plugin-yubikey + # Also supports age native secrets + (self + "/secret/age-yubikey-identity-0000ffff.txt.pub") + ]; + }; + secrets = { + example = { + file = ./secret/example.age; + mode = "640"; + owner = "root"; + group = "users"; + name = "example.toml"; + # path = "/some/place"; # not supported yet + }; + # ... + }; + }; +} +``` + +> [!TIP] +> During first setup, you need to manually create `storageDirRelative` and +> add it to git (create an placeholder since git ignores empty directory). + +```bash +mkdir -p ./secret/renc/YOUR_HOSTNAME_HERE +touch ./secret/renc/YOUR_HOSTNAME_HERE/.placeholder +# so that you could add the directory to git (for recognizing by flake) +git add . +# after this step you could remove placeholder +nix run .#vaultix.x86_64-linux.renc +``` + + +## Cli Args + +Seldon use cli directly. Use Nix Wrapped App such as `nix run .#vaultix.x86_64-linux.renc`. + +Currently not support `edit` command, you could directly use rage for creating your encrypted file. + + +```bash +> ./vaultix --help +Usage: vaultix [-f ] [] + +Vaultix cli | Secret manager for NixOS + +Positional Arguments: + profile toml secret profile + +Options: + -f, --flake-root toplevel of flake repository + --help display usage information + +Commands: + renc Re-encrypt changed files + edit Edit encrypted file # NOT SUPPORT YET + check Check secret status + deploy Decrypt and deploy cipher credentials +``` diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..ea43f3c --- /dev/null +++ b/TODO.md @@ -0,0 +1,12 @@ +- [ ] [edit] extra encrypt key +- [ ] deploy to other directory +- [x] check command +- [x] vaultix.d in ramfs +- [x] vaultix{,.d} permission +- [x] age plugin support +- [x] [renc] calc hash and skip unchanged +- [x] apply `Secret` metadata +- [x] nix integration +- [x] ~~remote machine, compare hash, needs host priv key~~ +- [ ] ~~feed the toml after renced, thus store path changed~~ +- [ ] ~~eval in vaultix to json, reduce requirement~~ diff --git a/apps/renc.nix b/apps/renc.nix new file mode 100644 index 0000000..460a356 --- /dev/null +++ b/apps/renc.nix @@ -0,0 +1,30 @@ +{ + nodes, + pkgs, + system, + package, + ... +}: +let + inherit (pkgs) writeShellScriptBin; + inherit (pkgs.lib) concatStringsSep; + inherit (builtins) attrValues; + + vaultixs = map (n: n.config.vaultix) (attrValues nodes); + bin = pkgs.lib.getExe package; + +in +writeShellScriptBin "renc" ( + concatStringsSep "\n" ( + map ( + n: + let + profile = pkgs.writeTextFile { + name = "secret-meta"; + text = builtins.toJSON n; + }; + in + "${bin} ${profile} renc" + ) vaultixs + ) +) diff --git a/flake-module.nix b/flake-module.nix new file mode 100644 index 0000000..0b20b71 --- /dev/null +++ b/flake-module.nix @@ -0,0 +1,69 @@ +localFlake: +{ + lib, + self, + config, + flake-parts-lib, + ... +}: +let + inherit (lib) + mkOption + mkPackageOption + types + ; + +in +{ + options = { + flake = flake-parts-lib.mkSubmoduleOptions { + vaultix = mkOption { + type = types.lazyAttrsOf (types.lazyAttrsOf types.package); + default = lib.mapAttrs ( + system: config': + lib.genAttrs + [ + "renc" + # "edit" + ] + ( + app: + import ./apps/${app}.nix { + inherit (config'.vaultix) nodes pkgs; + package = localFlake.packages.${system}.default; + userFlake' = self; + inherit system; + } + ) + ) config.allSystems; + readOnly = true; + description = ''''; + }; + }; + + perSystem = flake-parts-lib.mkPerSystemOption ( + { + config, + lib, + pkgs, + ... + }: + { + options.vaultix = { + nodes = mkOption { + type = types.lazyAttrsOf types.unspecified; + description = "All nixosSystems that should be considered for rekeying."; + default = self.nixosConfigurations; + defaultText = lib.literalExpression "self.nixosConfigurations"; + }; + pkgs = mkOption { + type = types.unspecified; + description = "The package set to use when defining agenix-rekey scripts."; + default = pkgs; + defaultText = lib.literalExpression "pkgs # (module argument)"; + }; + }; + } + ); + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9d19e45 --- /dev/null +++ b/flake.lock @@ -0,0 +1,172 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1730060262, + "narHash": "sha256-RMgSVkZ9H03sxC+Vh4jxtLTCzSjPq18UWpiM0gq6shQ=", + "owner": "ipetkov", + "repo": "crane", + "rev": "498d9f122c413ee1154e8131ace5a35a80d8fa76", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1725234343, + "narHash": "sha256-+ebgonl3NbiKD2UD0x4BszCZQ6sTfL4xioaM49o5B3Y=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "567b938d64d4b4112ee253b9274472dc3a346eb6", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1726062873, + "narHash": "sha256-IiA3jfbR7K/B5+9byVi9BZGWTD4VSbWe8VLpp9B/iYk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4f807e8940284ad7925ebd0a0993d2a1791acb2f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1725233747, + "narHash": "sha256-Ss8QWLXdr2JCBPcYChJhz4xJm+h/xjl4G0c0XlP6a74=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/356624c12086a18f2ea2825fed34523d60ccc4e3.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/356624c12086a18f2ea2825fed34523d60ccc4e3.tar.gz" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1720386169, + "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1725513492, + "narHash": "sha256-tyMUA6NgJSvvQuzB7A1Sf8+0XCHyfSPRx/b00o6K0uo=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "7570de7b9b504cfe92025dd1be797bf546f66528", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1726107922, + "narHash": "sha256-G8P6YT/U55G4YILkL/I0NaEqYYoL05Rfs7y/tI4mqqI=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "fb1cf4398436a12f1f8b07da08d94fc72fcb1a69", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c0f974b --- /dev/null +++ b/flake.nix @@ -0,0 +1,128 @@ +{ + description = "Vaultix"; + + inputs = { + flake-parts.url = "github:hercules-ci/flake-parts"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + crane.url = "github:ipetkov/crane"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + pre-commit-hooks = { + url = "github:cachix/pre-commit-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + inputs@{ + flake-parts, + self, + crane, + ... + }: + flake-parts.lib.mkFlake { inherit inputs; } ( + { flake-parts-lib, withSystem, ... }: + let + inherit (flake-parts-lib) importApply; + flakeModules.default = importApply ./flake-module.nix { + inherit (self) packages; + inherit withSystem; + }; + in + { + imports = with inputs; [ + pre-commit-hooks.flakeModule + flakeModules.default + ]; + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + perSystem = + { + # config, + self', + # inputs', + pkgs, + system, + ... + }: + let + toolchain = pkgs.rust-bin.nightly.latest.minimal; + craneLib = (crane.mkLib pkgs).overrideToolchain toolchain; + inherit (craneLib) buildPackage; + in + { + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + overlays = with inputs; [ + rust-overlay.overlays.default + self.overlays.default + ]; + }; + + vaultix = { + nodes = self.nixosConfigurations; + }; + apps = { + default = { + type = "app"; + program = pkgs.lib.getExe self'.packages.default; + }; + }; + + packages = rec { + default = ( + buildPackage { + src = craneLib.cleanCargoSource ./.; + nativeBuildInputs = [ + pkgs.rustPlatform.bindgenHook + ]; + meta.mainProgram = "vaultix"; + } + ); + vaultix = default; + }; + + formatter = pkgs.nixfmt-rfc-style; + + devShells.default = craneLib.devShell { + inputsFrom = [ + pkgs.vaultix + ]; + + # see https://discourse.nixos.org/t/rust-src-not-found-and-other-misadventures-of-developing-rust-on-nixos/11570/12 + RUST_SRC_PATH = "${ + pkgs.rust-bin.nightly.latest.default.override { + extensions = [ "rust-src" ]; + } + }/lib/rustlib/src/rust/library"; + buildInputs = with pkgs; [ + just + nushell + rust-bin.beta.latest.complete + ]; + }; + + pre-commit = { + check.enable = true; + settings.hooks = { + nixfmt-rfc-style.enable = true; + }; + }; + + }; + flake = { + inherit flakeModules; + + overlays.default = final: prev: { + vaultix = inputs.self.packages.${prev.system}.default; + }; + nixosModules.default = import ./module self; + + }; + } + ); +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..798b3e4 --- /dev/null +++ b/justfile @@ -0,0 +1,15 @@ +set shell := ["nu", "-c"] + +pwd := `pwd` + +default: + @just --choose + +build-package: + nix build . + +clean-exist-deploy: + #!/usr/bin/env nu + sudo umount /run/vaultix.d + sudo rm -r /run/vaultix.d + sudo rm -r /run/vaultix diff --git a/module/default.nix b/module/default.nix new file mode 100644 index 0000000..9e18080 --- /dev/null +++ b/module/default.nix @@ -0,0 +1,292 @@ +vaultixSelf: +{ + config, + options, + pkgs, + lib, + self, + ... +}: +let + inherit (lib) + types + mkOption + isAttrs + isPath + readFile + literalExpression + mkEnableOption + mkIf + assertMsg + ; + inherit (config.users) users; + + cfg = config.vaultix; + + # Using systemd instead of activationScript. Required. + sysusers = assertMsg ( + options.systemd ? sysusers && (config.systemd.sysusers.enable || config.services.userborn.enable) + ) "`systemd.sysusers` or `services.userborn` must be enabled."; + + storagePath = self + "/" + cfg.settings.storageDirRelative; + storageExist = assertMsg (builtins.pathExists storagePath) "${storagePath} doesn't exist plz manually create and add to git first (may need a placeholder for git to recognize it)"; + + settingsType = types.submodule (submod: { + options = { + + storageDirRelative = mkOption { + type = types.str; + example = literalExpression ''./. /* <- flake root */ + "/secrets/renced/myhost" /* separate folder for each host */''; + description = '' + The local storage directory for rekeyed secrets. MUST be a str of path related to flake root. + ''; + }; + storageDirStore = mkOption { + type = types.path; + readOnly = true; + default = builtins.path { path = self + "/" + submod.config.storageDirRelative; }; + example = literalExpression ''./. /* <- flake root */ + "/secrets/renced/myhost" /* separate folder for each host */''; + description = '' + The local storage directory for rekeyed secrets. MUST be a str of path related to flake root. + ''; + }; + + decryptedDir = mkOption { + type = types.path; + default = "/run/vaultix"; + description = '' + Folder where secrets are symlinked to + ''; + }; + + hostKeys = mkOption { + type = types.listOf ( + types.submodule { + options = { + path = mkOption { + type = types.path; + }; + type = mkOption { + type = types.str; + }; + }; + } + ); + default = config.services.openssh.hostKeys; + readOnly = true; + description = '' + `config.services.openssh.hostKeys` + ''; + }; + + hostIdentifier = mkOption { + type = types.str; + default = config.networking.hostName; + readOnly = true; + description = '' + Host identifier + ''; + }; + + decryptedMountPoint = mkOption { + type = + types.addCheck types.str ( + s: + (builtins.match "[ \t\n]*" s) == null # non-empty + && (builtins.match ".+/" s) == null + ) # without trailing slash + // { + description = "${types.str.description} (with check: non-empty without trailing slash)"; + }; + default = "/run/vaultix.d"; + description = '' + Where secrets are created before they are symlinked to {option}`vaultix.settings.decryptedDir` + ''; + }; + + masterIdentities = mkOption { + type = + with types; + let + identityPathType = coercedTo path toString str; + in + listOf ( + # By coercing the old identityPathType into a canonical submodule of the form + # ``` + # { + # identity = ; + # pubkey = ...; + # } + # ``` + # we don't have to worry about it at a later stage. + coercedTo identityPathType + ( + p: + if isAttrs p then + p + else + { + identity = p; + } + ) + (submodule { + options = { + identity = mkOption { type = identityPathType; }; + pubkey = mkOption { + type = coercedTo path (x: if isPath x then readFile x else x) str; + default = ""; + }; + }; + }) + ); + default = [ ]; + example = [ + ./secrets/my-public-yubikey-identity.txt.pub + { + identity = ./password-encrypted-identity.pub; + pubkey = "age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290gq"; + } + ]; + }; + + extraRecipients = mkOption { + type = with types; listOf (coercedTo path toString str); + + default = [ ]; + example = [ + ./backup-key.pub + "age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290gq" + ]; + }; + + hostPubkey = mkOption { + type = with types; coercedTo path (x: if isPath x then readFile x else x) str; + default = # This pubkey is just binary 0x01 in each byte, so you can be sure there is no known private key for this + "age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290gq"; + + #example = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI....."; + #example = "age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290gq"; + example = literalExpression "./secrets/host1.pub"; + #example = "/etc/ssh/ssh_host_ed25519_key.pub"; + }; + + }; + + }); + + secretType = types.submodule (submod: { + options = { + id = mkOption { + type = types.str; + default = submod.config._module.args.name; + readOnly = true; + description = "The true identifier of this secret as used in `age.secrets`."; + }; + name = mkOption { + type = types.str; + default = submod.config._module.args.name; + defaultText = literalExpression "submod.config._module.args.name"; + description = '' + Name of the file used in {option}`vaultix.settings.decryptedDir` + ''; + }; + file = mkOption { + type = types.path; + description = '' + Age file the secret is loaded from. + ''; + }; + path = mkOption { + type = types.str; + default = "${cfg.settings.decryptedDir}/${submod.config.name}"; + defaultText = literalExpression '' + "''${cfg.settings.decryptedDir}/''${config.name}" + ''; + description = '' + Path where the decrypted secret is installed. + ''; + }; + mode = mkOption { + type = types.str; + default = "0400"; + description = '' + Permissions mode of the decrypted secret in a format understood by chmod. + ''; + }; + owner = mkOption { + type = types.str; + default = "0"; + description = '' + User of the decrypted secret. + ''; + }; + group = mkOption { + type = types.str; + default = users.${submod.config.owner}.group or "0"; + defaultText = literalExpression '' + users.''${config.owner}.group or "0" + ''; + description = '' + Group of the decrypted secret. + ''; + }; + symlink = mkEnableOption "symlinking secrets to their destination" // { + default = true; + }; + }; + }); + +in +{ + options.vaultix = { + + package = mkOption { + type = types.package; + default = vaultixSelf.packages.${pkgs.system}.vaultix; + }; + + settings = mkOption { + type = settingsType; + default = { }; + description = '' + Attrset of settings. + ''; + }; + + secrets = mkOption { + type = types.attrsOf secretType; + default = { }; + description = '' + Attrset of secrets. + ''; + }; + + }; + + config = + let + profile = pkgs.writeTextFile { + name = "secret-meta-${config.networking.hostName}"; + text = (builtins.toJSON cfg); + }; + checkRencSecsReport = + pkgs.runCommandNoCCLocal "secret-check-report" { } + "${lib.getExe cfg.package} ${profile} check > $out"; + in + mkIf (sysusers && storageExist) { + systemd.services.vaultix-install-secrets = { + wantedBy = [ "sysinit.target" ]; + after = [ "systemd-sysusers.service" ]; + unitConfig.DefaultDependencies = "no"; + serviceConfig = { + Type = "oneshot"; + Environment = [ + ("storage:" + cfg.settings.storageDirStore) + checkRencSecsReport + ]; + ExecStart = "${lib.getExe cfg.package} ${profile} deploy"; + RemainAfterExit = true; + }; + }; + }; +} diff --git a/src/cmd/check.rs b/src/cmd/check.rs new file mode 100644 index 0000000..9f7e352 --- /dev/null +++ b/src/cmd/check.rs @@ -0,0 +1,24 @@ +use eyre::Result; +use spdlog::error; + +use crate::{ + helper::stored::{InStore, SecMap, SecPath}, + profile::Profile, +}; + +impl Profile { + pub fn check(self) -> Result<()> { + let s_p_map = SecMap::>::from(self.secrets).inner(); + + s_p_map + .into_values() + .map(|p| { + if !p.path.exists() { + error!("path {} not exist, try run renc", p.path.display()); + return Err(eyre::eyre!("rencypted secret not in expected location",)); + } + Ok(()) + }) + .collect() + } +} diff --git a/src/cmd/deploy.rs b/src/cmd/deploy.rs new file mode 100644 index 0000000..90bb8fb --- /dev/null +++ b/src/cmd/deploy.rs @@ -0,0 +1,214 @@ +use std::{ + collections::HashMap, + fs::{self, OpenOptions, Permissions, ReadDir}, + io::{ErrorKind, Write}, + os::unix::fs::PermissionsExt, + path::PathBuf, + rc::Rc, + str::FromStr, +}; + +use crate::{ + helper::{ + self, + secret_buf::{HostEnc, SecBuf}, + stored::{InStore, SecMap, SecPath}, + }, + profile::{self, HostKey, Profile}, +}; + +use age::Recipient; +use eyre::{eyre, Context, Result}; +use spdlog::{debug, error, info, trace}; +use sys_mount::{Mount, MountFlags, SupportedFilesystems}; + +impl HostKey { + pub fn get_identity(&self) -> Result { + fs::read_to_string(&self.path) + .wrap_err_with(|| eyre!("reading ssh host key error: {}", self.path)) + .and_then(|i| { + age::ssh::Identity::from_buffer(i.as_bytes(), None) + .map_err(|e| eyre!("convert age identity from ssh key error: {}", e)) + }) + } +} + +const KEY_TYPE: &str = "ed25519"; +impl Profile { + pub fn get_decrypted_mount_point_path(&self) -> String { + self.settings.decrypted_mount_point.to_string() + } + pub fn get_decrypt_dir_path(&self) -> String { + self.settings.decrypted_dir.to_string() + } + pub fn read_decrypted_mount_point(&self) -> std::io::Result { + fs::read_dir(self.get_decrypted_mount_point_path()) + } + + pub fn get_host_key_identity(&self) -> Result { + if let Some(k) = self + .settings + .host_keys + .iter() + .find(|i| i.r#type == KEY_TYPE) + { + debug!("found host priv key: {:?}", k); + k.get_identity() + } else { + Err(eyre!("key with type {} not found", KEY_TYPE)) + } + } + pub fn get_host_recip(&self) -> Result> { + let host_pubkey = age::ssh::Recipient::from_str(self.settings.host_pubkey.as_str()) + .map_err(|_| eyre!("parse pubkey error"))?; + Ok(Rc::new(host_pubkey) as Rc) + } + pub fn _get_extra_recip(&self) -> Result>> { + let extra_recips = self + .settings + .extra_recipients + .iter() + .map(|r| { + age::x25519::Recipient::from_str(r.as_str()) + .map(|r| Box::new(r) as Box) + .map_err(|_| eyre!("parse extra recipient error")) + }) + .collect::>>>()?; + Ok(extra_recips.into_iter()) + } + + /// init decrypted mount point and return the generation count + pub fn init_decrypted_mount_point(&self) -> Result { + let mut max = 0; + let res = match self.read_decrypted_mount_point() { + Err(e) if e.kind() == ErrorKind::NotFound => { + let support_ramfs = + SupportedFilesystems::new().and_then(|fss| Ok(fss.is_supported("ramfs"))); + if !support_ramfs? { + let err = + "ramfs not supported! Refusing extract secret since it will write to disk"; + error!("{}", err); + return Err(eyre!(err)); + } + let path = self.get_decrypted_mount_point_path(); + info!("creating mount point {}", path.clone()); + fs::create_dir_all(path.clone()).wrap_err_with(|| { + format!( + "creating decrypted mountpoint: {:?}", + self.get_decrypted_mount_point_path() + ) + })?; + Mount::builder() + .fstype("ramfs") + .flags(MountFlags::NOSUID) + .data("relatime") + .data("mode=751") + .mount(String::default(), self.get_decrypted_mount_point_path()) + .map(|_| ()) // not needed. + .wrap_err(eyre!("mount tmpfs error")) + } + Err(e) => { + error!("{}", e); + Err(e).wrap_err(eyre!("read mountpoint error")) + } + Ok(o) => { + o.for_each(|en| { + match str::parse::( + en.unwrap() + .file_name() + .to_string_lossy() + .to_string() + .as_str(), + ) { + Err(e) => { + error!("parse mount point generation err: {:?}", e) + } + Ok(res) => { + debug!("found mountpoint generation {}", res); + if res >= max { + max = res + 1; + } + } + } + }); + Ok(()) + } + }; + + res.map(|_| max) + } + /** + extract secrets to `/run/vaultix.d/$num` and link to `/run/vaultix` + */ + pub fn deploy(self) -> Result<()> { + let sec_ciphertext_map: HashMap> = + SecMap::>::from(self.secrets.clone()) + .renced( + self.settings.storage_dir_store.clone().into(), + self.settings.host_pubkey.clone(), + ) + .bake_ctx()? + .inner(); + + trace!("{:?}", sec_ciphertext_map); + + let generation_count = self.init_decrypted_mount_point()?; + + let target_extract_dir_with_gen = { + let mut p = PathBuf::from(self.get_decrypted_mount_point_path()); + p.push(generation_count.to_string()); + + debug!("target extract dir with generation number: {:?}", p); + + fs::create_dir_all(&p) + .map(|_| p) + .wrap_err(eyre!( + "cannot create target extract dir with generation number" + )) + .and_then(|p| { + let _ = fs::set_permissions(&p, Permissions::from_mode(0o751)) + .wrap_err(eyre!("set permission")); + Ok(p) + })? + }; + + let host_prv_key = &self.get_host_key_identity()?; + + sec_ciphertext_map.into_iter().for_each(|(n, c)| { + let ctx = SecBuf::::new(c) + .decrypt(host_prv_key) + .expect("err"); + + info!("{} -> generation {}", n.name, generation_count); + let mut the_file = { + let mut p = target_extract_dir_with_gen.clone(); + p.push(n.name); + + let mode = helper::parse_permission::parse_octal_string(&n.mode).unwrap(); + let permissions = Permissions::from_mode(mode); + + let file = OpenOptions::new().create(true).write(true).open(p).unwrap(); + + file.set_permissions(permissions).unwrap(); + + helper::set_owner_group::set_owner_and_group(&file, &n.owner, &n.group) + .expect("good report"); + + file + }; + the_file + .write_all(ctx.buf_ref()) + .expect("write decrypted file error") + }); + + let _ = fs::remove_file(self.get_decrypt_dir_path()); + // link back to /run/vaultix + if std::os::unix::fs::symlink(target_extract_dir_with_gen, self.get_decrypt_dir_path()) + .wrap_err("create symlink error") + .is_ok() + { + info!("deployment success"); + } + Ok(()) + } +} diff --git a/src/cmd/edit.rs b/src/cmd/edit.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs new file mode 100644 index 0000000..069049f --- /dev/null +++ b/src/cmd/mod.rs @@ -0,0 +1,98 @@ +use std::{fs, path::PathBuf}; + +use eyre::Context; +use spdlog::prelude::*; +use {argh::FromArgs, std::fmt::Debug}; + +mod check; +mod deploy; +// mod edit; +mod renc; + +#[derive(FromArgs, PartialEq, Debug)] +/// Vaultix cli | Secret manager for NixOS +pub struct Args { + #[argh(subcommand)] + app: SubCmd, + #[argh(positional)] + /// toml secret profile + profile: String, + #[argh(option, short = 'f')] + /// toplevel of flake repository + flake_root: Option, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum SubCmd { + Renc(RencSubCmd), + Edit(EditSubCmd), + Check(CheckSubCmd), + Deploy(DeploySubCmd), +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Re-encrypt changed files +#[argh(subcommand, name = "renc")] +pub struct RencSubCmd { + #[argh(switch, short = 'a')] + /// rekey all + all: bool, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Edit encrypted file +#[argh(subcommand, name = "edit")] +pub struct EditSubCmd { + #[argh(positional)] + /// file to edit + file: String, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Decrypt and deploy cipher credentials +#[argh(subcommand, name = "deploy")] +pub struct DeploySubCmd {} + +#[derive(FromArgs, PartialEq, Debug)] +/// Check secret status +#[argh(subcommand, name = "check")] +pub struct CheckSubCmd {} + +impl Args { + /// Parse Command Args + pub fn ayaya(&self) -> eyre::Result<()> { + use super::profile::Profile; + + let profile: Profile = { + let file = fs::read_to_string(&self.profile).wrap_err("arg `profile` not found")?; + serde_json::from_str(file.as_str())? + }; + + let flake_root = if let Some(f) = &self.flake_root { + PathBuf::from(f) + } else { + std::env::current_dir()? + }; + + trace!("{:#?}", profile); + + match self.app { + SubCmd::Renc(RencSubCmd { all }) => { + info!("start re-encrypt secrets"); + profile.renc(all, flake_root) + } + SubCmd::Deploy(DeploySubCmd {}) => { + info!("deploying secrets"); + profile.deploy() + } + SubCmd::Edit(_) => todo!("you can directly use rage."), + SubCmd::Check(_) => { + info!("start checking"); + profile.check()?; + info!("check complete"); + Ok(()) + } + } + } +} diff --git a/src/cmd/renc.rs b/src/cmd/renc.rs new file mode 100644 index 0000000..0c58213 --- /dev/null +++ b/src/cmd/renc.rs @@ -0,0 +1,87 @@ +use eyre::{eyre, ContextCompat, Result}; +use spdlog::{error, info}; +use std::{fs, path::PathBuf}; + +use crate::helper::stored::Renc; +use crate::interop::add_to_store; +use crate::profile::{MasterIdentity, Profile}; + +use crate::helper::parse_identity::ParsedIdentity; +impl Profile { + pub fn get_key_pair_iter<'a>(&'a self) -> impl Iterator> + 'a { + self.settings + .master_identities + .iter() + .map(MasterIdentity::parse) + } + + /** + read secret metadata from profile + + First decrypt `./secrets/every` with masterIdentity's privkey, + Then compare hash with decrypted existing file (using hostKey), + encrypt with host public key, output to `./secrets/renced/$host` + and add to nix store. + */ + pub fn renc(self, _all: bool, flake_root: PathBuf) -> Result<()> { + let mut key_pair_list = self.get_key_pair_iter(); + info!( + "rencrypt for host {}", + self.settings.host_identifier.clone() + ); + + // check if flake root + if !fs::read_dir(&flake_root)?.into_iter().any(|e| { + e.is_ok_and(|ie| { + ie.file_name() + .into_string() + .is_ok_and(|iie| iie.as_str() == "flake.nix") + }) + }) { + error!("please run app in flake root"); + return Err(eyre!( + "`flake.nix` not found here, make sure run in flake toplevel." + )); + }; + + // absolute path, in config directory, suffix host ident + let renc_path = { + let mut p = flake_root.clone(); + p.push(self.settings.storage_dir_relative.clone()); + let p = p.canonicalize()?; + info!( + "reading user identity encrypted dir under flake root: {}", + p.display() + ); + p + }; + + // from secrets metadata, from real config store + let data = Renc::new( + self.secrets.clone(), + renc_path.clone(), + self.settings.host_pubkey.clone(), + ) + .filter_exist(); + + let parsed_ident = key_pair_list + .find(|k| k.is_ok()) + .wrap_err_with(|| eyre!("available keypair not found"))??; + + let key = parsed_ident.get_identity(); + + let recip = self.get_host_recip()?; + if let Err(e) = data.map.makeup(vec![recip], &**key) { + return Err(eyre!("makeup error: {}", e)); + } else { + let o = add_to_store(renc_path)?; + if !o.status.success() { + error!("Command executed with failing error code"); + } + // Another side, calculate with nix `builtins.path` and pass to when deploy as `storage` + info!("path added to store: {}", String::from_utf8(o.stdout)?); + } + + Ok(()) + } +} diff --git a/src/helper/callback.rs b/src/helper/callback.rs new file mode 100644 index 0000000..b5aefff --- /dev/null +++ b/src/helper/callback.rs @@ -0,0 +1,105 @@ +use age::secrecy::{ExposeSecret, SecretString}; +use age::Callbacks; +use pinentry::{ConfirmationDialog, PassphraseInput}; +use rpassword::prompt_password; +use std::io; +use subtle::ConstantTimeEq; + +#[derive(Clone, Copy)] +pub struct UiCallbacks; + +impl Callbacks for UiCallbacks { + fn display_message(&self, message: &str) { + eprintln!("{}", message); + } + + fn confirm(&self, message: &str, yes_string: &str, no_string: Option<&str>) -> Option { + confirm(message, yes_string, no_string).ok() + } + + fn request_public_string(&self, description: &str) -> Option { + let term = console::Term::stderr(); + term.write_str(description).ok()?; + term.read_line().ok().filter(|s| !s.is_empty()) + } + + fn request_passphrase(&self, description: &str) -> Option { + read_secret(description, "input password:", None).ok() + } +} +fn confirm(query: &str, ok: &str, cancel: Option<&str>) -> pinentry::Result { + if let Some(mut input) = ConfirmationDialog::with_default_binary() { + // pinentry binary is available! + input.with_ok(ok).with_timeout(30); + if let Some(cancel) = cancel { + input.with_cancel(cancel); + } + input.confirm(query) + } else { + // Fall back to CLI interface. + let term = console::Term::stderr(); + let initial = format!("{}: (y/n) ", query); + loop { + term.write_str(&initial)?; + let response = term.read_line()?.to_lowercase(); + if ["y", "yes"].contains(&response.as_str()) { + break Ok(true); + } else if ["n", "no"].contains(&response.as_str()) { + break Ok(false); + } + } + } +} + +pub fn read_secret( + description: &str, + prompt: &str, + confirm: Option<&str>, +) -> pinentry::Result { + // Check for the pinentry environment variable. If it's not present try to use the default + // binary. + let input = if let Ok(pinentry) = std::env::var("PINENTRY_PROGRAM") { + PassphraseInput::with_binary(pinentry) + } else { + PassphraseInput::with_default_binary() + }; + + if let Some(mut input) = input { + // User-set or default pinentry binary is available! + let mismatch_error = "secret mismatch"; + let empty_error = "require secret input"; + input + .with_description(description) + .with_prompt(prompt) + .with_timeout(30); + if let Some(confirm_prompt) = confirm { + input.with_confirmation(confirm_prompt, &mismatch_error); + } else { + input.required(&empty_error); + } + input.interact() + } else { + // Fall back to CLI interface. + let passphrase = prompt_password(format!("{}: ", description)).map(SecretString::new)?; + if let Some(confirm_prompt) = confirm { + let confirm_passphrase = + prompt_password(format!("{}: ", confirm_prompt)).map(SecretString::new)?; + + if !bool::from( + passphrase + .expose_secret() + .as_bytes() + .ct_eq(confirm_passphrase.expose_secret().as_bytes()), + ) { + return Err(pinentry::Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid Input", + ))); + } + } else if passphrase.expose_secret().is_empty() { + return Err(pinentry::Error::Cancelled); + } + + Ok(passphrase) + } +} diff --git a/src/helper/mod.rs b/src/helper/mod.rs new file mode 100644 index 0000000..459f21b --- /dev/null +++ b/src/helper/mod.rs @@ -0,0 +1,6 @@ +pub mod callback; +pub mod parse_identity; +pub mod parse_permission; +pub mod secret_buf; +pub mod set_owner_group; +pub mod stored; diff --git a/src/helper/parse_identity.rs b/src/helper/parse_identity.rs new file mode 100644 index 0000000..912defc --- /dev/null +++ b/src/helper/parse_identity.rs @@ -0,0 +1,59 @@ +use crate::profile::MasterIdentity; +use age::{Identity, IdentityFile, Recipient}; +use eyre::{eyre, ContextCompat, Result}; + +use super::callback::UiCallbacks; + +#[allow(dead_code)] +pub struct ParsedIdentity { + identity: Box, + recipient: Box, +} +impl ParsedIdentity { + pub fn from_exist(identity: Box, recipient: Box) -> Self { + Self { + identity, + recipient, + } + } + pub fn get_identity(&self) -> &Box { + &self.identity + } + pub fn _get_recipient(&self) -> &Box { + &self.recipient + } +} + +impl MasterIdentity { + // get identiy and recipient from identity file, + // only file that contains info of identity and recip supported at present + // which is expected while using age generated identity + pub fn parse( + Self { + identity, + pubkey: _, // not required. trans from prv key so fast. + }: &Self, + ) -> Result { + if identity.is_empty() { + return Err(eyre!("No identity found")); + } else { + macro_rules! create { + ($method:ident, $err_context:expr) => {{ + IdentityFile::from_file(identity.clone()) + .map_err(|e| eyre!("import from file error: {}", e))? + .with_callbacks(UiCallbacks) + .$method() + .map_err(|e| eyre!("{}", e))? + .into_iter() + .next() + .with_context(|| $err_context)? + }}; + } + let ident = create!(into_identities, "into identity fail"); + + let recip = create!(to_recipients, "into recip fail"); + + return Ok(ParsedIdentity::from_exist(ident, recip)); + } + } +} diff --git a/src/helper/parse_permission.rs b/src/helper/parse_permission.rs new file mode 100644 index 0000000..a2df034 --- /dev/null +++ b/src/helper/parse_permission.rs @@ -0,0 +1,18 @@ +use nom::{bytes::complete::take_while_m_n, combinator::map_res, IResult}; + +fn is_oct_digit(c: char) -> bool { + c.is_digit(8) +} + +pub fn parse_octal_string(input: &str) -> Result { + match parse_octal_permissions(input) { + Ok((_, octal)) => Ok(octal), + Err(_) => Err(format!("Failed to parse octal string: {}", input)), + } +} + +fn parse_octal_permissions(input: &str) -> IResult<&str, u32> { + map_res(take_while_m_n(1, 3, is_oct_digit), |oct_str: &str| { + u32::from_str_radix(oct_str, 8) + })(input) +} diff --git a/src/helper/secret_buf.rs b/src/helper/secret_buf.rs new file mode 100644 index 0000000..863ebbf --- /dev/null +++ b/src/helper/secret_buf.rs @@ -0,0 +1,73 @@ +use std::rc::Rc; +use std::{io::Read, iter, marker::PhantomData}; + +use age::{Identity, Recipient}; +use spdlog::debug; + +#[derive(Debug, Clone)] +pub struct AgeEnc; +#[derive(Debug, Clone)] +pub struct HostEnc; +#[derive(Debug, Clone)] +pub struct Plain; + +pub struct SecBuf { + buf: Vec, + _marker: PhantomData, +} + +impl SecBuf { + pub fn new(i: Vec) -> Self { + SecBuf { + buf: i, + _marker: PhantomData, + } + } +} + +use eyre::Result; +impl SecBuf { + pub fn buf_ref<'a>(&'a self) -> &'a Vec { + self.buf.as_ref() + } + pub fn decrypt(&self, ident: &dyn Identity) -> Result> { + let buffer = self.buf_ref(); + let decryptor = age::Decryptor::new(&buffer[..])?; + + let mut dec_ctx = vec![]; + let mut reader = decryptor.decrypt(iter::once(ident))?; + let res = reader.read_to_end(&mut dec_ctx); + if let Ok(b) = res { + debug!("decrypted secret {} bytes", b); + } + Ok(SecBuf::new(dec_ctx)) + } +} + +impl SecBuf { + pub fn renc(&self, ident: &dyn Identity, recips: Rc) -> Result> { + self.decrypt(ident).and_then(|d| d.encrypt(vec![recips])) + } +} +use eyre::eyre; +use spdlog::info; + +impl SecBuf { + /// encrypt with host pub key, ssh key + pub fn encrypt(self, recips: Vec>) -> Result> { + let recips_iter = recips.iter().map(|boxed| boxed.as_ref() as &dyn Recipient); + info!("things in recips iter {}", recips.len()); + let encryptor = age::Encryptor::with_recipients(recips_iter) + .map_err(|_| eyre!("create encryptor err"))?; + + let buf = self.buf_ref(); + let mut enc_ctx = vec![]; + + let mut writer = encryptor.wrap_output(&mut enc_ctx)?; + + use std::io::Write; + writer.write_all(buf)?; + writer.finish()?; + Ok(SecBuf::new(enc_ctx)) + } +} diff --git a/src/helper/set_owner_group.rs b/src/helper/set_owner_group.rs new file mode 100644 index 0000000..1ea2b51 --- /dev/null +++ b/src/helper/set_owner_group.rs @@ -0,0 +1,48 @@ +use eyre::{eyre, Result}; +use libc::{fchown, getgrnam, getpwnam}; +use spdlog::warn; +use std::{ffi::CString, fs::File, os::fd::AsRawFd}; + +pub fn set_owner_and_group(file: &File, owner: &str, group: &str) -> Result<()> { + let fd = file.as_raw_fd(); + + let user_uid = get_uid_from_username(owner).or_else(|_| { + warn!("get uid of {} failed, fall back to root", owner); + eyre::Ok(0) + })?; + let group_gid = get_gid_from_groupname(group).or_else(|_| { + warn!("get gid of {} failed, fall back to root", group); + eyre::Ok(0) + })?; + let result = unsafe { fchown(fd, user_uid, group_gid) }; + + if result == -1 { + return Err(eyre::eyre!("set permission failed")); + } + Ok(()) +} + +fn get_uid_from_username(username: &str) -> Result { + let c_username = CString::new(username).map_err(|_| eyre!("Invalid username: {}", username))?; + + unsafe { + let pw = getpwnam(c_username.as_ptr()); + if pw.is_null() { + return Err(eyre!("User '{}' not found", username)); + } + Ok((*pw).pw_uid) + } +} + +fn get_gid_from_groupname(groupname: &str) -> Result { + let c_groupname = + CString::new(groupname).map_err(|_| eyre!("Invalid groupname: {}", groupname))?; + + unsafe { + let gr = getgrnam(c_groupname.as_ptr()); + if gr.is_null() { + return Err(eyre!("Group '{}' not found", groupname)); + } + Ok((*gr).gr_gid) + } +} diff --git a/src/helper/stored.rs b/src/helper/stored.rs new file mode 100644 index 0000000..5cb83bf --- /dev/null +++ b/src/helper/stored.rs @@ -0,0 +1,261 @@ +use std::{ + collections::HashMap, + fmt, + fs::{self, File}, + io::Read, + path::{Path, PathBuf}, + rc::Rc, +}; + +use age::{Identity, Recipient}; +use eyre::Context; +use spdlog::trace; + +use crate::{ + helper::secret_buf::{AgeEnc, SecBuf}, + profile::{self, SecretSet}, +}; +use eyre::{eyre, Result}; +use std::marker::PhantomData; + +#[derive(Debug, Clone)] +pub struct SecPath, T> { + pub path: P, + _marker: PhantomData, +} + +impl, T> fmt::Display for SecPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.path.as_ref().display()) + } +} + +#[derive(Debug, Clone)] +pub struct InStore; +#[derive(Debug, Clone)] +pub struct InCfg; + +pub trait GetSec { + fn read_buffer(&self) -> Result>; + fn open_file(&self) -> Result; +} + +impl SecPath +where + P: AsRef, +{ + pub fn new(path: P) -> Self { + SecPath { + path, + _marker: PhantomData, + } + } +} + +impl GetSec for SecPath +where + P: AsRef, +{ + fn open_file(&self) -> Result { + trace!("opening {}", &self); + File::open(&self.path).wrap_err_with(|| eyre!("open secret file error")) + } + + fn read_buffer(&self) -> Result> { + let mut f = self.open_file()?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer) + .wrap_err_with(|| eyre!("read secret file error"))?; + Ok(buffer) + } +} + +macro_rules! impl_from_iterator_for_secmap { + ($($t:ty),*) => { + $( + impl FromIterator<(profile::Secret, $t)> for SecMap<$t> { + fn from_iter>(iter: I) -> Self { + let map = HashMap::from_iter(iter); + SecMap(map) + } + } + )* + }; +} + +impl_from_iterator_for_secmap!(Vec, blake3::Hash, UniPath); + +#[derive(Debug, Clone)] +pub struct SecMap

(HashMap); + +impl SecMap { + pub fn inner(self) -> HashMap { + self.0 + } +} +impl SecPath { + pub fn calc_hash(&self, host_ssh_recip: String) -> Result { + let mut hasher = blake3::Hasher::new(); + hasher.update(self.read_buffer()?.as_slice()); + hasher.update(host_ssh_recip.as_bytes()); + Ok(hasher.finalize()) + } +} + +impl SecMap> { + /// read secret file + pub fn bake_ctx(self) -> Result>> { + self.inner() + .into_iter() + // TODO: reduce read + .map(|(k, v)| v.read_buffer().and_then(|b| Ok((k, b)))) + .try_collect::>>() + } +} + +impl SecMap> { + pub fn from(secrets: SecretSet) -> Self { + let res = secrets + .into_values() + .into_iter() + .map(|s| { + let secret_path = SecPath::<_, InStore>::new(PathBuf::from(s.file.clone())); + (s, secret_path) + }) + .collect(); + SecMap::>(res) + } + pub fn renced(self, per_host_dir: PathBuf, host_pubkey: String) -> Self { + let res = self + .inner() + .into_iter() + .map(|(k, v)| { + let mut dir = per_host_dir.clone(); + let sec_path = v; + let sec_hash = sec_path + .calc_hash(host_pubkey.clone()) + .expect("meow") + .to_string(); + dir.push(sec_hash); + + let renced_in_per_host_dir = dir; + (k, SecPath::new(renced_in_per_host_dir)) + }) + .collect::>>(); + SecMap::>(res) + } +} + +#[derive(Debug, Clone)] +pub struct UniPath { + store: SecPath, + real: SecPath, +} + +impl UniPath { + pub fn new(store: SecPath, real: SecPath) -> Self { + UniPath { store, real } + } +} + +pub struct Renc { + pub map: SecMap, + host_dir: PathBuf, + host_recip: String, +} +impl Renc { + pub fn new(secrets: SecretSet, host_dir: PathBuf, host_recip: String) -> Self { + let p2 = SecMap::>::from(secrets); + let p1 = SecMap::>::from( + p2.clone(), + host_dir.clone(), + host_recip.clone(), + ) + .inner(); + let p2 = p2.inner(); + + let mut merged_map = HashMap::new(); + + p1.into_iter().for_each(|(key, vout)| { + if let Some(vin) = p2.get(&key) { + merged_map.insert(key, UniPath::new(vin.clone(), vout)); + } + }); + Renc { + map: SecMap(merged_map), + host_dir, + host_recip, + } + } + + pub fn filter_exist(self) -> Self { + let ret = self + .map + .inner() + .into_iter() + .filter_map(|(k, v)| { + let enc_hash = v.store.calc_hash(self.host_recip.clone()).ok()?; + let mut renc_path = self.host_dir.clone(); + renc_path.push(enc_hash.to_string()); + if renc_path.exists() { + return None; + } + Some((k, v)) + }) + .collect::>(); + Renc { + map: SecMap(ret), + host_dir: self.host_dir, + host_recip: self.host_recip, + } + } +} + +impl SecMap { + pub fn makeup(self, recips: Vec>, ident: &dyn Identity) -> Result<()> { + self.inner() + .into_iter() + .map(|(_sec, sec_path)| { + let UniPath { store, real } = sec_path; + use std::io::Write; + + trace!("re-encrypted output path {}", real.path.display()); + let enc_ctx = store.read_buffer().expect("read buffer in store err"); + // rencrypt + let renc_ctx = SecBuf::::new(enc_ctx) + .renc(ident, recips.first().expect("have").clone()) + .expect("renc_ctx err"); + + let mut target_file = fs::OpenOptions::new() + .write(true) + .create(true) + .open(real.path.clone())?; + + target_file + .write_all(renc_ctx.buf_ref()) + .wrap_err_with(|| eyre!("write renc file error")) + // Ok(()) + }) + .collect() + } +} + +impl SecMap> { + fn from( + value: SecMap>, + host_dir: PathBuf, + host_recip: String, + ) -> Self { + let res = value + .inner() + .into_iter() + .map(|(k, v)| { + let enc_hash = v.calc_hash(host_recip.clone()).expect("ok"); + let mut renc_path = host_dir.clone(); + renc_path.push(enc_hash.to_string()); + (k, SecPath::<_, InCfg>::new(renc_path)) + }) + .collect::>>(); + SecMap(res) + } +} diff --git a/src/interop/mod.rs b/src/interop/mod.rs new file mode 100644 index 0000000..90877df --- /dev/null +++ b/src/interop/mod.rs @@ -0,0 +1,15 @@ +use std::{ + path::Path, + process::{Command, Output}, +}; + +use eyre::{eyre, Result}; + +pub fn add_to_store>(p: P) -> Result { + Command::new("nix") + .arg("store") + .arg("add-path") + .arg(p.as_ref()) + .output() + .map_err(|i| eyre!("nix cmd run failed {}", i)) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c4a71ae --- /dev/null +++ b/src/main.rs @@ -0,0 +1,25 @@ +#![feature(iterator_try_collect)] +use cmd::Args; +use eyre::Result; +use spdlog::formatter::{pattern, PatternFormatter}; + +mod cmd; +mod helper; +mod interop; +mod profile; +use std::os::unix::process::parent_id; + +fn main() -> Result<()> { + let _ = spdlog::init_env_level(); + let as_sd_unit = parent_id() == 1; + if as_sd_unit { + for sink in spdlog::default_logger().sinks() { + sink.set_formatter(Box::new(PatternFormatter::new(pattern!( + "{^{level}} - {payload}{eol}" + )))) + } + spdlog::debug!("Detected running as systemd unit"); + } + let args: Args = argh::from_env(); + args.ayaya() +} diff --git a/src/profile.rs b/src/profile.rs new file mode 100644 index 0000000..c0ed5ec --- /dev/null +++ b/src/profile.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; + +use serde::Deserialize; + +pub type SecretSet = HashMap; + +#[derive(Debug, Deserialize)] +pub struct Profile { + pub secrets: SecretSet, + pub settings: Settings, +} + +#[derive(Debug, Deserialize, Clone, Hash, Eq, PartialEq)] +pub struct Secret { + pub id: String, + pub file: String, + pub group: String, + pub mode: String, + pub name: String, + pub owner: String, + pub path: String, + pub symlink: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Settings { + pub decrypted_dir: String, + pub decrypted_mount_point: String, + pub host_identifier: String, + #[allow(dead_code)] + pub extra_recipients: Vec, + pub host_pubkey: String, + pub host_keys: Vec, + pub storage_dir_relative: String, + pub storage_dir_store: String, + pub master_identities: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct MasterIdentity { + pub identity: String, + #[allow(dead_code)] + pub pubkey: String, +} +#[derive(Debug, Deserialize)] +pub struct HostKey { + pub path: String, + pub r#type: String, +}