From 559f00e65c137af63acc4299bbd1e21e706d94cb Mon Sep 17 00:00:00 2001 From: Philip-NLnetLabs Date: Fri, 11 Jul 2025 14:38:21 +0200 Subject: [PATCH] Use clap to parse keyset subcommands (#100) Co-authored-by: Terts Diepraam --- .github/workflows/ci.yml | 4 +- Cargo.lock | 204 ++-- Cargo.toml | 2 +- src/commands/keygen.rs | 4 +- src/commands/keyset.rs | 2019 ++++++++++++++++++++----------------- src/commands/notify.rs | 2 +- src/commands/nsec3hash.rs | 2 +- src/commands/signzone.rs | 2 +- 8 files changed, 1249 insertions(+), 990 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dadfad..2563af1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [1.79.0, stable, beta, nightly] + rust: [1.82.0, stable, beta, nightly] env: RUSTFLAGS: "-D warnings" # We use 'vcpkg' to install OpenSSL on Windows. @@ -53,7 +53,7 @@ jobs: - name: Install Rust uses: hecrj/setup-rust-action@v2 with: - rust-version: "1.79.0" + rust-version: "1.82.0" - name: Install OpenSSL run: sudo apt-get install -y libssl-dev - name: Install nightly Rust diff --git a/Cargo.lock b/Cargo.lock index 5eafa9f..c040ac5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,9 +116,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -132,7 +132,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -143,9 +143,9 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -155,9 +155,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.26" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "shlex", ] @@ -184,9 +184,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -206,9 +206,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -353,7 +353,7 @@ dependencies = [ [[package]] name = "domain" version = "0.11.1-dev" -source = "git+https://github.com/NLnetLabs/domain.git?branch=crypto-and-keyset-fixes#422be4cc11219c754cfc90cb6f84f994f3e560b3" +source = "git+https://github.com/NLnetLabs/domain.git?branch=crypto-and-keyset-fixes#0fa094bb9812b0ebcf8a2651e4f46d5923505d8e" dependencies = [ "arc-swap", "bumpalo", @@ -385,7 +385,7 @@ dependencies = [ [[package]] name = "domain-macros" version = "0.11.1-dev" -source = "git+https://github.com/NLnetLabs/domain.git?branch=crypto-and-keyset-fixes#422be4cc11219c754cfc90cb6f84f994f3e560b3" +source = "git+https://github.com/NLnetLabs/domain.git?branch=crypto-and-keyset-fixes#0fa094bb9812b0ebcf8a2651e4f46d5923505d8e" dependencies = [ "proc-macro2", "quote", @@ -400,12 +400,12 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -685,6 +685,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -699,9 +710,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", @@ -712,9 +723,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", @@ -745,9 +756,9 @@ checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" @@ -974,7 +985,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1070,9 +1081,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -1126,9 +1137,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] @@ -1313,12 +1324,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" @@ -1350,9 +1358,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.102" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1417,12 +1425,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -1468,15 +1475,17 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -1518,9 +1527,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -1727,9 +1736,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.2" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b85ac982d24496e0b49912479da8a89c0ba7b1d8bf6de49df12af42e94b325" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", "windows-core", @@ -1795,9 +1804,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3bfe459f85da17560875b8bf1423d6f113b7a87a5d942e7da0ac71be7c61f8b" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" @@ -1833,7 +1842,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1842,7 +1851,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", ] [[package]] @@ -1851,14 +1869,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1876,48 +1910,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -1965,18 +2047,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 88fd612..5351d89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ description = "Rust reimplementation of important ldns programs." categories = ["command-line-utilities"] license = "BSD-3-Clause" keywords = ["DNS", "domain", "ldns"] -rust-version = "1.79" +rust-version = "1.82" [[bin]] name = "ldns" diff --git a/src/commands/keygen.rs b/src/commands/keygen.rs index 86fd91c..415274b 100644 --- a/src/commands/keygen.rs +++ b/src/commands/keygen.rs @@ -136,7 +136,7 @@ fn ldns_algs_help() -> String { } } - format!("{}", Printer) + format!("{Printer}") } impl LdnsCommand for Keygen { @@ -387,7 +387,7 @@ impl Keygen { } // Let the user know what the base name of the files is. - writeln!(stdout, "{}", base); + writeln!(stdout, "{base}"); Ok(()) } diff --git a/src/commands/keyset.rs b/src/commands/keyset.rs index 7a2a987..1848474 100644 --- a/src/commands/keyset.rs +++ b/src/commands/keyset.rs @@ -2,6 +2,7 @@ use crate::env::Env; use crate::error::Error; use crate::util; use bytes::Bytes; +use clap::Subcommand; use domain::base::iana::Class; use domain::base::iana::{DigestAlgorithm, SecurityAlgorithm}; use domain::base::zonefile_fmt::{DisplayKind, ZonefileFmt}; @@ -38,25 +39,167 @@ pub struct Keyset { #[arg(short = 'c')] keyset_conf: PathBuf, - /// Domain name (only for create) - #[arg(short = 'n')] - domain_name: Option>>, - - /// State file (only for create) - #[arg(short = 's')] - keyset_state: Option, - - #[arg(short = 't')] - ttl: Option, - - #[arg(short = 'b')] - bits: Option, - /// Subcommand - #[arg()] - cmd: String, + #[command(subcommand)] + cmd: Commands, +} - value: Option, +type OptDuration = Option; + +#[derive(Clone, Debug, Subcommand)] +enum Commands { + Create { + /// Domain name + #[arg(short = 'n')] + domain_name: Name>, + + /// State file + #[arg(short = 's')] + keyset_state: PathBuf, + }, + + Init, + StartKskRoll, + StartZskRoll, + StartCskRoll, + StartAlgorithmRoll, + KskPropagation1Complete { + ttl: u32, + }, + KskPropagation2Complete { + ttl: u32, + }, + ZskPropagation1Complete { + ttl: u32, + }, + ZskPropagation2Complete { + ttl: u32, + }, + CskPropagation1Complete { + ttl: u32, + }, + CskPropagation2Complete { + ttl: u32, + }, + AlgorithmPropagation1Complete { + ttl: u32, + }, + AlgorithmPropagation2Complete { + ttl: u32, + }, + KskCacheExpired1, + KskCacheExpired2, + ZskCacheExpired1, + ZskCacheExpired2, + CskCacheExpired1, + CskCacheExpired2, + AlgorithmCacheExpired1, + AlgorithmCacheExpired2, + KskRollDone, + ZskRollDone, + CskRollDone, + AlgorithmRollDone, + Status, + Actions, + Keys, + + Get { + #[command(subcommand)] + subcommand: GetCommands, + }, + + Set { + #[command(subcommand)] + subcommand: SetCommands, + }, + + Show, + Cron, +} + +#[derive(Clone, Debug, Subcommand)] +enum GetCommands { + UseCsk, + Autoremove, + KskAlgorithm, + ZskAlgorithm, + CskAlgorithm, + DsAlgorithm, + DnskeyLifetime, + CdsLifetime, + Dnskey, + Cds, + Ds, +} + +#[derive(Clone, Debug, Subcommand)] +enum SetCommands { + UseCsk { + #[arg(action = clap::ArgAction::Set)] + boolean: bool, + }, + Autoremove { + #[arg(action = clap::ArgAction::Set)] + boolean: bool, + }, + KskAlgorithm { + #[arg(short = 'b')] + bits: Option, + + algorithm: String, + }, + ZskAlgorithm { + #[arg(short = 'b')] + bits: Option, + + algorithm: String, + }, + CskAlgorithm { + #[arg(short = 'b')] + bits: Option, + + algorithm: String, + }, + DsAlgorithm { + #[arg(value_parser = DsAlgorithm::new)] + algorithm: DsAlgorithm, + }, + DnskeyInceptionOffset { + #[arg(value_parser = parse_duration)] + duration: Duration, + }, + DnskeyLifetime { + #[arg(value_parser = parse_duration)] + duration: Duration, + }, + DnskeyRemainTime { + #[arg(value_parser = parse_duration)] + duration: Duration, + }, + CdsInceptionOffset { + #[arg(value_parser = parse_duration)] + duration: Duration, + }, + CdsLifetime { + #[arg(value_parser = parse_duration)] + duration: Duration, + }, + CdsRemainTime { + #[arg(value_parser = parse_duration)] + duration: Duration, + }, + KskValidity { + #[arg(value_parser = parse_opt_duration)] + opt_duration: OptDuration, + }, + ZskValidity { + #[arg(value_parser = parse_opt_duration)] + opt_duration: OptDuration, + }, + CskValidity { + #[arg(value_parser = parse_opt_duration)] + opt_duration: OptDuration, + }, } impl Keyset { @@ -67,20 +210,17 @@ impl Keyset { /// Run the command as an async function pub async fn run(self, env: &impl Env) -> Result<(), Error> { - if self.cmd == "create" { - let domainname = self - .domain_name - .ok_or::("domain name option expected\n".into())?; - - let state_file = self - .keyset_state - .ok_or::("state file option expected\n".into())?; - let state_file = absolute(&state_file).map_err::(|e| { - format!("unable to make {} absolute: {}", state_file.display(), e).into() + if let Commands::Create { + domain_name, + keyset_state, + } = self.cmd + { + let state_file = absolute(&keyset_state).map_err::(|e| { + format!("unable to make {} absolute: {}", keyset_state.display(), e).into() })?; let keys_dir = make_parent_dir(state_file.clone()); - let ks = KeySet::new(domainname); + let ks = KeySet::new(domain_name); let kss = KeySetState { keyset: ks, dnskey_rrset: Vec::new(), @@ -155,908 +295,841 @@ impl Keyset { let mut config_changed = false; let mut state_changed = false; - if self.cmd == "init" { - // Check for re-init. - if !kss.keyset.keys().is_empty() { - // Avoid re-init. - return Err("already initialized\n".into()); - } - - // Check for CSK. - let actions = if ksc.use_csk { - // Generate CSK. - let (csk_pub_name, csk_priv_name, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.csk_generate_params.to_generate_params(), - true, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - kss.keyset - .add_key_csk( - csk_pub_name.to_string(), - Some(csk_priv_name.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .expect("should not happen"); - - kss.keyset - .start_roll(RollType::AlgorithmRoll, &[], &[csk_pub_name.as_str()]) - .expect("should not happen") - } else { - let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.ksk_generate_params.to_generate_params(), - true, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - kss.keyset - .add_key_ksk( - ksk_pub_url.to_string(), - Some(ksk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .expect("should not happen"); - let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.zsk_generate_params.to_generate_params(), - false, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - kss.keyset - .add_key_zsk( - zsk_pub_url.to_string(), - Some(zsk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .expect("should not happen"); - - let new = [ksk_pub_url.as_ref(), zsk_pub_url.as_ref()]; - kss.keyset - .start_roll(RollType::AlgorithmRoll, &[], &new) - .expect("should not happen") - }; - - handle_actions(&actions, &ksc, &mut kss, env)?; - - print_actions(&actions); - state_changed = true; - } else if self.cmd == "start-ksk-roll" { - if kss.keyset.keys().is_empty() { - // Avoid KSK roll without init. - return Err("not yet initialized\n".into()); - } - - // Check for CSK. - if ksc.use_csk { - return Err("wrong key roll, use start-csk-roll\n".into()); - } - - // Refuse if we can find a CSK key. - if kss - .keyset - .keys() - .iter() - .any(|(_, key)| matches!(key.keytype(), KeyType::Csk(_, _))) - { - return Err("cannot start key roll, found CSK\n".into()); - } - - // Find existing KSKs. Do we complain if there is none? - let old_stored: Vec<_> = kss - .keyset - .keys() - .iter() - .filter(|(_, key)| { - if let KeyType::Ksk(keystate) = key.keytype() { - !keystate.old() - || keystate.signer() - || keystate.present() - || keystate.at_parent() - } else { - false - } - }) - .map(|(name, _)| name.clone()) - .collect(); - let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); - - // Collect algorithms. Maybe this needs to be in the library. - - // Create a new KSK - let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.ksk_generate_params.to_generate_params(), - true, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - kss.keyset - .add_key_ksk( - ksk_pub_url.to_string(), - Some(ksk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .map_err::(|e| { - format!("unable to add KSK {ksk_pub_url}: {e}\n").into() - })?; - - let new = [ksk_pub_url.as_ref()]; - - // Start the key roll - let actions = match kss - .keyset - .start_roll(RollType::KskRoll, &old, &new) - .map_err::(|e| format!("cannot start roll: {e}\n").into()) - { - Ok(actions) => actions, - Err(e) => { - // Remove the key files we just created. - if ksk_priv_url.scheme() == "file" { - remove_file(ksk_priv_url.path()).map_err::(|e| { - format!("unable to remove private key file {ksk_priv_url}: {e}\n") - .into() - })?; - } else { - panic!("unsupported URL scheme in {ksk_priv_url}"); - } - - if ksk_pub_url.scheme() == "file" { - remove_file(ksk_pub_url.path()).map_err::(|e| { - format!("unable to remove public key file {ksk_pub_url}: {e}\n").into() - })?; - } else { - panic!("unsupported URL scheme in {ksk_pub_url}"); - } - - return Err(e); + match self.cmd { + Commands::Create { .. } => unreachable!(), + Commands::Init => { + // Check for re-init. + if !kss.keyset.keys().is_empty() { + // Avoid re-init. + return Err("already initialized\n".into()); } - }; - handle_actions(&actions, &ksc, &mut kss, env)?; - print_actions(&actions); - state_changed = true; - } else if self.cmd == "start-zsk-roll" { - if kss.keyset.keys().is_empty() { - // Avoid ZSK roll without init. - return Err("not yet initialized\n".into()); + // Check for CSK. + let actions = if ksc.use_csk { + // Generate CSK. + let (csk_pub_name, csk_priv_name, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.csk_generate_params.to_generate_params(), + true, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + kss.keyset + .add_key_csk( + csk_pub_name.to_string(), + Some(csk_priv_name.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .expect("should not happen"); + + kss.keyset + .start_roll(RollType::AlgorithmRoll, &[], &[csk_pub_name.as_str()]) + .expect("should not happen") + } else { + let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.ksk_generate_params.to_generate_params(), + true, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + kss.keyset + .add_key_ksk( + ksk_pub_url.to_string(), + Some(ksk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .expect("should not happen"); + let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.zsk_generate_params.to_generate_params(), + false, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + kss.keyset + .add_key_zsk( + zsk_pub_url.to_string(), + Some(zsk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .expect("should not happen"); + + let new = [ksk_pub_url.as_ref(), zsk_pub_url.as_ref()]; + kss.keyset + .start_roll(RollType::AlgorithmRoll, &[], &new) + .expect("should not happen") + }; + + handle_actions(&actions, &ksc, &mut kss, env)?; + + print_actions(&actions); + state_changed = true; } - - // Check for CSK. - if ksc.use_csk { - return Err("wrong key roll, use start-csk-roll\n".into()); - } - - // Refuse if we can find a CSK key. - if kss - .keyset - .keys() - .iter() - .any(|(_, key)| matches!(key.keytype(), KeyType::Csk(_, _))) - { - return Err("cannot start key roll, found CSK\n".into()); - } - - // Find existing ZSKs. Do we complain if there is none? - let old_stored: Vec<_> = kss - .keyset - .keys() - .iter() - .filter(|(_, key)| { - if let KeyType::Zsk(keystate) = key.keytype() { - !keystate.old() || keystate.signer() || keystate.present() - } else { - false - } - }) - .map(|(name, _)| name.clone()) - .collect(); - let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); - - // Collect algorithms. Maybe this needs to be in the library. - - // Create a new ZSK - let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.zsk_generate_params.to_generate_params(), - false, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - kss.keyset - .add_key_zsk( - zsk_pub_url.to_string(), - Some(zsk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .map_err::(|e| { - format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() - })?; - - let new = [zsk_pub_url.as_ref()]; - - // Start the key roll - let actions = match kss - .keyset - .start_roll(RollType::ZskRoll, &old, &new) - .map_err::(|e| format!("cannot start roll: {e}\n").into()) - { - Ok(actions) => actions, - Err(e) => { - // Remove the key files we just created. - if zsk_priv_url.scheme() == "file" { - remove_file(zsk_priv_url.path()).map_err::(|e| { - format!("unable to remove private key file {zsk_priv_url}: {e}\n") - .into() - })?; - } else { - panic!("unsupported URL scheme in {zsk_priv_url}"); - } - if zsk_pub_url.scheme() == "file" { - remove_file(zsk_pub_url.path()).map_err::(|e| { - format!("unable to remove public key file {zsk_pub_url}: {e}\n").into() - })?; - } else { - panic!("unsupported URL scheme in {zsk_pub_url}"); - } - return Err(e); + Commands::StartKskRoll => { + if kss.keyset.keys().is_empty() { + // Avoid KSK roll without init. + return Err("not yet initialized\n".into()); } - }; - handle_actions(&actions, &ksc, &mut kss, env)?; - print_actions(&actions); - state_changed = true; - } else if self.cmd == "start-csk-roll" { - // Find existing KSKs, ZSKs and CSKs. Do we complain if there - // are none? - let old_stored: Vec<_> = kss - .keyset - .keys() - .iter() - .filter(|(_, key)| match key.keytype() { - KeyType::Ksk(keystate) | KeyType::Zsk(keystate) | KeyType::Csk(keystate, _) => { - // Assume that for a CSK it is sufficient to check - // one of the key states. Also assume that we - // can check at_parent for a ZSK. - !keystate.old() - || keystate.signer() - || keystate.present() - || keystate.at_parent() - } - KeyType::Include(_) => false, - }) - .map(|(name, _)| name.clone()) - .collect(); - let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); - - // Collect algorithms. Maybe this needs to be in the library. - - let (new_stored, new_urls) = if ksc.use_csk { - let mut new_urls = Vec::new(); - - // Create a new CSK - let (csk_pub_url, csk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.csk_generate_params.to_generate_params(), - true, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - new_urls.push(csk_priv_url.clone()); - new_urls.push(csk_pub_url.clone()); - kss.keyset - .add_key_csk( - csk_pub_url.to_string(), - Some(csk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .map_err::(|e| { - format!("unable to add CSK {csk_pub_url}: {e}\n").into() - })?; - - let new = vec![csk_pub_url]; - (new, new_urls) - } else { - let mut new_urls = Vec::new(); - - // Create a new KSK - let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.ksk_generate_params.to_generate_params(), - true, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - new_urls.push(ksk_priv_url.clone()); - new_urls.push(ksk_pub_url.clone()); - kss.keyset - .add_key_ksk( - ksk_pub_url.to_string(), - Some(ksk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .map_err::(|e| { - format!("unable to add KSK {ksk_pub_url}: {e}\n").into() - })?; - - // Create a new ZSK - let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.zsk_generate_params.to_generate_params(), - false, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - new_urls.push(zsk_priv_url.clone()); - new_urls.push(zsk_pub_url.clone()); - kss.keyset - .add_key_zsk( - zsk_pub_url.to_string(), - Some(zsk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .map_err::(|e| { - format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() - })?; - - let new = vec![ksk_pub_url, zsk_pub_url]; - (new, new_urls) - }; - - let new: Vec<_> = new_stored.iter().map(|v| v.as_ref()).collect(); - - // Start the key roll - let actions = match kss - .keyset - .start_roll(RollType::CskRoll, &old, &new) - .map_err::(|e| format!("cannot start roll: {e}\n").into()) - { - Ok(actions) => actions, - Err(e) => { - // Remove the key files we just created. - for u in new_urls { - if u.scheme() == "file" { - remove_file(u.path()).map_err::(|e| { - format!("unable to remove private key file {u}: {e}\n").into() - })?; - } else { - panic!("unsupported URL scheme in {u}"); - } - } - return Err(e); + // Check for CSK. + if ksc.use_csk { + return Err("wrong key roll, use start-csk-roll\n".into()); } - }; - handle_actions(&actions, &ksc, &mut kss, env)?; - - print_actions(&actions); - state_changed = true; - } else if self.cmd == "start-algorithm-roll" { - // Find existing KSKs, ZSKs and CSKs. Do we complain if there - // are none? - let old_stored: Vec<_> = kss - .keyset - .keys() - .iter() - .filter(|(_, key)| match key.keytype() { - KeyType::Ksk(keystate) | KeyType::Zsk(keystate) | KeyType::Csk(keystate, _) => { - // Assume that for a CSK it is sufficient to check - // one of the key states. Also assume that we - // can check at_parent for a ZSK. - !keystate.old() - || keystate.signer() - || keystate.present() - || keystate.at_parent() - } - KeyType::Include(_) => false, - }) - .map(|(name, _)| name.clone()) - .collect(); - let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); - - let (new_stored, new_urls) = if ksc.use_csk { - let mut new_urls = Vec::new(); - - // Create a new CSK - let (csk_pub_url, csk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.csk_generate_params.to_generate_params(), - true, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - new_urls.push(csk_priv_url.clone()); - new_urls.push(csk_pub_url.clone()); - kss.keyset - .add_key_csk( - csk_pub_url.to_string(), - Some(csk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .map_err::(|e| { - format!("unable to add CSK {csk_pub_url}: {e}\n").into() - })?; - - let new = vec![csk_pub_url]; - (new, new_urls) - } else { - let mut new_urls = Vec::new(); - - // Create a new KSK - let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.ksk_generate_params.to_generate_params(), - true, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - new_urls.push(ksk_priv_url.clone()); - new_urls.push(ksk_pub_url.clone()); - kss.keyset - .add_key_ksk( - ksk_pub_url.to_string(), - Some(ksk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .map_err::(|e| { - format!("unable to add KSK {ksk_pub_url}: {e}\n").into() - })?; - - // Create a new ZSK - let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( - kss.keyset.name(), - ksc.zsk_generate_params.to_generate_params(), - false, - kss.keyset.keys(), - &ksc.keys_dir, - env, - )?; - new_urls.push(zsk_priv_url.clone()); - new_urls.push(zsk_pub_url.clone()); - kss.keyset - .add_key_zsk( - zsk_pub_url.to_string(), - Some(zsk_priv_url.to_string()), - algorithm, - key_tag, - UnixTime::now(), - ) - .map_err::(|e| { - format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() - })?; - - let new = vec![ksk_pub_url, zsk_pub_url]; - (new, new_urls) - }; - - let new: Vec<_> = new_stored.iter().map(|v| v.as_ref()).collect(); - - // Start the key roll - let actions = match kss - .keyset - .start_roll(RollType::AlgorithmRoll, &old, &new) - .map_err::(|e| format!("cannot start roll: {e}\n").into()) - { - Ok(actions) => actions, - Err(e) => { - // Remove the key files we just created. - for u in new_urls { - if u.scheme() == "file" { - remove_file(u.path()).map_err::(|e| { - format!("unable to private key file {u}: {e}\n").into() - })?; - } else { - panic!("unsupported scheme in {u}"); - } - } - return Err(e); + // Refuse if we can find a CSK key. + if kss + .keyset + .keys() + .iter() + .any(|(_, key)| matches!(key.keytype(), KeyType::Csk(_, _))) + { + return Err("cannot start key roll, found CSK\n".into()); } - }; - handle_actions(&actions, &ksc, &mut kss, env)?; - - print_actions(&actions); - state_changed = true; - } else if self.cmd == "ksk-propagation1-complete" - || self.cmd == "ksk-propagation2-complete" - || self.cmd == "zsk-propagation1-complete" - || self.cmd == "zsk-propagation2-complete" - || self.cmd == "csk-propagation1-complete" - || self.cmd == "csk-propagation2-complete" - || self.cmd == "algorithm-propagation1-complete" - || self.cmd == "algorithm-propagation2-complete" - { - let Some(ttl) = self.ttl else { - return Err("ttl option is required\n".into()); - }; - let actions = if self.cmd == "ksk-propagation1-complete" { - kss.keyset.propagation1_complete(RollType::KskRoll, ttl) - } else if self.cmd == "ksk-propagation2-complete" { - kss.keyset.propagation2_complete(RollType::KskRoll, ttl) - } else if self.cmd == "zsk-propagation1-complete" { - kss.keyset.propagation1_complete(RollType::ZskRoll, ttl) - } else if self.cmd == "zsk-propagation2-complete" { - kss.keyset.propagation2_complete(RollType::ZskRoll, ttl) - } else if self.cmd == "csk-propagation1-complete" { - kss.keyset.propagation1_complete(RollType::CskRoll, ttl) - } else if self.cmd == "csk-propagation2-complete" { - kss.keyset.propagation2_complete(RollType::CskRoll, ttl) - } else if self.cmd == "algorithm-propagation1-complete" { - kss.keyset - .propagation1_complete(RollType::AlgorithmRoll, ttl) - } else if self.cmd == "algorithm-propagation2-complete" { - kss.keyset - .propagation2_complete(RollType::AlgorithmRoll, ttl) - } else { - unreachable!(); - }; - - let actions = match actions { - Ok(actions) => actions, - Err(err) => { - return Err(format!("Error reporting propagation complete: {err}\n").into()); - } - }; - - // Handle error - - handle_actions(&actions, &ksc, &mut kss, env)?; - - // Report actions - print_actions(&actions); - state_changed = true; - } else if self.cmd == "ksk-cache-expired1" - || self.cmd == "ksk-cache-expired2" - || self.cmd == "zsk-cache-expired1" - || self.cmd == "zsk-cache-expired2" - || self.cmd == "csk-cache-expired1" - || self.cmd == "csk-cache-expired2" - || self.cmd == "algorithm-cache-expired1" - || self.cmd == "algorithm-cache-expired2" - { - let actions = if self.cmd == "ksk-cache-expired1" { - kss.keyset.cache_expired1(RollType::KskRoll) - } else if self.cmd == "ksk-cache-expired2" { - kss.keyset.cache_expired2(RollType::KskRoll) - } else if self.cmd == "zsk-cache-expired1" { - kss.keyset.cache_expired1(RollType::ZskRoll) - } else if self.cmd == "zsk-cache-expired2" { - kss.keyset.cache_expired2(RollType::ZskRoll) - } else if self.cmd == "csk-cache-expired1" { - kss.keyset.cache_expired1(RollType::CskRoll) - } else if self.cmd == "csk-cache-expired2" { - kss.keyset.cache_expired2(RollType::CskRoll) - } else if self.cmd == "algorithm-cache-expired1" { - kss.keyset.cache_expired1(RollType::AlgorithmRoll) - } else if self.cmd == "algorithm-cache-expired2" { - kss.keyset.cache_expired2(RollType::AlgorithmRoll) - } else { - unreachable!(); - }; - - let actions = match actions { - Ok(actions) => actions, - Err(err) => { - return Err(format!("Error reporting cache expired: {err}\n").into()); - } - }; - - // Handle error - - handle_actions(&actions, &ksc, &mut kss, env)?; - - // Report actions - print_actions(&actions); - state_changed = true; - } else if self.cmd == "ksk-roll-done" - || self.cmd == "zsk-roll-done" - || self.cmd == "csk-roll-done" - || self.cmd == "algorithm-roll-done" - { - let actions = if self.cmd == "ksk-roll-done" { - kss.keyset.roll_done(RollType::KskRoll) - } else if self.cmd == "zsk-roll-done" { - kss.keyset.roll_done(RollType::ZskRoll) - } else if self.cmd == "csk-roll-done" { - kss.keyset.roll_done(RollType::CskRoll) - } else if self.cmd == "algorithm-roll-done" { - kss.keyset.roll_done(RollType::AlgorithmRoll) - } else { - unreachable!(); - }; - - let actions = match actions { - Ok(actions) => actions, - Err(err) => { - return Err(format!("Error reporting done: {err}\n").into()); - } - }; - - if !actions.is_empty() { - return Err("List of actions after reporting done\n".into()); - } - - // Remove old keys. - if ksc.autoremove { - let files: Vec<_> = kss + // Find existing KSKs. Do we complain if there is none? + let old_stored: Vec<_> = kss .keyset .keys() .iter() .filter(|(_, key)| { - let state = match key.keytype() { - KeyType::Ksk(state) => state, - KeyType::Zsk(state) => state, - KeyType::Csk(state, _) => state, - KeyType::Include(state) => state, - }; - state.old() && !state.signer() && !state.present() && !state.at_parent() - }) - .map(|(pubref, key)| (pubref.clone(), key.privref().map(|r| r.to_string()))) - .collect(); - if !files.is_empty() { - print!("Removing:"); - for f in files { - let (pubkey, privkey) = &f; - print!(" {pubkey}"); - kss.keyset.delete_key(pubkey).map_err::(|e| { - format!("unable to remove key {pubkey}: {e}\n").into() - })?; - remove_file(pubkey).map_err::(|e| { - format!("unable to remove file {pubkey}: {e}\n").into() - })?; - if let Some(privkey) = privkey { - print!(" {privkey}"); - remove_file(privkey).map_err::(|e| { - format!("unable to remove file {privkey}: {e}\n").into() - })?; + if let KeyType::Ksk(keystate) = key.keytype() { + !keystate.old() + || keystate.signer() + || keystate.present() + || keystate.at_parent() + } else { + false } - } - println!(); - } - } - state_changed = true; - } else if self.cmd == "status" { - for (roll, state) in kss.keyset.rollstates().iter() { - println!("{roll:?}: {state:?}"); - } - if sig_renew(&kss.dnskey_rrset, &ksc.dnskey_remain_time) { - println!("DNSKEY RRSIG(s) need to be renewed"); - } - if sig_renew(&kss.cds_rrset, &ksc.cds_remain_time) { - println!("CDS/CDNSKEY RRSIG(s) need to be renewed"); - } + }) + .map(|(name, _)| name.clone()) + .collect(); + let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); - // Check for expired keys. - for (pubref, k) in kss.keyset.keys() { - let (expired, label) = key_expired(k, &ksc); - if expired { - println!("{label} {pubref} has expired"); - } - } - } else if self.cmd == "actions" { - for roll in kss.keyset.rollstates().keys() { - let actions = kss.keyset.actions(roll.clone()); - println!("{roll:?} actions:"); - for a in actions { - println!("\t{a:?}"); - } - } - } else if self.cmd == "keys" { - println!("Keys:"); - let mut keys: Vec<_> = kss.keyset.keys().iter().collect(); - keys.sort_by(|(pubref1, key1), (pubref2, key2)| { - (key1.timestamps().creation(), pubref1) - .cmp(&(key2.timestamps().creation(), pubref2)) - }); - for (pubref, key) in keys { - println!("\t{} {}", pubref, key.privref().unwrap_or_default(),); - let (keytype, state, opt_state) = match key.keytype() { - KeyType::Ksk(keystate) => ("KSK", keystate, None), - KeyType::Zsk(keystate) => ("ZSK", keystate, None), - KeyType::Include(keystate) => ("Include", keystate, None), - KeyType::Csk(keystate_ksk, keystate_zsk) => { - ("CSK", keystate_ksk, Some(keystate_zsk)) + // Collect algorithms. Maybe this needs to be in the library. + + // Create a new KSK + let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.ksk_generate_params.to_generate_params(), + true, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + kss.keyset + .add_key_ksk( + ksk_pub_url.to_string(), + Some(ksk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .map_err::(|e| { + format!("unable to add KSK {ksk_pub_url}: {e}\n").into() + })?; + + let new = [ksk_pub_url.as_ref()]; + + // Start the key roll + let actions = match kss + .keyset + .start_roll(RollType::KskRoll, &old, &new) + .map_err::(|e| format!("cannot start roll: {e}\n").into()) + { + Ok(actions) => actions, + Err(e) => { + // Remove the key files we just created. + if ksk_priv_url.scheme() == "file" { + remove_file(ksk_priv_url.path()).map_err::(|e| { + format!("unable to remove private key file {ksk_priv_url}: {e}\n") + .into() + })?; + } else { + panic!("unsupported URL scheme in {ksk_priv_url}"); + } + + if ksk_pub_url.scheme() == "file" { + remove_file(ksk_pub_url.path()).map_err::(|e| { + format!("unable to remove public key file {ksk_pub_url}: {e}\n") + .into() + })?; + } else { + panic!("unsupported URL scheme in {ksk_pub_url}"); + } + + return Err(e); } }; - println!( - "\t\tType: {keytype}, algorithm: {}, key tag: {}", - key.algorithm(), - key.key_tag() - ); - if let Some(zskstate) = opt_state { - println!("\t\tKSK role state: {state}"); - println!("\t\tZSK role state: {zskstate}"); - } else { - println!("\t\tState: {state}"); + handle_actions(&actions, &ksc, &mut kss, env)?; + + print_actions(&actions); + state_changed = true; + } + Commands::StartZskRoll => { + if kss.keyset.keys().is_empty() { + // Avoid ZSK roll without init. + return Err("not yet initialized\n".into()); } - let ts = key.timestamps(); - println!( - "\t\tCreated: {}", - ts.creation() - .map_or("".to_string(), |x| x.to_string()), - ); - println!( - "\t\tPublished: {}", - ts.published() - .map_or("".to_string(), |x| x.to_string()) - ); - println!( - "\t\tVisible: {}", - ts.visible() - .map_or("".to_string(), |x| x.to_string()), - ); - println!( - "\t\tDS visible: {}", - ts.ds_visible() - .map_or("".to_string(), |x| x.to_string()) - ); - println!( - "\t\tRRSIG visible: {}", - ts.rrsig_visible() - .map_or("".to_string(), |x| x.to_string()), - ); - println!( - "\t\tWithdrawn: {}", - ts.withdrawn() - .map_or("".to_string(), |x| x.to_string()) - ); - } - } else if self.cmd == "get-use-csk" { - println!("{}", ksc.use_csk); - } else if self.cmd == "set-use-csk" { - let arg = self.value.ok_or::("argument expected\n".into())?; - ksc.use_csk = arg - .parse() - .map_err::(|_| format!("unable to parse as boolean: {arg}\n").into())?; - config_changed = true; - } else if self.cmd == "get-autoremove" { - println!("{}", ksc.autoremove); - } else if self.cmd == "set-autoremove" { - let arg = self.value.ok_or::("argument expected\n".into())?; - ksc.autoremove = arg - .parse() - .map_err::(|_| format!("unable to parse as boolean: {arg}\n").into())?; - config_changed = true; - } else if self.cmd == "get-ksk-algorithm" { - println!("{}", ksc.ksk_generate_params); - } else if self.cmd == "set-ksk-algorithm" { - let arg = self.value.ok_or::("argument expected\n".into())?; - ksc.ksk_generate_params = KeyParameters::new(&arg, self.bits)?; - config_changed = true; - } else if self.cmd == "get-zsk-algorithm" { - println!("{}", ksc.zsk_generate_params); - } else if self.cmd == "set-zsk-algorithm" { - let arg = self.value.ok_or::("argument expected\n".into())?; - ksc.zsk_generate_params = KeyParameters::new(&arg, self.bits)?; - config_changed = true; - } else if self.cmd == "get-csk-algorithm" { - println!("{}", ksc.csk_generate_params); - } else if self.cmd == "set-csk-algorithm" { - let arg = self.value.ok_or::("argument expected\n".into())?; - ksc.csk_generate_params = KeyParameters::new(&arg, self.bits)?; - config_changed = true; - } else if self.cmd == "get-ds-algorithm" { - println!("{}", ksc.ds_algorithm); - } else if self.cmd == "set-ds-algorithm" { - let arg = self.value.ok_or::("argument expected\n".into())?; - ksc.ds_algorithm = DsAlgorithm::new(&arg)?; - config_changed = true; - } else if self.cmd == "set-dnskey-inception-offset" { - ksc.dnskey_inception_offset = parse_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "get-dnskey-lifetime" { - let span = Span::try_from(ksc.dnskey_signature_lifetime).expect("should not fail"); - let signeddur = span - .to_duration(SpanRelativeTo::days_are_24_hours()) - .expect("should not fail"); - println!("{signeddur:#}"); - } else if self.cmd == "set-dnskey-lifetime" { - ksc.dnskey_signature_lifetime = parse_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "set-dnskey-remain-time" { - ksc.dnskey_remain_time = parse_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "set-cds-inception-offset" { - ksc.cds_inception_offset = parse_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "get-cds-lifetime" { - let span = Span::try_from(ksc.cds_signature_lifetime).expect("should not fail"); - let signeddur = span - .to_duration(SpanRelativeTo::days_are_24_hours()) - .expect("should not fail"); - println!("{signeddur:#}"); - } else if self.cmd == "set-cds-lifetime" { - ksc.cds_signature_lifetime = parse_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "set-cds-remain-time" { - ksc.cds_remain_time = parse_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "set-ksk-validity" { - ksc.ksk_validity = parse_opt_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "set-zsk-validity" { - ksc.zsk_validity = parse_opt_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "set-csk-validity" { - ksc.csk_validity = parse_opt_duration_from_opt(&self.value)?; - config_changed = true; - } else if self.cmd == "show" { - println!("state-file: {:?}", ksc.state_file); - println!("use-csk: {}", ksc.use_csk); - println!("ksk-algorithm: {}", ksc.ksk_generate_params); - println!("zsk-algorithm: {}", ksc.zsk_generate_params); - println!("csk-algorithm: {}", ksc.csk_generate_params); - println!("ksk-validity: {:?}", ksc.ksk_validity); - println!("zsk-validity: {:?}", ksc.zsk_validity); - println!("csk-validity: {:?}", ksc.csk_validity); - println!("dnskey-inception-offset: {:?}", ksc.dnskey_inception_offset); - println!( - "dnskey-signature-lifetime: {:?}", - ksc.dnskey_signature_lifetime - ); - println!("dnskey-remain-time: {:?}", ksc.dnskey_remain_time); - println!("cds-inception-offset: {:?}", ksc.cds_inception_offset); - println!("cds-signature-lifetime: {:?}", ksc.cds_signature_lifetime); - println!("cds-remain-time: {:?}", ksc.cds_remain_time); - println!("ds-algorithm: {:?}", ksc.ds_algorithm); - println!("autoremove: {:?}", ksc.autoremove); - } else if self.cmd == "get-dnskey" { - for r in &kss.dnskey_rrset { - println!("{r}"); - } - } else if self.cmd == "get-cds" { - for r in &kss.cds_rrset { - println!("{r}"); - } - } else if self.cmd == "get-ds" { - for r in &kss.ds_rrset { - println!("{r}"); - } - } else if self.cmd == "cron" { - if sig_renew(&kss.dnskey_rrset, &ksc.dnskey_remain_time) { - println!("DNSKEY RRSIG(s) need to be renewed"); - update_dnskey_rrset(&mut kss, &ksc, env)?; + + // Check for CSK. + if ksc.use_csk { + return Err("wrong key roll, use start-csk-roll\n".into()); + } + + // Refuse if we can find a CSK key. + if kss + .keyset + .keys() + .iter() + .any(|(_, key)| matches!(key.keytype(), KeyType::Csk(_, _))) + { + return Err("cannot start key roll, found CSK\n".into()); + } + + // Find existing ZSKs. Do we complain if there is none? + let old_stored: Vec<_> = kss + .keyset + .keys() + .iter() + .filter(|(_, key)| { + if let KeyType::Zsk(keystate) = key.keytype() { + !keystate.old() || keystate.signer() || keystate.present() + } else { + false + } + }) + .map(|(name, _)| name.clone()) + .collect(); + let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); + + // Collect algorithms. Maybe this needs to be in the library. + + // Create a new ZSK + let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.zsk_generate_params.to_generate_params(), + false, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + kss.keyset + .add_key_zsk( + zsk_pub_url.to_string(), + Some(zsk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .map_err::(|e| { + format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() + })?; + + let new = [zsk_pub_url.as_ref()]; + + // Start the key roll + let actions = match kss + .keyset + .start_roll(RollType::ZskRoll, &old, &new) + .map_err::(|e| format!("cannot start roll: {e}\n").into()) + { + Ok(actions) => actions, + Err(e) => { + // Remove the key files we just created. + if zsk_priv_url.scheme() == "file" { + remove_file(zsk_priv_url.path()).map_err::(|e| { + format!("unable to remove private key file {zsk_priv_url}: {e}\n") + .into() + })?; + } else { + panic!("unsupported URL scheme in {zsk_priv_url}"); + } + if zsk_pub_url.scheme() == "file" { + remove_file(zsk_pub_url.path()).map_err::(|e| { + format!("unable to remove public key file {zsk_pub_url}: {e}\n") + .into() + })?; + } else { + panic!("unsupported URL scheme in {zsk_pub_url}"); + } + return Err(e); + } + }; + handle_actions(&actions, &ksc, &mut kss, env)?; + + print_actions(&actions); state_changed = true; } - if sig_renew(&kss.cds_rrset, &ksc.cds_remain_time) { - println!("CDS/CDNSKEY RRSIGs need to be renewed"); - create_cds_rrset(&mut kss, &ksc, ksc.ds_algorithm.to_digest_algorithm(), env)?; + Commands::StartCskRoll => { + // Find existing KSKs, ZSKs and CSKs. Do we complain if there + // are none? + let old_stored: Vec<_> = kss + .keyset + .keys() + .iter() + .filter(|(_, key)| match key.keytype() { + KeyType::Ksk(keystate) + | KeyType::Zsk(keystate) + | KeyType::Csk(keystate, _) => { + // Assume that for a CSK it is sufficient to check + // one of the key states. Also assume that we + // can check at_parent for a ZSK. + !keystate.old() + || keystate.signer() + || keystate.present() + || keystate.at_parent() + } + KeyType::Include(_) => false, + }) + .map(|(name, _)| name.clone()) + .collect(); + let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); + + // Collect algorithms. Maybe this needs to be in the library. + + let (new_stored, new_urls) = if ksc.use_csk { + let mut new_urls = Vec::new(); + + // Create a new CSK + let (csk_pub_url, csk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.csk_generate_params.to_generate_params(), + true, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + new_urls.push(csk_priv_url.clone()); + new_urls.push(csk_pub_url.clone()); + kss.keyset + .add_key_csk( + csk_pub_url.to_string(), + Some(csk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .map_err::(|e| { + format!("unable to add CSK {csk_pub_url}: {e}\n").into() + })?; + + let new = vec![csk_pub_url]; + (new, new_urls) + } else { + let mut new_urls = Vec::new(); + + // Create a new KSK + let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.ksk_generate_params.to_generate_params(), + true, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + new_urls.push(ksk_priv_url.clone()); + new_urls.push(ksk_pub_url.clone()); + kss.keyset + .add_key_ksk( + ksk_pub_url.to_string(), + Some(ksk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .map_err::(|e| { + format!("unable to add KSK {ksk_pub_url}: {e}\n").into() + })?; + + // Create a new ZSK + let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.zsk_generate_params.to_generate_params(), + false, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + new_urls.push(zsk_priv_url.clone()); + new_urls.push(zsk_pub_url.clone()); + kss.keyset + .add_key_zsk( + zsk_pub_url.to_string(), + Some(zsk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .map_err::(|e| { + format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() + })?; + + let new = vec![ksk_pub_url, zsk_pub_url]; + (new, new_urls) + }; + + let new: Vec<_> = new_stored.iter().map(|v| v.as_ref()).collect(); + + // Start the key roll + let actions = match kss + .keyset + .start_roll(RollType::CskRoll, &old, &new) + .map_err::(|e| format!("cannot start roll: {e}\n").into()) + { + Ok(actions) => actions, + Err(e) => { + // Remove the key files we just created. + for u in new_urls { + if u.scheme() == "file" { + remove_file(u.path()).map_err::(|e| { + format!("unable to remove private key file {u}: {e}\n").into() + })?; + } else { + panic!("unsupported URL scheme in {u}"); + } + } + return Err(e); + } + }; + + handle_actions(&actions, &ksc, &mut kss, env)?; + + print_actions(&actions); state_changed = true; } - } else { - return Err(format!("unknown subcommand {}\n", self.cmd).into()); + Commands::StartAlgorithmRoll => { + // Find existing KSKs, ZSKs and CSKs. Do we complain if there + // are none? + let old_stored: Vec<_> = kss + .keyset + .keys() + .iter() + .filter(|(_, key)| match key.keytype() { + KeyType::Ksk(keystate) + | KeyType::Zsk(keystate) + | KeyType::Csk(keystate, _) => { + // Assume that for a CSK it is sufficient to check + // one of the key states. Also assume that we + // can check at_parent for a ZSK. + !keystate.old() + || keystate.signer() + || keystate.present() + || keystate.at_parent() + } + KeyType::Include(_) => false, + }) + .map(|(name, _)| name.clone()) + .collect(); + let old: Vec<_> = old_stored.iter().map(|name| name.as_ref()).collect(); + + let (new_stored, new_urls) = if ksc.use_csk { + let mut new_urls = Vec::new(); + + // Create a new CSK + let (csk_pub_url, csk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.csk_generate_params.to_generate_params(), + true, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + new_urls.push(csk_priv_url.clone()); + new_urls.push(csk_pub_url.clone()); + kss.keyset + .add_key_csk( + csk_pub_url.to_string(), + Some(csk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .map_err::(|e| { + format!("unable to add CSK {csk_pub_url}: {e}\n").into() + })?; + + let new = vec![csk_pub_url]; + (new, new_urls) + } else { + let mut new_urls = Vec::new(); + + // Create a new KSK + let (ksk_pub_url, ksk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.ksk_generate_params.to_generate_params(), + true, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + new_urls.push(ksk_priv_url.clone()); + new_urls.push(ksk_pub_url.clone()); + kss.keyset + .add_key_ksk( + ksk_pub_url.to_string(), + Some(ksk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .map_err::(|e| { + format!("unable to add KSK {ksk_pub_url}: {e}\n").into() + })?; + + // Create a new ZSK + let (zsk_pub_url, zsk_priv_url, algorithm, key_tag) = new_keys( + kss.keyset.name(), + ksc.zsk_generate_params.to_generate_params(), + false, + kss.keyset.keys(), + &ksc.keys_dir, + env, + )?; + new_urls.push(zsk_priv_url.clone()); + new_urls.push(zsk_pub_url.clone()); + kss.keyset + .add_key_zsk( + zsk_pub_url.to_string(), + Some(zsk_priv_url.to_string()), + algorithm, + key_tag, + UnixTime::now(), + true, + ) + .map_err::(|e| { + format!("unable to add ZSK {zsk_pub_url}: {e}\n").into() + })?; + + let new = vec![ksk_pub_url, zsk_pub_url]; + (new, new_urls) + }; + + let new: Vec<_> = new_stored.iter().map(|v| v.as_ref()).collect(); + + // Start the key roll + let actions = match kss + .keyset + .start_roll(RollType::AlgorithmRoll, &old, &new) + .map_err::(|e| format!("cannot start roll: {e}\n").into()) + { + Ok(actions) => actions, + Err(e) => { + // Remove the key files we just created. + for u in new_urls { + if u.scheme() == "file" { + remove_file(u.path()).map_err::(|e| { + format!("unable to private key file {u}: {e}\n").into() + })?; + } else { + panic!("unsupported scheme in {u}"); + } + } + return Err(e); + } + }; + + handle_actions(&actions, &ksc, &mut kss, env)?; + + print_actions(&actions); + state_changed = true; + } + Commands::KskPropagation1Complete { ttl } + | Commands::KskPropagation2Complete { ttl } + | Commands::ZskPropagation1Complete { ttl } + | Commands::ZskPropagation2Complete { ttl } + | Commands::CskPropagation1Complete { ttl } + | Commands::CskPropagation2Complete { ttl } + | Commands::AlgorithmPropagation1Complete { ttl } + | Commands::AlgorithmPropagation2Complete { ttl } => { + let actions = match self.cmd { + Commands::KskPropagation1Complete { ttl: _ } => { + kss.keyset.propagation1_complete(RollType::KskRoll, ttl) + } + Commands::KskPropagation2Complete { ttl: _ } => { + kss.keyset.propagation2_complete(RollType::KskRoll, ttl) + } + Commands::ZskPropagation1Complete { ttl: _ } => { + kss.keyset.propagation1_complete(RollType::ZskRoll, ttl) + } + Commands::ZskPropagation2Complete { ttl: _ } => { + kss.keyset.propagation2_complete(RollType::ZskRoll, ttl) + } + Commands::CskPropagation1Complete { ttl: _ } => { + kss.keyset.propagation1_complete(RollType::CskRoll, ttl) + } + Commands::CskPropagation2Complete { ttl: _ } => { + kss.keyset.propagation2_complete(RollType::CskRoll, ttl) + } + Commands::AlgorithmPropagation1Complete { ttl: _ } => kss + .keyset + .propagation1_complete(RollType::AlgorithmRoll, ttl), + Commands::AlgorithmPropagation2Complete { ttl: _ } => kss + .keyset + .propagation2_complete(RollType::AlgorithmRoll, ttl), + _ => unreachable!(), + }; + + let actions = match actions { + Ok(actions) => actions, + Err(err) => { + return Err(format!("Error reporting propagation complete: {err}\n").into()); + } + }; + + // Handle error + + handle_actions(&actions, &ksc, &mut kss, env)?; + + // Report actions + print_actions(&actions); + state_changed = true; + } + Commands::KskCacheExpired1 + | Commands::KskCacheExpired2 + | Commands::ZskCacheExpired1 + | Commands::ZskCacheExpired2 + | Commands::CskCacheExpired1 + | Commands::CskCacheExpired2 + | Commands::AlgorithmCacheExpired1 + | Commands::AlgorithmCacheExpired2 => { + let actions = match self.cmd { + Commands::KskCacheExpired1 => kss.keyset.cache_expired1(RollType::KskRoll), + Commands::KskCacheExpired2 => kss.keyset.cache_expired2(RollType::KskRoll), + Commands::ZskCacheExpired1 => kss.keyset.cache_expired1(RollType::ZskRoll), + Commands::ZskCacheExpired2 => kss.keyset.cache_expired2(RollType::ZskRoll), + Commands::CskCacheExpired1 => kss.keyset.cache_expired1(RollType::CskRoll), + Commands::CskCacheExpired2 => kss.keyset.cache_expired2(RollType::CskRoll), + Commands::AlgorithmCacheExpired1 => { + kss.keyset.cache_expired1(RollType::AlgorithmRoll) + } + Commands::AlgorithmCacheExpired2 => { + kss.keyset.cache_expired2(RollType::AlgorithmRoll) + } + _ => unreachable!(), + }; + + let actions = match actions { + Ok(actions) => actions, + Err(err) => { + return Err(format!("Error reporting cache expired: {err}\n").into()); + } + }; + + // Handle error + + handle_actions(&actions, &ksc, &mut kss, env)?; + + // Report actions + print_actions(&actions); + state_changed = true; + } + Commands::KskRollDone + | Commands::ZskRollDone + | Commands::CskRollDone + | Commands::AlgorithmRollDone => { + let actions = match self.cmd { + Commands::KskRollDone => kss.keyset.roll_done(RollType::KskRoll), + Commands::ZskRollDone => kss.keyset.roll_done(RollType::ZskRoll), + Commands::CskRollDone => kss.keyset.roll_done(RollType::CskRoll), + Commands::AlgorithmRollDone => kss.keyset.roll_done(RollType::AlgorithmRoll), + _ => unreachable!(), + }; + + let actions = match actions { + Ok(actions) => actions, + Err(err) => { + return Err(format!("Error reporting done: {err}\n").into()); + } + }; + + if !actions.is_empty() { + return Err("List of actions after reporting done\n".into()); + } + + // Remove old keys. + if ksc.autoremove { + let files: Vec<_> = kss + .keyset + .keys() + .iter() + .filter(|(_, key)| { + let state = match key.keytype() { + KeyType::Ksk(state) => state, + KeyType::Zsk(state) => state, + KeyType::Csk(state, _) => state, + KeyType::Include(state) => state, + }; + state.old() && !state.signer() && !state.present() && !state.at_parent() + }) + .map(|(pubref, key)| (pubref.clone(), key.privref().map(|r| r.to_string()))) + .collect(); + if !files.is_empty() { + print!("Removing:"); + for f in files { + let (pubkey, privkey) = &f; + print!(" {pubkey}"); + kss.keyset.delete_key(pubkey).map_err::(|e| { + format!("unable to remove key {pubkey}: {e}\n").into() + })?; + remove_file(pubkey).map_err::(|e| { + format!("unable to remove file {pubkey}: {e}\n").into() + })?; + if let Some(privkey) = privkey { + print!(" {privkey}"); + remove_file(privkey).map_err::(|e| { + format!("unable to remove file {privkey}: {e}\n").into() + })?; + } + } + println!(); + } + } + state_changed = true; + } + Commands::Status => { + for (roll, state) in kss.keyset.rollstates().iter() { + println!("{roll:?}: {state:?}"); + } + if sig_renew(&kss.dnskey_rrset, &ksc.dnskey_remain_time) { + println!("DNSKEY RRSIG(s) need to be renewed"); + } + if sig_renew(&kss.cds_rrset, &ksc.cds_remain_time) { + println!("CDS/CDNSKEY RRSIG(s) need to be renewed"); + } + + // Check for expired keys. + for (pubref, k) in kss.keyset.keys() { + let (expired, label) = key_expired(k, &ksc); + if expired { + println!("{label} {pubref} has expired"); + } + } + } + Commands::Actions => { + for roll in kss.keyset.rollstates().keys() { + let actions = kss.keyset.actions(roll.clone()); + println!("{roll:?} actions:"); + for a in actions { + println!("\t{a:?}"); + } + } + } + Commands::Keys => { + println!("Keys:"); + let mut keys: Vec<_> = kss.keyset.keys().iter().collect(); + keys.sort_by(|(pubref1, key1), (pubref2, key2)| { + (key1.timestamps().creation(), pubref1) + .cmp(&(key2.timestamps().creation(), pubref2)) + }); + for (pubref, key) in keys { + println!("\t{} {}", pubref, key.privref().unwrap_or_default(),); + let (keytype, state, opt_state) = match key.keytype() { + KeyType::Ksk(keystate) => ("KSK", keystate, None), + KeyType::Zsk(keystate) => ("ZSK", keystate, None), + KeyType::Include(keystate) => ("Include", keystate, None), + KeyType::Csk(keystate_ksk, keystate_zsk) => { + ("CSK", keystate_ksk, Some(keystate_zsk)) + } + }; + println!( + "\t\tType: {keytype}, algorithm: {}, key tag: {}", + key.algorithm(), + key.key_tag() + ); + if let Some(zskstate) = opt_state { + println!("\t\tKSK role state: {state}"); + println!("\t\tZSK role state: {zskstate}"); + } else { + println!("\t\tState: {state}"); + } + let ts = key.timestamps(); + println!( + "\t\tCreated: {}", + ts.creation() + .map_or("".to_string(), |x| x.to_string()), + ); + println!( + "\t\tPublished: {}", + ts.published() + .map_or("".to_string(), |x| x.to_string()) + ); + println!( + "\t\tVisible: {}", + ts.visible() + .map_or("".to_string(), |x| x.to_string()), + ); + println!( + "\t\tDS visible: {}", + ts.ds_visible() + .map_or("".to_string(), |x| x.to_string()) + ); + println!( + "\t\tRRSIG visible: {}", + ts.rrsig_visible() + .map_or("".to_string(), |x| x.to_string()), + ); + println!( + "\t\tWithdrawn: {}", + ts.withdrawn() + .map_or("".to_string(), |x| x.to_string()) + ); + } + } + Commands::Get { subcommand } => get_command(subcommand, &ksc, &kss), + Commands::Set { subcommand } => set_command(subcommand, &mut ksc, &mut config_changed)?, + Commands::Show => { + println!("state-file: {:?}", ksc.state_file); + println!("use-csk: {}", ksc.use_csk); + println!("ksk-algorithm: {}", ksc.ksk_generate_params); + println!("zsk-algorithm: {}", ksc.zsk_generate_params); + println!("csk-algorithm: {}", ksc.csk_generate_params); + println!("ksk-validity: {:?}", ksc.ksk_validity); + println!("zsk-validity: {:?}", ksc.zsk_validity); + println!("csk-validity: {:?}", ksc.csk_validity); + println!("dnskey-inception-offset: {:?}", ksc.dnskey_inception_offset); + println!( + "dnskey-signature-lifetime: {:?}", + ksc.dnskey_signature_lifetime + ); + println!("dnskey-remain-time: {:?}", ksc.dnskey_remain_time); + println!("cds-inception-offset: {:?}", ksc.cds_inception_offset); + println!("cds-signature-lifetime: {:?}", ksc.cds_signature_lifetime); + println!("cds-remain-time: {:?}", ksc.cds_remain_time); + println!("ds-algorithm: {:?}", ksc.ds_algorithm); + println!("autoremove: {:?}", ksc.autoremove); + } + Commands::Cron => { + if sig_renew(&kss.dnskey_rrset, &ksc.dnskey_remain_time) { + println!("DNSKEY RRSIG(s) need to be renewed"); + update_dnskey_rrset(&mut kss, &ksc, env)?; + state_changed = true; + } + if sig_renew(&kss.cds_rrset, &ksc.cds_remain_time) { + println!("CDS/CDNSKEY RRSIGs need to be renewed"); + create_cds_rrset(&mut kss, &ksc, ksc.ds_algorithm.to_digest_algorithm(), env)?; + state_changed = true; + } + } } let cron_next_dnskey = compute_cron_next(&kss.dnskey_rrset, &ksc.dnskey_remain_time); @@ -1100,6 +1173,114 @@ impl Keyset { } } +fn get_command(cmd: GetCommands, ksc: &KeySetConfig, kss: &KeySetState) { + match cmd { + GetCommands::UseCsk => { + println!("{}", ksc.use_csk); + } + GetCommands::Autoremove => { + println!("{}", ksc.autoremove); + } + GetCommands::KskAlgorithm => { + println!("{}", ksc.ksk_generate_params); + } + GetCommands::ZskAlgorithm => { + println!("{}", ksc.zsk_generate_params); + } + GetCommands::CskAlgorithm => { + println!("{}", ksc.csk_generate_params); + } + GetCommands::DsAlgorithm => { + println!("{}", ksc.ds_algorithm); + } + GetCommands::DnskeyLifetime => { + let span = Span::try_from(ksc.dnskey_signature_lifetime).expect("should not fail"); + let signeddur = span + .to_duration(SpanRelativeTo::days_are_24_hours()) + .expect("should not fail"); + println!("{signeddur:#}"); + } + GetCommands::CdsLifetime => { + let span = Span::try_from(ksc.cds_signature_lifetime).expect("should not fail"); + let signeddur = span + .to_duration(SpanRelativeTo::days_are_24_hours()) + .expect("should not fail"); + println!("{signeddur:#}"); + } + GetCommands::Dnskey => { + for r in &kss.dnskey_rrset { + println!("{r}"); + } + } + GetCommands::Cds => { + for r in &kss.cds_rrset { + println!("{r}"); + } + } + GetCommands::Ds => { + for r in &kss.ds_rrset { + println!("{r}"); + } + } + } +} + +fn set_command( + cmd: SetCommands, + ksc: &mut KeySetConfig, + config_changed: &mut bool, +) -> Result<(), Error> { + match cmd { + SetCommands::UseCsk { boolean } => { + ksc.use_csk = boolean; + } + SetCommands::Autoremove { boolean } => { + ksc.autoremove = boolean; + } + SetCommands::KskAlgorithm { algorithm, bits } => { + ksc.ksk_generate_params = KeyParameters::new(&algorithm, bits)?; + } + SetCommands::ZskAlgorithm { algorithm, bits } => { + ksc.zsk_generate_params = KeyParameters::new(&algorithm, bits)?; + } + SetCommands::CskAlgorithm { algorithm, bits } => { + ksc.csk_generate_params = KeyParameters::new(&algorithm, bits)?; + } + SetCommands::DsAlgorithm { algorithm } => { + ksc.ds_algorithm = algorithm; + } + SetCommands::DnskeyInceptionOffset { duration } => { + ksc.dnskey_inception_offset = duration; + } + SetCommands::DnskeyLifetime { duration } => { + ksc.dnskey_signature_lifetime = duration; + } + SetCommands::DnskeyRemainTime { duration } => { + ksc.dnskey_remain_time = duration; + } + SetCommands::CdsInceptionOffset { duration } => { + ksc.cds_inception_offset = duration; + } + SetCommands::CdsLifetime { duration } => { + ksc.cds_signature_lifetime = duration; + } + SetCommands::CdsRemainTime { duration } => { + ksc.cds_remain_time = duration; + } + SetCommands::KskValidity { opt_duration } => { + ksc.ksk_validity = opt_duration; + } + SetCommands::ZskValidity { opt_duration } => { + ksc.zsk_validity = opt_duration; + } + SetCommands::CskValidity { opt_duration } => { + ksc.csk_validity = opt_duration; + } + } + *config_changed = true; + Ok(()) +} + /// Config for the keyset command. #[derive(Deserialize, Serialize)] struct KeySetConfig { @@ -1219,7 +1400,7 @@ impl Display for KeyParameters { } // Do we want Deserialize and Serialize for DigestAlgorithm? -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] enum DsAlgorithm { Sha256, Sha384, @@ -1740,6 +1921,7 @@ fn handle_actions( Action::ReportDsPropagated => (), Action::ReportRrsigPropagated => (), Action::WaitDnskeyPropagated => (), + Action::WaitDsPropagated => (), Action::WaitRrsigPropagated => (), } } @@ -1758,26 +1940,21 @@ fn print_actions(actions: &[Action]) { } } -fn parse_duration_from_opt(value: &Option) -> Result { - let arg = value - .as_ref() - .ok_or::("argument expected\n".into())?; - let span: Span = arg +fn parse_duration(value: &str) -> Result { + let span: Span = value .parse() - .map_err::(|e| format!("unable to parse {arg} as lifetime: {e}\n").into())?; + .map_err::(|e| format!("unable to parse {value} as lifetime: {e}\n").into())?; let signeddur = span .to_duration(SpanRelativeTo::days_are_24_hours()) .map_err::(|e| format!("unable to convert duration: {e}\n").into())?; Duration::try_from(signeddur).map_err(|e| format!("unable to convert duration: {e}\n").into()) } -fn parse_opt_duration_from_opt(value: &Option) -> Result, Error> { - if let Some(value) = value { - if value == "off" { - return Ok(None); - } +fn parse_opt_duration(value: &str) -> Result, Error> { + if value == "off" { + return Ok(None); } - let duration = parse_duration_from_opt(value)?; + let duration = parse_duration(value)?; Ok(Some(duration)) } diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 3a1eb2f..4eb0274 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -215,7 +215,7 @@ impl Notify { let resolver = env.stub_resolver().await; for server in &self.servers { - writeln!(env.stdout(), "# sending to {}", server); + writeln!(env.stdout(), "# sending to {server}"); // The specified server might be an IP address. In ldns, this case is // handled by `getaddrinfo`, but we have to do it ourselves. diff --git a/src/commands/nsec3hash.rs b/src/commands/nsec3hash.rs index 535c59d..54cde07 100644 --- a/src/commands/nsec3hash.rs +++ b/src/commands/nsec3hash.rs @@ -158,7 +158,7 @@ impl Nsec3Hash { .map_err(|err| format!("Error creating NSEC3 hash: {err}"))? .to_string() .to_lowercase(); - writeln!(env.stdout(), "{}.", hash); + writeln!(env.stdout(), "{hash}."); Ok(()) } } diff --git a/src/commands/signzone.rs b/src/commands/signzone.rs index 4c56361..d236e8c 100644 --- a/src/commands/signzone.rs +++ b/src/commands/signzone.rs @@ -4505,7 +4505,7 @@ vrcj1rgalbb9eh2ii8a43fbeib1ufqf6.example.org.\t238\tIN\tNSEC3\t1 0 0 - 8UM1KJCJM // Simulate that the time now is later than the 1234567890 SOA SERIAL // in the zonefile. let time_now = 1234567891; - let expected_soa_line = format!("example.org.\t238\tIN\tSOA\texample.net. hostmaster.example.net. {} 28800 7200 604800 239\n", time_now); + let expected_soa_line = format!("example.org.\t238\tIN\tSOA\texample.net. hostmaster.example.net. {time_now} 28800 7200 604800 239\n"); let res = FakeCmd::new([ "dnst",