├── .github └── workflows │ ├── main.yaml │ └── update-deps.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── default.nix ├── docs ├── ragenix.1 ├── ragenix.1.html └── ragenix.1.ronn ├── example ├── github-runner.token.age ├── keys │ ├── README.md │ ├── example_plugin_key.txt │ ├── id_ed25519 │ ├── id_rsa │ └── key.txt ├── root.passwd.age ├── secrets-configuration.nix ├── secrets-plugin.nix └── secrets.nix ├── flake.lock ├── flake.nix ├── rust-toolchain ├── src ├── age.rs ├── cli.rs ├── main.rs ├── ragenix │ ├── agenix.schema.json │ └── mod.rs └── util.rs └── tests └── ragenix.rs /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build: 12 | name: 'Build' 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, macos-latest] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - name: 'Checkout source' 19 | uses: actions/checkout@v4 20 | - name: 'Install Nix Flakes' 21 | uses: cachix/install-nix-action@v30 22 | - name: 'Setup Cachix' 23 | uses: cachix/cachix-action@v15 24 | with: 25 | name: wurzelpfropf 26 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN_PUBLIC }}' 27 | - name: 'Build default package' 28 | run: nix build -L . 29 | 30 | check: 31 | name: 'Check' 32 | strategy: 33 | matrix: 34 | os: [ubuntu-latest, macos-latest] 35 | runs-on: ${{ matrix.os }} 36 | steps: 37 | - name: 'Checkout source' 38 | uses: actions/checkout@v4 39 | - name: 'Install Nix Flakes (nixos-test, recursive-nix)' 40 | uses: cachix/install-nix-action@v30 41 | with: 42 | extra_nix_config: | 43 | experimental-features = nix-command flakes recursive-nix 44 | system-features = nixos-test benchmark big-parallel kvm recursive-nix 45 | - name: 'Setup Cachix' 46 | uses: cachix/cachix-action@v15 47 | with: 48 | name: wurzelpfropf 49 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN_PUBLIC }}' 50 | - name: 'Run all checks' 51 | run: nix flake check -L . 52 | -------------------------------------------------------------------------------- /.github/workflows/update-deps.yaml: -------------------------------------------------------------------------------- 1 | name: 'Update flake inputs & Cargo dependencies' 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 2 * * 0' 7 | 8 | env: 9 | NIX_CONFIG: 'access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}' 10 | 11 | jobs: 12 | update-deps: 13 | runs-on: [ ubuntu-latest ] 14 | steps: 15 | - name: 'Checkout' 16 | uses: actions/checkout@v4 17 | with: 18 | persist-credentials: false 19 | 20 | - name: 'Install Nix' 21 | uses: cachix/install-nix-action@v25 22 | 23 | - name: 'Get user data of token' 24 | uses: actions/github-script@v7 25 | id: pat-user 26 | with: 27 | github-token: ${{ secrets.PR }} 28 | script: | 29 | const { data } = await github.request("/user"); 30 | if (data.email == null) { 31 | data.email = `${data.id}+${data.login}@users.noreply.github.com`; 32 | } 33 | if (data.name == null) { 34 | data.name = data.login; 35 | } 36 | 37 | core.exportVariable('GIT_USER_NAME', data.name); 38 | core.exportVariable('GIT_USER_EMAIL', data.email); 39 | 40 | return data 41 | 42 | - name: 'Configure Git with PR user' 43 | run: | 44 | git config user.name "$GIT_USER_NAME" 45 | git config user.email "$GIT_USER_EMAIL" 46 | 47 | - name: 'Update flake inputs and commit' 48 | run: | 49 | nix flake update --commit-lock-file 50 | 51 | - name: 'Install workflow step dependencies' 52 | uses: yaxitech/nix-install-pkgs-action@v3 53 | with: 54 | packages: gnused 55 | 56 | - name: 'Get flake metadata' 57 | uses: actions/github-script@v7 58 | id: flake-metadata 59 | with: 60 | result-encoding: string 61 | script: | 62 | const res = await exec.getExecOutput('nix', [ 'flake', 'metadata' ]) 63 | .then((resRaw) => { 64 | // The sed expression strips any control sequences (e.g., the bold text elements) 65 | return exec.getExecOutput( 66 | 'sed', 67 | [ '-E', 's/[[:cntrl:]]\[[0-9]{1,3}m//g' ], 68 | { input: Buffer.from(resRaw.stdout) } 69 | ); 70 | }); 71 | 72 | return res.stdout; 73 | 74 | - name: 'Update Cargo dependencies and commit' 75 | run: | 76 | nix develop -c cargo update 77 | (git diff --quiet && git diff --staged --quiet) || git commit -am 'Cargo.lock: Update' 78 | 79 | - name: 'Create Pull Request' 80 | uses: peter-evans/create-pull-request@v6 81 | with: 82 | branch: update-flake 83 | token: ${{ secrets.PR }} 84 | title: Update flake inputs 85 | body: | 86 | Updated Flake dependencies through `nix flake update`. 87 | 88 | ``` 89 | ${{ steps.flake-metadata.outputs.result }} 90 | ``` 91 | 92 | Updated Cargo dependencies through `cargo update`. 93 | 94 | Dependency status of `main` prior to this PR: 95 | [![dependency status](https://deps.rs/repo/github/yaxitech/ragenix/status.svg) 96 | ](https://deps.rs/repo/github/yaxitech/ragenix) 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /result 2 | /target 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aead" 22 | version = "0.5.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 25 | dependencies = [ 26 | "crypto-common", 27 | "generic-array", 28 | ] 29 | 30 | [[package]] 31 | name = "aes" 32 | version = "0.8.4" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 35 | dependencies = [ 36 | "cfg-if", 37 | "cipher", 38 | "cpufeatures", 39 | ] 40 | 41 | [[package]] 42 | name = "aes-gcm" 43 | version = "0.10.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" 46 | dependencies = [ 47 | "aead", 48 | "aes", 49 | "cipher", 50 | "ctr", 51 | "ghash", 52 | "subtle", 53 | ] 54 | 55 | [[package]] 56 | name = "age" 57 | version = "0.10.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "77de71da1ca673855aacea507a7aed363beb8934cf61b62364fc4b479d2e8cda" 60 | dependencies = [ 61 | "aes", 62 | "aes-gcm", 63 | "age-core", 64 | "base64 0.21.7", 65 | "bcrypt-pbkdf", 66 | "bech32", 67 | "cbc", 68 | "chacha20poly1305", 69 | "cipher", 70 | "console", 71 | "cookie-factory", 72 | "ctr", 73 | "curve25519-dalek", 74 | "hmac", 75 | "i18n-embed", 76 | "i18n-embed-fl", 77 | "is-terminal", 78 | "lazy_static", 79 | "nom 7.1.3", 80 | "num-traits", 81 | "pin-project", 82 | "pinentry", 83 | "rand", 84 | "rpassword", 85 | "rsa", 86 | "rust-embed", 87 | "scrypt", 88 | "sha2", 89 | "subtle", 90 | "which", 91 | "wsl", 92 | "x25519-dalek", 93 | "zeroize", 94 | ] 95 | 96 | [[package]] 97 | name = "age-core" 98 | version = "0.10.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "a5f11899bc2bbddd135edbc30c36b1924fa59d0746bb45beb5933fafe3fe509b" 101 | dependencies = [ 102 | "base64 0.21.7", 103 | "chacha20poly1305", 104 | "cookie-factory", 105 | "hkdf", 106 | "io_tee", 107 | "nom 7.1.3", 108 | "rand", 109 | "secrecy", 110 | "sha2", 111 | "tempfile", 112 | ] 113 | 114 | [[package]] 115 | name = "ahash" 116 | version = "0.8.11" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 119 | dependencies = [ 120 | "cfg-if", 121 | "getrandom 0.2.15", 122 | "once_cell", 123 | "serde", 124 | "version_check", 125 | "zerocopy", 126 | ] 127 | 128 | [[package]] 129 | name = "aho-corasick" 130 | version = "1.1.3" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 133 | dependencies = [ 134 | "memchr", 135 | ] 136 | 137 | [[package]] 138 | name = "anstream" 139 | version = "0.6.18" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 142 | dependencies = [ 143 | "anstyle", 144 | "anstyle-parse", 145 | "anstyle-query", 146 | "anstyle-wincon", 147 | "colorchoice", 148 | "is_terminal_polyfill", 149 | "utf8parse", 150 | ] 151 | 152 | [[package]] 153 | name = "anstyle" 154 | version = "1.0.10" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 157 | 158 | [[package]] 159 | name = "anstyle-parse" 160 | version = "0.2.6" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 163 | dependencies = [ 164 | "utf8parse", 165 | ] 166 | 167 | [[package]] 168 | name = "anstyle-query" 169 | version = "1.1.2" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 172 | dependencies = [ 173 | "windows-sys 0.59.0", 174 | ] 175 | 176 | [[package]] 177 | name = "anstyle-wincon" 178 | version = "3.0.7" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 181 | dependencies = [ 182 | "anstyle", 183 | "once_cell", 184 | "windows-sys 0.59.0", 185 | ] 186 | 187 | [[package]] 188 | name = "anyhow" 189 | version = "1.0.97" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 192 | 193 | [[package]] 194 | name = "arc-swap" 195 | version = "1.7.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 198 | 199 | [[package]] 200 | name = "assert_cmd" 201 | version = "2.0.16" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" 204 | dependencies = [ 205 | "anstyle", 206 | "bstr", 207 | "doc-comment", 208 | "libc", 209 | "predicates", 210 | "predicates-core", 211 | "predicates-tree", 212 | "wait-timeout", 213 | ] 214 | 215 | [[package]] 216 | name = "autocfg" 217 | version = "1.4.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 220 | 221 | [[package]] 222 | name = "backtrace" 223 | version = "0.3.71" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 226 | dependencies = [ 227 | "addr2line", 228 | "cc", 229 | "cfg-if", 230 | "libc", 231 | "miniz_oxide", 232 | "object", 233 | "rustc-demangle", 234 | ] 235 | 236 | [[package]] 237 | name = "base64" 238 | version = "0.21.7" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 241 | 242 | [[package]] 243 | name = "base64" 244 | version = "0.22.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 247 | 248 | [[package]] 249 | name = "base64ct" 250 | version = "1.6.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 253 | 254 | [[package]] 255 | name = "basic-toml" 256 | version = "0.1.10" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" 259 | dependencies = [ 260 | "serde", 261 | ] 262 | 263 | [[package]] 264 | name = "bcrypt-pbkdf" 265 | version = "0.10.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" 268 | dependencies = [ 269 | "blowfish", 270 | "pbkdf2", 271 | "sha2", 272 | ] 273 | 274 | [[package]] 275 | name = "bech32" 276 | version = "0.9.1" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" 279 | 280 | [[package]] 281 | name = "bit-set" 282 | version = "0.5.3" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 285 | dependencies = [ 286 | "bit-vec", 287 | ] 288 | 289 | [[package]] 290 | name = "bit-vec" 291 | version = "0.6.3" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 294 | 295 | [[package]] 296 | name = "bitflags" 297 | version = "2.9.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 300 | 301 | [[package]] 302 | name = "block-buffer" 303 | version = "0.10.4" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 306 | dependencies = [ 307 | "generic-array", 308 | ] 309 | 310 | [[package]] 311 | name = "block-padding" 312 | version = "0.3.3" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" 315 | dependencies = [ 316 | "generic-array", 317 | ] 318 | 319 | [[package]] 320 | name = "blowfish" 321 | version = "0.9.1" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" 324 | dependencies = [ 325 | "byteorder", 326 | "cipher", 327 | ] 328 | 329 | [[package]] 330 | name = "bstr" 331 | version = "1.11.3" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" 334 | dependencies = [ 335 | "memchr", 336 | "regex-automata", 337 | "serde", 338 | ] 339 | 340 | [[package]] 341 | name = "bumpalo" 342 | version = "3.17.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 345 | 346 | [[package]] 347 | name = "bytecount" 348 | version = "0.6.8" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" 351 | 352 | [[package]] 353 | name = "byteorder" 354 | version = "1.5.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 357 | 358 | [[package]] 359 | name = "cbc" 360 | version = "0.1.2" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" 363 | dependencies = [ 364 | "cipher", 365 | ] 366 | 367 | [[package]] 368 | name = "cc" 369 | version = "1.2.16" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" 372 | dependencies = [ 373 | "shlex", 374 | ] 375 | 376 | [[package]] 377 | name = "cfg-if" 378 | version = "1.0.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 381 | 382 | [[package]] 383 | name = "chacha20" 384 | version = "0.9.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" 387 | dependencies = [ 388 | "cfg-if", 389 | "cipher", 390 | "cpufeatures", 391 | ] 392 | 393 | [[package]] 394 | name = "chacha20poly1305" 395 | version = "0.10.1" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" 398 | dependencies = [ 399 | "aead", 400 | "chacha20", 401 | "cipher", 402 | "poly1305", 403 | "zeroize", 404 | ] 405 | 406 | [[package]] 407 | name = "cipher" 408 | version = "0.4.4" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 411 | dependencies = [ 412 | "crypto-common", 413 | "inout", 414 | "zeroize", 415 | ] 416 | 417 | [[package]] 418 | name = "clap" 419 | version = "4.5.31" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" 422 | dependencies = [ 423 | "clap_builder", 424 | ] 425 | 426 | [[package]] 427 | name = "clap_builder" 428 | version = "4.5.31" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" 431 | dependencies = [ 432 | "anstream", 433 | "anstyle", 434 | "clap_lex", 435 | "strsim 0.11.1", 436 | ] 437 | 438 | [[package]] 439 | name = "clap_complete" 440 | version = "4.5.46" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98" 443 | dependencies = [ 444 | "clap", 445 | ] 446 | 447 | [[package]] 448 | name = "clap_lex" 449 | version = "0.7.4" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 452 | 453 | [[package]] 454 | name = "color-eyre" 455 | version = "0.6.3" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 458 | dependencies = [ 459 | "backtrace", 460 | "eyre", 461 | "indenter", 462 | "once_cell", 463 | "owo-colors", 464 | ] 465 | 466 | [[package]] 467 | name = "colorchoice" 468 | version = "1.0.3" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 471 | 472 | [[package]] 473 | name = "console" 474 | version = "0.15.11" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 477 | dependencies = [ 478 | "encode_unicode", 479 | "libc", 480 | "once_cell", 481 | "windows-sys 0.59.0", 482 | ] 483 | 484 | [[package]] 485 | name = "const-oid" 486 | version = "0.9.6" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 489 | 490 | [[package]] 491 | name = "cookie-factory" 492 | version = "0.3.3" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" 495 | dependencies = [ 496 | "futures", 497 | ] 498 | 499 | [[package]] 500 | name = "copy_dir" 501 | version = "0.1.3" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" 504 | dependencies = [ 505 | "walkdir", 506 | ] 507 | 508 | [[package]] 509 | name = "cpufeatures" 510 | version = "0.2.17" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 513 | dependencies = [ 514 | "libc", 515 | ] 516 | 517 | [[package]] 518 | name = "crypto-common" 519 | version = "0.1.6" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 522 | dependencies = [ 523 | "generic-array", 524 | "rand_core", 525 | "typenum", 526 | ] 527 | 528 | [[package]] 529 | name = "ctr" 530 | version = "0.9.2" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" 533 | dependencies = [ 534 | "cipher", 535 | ] 536 | 537 | [[package]] 538 | name = "curve25519-dalek" 539 | version = "4.1.3" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" 542 | dependencies = [ 543 | "cfg-if", 544 | "cpufeatures", 545 | "curve25519-dalek-derive", 546 | "fiat-crypto", 547 | "rustc_version", 548 | "subtle", 549 | "zeroize", 550 | ] 551 | 552 | [[package]] 553 | name = "curve25519-dalek-derive" 554 | version = "0.1.1" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" 557 | dependencies = [ 558 | "proc-macro2", 559 | "quote", 560 | "syn 2.0.99", 561 | ] 562 | 563 | [[package]] 564 | name = "dashmap" 565 | version = "5.5.3" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 568 | dependencies = [ 569 | "cfg-if", 570 | "hashbrown", 571 | "lock_api", 572 | "once_cell", 573 | "parking_lot_core", 574 | ] 575 | 576 | [[package]] 577 | name = "der" 578 | version = "0.7.9" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" 581 | dependencies = [ 582 | "const-oid", 583 | "zeroize", 584 | ] 585 | 586 | [[package]] 587 | name = "deranged" 588 | version = "0.3.11" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 591 | dependencies = [ 592 | "powerfmt", 593 | ] 594 | 595 | [[package]] 596 | name = "difflib" 597 | version = "0.4.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 600 | 601 | [[package]] 602 | name = "digest" 603 | version = "0.10.7" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 606 | dependencies = [ 607 | "block-buffer", 608 | "const-oid", 609 | "crypto-common", 610 | "subtle", 611 | ] 612 | 613 | [[package]] 614 | name = "displaydoc" 615 | version = "0.2.5" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 618 | dependencies = [ 619 | "proc-macro2", 620 | "quote", 621 | "syn 2.0.99", 622 | ] 623 | 624 | [[package]] 625 | name = "doc-comment" 626 | version = "0.3.3" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 629 | 630 | [[package]] 631 | name = "either" 632 | version = "1.15.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 635 | 636 | [[package]] 637 | name = "encode_unicode" 638 | version = "1.0.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 641 | 642 | [[package]] 643 | name = "errno" 644 | version = "0.3.10" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 647 | dependencies = [ 648 | "libc", 649 | "windows-sys 0.59.0", 650 | ] 651 | 652 | [[package]] 653 | name = "eyre" 654 | version = "0.6.12" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 657 | dependencies = [ 658 | "indenter", 659 | "once_cell", 660 | ] 661 | 662 | [[package]] 663 | name = "fancy-regex" 664 | version = "0.13.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" 667 | dependencies = [ 668 | "bit-set", 669 | "regex-automata", 670 | "regex-syntax", 671 | ] 672 | 673 | [[package]] 674 | name = "fastrand" 675 | version = "2.3.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 678 | 679 | [[package]] 680 | name = "fiat-crypto" 681 | version = "0.2.9" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" 684 | 685 | [[package]] 686 | name = "find-crate" 687 | version = "0.6.3" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" 690 | dependencies = [ 691 | "toml", 692 | ] 693 | 694 | [[package]] 695 | name = "float-cmp" 696 | version = "0.10.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" 699 | dependencies = [ 700 | "num-traits", 701 | ] 702 | 703 | [[package]] 704 | name = "fluent" 705 | version = "0.16.1" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" 708 | dependencies = [ 709 | "fluent-bundle", 710 | "unic-langid", 711 | ] 712 | 713 | [[package]] 714 | name = "fluent-bundle" 715 | version = "0.15.3" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" 718 | dependencies = [ 719 | "fluent-langneg", 720 | "fluent-syntax", 721 | "intl-memoizer", 722 | "intl_pluralrules", 723 | "rustc-hash", 724 | "self_cell 0.10.3", 725 | "smallvec", 726 | "unic-langid", 727 | ] 728 | 729 | [[package]] 730 | name = "fluent-langneg" 731 | version = "0.13.0" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" 734 | dependencies = [ 735 | "unic-langid", 736 | ] 737 | 738 | [[package]] 739 | name = "fluent-syntax" 740 | version = "0.11.1" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" 743 | dependencies = [ 744 | "thiserror", 745 | ] 746 | 747 | [[package]] 748 | name = "form_urlencoded" 749 | version = "1.2.1" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 752 | dependencies = [ 753 | "percent-encoding", 754 | ] 755 | 756 | [[package]] 757 | name = "fraction" 758 | version = "0.15.3" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" 761 | dependencies = [ 762 | "lazy_static", 763 | "num", 764 | ] 765 | 766 | [[package]] 767 | name = "futures" 768 | version = "0.3.31" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 771 | dependencies = [ 772 | "futures-channel", 773 | "futures-core", 774 | "futures-executor", 775 | "futures-io", 776 | "futures-sink", 777 | "futures-task", 778 | "futures-util", 779 | ] 780 | 781 | [[package]] 782 | name = "futures-channel" 783 | version = "0.3.31" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 786 | dependencies = [ 787 | "futures-core", 788 | "futures-sink", 789 | ] 790 | 791 | [[package]] 792 | name = "futures-core" 793 | version = "0.3.31" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 796 | 797 | [[package]] 798 | name = "futures-executor" 799 | version = "0.3.31" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 802 | dependencies = [ 803 | "futures-core", 804 | "futures-task", 805 | "futures-util", 806 | ] 807 | 808 | [[package]] 809 | name = "futures-io" 810 | version = "0.3.31" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 813 | 814 | [[package]] 815 | name = "futures-macro" 816 | version = "0.3.31" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 819 | dependencies = [ 820 | "proc-macro2", 821 | "quote", 822 | "syn 2.0.99", 823 | ] 824 | 825 | [[package]] 826 | name = "futures-sink" 827 | version = "0.3.31" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 830 | 831 | [[package]] 832 | name = "futures-task" 833 | version = "0.3.31" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 836 | 837 | [[package]] 838 | name = "futures-util" 839 | version = "0.3.31" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 842 | dependencies = [ 843 | "futures-channel", 844 | "futures-core", 845 | "futures-io", 846 | "futures-macro", 847 | "futures-sink", 848 | "futures-task", 849 | "memchr", 850 | "pin-project-lite", 851 | "pin-utils", 852 | "slab", 853 | ] 854 | 855 | [[package]] 856 | name = "generic-array" 857 | version = "0.14.7" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 860 | dependencies = [ 861 | "typenum", 862 | "version_check", 863 | ] 864 | 865 | [[package]] 866 | name = "getrandom" 867 | version = "0.2.15" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 870 | dependencies = [ 871 | "cfg-if", 872 | "js-sys", 873 | "libc", 874 | "wasi 0.11.0+wasi-snapshot-preview1", 875 | "wasm-bindgen", 876 | ] 877 | 878 | [[package]] 879 | name = "getrandom" 880 | version = "0.3.1" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 883 | dependencies = [ 884 | "cfg-if", 885 | "libc", 886 | "wasi 0.13.3+wasi-0.2.2", 887 | "windows-targets 0.52.6", 888 | ] 889 | 890 | [[package]] 891 | name = "ghash" 892 | version = "0.5.1" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" 895 | dependencies = [ 896 | "opaque-debug", 897 | "polyval", 898 | ] 899 | 900 | [[package]] 901 | name = "gimli" 902 | version = "0.28.1" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 905 | 906 | [[package]] 907 | name = "hashbrown" 908 | version = "0.14.5" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 911 | 912 | [[package]] 913 | name = "hermit-abi" 914 | version = "0.5.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" 917 | 918 | [[package]] 919 | name = "hex-literal" 920 | version = "0.4.1" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" 923 | 924 | [[package]] 925 | name = "hkdf" 926 | version = "0.12.4" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 929 | dependencies = [ 930 | "hmac", 931 | ] 932 | 933 | [[package]] 934 | name = "hmac" 935 | version = "0.12.1" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 938 | dependencies = [ 939 | "digest", 940 | ] 941 | 942 | [[package]] 943 | name = "home" 944 | version = "0.5.11" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 947 | dependencies = [ 948 | "windows-sys 0.59.0", 949 | ] 950 | 951 | [[package]] 952 | name = "i18n-config" 953 | version = "0.4.7" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "8e88074831c0be5b89181b05e6748c4915f77769ecc9a4c372f88b169a8509c9" 956 | dependencies = [ 957 | "basic-toml", 958 | "log", 959 | "serde", 960 | "serde_derive", 961 | "thiserror", 962 | "unic-langid", 963 | ] 964 | 965 | [[package]] 966 | name = "i18n-embed" 967 | version = "0.14.1" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "94205d95764f5bb9db9ea98fa77f89653365ca748e27161f5bbea2ffd50e459c" 970 | dependencies = [ 971 | "arc-swap", 972 | "fluent", 973 | "fluent-langneg", 974 | "fluent-syntax", 975 | "i18n-embed-impl", 976 | "intl-memoizer", 977 | "lazy_static", 978 | "log", 979 | "parking_lot", 980 | "rust-embed", 981 | "thiserror", 982 | "unic-langid", 983 | "walkdir", 984 | ] 985 | 986 | [[package]] 987 | name = "i18n-embed-fl" 988 | version = "0.7.0" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "9fc1f8715195dffc4caddcf1cf3128da15fe5d8a137606ea8856c9300047d5a2" 991 | dependencies = [ 992 | "dashmap", 993 | "find-crate", 994 | "fluent", 995 | "fluent-syntax", 996 | "i18n-config", 997 | "i18n-embed", 998 | "lazy_static", 999 | "proc-macro-error", 1000 | "proc-macro2", 1001 | "quote", 1002 | "strsim 0.10.0", 1003 | "syn 2.0.99", 1004 | "unic-langid", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "i18n-embed-impl" 1009 | version = "0.8.4" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" 1012 | dependencies = [ 1013 | "find-crate", 1014 | "i18n-config", 1015 | "proc-macro2", 1016 | "quote", 1017 | "syn 2.0.99", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "icu_collections" 1022 | version = "1.5.0" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 1025 | dependencies = [ 1026 | "displaydoc", 1027 | "yoke", 1028 | "zerofrom", 1029 | "zerovec", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "icu_locid" 1034 | version = "1.5.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 1037 | dependencies = [ 1038 | "displaydoc", 1039 | "litemap", 1040 | "tinystr", 1041 | "writeable", 1042 | "zerovec", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "icu_locid_transform" 1047 | version = "1.5.0" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 1050 | dependencies = [ 1051 | "displaydoc", 1052 | "icu_locid", 1053 | "icu_locid_transform_data", 1054 | "icu_provider", 1055 | "tinystr", 1056 | "zerovec", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "icu_locid_transform_data" 1061 | version = "1.5.0" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 1064 | 1065 | [[package]] 1066 | name = "icu_normalizer" 1067 | version = "1.5.0" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 1070 | dependencies = [ 1071 | "displaydoc", 1072 | "icu_collections", 1073 | "icu_normalizer_data", 1074 | "icu_properties", 1075 | "icu_provider", 1076 | "smallvec", 1077 | "utf16_iter", 1078 | "utf8_iter", 1079 | "write16", 1080 | "zerovec", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "icu_normalizer_data" 1085 | version = "1.5.0" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 1088 | 1089 | [[package]] 1090 | name = "icu_properties" 1091 | version = "1.5.1" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 1094 | dependencies = [ 1095 | "displaydoc", 1096 | "icu_collections", 1097 | "icu_locid_transform", 1098 | "icu_properties_data", 1099 | "icu_provider", 1100 | "tinystr", 1101 | "zerovec", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "icu_properties_data" 1106 | version = "1.5.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 1109 | 1110 | [[package]] 1111 | name = "icu_provider" 1112 | version = "1.5.0" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 1115 | dependencies = [ 1116 | "displaydoc", 1117 | "icu_locid", 1118 | "icu_provider_macros", 1119 | "stable_deref_trait", 1120 | "tinystr", 1121 | "writeable", 1122 | "yoke", 1123 | "zerofrom", 1124 | "zerovec", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "icu_provider_macros" 1129 | version = "1.5.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 1132 | dependencies = [ 1133 | "proc-macro2", 1134 | "quote", 1135 | "syn 2.0.99", 1136 | ] 1137 | 1138 | [[package]] 1139 | name = "idna" 1140 | version = "1.0.3" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1143 | dependencies = [ 1144 | "idna_adapter", 1145 | "smallvec", 1146 | "utf8_iter", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "idna_adapter" 1151 | version = "1.2.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 1154 | dependencies = [ 1155 | "icu_normalizer", 1156 | "icu_properties", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "indenter" 1161 | version = "0.3.3" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 1164 | 1165 | [[package]] 1166 | name = "indoc" 1167 | version = "2.0.6" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" 1170 | 1171 | [[package]] 1172 | name = "inout" 1173 | version = "0.1.4" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 1176 | dependencies = [ 1177 | "block-padding", 1178 | "generic-array", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "intl-memoizer" 1183 | version = "0.5.2" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda" 1186 | dependencies = [ 1187 | "type-map", 1188 | "unic-langid", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "intl_pluralrules" 1193 | version = "7.0.2" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" 1196 | dependencies = [ 1197 | "unic-langid", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "io_tee" 1202 | version = "0.1.1" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" 1205 | 1206 | [[package]] 1207 | name = "is-terminal" 1208 | version = "0.4.16" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" 1211 | dependencies = [ 1212 | "hermit-abi", 1213 | "libc", 1214 | "windows-sys 0.59.0", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "is_terminal_polyfill" 1219 | version = "1.70.1" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1222 | 1223 | [[package]] 1224 | name = "iso8601" 1225 | version = "0.6.2" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "c5c177cff824ab21a6f41079a4c401241c4e8be14f316c4c6b07d5fca351c98d" 1228 | dependencies = [ 1229 | "nom 8.0.0", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "itoa" 1234 | version = "1.0.15" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1237 | 1238 | [[package]] 1239 | name = "js-sys" 1240 | version = "0.3.77" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1243 | dependencies = [ 1244 | "once_cell", 1245 | "wasm-bindgen", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "jsonschema" 1250 | version = "0.18.3" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "fa0f4bea31643be4c6a678e9aa4ae44f0db9e5609d5ca9dc9083d06eb3e9a27a" 1253 | dependencies = [ 1254 | "ahash", 1255 | "anyhow", 1256 | "base64 0.22.1", 1257 | "bytecount", 1258 | "fancy-regex", 1259 | "fraction", 1260 | "getrandom 0.2.15", 1261 | "iso8601", 1262 | "itoa", 1263 | "memchr", 1264 | "num-cmp", 1265 | "once_cell", 1266 | "parking_lot", 1267 | "percent-encoding", 1268 | "regex", 1269 | "serde", 1270 | "serde_json", 1271 | "time", 1272 | "url", 1273 | "uuid", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "lazy_static" 1278 | version = "1.5.0" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1281 | dependencies = [ 1282 | "spin", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "libc" 1287 | version = "0.2.170" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" 1290 | 1291 | [[package]] 1292 | name = "libm" 1293 | version = "0.2.11" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" 1296 | 1297 | [[package]] 1298 | name = "linux-raw-sys" 1299 | version = "0.4.15" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1302 | 1303 | [[package]] 1304 | name = "linux-raw-sys" 1305 | version = "0.9.2" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" 1308 | 1309 | [[package]] 1310 | name = "litemap" 1311 | version = "0.7.5" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 1314 | 1315 | [[package]] 1316 | name = "lock_api" 1317 | version = "0.4.12" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1320 | dependencies = [ 1321 | "autocfg", 1322 | "scopeguard", 1323 | ] 1324 | 1325 | [[package]] 1326 | name = "log" 1327 | version = "0.4.26" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 1330 | 1331 | [[package]] 1332 | name = "memchr" 1333 | version = "2.7.4" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1336 | 1337 | [[package]] 1338 | name = "minimal-lexical" 1339 | version = "0.2.1" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1342 | 1343 | [[package]] 1344 | name = "miniz_oxide" 1345 | version = "0.7.4" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 1348 | dependencies = [ 1349 | "adler", 1350 | ] 1351 | 1352 | [[package]] 1353 | name = "nom" 1354 | version = "7.1.3" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1357 | dependencies = [ 1358 | "memchr", 1359 | "minimal-lexical", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "nom" 1364 | version = "8.0.0" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" 1367 | dependencies = [ 1368 | "memchr", 1369 | ] 1370 | 1371 | [[package]] 1372 | name = "normalize-line-endings" 1373 | version = "0.3.0" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 1376 | 1377 | [[package]] 1378 | name = "num" 1379 | version = "0.4.3" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" 1382 | dependencies = [ 1383 | "num-bigint", 1384 | "num-complex", 1385 | "num-integer", 1386 | "num-iter", 1387 | "num-rational", 1388 | "num-traits", 1389 | ] 1390 | 1391 | [[package]] 1392 | name = "num-bigint" 1393 | version = "0.4.6" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1396 | dependencies = [ 1397 | "num-integer", 1398 | "num-traits", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "num-bigint-dig" 1403 | version = "0.8.4" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 1406 | dependencies = [ 1407 | "byteorder", 1408 | "lazy_static", 1409 | "libm", 1410 | "num-integer", 1411 | "num-iter", 1412 | "num-traits", 1413 | "rand", 1414 | "smallvec", 1415 | "zeroize", 1416 | ] 1417 | 1418 | [[package]] 1419 | name = "num-cmp" 1420 | version = "0.1.0" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" 1423 | 1424 | [[package]] 1425 | name = "num-complex" 1426 | version = "0.4.6" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 1429 | dependencies = [ 1430 | "num-traits", 1431 | ] 1432 | 1433 | [[package]] 1434 | name = "num-conv" 1435 | version = "0.1.0" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1438 | 1439 | [[package]] 1440 | name = "num-integer" 1441 | version = "0.1.46" 1442 | source = "registry+https://github.com/rust-lang/crates.io-index" 1443 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1444 | dependencies = [ 1445 | "num-traits", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "num-iter" 1450 | version = "0.1.45" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1453 | dependencies = [ 1454 | "autocfg", 1455 | "num-integer", 1456 | "num-traits", 1457 | ] 1458 | 1459 | [[package]] 1460 | name = "num-rational" 1461 | version = "0.4.2" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 1464 | dependencies = [ 1465 | "num-bigint", 1466 | "num-integer", 1467 | "num-traits", 1468 | ] 1469 | 1470 | [[package]] 1471 | name = "num-traits" 1472 | version = "0.2.19" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1475 | dependencies = [ 1476 | "autocfg", 1477 | "libm", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "object" 1482 | version = "0.32.2" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 1485 | dependencies = [ 1486 | "memchr", 1487 | ] 1488 | 1489 | [[package]] 1490 | name = "once_cell" 1491 | version = "1.20.3" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 1494 | 1495 | [[package]] 1496 | name = "opaque-debug" 1497 | version = "0.3.1" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1500 | 1501 | [[package]] 1502 | name = "owo-colors" 1503 | version = "3.5.0" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 1506 | 1507 | [[package]] 1508 | name = "parking_lot" 1509 | version = "0.12.3" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1512 | dependencies = [ 1513 | "lock_api", 1514 | "parking_lot_core", 1515 | ] 1516 | 1517 | [[package]] 1518 | name = "parking_lot_core" 1519 | version = "0.9.10" 1520 | source = "registry+https://github.com/rust-lang/crates.io-index" 1521 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1522 | dependencies = [ 1523 | "cfg-if", 1524 | "libc", 1525 | "redox_syscall", 1526 | "smallvec", 1527 | "windows-targets 0.52.6", 1528 | ] 1529 | 1530 | [[package]] 1531 | name = "pbkdf2" 1532 | version = "0.12.2" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" 1535 | dependencies = [ 1536 | "digest", 1537 | "hmac", 1538 | ] 1539 | 1540 | [[package]] 1541 | name = "percent-encoding" 1542 | version = "2.3.1" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1545 | 1546 | [[package]] 1547 | name = "pin-project" 1548 | version = "1.1.10" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 1551 | dependencies = [ 1552 | "pin-project-internal", 1553 | ] 1554 | 1555 | [[package]] 1556 | name = "pin-project-internal" 1557 | version = "1.1.10" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 1560 | dependencies = [ 1561 | "proc-macro2", 1562 | "quote", 1563 | "syn 2.0.99", 1564 | ] 1565 | 1566 | [[package]] 1567 | name = "pin-project-lite" 1568 | version = "0.2.16" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1571 | 1572 | [[package]] 1573 | name = "pin-utils" 1574 | version = "0.1.0" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1577 | 1578 | [[package]] 1579 | name = "pinentry" 1580 | version = "0.5.1" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "72268b7db3a2075ea65d4b93b755d086e99196e327837e690db6559b393a8d69" 1583 | dependencies = [ 1584 | "log", 1585 | "nom 7.1.3", 1586 | "percent-encoding", 1587 | "secrecy", 1588 | "which", 1589 | "zeroize", 1590 | ] 1591 | 1592 | [[package]] 1593 | name = "pkcs1" 1594 | version = "0.7.5" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 1597 | dependencies = [ 1598 | "der", 1599 | "pkcs8", 1600 | "spki", 1601 | ] 1602 | 1603 | [[package]] 1604 | name = "pkcs8" 1605 | version = "0.10.2" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 1608 | dependencies = [ 1609 | "der", 1610 | "spki", 1611 | ] 1612 | 1613 | [[package]] 1614 | name = "poly1305" 1615 | version = "0.8.0" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" 1618 | dependencies = [ 1619 | "cpufeatures", 1620 | "opaque-debug", 1621 | "universal-hash", 1622 | ] 1623 | 1624 | [[package]] 1625 | name = "polyval" 1626 | version = "0.6.2" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" 1629 | dependencies = [ 1630 | "cfg-if", 1631 | "cpufeatures", 1632 | "opaque-debug", 1633 | "universal-hash", 1634 | ] 1635 | 1636 | [[package]] 1637 | name = "powerfmt" 1638 | version = "0.2.0" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1641 | 1642 | [[package]] 1643 | name = "ppv-lite86" 1644 | version = "0.2.20" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1647 | dependencies = [ 1648 | "zerocopy", 1649 | ] 1650 | 1651 | [[package]] 1652 | name = "predicates" 1653 | version = "3.1.3" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" 1656 | dependencies = [ 1657 | "anstyle", 1658 | "difflib", 1659 | "float-cmp", 1660 | "normalize-line-endings", 1661 | "predicates-core", 1662 | "regex", 1663 | ] 1664 | 1665 | [[package]] 1666 | name = "predicates-core" 1667 | version = "1.0.9" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 1670 | 1671 | [[package]] 1672 | name = "predicates-tree" 1673 | version = "1.0.12" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 1676 | dependencies = [ 1677 | "predicates-core", 1678 | "termtree", 1679 | ] 1680 | 1681 | [[package]] 1682 | name = "proc-macro-error" 1683 | version = "1.0.4" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1686 | dependencies = [ 1687 | "proc-macro-error-attr", 1688 | "proc-macro2", 1689 | "quote", 1690 | "syn 1.0.109", 1691 | "version_check", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "proc-macro-error-attr" 1696 | version = "1.0.4" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1699 | dependencies = [ 1700 | "proc-macro2", 1701 | "quote", 1702 | "version_check", 1703 | ] 1704 | 1705 | [[package]] 1706 | name = "proc-macro2" 1707 | version = "1.0.94" 1708 | source = "registry+https://github.com/rust-lang/crates.io-index" 1709 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 1710 | dependencies = [ 1711 | "unicode-ident", 1712 | ] 1713 | 1714 | [[package]] 1715 | name = "quote" 1716 | version = "1.0.39" 1717 | source = "registry+https://github.com/rust-lang/crates.io-index" 1718 | checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" 1719 | dependencies = [ 1720 | "proc-macro2", 1721 | ] 1722 | 1723 | [[package]] 1724 | name = "ragenix" 1725 | version = "0.1.0" 1726 | dependencies = [ 1727 | "age", 1728 | "assert_cmd", 1729 | "clap", 1730 | "clap_complete", 1731 | "color-eyre", 1732 | "copy_dir", 1733 | "hex-literal", 1734 | "home", 1735 | "indoc", 1736 | "jsonschema", 1737 | "lazy_static", 1738 | "predicates", 1739 | "serde", 1740 | "serde_json", 1741 | "sha2", 1742 | "shlex", 1743 | "tempfile", 1744 | ] 1745 | 1746 | [[package]] 1747 | name = "rand" 1748 | version = "0.8.5" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1751 | dependencies = [ 1752 | "libc", 1753 | "rand_chacha", 1754 | "rand_core", 1755 | ] 1756 | 1757 | [[package]] 1758 | name = "rand_chacha" 1759 | version = "0.3.1" 1760 | source = "registry+https://github.com/rust-lang/crates.io-index" 1761 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1762 | dependencies = [ 1763 | "ppv-lite86", 1764 | "rand_core", 1765 | ] 1766 | 1767 | [[package]] 1768 | name = "rand_core" 1769 | version = "0.6.4" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1772 | dependencies = [ 1773 | "getrandom 0.2.15", 1774 | ] 1775 | 1776 | [[package]] 1777 | name = "redox_syscall" 1778 | version = "0.5.10" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" 1781 | dependencies = [ 1782 | "bitflags", 1783 | ] 1784 | 1785 | [[package]] 1786 | name = "regex" 1787 | version = "1.11.1" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1790 | dependencies = [ 1791 | "aho-corasick", 1792 | "memchr", 1793 | "regex-automata", 1794 | "regex-syntax", 1795 | ] 1796 | 1797 | [[package]] 1798 | name = "regex-automata" 1799 | version = "0.4.9" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1802 | dependencies = [ 1803 | "aho-corasick", 1804 | "memchr", 1805 | "regex-syntax", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "regex-syntax" 1810 | version = "0.8.5" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1813 | 1814 | [[package]] 1815 | name = "rpassword" 1816 | version = "7.3.1" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" 1819 | dependencies = [ 1820 | "libc", 1821 | "rtoolbox", 1822 | "windows-sys 0.48.0", 1823 | ] 1824 | 1825 | [[package]] 1826 | name = "rsa" 1827 | version = "0.9.7" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" 1830 | dependencies = [ 1831 | "const-oid", 1832 | "digest", 1833 | "num-bigint-dig", 1834 | "num-integer", 1835 | "num-traits", 1836 | "pkcs1", 1837 | "pkcs8", 1838 | "rand_core", 1839 | "signature", 1840 | "spki", 1841 | "subtle", 1842 | "zeroize", 1843 | ] 1844 | 1845 | [[package]] 1846 | name = "rtoolbox" 1847 | version = "0.0.2" 1848 | source = "registry+https://github.com/rust-lang/crates.io-index" 1849 | checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" 1850 | dependencies = [ 1851 | "libc", 1852 | "windows-sys 0.48.0", 1853 | ] 1854 | 1855 | [[package]] 1856 | name = "rust-embed" 1857 | version = "8.6.0" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f" 1860 | dependencies = [ 1861 | "rust-embed-impl", 1862 | "rust-embed-utils", 1863 | "walkdir", 1864 | ] 1865 | 1866 | [[package]] 1867 | name = "rust-embed-impl" 1868 | version = "8.6.0" 1869 | source = "registry+https://github.com/rust-lang/crates.io-index" 1870 | checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae" 1871 | dependencies = [ 1872 | "proc-macro2", 1873 | "quote", 1874 | "rust-embed-utils", 1875 | "syn 2.0.99", 1876 | "walkdir", 1877 | ] 1878 | 1879 | [[package]] 1880 | name = "rust-embed-utils" 1881 | version = "8.6.0" 1882 | source = "registry+https://github.com/rust-lang/crates.io-index" 1883 | checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a" 1884 | dependencies = [ 1885 | "sha2", 1886 | "walkdir", 1887 | ] 1888 | 1889 | [[package]] 1890 | name = "rustc-demangle" 1891 | version = "0.1.24" 1892 | source = "registry+https://github.com/rust-lang/crates.io-index" 1893 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1894 | 1895 | [[package]] 1896 | name = "rustc-hash" 1897 | version = "1.1.0" 1898 | source = "registry+https://github.com/rust-lang/crates.io-index" 1899 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1900 | 1901 | [[package]] 1902 | name = "rustc_version" 1903 | version = "0.4.1" 1904 | source = "registry+https://github.com/rust-lang/crates.io-index" 1905 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1906 | dependencies = [ 1907 | "semver", 1908 | ] 1909 | 1910 | [[package]] 1911 | name = "rustix" 1912 | version = "0.38.44" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1915 | dependencies = [ 1916 | "bitflags", 1917 | "errno", 1918 | "libc", 1919 | "linux-raw-sys 0.4.15", 1920 | "windows-sys 0.59.0", 1921 | ] 1922 | 1923 | [[package]] 1924 | name = "rustix" 1925 | version = "1.0.1" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" 1928 | dependencies = [ 1929 | "bitflags", 1930 | "errno", 1931 | "libc", 1932 | "linux-raw-sys 0.9.2", 1933 | "windows-sys 0.59.0", 1934 | ] 1935 | 1936 | [[package]] 1937 | name = "ryu" 1938 | version = "1.0.20" 1939 | source = "registry+https://github.com/rust-lang/crates.io-index" 1940 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1941 | 1942 | [[package]] 1943 | name = "salsa20" 1944 | version = "0.10.2" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" 1947 | dependencies = [ 1948 | "cipher", 1949 | ] 1950 | 1951 | [[package]] 1952 | name = "same-file" 1953 | version = "1.0.6" 1954 | source = "registry+https://github.com/rust-lang/crates.io-index" 1955 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1956 | dependencies = [ 1957 | "winapi-util", 1958 | ] 1959 | 1960 | [[package]] 1961 | name = "scopeguard" 1962 | version = "1.2.0" 1963 | source = "registry+https://github.com/rust-lang/crates.io-index" 1964 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1965 | 1966 | [[package]] 1967 | name = "scrypt" 1968 | version = "0.11.0" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" 1971 | dependencies = [ 1972 | "pbkdf2", 1973 | "salsa20", 1974 | "sha2", 1975 | ] 1976 | 1977 | [[package]] 1978 | name = "secrecy" 1979 | version = "0.8.0" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" 1982 | dependencies = [ 1983 | "zeroize", 1984 | ] 1985 | 1986 | [[package]] 1987 | name = "self_cell" 1988 | version = "0.10.3" 1989 | source = "registry+https://github.com/rust-lang/crates.io-index" 1990 | checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" 1991 | dependencies = [ 1992 | "self_cell 1.1.0", 1993 | ] 1994 | 1995 | [[package]] 1996 | name = "self_cell" 1997 | version = "1.1.0" 1998 | source = "registry+https://github.com/rust-lang/crates.io-index" 1999 | checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" 2000 | 2001 | [[package]] 2002 | name = "semver" 2003 | version = "1.0.26" 2004 | source = "registry+https://github.com/rust-lang/crates.io-index" 2005 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 2006 | 2007 | [[package]] 2008 | name = "serde" 2009 | version = "1.0.218" 2010 | source = "registry+https://github.com/rust-lang/crates.io-index" 2011 | checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" 2012 | dependencies = [ 2013 | "serde_derive", 2014 | ] 2015 | 2016 | [[package]] 2017 | name = "serde_derive" 2018 | version = "1.0.218" 2019 | source = "registry+https://github.com/rust-lang/crates.io-index" 2020 | checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" 2021 | dependencies = [ 2022 | "proc-macro2", 2023 | "quote", 2024 | "syn 2.0.99", 2025 | ] 2026 | 2027 | [[package]] 2028 | name = "serde_json" 2029 | version = "1.0.140" 2030 | source = "registry+https://github.com/rust-lang/crates.io-index" 2031 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 2032 | dependencies = [ 2033 | "itoa", 2034 | "memchr", 2035 | "ryu", 2036 | "serde", 2037 | ] 2038 | 2039 | [[package]] 2040 | name = "sha2" 2041 | version = "0.10.8" 2042 | source = "registry+https://github.com/rust-lang/crates.io-index" 2043 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 2044 | dependencies = [ 2045 | "cfg-if", 2046 | "cpufeatures", 2047 | "digest", 2048 | ] 2049 | 2050 | [[package]] 2051 | name = "shlex" 2052 | version = "1.3.0" 2053 | source = "registry+https://github.com/rust-lang/crates.io-index" 2054 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2055 | 2056 | [[package]] 2057 | name = "signature" 2058 | version = "2.2.0" 2059 | source = "registry+https://github.com/rust-lang/crates.io-index" 2060 | checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 2061 | dependencies = [ 2062 | "digest", 2063 | "rand_core", 2064 | ] 2065 | 2066 | [[package]] 2067 | name = "slab" 2068 | version = "0.4.9" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 2071 | dependencies = [ 2072 | "autocfg", 2073 | ] 2074 | 2075 | [[package]] 2076 | name = "smallvec" 2077 | version = "1.14.0" 2078 | source = "registry+https://github.com/rust-lang/crates.io-index" 2079 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 2080 | 2081 | [[package]] 2082 | name = "spin" 2083 | version = "0.9.8" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 2086 | 2087 | [[package]] 2088 | name = "spki" 2089 | version = "0.7.3" 2090 | source = "registry+https://github.com/rust-lang/crates.io-index" 2091 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 2092 | dependencies = [ 2093 | "base64ct", 2094 | "der", 2095 | ] 2096 | 2097 | [[package]] 2098 | name = "stable_deref_trait" 2099 | version = "1.2.0" 2100 | source = "registry+https://github.com/rust-lang/crates.io-index" 2101 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 2102 | 2103 | [[package]] 2104 | name = "strsim" 2105 | version = "0.10.0" 2106 | source = "registry+https://github.com/rust-lang/crates.io-index" 2107 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 2108 | 2109 | [[package]] 2110 | name = "strsim" 2111 | version = "0.11.1" 2112 | source = "registry+https://github.com/rust-lang/crates.io-index" 2113 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2114 | 2115 | [[package]] 2116 | name = "subtle" 2117 | version = "2.6.1" 2118 | source = "registry+https://github.com/rust-lang/crates.io-index" 2119 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2120 | 2121 | [[package]] 2122 | name = "syn" 2123 | version = "1.0.109" 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" 2125 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 2126 | dependencies = [ 2127 | "proc-macro2", 2128 | "unicode-ident", 2129 | ] 2130 | 2131 | [[package]] 2132 | name = "syn" 2133 | version = "2.0.99" 2134 | source = "registry+https://github.com/rust-lang/crates.io-index" 2135 | checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" 2136 | dependencies = [ 2137 | "proc-macro2", 2138 | "quote", 2139 | "unicode-ident", 2140 | ] 2141 | 2142 | [[package]] 2143 | name = "synstructure" 2144 | version = "0.13.1" 2145 | source = "registry+https://github.com/rust-lang/crates.io-index" 2146 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 2147 | dependencies = [ 2148 | "proc-macro2", 2149 | "quote", 2150 | "syn 2.0.99", 2151 | ] 2152 | 2153 | [[package]] 2154 | name = "tempfile" 2155 | version = "3.18.0" 2156 | source = "registry+https://github.com/rust-lang/crates.io-index" 2157 | checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" 2158 | dependencies = [ 2159 | "cfg-if", 2160 | "fastrand", 2161 | "getrandom 0.3.1", 2162 | "once_cell", 2163 | "rustix 1.0.1", 2164 | "windows-sys 0.59.0", 2165 | ] 2166 | 2167 | [[package]] 2168 | name = "termtree" 2169 | version = "0.5.1" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 2172 | 2173 | [[package]] 2174 | name = "thiserror" 2175 | version = "1.0.69" 2176 | source = "registry+https://github.com/rust-lang/crates.io-index" 2177 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2178 | dependencies = [ 2179 | "thiserror-impl", 2180 | ] 2181 | 2182 | [[package]] 2183 | name = "thiserror-impl" 2184 | version = "1.0.69" 2185 | source = "registry+https://github.com/rust-lang/crates.io-index" 2186 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2187 | dependencies = [ 2188 | "proc-macro2", 2189 | "quote", 2190 | "syn 2.0.99", 2191 | ] 2192 | 2193 | [[package]] 2194 | name = "time" 2195 | version = "0.3.39" 2196 | source = "registry+https://github.com/rust-lang/crates.io-index" 2197 | checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" 2198 | dependencies = [ 2199 | "deranged", 2200 | "num-conv", 2201 | "powerfmt", 2202 | "serde", 2203 | "time-core", 2204 | "time-macros", 2205 | ] 2206 | 2207 | [[package]] 2208 | name = "time-core" 2209 | version = "0.1.3" 2210 | source = "registry+https://github.com/rust-lang/crates.io-index" 2211 | checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" 2212 | 2213 | [[package]] 2214 | name = "time-macros" 2215 | version = "0.2.20" 2216 | source = "registry+https://github.com/rust-lang/crates.io-index" 2217 | checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" 2218 | dependencies = [ 2219 | "num-conv", 2220 | "time-core", 2221 | ] 2222 | 2223 | [[package]] 2224 | name = "tinystr" 2225 | version = "0.7.6" 2226 | source = "registry+https://github.com/rust-lang/crates.io-index" 2227 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 2228 | dependencies = [ 2229 | "displaydoc", 2230 | "zerovec", 2231 | ] 2232 | 2233 | [[package]] 2234 | name = "toml" 2235 | version = "0.5.11" 2236 | source = "registry+https://github.com/rust-lang/crates.io-index" 2237 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 2238 | dependencies = [ 2239 | "serde", 2240 | ] 2241 | 2242 | [[package]] 2243 | name = "type-map" 2244 | version = "0.5.0" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" 2247 | dependencies = [ 2248 | "rustc-hash", 2249 | ] 2250 | 2251 | [[package]] 2252 | name = "typenum" 2253 | version = "1.18.0" 2254 | source = "registry+https://github.com/rust-lang/crates.io-index" 2255 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 2256 | 2257 | [[package]] 2258 | name = "unic-langid" 2259 | version = "0.9.5" 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" 2261 | checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" 2262 | dependencies = [ 2263 | "unic-langid-impl", 2264 | ] 2265 | 2266 | [[package]] 2267 | name = "unic-langid-impl" 2268 | version = "0.9.5" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" 2271 | dependencies = [ 2272 | "serde", 2273 | "tinystr", 2274 | ] 2275 | 2276 | [[package]] 2277 | name = "unicode-ident" 2278 | version = "1.0.18" 2279 | source = "registry+https://github.com/rust-lang/crates.io-index" 2280 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2281 | 2282 | [[package]] 2283 | name = "universal-hash" 2284 | version = "0.5.1" 2285 | source = "registry+https://github.com/rust-lang/crates.io-index" 2286 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 2287 | dependencies = [ 2288 | "crypto-common", 2289 | "subtle", 2290 | ] 2291 | 2292 | [[package]] 2293 | name = "url" 2294 | version = "2.5.4" 2295 | source = "registry+https://github.com/rust-lang/crates.io-index" 2296 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2297 | dependencies = [ 2298 | "form_urlencoded", 2299 | "idna", 2300 | "percent-encoding", 2301 | ] 2302 | 2303 | [[package]] 2304 | name = "utf16_iter" 2305 | version = "1.0.5" 2306 | source = "registry+https://github.com/rust-lang/crates.io-index" 2307 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2308 | 2309 | [[package]] 2310 | name = "utf8_iter" 2311 | version = "1.0.4" 2312 | source = "registry+https://github.com/rust-lang/crates.io-index" 2313 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2314 | 2315 | [[package]] 2316 | name = "utf8parse" 2317 | version = "0.2.2" 2318 | source = "registry+https://github.com/rust-lang/crates.io-index" 2319 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2320 | 2321 | [[package]] 2322 | name = "uuid" 2323 | version = "1.15.1" 2324 | source = "registry+https://github.com/rust-lang/crates.io-index" 2325 | checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" 2326 | 2327 | [[package]] 2328 | name = "version_check" 2329 | version = "0.9.5" 2330 | source = "registry+https://github.com/rust-lang/crates.io-index" 2331 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2332 | 2333 | [[package]] 2334 | name = "wait-timeout" 2335 | version = "0.2.1" 2336 | source = "registry+https://github.com/rust-lang/crates.io-index" 2337 | checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" 2338 | dependencies = [ 2339 | "libc", 2340 | ] 2341 | 2342 | [[package]] 2343 | name = "walkdir" 2344 | version = "2.5.0" 2345 | source = "registry+https://github.com/rust-lang/crates.io-index" 2346 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2347 | dependencies = [ 2348 | "same-file", 2349 | "winapi-util", 2350 | ] 2351 | 2352 | [[package]] 2353 | name = "wasi" 2354 | version = "0.11.0+wasi-snapshot-preview1" 2355 | source = "registry+https://github.com/rust-lang/crates.io-index" 2356 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2357 | 2358 | [[package]] 2359 | name = "wasi" 2360 | version = "0.13.3+wasi-0.2.2" 2361 | source = "registry+https://github.com/rust-lang/crates.io-index" 2362 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 2363 | dependencies = [ 2364 | "wit-bindgen-rt", 2365 | ] 2366 | 2367 | [[package]] 2368 | name = "wasm-bindgen" 2369 | version = "0.2.100" 2370 | source = "registry+https://github.com/rust-lang/crates.io-index" 2371 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2372 | dependencies = [ 2373 | "cfg-if", 2374 | "once_cell", 2375 | "wasm-bindgen-macro", 2376 | ] 2377 | 2378 | [[package]] 2379 | name = "wasm-bindgen-backend" 2380 | version = "0.2.100" 2381 | source = "registry+https://github.com/rust-lang/crates.io-index" 2382 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2383 | dependencies = [ 2384 | "bumpalo", 2385 | "log", 2386 | "proc-macro2", 2387 | "quote", 2388 | "syn 2.0.99", 2389 | "wasm-bindgen-shared", 2390 | ] 2391 | 2392 | [[package]] 2393 | name = "wasm-bindgen-macro" 2394 | version = "0.2.100" 2395 | source = "registry+https://github.com/rust-lang/crates.io-index" 2396 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2397 | dependencies = [ 2398 | "quote", 2399 | "wasm-bindgen-macro-support", 2400 | ] 2401 | 2402 | [[package]] 2403 | name = "wasm-bindgen-macro-support" 2404 | version = "0.2.100" 2405 | source = "registry+https://github.com/rust-lang/crates.io-index" 2406 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2407 | dependencies = [ 2408 | "proc-macro2", 2409 | "quote", 2410 | "syn 2.0.99", 2411 | "wasm-bindgen-backend", 2412 | "wasm-bindgen-shared", 2413 | ] 2414 | 2415 | [[package]] 2416 | name = "wasm-bindgen-shared" 2417 | version = "0.2.100" 2418 | source = "registry+https://github.com/rust-lang/crates.io-index" 2419 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2420 | dependencies = [ 2421 | "unicode-ident", 2422 | ] 2423 | 2424 | [[package]] 2425 | name = "which" 2426 | version = "4.4.2" 2427 | source = "registry+https://github.com/rust-lang/crates.io-index" 2428 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 2429 | dependencies = [ 2430 | "either", 2431 | "home", 2432 | "once_cell", 2433 | "rustix 0.38.44", 2434 | ] 2435 | 2436 | [[package]] 2437 | name = "winapi-util" 2438 | version = "0.1.9" 2439 | source = "registry+https://github.com/rust-lang/crates.io-index" 2440 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2441 | dependencies = [ 2442 | "windows-sys 0.59.0", 2443 | ] 2444 | 2445 | [[package]] 2446 | name = "windows-sys" 2447 | version = "0.48.0" 2448 | source = "registry+https://github.com/rust-lang/crates.io-index" 2449 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2450 | dependencies = [ 2451 | "windows-targets 0.48.5", 2452 | ] 2453 | 2454 | [[package]] 2455 | name = "windows-sys" 2456 | version = "0.59.0" 2457 | source = "registry+https://github.com/rust-lang/crates.io-index" 2458 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2459 | dependencies = [ 2460 | "windows-targets 0.52.6", 2461 | ] 2462 | 2463 | [[package]] 2464 | name = "windows-targets" 2465 | version = "0.48.5" 2466 | source = "registry+https://github.com/rust-lang/crates.io-index" 2467 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2468 | dependencies = [ 2469 | "windows_aarch64_gnullvm 0.48.5", 2470 | "windows_aarch64_msvc 0.48.5", 2471 | "windows_i686_gnu 0.48.5", 2472 | "windows_i686_msvc 0.48.5", 2473 | "windows_x86_64_gnu 0.48.5", 2474 | "windows_x86_64_gnullvm 0.48.5", 2475 | "windows_x86_64_msvc 0.48.5", 2476 | ] 2477 | 2478 | [[package]] 2479 | name = "windows-targets" 2480 | version = "0.52.6" 2481 | source = "registry+https://github.com/rust-lang/crates.io-index" 2482 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2483 | dependencies = [ 2484 | "windows_aarch64_gnullvm 0.52.6", 2485 | "windows_aarch64_msvc 0.52.6", 2486 | "windows_i686_gnu 0.52.6", 2487 | "windows_i686_gnullvm", 2488 | "windows_i686_msvc 0.52.6", 2489 | "windows_x86_64_gnu 0.52.6", 2490 | "windows_x86_64_gnullvm 0.52.6", 2491 | "windows_x86_64_msvc 0.52.6", 2492 | ] 2493 | 2494 | [[package]] 2495 | name = "windows_aarch64_gnullvm" 2496 | version = "0.48.5" 2497 | source = "registry+https://github.com/rust-lang/crates.io-index" 2498 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2499 | 2500 | [[package]] 2501 | name = "windows_aarch64_gnullvm" 2502 | version = "0.52.6" 2503 | source = "registry+https://github.com/rust-lang/crates.io-index" 2504 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2505 | 2506 | [[package]] 2507 | name = "windows_aarch64_msvc" 2508 | version = "0.48.5" 2509 | source = "registry+https://github.com/rust-lang/crates.io-index" 2510 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2511 | 2512 | [[package]] 2513 | name = "windows_aarch64_msvc" 2514 | version = "0.52.6" 2515 | source = "registry+https://github.com/rust-lang/crates.io-index" 2516 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2517 | 2518 | [[package]] 2519 | name = "windows_i686_gnu" 2520 | version = "0.48.5" 2521 | source = "registry+https://github.com/rust-lang/crates.io-index" 2522 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2523 | 2524 | [[package]] 2525 | name = "windows_i686_gnu" 2526 | version = "0.52.6" 2527 | source = "registry+https://github.com/rust-lang/crates.io-index" 2528 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2529 | 2530 | [[package]] 2531 | name = "windows_i686_gnullvm" 2532 | version = "0.52.6" 2533 | source = "registry+https://github.com/rust-lang/crates.io-index" 2534 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2535 | 2536 | [[package]] 2537 | name = "windows_i686_msvc" 2538 | version = "0.48.5" 2539 | source = "registry+https://github.com/rust-lang/crates.io-index" 2540 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2541 | 2542 | [[package]] 2543 | name = "windows_i686_msvc" 2544 | version = "0.52.6" 2545 | source = "registry+https://github.com/rust-lang/crates.io-index" 2546 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2547 | 2548 | [[package]] 2549 | name = "windows_x86_64_gnu" 2550 | version = "0.48.5" 2551 | source = "registry+https://github.com/rust-lang/crates.io-index" 2552 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2553 | 2554 | [[package]] 2555 | name = "windows_x86_64_gnu" 2556 | version = "0.52.6" 2557 | source = "registry+https://github.com/rust-lang/crates.io-index" 2558 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2559 | 2560 | [[package]] 2561 | name = "windows_x86_64_gnullvm" 2562 | version = "0.48.5" 2563 | source = "registry+https://github.com/rust-lang/crates.io-index" 2564 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2565 | 2566 | [[package]] 2567 | name = "windows_x86_64_gnullvm" 2568 | version = "0.52.6" 2569 | source = "registry+https://github.com/rust-lang/crates.io-index" 2570 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2571 | 2572 | [[package]] 2573 | name = "windows_x86_64_msvc" 2574 | version = "0.48.5" 2575 | source = "registry+https://github.com/rust-lang/crates.io-index" 2576 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2577 | 2578 | [[package]] 2579 | name = "windows_x86_64_msvc" 2580 | version = "0.52.6" 2581 | source = "registry+https://github.com/rust-lang/crates.io-index" 2582 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2583 | 2584 | [[package]] 2585 | name = "wit-bindgen-rt" 2586 | version = "0.33.0" 2587 | source = "registry+https://github.com/rust-lang/crates.io-index" 2588 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 2589 | dependencies = [ 2590 | "bitflags", 2591 | ] 2592 | 2593 | [[package]] 2594 | name = "write16" 2595 | version = "1.0.0" 2596 | source = "registry+https://github.com/rust-lang/crates.io-index" 2597 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2598 | 2599 | [[package]] 2600 | name = "writeable" 2601 | version = "0.5.5" 2602 | source = "registry+https://github.com/rust-lang/crates.io-index" 2603 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2604 | 2605 | [[package]] 2606 | name = "wsl" 2607 | version = "0.1.0" 2608 | source = "registry+https://github.com/rust-lang/crates.io-index" 2609 | checksum = "f8dab7ac864710bdea6594becbea5b5050333cf34fefb0dc319567eb347950d4" 2610 | 2611 | [[package]] 2612 | name = "x25519-dalek" 2613 | version = "2.0.1" 2614 | source = "registry+https://github.com/rust-lang/crates.io-index" 2615 | checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" 2616 | dependencies = [ 2617 | "curve25519-dalek", 2618 | "rand_core", 2619 | "serde", 2620 | "zeroize", 2621 | ] 2622 | 2623 | [[package]] 2624 | name = "yoke" 2625 | version = "0.7.5" 2626 | source = "registry+https://github.com/rust-lang/crates.io-index" 2627 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2628 | dependencies = [ 2629 | "serde", 2630 | "stable_deref_trait", 2631 | "yoke-derive", 2632 | "zerofrom", 2633 | ] 2634 | 2635 | [[package]] 2636 | name = "yoke-derive" 2637 | version = "0.7.5" 2638 | source = "registry+https://github.com/rust-lang/crates.io-index" 2639 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2640 | dependencies = [ 2641 | "proc-macro2", 2642 | "quote", 2643 | "syn 2.0.99", 2644 | "synstructure", 2645 | ] 2646 | 2647 | [[package]] 2648 | name = "zerocopy" 2649 | version = "0.7.35" 2650 | source = "registry+https://github.com/rust-lang/crates.io-index" 2651 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2652 | dependencies = [ 2653 | "byteorder", 2654 | "zerocopy-derive", 2655 | ] 2656 | 2657 | [[package]] 2658 | name = "zerocopy-derive" 2659 | version = "0.7.35" 2660 | source = "registry+https://github.com/rust-lang/crates.io-index" 2661 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2662 | dependencies = [ 2663 | "proc-macro2", 2664 | "quote", 2665 | "syn 2.0.99", 2666 | ] 2667 | 2668 | [[package]] 2669 | name = "zerofrom" 2670 | version = "0.1.6" 2671 | source = "registry+https://github.com/rust-lang/crates.io-index" 2672 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2673 | dependencies = [ 2674 | "zerofrom-derive", 2675 | ] 2676 | 2677 | [[package]] 2678 | name = "zerofrom-derive" 2679 | version = "0.1.6" 2680 | source = "registry+https://github.com/rust-lang/crates.io-index" 2681 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2682 | dependencies = [ 2683 | "proc-macro2", 2684 | "quote", 2685 | "syn 2.0.99", 2686 | "synstructure", 2687 | ] 2688 | 2689 | [[package]] 2690 | name = "zeroize" 2691 | version = "1.8.1" 2692 | source = "registry+https://github.com/rust-lang/crates.io-index" 2693 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2694 | dependencies = [ 2695 | "zeroize_derive", 2696 | ] 2697 | 2698 | [[package]] 2699 | name = "zeroize_derive" 2700 | version = "1.4.2" 2701 | source = "registry+https://github.com/rust-lang/crates.io-index" 2702 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 2703 | dependencies = [ 2704 | "proc-macro2", 2705 | "quote", 2706 | "syn 2.0.99", 2707 | ] 2708 | 2709 | [[package]] 2710 | name = "zerovec" 2711 | version = "0.10.4" 2712 | source = "registry+https://github.com/rust-lang/crates.io-index" 2713 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2714 | dependencies = [ 2715 | "yoke", 2716 | "zerofrom", 2717 | "zerovec-derive", 2718 | ] 2719 | 2720 | [[package]] 2721 | name = "zerovec-derive" 2722 | version = "0.10.3" 2723 | source = "registry+https://github.com/rust-lang/crates.io-index" 2724 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2725 | dependencies = [ 2726 | "proc-macro2", 2727 | "quote", 2728 | "syn 2.0.99", 2729 | ] 2730 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ragenix" 3 | description = "A rust drop-in replacement for agenix" 4 | version = "0.1.0" 5 | authors = ["Vincent Haupert "] 6 | edition = "2021" 7 | 8 | [features] 9 | default = [ "recursive-nix" ] 10 | # Run tests which require a system with `recursive-nix` conditionally 11 | recursive-nix = [] 12 | 13 | [dependencies] 14 | age = { version = "^0.10", default-features = false, features = [ "cli-common", "ssh", "armor", "plugin" ] } 15 | clap = { version = "^4.0", features = [ "cargo", "env" ] } 16 | color-eyre = { version = "^0.6", default-features = false, features = [ "track-caller" ] } 17 | home = "^0.5" 18 | jsonschema = { version = "^0.18", default-features = false } 19 | serde = "^1.0" 20 | serde_json = "^1.0" 21 | sha2 = "^0.10" 22 | shlex = "^1.1" 23 | tempfile = "^3.2" 24 | lazy_static = "^1.4" 25 | 26 | [dev-dependencies] 27 | assert_cmd = "^2.0" 28 | predicates = "^3.0" 29 | copy_dir = "^0.1" 30 | indoc = "^2.0" 31 | hex-literal = "^0.4" 32 | 33 | [build-dependencies] 34 | clap = { version = "^4.0", features = [ "cargo", "env" ] } 35 | clap_complete = "^4.0" 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ragenix 2 | 3 | [![Build status](https://img.shields.io/github/actions/workflow/status/yaxitech/ragenix/main.yaml?branch=main)](https://github.com/yaxitech/ragenix/actions?query=branch%3Amain) 4 | [![License](https://img.shields.io/github/license/yaxitech/ragenix)](http://www.apache.org/licenses/LICENSE-2.0.html) 5 | [![Written in Rust](https://img.shields.io/badge/code-rust-orange)](https://www.rust-lang.org) 6 | [![ragenix(1)](https://img.shields.io/badge/man-ragenix(1)-blue)](https://htmlpreview.github.io/?https://github.com/yaxitech/ragenix/blob/main/docs/ragenix.1.html) 7 | 8 | `ragenix` provides age-encrypted secrets for NixOS systems which live in the Nix store 9 | and are decrypted on system activation. Using `ragenix` to create, edit and rekey secrets 10 | is possible on any system which has Nix installed—with particular support for NixOS and macOS. 11 | 12 | `ragenix` is a drop-in replacement for [@ryantm](https://github.com/ryantm)'s 13 | [`agenix`](https://github.com/ryantm/agenix) written in Rust. It aims at being fully compatible 14 | with its flake while offering more robust command line parsing, additional validation logic, 15 | plugin support, shell completions, and solid tests. 16 | 17 | **As opposed to `agenix`, `ragenix` only strives for supporting Nix Flakes**. 18 | 19 | ## Installation 20 | 21 | As `ragenix` seeks to replace `agenix` without breaking compatibility, getting started with age-encrypted 22 | secrets or switching from `agenix` to `ragenix` is easy: just follow the original [instructions from `agenix`]( 23 | https://github.com/ryantm/agenix#installation) while replacing references to 24 | `github.com/ryantm/agenix` with `github.com/yaxitech/ragenix`. Everything else should remain the 25 | same as the `ragenix` package provides aliases for a) an `agenix` package and b) the `agenix` binary. 26 | The flake also exposes a NixOS and Darwin module which is passed through from the `agenix` flake. 27 | 28 | ## Usage 29 | 30 | `ragenix` resembles the command line options and behavior of `agenix`. 31 | For the full documentation, read the [ragenix(1) man page](https://htmlpreview.github.io/?https://github.com/yaxitech/ragenix/blob/main/docs/ragenix.1.html). 32 | 33 | ``` 34 | USAGE: 35 | ragenix [OPTIONS] <--edit |--rekey|--schema> 36 | 37 | OPTIONS: 38 | -e, --edit edits the age-encrypted FILE using $EDITOR 39 | --editor editor to use when editing FILE [env: EDITOR=vim] 40 | -h, --help Print help information 41 | -i, --identity ... private key to use when decrypting 42 | -r, --rekey re-encrypts all secrets with specified recipients 43 | --rules path to Nix file specifying recipient public keys [env: 44 | RULES=] [default: ./secrets.nix] 45 | -s, --schema Prints the JSON schema Agenix rules have to conform to 46 | -v, --verbose verbose output 47 | -V, --version Print version information 48 | ``` 49 | 50 | The `ragenix` package also provides shell completions for `bash`, `zsh`, and `fish`. Make sure to install the package with either `nix profile install github:yaxitech/ragenix`, `environment.systemPackages` on NixOS or `home.packages` for home-manager. 51 | 52 | ## Contributions 53 | 54 | We'd love to see PRs from you! Please consider the following guidelines: 55 | 56 | - `ragenix` stays compatible to `agenix`. Please make sure your contributions 57 | don't introduce breaking changes. 58 | - The secrets configuration happens through a Nix configuration. 59 | - New features should support both NixOS and macOS, if applicable. 60 | - Update the manpage, if necessary 61 | 62 | The CI invokes `nix flake check`. Some of the checks invoke `nix` itself. 63 | To allow those tests to run `nix`, you have to enable the `recursive-nix` feature. 64 | On NixOS, you can put the following snippet into your `configuration.nix`: 65 | 66 | ```nix 67 | { 68 | nix = { 69 | extraOptions = '' 70 | experimental-features = nix-command flakes recursive-nix 71 | ''; 72 | systemFeatures = [ "recursive-nix" ]; 73 | }; 74 | } 75 | ``` 76 | 77 | ## Similar projects / acknowledgements 78 | 79 | The [`agenix-cli`](https://github.com/cole-h/agenix-cli) project is quite similar to ragenix. In fact, it 80 | served as an inspiration (thanks!). Both projects have in common that they aim 81 | at replacing the fragile shell script with a version written in Rust. In contrast to `ragenix`, however, 82 | `agenix-cli` is not compatible to the original `agenix`. It uses a TOML configuration file to declare rules 83 | on a repository level (similar to `.sops.yaml`). While having a global rules file might be 84 | useful for some (particularly if you're looking to switch from [`sops-nix`]( 85 | https://github.com/Mic92/sops-nix)), we wanted to continue to define our rules using Nix expressions which 86 | reside in different directories. 87 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, io::Error}; 2 | 3 | use clap_complete::{ 4 | generate_to, 5 | Shell::{Bash, Fish, Zsh}, 6 | }; 7 | 8 | include!("src/cli.rs"); 9 | 10 | fn main() -> Result<(), Error> { 11 | // Read path to the `nix` binary from the environment 12 | let nix_bin_path = match env::var("RAGENIX_NIX_BIN_PATH") { 13 | Err(_) => { 14 | println!( 15 | "cargo:warning=Environment variable RAGENIX_NIX_BIN_PATH not given, using 'nix'" 16 | ); 17 | "nix".to_string() 18 | } 19 | Ok(val) => val, 20 | }; 21 | println!("cargo:rustc-env=RAGENIX_NIX_BIN_PATH={nix_bin_path}"); 22 | 23 | // Make the paths to the shell completion files available as environment variables 24 | let Some(outdir) = env::var_os("OUT_DIR") else { 25 | return Ok(()); 26 | }; 27 | 28 | let mut app = build(); 29 | app.set_bin_name(crate_name!()); 30 | 31 | let bash_outpath = generate_to(Bash, &mut app, crate_name!(), &outdir)?; 32 | println!( 33 | "cargo:rustc-env=RAGENIX_COMPLETIONS_BASH={}", 34 | bash_outpath.to_string_lossy() 35 | ); 36 | 37 | let fish_outpath = generate_to(Fish, &mut app, crate_name!(), &outdir)?; 38 | println!( 39 | "cargo:rustc-env=RAGENIX_COMPLETIONS_FISH={}", 40 | fish_outpath.to_string_lossy() 41 | ); 42 | 43 | let zsh_outpath = generate_to(Zsh, &mut app, crate_name!(), &outdir)?; 44 | println!( 45 | "cargo:rustc-env=RAGENIX_COMPLETIONS_ZSH={}", 46 | zsh_outpath.to_string_lossy() 47 | ); 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { craneLib 2 | , lib 3 | , stdenv 4 | , darwin 5 | , installShellFiles 6 | , makeWrapper 7 | , nix 8 | , openssl 9 | , pkg-config 10 | , plugins ? [ ] 11 | # Allowing running the tests without the "recursive-nix" feature to allow 12 | # building the package without having a recursive-nix-enabled Nix. 13 | , enableRecursiveNixTests ? false 14 | , target ? stdenv.targetPlatform.rust.cargoShortTarget 15 | }: 16 | let 17 | commonArgs = { 18 | src = lib.cleanSourceWith rec { 19 | src = craneLib.path ./.; 20 | filter = path: type: 21 | let pathWithoutPrefix = lib.removePrefix (toString src) path; in 22 | lib.hasPrefix "/docs/" pathWithoutPrefix || 23 | lib.hasPrefix "/example/" pathWithoutPrefix || 24 | lib.hasPrefix "/src/" pathWithoutPrefix || 25 | craneLib.filterCargoSources path type; 26 | }; 27 | 28 | # build dependencies 29 | nativeBuildInputs = [ 30 | pkg-config 31 | installShellFiles 32 | ] ++ lib.optionals (plugins != [ ]) [ 33 | makeWrapper 34 | ]; 35 | 36 | # runtime dependencies 37 | buildInputs = [ 38 | openssl 39 | ] ++ lib.optionals stdenv.isDarwin [ 40 | darwin.Security 41 | ]; 42 | 43 | # Absolute path to the `nix` binary, used in `build.rs` 44 | RAGENIX_NIX_BIN_PATH = lib.getExe nix; 45 | }; 46 | cargoArtifacts = craneLib.buildDepsOnly commonArgs; 47 | in 48 | craneLib.buildPackage (commonArgs // { 49 | inherit cargoArtifacts; 50 | 51 | cargoExtraArgs = "--target ${target}"; 52 | 53 | cargoTestExtraArgs = lib.optionalString (!enableRecursiveNixTests) "--no-default-features"; 54 | requiredSystemFeatures = lib.optionals enableRecursiveNixTests [ "recursive-nix" ]; 55 | 56 | postInstall = '' 57 | set -euo pipefail 58 | 59 | # Provide a symlink from `agenix` to `ragenix` for compat 60 | ln -sr "$out/bin/ragenix" "$out/bin/agenix" 61 | 62 | # Stdout of build.rs 63 | buildOut=$(grep -m 1 -Rl 'RAGENIX_COMPLETIONS_BASH=/' target/ | head -n 1) 64 | printf "found build script output at %s\n" "$buildOut" 65 | 66 | set +u # required due to `installShellCompletion`'s implementation 67 | installShellCompletion --bash "$(grep -oP 'RAGENIX_COMPLETIONS_BASH=\K.+' "$buildOut")" 68 | installShellCompletion --zsh "$(grep -oP 'RAGENIX_COMPLETIONS_ZSH=\K.+' "$buildOut")" 69 | installShellCompletion --fish "$(grep -oP 'RAGENIX_COMPLETIONS_FISH=\K.+' "$buildOut")" 70 | 71 | installManPage docs/ragenix.1 72 | ''; 73 | 74 | # Make the plugins available in ragenix' PATH 75 | postFixup = lib.optionalString (plugins != [ ]) '' 76 | wrapProgram "$out/bin/ragenix" --suffix PATH : ${lib.strings.makeBinPath plugins} 77 | ''; 78 | 79 | passthru.cargoArtifacts = cargoArtifacts; 80 | 81 | meta.mainProgram = "ragenix"; 82 | }) 83 | -------------------------------------------------------------------------------- /docs/ragenix.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn-NG/v0.9.1 2 | .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 3 | .TH "RAGENIX" "1" "January 2022" "" 4 | .SH "NAME" 5 | \fBragenix\fR \- age\-encrypted secrets for Nix 6 | .SH "SYNOPSIS" 7 | \fBragenix\fR [\fB\-\-rules\fR \fIPATH\fR=\./secrets\.nix] [\fB\-i\fR \fIPATH\fR]\|\.\|\.\|\. (\fB\-e\fR \fIPATH\fR | \fB\-r\fR) 8 | .br 9 | \fBragenix\fR \fB\-e\fR \fIPATH\fR 10 | .br 11 | \fBragenix\fR \fB\-r\fR 12 | .br 13 | .SH "DESCRIPTION" 14 | \fBragenix\fR encrypts secrets defined in a Nix configuration expression using \fBage(1)\fR\. It is safe to publicly expose the resulting age\-encrypted files, e\.g\., by checking them into version control or copying them to the world\-readable Nix store\. 15 | .SH "OPTIONS" 16 | .TP 17 | \fB\-e\fR, \fB\-\-edit\fR \fIPATH\fR 18 | Decrypt the file at \fIPATH\fR and open it for editing\. If the \fIPATH\fR does not exist yet, \fBragenix\fR opens an empty file for editing\. In any case, the given \fIPATH\fR has to match a rule as configured in the file given to the \fB\-\-rules\fR option\. After editing, \fBragenix\fR encrypts the updated contents and replaces the original file\. 19 | .IP 20 | If the \fB\-\-identity\fR option is not given, \fBragenix\fR tries to decrypt \fIPATH\fR with the default SSH private keys\. See \fB\-\-identity\fR for details\. 21 | .IP 22 | The encrypted file always uses an ASCII\-armored format\. 23 | .IP 24 | \fBragenix\fR writes the decrypted plaintext contents of the secret at \fIPATH\fR to a temporary file which is only accessible by the calling user\. After editing, \fBragenix\fR deletes the file, making it inaccessible after \fBragenix\fR exits\. 25 | .TP 26 | \fB\-\-editor\fR \fIPROGRAM\fR 27 | Use the given \fIPROGRAM\fR to open the decrypted file for editing\. Defaults to the \fBEDITOR\fR environment variable\. 28 | .IP 29 | \fIPROGRAM\fR may denote an absolute binary path or a binary relative to the \fBPATH\fR environment variable\. \fBragenix\fR assumes \fIPROGRAM\fR accepts the absolute path to the decrypted age secret file as its first argument\. 30 | .IP 31 | Giving the special token \fB\-\fR as a \fIPROGRAM\fR causes \fBragenix\fR to read from standard input\. In this case, \fBragenix\fR stream\-encrypts data from standard input only and does not open the file for editing\. 32 | .TP 33 | \fB\-r\fR, \fB\-\-rekey\fR 34 | Decrypt all secrets given in the rules configuration file and encrypt them with the defined public keys\. If a secret file does not exist yet, it is ignored\. This option is useful to grant a new recipient access to one or multiple secrets\. 35 | .IP 36 | If the \fB\-\-identity\fR option is not given, \fBragenix\fR tries to decrypt \fIPATH\fR with the default SSH private keys\. See \fB\-\-identity\fR for details\. 37 | .IP 38 | When rekeying, \fBragenix\fR does not write any plaintext data to disk; all processing happens in\-memory\. 39 | .SH "COMMON OPTIONS" 40 | .TP 41 | \fB\-\-rules\fR \fIPATH\fR 42 | Path to a file containing a Nix expression which maps age\-encrypted secret files to the public keys of recipients who should be able to decrypt them\. Each defined secret file string is considered relative to the parent directory of the rules file\. See the \fIEXAMPLES\fR section for a simple rules configuration\. 43 | .IP 44 | If omitted, \fBragenix\fR reads the content of the \fBRULES\fR environment variable\. If the environment variable is also unset, \fBragenix\fR tries opening the file \fBsecrets\.nix\fR in the current working directory\. 45 | .TP 46 | \fB\-i\fR, \fB\-\-identity\fR \fIPATH\fR 47 | Decrypt using the identities at \fIPATH\fR\. 48 | .IP 49 | This option can be repeated\. Additionally, \fBragenix\fR uses the default Ed25519 and RSA SSH authentication identities at ~/\.ssh/id_ed25519 and ~/\.ssh/id_rsa, respectively\. Identities given explicitly take precedence over the default SSH identities\. If no identities are given, \fBragenix\fR tries using the default SSH identities only\. 50 | .IP 51 | Passphrase\-encrypted age identities and passphrase\-encryted SSH identities are supported\. Currently, however, it is necessary to enter the passphrase of an SSH identity for each file to decrypt\. This may result in poor usability, particularly when using the \fB\-\-rekey\fR option\. 52 | .IP 53 | For further details regarding this option also refer to \fBage(1)\fR\. 54 | .SH "FURTHER OPTIONS" 55 | .TP 56 | \fB\-s\fR, \fB\-\-schema\fR 57 | Print the JSON schema the Nix configuration rules have to conform to and exit\. Useful for consumption by third\-party applications\. 58 | .TP 59 | \fB\-v\fR, \fB\-\-verbose\fR 60 | Print additional information during program execution\. 61 | .TP 62 | \fB\-V\fR, \fB\-\-version\fR 63 | Print the version and exit\. 64 | .SH "PLUGINS" 65 | \fBragenix\fR also supports \fBage\fR plugins\. If the plugin binaries are present in \fBPATH\fR, \fBragenix\fR picks them up as needed\. 66 | .P 67 | Additionally, \fBragenix\fR supports adding plugins to its derviation to make them always available to \fBragenix\fR\. Use the \fBplugins\fR argument of the derivation to wrap the \fBragenix\fR binary with a \fBPATH\fR extended by the given plugin derivations\. Matching plugin binaries which are part of \fBPATH\fR when invoking \fBragenix\fR are preferred\. See the \fIEXAMPLES\fR section for an example\. 68 | .SH "EXAMPLES" 69 | A basic Nix configuration rules file (typically named secrets\.nix) which defines a secret file secret\.txt\.age which should be encrypted to an age and an SSH recipient\. \fBragenix\fR looks for secret\.txt\.age relative to \./secrets/: 70 | .IP "" 4 71 | .nf 72 | $ cat \./secrets/secrets\.nix 73 | { 74 | "secret\.txt\.age"\.publicKeys = [ 75 | "age1g4eapz2lkdvrevsg443yx8rhxklhyz4sa8w0jdfyh8sgx3azhftsz8zu07" 76 | "ssh\-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrb9ne3nZjw6DW[\|\.\|\.\|\.]8h/Zoa" 77 | ]; 78 | } 79 | $ file \./secrets/secret\.txt\.age 80 | \&\./secrets/secret\.txt\.age: ASCII text 81 | .fi 82 | .IP "" 0 83 | .P 84 | Edit the secret file secret\.txt\.age in the default editor while using the default SSH Ed25519 identity at ~/\.ssh/id_ed25519 with a rules configuration file different from \./secrets\.nix: 85 | .IP "" 4 86 | .nf 87 | $ ls ~/\.ssh/ 88 | id_ed25519 id_ed25519\.pub 89 | $ ls /var/lib/secrets/ 90 | rules\.nix secret\.txt\.age 91 | $ ragenix \-\-rules /var/lib/secrets/rules\.nix \-e secret\.txt\.age 92 | .fi 93 | .IP "" 0 94 | .P 95 | Rekey all secrets given in \./secrets\.nix with the age identity ~/\.age/ragenix\.key: 96 | .IP "" 4 97 | .nf 98 | $ ragenix \-i ~/\.age/ragenix\.key \-r 99 | .fi 100 | .IP "" 0 101 | .P 102 | Create/edit a secret from the system clipboard (on macOS): 103 | .IP "" 4 104 | .nf 105 | $ pbpaste | ragenix \-\-editor \- \-e secret\.txt\.age 106 | .fi 107 | .IP "" 0 108 | .P 109 | Use \fB\-\-editor\fR to generate an SSH Ed25519 private key: 110 | .IP "" 4 111 | .nf 112 | $ ragenix \-\-editor 'ssh\-keygen \-q \-N "" \-t ed25519 \-f' \-e ssh_host_key\.age 113 | .fi 114 | .IP "" 0 115 | .P 116 | Make the \fBage\fR YubiKey plugin available to \fBragenix\fR: 117 | .IP "" 4 118 | .nf 119 | $ cat myragenix\.nix 120 | { ragenix, age\-plugin\-yubikey }: 121 | ragenix\.override { plugins = [ age\-plugin\-yubikey ]; } 122 | .fi 123 | .IP "" 0 124 | .SH "SEE ALSO" 125 | age(1), age\-keygen(1) 126 | .SH "AUTHORS" 127 | Vincent Haupert \fImail@vincent\-haupert\.de\fR 128 | -------------------------------------------------------------------------------- /docs/ragenix.1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ragenix(1) - age-encrypted secrets for Nix 7 | 44 | 45 | 52 | 53 |
54 | 55 | 67 | 68 |
    69 |
  1. ragenix(1)
  2. 70 |
  3. 71 |
  4. ragenix(1)
  5. 72 |
73 | 74 | 75 | 76 |

NAME

77 |

78 | ragenix - age-encrypted secrets for Nix 79 |

80 |

SYNOPSIS

81 | 82 |

ragenix [--rules PATH=./secrets.nix] [-i PATH]... (-e PATH | -r)
83 | ragenix -e PATH
84 | ragenix -r

85 | 86 |

DESCRIPTION

87 | 88 |

ragenix encrypts secrets defined in a Nix configuration expression using 89 | age(1). It is safe to publicly expose the resulting age-encrypted files, e.g., 90 | by checking them into version control or copying them to the world-readable Nix 91 | store.

92 | 93 |

OPTIONS

94 | 95 |
96 |
97 | -e, --edit PATH 98 |
99 |
Decrypt the file at PATH and open it for editing. If the PATH does not 100 | exist yet, ragenix opens an empty file for editing. In any case, the 101 | given PATH has to match a rule as configured in the file given to the 102 | --rules option. After editing, ragenix encrypts the updated contents 103 | and replaces the original file. 104 | 105 |

If the --identity option is not given, ragenix tries to decrypt PATH 106 | with the default SSH private keys. See --identity for details.

107 | 108 |

The encrypted file always uses an ASCII-armored format.

109 | 110 |

ragenix writes the decrypted plaintext contents of the secret at PATH 111 | to a temporary file which is only accessible by the calling user. After 112 | editing, ragenix deletes the file, making it inaccessible after ragenix 113 | exits.

114 |
115 |
116 | --editor PROGRAM 117 |
118 |
Use the given PROGRAM to open the decrypted file for editing. Defaults to 119 | the EDITOR environment variable. 120 | 121 |

PROGRAM may denote an absolute binary path or a binary relative to the 122 | PATH environment variable. ragenix assumes PROGRAM accepts the 123 | absolute path to the decrypted age secret file as its first argument.

124 | 125 |

Giving the special token - as a PROGRAM causes ragenix to read from 126 | standard input. In this case, ragenix stream-encrypts data from standard 127 | input only and does not open the file for editing.

128 |
129 |
130 | -r, --rekey 131 |
132 |
Decrypt all secrets given in the rules configuration file and encrypt them 133 | with the defined public keys. If a secret file does not exist yet, it is 134 | ignored. This option is useful to grant a new recipient access to one or 135 | multiple secrets. 136 | 137 |

If the --identity option is not given, ragenix tries to decrypt PATH 138 | with the default SSH private keys. See --identity for details.

139 | 140 |

When rekeying, ragenix does not write any plaintext data to disk; all 141 | processing happens in-memory.

142 |
143 |
144 | 145 |

COMMON OPTIONS

146 | 147 |
148 |
149 | --rules PATH 150 |
151 |
Path to a file containing a Nix expression which maps age-encrypted secret 152 | files to the public keys of recipients who should be able to decrypt them. 153 | Each defined secret file string is considered relative to the parent 154 | directory of the rules file. See the EXAMPLES section for a 155 | simple rules configuration. 156 | 157 |

If omitted, ragenix reads the content of the RULES environment 158 | variable. If the environment variable is also unset, ragenix tries 159 | opening the file secrets.nix in the current working directory.

160 |
161 |
162 | -i, --identity PATH 163 |
164 |
Decrypt using the identities at PATH. 165 | 166 |

This option can be repeated. Additionally, ragenix uses the default 167 | Ed25519 and RSA SSH authentication identities at ~/.ssh/id_ed25519 and 168 | ~/.ssh/id_rsa, respectively. Identities given explicitly take precedence 169 | over the default SSH identities. If no identities are given, ragenix 170 | tries using the default SSH identities only.

171 | 172 |

Passphrase-encrypted age identities and passphrase-encryted SSH identities 173 | are supported. Currently, however, it is necessary to enter the passphrase 174 | of an SSH identity for each file to decrypt. This may result in poor 175 | usability, particularly when using the --rekey option.

176 | 177 |

For further details regarding this option also refer to age(1).

178 |
179 |
180 | 181 |

FURTHER OPTIONS

182 | 183 |
184 |
185 | -s, --schema 186 |
187 |
Print the JSON schema the Nix configuration rules have to conform to and 188 | exit. Useful for consumption by third-party applications.
189 |
190 | -v, --verbose 191 |
192 |
Print additional information during program execution.
193 |
194 | -V, --version 195 |
196 |
Print the version and exit.
197 |
198 | 199 |

PLUGINS

200 | 201 |

ragenix also supports age plugins. If the plugin binaries are present in 202 | PATH, ragenix picks them up as needed.

203 | 204 |

Additionally, ragenix supports adding plugins to its derviation to make them 205 | always available to ragenix. Use the plugins argument of the derivation to 206 | wrap the ragenix binary with a PATH extended by the given plugin 207 | derivations. Matching plugin binaries which are part of PATH when invoking 208 | ragenix are preferred. See the EXAMPLES section for an example.

209 | 210 |

EXAMPLES

211 | 212 |

A basic Nix configuration rules file (typically named secrets.nix) which 213 | defines a secret file secret.txt.age which should be encrypted to an age and 214 | an SSH recipient. ragenix looks for secret.txt.age relative to ./secrets/:

215 | 216 |
$ cat ./secrets/secrets.nix
217 | {
218 |   "secret.txt.age".publicKeys = [
219 |     "age1g4eapz2lkdvrevsg443yx8rhxklhyz4sa8w0jdfyh8sgx3azhftsz8zu07"
220 |     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrb9ne3nZjw6DW[...]8h/Zoa"
221 |   ];
222 | }
223 | $ file ./secrets/secret.txt.age
224 | ./secrets/secret.txt.age: ASCII text
225 | 
226 | 227 |

Edit the secret file secret.txt.age in the default editor while using the 228 | default SSH Ed25519 identity at ~/.ssh/id_ed25519 with a rules configuration 229 | file different from ./secrets.nix:

230 | 231 |
$ ls ~/.ssh/
232 | id_ed25519  id_ed25519.pub
233 | $ ls /var/lib/secrets/
234 | rules.nix secret.txt.age
235 | $ ragenix --rules /var/lib/secrets/rules.nix -e secret.txt.age
236 | 
237 | 238 |

Rekey all secrets given in ./secrets.nix with the age identity 239 | ~/.age/ragenix.key:

240 | 241 |
$ ragenix -i ~/.age/ragenix.key -r
242 | 
243 | 244 |

Create/edit a secret from the system clipboard (on macOS):

245 | 246 |
$ pbpaste | ragenix --editor - -e secret.txt.age
247 | 
248 | 249 |

Use --editor to generate an SSH Ed25519 private key:

250 | 251 |
$ ragenix --editor 'ssh-keygen -q -N "" -t ed25519 -f' -e ssh_host_key.age
252 | 
253 | 254 |

Make the age YubiKey plugin available to ragenix:

255 | 256 |
$ cat myragenix.nix
257 | { ragenix, age-plugin-yubikey }:
258 | ragenix.override { plugins = [ age-plugin-yubikey ]; }
259 | 
260 | 261 |

SEE ALSO

262 | 263 |

age(1), age-keygen(1)

264 | 265 |

AUTHORS

266 | 267 |

Vincent Haupert mail@vincent-haupert.de

268 | 269 |
    270 |
  1. 271 |
  2. January 2022
  3. 272 |
  4. ragenix(1)
  5. 273 |
274 | 275 |
276 | 277 | 278 | -------------------------------------------------------------------------------- /docs/ragenix.1.ronn: -------------------------------------------------------------------------------- 1 | ragenix(1) -- age-encrypted secrets for Nix 2 | =========================================== 3 | 4 | ## SYNOPSIS 5 | 6 | `ragenix` [`--rules` =./secrets.nix] [`-i` ]... (`-e` | `-r`)
7 | `ragenix` `-e`
8 | `ragenix` `-r`
9 | 10 | ## DESCRIPTION 11 | 12 | `ragenix` encrypts secrets defined in a Nix configuration expression using 13 | `age(1)`. It is safe to publicly expose the resulting age-encrypted files, e.g., 14 | by checking them into version control or copying them to the world-readable Nix 15 | store. 16 | 17 | ## OPTIONS 18 | 19 | * `-e`, `--edit` : 20 | Decrypt the file at and open it for editing. If the does not 21 | exist yet, `ragenix` opens an empty file for editing. In any case, the 22 | given has to match a rule as configured in the file given to the 23 | `--rules` option. After editing, `ragenix` encrypts the updated contents 24 | and replaces the original file. 25 | 26 | If the `--identity` option is not given, `ragenix` tries to decrypt 27 | with the default SSH private keys. See `--identity` for details. 28 | 29 | The encrypted file always uses an ASCII-armored format. 30 | 31 | `ragenix` writes the decrypted plaintext contents of the secret at 32 | to a temporary file which is only accessible by the calling user. After 33 | editing, `ragenix` deletes the file, making it inaccessible after `ragenix` 34 | exits. 35 | 36 | * `--editor` : 37 | Use the given to open the decrypted file for editing. Defaults to 38 | the `EDITOR` environment variable. 39 | 40 | may denote an absolute binary path or a binary relative to the 41 | `PATH` environment variable. `ragenix` assumes accepts the 42 | absolute path to the decrypted age secret file as its first argument. 43 | 44 | Giving the special token `-` as a causes `ragenix` to read from 45 | standard input. In this case, `ragenix` stream-encrypts data from standard 46 | input only and does not open the file for editing. 47 | 48 | * `-r`, `--rekey`: 49 | Decrypt all secrets given in the rules configuration file and encrypt them 50 | with the defined public keys. If a secret file does not exist yet, it is 51 | ignored. This option is useful to grant a new recipient access to one or 52 | multiple secrets. 53 | 54 | If the `--identity` option is not given, `ragenix` tries to decrypt 55 | with the default SSH private keys. See `--identity` for details. 56 | 57 | When rekeying, `ragenix` does not write any plaintext data to disk; all 58 | processing happens in-memory. 59 | 60 | ## COMMON OPTIONS 61 | 62 | * `--rules` : 63 | Path to a file containing a Nix expression which maps age-encrypted secret 64 | files to the public keys of recipients who should be able to decrypt them. 65 | Each defined secret file string is considered relative to the parent 66 | directory of the rules file. See the [EXAMPLES][] section for a 67 | simple rules configuration. 68 | 69 | If omitted, `ragenix` reads the content of the `RULES` environment 70 | variable. If the environment variable is also unset, `ragenix` tries 71 | opening the file `secrets.nix` in the current working directory. 72 | 73 | * `-i`, `--identity` : 74 | Decrypt using the identities at . 75 | 76 | This option can be repeated. Additionally, `ragenix` uses the default 77 | Ed25519 and RSA SSH authentication identities at ~/.ssh/id_ed25519 and 78 | ~/.ssh/id_rsa, respectively. Identities given explicitly take precedence 79 | over the default SSH identities. If no identities are given, `ragenix` 80 | tries using the default SSH identities only. 81 | 82 | Passphrase-encrypted age identities and passphrase-encryted SSH identities 83 | are supported. Currently, however, it is necessary to enter the passphrase 84 | of an SSH identity for each file to decrypt. This may result in poor 85 | usability, particularly when using the `--rekey` option. 86 | 87 | For further details regarding this option also refer to `age(1)`. 88 | 89 | ## FURTHER OPTIONS 90 | 91 | * `-s`, `--schema`: 92 | Print the JSON schema the Nix configuration rules have to conform to and 93 | exit. Useful for consumption by third-party applications. 94 | 95 | * `-v`, `--verbose`: 96 | Print additional information during program execution. 97 | 98 | * `-V`, `--version`: 99 | Print the version and exit. 100 | 101 | ## PLUGINS 102 | 103 | `ragenix` also supports `age` plugins. If the plugin binaries are present in 104 | `PATH`, `ragenix` picks them up as needed. 105 | 106 | Additionally, `ragenix` supports adding plugins to its derviation to make them 107 | always available to `ragenix`. Use the `plugins` argument of the derivation to 108 | wrap the `ragenix` binary with a `PATH` extended by the given plugin 109 | derivations. Matching plugin binaries which are part of `PATH` when invoking 110 | `ragenix` are preferred. See the [EXAMPLES][] section for an example. 111 | 112 | ## EXAMPLES 113 | 114 | A basic Nix configuration rules file (typically named secrets.nix) which 115 | defines a secret file secret.txt.age which should be encrypted to an age and 116 | an SSH recipient. `ragenix` looks for secret.txt.age relative to ./secrets/: 117 | 118 | $ cat ./secrets/secrets.nix 119 | { 120 | "secret.txt.age".publicKeys = [ 121 | "age1g4eapz2lkdvrevsg443yx8rhxklhyz4sa8w0jdfyh8sgx3azhftsz8zu07" 122 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrb9ne3nZjw6DW[...]8h/Zoa" 123 | ]; 124 | } 125 | $ file ./secrets/secret.txt.age 126 | ./secrets/secret.txt.age: ASCII text 127 | 128 | Edit the secret file secret.txt.age in the default editor while using the 129 | default SSH Ed25519 identity at ~/.ssh/id_ed25519 with a rules configuration 130 | file different from ./secrets.nix: 131 | 132 | $ ls ~/.ssh/ 133 | id_ed25519 id_ed25519.pub 134 | $ ls /var/lib/secrets/ 135 | rules.nix secret.txt.age 136 | $ ragenix --rules /var/lib/secrets/rules.nix -e secret.txt.age 137 | 138 | Rekey all secrets given in ./secrets.nix with the age identity 139 | ~/.age/ragenix.key: 140 | 141 | $ ragenix -i ~/.age/ragenix.key -r 142 | 143 | Create/edit a secret from the system clipboard (on macOS): 144 | 145 | $ pbpaste | ragenix --editor - -e secret.txt.age 146 | 147 | Use `--editor` to generate an SSH Ed25519 private key: 148 | 149 | $ ragenix --editor 'ssh-keygen -q -N "" -t ed25519 -f' -e ssh_host_key.age 150 | 151 | Make the `age` YubiKey plugin available to `ragenix`: 152 | 153 | $ cat myragenix.nix 154 | { ragenix, age-plugin-yubikey }: 155 | ragenix.override { plugins = [ age-plugin-yubikey ]; } 156 | 157 | ## SEE ALSO 158 | 159 | age(1), age-keygen(1) 160 | 161 | ## AUTHORS 162 | 163 | Vincent Haupert 164 | -------------------------------------------------------------------------------- /example/github-runner.token.age: -------------------------------------------------------------------------------- 1 | -----BEGIN AGE ENCRYPTED FILE----- 2 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvbmU4SHk4Z2ZZSWdTTGpv 3 | dkhzcmk3NjBHL1J1M0ZoYng3N1BTVUJ4RkN3Cld2SHRFNkk4S0JmaDE5QUpwTGVU 4 | K1dDc0lYNmNVcjBIZ0IyTUNZSFdZUWcKLT4gc3NoLWVkMjU1MTkgYTZIN05nIEdz 5 | YUdLQjhwWkFkV2s2ZjI5VWxxNkJhUmkzQjYwYWtHd0pDYVE3S2paUzgKdVdMZUxm 6 | cnpudmtzM1F4N0xVQW9vS2VDL0lYSm9QQjFiYjNpNmpLRTJHYwotPiBzc2gtcnNh 7 | IDFORE5uQQpTWGlwcG9iVExwZThSRk9QMFB1cUFaSWJvdk9KZXYyN3NlOFJNZDds 8 | WXErMWYrcTFQK3l1QjBnelRndnoyZDhFCi8yTms1WkdHN21kNTJQTmkxUm1xUTlR 9 | QUs3TDJDMjZtTHdCUERzcVlEdFdkalhyT3d3WXpQcjREVkRLQ3JXcW0KN2liN0Y2 10 | U1gzOU9rdnFzelpzVlFwQitJaTVnRFRSd3RDL2dvb1BNK2szUUNVRHRiVkV5V2py 11 | bWJSellBcjBkVgpLbnhON05yTVhMMTVKVU1ISnJMUFFCaVI4WE4rUkttUDd4V2py 12 | dXJvR05yaE9MaGovN1B3ZDlCS0pGOE9IcmxqCjl3Vk0zMVFzeDg5TE1LOHh6SmpW 13 | UkM3NUpENC90dVU1Ni9CQ0wraXhzUkRtTUlxOFZtWlN2bE0vcjV4TC9abmcKWUxI 14 | dk9ab1VRRURZR3ljQWxBbTdvTDhRWjlDelVad1dNeWswWm1aUkFMRm1zOUt2T29l 15 | QUdHakZYZTI5L3JJQQorbjNjYnRpWm9pMk9UOUZFbWVkTFpQdTZhV2o4S2gzZ09U 16 | d2p5bFRjRlJ3RW9rbWRIZmd4MzlkVDZyN3Z1bXBHCkkvTklMbnByLzUraTVJa3J5 17 | bTBlSDVsbVg5djN1dWM5SVkvbFJNRkRTUnRkcGY2QzlVNzZyOUtrZHA1TDc5SkYK 18 | Ci0+IGstZ3JlYXNlIEtJdXpZICR8ClVPTytCTHhsUXZQWWIxM3JaN005M1paY1hp 19 | eGhRTGhjK3BvYTE4am1PK2xkWmd1SmQ3MlJYLzNEOGVMOEQyazQKYmgxMlM2Rjhn 20 | VHpsQWxJOU05eWVuMlp1anpZWk1KVWZFY3hMcEhpMzRjZDUKLS0tIC9XMnk2Rzdu 21 | ZkVlRXFYMllxVHpROU5jRjIrYkt1UkJydmdjbnFoK3g4ZVEKddEezhLGwe00kNfD 22 | 3x7xI7Cyft9CICJzCpuxcj5yOOc2GG2s3BQCO14kRPot4Q== 23 | -----END AGE ENCRYPTED FILE----- 24 | -------------------------------------------------------------------------------- /example/keys/README.md: -------------------------------------------------------------------------------- 1 | # Example keys 2 | 3 | Naturally, you should never check in your private keys into source control ... 4 | 5 | -------------------------------------------------------------------------------- /example/keys/example_plugin_key.txt: -------------------------------------------------------------------------------- 1 | # created: 2021-12-25T18:53:13+01:00 2 | # recipient: age1unencrypted1k5fr0r 3 | AGE-PLUGIN-UNENCRYPTED-1CR7ZD5 4 | -------------------------------------------------------------------------------- /example/keys/id_ed25519: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACC6D3ZBH4XLJlui4POhjKxCWZ2HxQW+3y/RvyFyu6Q0HQAAAJhxV11acVdd 4 | WgAAAAtzc2gtZWQyNTUxOQAAACC6D3ZBH4XLJlui4POhjKxCWZ2HxQW+3y/RvyFyu6Q0HQ 5 | AAAECwi4tHneTYgkHYpQEoG12u0LpD9hEXMpZhCvI3MtNFvboPdkEfhcsmW6Lg86GMrEJZ 6 | nYfFBb7fL9G/IXK7pDQdAAAAFHZlZWhhaXRjaEB2LW0xLmxvY2FsAQ== 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /example/keys/id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAYEAx3d8gWIWWwZDKsnMv0joMfXe/U1eUO+nM35JilClc8sFCWyQsGeM 4 | DAPn3+0Mwbn2UaAFqRnkG/q7LC0BDsEflV99BcOyvdluJCugQheaYT+gFAqNO3t74xqir4 5 | CePvC2L3Agde22H/KSYW+BqDhi65STSmxmToX2BNHhhbv31rFcm0GrAtp2onin6hzsEvZD 6 | StDdYEp1odKrtw7vbwQG0ARBNd5jxnuMxHSM06IDNOvMYCoXyVFqrtcQslQVSTXc/V8NDS 7 | 2lkCaJ/GFw3uton8S49y4J3cCpvdXDfZpi9JJXBgeTvCtpjxNGRhTA7sXXJZVHIUIGF3Tc 8 | RFgeBL6ZL6UG4jvn44DKR3hBIjT3f79OmpkgaivkJSBhJDwBJRC6YTy4kyZh1asXOTWn31 9 | XOxfszPnN/mlgeRaoJNY3J6pvB5pSxLzz09UtNHPNEpGNVd5VFDoqxZXIj/6RIryXowOWI 10 | 9otTRBBC56wnQcP8UOI33rCMXKpgYcZOXVngW3xrAAAFmP2A9FL9gPRSAAAAB3NzaC1yc2 11 | EAAAGBAMd3fIFiFlsGQyrJzL9I6DH13v1NXlDvpzN+SYpQpXPLBQlskLBnjAwD59/tDMG5 12 | 9lGgBakZ5Bv6uywtAQ7BH5VffQXDsr3ZbiQroEIXmmE/oBQKjTt7e+Maoq+Anj7wti9wIH 13 | Xtth/ykmFvgag4YuuUk0psZk6F9gTR4YW799axXJtBqwLadqJ4p+oc7BL2Q0rQ3WBKdaHS 14 | q7cO728EBtAEQTXeY8Z7jMR0jNOiAzTrzGAqF8lRaq7XELJUFUk13P1fDQ0tpZAmifxhcN 15 | 7raJ/EuPcuCd3Aqb3Vw32aYvSSVwYHk7wraY8TRkYUwO7F1yWVRyFCBhd03ERYHgS+mS+l 16 | BuI75+OAykd4QSI093+/TpqZIGor5CUgYSQ8ASUQumE8uJMmYdWrFzk1p99VzsX7Mz5zf5 17 | pYHkWqCTWNyeqbweaUsS889PVLTRzzRKRjVXeVRQ6KsWVyI/+kSK8l6MDliPaLU0QQQues 18 | J0HD/FDiN96wjFyqYGHGTl1Z4Ft8awAAAAMBAAEAAAGAfjYAte1Wr+nafiyKPU7ofRRSMH 19 | zP/O5nIZH6/mXhCs+fNENZmgUq/D9MbMgVlk8QqL1UV5dmWr3fySgAz1Erptxww0D8zFvT 20 | 8VcZZDiDGp2jsIqsqsKLtTuZTOtZNgIhhQqxv2zWHIjkTnq707E6Bk5QlHrakTz6WjpjNL 21 | enLlWzfxmiF4AcKNmnhd8fETB5i9F1AgSbQfWHaaEtp1Uo6l6G1IsPHvv2KiiFIzQUS1wY 22 | SZDY1C2MD21Nx0ik31oQiJNOEjtCp8hO1VTGOeX8Iw7VXwYsHaNE+ud/3oLJGYU0Ne64m5 23 | snVpg53bP47LZ7r3U3SgYgGOeUD7QEPBbD2eQ6BmO0kfeQUcjzAMEC20W2bp/gYmYctaXb 24 | zoAdN6Jh/gduS8ukDGIntBEzIs2XyWl8kcXYQYYIqBUyUBd+e6BjFxoSjsXOvZreR2f5FZ 25 | +AXDANHlp0LPMkdiFMPZJwF1lsL9MIgyTi6aucW5SFe7FIL5xXJ9L1AmtmZnY/072BAAAA 26 | wG3J9nGeLMOZtLXboH95WDESbaJJ5kiQnBS8TRRIlByEyqTw9NKI+OpwlIaHm16iIMeLB+ 27 | Wzy2MD4MykCgoX/EY/XQktF3AY/4yVYL5tDANRTgPukmVEUGod82HvDDu0FcwV4PZe9i1r 28 | o/dvL3Y/b0siuI6CHJDIqS31xytG+uKCvbkkmDfVVsFdTyYqj8KUUHSx4Dc/eSwPfAjw4f 29 | iKIea9OKZ8BDEnYQBVP9rOm1VvgfoK5DIes0wKl4L3/VzbRQAAAMEA7BnWKukv1o/mAOF2 30 | a24r4acBcxfti1QHO7e64D0kWFhrUvQyi78weB9EqJW//eYzT32VJ278dayC2I7Fw+cxuF 31 | CCQx/gSKiELJe63g7GsPndLbtw5N/3uhkK2BUHjrkk9iYa45JYiRtONoxGL7V2h7AfVsRL 32 | GkjztZUihZ+AxjjTNcp/kH5FG0xMSbhzsKwo501XL7YGXFyBWQrklGkPIJOoL4rGQWuF2u 33 | 2J8+h+mjMwrUcghBenIb5dWNJiMJM7AAAAwQDYRzkSdh0OPrb2N8bMzh8UOsEJSYc/Pbmo 34 | VxvDvyIJp5l/18DWnPNFCMusFCmO0YNA1olCtihVD6iq7YvQK4k+PNypyov6SQk6Ag3AD/ 35 | Gp/69hOJCQvTL+iIBGTOGvbvVmfIy53RTUtZc9IX8x7kMclBW8pyrGWSKfgPiIaN/N14T6 36 | 1tOnuyvLBZKNs/Xu8i4MEXJcuNjrjhUsKCGDLPlfEdNJcHX1Ju+VA4WtXgpRhWUsACjTf3 37 | UWPybQWO5WyJEAAAAddmVlaGFpdGNoQHYtbTEubXVjLmhhdXBlcnQuZXUBAgMEBQY= 38 | -----END OPENSSH PRIVATE KEY----- 39 | -------------------------------------------------------------------------------- /example/keys/key.txt: -------------------------------------------------------------------------------- 1 | # created: 2021-05-02T12:45:21+02:00 2 | # public key: age1wl3fqfvyml0c5eaj00j0frad4vhspgx9t8sngq4342j7rzjw4pqs80euxk 3 | AGE-SECRET-KEY-1J09D7VV27N4P7GFWPMUL4KVG7H2NU7TE27TSPSUQM6SLVNJF4L3SLVENJT 4 | -------------------------------------------------------------------------------- /example/root.passwd.age: -------------------------------------------------------------------------------- 1 | -----BEGIN AGE ENCRYPTED FILE----- 2 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwSzFIVzYxVzZacEVxMlhX 3 | WXBVelgvY0s3T2R4Sk1OWnliL1BIL2U3RjFrCk96Q2xBMWZkR1NxaERzYUZWOWdZ 4 | WnR6U1hVWXQ5am43VG9VdVlPdVJ5Wk0KLT4gc3NoLWVkMjU1MTkgYTZIN05nIG5z 5 | NHh2c1Z6dmkzVGwyQ1BXVDZKYTNwZWlTVzVhMFpGUUl5RjdrRU5xUjAKVW5ZeGtj 6 | cjlqMXBjemgyY2g4WTJPMmx6dHI1dHBKRTJjRldIQTB3OUd2QQotPiBzc2gtcnNh 7 | IDFORE5uQQpHMGw2N2Z1Q0FIVXRwRjlIdk9yVzIwbzQzWTZEUU5tMDZQUWRwSjF1 8 | MmVPUEJMYjBJdmtRQVlJZ1NHREY1cGtxCkNXa3lCYTBNQUdWZ1VrSVJQZURvSG9r 9 | b1VRN0lmMm9hT2FreTBPK3p4Kzd1VDFSUWJURkkyaFNsSjdVUUtQaUcKTmFnMkND 10 | R3VXT1JVdmxHVGdtR245bmNoZ1hqOUhJcDM3KzIrcFc4eW1EQ1BaQVZGeldReC8x 11 | ZFRETXhBb3BDMAp5UjhQTEdWWlVZbURwUHlMRnNNN3M1cVVCMnJKL1NTWEFUYmpm 12 | MHNyK3VnM3FFRUZlUDlDTC9LQVlSWElYZ0k5CnZSR243Q0x2UVFBcXJjbXhEMjhZ 13 | VjF5TzVtVkR2bVBxd09KVmxrRW1XNVFnSFBNbEJqcTFqSmlQeGdQUE1Tc1gKTklr 14 | ejY3MTRZZGFXeWFkQnNDWHdZNTJtZFI0d2d1NnVvVVpManZuUDk4a0poZk45YUhr 15 | Y0hOTGsyNzA2L1Q2cgpLaUdITTZqOEJsT0tDcUVTN2tNRkx2WlIvQ2dnNXNydXZ0 16 | RGNhcUlqNEZGN3Z5dWtjTUVyNHRZYzR3dGovOHpECmJKYi9vQllLRm9rbzZhTEtQ 17 | anIrNVk3RkVETU8rWmV6UnRMZnZ0ZXVDZHZaVVh5YjJzd2pla2tETjRMNEZIQUwK 18 | Ci0+IHUtZ3JlYXNlIDYgYyRgSFk7KSB2fEQpd2MKV0xScXdGZjdwcHhzUW56QmxB 19 | YldtUnJwMWtiZWNMRjVlRWpBamJqUGxhN0tQUGZNTm5xTCs2c3BKeFptaVZXZAp2 20 | c1FKcXpzYTJPQUFyMWVCSGcKLS0tIGx4MDByVEJFZkwrT3hHdzRiTnZ5UGErQ1hD 21 | NlMvQTdRR0V0NC9YZzdLaWMKleOomFCmM9ORdQjCtXzaMeZr6Bx8LX9WL58aShSO 22 | qYzMdLtFCC+TZRdzb0fuZYzTAg44K46OZsPHXjO5rsNpxLxIrdNaLpIiq3EHYJ7T 23 | ZOQ1vL7s/GCYyQqHSfpKmPCwjyhV84u8HBteTDoXWO2VbWUh6G6DUVyY6b/7zRej 24 | ib4npRmgvn6E 25 | -----END AGE ENCRYPTED FILE----- 26 | -------------------------------------------------------------------------------- /example/secrets-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | age.secrets."github-runner.token".file = ./github-runner.token.age; 3 | age.identityPaths = [ 4 | "/persist/secrets/id_ed25519" 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /example/secrets-plugin.nix: -------------------------------------------------------------------------------- 1 | let 2 | sshEd25519 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoPdkEfhcsmW6Lg86GMrEJZnYfFBb7fL9G/IXK7pDQd"; 3 | plugin = "age1unencrypted1k5fr0r"; 4 | in 5 | { 6 | "unencrypted.age".publicKeys = [ plugin sshEd25519 ]; 7 | } 8 | -------------------------------------------------------------------------------- /example/secrets.nix: -------------------------------------------------------------------------------- 1 | let 2 | age = "age1wl3fqfvyml0c5eaj00j0frad4vhspgx9t8sngq4342j7rzjw4pqs80euxk"; 3 | sshEd25519 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoPdkEfhcsmW6Lg86GMrEJZnYfFBb7fL9G/IXK7pDQd"; 4 | sshRsa = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDHd3yBYhZbBkMqycy/SOgx9d79TV5Q76czfkmKUKVzywUJbJCwZ4wMA+ff7QzBufZRoAWpGeQb+rssLQEOwR+VX30Fw7K92W4kK6BCF5phP6AUCo07e3vjGqKvgJ4+8LYvcCB17bYf8pJhb4GoOGLrlJNKbGZOhfYE0eGFu/fWsVybQasC2naieKfqHOwS9kNK0N1gSnWh0qu3Du9vBAbQBEE13mPGe4zEdIzTogM068xgKhfJUWqu1xCyVBVJNdz9Xw0NLaWQJon8YXDe62ifxLj3LgndwKm91cN9mmL0klcGB5O8K2mPE0ZGFMDuxdcllUchQgYXdNxEWB4EvpkvpQbiO+fjgMpHeEEiNPd/v06amSBqK+QlIGEkPAElELphPLiTJmHVqxc5NaffVc7F+zM+c3+aWB5Fqgk1jcnqm8HmlLEvPPT1S00c80SkY1V3lUUOirFlciP/pEivJejA5Yj2i1NEEELnrCdBw/xQ4jfesIxcqmBhxk5dWeBbfGs="; 5 | in 6 | { 7 | "root.passwd.age" = { 8 | publicKeys = [ age sshEd25519 sshRsa ]; 9 | extra = "Additional attributes are perfectly fine"; 10 | }; 11 | "github-runner.token.age".publicKeys = [ age sshEd25519 sshRsa ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "agenix": { 4 | "inputs": { 5 | "darwin": "darwin", 6 | "home-manager": "home-manager", 7 | "nixpkgs": [ 8 | "nixpkgs" 9 | ], 10 | "systems": "systems" 11 | }, 12 | "locked": { 13 | "lastModified": 1736955230, 14 | "narHash": "sha256-uenf8fv2eG5bKM8C/UvFaiJMZ4IpUFaQxk9OH5t/1gA=", 15 | "owner": "ryantm", 16 | "repo": "agenix", 17 | "rev": "e600439ec4c273cf11e06fe4d9d906fb98fa097c", 18 | "type": "github" 19 | }, 20 | "original": { 21 | "owner": "ryantm", 22 | "repo": "agenix", 23 | "type": "github" 24 | } 25 | }, 26 | "crane": { 27 | "locked": { 28 | "lastModified": 1741481578, 29 | "narHash": "sha256-JBTSyJFQdO3V8cgcL08VaBUByEU6P5kXbTJN6R0PFQo=", 30 | "owner": "ipetkov", 31 | "repo": "crane", 32 | "rev": "bb1c9567c43e4434f54e9481eb4b8e8e0d50f0b5", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "ipetkov", 37 | "repo": "crane", 38 | "type": "github" 39 | } 40 | }, 41 | "darwin": { 42 | "inputs": { 43 | "nixpkgs": [ 44 | "agenix", 45 | "nixpkgs" 46 | ] 47 | }, 48 | "locked": { 49 | "lastModified": 1700795494, 50 | "narHash": "sha256-gzGLZSiOhf155FW7262kdHo2YDeugp3VuIFb4/GGng0=", 51 | "owner": "lnl7", 52 | "repo": "nix-darwin", 53 | "rev": "4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d", 54 | "type": "github" 55 | }, 56 | "original": { 57 | "owner": "lnl7", 58 | "ref": "master", 59 | "repo": "nix-darwin", 60 | "type": "github" 61 | } 62 | }, 63 | "flake-utils": { 64 | "inputs": { 65 | "systems": "systems_2" 66 | }, 67 | "locked": { 68 | "lastModified": 1731533236, 69 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 70 | "owner": "numtide", 71 | "repo": "flake-utils", 72 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 73 | "type": "github" 74 | }, 75 | "original": { 76 | "owner": "numtide", 77 | "repo": "flake-utils", 78 | "type": "github" 79 | } 80 | }, 81 | "home-manager": { 82 | "inputs": { 83 | "nixpkgs": [ 84 | "agenix", 85 | "nixpkgs" 86 | ] 87 | }, 88 | "locked": { 89 | "lastModified": 1703113217, 90 | "narHash": "sha256-7ulcXOk63TIT2lVDSExj7XzFx09LpdSAPtvgtM7yQPE=", 91 | "owner": "nix-community", 92 | "repo": "home-manager", 93 | "rev": "3bfaacf46133c037bb356193bd2f1765d9dc82c1", 94 | "type": "github" 95 | }, 96 | "original": { 97 | "owner": "nix-community", 98 | "repo": "home-manager", 99 | "type": "github" 100 | } 101 | }, 102 | "nixpkgs": { 103 | "locked": { 104 | "lastModified": 1741379970, 105 | "narHash": "sha256-Wh7esNh7G24qYleLvgOSY/7HlDUzWaL/n4qzlBePpiw=", 106 | "owner": "nixos", 107 | "repo": "nixpkgs", 108 | "rev": "36fd87baa9083f34f7f5027900b62ee6d09b1f2f", 109 | "type": "github" 110 | }, 111 | "original": { 112 | "owner": "nixos", 113 | "ref": "nixos-unstable", 114 | "repo": "nixpkgs", 115 | "type": "github" 116 | } 117 | }, 118 | "root": { 119 | "inputs": { 120 | "agenix": "agenix", 121 | "crane": "crane", 122 | "flake-utils": "flake-utils", 123 | "nixpkgs": "nixpkgs", 124 | "rust-overlay": "rust-overlay" 125 | } 126 | }, 127 | "rust-overlay": { 128 | "inputs": { 129 | "nixpkgs": [ 130 | "nixpkgs" 131 | ] 132 | }, 133 | "locked": { 134 | "lastModified": 1741400194, 135 | "narHash": "sha256-tEpgT+q5KlGjHSm8MnINgTPErEl8YDzX3Eps8PVc09g=", 136 | "owner": "oxalica", 137 | "repo": "rust-overlay", 138 | "rev": "16b6045a232fea0e9e4c69e55a6e269607dd8e3f", 139 | "type": "github" 140 | }, 141 | "original": { 142 | "owner": "oxalica", 143 | "repo": "rust-overlay", 144 | "type": "github" 145 | } 146 | }, 147 | "systems": { 148 | "locked": { 149 | "lastModified": 1681028828, 150 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 151 | "owner": "nix-systems", 152 | "repo": "default", 153 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 154 | "type": "github" 155 | }, 156 | "original": { 157 | "owner": "nix-systems", 158 | "repo": "default", 159 | "type": "github" 160 | } 161 | }, 162 | "systems_2": { 163 | "locked": { 164 | "lastModified": 1681028828, 165 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 166 | "owner": "nix-systems", 167 | "repo": "default", 168 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 169 | "type": "github" 170 | }, 171 | "original": { 172 | "owner": "nix-systems", 173 | "repo": "default", 174 | "type": "github" 175 | } 176 | } 177 | }, 178 | "root": "root", 179 | "version": 7 180 | } 181 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A rust drop-in replacement for agenix"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | rust-overlay = { 8 | url = "github:oxalica/rust-overlay"; 9 | inputs.nixpkgs.follows = "nixpkgs"; 10 | }; 11 | crane.url = "github:ipetkov/crane"; 12 | agenix = { 13 | url = "github:ryantm/agenix"; 14 | inputs.nixpkgs.follows = "nixpkgs"; 15 | }; 16 | }; 17 | 18 | outputs = { self, nixpkgs, flake-utils, rust-overlay, crane, agenix }: 19 | let 20 | cargoTOML = lib.importTOML ./Cargo.toml; 21 | name = cargoTOML.package.name; 22 | 23 | lib = nixpkgs.lib; 24 | 25 | # Recursively merge a list of attribute sets. Following elements take 26 | # precedence over previous elements if they have conflicting keys. 27 | recursiveMerge = with lib; foldl recursiveUpdate { }; 28 | eachSystem = systems: f: flake-utils.lib.eachSystem systems (system: f (pkgsFor system)); 29 | defaultSystems = flake-utils.lib.defaultSystems; 30 | eachDefaultSystem = eachSystem defaultSystems; 31 | eachLinuxSystem = eachSystem (lib.filter (lib.hasSuffix "-linux") flake-utils.lib.defaultSystems); 32 | 33 | pkgsFor = system: import nixpkgs { 34 | inherit system; 35 | overlays = [ 36 | rust-overlay.overlays.default 37 | self.overlays.default 38 | (final: prev: { 39 | rustToolchain = final.rust-bin.fromRustupToolchainFile ./rust-toolchain; 40 | craneLib = (crane.mkLib prev).overrideToolchain final.rustToolchain; 41 | }) 42 | ]; 43 | config = { allowAliases = false; }; 44 | }; 45 | in 46 | recursiveMerge [ 47 | # 48 | # COMMON OUTPUTS FOR ALL SYSTEMS 49 | # 50 | (eachDefaultSystem (pkgs: rec { 51 | # `nix build` 52 | packages.${name} = pkgs.callPackage ./default.nix { }; 53 | packages.default = packages.${name}; 54 | 55 | # `nix run` 56 | apps.${name} = flake-utils.lib.mkApp { 57 | drv = packages.${name}; 58 | }; 59 | apps.default = apps.${name}; 60 | 61 | # Regenerate the roff and HTML manpages and commit the changes, if any 62 | apps.update-manpage = flake-utils.lib.mkApp { 63 | drv = pkgs.writeShellApplication { 64 | name = "update-manpage"; 65 | runtimeInputs = with pkgs; [ ronn git ]; 66 | text = '' 67 | ronn docs/ragenix.1.ronn 68 | 69 | git diff --quiet -- docs/ragenix.1* || changes=1 70 | git diff --staged --quiet -- docs/ragenix.1* || changes=1 71 | 72 | if [[ -z "''${changes:-}" ]]; then 73 | echo 'No changes to commit' 74 | else 75 | echo 'Committing changes' 76 | git commit -m "docs: update manpage" docs/ragenix.1* 77 | fi 78 | ''; 79 | }; 80 | }; 81 | 82 | # nix `check` 83 | checks.clippy = pkgs.craneLib.cargoClippy { 84 | inherit (self.packages.${pkgs.stdenv.system}.${name}) src cargoArtifacts; 85 | cargoClippyExtraArgs = "--all-targets --tests --examples -- -D clippy::pedantic -D warnings"; 86 | doInstallCargoArtifacts = false; 87 | RAGENIX_NIX_BIN_PATH = lib.getExe pkgs.nix; 88 | }; 89 | 90 | checks.fmt = pkgs.craneLib.cargoFmt { 91 | inherit (self.packages.${pkgs.stdenv.system}.${name}) src cargoArtifacts; 92 | doInstallCargoArtifacts = false; 93 | RAGENIX_NIX_BIN_PATH = lib.getExe pkgs.nix; 94 | }; 95 | 96 | checks.nixpkgs-fmt = pkgs.runCommand "check-nix-format" { } '' 97 | ${pkgs.nixpkgs-fmt}/bin/nixpkgs-fmt --check ${self} 98 | mkdir $out #sucess 99 | ''; 100 | 101 | checks.schema = pkgs.runCommand "emit-schema" { } '' 102 | set -euo pipefail 103 | ${pkgs.ragenix}/bin/ragenix --schema > "$TMPDIR/agenix.schema.json" 104 | ${pkgs.diffutils}/bin/diff '${self}/src/ragenix/agenix.schema.json' "$TMPDIR/agenix.schema.json" 105 | echo "Schema matches" 106 | mkdir "$out" 107 | ''; 108 | 109 | checks.agenix-symlink = pkgs.runCommand "check-agenix-symlink" { } '' 110 | set -euo pipefail 111 | agenix="$(readlink -f '${pkgs.ragenix}/bin/agenix')" 112 | ragenix="$(readlink -f '${pkgs.ragenix}/bin/ragenix')" 113 | 114 | if [[ "$agenix" == "$ragenix" ]]; then 115 | echo "agenix symlinked to ragenix" 116 | mkdir $out 117 | else 118 | echo "agenix doesn't resolve to ragenix" 119 | echo "agenix: $agenix" 120 | echo "ragenix: $ragenix" 121 | exit 1 122 | fi 123 | ''; 124 | 125 | checks.shell-files = pkgs.runCommand "check-shell-files" { } '' 126 | set -euo pipefail 127 | 128 | if [[ ! -e "${pkgs.ragenix}/share/bash-completion" ]]; then 129 | echo 'Failed to install bash completions' 130 | elif [[ ! -e "${pkgs.ragenix}/share/zsh" ]]; then 131 | echo 'Failed to install zsh completions' 132 | elif [[ ! -e "${pkgs.ragenix}/share/fish" ]]; then 133 | echo 'Failed to install fish completions' 134 | elif [[ ! -e "${pkgs.ragenix}/share/man/man1/ragenix.1.gz" ]]; then 135 | echo 'Failed to install manpage' 136 | else 137 | echo '${name} shell files installed successfully' 138 | mkdir $out 139 | exit 0 140 | fi 141 | 142 | exit 1 143 | ''; 144 | 145 | checks.decrypt-with-age = pkgs.runCommand "decrypt-with-age" { } '' 146 | set -euo pipefail 147 | 148 | # Required to prevent a panic in the locale_config crate 149 | # https://github.com/yaxitech/ragenix/issues/76 150 | ${lib.optionalString pkgs.stdenv.isDarwin ''export LANG="en_US.UTF-8"''} 151 | 152 | files=('${self}/example/root.passwd.age' '${self}/example/github-runner.token.age') 153 | 154 | for file in ''${files[@]}; do 155 | rage_output="$(${pkgs.rage}/bin/rage -i '${self}/example/keys/id_ed25519' -d "$file")" 156 | age_output="$(${pkgs.age}/bin/age -i '${self}/example/keys/id_ed25519' -d "$file")" 157 | 158 | if [[ "$rage_output" != "$age_output" ]]; then 159 | printf 'Decrypted plaintext for %s differs for rage and age' "$file" 160 | exit 1 161 | fi 162 | done 163 | 164 | echo "rage and age decryption of examples successful and equal" 165 | mkdir $out 166 | ''; 167 | 168 | checks.metadata = pkgs.runCommand "check-metadata" { } '' 169 | set -euo pipefail 170 | 171 | flakeDescription=${lib.escapeShellArg (import "${self}/flake.nix").description} 172 | packageDescription=${lib.escapeShellArg cargoTOML.package.description} 173 | if [[ "$flakeDescription" != "$packageDescription" ]]; then 174 | echo 'The descriptions given in flake.nix and Cargo.toml do not match' 175 | exit 1 176 | fi 177 | 178 | flakePackageName=${pkgs.ragenix.pname} 179 | cargoName=${cargoTOML.package.name} 180 | if [[ "$flakePackageName" != "$cargoName" ]]; then 181 | echo 'The package name given in flake.nix and Cargo.toml do not match' 182 | exit 1 183 | fi 184 | 185 | echo 'All metadata checks completed successfully' 186 | mkdir $out # success 187 | ''; 188 | 189 | # Make sure the roff and HTML manpages are up-to-date 190 | checks.manpage = pkgs.runCommand "check-manpage" 191 | { 192 | buildInputs = with pkgs; [ ronn diffutils ]; 193 | } '' 194 | set -euo pipefail 195 | 196 | echo "Generate roff and HTML manpage" 197 | ln -s ${self}/docs/ragenix.1.ronn . 198 | ronn ragenix.1.ronn 199 | 200 | echo "roff: strip date" 201 | tail -n '+5' ${self}/docs/ragenix.1 > ragenix.1.old 202 | tail -n '+5' ragenix.1 > ragenix.1.new 203 | 204 | diff -u ragenix.1.{old,new} > diff \ 205 | || (printf "roff: error, not up-to-date:\n\n%s\n" "$(cat diff)" >&2 && exit 1) 206 | 207 | echo "html: strip date" 208 | grep -v "
  • " ${self}/docs/ragenix.1.html > ragenix.1.html.old 209 | grep -v "
  • " ragenix.1.html > ragenix.1.html.new 210 | 211 | diff -u ragenix.1.html.{old,new} > diff \ 212 | || (printf "html: error, not up-to-date:\n\n%s\n" "$(cat diff)" >&2 && exit 1) 213 | 214 | echo 'Manpage is up-to-date' 215 | mkdir -p $out 216 | ''; 217 | 218 | # `nix develop` 219 | devShells.default = pkgs.mkShell { 220 | name = "${name}-dev-shell"; 221 | 222 | inputsFrom = [ self.packages."${pkgs.stdenv.system}".ragenix ]; 223 | 224 | nativeBuildInputs = with pkgs; [ 225 | ronn 226 | rust-analyzer 227 | ]; 228 | 229 | RAGENIX_NIX_BIN_PATH = lib.getExe pkgs.nix; 230 | 231 | RUST_SRC_PATH = "${pkgs.rustToolchain}/lib/rustlib/src/rust/library"; 232 | 233 | shellHook = '' 234 | export PATH=$PWD/target/debug:$PATH 235 | ''; 236 | }; 237 | }) 238 | ) 239 | # 240 | # PACKAGES SPECIFIC TO LINUX SYSTEMS 241 | # 242 | (eachLinuxSystem (pkgs: { 243 | packages.ragenix-static = self.packages.${pkgs.stdenv.system}.ragenix.override { 244 | target = pkgs.pkgsStatic.stdenv.targetPlatform.rust.cargoShortTarget; 245 | }; 246 | packages.default = self.packages.${pkgs.stdenv.system}.ragenix-static; 247 | })) 248 | # 249 | # CHECKS SPECIFIC TO LINUX SYSTEMS 250 | # 251 | (eachLinuxSystem (pkgs: { 252 | checks.nixos-module = 253 | let 254 | pythonTest = import ("${nixpkgs}/nixos/lib/testing-python.nix") { 255 | inherit (pkgs.stdenv.hostPlatform) system; 256 | }; 257 | secretsConfig = import "${self}/example/secrets-configuration.nix"; 258 | secretPath = "/run/agenix/github-runner.token"; 259 | ageIdentitiesConfig = { lib, ... }: { 260 | # XXX: This is insecure and copies your private key plaintext to the Nix store 261 | # NEVER DO THIS IN YOUR CONFIG! 262 | age.identityPaths = lib.mkForce [ "${self}/example/keys/id_ed25519" ]; 263 | }; 264 | in 265 | pythonTest.runTest { 266 | name = "ragenix-nixos-module"; 267 | 268 | nodes.machine.imports = [ 269 | self.nixosModules.age 270 | secretsConfig 271 | ageIdentitiesConfig 272 | ]; 273 | 274 | testScript = '' 275 | machine.start() 276 | machine.wait_for_unit("multi-user.target") 277 | machine.succeed('test -e "${secretPath}"') 278 | machine.succeed( 279 | '[[ "$(cat "${secretPath}")" == "wurzelpfropf!" ]] || exit 1' 280 | ) 281 | machine.succeed( 282 | '[[ "$(stat -c "%a" "${secretPath}")" == "400" ]] || exit 1' 283 | ) 284 | machine.succeed( 285 | '[[ "$(stat -c "%U" "${secretPath}")" == "root" ]] || exit 1' 286 | ) 287 | machine.succeed( 288 | '[[ "$(stat -c "%G" "${secretPath}")" == "root" ]] || exit 1' 289 | ) 290 | ''; 291 | }; 292 | 293 | checks.tests-recursive-nix = (pkgs.ragenix.override { 294 | enableRecursiveNixTests = true; 295 | }).overrideAttrs (_: { 296 | name = "tests-recursive-nix"; 297 | }); 298 | 299 | checks.rekey = pkgs.runCommand "run-rekey" 300 | { 301 | requiredSystemFeatures = [ "recursive-nix" ]; 302 | } 303 | '' 304 | set -euo pipefail 305 | cp -r '${self}/example/.' "$TMPDIR" 306 | chmod 600 *.age 307 | 308 | ln -s "${self}/example/keys" "$TMPDIR/.ssh" 309 | export HOME="$TMPDIR" 310 | 311 | ${pkgs.ragenix}/bin/ragenix --rekey 312 | ${pkgs.agenix}/bin/agenix --rekey 313 | 314 | mkdir "$out" 315 | ''; 316 | 317 | checks.age-plugin = 318 | let 319 | rageExamplePlugin = pkgs.rage.overrideAttrs (_: rec { 320 | pname = "age-plugin-unencrypted"; 321 | doCheck = false; 322 | cargoBuildFlags = [ "--example" pname ]; 323 | installPhase = '' 324 | set -euo pipefail 325 | find target/**/release/examples -name ${pname} \ 326 | -exec install -D {} $out/bin/${pname} \; 327 | # strip.sh uses unset variables 328 | set +u 329 | ''; 330 | }); 331 | plugins = [ rageExamplePlugin ]; 332 | ragenixWithPlugins = pkgs.ragenix.override { inherit plugins; }; 333 | pluginsSearchPath = lib.strings.makeBinPath plugins; 334 | in 335 | pkgs.runCommand "age-plugin" 336 | { 337 | buildInputs = [ pkgs.rage ragenixWithPlugins ]; 338 | requiredSystemFeatures = [ "recursive-nix" ]; 339 | } 340 | '' 341 | set -euo pipefail 342 | cp -r '${self}/example/.' "$TMPDIR" 343 | cd "$TMPDIR" 344 | 345 | # Encrypt with ragenix 346 | echo 'wurzelpfropf' | ragenix --rules ./secrets-plugin.nix --editor - --edit unencrypted.age 347 | 348 | # Decrypt with rage 349 | decrypted="$(PATH="${pluginsSearchPath}:$PATH" rage -i '${self}/example/keys/example_plugin_key.txt' -d unencrypted.age)" 350 | if [[ "$decrypted" != "wurzelpfropf" ]]; then 351 | echo 'Unexpected value for decryption with plugin' 352 | exit 1 353 | fi 354 | 355 | # Rekey 356 | ragenix --rules ./secrets-plugin.nix -i '${self}/example/keys/example_plugin_key.txt' --rekey 357 | 358 | mkdir $out # success 359 | ''; 360 | }) 361 | ) 362 | # 363 | # SYSTEM-INDEPENDENT OUTPUTS 364 | # 365 | { 366 | # Passthrough the agenix NixOS, Darwin and Home Manager modules 367 | inherit (agenix) nixosModules darwinModules homeManagerModules; 368 | 369 | # Overlay to add ragenix and replace agenix 370 | overlays.default = _final: prev: rec { 371 | ragenix = self.packages.${prev.stdenv.hostPlatform.system}.default; 372 | agenix = ragenix; 373 | }; 374 | } 375 | ]; 376 | } 377 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | profile = "minimal" 3 | channel = "stable" 4 | components = [ "rust-src", "clippy-preview", "rustfmt-preview" ] 5 | targets = [ 6 | "aarch64-apple-darwin", 7 | "aarch64-unknown-linux-gnu", 8 | "x86_64-apple-darwin", 9 | "x86_64-unknown-linux-gnu", 10 | "aarch64-unknown-linux-musl", 11 | "x86_64-unknown-linux-musl", 12 | ] 13 | -------------------------------------------------------------------------------- /src/age.rs: -------------------------------------------------------------------------------- 1 | //! Uses the age crate to encrypt, decrypt and rekey files 2 | 3 | use std::{ 4 | convert::Into, 5 | fs, 6 | io::{self, BufReader}, 7 | path::Path, 8 | }; 9 | 10 | use age::{ 11 | armor::{ArmoredReader, ArmoredWriter, Format}, 12 | cli_common::{ 13 | file_io::{InputReader, OutputFormat, OutputWriter}, 14 | StdinGuard, 15 | }, 16 | decryptor::RecipientsDecryptor, 17 | }; 18 | 19 | use color_eyre::{ 20 | eyre::{eyre, Result, WrapErr}, 21 | Help, 22 | }; 23 | 24 | use tempfile::NamedTempFile; 25 | 26 | fn get_age_decryptor>( 27 | path: P, 28 | ) -> Result>>> { 29 | let s = path.as_ref().to_str().map(std::string::ToString::to_string); 30 | let input_reader = InputReader::new(s)?; 31 | let decryptor = age::Decryptor::new(ArmoredReader::new(input_reader))?; 32 | 33 | match decryptor { 34 | age::Decryptor::Passphrase(_) => { 35 | Err(eyre!(String::from("Agenix does not support passphrases"))) 36 | } 37 | age::Decryptor::Recipients(decryptor) => Ok(decryptor), 38 | } 39 | } 40 | 41 | /// Parses a recipient from a string. 42 | /// [Copied from str4d/rage (ASL-2.0)]( 43 | /// https://github.com/str4d/rage/blob/85c0788dc511f1410b4c1811be6b8904d91f85db/rage/src/bin/rage/main.rs) 44 | fn parse_recipient( 45 | s: &str, 46 | recipients: &mut Vec>, 47 | plugin_recipients: &mut Vec, 48 | ) -> Result<()> { 49 | if let Ok(pk) = s.parse::() { 50 | recipients.push(Box::new(pk)); 51 | Ok(()) 52 | } else if let Some(pk) = { s.parse::().ok().map(Box::new) } { 53 | recipients.push(pk); 54 | Ok(()) 55 | } else if let Ok(pk) = s.parse::() { 56 | plugin_recipients.push(pk); 57 | Ok(()) 58 | } else { 59 | Err(eyre!("Invalid recipient: {}", s)) 60 | .with_suggestion(|| "Make sure you use an ssh-ed25519, ssh-rsa or an X25519 public key, alternatively install an age plugin which supports your key") 61 | } 62 | } 63 | 64 | /// Returns the file paths to `$HOME/.ssh/{id_rsa,id_ed25519}` if each exists 65 | fn get_default_identity_paths() -> Result> { 66 | let home_path = home::home_dir().ok_or_else(|| eyre!("Could not determine home directory"))?; 67 | let ssh_dir = home_path.join(".ssh"); 68 | 69 | let id_rsa = ssh_dir.join("id_rsa"); 70 | let id_ed25519 = ssh_dir.join("id_ed25519"); 71 | 72 | let filtered_paths = [id_rsa, id_ed25519] 73 | .iter() 74 | .filter(|x| x.exists()) 75 | .filter_map(|x| x.to_str()) 76 | .map(std::string::ToString::to_string) 77 | .collect(); 78 | 79 | Ok(filtered_paths) 80 | } 81 | 82 | /// Searches plugins and transforms `age::plugin::Recipient` to `age::Recipients` 83 | fn merge_plugin_recipients_and_recipients( 84 | recipients: &mut Vec>, 85 | plugin_recipients: &[age::plugin::Recipient], 86 | ) -> Result<()> { 87 | // Get names of all required plugins from the recipients 88 | let mut plugin_names = plugin_recipients 89 | .iter() 90 | .map(age::plugin::Recipient::plugin) 91 | .collect::>(); 92 | plugin_names.sort_unstable(); 93 | plugin_names.dedup(); 94 | 95 | // Add to recipients 96 | for plugin_name in plugin_names { 97 | recipients.push(Box::new(age::plugin::RecipientPluginV1::new( 98 | plugin_name, 99 | plugin_recipients, 100 | // Rage allows for symmetric encryption, but this is not actually something which fits 101 | // into ragenix's design 102 | &Vec::::new(), 103 | age::cli_common::UiCallbacks, 104 | )?)); 105 | } 106 | Ok(()) 107 | } 108 | 109 | /// Get all the identities from the given paths and the default locations. 110 | /// 111 | /// Default locations are `$HOME/.ssh/id_rsa` and `$HOME/.ssh/id_ed25519`. 112 | pub(crate) fn get_identities(identity_paths: &[String]) -> Result>> { 113 | let mut identities: Vec = identity_paths.to_vec(); 114 | let mut default_identities = get_default_identity_paths()?; 115 | 116 | identities.append(&mut default_identities); 117 | 118 | if identities.is_empty() { 119 | Err(eyre!("No usable identity or identities")) 120 | } else { 121 | // Error out if an identity is tried to be read from stdin 122 | let mut stdin_guard = StdinGuard::new(true); 123 | Ok(age::cli_common::read_identities( 124 | identities, 125 | None, 126 | &mut stdin_guard, 127 | )?) 128 | } 129 | } 130 | 131 | /// Decrypt an age-encrypted file to a plaintext file. 132 | /// 133 | /// The output file is created with a mode of `0o600`. 134 | pub(crate) fn decrypt>( 135 | input_file: P, 136 | output_file: P, 137 | identities: &[Box], 138 | ) -> Result<()> { 139 | let output_file_mode: u32 = 0o600; 140 | let decryptor = get_age_decryptor(input_file)?; 141 | decryptor 142 | .decrypt(identities.iter().map(|i| i.as_ref() as &dyn age::Identity)) 143 | .map_err(Into::into) 144 | .and_then(|mut plaintext_reader| { 145 | let output = output_file 146 | .as_ref() 147 | .to_str() 148 | .map(std::string::ToString::to_string); 149 | let mut ciphertext_writer = 150 | OutputWriter::new(output, true, OutputFormat::Unknown, output_file_mode, false)?; 151 | io::copy(&mut plaintext_reader, &mut ciphertext_writer)?; 152 | Ok(()) 153 | }) 154 | } 155 | 156 | /// Encrypt a plaintext file to an age-encrypted file. 157 | /// 158 | /// The output file is created with a mode of `0o644`. 159 | pub(crate) fn encrypt>( 160 | input_file: P, 161 | output_file: P, 162 | public_keys: &[String], 163 | ) -> Result<()> { 164 | let output_file_mode: u32 = 0o644; 165 | let mut input = InputReader::new(input_file.as_ref().to_str().map(str::to_string))?; 166 | 167 | // Create an output to the user-requested location. 168 | let output = OutputWriter::new( 169 | output_file.as_ref().to_str().map(str::to_string), 170 | true, 171 | OutputFormat::Text, 172 | output_file_mode, 173 | false, 174 | )?; 175 | 176 | let mut recipients: Vec> = vec![]; 177 | let mut plugin_recipients: Vec = vec![]; 178 | 179 | for pubkey in public_keys { 180 | parse_recipient(pubkey, &mut recipients, &mut plugin_recipients)?; 181 | } 182 | 183 | merge_plugin_recipients_and_recipients(&mut recipients, &plugin_recipients)?; 184 | 185 | let encryptor = 186 | age::Encryptor::with_recipients(recipients).ok_or(eyre!("Missing recipients"))?; 187 | 188 | let mut output = encryptor 189 | .wrap_output( 190 | ArmoredWriter::wrap_output(output, Format::AsciiArmor) 191 | .wrap_err("Failed to wrap output with age::ArmoredWriter")?, 192 | ) 193 | .map_err(|err| eyre!(err))?; 194 | 195 | io::copy(&mut input, &mut output)?; 196 | output.finish().and_then(ArmoredWriter::finish)?; 197 | 198 | Ok(()) 199 | } 200 | 201 | /// Re-encrypt a file in memory using the given public keys. 202 | /// 203 | /// Decrypts the file and stream-encrypts the contents into a temporary 204 | /// file. Afterward, the temporary file replaces the file at the input path. 205 | /// 206 | /// Plaintext is never written to persistent storage but only processed in memory. 207 | pub(crate) fn rekey>( 208 | file: P, 209 | identities: &[Box], 210 | public_keys: &[String], 211 | ) -> Result<()> { 212 | let mut recipients: Vec> = vec![]; 213 | let mut plugin_recipients: Vec = vec![]; 214 | 215 | for pubkey in public_keys { 216 | parse_recipient(pubkey, &mut recipients, &mut plugin_recipients)?; 217 | } 218 | let decryptor = get_age_decryptor(&file)?; 219 | decryptor 220 | .decrypt(identities.iter().map(|i| i.as_ref() as &dyn age::Identity)) 221 | .map_err(Into::into) 222 | .and_then(|mut plaintext_reader| { 223 | // Create a temporary file to write the re-encrypted data to 224 | let outfile = NamedTempFile::new()?; 225 | 226 | // Merge plugin recipients 227 | merge_plugin_recipients_and_recipients(&mut recipients, &plugin_recipients)?; 228 | 229 | // Create an encryptor for the (new) recipients to encrypt the file for 230 | let encryptor = 231 | age::Encryptor::with_recipients(recipients).ok_or(eyre!("Missing recipients"))?; 232 | let mut ciphertext_writer = encryptor 233 | .wrap_output( 234 | ArmoredWriter::wrap_output(&outfile, Format::AsciiArmor) 235 | .wrap_err("Failed to wrap output with age::ArmoredWriter")?, 236 | ) 237 | .map_err(|err| eyre!(err))?; 238 | 239 | // Do the re-encryption 240 | io::copy(&mut plaintext_reader, &mut ciphertext_writer)?; 241 | ciphertext_writer.finish().and_then(ArmoredWriter::finish)?; 242 | 243 | // Re-encrpytion is done, now replace the original file 244 | fs::copy(outfile, file)?; 245 | 246 | Ok(()) 247 | }) 248 | } 249 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::clone::Clone; 2 | use std::ffi::OsString; 3 | 4 | use clap::{ 5 | crate_authors, crate_description, crate_name, crate_version, Arg, ArgAction, ArgGroup, Command, 6 | ValueHint, 7 | }; 8 | 9 | #[allow(dead_code)] // False positive 10 | #[derive(Debug, Clone)] 11 | pub(crate) struct Opts { 12 | pub edit: Option, 13 | pub editor: Option, 14 | pub identities: Option>, 15 | pub rekey: bool, 16 | pub rules: String, 17 | pub schema: bool, 18 | pub verbose: bool, 19 | } 20 | 21 | fn build() -> Command { 22 | Command::new(crate_name!()) 23 | .version(crate_version!()) 24 | .author(crate_authors!()) 25 | .about(crate_description!()) 26 | .arg( 27 | Arg::new("edit") 28 | .help("edits the age-encrypted FILE using $EDITOR") 29 | .long("edit") 30 | .short('e') 31 | .num_args(1) 32 | .value_name("FILE") 33 | .requires("editor") 34 | .value_hint(ValueHint::FilePath), 35 | ) 36 | .arg( 37 | Arg::new("rekey") 38 | .help("re-encrypts all secrets with specified recipients") 39 | .long("rekey") 40 | .short('r') 41 | .action(ArgAction::SetTrue), 42 | ) 43 | .arg( 44 | Arg::new("identity") 45 | .help("private key to use when decrypting") 46 | .long("identity") 47 | .short('i') 48 | .num_args(1..) 49 | .value_name("PRIVATE_KEY") 50 | .required(false) 51 | .value_hint(ValueHint::FilePath), 52 | ) 53 | .arg( 54 | Arg::new("verbose") 55 | .help("verbose output") 56 | .long("verbose") 57 | .short('v') 58 | .action(ArgAction::SetTrue), 59 | ) 60 | .arg( 61 | Arg::new("schema") 62 | .help("Prints the JSON schema Agenix rules have to conform to") 63 | .long("schema") 64 | .short('s') 65 | .action(ArgAction::SetTrue), 66 | ) 67 | .group( 68 | ArgGroup::new("action") 69 | .args(["edit", "rekey", "schema"]) 70 | .required(true), 71 | ) 72 | .arg( 73 | Arg::new("editor") 74 | .help("editor to use when editing FILE") 75 | .long("editor") 76 | .num_args(1) 77 | .env("EDITOR") 78 | .value_name("EDITOR") 79 | .value_hint(ValueHint::CommandString), 80 | ) 81 | .arg( 82 | Arg::new("rules") 83 | .help("path to Nix file specifying recipient public keys") 84 | .long("rules") 85 | .num_args(1) 86 | .env("RULES") 87 | .value_name("RULES") 88 | .default_value("./secrets.nix") 89 | .value_hint(ValueHint::FilePath), 90 | ) 91 | } 92 | 93 | /// Parse the command line arguments using Clap 94 | #[allow(dead_code)] // False positive 95 | pub(crate) fn parse_args(itr: I) -> Opts 96 | where 97 | I: IntoIterator, 98 | T: Into + Clone, 99 | { 100 | let app = build(); 101 | 102 | let matches = app.get_matches_from(itr); 103 | 104 | Opts { 105 | edit: matches.get_one::("edit").cloned(), 106 | editor: matches.get_one::("editor").cloned(), 107 | identities: matches 108 | .get_many::("identity") 109 | .map(|vals| vals.cloned().collect::>()), 110 | rekey: matches.get_flag("rekey"), 111 | rules: matches 112 | .get_one::("rules") 113 | .cloned() 114 | .expect("Should never happen"), 115 | schema: matches.get_flag("schema"), 116 | verbose: matches.get_flag("verbose"), 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::eyre::{eyre, Result}; 2 | use std::{env, fs, path::Path, process}; 3 | 4 | mod age; 5 | mod cli; 6 | mod ragenix; 7 | mod util; 8 | 9 | fn main() -> Result<()> { 10 | color_eyre::install()?; 11 | let opts = cli::parse_args(env::args()); 12 | 13 | if opts.schema { 14 | print!("{}", ragenix::AGENIX_JSON_SCHEMA_STRING); 15 | } else { 16 | if let Err(report) = ragenix::validate_rules_file(&opts.rules) { 17 | eprintln!( 18 | "error: secrets rules are invalid: '{}'\n{report}", 19 | &opts.rules 20 | ); 21 | process::exit(1); 22 | } 23 | 24 | let rules = ragenix::parse_rules(&opts.rules)?; 25 | if opts.verbose { 26 | println!("{rules:#?}"); 27 | } 28 | 29 | let identities = opts.identities.unwrap_or_default(); 30 | 31 | if let Some(path) = &opts.edit { 32 | let path_normalized = util::normalize_path(Path::new(path)); 33 | let edit_path = std::env::current_dir() 34 | .and_then(fs::canonicalize) 35 | .map(|p| p.join(path_normalized))?; 36 | let rule = rules 37 | .into_iter() 38 | .find(|x| x.path == edit_path) 39 | .ok_or_else(|| eyre!("No rule for the given file {}", path))?; 40 | 41 | // `EDITOR`/`--editor` is mandatory if action is `--edit` 42 | let editor = &opts.editor.unwrap(); 43 | ragenix::edit(&rule, &identities, editor, &mut std::io::stdout())?; 44 | } else if opts.rekey { 45 | ragenix::rekey(&rules, &identities, &mut std::io::stdout())?; 46 | } 47 | } 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /src/ragenix/agenix.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "description": "Agenix secrets rules schema", 4 | "type": "object", 5 | "properties": {}, 6 | "additionalProperties": { 7 | "type": "object", 8 | "description": "An age-encrypted file", 9 | "required": [ 10 | "publicKeys" 11 | ], 12 | "properties": { 13 | "publicKeys": { 14 | "type": "array", 15 | "minItems": 1, 16 | "items": { 17 | "type": "string", 18 | "description": "An age-compatible recipient, e.g., an ed25519 SSH public key" 19 | }, 20 | "uniqueItems": true 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ragenix/mod.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::{ 2 | eyre::{eyre, Result, WrapErr}, 3 | Help, SectionExt, 4 | }; 5 | use jsonschema::JSONSchema; 6 | use lazy_static::lazy_static; 7 | use std::{ 8 | fs::{self, OpenOptions}, 9 | io::{self, Write}, 10 | os::unix::prelude::{OpenOptionsExt, PermissionsExt}, 11 | path::{Path, PathBuf}, 12 | process, 13 | }; 14 | 15 | use crate::{age, util}; 16 | 17 | pub(crate) static AGENIX_JSON_SCHEMA_STRING: &str = std::include_str!("agenix.schema.json"); 18 | 19 | lazy_static! { 20 | static ref AGENIX_JSON_SCHEMA: serde_json::Value = 21 | serde_json::from_str(AGENIX_JSON_SCHEMA_STRING).expect("Valid schema!"); 22 | } 23 | 24 | /// Reads the rules file using Nix to output the attribute set as a JSON string. 25 | /// Return value is parsed into a serde JSON value. 26 | fn nix_rules_to_json>(path: P) -> Result { 27 | let rules_filepath = path.as_ref().to_string_lossy(); 28 | 29 | let nix_binary = env!("RAGENIX_NIX_BIN_PATH"); 30 | let output = process::Command::new(nix_binary) 31 | .arg("--extra-experimental-features") 32 | .arg("nix-command") 33 | .arg("eval") 34 | .arg("--no-net") 35 | .arg("--json") 36 | .arg("--file") 37 | .arg(&*rules_filepath) 38 | .output() 39 | .wrap_err("failed to execute nix")?; 40 | 41 | if !output.status.success() { 42 | let stderr = String::from_utf8_lossy(&output.stderr); 43 | return Err(eyre!("Failed to read {} as JSON", rules_filepath)) 44 | .with_section(|| stderr.trim().to_string().header("Stderr:")); 45 | } 46 | 47 | let val = serde_json::from_slice(&output.stdout)?; 48 | Ok(val) 49 | } 50 | 51 | /// Open a file for editing. 52 | /// 53 | /// [Copied from cole-h/agenix-rs (ASL 2.0 / MIT)]( 54 | /// https://github.com/cole-h/agenix-rs/blob/8e0554179f1ac692fb865c256e9d7fb91b6a692d/src/cli.rs#L236-L257) 55 | fn editor_hook(path: &Path, editor: &str) -> Result<()> { 56 | if util::is_stdin(editor) { 57 | let mut src = io::stdin(); 58 | let mut dst = OpenOptions::new() 59 | .mode(0o600) 60 | .create(true) 61 | .truncate(true) 62 | .write(true) 63 | .open(path)?; 64 | io::copy(&mut src, &mut dst)?; 65 | } else { 66 | let (editor, args) = util::split_editor(editor)?; 67 | let cmd = process::Command::new(&editor) 68 | .args(args.unwrap_or_default()) 69 | .arg(path) 70 | .stdin(process::Stdio::inherit()) 71 | .stdout(process::Stdio::inherit()) 72 | .stderr(process::Stdio::piped()) 73 | .output() 74 | .wrap_err_with(|| format!("Failed to spawn editor '{}'", &editor))?; 75 | 76 | if !cmd.status.success() { 77 | let stderr = String::from_utf8_lossy(&cmd.stderr); 78 | 79 | return Err(eyre!( 80 | "Editor '{}' exited with non-zero status code", 81 | &editor 82 | )) 83 | .with_section(|| stderr.trim().to_string().header("Stderr:")); 84 | } 85 | } 86 | 87 | Ok(()) 88 | } 89 | 90 | #[derive(Debug)] 91 | pub(crate) struct RagenixRule { 92 | pub path: PathBuf, 93 | pub public_keys: Vec, 94 | } 95 | 96 | /// Validate conformance of the passed path to the JSON schema [`AGENIX_JSON_SCHEMA`]. 97 | pub(crate) fn validate_rules_file>(path: P) -> Result<()> { 98 | if !path.as_ref().exists() { 99 | return Err(eyre!("{} does not exist!", path.as_ref().to_string_lossy())); 100 | } 101 | 102 | let instance = nix_rules_to_json(&path)?; 103 | let compiled = JSONSchema::options() 104 | .with_draft(jsonschema::Draft::Draft7) 105 | .compile(&AGENIX_JSON_SCHEMA)?; 106 | let result = compiled.validate(&instance); 107 | 108 | if let Err(errors) = result { 109 | let error_msg = errors 110 | .into_iter() 111 | .map(|err| format!(" - {}: {err}", err.instance_path)) 112 | .collect::>() 113 | .join("\n"); 114 | Err(eyre!(error_msg)) 115 | } else { 116 | Ok(()) 117 | } 118 | } 119 | 120 | /// Parse the given rules file path. 121 | /// 122 | /// This method assumes that the passed file adheres to the [`AGENIX_JSON_SCHEMA`]. 123 | pub(crate) fn parse_rules>(rules_path: P) -> Result> { 124 | let instance = nix_rules_to_json(&rules_path)?; 125 | 126 | // It's fine to force unwrap here as we validated the JSON schema 127 | let mut rules: Vec = Vec::new(); 128 | for (rel_path, val) in instance.as_object().unwrap() { 129 | let dir = fs::canonicalize(rules_path.as_ref().parent().unwrap())?; 130 | let p = dir.join(rel_path); 131 | let public_keys = val.as_object().unwrap()["publicKeys"] 132 | .as_array() 133 | .unwrap() 134 | .iter() 135 | .map(|x| x.as_str().unwrap().to_string()) 136 | .collect(); 137 | let rule = RagenixRule { 138 | path: p, 139 | public_keys, 140 | }; 141 | rules.push(rule); 142 | } 143 | 144 | Ok(rules) 145 | } 146 | 147 | /// Rekey all entries with the specified public keys 148 | pub(crate) fn rekey( 149 | entries: &[RagenixRule], 150 | identities: &[String], 151 | mut writer: impl Write, 152 | ) -> Result<()> { 153 | let identities = age::get_identities(identities)?; 154 | for entry in entries { 155 | if entry.path.exists() { 156 | writeln!(writer, "Rekeying {}", entry.path.display())?; 157 | age::rekey(&entry.path, &identities, &entry.public_keys)?; 158 | } else { 159 | writeln!(writer, "Does not exist, ignored: {}", entry.path.display())?; 160 | } 161 | } 162 | Ok(()) 163 | } 164 | 165 | /// Edit/create an age-encrypted file 166 | /// 167 | /// If the file doesn't exist yet, a new file is created and opened in `editor`. 168 | pub(crate) fn edit( 169 | entry: &RagenixRule, 170 | identity_paths: &[String], 171 | editor: &str, 172 | mut writer: impl Write, 173 | ) -> Result<()> { 174 | let dir = tempfile::tempdir()?; 175 | fs::set_permissions(&dir, PermissionsExt::from_mode(0o700))?; 176 | 177 | let input_path = dir.path().join("input"); 178 | let output_path = &entry.path; 179 | 180 | if !output_path.exists() || util::is_stdin(editor) { 181 | // If the target file does not yet exist, we don't have to decrypt the result for editing. 182 | // Likewise, if we're reading from stdin, we're going to replace the target file completely. 183 | fs::File::create(&input_path)?; 184 | editor_hook(&input_path, editor)?; 185 | } else { 186 | // If the file already exists, first decrypt it, hash it, open it in `editor`, 187 | // hash the result, and if the hashes are equal, return. 188 | let identities = age::get_identities(identity_paths)?; 189 | age::decrypt(output_path, &input_path, &identities)?; 190 | 191 | // Calculate hash before editing 192 | let pre_edit_hash = util::sha256(&input_path)?; 193 | 194 | // Prompt user to edit file 195 | editor_hook(&input_path, editor)?; 196 | 197 | // Calculate hash after editing 198 | let post_edit_hash = util::sha256(&input_path)?; 199 | 200 | // Return if the file wasn't changed when editing 201 | if pre_edit_hash == post_edit_hash { 202 | writeln!( 203 | writer, 204 | "{} wasn't changed, skipping re-encryption.", 205 | output_path.display() 206 | )?; 207 | return Ok(()); 208 | } 209 | } 210 | 211 | age::encrypt(input_path, output_path.clone(), &entry.public_keys)?; 212 | 213 | Ok(()) 214 | } 215 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Util functions 2 | 3 | use std::{ 4 | fs::File, 5 | io, 6 | path::{Component, Path, PathBuf}, 7 | }; 8 | 9 | use color_eyre::eyre::{eyre, Result}; 10 | use sha2::{Digest, Sha256}; 11 | 12 | /// Normalize a path, removing things like `.` and `..`. 13 | /// 14 | /// CAUTION: This does not resolve symlinks (unlike 15 | /// [`std::fs::canonicalize`]). This may cause incorrect or surprising 16 | /// behavior at times. This should be used carefully. Unfortunately, 17 | /// [`std::fs::canonicalize`] can be hard to use correctly, since it can often 18 | /// fail, or on Windows returns annoying device paths. This is a problem Cargo 19 | /// needs to improve on. 20 | /// 21 | /// [Copied from Cargo (ASL 2.0 / MIT)]( 22 | /// https://github.com/rust-lang/cargo/blob/58a961314437258065e23cb6316dfc121d96fb71/crates/cargo-util/src/paths.rs#L81-L106) 23 | #[allow(clippy::option_if_let_else)] 24 | #[allow(clippy::cloned_instead_of_copied)] 25 | pub(crate) fn normalize_path(path: &Path) -> PathBuf { 26 | let mut components = path.components().peekable(); 27 | let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { 28 | components.next(); 29 | PathBuf::from(c.as_os_str()) 30 | } else { 31 | PathBuf::new() 32 | }; 33 | 34 | for component in components { 35 | match component { 36 | Component::Prefix(..) => unreachable!(), 37 | Component::RootDir => { 38 | ret.push(component.as_os_str()); 39 | } 40 | Component::CurDir => {} 41 | Component::ParentDir => { 42 | ret.pop(); 43 | } 44 | Component::Normal(c) => { 45 | ret.push(c); 46 | } 47 | } 48 | } 49 | ret 50 | } 51 | 52 | /// Hash a file using SHA-256 53 | pub(crate) fn sha256>(path: P) -> Result> { 54 | let mut file = File::open(path)?; 55 | let mut hasher = Sha256::new(); 56 | io::copy(&mut file, &mut hasher)?; 57 | Ok(hasher.finalize().to_vec()) 58 | } 59 | 60 | #[cfg(test)] 61 | mod test_sha256 { 62 | use hex_literal::hex; 63 | use std::io::Write; 64 | 65 | use super::*; 66 | use tempfile::NamedTempFile; 67 | 68 | #[test] 69 | fn hashes_files_correctly() -> Result<()> { 70 | let tmpfile = NamedTempFile::new()?; 71 | tmpfile.as_file().write_all(b"wurzelpfropf")?; 72 | let result = sha256(tmpfile.path())?; 73 | assert_eq!( 74 | result[..], 75 | hex!("8be65cca515ad097c953967d18d635d86dd78a142ed3d077526bed11c6bec67b") 76 | ); 77 | 78 | let tmpfile = NamedTempFile::new()?; 79 | tmpfile.as_file().write_all(b"yaxifaxi")?; 80 | let result = sha256(tmpfile.path())?; 81 | assert_eq!( 82 | result[..], 83 | hex!("a5fa47e8f93604b1ff552d0d4013440e35fbf7f5f00b4a115a100891e4266c63") 84 | ); 85 | 86 | Ok(()) 87 | } 88 | } 89 | 90 | /// Test if an editor string is a single hyphen to read from stdin 91 | pub(crate) fn is_stdin(editor: &str) -> bool { 92 | split_editor(editor).is_ok_and(|(program, args)| program == "-" && args.is_none()) 93 | } 94 | 95 | /// Split editor into binary and (shell) arguments 96 | pub(crate) fn split_editor(editor: &str) -> Result<(String, Option>)> { 97 | let mut splitted: Vec = shlex::split(editor) 98 | .ok_or_else(|| eyre!("Could not parse editor"))? 99 | .iter() 100 | .map(String::from) 101 | .collect(); 102 | 103 | if splitted.is_empty() { 104 | Err(eyre!("Editor is empty")) 105 | } else { 106 | let binary = splitted.first().unwrap().clone(); 107 | let args = if splitted.len() >= 2 { 108 | Some(splitted.split_off(1)) 109 | } else { 110 | None 111 | }; 112 | Ok((binary, args)) 113 | } 114 | } 115 | 116 | #[cfg(test)] 117 | mod test_split_editor { 118 | use super::*; 119 | 120 | #[test] 121 | fn parse_editor_no_args() -> Result<()> { 122 | let actual = split_editor("vim")?; 123 | let expected = (String::from("vim"), None); 124 | assert_eq!(actual, expected); 125 | Ok(()) 126 | } 127 | 128 | #[test] 129 | fn parse_editor_one_arg() -> Result<()> { 130 | let actual = split_editor("vim -R")?; 131 | let expected = (String::from("vim"), Some(vec![String::from("-R")])); 132 | assert_eq!(actual, expected); 133 | Ok(()) 134 | } 135 | 136 | #[test] 137 | fn parse_editor_complex_1() -> Result<()> { 138 | let actual = split_editor(r#"sed -i "s/.*/ x /""#)?; 139 | let expected = ( 140 | String::from("sed"), 141 | Some(vec![String::from("-i"), String::from("s/.*/ x /")]), 142 | ); 143 | assert_eq!(actual, expected); 144 | Ok(()) 145 | } 146 | 147 | #[test] 148 | fn parse_editor_complex_2() -> Result<()> { 149 | let actual = split_editor(r"sed -i 's/.*/ x /'")?; 150 | let expected = ( 151 | String::from("sed"), 152 | Some(vec![String::from("-i"), String::from("s/.*/ x /")]), 153 | ); 154 | assert_eq!(actual, expected); 155 | Ok(()) 156 | } 157 | 158 | #[test] 159 | fn parse_editor_stdin() -> Result<()> { 160 | let actual = split_editor(r" - ")?; 161 | let expected = (String::from("-"), None); 162 | assert_eq!(actual, expected); 163 | Ok(()) 164 | } 165 | 166 | #[test] 167 | fn err_for_empty_editor() { 168 | let result = split_editor(""); 169 | assert!(result.is_err()); 170 | assert_eq!(result.unwrap_err().to_string(), "Editor is empty"); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /tests/ragenix.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::{crate_name, Command}; 2 | use color_eyre::Result; 3 | use copy_dir::copy_dir; 4 | use indoc::{formatdoc, indoc}; 5 | use predicates::prelude::*; 6 | use std::{ 7 | fs, 8 | io::Write, 9 | path::{Path, PathBuf}, 10 | }; 11 | use tempfile::TempDir; 12 | 13 | fn copy_example_to_tmpdir() -> Result<(TempDir, PathBuf)> { 14 | let base_dir = Path::new(env!("CARGO_MANIFEST_DIR")); 15 | let example_dir = base_dir.join("example"); 16 | 17 | let dir = tempfile::tempdir()?; 18 | let dir_canonicalized = fs::canonicalize(&dir)?; 19 | let res_path = dir_canonicalized.join(example_dir.file_name().unwrap()); 20 | copy_dir(example_dir, &res_path)?; 21 | 22 | Ok((dir, res_path)) 23 | } 24 | 25 | #[test] 26 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 27 | fn edit_no_rekey_if_unchanged() -> Result<()> { 28 | let (_dir, path) = copy_example_to_tmpdir()?; 29 | 30 | let file = "github-runner.token.age"; 31 | let file_full_path = path.join(file); 32 | let expected = format!( 33 | "{} wasn't changed, skipping re-encryption.\n", 34 | file_full_path.to_string_lossy() 35 | ); 36 | 37 | let mut cmd = Command::cargo_bin(crate_name!())?; 38 | let assert = cmd 39 | .current_dir(&path) 40 | .arg("--edit") 41 | .arg(file) 42 | .arg("--identity") 43 | .arg("keys/id_ed25519") 44 | .env("EDITOR", "true") 45 | .assert(); 46 | 47 | assert.success().stdout(expected); 48 | 49 | Ok(()) 50 | } 51 | 52 | #[test] 53 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 54 | fn edit_works() -> Result<()> { 55 | let (_dir, path) = copy_example_to_tmpdir()?; 56 | 57 | let file = "github-runner.token.age"; 58 | 59 | let mut cmd = Command::cargo_bin(crate_name!())?; 60 | let assert = cmd 61 | .current_dir(&path) 62 | .arg("--edit") 63 | .arg(file) 64 | .arg("--identity") 65 | .arg("keys/id_ed25519") 66 | .env("EDITOR", "sed '-i' 's|.*|yaxifaxi|g'") 67 | .assert(); 68 | 69 | assert.success().stdout(""); 70 | 71 | Ok(()) 72 | } 73 | 74 | #[test] 75 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 76 | fn edit_new_entry() -> Result<()> { 77 | let dir = tempfile::tempdir()?; 78 | let dir_path = fs::canonicalize(dir.path())?; 79 | let rules = indoc! {r#" 80 | { 81 | "pandora.age".publicKeys = [ 82 | "age1qjzezkeazfdg4p9x0kjapjtreyyt74pg34ftzfypcdpy7wgh6acqxeyvwt" 83 | ]; 84 | } 85 | "#}; 86 | fs::File::create(dir.path().join("secrets.nix")) 87 | .and_then(|mut file| file.write_all(rules.as_bytes()))?; 88 | 89 | let pandora = dir_path.join("pandora"); 90 | fs::File::create(&pandora).and_then(|mut file| file.write_all(b"wurzelpfropf!"))?; 91 | 92 | let mut cmd = Command::cargo_bin(crate_name!())?; 93 | let assert = cmd 94 | .current_dir(dir.path()) 95 | .arg("--edit") 96 | .arg("pandora.age") 97 | .env("EDITOR", format!("cp {}", &pandora.display())) 98 | .assert(); 99 | 100 | assert.success().stdout(""); 101 | assert!(fs::metadata(pandora).is_ok()); 102 | let ciphertext = fs::read_to_string(dir.path().join("pandora.age"))?; 103 | assert!(predicate::str::starts_with("-----BEGIN AGE ENCRYPTED FILE-----").eval(&ciphertext)); 104 | assert!(predicate::str::ends_with("-----END AGE ENCRYPTED FILE-----\n").eval(&ciphertext)); 105 | 106 | Ok(()) 107 | } 108 | 109 | #[test] 110 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 111 | fn edit_new_entry_stdin() -> Result<()> { 112 | // # created: 2021-09-20T23:41:59+02:00 113 | // # public key: age1fjc9tyguvxfqh2ey2qqfc066g3gee7hlnhqn2g7yn4f6smymmsnq6xdn2t 114 | // AGE-SECRET-KEY-1C744H5LMUVHGVLX8HXAWA9ENXXXJ6R6F89V5AGEDXXD8GECQ624QQUXKHX 115 | let plaintext = "secret wurzelpfropf"; 116 | 117 | let dir = tempfile::tempdir()?; 118 | let rules = indoc! {r#" 119 | { 120 | "pandora.age".publicKeys = [ 121 | "age1fjc9tyguvxfqh2ey2qqfc066g3gee7hlnhqn2g7yn4f6smymmsnq6xdn2t" 122 | ]; 123 | } 124 | "#}; 125 | fs::write(dir.path().join("secrets.nix"), rules)?; 126 | 127 | let stdin_path = dir.path().join("stdin"); 128 | fs::write(&stdin_path, plaintext)?; 129 | 130 | let mut cmd = Command::cargo_bin(crate_name!())?; 131 | let assert = cmd 132 | .current_dir(dir.path()) 133 | .arg("--edit") 134 | .arg("pandora.age") 135 | .env("EDITOR", "-") 136 | .pipe_stdin(stdin_path)? 137 | .assert(); 138 | 139 | assert.success().stdout(""); 140 | 141 | // Verify the plaintext of the encrypted file 142 | let privkey_path = dir.path().join("key.txt"); 143 | fs::write( 144 | &privkey_path, 145 | "AGE-SECRET-KEY-1C744H5LMUVHGVLX8HXAWA9ENXXXJ6R6F89V5AGEDXXD8GECQ624QQUXKHX\n", 146 | )?; 147 | 148 | let mut cmd = Command::cargo_bin(crate_name!())?; 149 | let assert = cmd 150 | .current_dir(dir.path()) 151 | .arg("--identity") 152 | .arg(privkey_path) 153 | .arg("--edit") 154 | .arg("pandora.age") 155 | .env("EDITOR", "cat") 156 | .assert(); 157 | 158 | assert.stdout(predicate::str::starts_with(plaintext)); 159 | 160 | Ok(()) 161 | } 162 | 163 | #[test] 164 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 165 | fn edit_existing_entry_stdin() -> Result<()> { 166 | let plaintext = "secret wurzelpfropf"; 167 | 168 | let (_dir, path) = copy_example_to_tmpdir()?; 169 | let stdin_path = path.join("stdin"); 170 | fs::write(&stdin_path, plaintext)?; 171 | 172 | let mut cmd = Command::cargo_bin(crate_name!())?; 173 | let assert = cmd 174 | .current_dir(&path) 175 | .arg("--edit") 176 | .arg("github-runner.token.age") 177 | .env("EDITOR", "-") 178 | .pipe_stdin(stdin_path)? 179 | .assert(); 180 | 181 | assert.success(); 182 | 183 | let mut cmd = Command::cargo_bin(crate_name!())?; 184 | let assert = cmd 185 | .current_dir(&path) 186 | .arg("--identity") 187 | .arg("keys/id_ed25519") 188 | .arg("--edit") 189 | .arg("github-runner.token.age") 190 | .env("EDITOR", "cat") 191 | .assert(); 192 | 193 | assert 194 | .success() 195 | .stdout(predicate::str::starts_with(plaintext)); 196 | 197 | Ok(()) 198 | } 199 | 200 | #[test] 201 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 202 | fn edit_permissions_correct() -> Result<()> { 203 | let (_dir, path) = copy_example_to_tmpdir()?; 204 | let script = indoc! { r#" 205 | #!/usr/bin/env sh 206 | set -euo pipefail 207 | 208 | FILE="$(readlink -f "$1")" 209 | TMPDIR="$(dirname "$FILE")" 210 | 211 | file_permissions="$(stat -c '%a' "$FILE")" 212 | tmpdir_permissions="$(stat -c '%a' "$TMPDIR")" 213 | 214 | if [[ "$file_permissions" != "600" ]]; then 215 | >&2 printf '%s has wrong permissions %s\n' "$FILE" "$file_permissions" 216 | exit 1 217 | fi 218 | 219 | if [[ "$tmpdir_permissions" != "700" ]]; then 220 | >&2 printf '%s has wrong permissions %s\n' "$TMPDIR" "$tmpdir_permissions" 221 | exit 1 222 | fi 223 | "# }; 224 | let script_path = path.join("verify.sh"); 225 | 226 | fs::File::create(&script_path).and_then(|mut f| f.write_all(script.as_bytes()))?; 227 | 228 | let mut cmd = Command::cargo_bin(crate_name!())?; 229 | let assert = cmd 230 | .current_dir(&path) 231 | .arg("--identity") 232 | .arg("keys/id_ed25519") 233 | .arg("--edit") 234 | .arg("github-runner.token.age") 235 | .env("EDITOR", format!("sh {}", script_path.display())) 236 | .assert(); 237 | 238 | assert.success(); 239 | 240 | Ok(()) 241 | } 242 | 243 | #[test] 244 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 245 | fn rekeying_works() -> Result<()> { 246 | let (_dir, path) = copy_example_to_tmpdir()?; 247 | 248 | let files = &["github-runner.token.age", "root.passwd.age"]; 249 | let expected = files 250 | .iter() 251 | .map(|s| path.join(s)) 252 | .map(|p| format!("Rekeying {}", p.display())) 253 | .collect::>() 254 | .join("\n") 255 | + "\n"; 256 | 257 | let mut cmd = Command::cargo_bin(crate_name!())?; 258 | let assert = cmd 259 | .current_dir(&path) 260 | .arg("--rekey") 261 | .arg("--identity") 262 | .arg("keys/id_ed25519") 263 | .assert(); 264 | 265 | assert.success().stdout(expected); 266 | 267 | Ok(()) 268 | } 269 | 270 | #[test] 271 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 272 | fn rekeying_ignores_not_existing_files() -> Result<()> { 273 | let (_dir, path) = copy_example_to_tmpdir()?; 274 | 275 | let missing_file = path.join("root.passwd.age"); 276 | fs::remove_file(&missing_file)?; 277 | 278 | let mut cmd = Command::cargo_bin(crate_name!())?; 279 | let assert = cmd 280 | .current_dir(&path) 281 | .arg("--rekey") 282 | .arg("--identity") 283 | .arg("keys/id_ed25519") 284 | .assert(); 285 | 286 | assert.success().stdout(predicate::str::contains(format!( 287 | "Does not exist, ignored: {}", 288 | missing_file.display() 289 | ))); 290 | 291 | Ok(()) 292 | } 293 | 294 | #[test] 295 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 296 | fn rekeying_works_default_identities() -> Result<()> { 297 | let (_dir, path) = copy_example_to_tmpdir()?; 298 | 299 | let keys_dir = path.join("keys"); 300 | let ssh_dir = path.join(".ssh"); 301 | fs::create_dir(&ssh_dir)?; 302 | 303 | for filename in &["id_rsa", "id_ed25519"] { 304 | fs::copy(keys_dir.join(filename), ssh_dir.join(filename))?; 305 | } 306 | 307 | let mut cmd = Command::cargo_bin(crate_name!())?; 308 | let assert = cmd 309 | .current_dir(&path) 310 | .arg("--rekey") 311 | .env("HOME", &path) 312 | .assert(); 313 | 314 | assert 315 | .success() 316 | .stdout(predicate::str::starts_with("Rekeying ")); 317 | 318 | Ok(()) 319 | } 320 | 321 | #[test] 322 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 323 | fn rekeying_fails_no_given_identites() -> Result<()> { 324 | let (_dir, path) = copy_example_to_tmpdir()?; 325 | 326 | let mut cmd = Command::cargo_bin(crate_name!())?; 327 | let assert = cmd 328 | .current_dir(&path) 329 | .arg("--rekey") 330 | .env("HOME", "/homeless-shelter") 331 | .assert(); 332 | 333 | assert 334 | .failure() 335 | .stderr(predicate::str::contains("No usable identity or identities")); 336 | 337 | Ok(()) 338 | } 339 | 340 | #[test] 341 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 342 | fn rekeying_fails_no_valid_identites() -> Result<()> { 343 | let (_dir, path) = copy_example_to_tmpdir()?; 344 | let ssh_dir = path.join(".ssh"); 345 | fs::create_dir(&ssh_dir)?; 346 | 347 | let empty_key_1 = path.join("empty-key-1"); 348 | fs::File::create(&empty_key_1)?; 349 | 350 | let empty_key_2 = path.join("empty-key-2"); 351 | fs::File::create(&empty_key_2)?; 352 | 353 | let mut cmd = Command::cargo_bin(crate_name!())?; 354 | let assert = cmd 355 | .current_dir(&path) 356 | .arg("--rekey") 357 | .env("HOME", ssh_dir) 358 | .arg("--identity") 359 | .arg(empty_key_1) 360 | .arg(empty_key_2) 361 | .assert(); 362 | 363 | assert 364 | .failure() 365 | .stderr(predicate::str::contains("No matching keys found")); 366 | 367 | Ok(()) 368 | } 369 | 370 | #[test] 371 | fn prints_schema() -> Result<()> { 372 | let mut cmd = Command::cargo_bin(crate_name!())?; 373 | let assert = cmd.arg("--schema").assert(); 374 | 375 | let schema = include_str!("../src/ragenix/agenix.schema.json"); 376 | assert.success().stdout(schema); 377 | 378 | Ok(()) 379 | } 380 | 381 | #[test] 382 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 383 | fn rejects_invalid_rules() -> Result<()> { 384 | let (_dir, path) = copy_example_to_tmpdir()?; 385 | 386 | fs::File::create(path.join("secrets.nix")) 387 | .and_then(|mut f| f.write_all(r#"{ wurzel = "pfropf"; }"#.as_bytes()))?; 388 | 389 | let mut cmd = Command::cargo_bin(crate_name!())?; 390 | let assert = cmd 391 | .current_dir(&path) 392 | .arg("--edit") 393 | .arg("wurzel") 394 | .env("EDITOR", "true") 395 | .assert(); 396 | 397 | assert.failure().stderr(indoc! {r#" 398 | error: secrets rules are invalid: './secrets.nix' 399 | - /wurzel: "pfropf" is not of type "object" 400 | "#}); 401 | 402 | Ok(()) 403 | } 404 | 405 | #[test] 406 | #[cfg_attr(not(feature = "recursive-nix"), ignore)] 407 | fn fails_for_invalid_recipient() -> Result<()> { 408 | let dir = tempfile::tempdir()?; 409 | let invalid_key = "invalid-key abcdefghijklmnopqrstuvwxyz"; 410 | let rules = formatdoc! {" 411 | {{ 412 | \"wurzelpfropf.txt.age\".publicKeys = [ \"{}\" ]; 413 | }} 414 | ", invalid_key }; 415 | fs::File::create(dir.path().join("secrets.nix")) 416 | .and_then(|mut f| f.write_all(rules.as_bytes()))?; 417 | 418 | let mut cmd = Command::cargo_bin(crate_name!())?; 419 | let assert = cmd 420 | .current_dir(dir.path()) 421 | .arg("--edit") 422 | .arg("wurzelpfropf.txt.age") 423 | .env("EDITOR", "true") 424 | .assert(); 425 | 426 | assert 427 | .failure() 428 | .stderr(predicate::str::contains(format!("Invalid recipient: {invalid_key}"))) 429 | .stderr(predicate::str::contains( 430 | "Make sure you use an ssh-ed25519, ssh-rsa or an X25519 public key, alternatively install an age plugin which supports your key", 431 | )); 432 | 433 | Ok(()) 434 | } 435 | --------------------------------------------------------------------------------