├── .agenix.toml.example ├── .gitattributes ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── default.nix ├── flake.lock ├── flake.nix ├── rustfmt.toml ├── shell.nix ├── src ├── cli.rs └── main.rs └── tests ├── machine1 ├── machine1.pub ├── machine2 ├── machine2.pub ├── user1 ├── user2 └── user2.pub /.agenix.toml.example: -------------------------------------------------------------------------------- 1 | [identities] 2 | user1 = "age1szr8hp9lrjvc2d2hnr9c56fcj6f5ngnjy8gldnu6qtejnjrp6pmsc47jw8" 3 | machine1 = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBKD0YBA9vjNiIhBMVZgIjFjY282BfR6JM84HwcemN3Xt/vWaH1k53QzJqAF3LqJBisP9/xCSy+BL8cUV0z9goei3xOrWIfRTk0Hp5xYsVo7POvq1aQ3x+fFj3LAO/7HMYX/VD0jfHilv49HD0eQOiNp0T/OK3NuuFJmh2Wq45GibWRN6zdP42tB+4eKsJf7rIV+kcdybDlYYEiyCbGAcKMqcpzF+3CSQSbqA+XWPiyagUTucnoakjcJvZC6KPfK189t1KYV+1pKB1lD1MLJp+5jiaZFFyFASJ6jCIBO+il9XrCMDVO9RucxY89TBJBp24fd+hYwsH3YxIPN/esnftRePkIFbwIHout/9JVkFNpWeG6vORdAlnkyYmr8lNsodiGAmnGN3diAYNcmPqQ/9m9uovptFZWDB8yXEbnd3DZmTbuyhlrnaqqSE72p2a8WSqFr6aT2F1fk7AKLzJGT6/Grhk/7mXkqF5W7FnKP6D/XqYeNZA1NizUdxZopSJE6c=" 4 | 5 | [groups] 6 | machine1Admins = [ 7 | "user1", 8 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKbG8+IyUzm2v37k+SihwJ59JgZYsgU9/cJDUzeZUvgs" 9 | ] 10 | 11 | [[paths]] 12 | glob = "secrets/user1/*" 13 | identities = [ "user1" ] 14 | 15 | [[paths]] 16 | glob = "secrets/machine1/*" 17 | identities = [ "machine1" ] 18 | groups = [ "machine1Admins" ] 19 | 20 | [[paths]] 21 | glob = "secrets/misc/*" 22 | identities = [ "user1", "machine1" ] 23 | 24 | [[paths]] 25 | glob = "secrets/user2/*" 26 | identities = [ 27 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKbG8+IyUzm2v37k+SihwJ59JgZYsgU9/cJDUzeZUvgs" 28 | ] 29 | 30 | [[paths]] 31 | glob = "secrets/machine2/*" 32 | identities = [ 33 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICY1Zxf4TBam5LYfFhOv3D9aFvtBrfn+rKHPzprCzv5b" 34 | ] 35 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | Cargo.nix linguist-generated=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | result* 3 | /.envrc 4 | -------------------------------------------------------------------------------- /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 = "Inflector" 7 | version = "0.11.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 10 | 11 | [[package]] 12 | name = "addr2line" 13 | version = "0.14.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" 16 | dependencies = [ 17 | "gimli", 18 | ] 19 | 20 | [[package]] 21 | name = "adler" 22 | version = "0.2.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" 25 | 26 | [[package]] 27 | name = "aead" 28 | version = "0.3.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" 31 | dependencies = [ 32 | "generic-array", 33 | ] 34 | 35 | [[package]] 36 | name = "aes" 37 | version = "0.6.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" 40 | dependencies = [ 41 | "aes-soft", 42 | "aesni", 43 | "cipher 0.2.5", 44 | ] 45 | 46 | [[package]] 47 | name = "aes-ctr" 48 | version = "0.6.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763" 51 | dependencies = [ 52 | "aes-soft", 53 | "aesni", 54 | "cipher 0.2.5", 55 | "ctr", 56 | ] 57 | 58 | [[package]] 59 | name = "aes-soft" 60 | version = "0.6.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" 63 | dependencies = [ 64 | "cipher 0.2.5", 65 | "opaque-debug", 66 | ] 67 | 68 | [[package]] 69 | name = "aesni" 70 | version = "0.10.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" 73 | dependencies = [ 74 | "cipher 0.2.5", 75 | "opaque-debug", 76 | ] 77 | 78 | [[package]] 79 | name = "age" 80 | version = "0.6.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "4d2b0779d3a7a6527e6d78937720934dde5c257145e3fd5b54e05e937baf51b9" 83 | dependencies = [ 84 | "aes", 85 | "aes-ctr", 86 | "age-core", 87 | "base64", 88 | "bcrypt-pbkdf", 89 | "bech32", 90 | "block-modes", 91 | "c2-chacha", 92 | "chacha20poly1305", 93 | "console", 94 | "cookie-factory", 95 | "curve25519-dalek", 96 | "hkdf", 97 | "hmac", 98 | "i18n-embed", 99 | "i18n-embed-fl", 100 | "lazy_static", 101 | "nom", 102 | "num-traits", 103 | "pin-project", 104 | "pinentry", 105 | "rand 0.7.3", 106 | "rpassword", 107 | "rsa", 108 | "rust-embed", 109 | "scrypt", 110 | "secrecy", 111 | "sha2", 112 | "subtle", 113 | "x25519-dalek", 114 | "zeroize", 115 | ] 116 | 117 | [[package]] 118 | name = "age-core" 119 | version = "0.6.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "ad65fc4325804de2e915f5a50dda38218ed49f97e1270750acef9ff8bb67ac36" 122 | dependencies = [ 123 | "base64", 124 | "c2-chacha", 125 | "chacha20poly1305", 126 | "cookie-factory", 127 | "hkdf", 128 | "nom", 129 | "rand 0.7.3", 130 | "secrecy", 131 | "sha2", 132 | ] 133 | 134 | [[package]] 135 | name = "agenix" 136 | version = "0.1.1" 137 | dependencies = [ 138 | "age", 139 | "clap", 140 | "color-eyre", 141 | "env_logger", 142 | "glob", 143 | "log", 144 | "serde", 145 | "tempfile", 146 | "toml", 147 | ] 148 | 149 | [[package]] 150 | name = "anstyle" 151 | version = "1.0.10" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 154 | 155 | [[package]] 156 | name = "atty" 157 | version = "0.2.14" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 160 | dependencies = [ 161 | "hermit-abi", 162 | "libc", 163 | "winapi", 164 | ] 165 | 166 | [[package]] 167 | name = "autocfg" 168 | version = "0.1.7" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 171 | 172 | [[package]] 173 | name = "autocfg" 174 | version = "1.0.1" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 177 | 178 | [[package]] 179 | name = "backtrace" 180 | version = "0.3.56" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc" 183 | dependencies = [ 184 | "addr2line", 185 | "cfg-if", 186 | "libc", 187 | "miniz_oxide", 188 | "object", 189 | "rustc-demangle", 190 | ] 191 | 192 | [[package]] 193 | name = "base64" 194 | version = "0.13.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 197 | 198 | [[package]] 199 | name = "bcrypt-pbkdf" 200 | version = "0.6.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "12621b8e87feb183a6e5dbb315e49026b2229c4398797ee0ae2d1bc00aef41b9" 203 | dependencies = [ 204 | "blowfish", 205 | "crypto-mac", 206 | "pbkdf2", 207 | "sha2", 208 | "zeroize", 209 | ] 210 | 211 | [[package]] 212 | name = "bech32" 213 | version = "0.8.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "6c7f7096bc256f5e5cb960f60dfc4f4ef979ca65abe7fb9d5a4f77150d3783d4" 216 | 217 | [[package]] 218 | name = "bitflags" 219 | version = "1.2.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 222 | 223 | [[package]] 224 | name = "bitvec" 225 | version = "0.19.4" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81" 228 | dependencies = [ 229 | "funty", 230 | "radium", 231 | "tap", 232 | "wyz", 233 | ] 234 | 235 | [[package]] 236 | name = "block-buffer" 237 | version = "0.9.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 240 | dependencies = [ 241 | "generic-array", 242 | ] 243 | 244 | [[package]] 245 | name = "block-modes" 246 | version = "0.7.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" 249 | dependencies = [ 250 | "block-padding", 251 | "cipher 0.2.5", 252 | ] 253 | 254 | [[package]] 255 | name = "block-padding" 256 | version = "0.2.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" 259 | 260 | [[package]] 261 | name = "blowfish" 262 | version = "0.8.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "fe3ff3fc1de48c1ac2e3341c4df38b0d1bfb8fdf04632a187c8b75aaa319a7ab" 265 | dependencies = [ 266 | "byteorder", 267 | "cipher 0.3.0", 268 | "opaque-debug", 269 | ] 270 | 271 | [[package]] 272 | name = "byteorder" 273 | version = "1.4.2" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 276 | 277 | [[package]] 278 | name = "c2-chacha" 279 | version = "0.3.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "fb6b83fa00a7c53f420893670940c8fdfaa89f9dd9adb52062cca39482a31ab6" 282 | dependencies = [ 283 | "cipher 0.2.5", 284 | "ppv-lite86", 285 | ] 286 | 287 | [[package]] 288 | name = "cfg-if" 289 | version = "1.0.0" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 292 | 293 | [[package]] 294 | name = "chacha20poly1305" 295 | version = "0.7.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "af1fc18e6d90c40164bf6c317476f2a98f04661e310e79830366b7e914c58a8e" 298 | dependencies = [ 299 | "aead", 300 | "cipher 0.2.5", 301 | "poly1305", 302 | "zeroize", 303 | ] 304 | 305 | [[package]] 306 | name = "chrono" 307 | version = "0.4.19" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 310 | dependencies = [ 311 | "libc", 312 | "num-integer", 313 | "num-traits", 314 | "time", 315 | "winapi", 316 | ] 317 | 318 | [[package]] 319 | name = "cipher" 320 | version = "0.2.5" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" 323 | dependencies = [ 324 | "generic-array", 325 | ] 326 | 327 | [[package]] 328 | name = "cipher" 329 | version = "0.3.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 332 | dependencies = [ 333 | "generic-array", 334 | ] 335 | 336 | [[package]] 337 | name = "clap" 338 | version = "4.5.23" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" 341 | dependencies = [ 342 | "clap_builder", 343 | "clap_derive", 344 | ] 345 | 346 | [[package]] 347 | name = "clap_builder" 348 | version = "4.5.23" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" 351 | dependencies = [ 352 | "anstyle", 353 | "clap_lex", 354 | ] 355 | 356 | [[package]] 357 | name = "clap_derive" 358 | version = "4.5.18" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 361 | dependencies = [ 362 | "heck", 363 | "proc-macro2", 364 | "quote", 365 | "syn 2.0.90", 366 | ] 367 | 368 | [[package]] 369 | name = "clap_lex" 370 | version = "0.7.4" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 373 | 374 | [[package]] 375 | name = "color-eyre" 376 | version = "0.5.10" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "7b29030875fd8376e4a28ef497790d5b4a7843d8d1396bf08ce46f5eec562c5c" 379 | dependencies = [ 380 | "backtrace", 381 | "eyre", 382 | "indenter", 383 | "once_cell", 384 | "owo-colors", 385 | ] 386 | 387 | [[package]] 388 | name = "console" 389 | version = "0.14.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa" 392 | dependencies = [ 393 | "encode_unicode", 394 | "lazy_static", 395 | "libc", 396 | "regex", 397 | "terminal_size", 398 | "unicode-width", 399 | "winapi", 400 | ] 401 | 402 | [[package]] 403 | name = "cookie-factory" 404 | version = "0.3.2" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" 407 | 408 | [[package]] 409 | name = "cpuid-bool" 410 | version = "0.1.2" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 413 | 414 | [[package]] 415 | name = "cpuid-bool" 416 | version = "0.2.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" 419 | 420 | [[package]] 421 | name = "crypto-mac" 422 | version = "0.11.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" 425 | dependencies = [ 426 | "generic-array", 427 | "subtle", 428 | ] 429 | 430 | [[package]] 431 | name = "ctr" 432 | version = "0.6.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" 435 | dependencies = [ 436 | "cipher 0.2.5", 437 | ] 438 | 439 | [[package]] 440 | name = "curve25519-dalek" 441 | version = "3.0.2" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "f627126b946c25a4638eec0ea634fc52506dea98db118aae985118ce7c3d723f" 444 | dependencies = [ 445 | "byteorder", 446 | "digest", 447 | "rand_core 0.5.1", 448 | "subtle", 449 | "zeroize", 450 | ] 451 | 452 | [[package]] 453 | name = "dashmap" 454 | version = "4.0.2" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" 457 | dependencies = [ 458 | "cfg-if", 459 | "num_cpus", 460 | ] 461 | 462 | [[package]] 463 | name = "digest" 464 | version = "0.9.0" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 467 | dependencies = [ 468 | "generic-array", 469 | ] 470 | 471 | [[package]] 472 | name = "encode_unicode" 473 | version = "0.3.6" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 476 | 477 | [[package]] 478 | name = "env_logger" 479 | version = "0.8.2" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" 482 | dependencies = [ 483 | "atty", 484 | "log", 485 | "termcolor", 486 | ] 487 | 488 | [[package]] 489 | name = "eyre" 490 | version = "0.6.5" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b" 493 | dependencies = [ 494 | "indenter", 495 | "once_cell", 496 | ] 497 | 498 | [[package]] 499 | name = "find-crate" 500 | version = "0.6.3" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" 503 | dependencies = [ 504 | "toml", 505 | ] 506 | 507 | [[package]] 508 | name = "fluent" 509 | version = "0.15.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "bc4d7142005e2066e4844caf9f271b93fc79836ee96ec85057b8c109687e629a" 512 | dependencies = [ 513 | "fluent-bundle", 514 | "unic-langid", 515 | ] 516 | 517 | [[package]] 518 | name = "fluent-bundle" 519 | version = "0.15.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "8acf044eeb4872d9dbf2667541fbf461f5965c57e343878ad0fb24b5793fa007" 522 | dependencies = [ 523 | "fluent-langneg", 524 | "fluent-syntax", 525 | "intl-memoizer", 526 | "intl_pluralrules", 527 | "ouroboros", 528 | "rustc-hash", 529 | "smallvec", 530 | "unic-langid", 531 | ] 532 | 533 | [[package]] 534 | name = "fluent-langneg" 535 | version = "0.13.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" 538 | dependencies = [ 539 | "unic-langid", 540 | ] 541 | 542 | [[package]] 543 | name = "fluent-syntax" 544 | version = "0.11.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" 547 | dependencies = [ 548 | "thiserror", 549 | ] 550 | 551 | [[package]] 552 | name = "funty" 553 | version = "1.1.0" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" 556 | 557 | [[package]] 558 | name = "generic-array" 559 | version = "0.14.4" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 562 | dependencies = [ 563 | "typenum", 564 | "version_check", 565 | ] 566 | 567 | [[package]] 568 | name = "getrandom" 569 | version = "0.1.16" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 572 | dependencies = [ 573 | "cfg-if", 574 | "libc", 575 | "wasi 0.9.0+wasi-snapshot-preview1", 576 | ] 577 | 578 | [[package]] 579 | name = "getrandom" 580 | version = "0.2.2" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 583 | dependencies = [ 584 | "cfg-if", 585 | "libc", 586 | "wasi 0.10.2+wasi-snapshot-preview1", 587 | ] 588 | 589 | [[package]] 590 | name = "gimli" 591 | version = "0.23.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" 594 | 595 | [[package]] 596 | name = "glob" 597 | version = "0.3.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 600 | 601 | [[package]] 602 | name = "heck" 603 | version = "0.5.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 606 | 607 | [[package]] 608 | name = "hermit-abi" 609 | version = "0.1.18" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 612 | dependencies = [ 613 | "libc", 614 | ] 615 | 616 | [[package]] 617 | name = "hkdf" 618 | version = "0.11.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" 621 | dependencies = [ 622 | "digest", 623 | "hmac", 624 | ] 625 | 626 | [[package]] 627 | name = "hmac" 628 | version = "0.11.0" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" 631 | dependencies = [ 632 | "crypto-mac", 633 | "digest", 634 | ] 635 | 636 | [[package]] 637 | name = "i18n-config" 638 | version = "0.4.2" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "b62affcd43abfb51f3cbd8736f9407908dc5b44fc558a9be07460bbfd104d983" 641 | dependencies = [ 642 | "log", 643 | "serde", 644 | "serde_derive", 645 | "thiserror", 646 | "toml", 647 | "unic-langid", 648 | ] 649 | 650 | [[package]] 651 | name = "i18n-embed" 652 | version = "0.12.0" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "76146ceb86e5b4128a7d4f8716508841b6156bb231bf138f45235fe985aa79e0" 655 | dependencies = [ 656 | "fluent", 657 | "fluent-langneg", 658 | "fluent-syntax", 659 | "i18n-embed-impl", 660 | "intl-memoizer", 661 | "lazy_static", 662 | "log", 663 | "parking_lot", 664 | "rust-embed", 665 | "thiserror", 666 | "unic-langid", 667 | "walkdir", 668 | ] 669 | 670 | [[package]] 671 | name = "i18n-embed-fl" 672 | version = "0.5.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "4d91f4951bd0bc19624a06781bf8cd05bdd59057622e5d4240823b42a5f102d2" 675 | dependencies = [ 676 | "dashmap", 677 | "find-crate", 678 | "fluent", 679 | "fluent-syntax", 680 | "i18n-config", 681 | "i18n-embed", 682 | "lazy_static", 683 | "proc-macro-error", 684 | "proc-macro2", 685 | "quote", 686 | "strsim", 687 | "syn 1.0.60", 688 | "unic-langid", 689 | ] 690 | 691 | [[package]] 692 | name = "i18n-embed-impl" 693 | version = "0.7.0" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "2757ae6d1dd47fba009e86795350186fc4740a6e53a1b4f336a8a6725d20eb53" 696 | dependencies = [ 697 | "find-crate", 698 | "i18n-config", 699 | "proc-macro2", 700 | "quote", 701 | "syn 1.0.60", 702 | ] 703 | 704 | [[package]] 705 | name = "indenter" 706 | version = "0.3.2" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "f4d5eb2e114fec2b7fe0fadc22888ad2658789bb7acac4dbee9cf8389f971ec8" 709 | 710 | [[package]] 711 | name = "instant" 712 | version = "0.1.9" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 715 | dependencies = [ 716 | "cfg-if", 717 | ] 718 | 719 | [[package]] 720 | name = "intl-memoizer" 721 | version = "0.5.1" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" 724 | dependencies = [ 725 | "type-map", 726 | "unic-langid", 727 | ] 728 | 729 | [[package]] 730 | name = "intl_pluralrules" 731 | version = "7.0.1" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf" 734 | dependencies = [ 735 | "tinystr", 736 | "unic-langid", 737 | ] 738 | 739 | [[package]] 740 | name = "lazy_static" 741 | version = "1.4.0" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 744 | dependencies = [ 745 | "spin", 746 | ] 747 | 748 | [[package]] 749 | name = "libc" 750 | version = "0.2.84" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" 753 | 754 | [[package]] 755 | name = "libm" 756 | version = "0.2.1" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" 759 | 760 | [[package]] 761 | name = "lock_api" 762 | version = "0.4.2" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" 765 | dependencies = [ 766 | "scopeguard", 767 | ] 768 | 769 | [[package]] 770 | name = "log" 771 | version = "0.4.14" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 774 | dependencies = [ 775 | "cfg-if", 776 | ] 777 | 778 | [[package]] 779 | name = "memchr" 780 | version = "2.3.4" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 783 | 784 | [[package]] 785 | name = "miniz_oxide" 786 | version = "0.4.3" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" 789 | dependencies = [ 790 | "adler", 791 | "autocfg 1.0.1", 792 | ] 793 | 794 | [[package]] 795 | name = "nom" 796 | version = "6.2.2" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "c6a7a9657c84d5814c6196b68bb4429df09c18b1573806259fba397ea4ad0d44" 799 | dependencies = [ 800 | "bitvec", 801 | "funty", 802 | "memchr", 803 | "version_check", 804 | ] 805 | 806 | [[package]] 807 | name = "num-bigint" 808 | version = "0.2.6" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" 811 | dependencies = [ 812 | "autocfg 1.0.1", 813 | "num-integer", 814 | "num-traits", 815 | ] 816 | 817 | [[package]] 818 | name = "num-bigint-dig" 819 | version = "0.6.1" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "5d51546d704f52ef14b3c962b5776e53d5b862e5790e40a350d366c209bd7f7a" 822 | dependencies = [ 823 | "autocfg 0.1.7", 824 | "byteorder", 825 | "lazy_static", 826 | "libm", 827 | "num-integer", 828 | "num-iter", 829 | "num-traits", 830 | "rand 0.7.3", 831 | "serde", 832 | "smallvec", 833 | "zeroize", 834 | ] 835 | 836 | [[package]] 837 | name = "num-integer" 838 | version = "0.1.44" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 841 | dependencies = [ 842 | "autocfg 1.0.1", 843 | "num-traits", 844 | ] 845 | 846 | [[package]] 847 | name = "num-iter" 848 | version = "0.1.42" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 851 | dependencies = [ 852 | "autocfg 1.0.1", 853 | "num-integer", 854 | "num-traits", 855 | ] 856 | 857 | [[package]] 858 | name = "num-traits" 859 | version = "0.2.14" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 862 | dependencies = [ 863 | "autocfg 1.0.1", 864 | ] 865 | 866 | [[package]] 867 | name = "num_cpus" 868 | version = "1.13.0" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 871 | dependencies = [ 872 | "hermit-abi", 873 | "libc", 874 | ] 875 | 876 | [[package]] 877 | name = "object" 878 | version = "0.23.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" 881 | 882 | [[package]] 883 | name = "once_cell" 884 | version = "1.5.2" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" 887 | 888 | [[package]] 889 | name = "opaque-debug" 890 | version = "0.3.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 893 | 894 | [[package]] 895 | name = "ouroboros" 896 | version = "0.9.3" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "cc1f52300b81ac4eeeb6c00c20f7e86556c427d9fb2d92b68fc73c22f331cd15" 899 | dependencies = [ 900 | "ouroboros_macro", 901 | "stable_deref_trait", 902 | ] 903 | 904 | [[package]] 905 | name = "ouroboros_macro" 906 | version = "0.9.3" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "41db02c8f8731cdd7a72b433c7900cce4bf245465b452c364bfd21f4566ab055" 909 | dependencies = [ 910 | "Inflector", 911 | "proc-macro-error", 912 | "proc-macro2", 913 | "quote", 914 | "syn 1.0.60", 915 | ] 916 | 917 | [[package]] 918 | name = "owo-colors" 919 | version = "1.3.0" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" 922 | 923 | [[package]] 924 | name = "parking_lot" 925 | version = "0.11.1" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 928 | dependencies = [ 929 | "instant", 930 | "lock_api", 931 | "parking_lot_core", 932 | ] 933 | 934 | [[package]] 935 | name = "parking_lot_core" 936 | version = "0.8.2" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" 939 | dependencies = [ 940 | "cfg-if", 941 | "instant", 942 | "libc", 943 | "redox_syscall 0.1.57", 944 | "smallvec", 945 | "winapi", 946 | ] 947 | 948 | [[package]] 949 | name = "pbkdf2" 950 | version = "0.8.0" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" 953 | dependencies = [ 954 | "crypto-mac", 955 | ] 956 | 957 | [[package]] 958 | name = "pem" 959 | version = "0.8.2" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "f4c220d01f863d13d96ca82359d1e81e64a7c6bf0637bcde7b2349630addf0c6" 962 | dependencies = [ 963 | "base64", 964 | "once_cell", 965 | "regex", 966 | ] 967 | 968 | [[package]] 969 | name = "percent-encoding" 970 | version = "2.1.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 973 | 974 | [[package]] 975 | name = "pin-project" 976 | version = "1.0.4" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2" 979 | dependencies = [ 980 | "pin-project-internal", 981 | ] 982 | 983 | [[package]] 984 | name = "pin-project-internal" 985 | version = "1.0.4" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71" 988 | dependencies = [ 989 | "proc-macro2", 990 | "quote", 991 | "syn 1.0.60", 992 | ] 993 | 994 | [[package]] 995 | name = "pinentry" 996 | version = "0.3.0" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "a8266a6e77c40ef16f3d00bfe72ddb6e2fd29384d5b87e6bae1975099aa12921" 999 | dependencies = [ 1000 | "log", 1001 | "nom", 1002 | "percent-encoding", 1003 | "secrecy", 1004 | "which", 1005 | "zeroize", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "poly1305" 1010 | version = "0.6.2" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "4b7456bc1ad2d4cf82b3a016be4c2ac48daf11bf990c1603ebd447fe6f30fca8" 1013 | dependencies = [ 1014 | "cpuid-bool 0.2.0", 1015 | "universal-hash", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "ppv-lite86" 1020 | version = "0.2.10" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 1023 | 1024 | [[package]] 1025 | name = "proc-macro-error" 1026 | version = "1.0.4" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1029 | dependencies = [ 1030 | "proc-macro-error-attr", 1031 | "proc-macro2", 1032 | "quote", 1033 | "syn 1.0.60", 1034 | "version_check", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "proc-macro-error-attr" 1039 | version = "1.0.4" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1042 | dependencies = [ 1043 | "proc-macro2", 1044 | "quote", 1045 | "version_check", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "proc-macro2" 1050 | version = "1.0.92" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 1053 | dependencies = [ 1054 | "unicode-ident", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "quote" 1059 | version = "1.0.37" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1062 | dependencies = [ 1063 | "proc-macro2", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "radium" 1068 | version = "0.5.3" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" 1071 | 1072 | [[package]] 1073 | name = "rand" 1074 | version = "0.7.3" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1077 | dependencies = [ 1078 | "getrandom 0.1.16", 1079 | "libc", 1080 | "rand_chacha 0.2.2", 1081 | "rand_core 0.5.1", 1082 | "rand_hc 0.2.0", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "rand" 1087 | version = "0.8.3" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 1090 | dependencies = [ 1091 | "libc", 1092 | "rand_chacha 0.3.0", 1093 | "rand_core 0.6.2", 1094 | "rand_hc 0.3.0", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "rand_chacha" 1099 | version = "0.2.2" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1102 | dependencies = [ 1103 | "ppv-lite86", 1104 | "rand_core 0.5.1", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "rand_chacha" 1109 | version = "0.3.0" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 1112 | dependencies = [ 1113 | "ppv-lite86", 1114 | "rand_core 0.6.2", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "rand_core" 1119 | version = "0.5.1" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1122 | dependencies = [ 1123 | "getrandom 0.1.16", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "rand_core" 1128 | version = "0.6.2" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 1131 | dependencies = [ 1132 | "getrandom 0.2.2", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "rand_hc" 1137 | version = "0.2.0" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1140 | dependencies = [ 1141 | "rand_core 0.5.1", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "rand_hc" 1146 | version = "0.3.0" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 1149 | dependencies = [ 1150 | "rand_core 0.6.2", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "redox_syscall" 1155 | version = "0.1.57" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 1158 | 1159 | [[package]] 1160 | name = "redox_syscall" 1161 | version = "0.2.4" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" 1164 | dependencies = [ 1165 | "bitflags", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "regex" 1170 | version = "1.4.3" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" 1173 | dependencies = [ 1174 | "regex-syntax", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "regex-syntax" 1179 | version = "0.6.22" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" 1182 | 1183 | [[package]] 1184 | name = "remove_dir_all" 1185 | version = "0.5.3" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1188 | dependencies = [ 1189 | "winapi", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "rpassword" 1194 | version = "5.0.1" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" 1197 | dependencies = [ 1198 | "libc", 1199 | "winapi", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "rsa" 1204 | version = "0.3.0" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd" 1207 | dependencies = [ 1208 | "byteorder", 1209 | "digest", 1210 | "lazy_static", 1211 | "num-bigint-dig", 1212 | "num-integer", 1213 | "num-iter", 1214 | "num-traits", 1215 | "pem", 1216 | "rand 0.7.3", 1217 | "sha2", 1218 | "simple_asn1", 1219 | "subtle", 1220 | "thiserror", 1221 | "zeroize", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "rust-embed" 1226 | version = "5.9.0" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "2fe1fe6aac5d6bb9e1ffd81002340363272a7648234ec7bdfac5ee202cb65523" 1229 | dependencies = [ 1230 | "rust-embed-impl", 1231 | "rust-embed-utils", 1232 | "walkdir", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "rust-embed-impl" 1237 | version = "5.9.0" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "3ed91c41c42ef7bf687384439c312e75e0da9c149b0390889b94de3c7d9d9e66" 1240 | dependencies = [ 1241 | "proc-macro2", 1242 | "quote", 1243 | "rust-embed-utils", 1244 | "syn 1.0.60", 1245 | "walkdir", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "rust-embed-utils" 1250 | version = "5.1.0" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "2a512219132473ab0a77b52077059f1c47ce4af7fbdc94503e9862a34422876d" 1253 | dependencies = [ 1254 | "walkdir", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "rustc-demangle" 1259 | version = "0.1.18" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" 1262 | 1263 | [[package]] 1264 | name = "rustc-hash" 1265 | version = "1.1.0" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1268 | 1269 | [[package]] 1270 | name = "salsa20" 1271 | version = "0.8.0" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "1c7c5f10864beba947e1a1b43f3ef46c8cc58d1c2ae549fa471713e8ff60787a" 1274 | dependencies = [ 1275 | "cipher 0.3.0", 1276 | ] 1277 | 1278 | [[package]] 1279 | name = "same-file" 1280 | version = "1.0.6" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1283 | dependencies = [ 1284 | "winapi-util", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "scopeguard" 1289 | version = "1.1.0" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1292 | 1293 | [[package]] 1294 | name = "scrypt" 1295 | version = "0.7.0" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "879588d8f90906e73302547e20fffefdd240eb3e0e744e142321f5d49dea0518" 1298 | dependencies = [ 1299 | "hmac", 1300 | "pbkdf2", 1301 | "salsa20", 1302 | "sha2", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "secrecy" 1307 | version = "0.7.0" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "0673d6a6449f5e7d12a1caf424fd9363e2af3a4953023ed455e3c4beef4597c0" 1310 | dependencies = [ 1311 | "zeroize", 1312 | ] 1313 | 1314 | [[package]] 1315 | name = "serde" 1316 | version = "1.0.123" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" 1319 | dependencies = [ 1320 | "serde_derive", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "serde_derive" 1325 | version = "1.0.123" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" 1328 | dependencies = [ 1329 | "proc-macro2", 1330 | "quote", 1331 | "syn 1.0.60", 1332 | ] 1333 | 1334 | [[package]] 1335 | name = "sha2" 1336 | version = "0.9.2" 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" 1338 | checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8" 1339 | dependencies = [ 1340 | "block-buffer", 1341 | "cfg-if", 1342 | "cpuid-bool 0.1.2", 1343 | "digest", 1344 | "opaque-debug", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "simple_asn1" 1349 | version = "0.4.1" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" 1352 | dependencies = [ 1353 | "chrono", 1354 | "num-bigint", 1355 | "num-traits", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "smallvec" 1360 | version = "1.6.1" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 1363 | 1364 | [[package]] 1365 | name = "spin" 1366 | version = "0.5.2" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1369 | 1370 | [[package]] 1371 | name = "stable_deref_trait" 1372 | version = "1.2.0" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1375 | 1376 | [[package]] 1377 | name = "strsim" 1378 | version = "0.10.0" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1381 | 1382 | [[package]] 1383 | name = "subtle" 1384 | version = "2.4.0" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" 1387 | 1388 | [[package]] 1389 | name = "syn" 1390 | version = "1.0.60" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 1393 | dependencies = [ 1394 | "proc-macro2", 1395 | "quote", 1396 | "unicode-xid", 1397 | ] 1398 | 1399 | [[package]] 1400 | name = "syn" 1401 | version = "2.0.90" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 1404 | dependencies = [ 1405 | "proc-macro2", 1406 | "quote", 1407 | "unicode-ident", 1408 | ] 1409 | 1410 | [[package]] 1411 | name = "synstructure" 1412 | version = "0.12.4" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" 1415 | dependencies = [ 1416 | "proc-macro2", 1417 | "quote", 1418 | "syn 1.0.60", 1419 | "unicode-xid", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "tap" 1424 | version = "1.0.0" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e" 1427 | 1428 | [[package]] 1429 | name = "tempfile" 1430 | version = "3.2.0" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 1433 | dependencies = [ 1434 | "cfg-if", 1435 | "libc", 1436 | "rand 0.8.3", 1437 | "redox_syscall 0.2.4", 1438 | "remove_dir_all", 1439 | "winapi", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "termcolor" 1444 | version = "1.1.2" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1447 | dependencies = [ 1448 | "winapi-util", 1449 | ] 1450 | 1451 | [[package]] 1452 | name = "terminal_size" 1453 | version = "0.1.16" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" 1456 | dependencies = [ 1457 | "libc", 1458 | "winapi", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "thiserror" 1463 | version = "1.0.23" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" 1466 | dependencies = [ 1467 | "thiserror-impl", 1468 | ] 1469 | 1470 | [[package]] 1471 | name = "thiserror-impl" 1472 | version = "1.0.23" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" 1475 | dependencies = [ 1476 | "proc-macro2", 1477 | "quote", 1478 | "syn 1.0.60", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "time" 1483 | version = "0.1.43" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1486 | dependencies = [ 1487 | "libc", 1488 | "winapi", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "tinystr" 1493 | version = "0.3.4" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" 1496 | 1497 | [[package]] 1498 | name = "toml" 1499 | version = "0.5.8" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 1502 | dependencies = [ 1503 | "serde", 1504 | ] 1505 | 1506 | [[package]] 1507 | name = "type-map" 1508 | version = "0.4.0" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" 1511 | dependencies = [ 1512 | "rustc-hash", 1513 | ] 1514 | 1515 | [[package]] 1516 | name = "typenum" 1517 | version = "1.12.0" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 1520 | 1521 | [[package]] 1522 | name = "unic-langid" 1523 | version = "0.9.0" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" 1526 | dependencies = [ 1527 | "unic-langid-impl", 1528 | ] 1529 | 1530 | [[package]] 1531 | name = "unic-langid-impl" 1532 | version = "0.9.0" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" 1535 | dependencies = [ 1536 | "serde", 1537 | "tinystr", 1538 | ] 1539 | 1540 | [[package]] 1541 | name = "unicode-ident" 1542 | version = "1.0.14" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 1545 | 1546 | [[package]] 1547 | name = "unicode-width" 1548 | version = "0.1.8" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 1551 | 1552 | [[package]] 1553 | name = "unicode-xid" 1554 | version = "0.2.1" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1557 | 1558 | [[package]] 1559 | name = "universal-hash" 1560 | version = "0.4.0" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" 1563 | dependencies = [ 1564 | "generic-array", 1565 | "subtle", 1566 | ] 1567 | 1568 | [[package]] 1569 | name = "version_check" 1570 | version = "0.9.2" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1573 | 1574 | [[package]] 1575 | name = "walkdir" 1576 | version = "2.3.1" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1579 | dependencies = [ 1580 | "same-file", 1581 | "winapi", 1582 | "winapi-util", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "wasi" 1587 | version = "0.9.0+wasi-snapshot-preview1" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1590 | 1591 | [[package]] 1592 | name = "wasi" 1593 | version = "0.10.2+wasi-snapshot-preview1" 1594 | source = "registry+https://github.com/rust-lang/crates.io-index" 1595 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1596 | 1597 | [[package]] 1598 | name = "which" 1599 | version = "4.0.2" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef" 1602 | dependencies = [ 1603 | "libc", 1604 | "thiserror", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "winapi" 1609 | version = "0.3.9" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1612 | dependencies = [ 1613 | "winapi-i686-pc-windows-gnu", 1614 | "winapi-x86_64-pc-windows-gnu", 1615 | ] 1616 | 1617 | [[package]] 1618 | name = "winapi-i686-pc-windows-gnu" 1619 | version = "0.4.0" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1622 | 1623 | [[package]] 1624 | name = "winapi-util" 1625 | version = "0.1.5" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1628 | dependencies = [ 1629 | "winapi", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "winapi-x86_64-pc-windows-gnu" 1634 | version = "0.4.0" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1637 | 1638 | [[package]] 1639 | name = "wyz" 1640 | version = "0.2.0" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" 1643 | 1644 | [[package]] 1645 | name = "x25519-dalek" 1646 | version = "1.1.0" 1647 | source = "registry+https://github.com/rust-lang/crates.io-index" 1648 | checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" 1649 | dependencies = [ 1650 | "curve25519-dalek", 1651 | "rand_core 0.5.1", 1652 | "zeroize", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "zeroize" 1657 | version = "1.2.0" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36" 1660 | dependencies = [ 1661 | "zeroize_derive", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "zeroize_derive" 1666 | version = "1.0.1" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" 1669 | dependencies = [ 1670 | "proc-macro2", 1671 | "quote", 1672 | "syn 1.0.60", 1673 | "synstructure", 1674 | ] 1675 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "agenix" 3 | version = "0.1.1" 4 | authors = ["Cole Helbling "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/cole-h/agenix-cli" 8 | 9 | [dependencies] 10 | age = { version = "0.6.0", default-features = false, features = [ "cli-common", "ssh", "armor" ] } 11 | clap = { version = "4.5.23", default-features = false, features = [ "std", "cargo", "derive" ] } 12 | color-eyre = { version = "0.5.10", default-features = false, features = [ "track-caller" ] } 13 | env_logger = { version = "0.8.2", default-features = false, features = [ "termcolor", "atty" ] } 14 | glob = "0.3.0" 15 | log = "0.4.14" 16 | serde = "1.0.123" 17 | tempfile = "3.2.0" 18 | toml = "0.5.8" 19 | 20 | [profile.dev.package.backtrace] 21 | opt-level = 3 22 | 23 | [profile.release] 24 | lto = true 25 | debug = 1 26 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Cole Helbling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # agenix-cli 2 | 3 | This project provides a binary, `agenix`, intended for use with 4 | https://github.com/ryantm/agenix and aims to provide a more sophisticated 5 | replacement for the shell script version of `agenix`. 6 | 7 | ## Configuration 8 | 9 | For an example configuration, check out the 10 | [.agenix.toml.example](./.agenix.toml.example) file. 11 | 12 | The basic layout is one `[identities]` TOML table and one or many `[[paths]]` 13 | TOML array-of-tables. The `[identities]` table is essentially an association of 14 | identity names (which can be anything that TOML itself supports as a key) to its 15 | public key. 16 | 17 | > **__NOTE__**: The given name is only so that you can use multiple keys for 18 | multiple globs (mentioned below) without having to copy-paste the key everywhere 19 | -- it holds no other meaning. 20 | 21 | The `[[paths]]` array-of-tables contains two keys: `glob` and `identities`. 22 | `glob` is a a path glob `agenix` uses to match against, and `identities` is an 23 | array of identities (either specified by a name that is then looked up in the 24 | `[identities]` table, or the public key itself). 25 | 26 | ## Usage 27 | 28 | Using `agenix` is as simple [setting up a configuration](#configuration) and 29 | then running `agenix [file]`. 30 | 31 | > **__NOTE__**: If the specified file exists and was previously encrypted to an 32 | `age` identity, you must use the `-i`/`--identity` flag to specify the private 33 | key associated with that identity; otherwise, `agenix` will be unable to decrypt 34 | the contents. 35 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in 4 | fetchTarball { 5 | url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | } 8 | ) 9 | { src = ./.; } 10 | ).defaultNix 11 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "locked": { 5 | "lastModified": 1733328505, 6 | "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", 7 | "owner": "edolstra", 8 | "repo": "flake-compat", 9 | "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "edolstra", 14 | "repo": "flake-compat", 15 | "type": "github" 16 | } 17 | }, 18 | "flake-utils": { 19 | "inputs": { 20 | "systems": "systems" 21 | }, 22 | "locked": { 23 | "lastModified": 1731533236, 24 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 25 | "owner": "numtide", 26 | "repo": "flake-utils", 27 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "numtide", 32 | "repo": "flake-utils", 33 | "type": "github" 34 | } 35 | }, 36 | "nixpkgs": { 37 | "locked": { 38 | "lastModified": 1734435836, 39 | "narHash": "sha256-kMBQ5PRiFLagltK0sH+08aiNt3zGERC2297iB6vrvlU=", 40 | "owner": "NixOS", 41 | "repo": "nixpkgs", 42 | "rev": "4989a246d7a390a859852baddb1013f825435cee", 43 | "type": "github" 44 | }, 45 | "original": { 46 | "owner": "NixOS", 47 | "ref": "nixpkgs-unstable", 48 | "repo": "nixpkgs", 49 | "type": "github" 50 | } 51 | }, 52 | "root": { 53 | "inputs": { 54 | "flake-compat": "flake-compat", 55 | "flake-utils": "flake-utils", 56 | "nixpkgs": "nixpkgs" 57 | } 58 | }, 59 | "systems": { 60 | "locked": { 61 | "lastModified": 1681028828, 62 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 63 | "owner": "nix-systems", 64 | "repo": "default", 65 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 66 | "type": "github" 67 | }, 68 | "original": { 69 | "owner": "nix-systems", 70 | "repo": "default", 71 | "type": "github" 72 | } 73 | } 74 | }, 75 | "root": "root", 76 | "version": 7 77 | } 78 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "agenix-cli"; 3 | 4 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 5 | inputs.flake-utils.url = "github:numtide/flake-utils"; 6 | inputs.flake-compat.url = "github:edolstra/flake-compat"; 7 | 8 | outputs = { self, nixpkgs, flake-utils, ... }: 9 | flake-utils.lib.eachDefaultSystem (system: 10 | let 11 | pkgs = nixpkgs.legacyPackages.${system}; 12 | lib = pkgs.lib; 13 | in 14 | { 15 | packages = rec { 16 | default = agenix-cli; 17 | 18 | agenix-cli = pkgs.rustPlatform.buildRustPackage { 19 | pname = "agenix-cli"; 20 | version = (lib.importTOML ./Cargo.toml).package.version; 21 | 22 | src = self; 23 | cargoLock.lockFile = ./Cargo.lock; 24 | }; 25 | }; 26 | 27 | devShell = pkgs.mkShell { 28 | nativeBuildInputs = with pkgs; [ rustc cargo rustfmt ]; 29 | }; 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | wrap_comments = true 3 | imports_granularity = "crate" 4 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in 4 | fetchTarball { 5 | url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | } 8 | ) 9 | { src = ./.; } 10 | ).defaultNix 11 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | //! The `agenix` command-line interface. 2 | 3 | use std::collections::HashMap; 4 | use std::env; 5 | use std::fs::{self, File}; 6 | use std::io::{self, BufReader, Read, Seek, SeekFrom, Write}; 7 | use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR}; 8 | use std::process::{Command, Stdio}; 9 | 10 | use age::{ 11 | armor::{ArmoredReader, ArmoredWriter, Format}, 12 | Decryptor, Encryptor, 13 | }; 14 | use clap::Parser; 15 | use color_eyre::{ 16 | eyre::{bail, eyre, Result, WrapErr}, 17 | Section, SectionExt, 18 | }; 19 | use env_logger::{fmt::Color, WriteStyle}; 20 | use log::{debug, error, info, trace, warn, Level, LevelFilter}; 21 | use serde::Deserialize; 22 | 23 | /// The maximum number of directories `agenix` is allowed to ascend in search of 24 | /// the `.agenix.toml` configuration. 25 | const MAX_DEPTH: u8 = 100; 26 | 27 | #[doc(hidden)] 28 | const LF: [u8; 1] = [0x0a]; 29 | #[doc(hidden)] 30 | const CRLF: [u8; 2] = [0x0d, 0x0a]; 31 | 32 | /// The `agenix` command-line options. 33 | #[derive(Parser, Debug)] 34 | struct Agenix { 35 | /// The file to edit. 36 | /// 37 | /// Optional when used with `--rekey` to rekey everything or validating the 38 | /// config, required otherwise. 39 | path: Option, 40 | /// Whether to re-encrypt the specified file or all files, if no `path` is given. 41 | #[clap(short, long)] 42 | rekey: bool, 43 | /// The identity or identities to use for decryption. May be specified 44 | /// multiple times. 45 | /// 46 | /// If unspecified, falls back to `~/.ssh/id_rsa` and `~/.ssh/id_ed25519`, 47 | /// whichever (if any) exists. 48 | #[clap(short, long, number_of_values = 1, action = clap::ArgAction::Append)] 49 | identity: Vec, 50 | /// Whether or not to save encrypted files in binary format. 51 | /// 52 | /// By default, output files are ASCII-armored. 53 | #[clap(short, long)] 54 | binary: bool, 55 | /// Whether or not to encrypt a plaintext file in-place. 56 | #[clap(short, long)] 57 | encrypt_in_place: bool, 58 | /// The verbosity of logging. 59 | /// 60 | /// By default, only warnings and errors are printed. 61 | #[clap(short, long, action = clap::ArgAction::Count)] 62 | verbose: u8, 63 | /// Whether or not to read contents from stdin. 64 | /// 65 | /// NOTE: This does not support writing to an existing file. 66 | /// 67 | /// By default, an editor is spawned. 68 | #[clap(short, long)] 69 | stdin: bool, 70 | /// Validate the config file. 71 | #[clap(long)] 72 | validate_config: bool, 73 | } 74 | 75 | /// The `.agenix.toml` configuration schema. 76 | /// 77 | /// # Example configuration 78 | /// 79 | /// ```toml 80 | /// [identities] 81 | /// user1 = "age1szr8hp9lrjvc2d2hnr9c56fcj6f5ngnjy8gldnu6qtejnjrp6pmsc47jw8" 82 | /// machine1 = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBKD0YBA9vjNiIhBMVZgIjFjY282BfR6JM84HwcemN3Xt/vWaH1k53QzJqAF3LqJBisP9/xCSy+BL8cUV0z9goei3xOrWIfRTk0Hp5xYsVo7POvq1aQ3x+fFj3LAO/7HMYX/VD0jfHilv49HD0eQOiNp0T/OK3NuuFJmh2Wq45GibWRN6zdP42tB+4eKsJf7rIV+kcdybDlYYEiyCbGAcKMqcpzF+3CSQSbqA+XWPiyagUTucnoakjcJvZC6KPfK189t1KYV+1pKB1lD1MLJp+5jiaZFFyFASJ6jCIBO+il9XrCMDVO9RucxY89TBJBp24fd+hYwsH3YxIPN/esnftRePkIFbwIHout/9JVkFNpWeG6vORdAlnkyYmr8lNsodiGAmnGN3diAYNcmPqQ/9m9uovptFZWDB8yXEbnd3DZmTbuyhlrnaqqSE72p2a8WSqFr6aT2F1fk7AKLzJGT6/Grhk/7mXkqF5W7FnKP6D/XqYeNZA1NizUdxZopSJE6c=" 83 | /// 84 | /// [groups] 85 | /// machine1Admins = [ 86 | /// "user1", 87 | /// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKbG8+IyUzm2v37k+SihwJ59JgZYsgU9/cJDUzeZUvgs" 88 | /// ] 89 | /// 90 | /// [[paths]] 91 | /// glob = "secrets/user1/*" 92 | /// identities = [ "user1" ] 93 | /// 94 | /// [[paths]] 95 | /// glob = "secrets/machine1/*" 96 | /// identities = [ "machine1" ] 97 | /// groups = [ "machine1Admins" ] 98 | /// 99 | /// [[paths]] 100 | /// glob = "secrets/misc/*" 101 | /// identities = [ "user1", "machine1" ] 102 | /// 103 | /// [[paths]] 104 | /// glob = "secrets/user2/*" 105 | /// identities = [ 106 | /// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKbG8+IyUzm2v37k+SihwJ59JgZYsgU9/cJDUzeZUvgs" 107 | /// ] 108 | /// 109 | /// [[paths]] 110 | /// glob = "secrets/machine2/*" 111 | /// identities = [ 112 | /// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICY1Zxf4TBam5LYfFhOv3D9aFvtBrfn+rKHPzprCzv5b" 113 | /// ] 114 | /// ``` 115 | #[derive(Debug, Deserialize)] 116 | struct AgenixConfig { 117 | /// A list of names and their associated identities. Optional. 118 | #[serde(default)] 119 | identities: HashMap, 120 | /// A list of group names and their associated identities. Optional. 121 | #[serde(default)] 122 | groups: HashMap>, 123 | /// A list of paths managed by `agenix`. Required. 124 | paths: Vec, 125 | } 126 | 127 | /// The `[[paths]]` array-of-tables. 128 | /// 129 | /// One of `identities` or `groups` must be specified, and both may be specified 130 | /// at the same time. 131 | #[derive(Debug, Deserialize)] 132 | struct PathSpec { 133 | /// All paths matching this glob (relative to the `.agenix.toml` file) will 134 | /// be encrypted to the associated list of identities. Required. 135 | glob: String, 136 | /// A list of identities with access to the files matched by the associated 137 | /// glob. Can either be a name from the identities table, or a bare key 138 | /// (e.g. `age...` or `ssh-ed25519 ...` or `ssh-rsa ...`). 139 | #[serde(default)] 140 | identities: Vec, 141 | /// A list of groups with access to the files matched by the associated 142 | /// glob. Must be a name from the `[groups]` table. 143 | #[serde(default)] 144 | groups: Vec, 145 | // TODO: keyfile: Vec? to sidestep the necessity of -i for age keys 146 | } 147 | 148 | /// A structure that contains the `.agenix.toml` configuration and the root 149 | /// directory of that configuration. 150 | #[derive(Debug)] 151 | struct Config { 152 | /// The `.agenix.toml` configuration. 153 | agenix: AgenixConfig, 154 | /// The root directory of the configuration. 155 | root: PathBuf, 156 | } 157 | 158 | const MATCH_OPTS: glob::MatchOptions = glob::MatchOptions { 159 | case_sensitive: true, 160 | require_literal_separator: true, 161 | require_literal_leading_dot: false, 162 | }; 163 | 164 | /// Run `agenix`. 165 | pub fn run() -> Result<()> { 166 | let opts = Agenix::parse(); 167 | 168 | match &opts.path { 169 | None => { 170 | if !opts.rekey && !opts.validate_config { 171 | bail!("agenix requires a path argument (unless rekeying or validating the config).") 172 | } 173 | } 174 | Some(path) => { 175 | if path.ends_with('/') || Path::new(&path).is_dir() { 176 | bail!("agenix cannot operate on a directory. Please specify a filename (whether or not it exists)."); 177 | } 178 | 179 | if opts.rekey && !Path::new(&path).exists() { 180 | bail!("agenix cannot rekey a nonexistent file."); 181 | } 182 | 183 | if opts.stdin && Path::new(&path).exists() { 184 | bail!("agenix does not allow writing contents from stdin to an existing file."); 185 | } 186 | } 187 | } 188 | 189 | env_logger::Builder::new() 190 | .format(|buf, record| { 191 | let mut style = buf.style(); 192 | 193 | match record.level() { 194 | Level::Trace => style.set_color(Color::Cyan), 195 | Level::Debug => style.set_color(Color::Blue), 196 | Level::Info => style.set_color(Color::Green), 197 | Level::Warn => style.set_color(Color::Yellow), 198 | Level::Error => style.set_color(Color::Red).set_bold(true), 199 | }; 200 | 201 | writeln!(buf, "{:<5} {}", style.value(record.level()), record.args()) 202 | }) 203 | .filter( 204 | Some(env!("CARGO_PKG_NAME")), // only log for agenix 205 | match opts.verbose { 206 | 0 => LevelFilter::Warn, 207 | 1 => LevelFilter::Info, 208 | 2 => LevelFilter::Debug, 209 | _ => LevelFilter::Trace, 210 | }, 211 | ) 212 | .write_style(WriteStyle::Auto) 213 | .try_init() 214 | .wrap_err("Failed to initialize logging")?; 215 | 216 | let conf_path = self::find_config_dir()?.ok_or_else(|| eyre!("Failed to find config root"))?; 217 | let agenix_conf = toml::from_str::( 218 | &self::read_config(&conf_path).wrap_err("Failed to read config file")?, 219 | ) 220 | .wrap_err("Failed to parse config as TOML")?; 221 | 222 | let current_path = env::current_dir().wrap_err("Failed to get current directory")?; 223 | let conf = Config { 224 | agenix: agenix_conf, 225 | root: conf_path, 226 | }; 227 | 228 | trace!("validate_config? {}", opts.validate_config); 229 | trace!("rekey? {}", opts.rekey); 230 | trace!("path.is_none()? {}", opts.path.is_none()); 231 | if opts.validate_config { 232 | self::validate_config(&conf)?; 233 | } else if opts.rekey && opts.path.is_none() { 234 | let mut paths = Vec::new(); 235 | for pathspec in &conf.agenix.paths { 236 | for path in glob::glob_with(&pathspec.glob, MATCH_OPTS) 237 | .wrap_err_with(|| format!("Failed to match glob pattern '{}'", &pathspec.glob))? 238 | { 239 | let path = path.wrap_err_with(|| { 240 | format!("Failed to iterate over glob pattern '{}'", &pathspec.glob) 241 | })?; 242 | paths.push(path); 243 | } 244 | } 245 | 246 | for path in paths { 247 | if let Err(e) = self::try_process_file(&conf, &path, &opts, ¤t_path) { 248 | error!("Failed to rekey file '{}': {}", path.display(), e); 249 | } else { 250 | info!("Successfully rekeyed file '{}'", path.display()); 251 | } 252 | } 253 | } else { 254 | // This `unwrap()` is safe because we verify that the path is specified if we're not in 255 | // `rekey` mode. 256 | self::try_process_file(&conf, opts.path.clone().unwrap(), &opts, ¤t_path)?; 257 | } 258 | 259 | Ok(()) 260 | } 261 | 262 | /// Validates the config and logs any errors found in the config. 263 | fn validate_config(conf: &Config) -> Result<()> { 264 | // validate keys 265 | for (identity, key) in &conf.agenix.identities { 266 | if self::try_parse_key_to_recipient(key).is_none() { 267 | warn!( 268 | "Identity '{}' is not a valid age, ssh-rsa, or ssh-ed25591 public key", 269 | &identity 270 | ); 271 | } 272 | } 273 | 274 | for (group, identities) in &conf.agenix.groups { 275 | // check for empty groups 276 | if identities.is_empty() { 277 | warn!("Group '{}' contains no identities", group); 278 | } 279 | 280 | // check for groups with unknown identites 281 | for identity in identities { 282 | if !conf.agenix.identities.contains_key(identity) { 283 | warn!("Group '{}' contains unknown identity '{}'", group, identity); 284 | } 285 | } 286 | } 287 | 288 | let mut patterns_by_path = HashMap::new(); 289 | for pathspec in &conf.agenix.paths { 290 | // collect patterns matching each path 291 | for path in glob::glob_with(&pathspec.glob, MATCH_OPTS) 292 | .wrap_err_with(|| format!("Failed to match glob pattern '{}'", &pathspec.glob))? 293 | { 294 | let path = path.wrap_err_with(|| { 295 | format!("Failed to iterate over glob pattern '{}'", &pathspec.glob) 296 | })?; 297 | let patterns = patterns_by_path.entry(path).or_insert_with(Vec::new); 298 | patterns.push(pathspec.glob.clone()); 299 | } 300 | 301 | // check for paths without group or identity 302 | if pathspec.identities.is_empty() && pathspec.groups.is_empty() { 303 | warn!( 304 | "Path glob '{}' has no associated identities or groups", 305 | pathspec.glob 306 | ); 307 | } 308 | 309 | // check for unkown identities 310 | for identity in &pathspec.identities { 311 | if !conf.agenix.identities.contains_key(identity) 312 | && self::try_parse_key_to_recipient(identity).is_none() 313 | { 314 | warn!( 315 | "Path glob '{}' has associated identity '{}' which is neither a valid key nor a name of an identity", 316 | pathspec.glob, identity 317 | ); 318 | } 319 | } 320 | 321 | // check for unkown groups 322 | for group in &pathspec.groups { 323 | if !conf.agenix.groups.contains_key(group) { 324 | warn!( 325 | "Path glob '{}' has unknown associated group '{}'", 326 | pathspec.glob, group 327 | ); 328 | } 329 | } 330 | } 331 | 332 | // check for paths matched by multiple patterns 333 | for (path, patterns) in patterns_by_path { 334 | if 1 < patterns.len() { 335 | warn!( 336 | "Path '{}' is matched by {} glob patterns ('{}')", 337 | &path.display(), 338 | &patterns.len(), 339 | &patterns.join("', '") 340 | ); 341 | } 342 | } 343 | 344 | Ok(()) 345 | } 346 | 347 | /// Try to process the specified path in order to decrypt and encrypt its contents. 348 | fn try_process_file

(conf: &Config, path: P, opts: &Agenix, current_path: &Path) -> Result<()> 349 | where 350 | P: AsRef, 351 | { 352 | let path = path.as_ref(); 353 | let relative_path = current_path 354 | .strip_prefix(&conf.root) 355 | .unwrap_or(&env::current_dir().wrap_err("Failed to get current directory")?) 356 | .join(path); 357 | let recipients = self::get_recipients_from_config(conf, &relative_path) 358 | .wrap_err("Failed to get recipients from config file")?; 359 | 360 | if recipients.is_empty() { 361 | bail!( 362 | "File '{}' has no valid recipients", 363 | &relative_path.display() 364 | ); 365 | } 366 | 367 | let decrypted = 368 | self::try_decrypt_target_with_identities(path, &opts.identity, opts.encrypt_in_place) 369 | .wrap_err_with(|| format!("Failed to decrypt file '{}'", &path.display()))?; 370 | let mut temp_file = 371 | self::create_temp_file(&relative_path).wrap_err("Failed to create temporary file")?; 372 | 373 | if let Some(ref dec) = decrypted { 374 | temp_file 375 | .write_all(dec) 376 | .wrap_err("Failed to write decrypted contents to temporary file")?; 377 | } 378 | 379 | trace!("rekey? {}", opts.rekey); 380 | trace!("encrypt_in_place? {}", opts.encrypt_in_place); 381 | if !opts.rekey && !opts.encrypt_in_place && !opts.stdin { 382 | self::try_edit_file(temp_file.path())?; 383 | } 384 | 385 | let contents = if opts.stdin { 386 | let mut input = Vec::new(); 387 | 388 | for byte in io::stdin().bytes() { 389 | let b = byte?; 390 | input.push(b); 391 | } 392 | 393 | input 394 | } else { 395 | let mut new_contents = Vec::new(); 396 | let mut temp_file = fs::OpenOptions::new() 397 | .read(true) 398 | .open(temp_file.path()) 399 | .wrap_err("Failed to open temporary file for reading")?; 400 | 401 | // Ensure the cursor is at the beginning of the file. 402 | temp_file.seek(SeekFrom::Start(0))?; 403 | temp_file 404 | .read_to_end(&mut new_contents) 405 | .wrap_err("Failed to read new contents from temporary file")?; 406 | 407 | if new_contents.is_empty() || new_contents == LF || new_contents == CRLF { 408 | warn!("contents empty, not saving"); 409 | return Ok(()); 410 | } 411 | 412 | if let Some(ref dec) = decrypted { 413 | if !(opts.rekey || opts.encrypt_in_place) && dec == &new_contents { 414 | warn!("contents unchanged, not saving"); 415 | return Ok(()); 416 | } 417 | } 418 | 419 | new_contents 420 | }; 421 | 422 | self::try_encrypt_target_with_recipients(path, recipients, contents, opts.binary) 423 | .wrap_err_with(|| format!("Failed to encrypt file '{}'", &path.display()))?; 424 | 425 | Ok(()) 426 | } 427 | 428 | /// A light wrapper around [`fs::create_dir_all`] that creates all directories 429 | /// that would allow the specified `file` to be created. 430 | /// 431 | /// [`fs::create_dir_all`]: https://doc.rust-lang.org/std/fs/fn.create_dir_all.html 432 | fn create_dirs_to_file(file: &Path) -> Result<()> { 433 | if file.exists() { 434 | return Ok(()); 435 | } 436 | 437 | let dir = file 438 | .parent() 439 | .ok_or_else(|| eyre!("Path '{}' had no parent", file.display()))?; 440 | 441 | fs::create_dir_all(dir) 442 | .wrap_err_with(|| format!("Failed to create directories to '{}'", &dir.display()))?; 443 | 444 | Ok(()) 445 | } 446 | 447 | /// Try to encrypt the given contents into the `target` path for the specified 448 | /// `recipients`, optionally in `binary` format (as opposed to the default of 449 | /// ASCII-armored text). 450 | fn try_encrypt_target_with_recipients( 451 | target: &Path, 452 | recipients: Vec>, 453 | contents: Vec, 454 | binary: bool, 455 | ) -> Result<()> { 456 | self::create_dirs_to_file(target) 457 | .wrap_err_with(|| format!("Failed to create directories to '{}'", &target.display()))?; 458 | 459 | let target = fs::OpenOptions::new() 460 | .create(true) 461 | .write(true) 462 | .truncate(true) 463 | .open(target) 464 | .wrap_err_with(|| format!("Failed to open '{}' for writing", &target.display()))?; 465 | 466 | trace!("binary format? {}", binary); 467 | let format = match binary { 468 | true => Format::Binary, 469 | false => Format::AsciiArmor, 470 | }; 471 | 472 | let encryptor = Encryptor::with_recipients(recipients); 473 | let mut output = encryptor 474 | .wrap_output( 475 | ArmoredWriter::wrap_output(target, format) 476 | .wrap_err("Failed to wrap output with age::ArmoredWriter")?, 477 | ) 478 | .wrap_err("Failed to wrap output with age::Encryptor")?; 479 | 480 | output 481 | .write_all(&contents) 482 | .wrap_err("Failed to write encrypted contents")?; 483 | output 484 | .finish() 485 | .and_then(|armor| armor.finish()) 486 | .wrap_err("Failed to finish age transaction")?; 487 | 488 | Ok(()) 489 | } 490 | 491 | /// Open `target` for editing and wait for the user to complete the editing. 492 | fn try_edit_file(target: &Path) -> Result<()> { 493 | let (editor, args) = self::find_suitable_editor().wrap_err("Failed to find suitable editor")?; 494 | debug!("editor: '{}'", &editor); 495 | debug!("args: '{:?}'", &args); 496 | 497 | let cmd = Command::new(&editor) 498 | .args(args.unwrap_or_default()) 499 | .arg(target) 500 | .stdin(Stdio::inherit()) 501 | .stdout(Stdio::inherit()) 502 | .stderr(Stdio::piped()) 503 | .output() 504 | .wrap_err_with(|| format!("Failed to spawn editor '{}'", &editor))?; 505 | 506 | if !cmd.status.success() { 507 | let stderr = String::from_utf8_lossy(&cmd.stderr); 508 | 509 | Err(eyre!( 510 | "Editor '{}' exited with non-zero status code", 511 | &editor 512 | )) 513 | .with_section(|| stderr.trim().to_string().header("Stderr:")) 514 | } else { 515 | Ok(()) 516 | } 517 | } 518 | 519 | /// Try to decrypt the given target path with the specified identity. 520 | /// 521 | /// Uses [`get_identities`](get_identities) to find a valid identity. 522 | fn try_decrypt_target_with_identities( 523 | target: &Path, 524 | identities: &[String], 525 | encrypt_in_place: bool, 526 | ) -> Result>> { 527 | if target.exists() && target.is_file() { 528 | let f = File::open(target) 529 | .wrap_err_with(|| format!("Failed to open '{}'", &target.display()))?; 530 | let mut b = BufReader::new(f); 531 | let mut contents = Vec::new(); 532 | 533 | b.read_to_end(&mut contents) 534 | .wrap_err_with(|| format!("Failed to read '{}'", &target.display()))?; 535 | 536 | let dec = match Decryptor::new(ArmoredReader::new(&contents[..])) { 537 | Ok(_) if encrypt_in_place => { 538 | bail!( 539 | "File '{}' is already encrypted; refusing to encrypt in place", 540 | &target.display() 541 | ); 542 | } 543 | Err(_) if encrypt_in_place => contents, 544 | Ok(decryptor) => match decryptor { 545 | Decryptor::Recipients(d) => { 546 | let mut decrypted = Vec::new(); 547 | let ids = self::get_identities(identities.to_vec()) 548 | .wrap_err("Failed to get usable identity or identities")?; 549 | let mut reader = d 550 | .decrypt(ids.iter().map(|i| i.as_ref() as &dyn age::Identity)) 551 | .wrap_err("Failed to decrypt contents")?; 552 | 553 | reader 554 | .read_to_end(&mut decrypted) 555 | .wrap_err("Failed to read decrypted contents")?; 556 | 557 | decrypted 558 | } 559 | Decryptor::Passphrase(_) => { 560 | bail!("Age password-encrypted files are not supported"); 561 | } 562 | }, 563 | Err(e) => Err(e) 564 | .wrap_err_with(|| format!("Failed to parse header of '{}'", &target.display()))?, 565 | }; 566 | 567 | Ok(Some(dec)) 568 | } else { 569 | info!( 570 | "specified path '{}' does not exist; not decrypting", 571 | target.display() 572 | ); 573 | 574 | Ok(None) 575 | } 576 | } 577 | 578 | /// Tries parsing the given key into a Recipient. Returns None if parsing fails 579 | fn try_parse_key_to_recipient(key: &str) -> Option> { 580 | if let Ok(pk) = key.parse::().map(Box::new) { 581 | trace!("got valid age identity '{}'", &key); 582 | Some(pk) 583 | } else if let Ok(pk) = key.parse::().map(Box::new) { 584 | trace!("got valid ssh identity '{}'", &key); 585 | Some(pk) 586 | } else { 587 | None 588 | } 589 | } 590 | 591 | /// Parses the recipients of a specified path from the `.agenix.toml` 592 | /// configuration. 593 | fn get_recipients_from_config( 594 | conf: &Config, 595 | target: &Path, 596 | ) -> Result>> { 597 | let mut recipients: Vec> = Vec::new(); 598 | let mut matches = 0; 599 | 600 | for path in &conf.agenix.paths { 601 | if path.identities.is_empty() && path.groups.is_empty() { 602 | warn!( 603 | "Path '{}' has no associated identities or groups", 604 | &target.display() 605 | ); 606 | } 607 | 608 | let target = self::normalize_path(target); 609 | let glob = glob::Pattern::new(&path.glob) 610 | .wrap_err_with(|| format!("Failed to construct glob pattern from '{}'", &path.glob))?; 611 | 612 | if glob.matches_path_with(&target, MATCH_OPTS) { 613 | let identities = { 614 | let mut ids = path.identities.clone(); 615 | 616 | for group in &path.groups { 617 | if let Some(i) = conf.agenix.groups.get(group) { 618 | ids.extend(i.clone()); 619 | } else { 620 | warn!("group '{}' doesn't reference the [groups] table", group); 621 | } 622 | } 623 | 624 | ids 625 | }; 626 | 627 | for key in identities { 628 | let key = match conf.agenix.identities.get(&key) { 629 | Some(key) => key, 630 | None => &key, 631 | }; 632 | 633 | match self::try_parse_key_to_recipient(key) { 634 | Some(pk) => recipients.push(pk), 635 | None => { 636 | warn!("identity '{}' either:", &key); 637 | warn!(" * isn't a valid age, ssh-rsa, or ssh-ed25519 public key; or"); 638 | warn!(" * doesn't reference the [identities] table"); 639 | } 640 | } 641 | } 642 | 643 | matches += 1; 644 | } 645 | } 646 | 647 | if matches == 0 { 648 | warn!( 649 | "Path '{}' is not matched by any configured glob pattern", 650 | &target.display() 651 | ); 652 | } else if 1 < matches { 653 | warn!( 654 | "Path '{}' is matched by more than one configured glob pattern", 655 | &target.display() 656 | ); 657 | } 658 | 659 | Ok(recipients) 660 | } 661 | 662 | /// Find an acceptable identity or identities to use for decryption. 663 | fn get_identities(mut identities: Vec) -> Result>> { 664 | let home = env::var("HOME").wrap_err("Failed to get $HOME")?; 665 | 666 | // Always try id_rsa and id_ed25519. This is consistent with the Go 667 | // implementation of `age`: 668 | // https://github.com/FiloSottile/age/blob/b47610677cea90662979854d63473c3cbdd5315f/cmd/age/age.go#L299-L314 669 | identities.extend_from_slice(&[ 670 | format!("{}/.ssh/id_rsa", home), 671 | format!("{}/.ssh/id_ed25519", home), 672 | ]); 673 | 674 | identities.retain(|id| fs::metadata(id).is_ok()); 675 | 676 | if !identities.is_empty() { 677 | debug!("using {:?} as identity file(s)", &identities); 678 | return age::cli_common::read_identities( 679 | identities, 680 | |s| eyre!(s), 681 | |s, e| eyre!("{}: {:?}", s, e), 682 | ); 683 | } 684 | 685 | Err(eyre!("No usable identity or identities")) 686 | } 687 | 688 | /// Looks for the directory that contains the config file. Used for resolving 689 | /// the contained paths. 690 | /// 691 | /// One can specify the `$AGENIX_ROOT` environment variable to set the root 692 | /// of the `agenix` configuration (requires `.agenix.toml` in this directory). 693 | /// This will prevent `agenix` from ascending the filesystem in search of 694 | /// `.agenix.toml`. 695 | fn find_config_dir() -> Result> { 696 | let mut path = env::current_dir().wrap_err("Failed to get current directory")?; 697 | 698 | if let Ok(root) = env::var("AGENIX_ROOT") { 699 | let dir = Path::new(&root) 700 | .canonicalize() 701 | .wrap_err_with(|| format!("Failed to canonicalize AGENIX_ROOT ('{}')", &root))?; 702 | 703 | if dir.is_dir() { 704 | return Ok(Some(dir)); 705 | } else { 706 | warn!("AGENIX_ROOT ('{}') isn't a directory", dir.display()) 707 | } 708 | } else { 709 | for _ in 0..MAX_DEPTH { 710 | debug!("checking '{}' for .agenix.toml config", path.display()); 711 | let found = path.join(".agenix.toml"); 712 | 713 | if !found.exists() { 714 | path = path.join("..").canonicalize()?; 715 | } else { 716 | debug!("found config at '{}'", found.display()); 717 | return Ok(Some(path)); 718 | } 719 | } 720 | } 721 | 722 | Ok(None) 723 | } 724 | 725 | /// Read the config file and return its contents as a `String`. 726 | fn read_config(conf_path: &Path) -> Result { 727 | let file = File::open(conf_path.join(".agenix.toml")) 728 | .wrap_err_with(|| format!("Failed to find .agenix.toml in '{}'", &conf_path.display()))?; 729 | let mut buf = BufReader::new(file); 730 | let mut contents = String::new(); 731 | 732 | buf.read_to_string(&mut contents).wrap_err_with(|| { 733 | format!( 734 | "Failed to read contents of .agenix.toml in '{}'", 735 | &conf_path.display() 736 | ) 737 | })?; 738 | 739 | Ok(contents) 740 | } 741 | 742 | /// Create a tempfile in `$XDG_RUNTIME_DIR` (if set; falling back to `$TMPDIR` 743 | /// or `/tmp` if unset). 744 | fn create_temp_file(filename: &Path) -> Result { 745 | let filename = self::normalize_path(filename); 746 | let filename = format!("{}-", filename.display()); 747 | let filename = filename.replace(MAIN_SEPARATOR, "-"); 748 | let temp_dir = match env::var("XDG_RUNTIME_DIR") { 749 | Ok(v) => PathBuf::from(v), 750 | Err(_) => match env::var("TMPDIR") { 751 | Ok(v) => PathBuf::from(v), 752 | Err(_) => PathBuf::from("/tmp"), 753 | }, 754 | }; 755 | 756 | let temp_file = tempfile::Builder::new() 757 | .prefix(&filename) 758 | .tempfile_in(&temp_dir) 759 | .wrap_err_with(|| { 760 | format!( 761 | "Failed to create temporary file '{}' in '{}'", 762 | &filename, 763 | &temp_dir.display() 764 | ) 765 | })?; 766 | 767 | Ok(temp_file) 768 | } 769 | 770 | /// Parse the `EDITOR` and / or `VISUAL` environment variables to find 771 | /// a suitable editor. If the editor contains whitespace, split on it 772 | /// and treat the first split as the binary, and all following splits 773 | /// as arguments. 774 | fn find_suitable_editor() -> Result<(String, Option>)> { 775 | let editor = env::var("EDITOR") 776 | .or_else(|_| env::var("VISUAL")) 777 | .map_err(|e| eyre!(e))?; 778 | 779 | if editor.contains(' ') { 780 | let mut split = editor.split_ascii_whitespace(); 781 | let editor = split.next(); 782 | let args = split.map(String::from).collect::>(); 783 | 784 | Ok(( 785 | String::from(editor.ok_or_else(|| eyre!("EDITOR or VISUAL was empty"))?), 786 | Some(args), 787 | )) 788 | } else { 789 | Ok((editor, None)) 790 | } 791 | } 792 | 793 | // https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61-L86 794 | // 795 | // Permission is hereby granted, free of charge, to any person obtaining a copy 796 | // of this software and associated documentation files (the "Software"), to 797 | // deal in the Software without restriction, including without limitation the 798 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 799 | // sell copies of the Software, and to permit persons to whom the Software is 800 | // furnished to do so, subject to the following conditions: 801 | // 802 | // The above copyright notice and this permission notice shall be included in 803 | // all copies or substantial portions of the Software. 804 | // 805 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 806 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 807 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 808 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 809 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 810 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 811 | // SOFTWARE. 812 | 813 | /// Normalize the specified path by stripping `./` and resolving `../`, without 814 | /// actually resolving the path (like 815 | /// [`fs::canonicalize`](std::fs::canonicalize) does). 816 | fn normalize_path(path: &Path) -> PathBuf { 817 | let mut components = path.components().peekable(); 818 | let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { 819 | components.next(); 820 | PathBuf::from(c.as_os_str()) 821 | } else { 822 | PathBuf::new() 823 | }; 824 | 825 | for component in components { 826 | match component { 827 | Component::Prefix(..) => unreachable!(), 828 | Component::RootDir => { 829 | ret.push(component.as_os_str()); 830 | } 831 | Component::ParentDir => { 832 | ret.pop(); 833 | } 834 | Component::CurDir => {} 835 | Component::Normal(c) => { 836 | ret.push(c); 837 | } 838 | } 839 | } 840 | 841 | ret 842 | } 843 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use color_eyre::eyre::Result; 4 | 5 | mod cli; 6 | 7 | #[doc(hidden)] 8 | fn main() -> Result<()> { 9 | color_eyre::config::HookBuilder::default() 10 | .display_env_section(false) 11 | .install()?; 12 | 13 | if let Err(e) = cli::run() { 14 | writeln!(io::stderr(), "Error: {:?}", e)?; 15 | 16 | std::process::exit(1); 17 | } 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /tests/machine1: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAYEAwSg9GAQPb4zYiIQTFWYCIxY2NvNgX0eiTPOB8HHpjd17f71mh9ZO 4 | d0MyagBdy6iQYrD/f8QksvgS/HFFdM/YKHot8Tq1iH0U5NB6ecWLFaOzzr6tWkN8fnxY9y 5 | wDv+xzGF/1Q9I3x4pb+PRw9HkDojadE/zitzbrhSZodlquORom1kTes3T+NrQfuHirCX+6 6 | yFfpHHcmw5WGBIsgmxgHCjKnKcxftwkkEm6gPl1j4smoFE7nJ6GpI3Cb2Quij3ytfPbdSm 7 | FftaSgdZQ9TCyafuY4mmRRchQEieowiATvopfV6wjA1TvUbnMWPPUwSQaduH3foWMLB92M 8 | SDzf3rJ37UXj5CBW8CB6Lrf/SVZBTaVnhurzkXQJZ5MmJq/JTbKHYhgJpxjd3YgGDXJj6k 9 | P/ZvbqL6bRWVgwfMlxG53dw2Zk27soZa52qqkhO9qdmvFkqha+mk9hdX5OwCi8yRk+vxq4 10 | ZP+5l5KheVuxZyj+g/16mHjWQNTYs1HcWaKUiROnAAAFiLsV99a7FffWAAAAB3NzaC1yc2 11 | EAAAGBAMEoPRgED2+M2IiEExVmAiMWNjbzYF9HokzzgfBx6Y3de3+9ZofWTndDMmoAXcuo 12 | kGKw/3/EJLL4EvxxRXTP2Ch6LfE6tYh9FOTQennFixWjs86+rVpDfH58WPcsA7/scxhf9U 13 | PSN8eKW/j0cPR5A6I2nRP84rc264UmaHZarjkaJtZE3rN0/ja0H7h4qwl/ushX6Rx3JsOV 14 | hgSLIJsYBwoypynMX7cJJBJuoD5dY+LJqBRO5yehqSNwm9kLoo98rXz23UphX7WkoHWUPU 15 | wsmn7mOJpkUXIUBInqMIgE76KX1esIwNU71G5zFjz1MEkGnbh936FjCwfdjEg8396yd+1F 16 | 4+QgVvAgei63/0lWQU2lZ4bq85F0CWeTJiavyU2yh2IYCacY3d2IBg1yY+pD/2b26i+m0V 17 | lYMHzJcRud3cNmZNu7KGWudqqpITvanZrxZKoWvppPYXV+TsAovMkZPr8auGT/uZeSoXlb 18 | sWco/oP9eph41kDU2LNR3FmilIkTpwAAAAMBAAEAAAGBAIbeUcozC5QYBBlOHwUR9OwK1l 19 | AIH4Jnwit424HICAvGWjUXSkAOozsi8FCTcOqDCE3gyIWtrvJHxPn/HUAy3/tjVDDbjWV/ 20 | 8NbTurDL/hFd/G6fNOuRs5udt4Deer/HmZWratWwMsJhGVb7VuNcm+zntUX/jU3gSxLtfo 21 | MlTgERAY0bCWTEi7wmiP+FQeegCAtbVqlJiQeHn8wMWPfyDvhP5eiJi0uCVm2IUNs62kPA 22 | Clwju8MwXakhN7CVLyyBIGnjIeNgafBvxASvtAKYtPiLRchCt+RDGjtVWwvLhJ6rOQDW9c 23 | niRx6KjPW7RdaZfCF4/VRlfynJZdl3g/V08fXSIXJow6kHXKYMFU1PpdrCYpptKklSO5If 24 | dhTaDkxBLX3jKPb381gIkHorh5JPjVSgSUiyUvCxVvu6YTCH46h+d4+Ef0hNnsn51C4pDV 25 | W2Vr0S7yjjjC0+Sd2Am3MftUXCwg/UmoOvBvKq6dx5M731unbSHRm8kjjtkMTSxiWIAQAA 26 | AMBm/XlCYQc8d9xhqE5R6gb1u/QyDJhA9MOztnD3FvKQHiUsGl95cCOIiA9iNbbFByMhk/ 27 | PDpU3ybO8OEd2OzKHB4uSR2ul5CI2tH77DR5K4Cul+edbmbIAlht/CCht92D8ZGhIk6cWE 28 | AoVXk/CjF9Y9Brv63Hd2fSPpEJLWXs5MiJpOFTcvwYiIZhCeJpauCXqd28timn7tmU/HJh 29 | aQMC7dOkZwgiPBPnZJ1LP28lT+1TUaVZIp4MrligKJxr6SObkAAADBAPT4OXVjm4MSa6sG 30 | 2Fsy39BqrB3ha2GmHoRA277n+xyPrFYnSvOebKzHPLISe0G34fu1FMpIC9GRy9+lpj6v9J 31 | LHHwT7m1YPVI3IzCQxozTrDtIRqkSMe7viB4E1QHXWGbXJ3yhrDSiXBX7aZTbMemUgu7jO 32 | HdNkKUj/ELG/AVM8zsuzQwHHVm8lKpR9/TXP9jRCGARa/yVXmIJbQ1rh3ioWvgRbeXTi3A 33 | P4+mmTMga2hkhRQJ8gG+Iaw81wwWvytwAAAMEAydrFF+6ndb08X+ysoFhMqor0NZJLLAbK 34 | di2ud/rZbzUkrjZebMIwQWS2z5VlFc/ht9UmyDscfGyann2jtj+Ei+HRBQnvaxoQ91pHxP 35 | soEESQwgITpbfoDUk93IbejxeKUlf5TALK44J/pYffkD/Ylis6aIykSiLjgQfMTVr9G3lA 36 | DeeW6E5hn0V+2BIt1BphSHv3hs/syAlm9WtpB3xA3WJ1FPfLos/vhNxKGr+ZVrwTklJwJm 37 | VdR17Ix8nuXjaRAAAADHZpbkBzY2FkcmlhbAECAwQFBg== 38 | -----END OPENSSH PRIVATE KEY----- 39 | -------------------------------------------------------------------------------- /tests/machine1.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBKD0YBA9vjNiIhBMVZgIjFjY282BfR6JM84HwcemN3Xt/vWaH1k53QzJqAF3LqJBisP9/xCSy+BL8cUV0z9goei3xOrWIfRTk0Hp5xYsVo7POvq1aQ3x+fFj3LAO/7HMYX/VD0jfHilv49HD0eQOiNp0T/OK3NuuFJmh2Wq45GibWRN6zdP42tB+4eKsJf7rIV+kcdybDlYYEiyCbGAcKMqcpzF+3CSQSbqA+XWPiyagUTucnoakjcJvZC6KPfK189t1KYV+1pKB1lD1MLJp+5jiaZFFyFASJ6jCIBO+il9XrCMDVO9RucxY89TBJBp24fd+hYwsH3YxIPN/esnftRePkIFbwIHout/9JVkFNpWeG6vORdAlnkyYmr8lNsodiGAmnGN3diAYNcmPqQ/9m9uovptFZWDB8yXEbnd3DZmTbuyhlrnaqqSE72p2a8WSqFr6aT2F1fk7AKLzJGT6/Grhk/7mXkqF5W7FnKP6D/XqYeNZA1NizUdxZopSJE6c= 2 | -------------------------------------------------------------------------------- /tests/machine2: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACAmNWcX+EwWpuS2HxYTr9w/Whb7Qa35/qyhz86aws7+WwAAAJA/iWR7P4lk 4 | ewAAAAtzc2gtZWQyNTUxOQAAACAmNWcX+EwWpuS2HxYTr9w/Whb7Qa35/qyhz86aws7+Ww 5 | AAAEDjV9HJYedWUDXn079RvEpdX1TQJZxoHzmuNbucF5exgSY1Zxf4TBam5LYfFhOv3D9a 6 | FvtBrfn+rKHPzprCzv5bAAAADHZpbkBzY2FkcmlhbAE= 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /tests/machine2.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICY1Zxf4TBam5LYfFhOv3D9aFvtBrfn+rKHPzprCzv5b 2 | -------------------------------------------------------------------------------- /tests/user1: -------------------------------------------------------------------------------- 1 | # created: 2021-01-28T16:19:00-08:00 2 | # public key: age1szr8hp9lrjvc2d2hnr9c56fcj6f5ngnjy8gldnu6qtejnjrp6pmsc47jw8 3 | AGE-SECRET-KEY-1DDDX0RMJR7MN2XF7KTKSAT6V7XVC9T4HXXZJE7KTZJRHVW097D5QG3NJJK 4 | -------------------------------------------------------------------------------- /tests/user2: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACCmxvPiMlM5tr9+5PkoocCefSYGWLIFPf3CQ1M3mVL4LAAAAJAVZdxeFWXc 4 | XgAAAAtzc2gtZWQyNTUxOQAAACCmxvPiMlM5tr9+5PkoocCefSYGWLIFPf3CQ1M3mVL4LA 5 | AAAEAdmGA3elR+98A/T4qj5l1RUoZ/4ZdF20AMERYiE74Z3abG8+IyUzm2v37k+SihwJ59 6 | JgZYsgU9/cJDUzeZUvgsAAAADHZpbkBzY2FkcmlhbAE= 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /tests/user2.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKbG8+IyUzm2v37k+SihwJ59JgZYsgU9/cJDUzeZUvgs 2 | --------------------------------------------------------------------------------