diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4eedd9985..98ba5f470 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ on: - 'docker/**' - 'LICENSE' - 'README.md' - - 'tests/e2e/**' # run the tests on creation or update of any pull request pull_request: @@ -33,7 +32,6 @@ on: - 'docker/**' - 'LICENSE' - 'README.md' - - 'tests/e2e/**' jobs: build: @@ -41,7 +39,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest, macOS-latest] # Test against the oldest supported version. # Test against beta Rust to get early warning of any problems that might occur with the upcoming Rust release. @@ -51,13 +49,35 @@ jobs: # Test with no features and all features. args: ["--no-default-features", "--all-features"] steps: + - if: runner.os == 'Windows' + name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: hecrj/setup-rust-action@v2 with: rust-version: ${{ matrix.rust }} - - if: matrix.rust == 'stable' && matrix.args == '--all-features' + - if: runner.os == 'Windows' + name: Set VCPKG root + run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + - if: runner.os == 'Windows' + name: Cache vcpkg + id: cache-vckpg + uses: actions/cache@v4 + with: + path: C:/vcpkg + key: ${{ runner.os }}-vcpkg + - if: runner.os == 'Windows' + name: Make vcpkg.json + run: | + '{"dependencies": ["openssl"], "overrides": [{"name": "openssl", "version": "3.4.0"}], "builtin-baseline": "4bee3f5aae7aefbc129ca81c33d6a062b02fcf3b"}' | Out-File -FilePath vcpkg.json -Encoding utf8 + - if: runner.os == 'Windows' && steps.cache-vckpg.outputs.cache-hit != 'true' + name: Install OpenSSL for Windows + run: vcpkg install --triplet x64-windows-static-md + - if: matrix.rust == 'stable' && matrix.args == '--all-features' && matrix.os == 'ubuntu-latest' run: cargo clippy ${{ matrix.args }} -- -D warnings - run: cargo build ${{ matrix.args }} --locked - run: cargo test ${{ matrix.args }} -- --test-threads=1 2>&1 @@ -142,4 +162,4 @@ jobs: - name: Dump the SoftHSM2 log if: always() run: | - cat /var/log/syslog + cat /var/log/syslog \ No newline at end of file diff --git a/.github/workflows/pkg.yml b/.github/workflows/pkg.yml index 6654e37e9..0066a7461 100644 --- a/.github/workflows/pkg.yml +++ b/.github/workflows/pkg.yml @@ -16,7 +16,6 @@ jobs: DOCKER_HUB_ID: ${{ secrets.DOCKER_HUB_ID }} DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} with: - runs_on: ubuntu-24.04 cross_build_args: --features openssl/vendored cross_max_wait_mins: 20 diff --git a/Cargo.lock b/Cargo.lock index 2af5131cf..d2c42f7dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" @@ -120,9 +120,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -228,9 +228,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" @@ -281,9 +281,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -335,7 +335,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -511,7 +511,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -535,7 +535,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -546,7 +546,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -568,9 +568,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -617,7 +617,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -890,7 +890,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1461,9 +1461,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" dependencies = [ "once_cell", "wasm-bindgen", @@ -1505,7 +1505,7 @@ dependencies = [ [[package]] name = "krill" -version = "0.16.1-dev" +version = "0.17.0-dev" dependencies = [ "arc-swap", "backoff", @@ -1656,7 +1656,7 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", - "redox_syscall 0.7.1", + "redox_syscall 0.7.2", ] [[package]] @@ -1700,7 +1700,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1890,7 +1890,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2093,7 +2093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2116,9 +2116,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.39.1" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd58c6a1fc307e1092aa0bb23d204ca4d1f021764142cd0424dccc84d2d5d106" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] @@ -2292,9 +2292,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +checksum = "6d94dd2f7cd932d4dc02cc8b2b50dfd38bd079a4e5d79198b99743d7fcf9a4b4" dependencies = [ "bitflags", ] @@ -2327,7 +2327,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2355,9 +2355,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -2518,9 +2518,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "log", @@ -2710,9 +2710,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -2723,9 +2723,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.16.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -2784,7 +2784,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2831,9 +2831,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" dependencies = [ "base64 0.22.1", "chrono", @@ -2850,14 +2850,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2997,9 +2997,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3023,7 +3023,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3139,7 +3139,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3150,7 +3150,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3253,7 +3253,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3532,9 +3532,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" dependencies = [ "cfg-if", "once_cell", @@ -3545,9 +3545,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "8a89f4650b770e4521aa6573724e2aed4704372151bd0de9d16a3bbabb87441a" dependencies = [ "cfg-if", "futures-util", @@ -3559,9 +3559,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3569,22 +3569,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" dependencies = [ "unicode-ident", ] @@ -3625,9 +3625,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "705eceb4ce901230f8625bd1d665128056ccbe4b7408faa625eec1ba80f59a97" dependencies = [ "js-sys", "wasm-bindgen", @@ -3704,7 +3704,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3715,7 +3715,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4020,7 +4020,7 @@ dependencies = [ "heck", "indexmap 2.13.0", "prettyplease", - "syn 2.0.116", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4036,7 +4036,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4113,28 +4113,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4154,7 +4154,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -4194,7 +4194,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a6860a49e..706491466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] # Note: some of these values are also used when building Debian packages below. name = "krill" -version = "0.16.1-dev" +version = "0.17.0-dev" edition = "2024" rust-version = "1.88" authors = ["NLnet Labs "] @@ -32,7 +32,7 @@ futures-util = "0.3" hex = "0.4" http-body-util = "0.1" hyper = { version = "1.6.0", features = ["server"] } -hyper-util = { version = "0.1", features = [ "server" ] } +hyper-util = { version = "0.1", features = [ "server", "server-auto", "server-graceful" ] } intervaltree = "0.2.7" lazy_static = "1.5" libflate = "2.1.0" diff --git a/README.md b/README.md index 9bcceb17e..443a8a799 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Krill is a Resource Public Key Infrastructure (RPKI) daemon, featuring a Certificate Authority (CA) and publication server, written in Rust. If you have any feedback, we would love to hear from you. Don’t hesitate to [create an issue on Github](https://github.com/NLnetLabs/krill/issues/new) or post a message on -our [forum](https://community.nlnetlabs.nl/c/rpki/11). +our [forum](https://community.nlnetlabs.nl/c/rpki/11). For more information please refer to the [documentation](https://krill.docs.nlnetlabs.nl/en/stable/). diff --git a/doc/krill.1.gz b/doc/krill.1.gz index e521e6618..308c42161 100644 Binary files a/doc/krill.1.gz and b/doc/krill.1.gz differ diff --git a/doc/krill.conf.5.gz b/doc/krill.conf.5.gz index 985db6d43..d686ee2f1 100644 Binary files a/doc/krill.conf.5.gz and b/doc/krill.conf.5.gz differ diff --git a/doc/krillc.1.gz b/doc/krillc.1.gz index 133cbeb3f..ad37f9e28 100755 Binary files a/doc/krillc.1.gz and b/doc/krillc.1.gz differ diff --git a/doc/krillta.1.gz b/doc/krillta.1.gz index 5b760b01e..999a0edc7 100755 Binary files a/doc/krillta.1.gz and b/doc/krillta.1.gz differ diff --git a/doc/krillup.1.gz b/doc/krillup.1.gz index 90c382027..46b2ed1ca 100755 Binary files a/doc/krillup.1.gz and b/doc/krillup.1.gz differ diff --git a/src/api/history.rs b/src/api/history.rs index bc89336ff..bb7d3acf2 100644 --- a/src/api/history.rs +++ b/src/api/history.rs @@ -294,7 +294,7 @@ impl CommandHistoryCriteria { /// Returns whether the given label is included in the criteria. fn matches_label(&self, label: &String) -> bool { if - let Some(includes) = &self.label_includes + let Some(includes) = &self.label_includes && !includes.contains(label) { return false; diff --git a/src/api/status.rs b/src/api/status.rs index 5b660acae..d3a63bf10 100644 --- a/src/api/status.rs +++ b/src/api/status.rs @@ -143,6 +143,12 @@ impl ErrorResponse { } } +impl From<(&'static str, &'static str)> for ErrorResponse { + fn from((label, msg): (&'static str, &'static str)) -> Self { + Self::new(label, msg) + } +} + impl fmt::Display for ErrorResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", &serde_json::to_string(&self).unwrap()) diff --git a/src/bin/krill.rs b/src/bin/krill.rs index 7d067afda..20acf6b03 100644 --- a/src/bin/krill.rs +++ b/src/bin/krill.rs @@ -1,7 +1,6 @@ //! The Krill daemon binary. use std::path::PathBuf; -use std::sync::Arc; use clap::Parser; use clap::crate_version; use log::error; @@ -12,15 +11,12 @@ use krill::daemon::start::start_krill_daemon; //------------ main ---------------------------------------------------------- -#[tokio::main] -async fn main() { +fn main() { let args = Args::parse(); match Config::create(&args.config, false) { Ok(config) => { - if let Err(e) = start_krill_daemon( - Arc::new(config), None - ).await { + if let Err(e) = start_krill_daemon( config, None, None) { error!("Krill failed to start: {e}"); ::std::process::exit(1); } diff --git a/src/cli/client.rs b/src/cli/client.rs index 7fbd6e0e2..a5c4e5889 100644 --- a/src/cli/client.rs +++ b/src/cli/client.rs @@ -592,6 +592,14 @@ impl KrillClient { ).await } + pub async fn repo_refresh( + &self, ca: &CaHandle, + ) -> Result { + self.post_empty( + ca_path(ca).into_iter().chain(once("sync/repo")), + ).await + } + pub async fn roas_list( &self, ca: &CaHandle ) -> Result { diff --git a/src/cli/ta/signer.rs b/src/cli/ta/signer.rs index bd05d76d1..725f22994 100644 --- a/src/cli/ta/signer.rs +++ b/src/cli/ta/signer.rs @@ -19,8 +19,8 @@ use crate::commons::storage::Ident; use crate::commons::httpclient; use crate::tasigner::{ Config, TrustAnchorProxySignerExchanges, - TrustAnchorSigner, TrustAnchorSignerCommand, TrustAnchorSignerInitCommand, - TrustAnchorSignerInitCommandDetails, + TrustAnchorSigner, TrustAnchorSignerCommand, TrustAnchorSignerContext, + TrustAnchorSignerInitCommand, TrustAnchorSignerInitCommandDetails, }; @@ -116,7 +116,7 @@ pub struct TrustAnchorSignerManager { store: AggregateStore, ta_handle: CaHandle, config: Config, - signer: Arc, + signer: KrillSigner, actor: Actor, } @@ -140,6 +140,10 @@ impl TrustAnchorSignerManager { }) } + fn context(&self) -> TrustAnchorSignerContext<'_> { + TrustAnchorSignerContext::new(&self.signer, self.config.ta_timing) + } + pub fn init( &self, info: SignerInitInfo, @@ -158,13 +162,11 @@ impl TrustAnchorSignerManager { tal_rsync: info.tal_rsync, private_key_pem: info.private_key_pem, ta_mft_nr_override: info.ta_mft_nr_override, - timing: self.config.ta_timing, - signer: self.signer.clone(), }, &self.actor, ); - self.store.add(cmd)?; + self.store.add_with_context(cmd, self.context())?; Ok(Success) } @@ -181,12 +183,10 @@ impl TrustAnchorSignerManager { info.repo_info, info.tal_https, info.tal_rsync, - self.config.ta_timing, - self.signer.clone(), &self.actor, ); - self.store.command(cmd)?; + self.store.command_with_context(cmd, self.context())?; Ok(Success) } @@ -204,12 +204,10 @@ impl TrustAnchorSignerManager { let cmd = TrustAnchorSignerCommand::make_process_request_command( &self.ta_handle, signed_request, - self.config.ta_timing, ta_mft_number_override, - self.signer.clone(), &self.actor, ); - self.store.command(cmd)?; + self.store.command_with_context(cmd, self.context())?; self.show_last_response() } diff --git a/src/commons/actor.rs b/src/commons/actor.rs index e30829848..172ef6751 100644 --- a/src/commons/actor.rs +++ b/src/commons/actor.rs @@ -3,7 +3,7 @@ //! All actors are represented by the type [`Actor`] defined in this module. //! An actor can be anonymous, a system actor representing Krill’s own //! subsytems, or a user identified by the server’s -//! [`Authorizer`](crate::daemon::auth::Authorizer). +//! [`Authorizer`](crate::daemon::http::auth::Authorizer). use std::fmt; use std::sync::Arc; diff --git a/src/commons/crypto/signing/dispatch/signerinfo.rs b/src/commons/crypto/signing/dispatch/signerinfo.rs index fe109b3a5..89b508b58 100644 --- a/src/commons/crypto/signing/dispatch/signerinfo.rs +++ b/src/commons/crypto/signing/dispatch/signerinfo.rs @@ -334,6 +334,8 @@ impl Aggregate for SignerInfo { type Error = Error; + type Context<'a> = (); + fn init(handle: &MyHandle, init: SignerInfoInitEvent) -> Self { SignerInfo { version: 0, @@ -373,6 +375,7 @@ impl Aggregate for SignerInfo { fn process_command( &self, command: Self::Command, + _context: Self::Context<'_>, ) -> Result, Self::Error> { Ok(match command.into_details() { SignerInfoCommandDetails::Init => { @@ -413,6 +416,7 @@ impl Aggregate for SignerInfo { fn process_init_command( command: SignerInfoInitCommand, + _context: Self::Context<'_>, ) -> Result { let details = command.into_details(); Ok(SignerInfoInitEvent { diff --git a/src/commons/error.rs b/src/commons/error.rs index ac5fc2c82..505c0daeb 100644 --- a/src/commons/error.rs +++ b/src/commons/error.rs @@ -223,6 +223,7 @@ pub enum Error { //----------------------------------------------------------------- // System Issues //----------------------------------------------------------------- + InternalError(String), IoError(KrillIoError), KeyValueError(KeyValueError), QueueError(queue::Error), @@ -406,6 +407,7 @@ impl fmt::Display for Error { //----------------------------------------------------------------- // System Issues //----------------------------------------------------------------- + Error::InternalError(e) => write!(f, "Internal error: {e}"), Error::IoError(e) => write!(f, "I/O error: {e}"), Error::KeyValueError(e) => write!(f, "Key/Value error: {e}"), Error::QueueError(e) => write!(f, "Queue error: {e}"), @@ -750,6 +752,10 @@ impl Error { pub fn io_error_with_context(context: String, cause: io::Error) -> Self { Error::IoError(KrillIoError::new(context, cause)) } + + pub fn internal(msg: impl fmt::Display) -> Self { + Error::InternalError(msg.to_string()) + } } impl std::error::Error for Error {} @@ -760,7 +766,8 @@ impl Error { match self { // Most is bad requests by users, so just mapping the things that // are not - Error::IoError(_) + Error::InternalError(_) + | Error::IoError(_) | Error::SignerError(_) | Error::AggregateStoreError(_) | Error::WalStoreError(_) @@ -792,6 +799,11 @@ impl Error { // System Issues (label: sys-*) //----------------------------------------------------------------- + // internal server error + Error::InternalError(e) => { + ErrorResponse::new("sys-internal", self).with_cause(e) + } + // internal server error Error::IoError(e) => { ErrorResponse::new("sys-io", self).with_cause(e) diff --git a/src/commons/eventsourcing/agg.rs b/src/commons/eventsourcing/agg.rs index f29dfc810..4f979a7aa 100644 --- a/src/commons/eventsourcing/agg.rs +++ b/src/commons/eventsourcing/agg.rs @@ -40,7 +40,9 @@ pub trait Aggregate: Storable + 'static { >; /// The type representing consecutive commands. - type Command: Command; + type Command: Command< + StorableDetails = Self::StorableCommandDetails + >; /// The type representing the details of a command to be stored. type StorableCommandDetails: WithStorableDetails; @@ -54,6 +56,12 @@ pub trait Aggregate: Storable + 'static { /// The type returned when processing a command fails. type Error: std::error::Error + Send + Sync + From; + /// The type for passing context into processing. + /// + /// This should be a shared reference or a collection of + /// shared references, hence the requirement to be `Copy`. + type Context<'a>: Copy where Self: 'a; + /// Creates a new instance. /// /// Expects an [`InitEvent`][Self::InitEvent] with data needed to @@ -77,6 +85,7 @@ pub trait Aggregate: Storable + 'static { /// history. fn process_init_command( command: Self::InitCommand, + context: Self::Context<'_>, ) -> Result; /// Processes a command. @@ -91,6 +100,7 @@ pub trait Aggregate: Storable + 'static { fn process_command( &self, command: Self::Command, + context: Self::Context<'_>, ) -> Result, Self::Error>; /// Returns the current version of the aggregate. @@ -124,6 +134,36 @@ pub trait Aggregate: Storable + 'static { } } } + + /// Process events before they are saved. + /// + /// This method is called on the updated aggregate, i.e., the events + /// given by `events` have already been applied to it. + /// + /// The method is allowed to return an error, in which case all the + /// changes made by `events` are rolled back to the previous version of + /// the aggregate. + /// + /// The default implementation of this method does nothing and returns + /// `Ok(())`. + fn pre_save_events( + &self, events: &[Self::Event], context: Self::Context<'_> + ) -> Result<(), Self::Error> { + let _ = (events, context); + Ok(()) + } + + /// Process events after they have been saved. + /// + /// This method is called on the updated aggregate, i.e., the events + /// given by `events` have already been applied to it. + /// + /// The default implementation does nothing. + fn post_save_events( + &self, events: &[Self::Event], context: Self::Context<'_> + ) { + let _ = (events, context); + } } @@ -679,26 +719,6 @@ impl StoredEffect { } -//------------ PreSaveEventListener ------------------------------------------ - -/// A listener that receives events before the aggregate is saved. -/// -/// The listener is allowed to return an error in case of issues, which will -/// will result in rolling back the intended change to an aggregate. -pub trait PreSaveEventListener: Send + Sync + 'static { - fn listen(&self, agg: &A, events: &[A::Event]) -> Result<(), A::Error>; -} - -//------------ PostSaveEventListener ----------------------------------------- - -/// A listener that receives events after the aggregate is saved. -/// -/// The listener is not allowed to fail. -pub trait PostSaveEventListener: Send + Sync + 'static { - fn listen(&self, agg: &A, events: &[A::Event]); -} - - //------------ Helper Functions ---------------------------------------------- /// Unfailably creates a JSON value from a serializable object. diff --git a/src/commons/eventsourcing/mod.rs b/src/commons/eventsourcing/mod.rs index 05c6eb5fa..4821c1975 100644 --- a/src/commons/eventsourcing/mod.rs +++ b/src/commons/eventsourcing/mod.rs @@ -110,8 +110,8 @@ //! should be called “aggregate root,” but because this a bit wordy Krill //! just calls it 'Aggregate' instead. The trait [`Aggregate`] is defined for //! this that is implemented by, for instance, -//! [`CertAuth`][crate::ca::CertAuth] and -//! [`RepositoryAccess`][crate::pubd::RepositoryAccess]. +//! [`CertAuth`][crate::server::ca::CertAuth] and +//! [`RepositoryAccess`][crate::server::pubd::RepositoryAccess]. //! //! As we will see further down, when we get to describe the //! [`AggregateStore`] which ties all of this together. This allows us to @@ -164,7 +164,7 @@ //! implemented in aggregates and used event sourcing. However, CAs in the //! RPKI have to re-publish CRLs and Manifests very often. Even when //! there are no changes to ROAs these objects must be refreshed before they -//! go stale. By default Krill re-publishes every! 16 hours. +//! go stale. By default Krill re-publishes every 16 hours. //! //! This resulted in an enormous amount of commands and events to be //! generated for CAs, for a change that is done automatically – and which @@ -174,16 +174,17 @@ //! events from scratch, i.e., without using snapshots. //! //! Therefore we decided to implement a hybrid model in Krill. The Krill -//! [`CertAuth`][crate::ca::CertAuth] is still in charge of *almost* +//! [`CertAuth`][crate::server::ca::CertAuth] is still in charge of *almost* //! all changes, and in particular all *semantic* changes that users made. //! But, the generation of Manifests and CRLs is offloaded to an associated -//! component [`CaObjects`][crate::ca::publishing::CaObjects] that just keeps +//! component +//! [`CaObjects`][crate::server::ca::publishing::CaObjects] that just keeps //! the latest Manifest and CRL. It can re-sign these because it has access //! to a [`KrillSigner`][crate::commons::crypto::KrillSigner] and it can get //! the public key identifier needed for signing from the `CertAuth`. //! //! This is relevant here, because under the hood we use a -//! [`PreSaveEventListener`] to ensure that a new Manifest and CRL are +//! event listeners to ensure that a new Manifest and CRL are //! written when there is a change in ROAs or issued certificates, observed //! in events. //! @@ -191,19 +192,26 @@ //! ## Event Listeners //! //! The Krill event sourcing stack defines two different event listener -//! traits which are called by the [`AggregateStore`] when an aggregate is -//! successfully updated. The first, [`PreSaveEventListener`] is called -//! before updates are saved, and it can fail, the second, -//! [`PostSaveEventListener`] is called after all changes have been applied, -//! and thus cannot fail. +//! which are called by the [`AggregateStore`] when an aggregate is +//! successfully updated. Both of them are methods on the [`Aggregate`] +//! trait. The first, [`pre_save_events`][Aggregate::pre_save_events] is +//! called before updates are saved, and it can fail, the second, +//! [`post_save_events`][Aggregate::post_save_events] is called after all +//! changes have been applied, and thus cannot fail. //! //! In a nutshell, we use the event listeners for two things: //! //! * a pre-save trigger that the -//! [`CaObjects`][crate::ca::publishing::CaObjects] +//! [`CaObjects`][crate::server::ca::publishing::CaObjects] //! for a CA gets an updated Manifest and CRL, and //! * triggers that follow-up tasks are put on the scheduler, based on events. //! +//! In order to allow these listeners to trigger functionality in other Krill +//! components, the methods receive a reference to a +//! [`KrillRuntime`][crate::server::runtime::KrillRuntime]. This means that +//! the event sourcing module isn’t completely generic and tied to the use in +//! Krill specifically. +//! //! As discussed in issue //! [1182](https://github.com/NLnetLabs/krill/issues/1182), it would be best //! to remove the `PreSaveEventListener` trait and do everything through @@ -301,9 +309,8 @@ mod wal; pub use self::agg::{ Aggregate, Command, CommandDetails, Event, InitCommand, - InitCommandDetails, InitEvent, PostSaveEventListener, - PreSaveEventListener, SentCommand, SentInitCommand, StoredCommand, - StoredCommandBuilder, StoredEffect, WithStorableDetails + InitCommandDetails, InitEvent, SentCommand, SentInitCommand, + StoredCommand, StoredCommandBuilder, StoredEffect, WithStorableDetails }; pub use self::store::{AggregateStore, AggregateStoreError, Storable}; pub use self::wal::{ diff --git a/src/commons/eventsourcing/store.rs b/src/commons/eventsourcing/store.rs index 0caf9317a..a188e8b5b 100644 --- a/src/commons/eventsourcing/store.rs +++ b/src/commons/eventsourcing/store.rs @@ -18,10 +18,7 @@ use crate::api::history::{ }; use crate::commons::error::KrillIoError; use crate::commons::storage::{Ident, KeyValueError, KeyValueStore}; -use super::agg::{ - Aggregate, Command, InitCommand, PostSaveEventListener, - PreSaveEventListener, StoredCommand -}; +use super::agg::{Aggregate, Command, InitCommand, StoredCommand}; //------------ Storable ------------------------------------------------------ @@ -61,12 +58,6 @@ pub struct AggregateStore { /// A cache for the command history of an instance. history_cache: Option>>>, - - /// The pre-save listeners. - pre_save_listeners: Vec>>, - - /// The post-save listeners. - post_save_listeners: Vec>>, } /// # Starting up @@ -114,8 +105,6 @@ impl AggregateStore { else { None }, - pre_save_listeners: Vec::new(), - post_save_listeners: Vec::new(), } } @@ -135,23 +124,6 @@ impl AggregateStore { } Ok(()) } - - /// Adds a listener that will receive all events before they are stored. - pub fn add_pre_save_listener>( - &mut self, - sync_listener: Arc, - ) { - self.pre_save_listeners.push(sync_listener); - } - - /// Adds a listener that will receive a reference to all events after they - /// are stored. - pub fn add_post_save_listener>( - &mut self, - listener: Arc, - ) { - self.post_save_listeners.push(listener); - } } /// # Manage Aggregates @@ -217,7 +189,9 @@ impl AggregateStore { } /// Adds a new aggregate instance based on the init command. - pub fn add(&self, cmd: A::InitCommand) -> Result, A::Error> { + pub fn add_with_context( + &self, cmd: A::InitCommand, context: A::Context<'_>, + ) -> Result, A::Error> { let scope = Self::scope_for_agg(cmd.handle()); self.kv.execute(Some(&scope), |kv| { @@ -242,7 +216,7 @@ impl AggregateStore { // XXX cmd needs to be cloned here because of the Fn // closure of execute. - match A::process_init_command(cmd.clone()) { + match A::process_init_command(cmd.clone(), context) { Ok(init_event) => { let aggregate = A::init( cmd.handle(), init_event.clone(), @@ -285,8 +259,10 @@ impl AggregateStore { /// /// On error, it will save the command and the error, then return the /// error. - pub fn command(&self, cmd: A::Command) -> Result, A::Error> { - self.execute_opt_command(cmd.handle(), Some(&cmd), false) + pub fn command_with_context( + &self, cmd: A::Command, context: A::Context<'_> + ) -> Result, A::Error> { + self.execute_opt_command(cmd.handle(), Some((&cmd, context)), false) } /// Get the latest aggregate and optionally apply a command to it. @@ -295,7 +271,7 @@ impl AggregateStore { fn execute_opt_command( &self, handle: &MyHandle, - cmd_opt: Option<&A::Command>, + cmd_opt: Option<(&A::Command, A::Context<'_>)>, save_snapshot: bool, ) -> Result, A::Error> { let scope = Self::scope_for_agg(handle); @@ -401,7 +377,7 @@ impl AggregateStore { // If a command was passed in, try to apply it, and make sure that // it is preserved. - let res = if let Some(cmd) = cmd_opt { + let res = if let Some((cmd, context)) = cmd_opt { let aggregate = Arc::make_mut(&mut agg); let version = aggregate.version(); @@ -438,7 +414,7 @@ impl AggregateStore { std::process::exit(1); } - match aggregate.process_command(cmd.clone()) { + match aggregate.process_command(cmd.clone(), context) { Err(e) => { // Store the processed command with the error. let processed = processed.finish_with_error(&e); @@ -471,18 +447,14 @@ impl AggregateStore { // should inform the pre-save listeners. They may // still generate errors, and if they do, then we // return with an error, without saving. - let mut opt_err: Option = None; - if let Some(events) = processed.events() { - for pre_save_listener - in &self.pre_save_listeners { - if let Err(e) - = pre_save_listener.as_ref() - .listen(aggregate, events) - { - opt_err = Some(e); - break; - } - } + let mut opt_err = None; + if + let Some(events) = processed.events() + && let Err(err) = aggregate.pre_save_events( + events, context + ) + { + opt_err = Some(err); } if let Some(e) = opt_err { @@ -500,11 +472,9 @@ impl AggregateStore { // Now send the events to the 'post-save' // listeners. if let Some(events) = processed.events() { - for listener in &self.post_save_listeners { - listener.as_ref().listen( - aggregate, events - ); - } + aggregate.post_save_events( + events, context + ); } Ok(()) @@ -555,6 +525,16 @@ impl AggregateStore { } } +impl<'a, A: Aggregate = ()>> AggregateStore { + pub fn add(&self, cmd: A::InitCommand) -> Result, A::Error> { + self.add_with_context(cmd, ()) + } + + pub fn command(&self, cmd: A::Command) -> Result, A::Error> { + self.command_with_context(cmd, ()) + } +} + //--- Command History diff --git a/src/commons/eventsourcing/test.rs b/src/commons/eventsourcing/test.rs index 4ae69874e..afc196df3 100644 --- a/src/commons/eventsourcing/test.rs +++ b/src/commons/eventsourcing/test.rs @@ -5,7 +5,7 @@ use std::fmt; use std::str::FromStr; -use std::sync::{Arc, RwLock}; +use std::sync::{RwLock}; use serde::{Deserialize, Serialize}; use rpki::ca::idexchange::MyHandle; use crate::api::history::{CommandHistoryCriteria, CommandSummary}; @@ -302,6 +302,8 @@ impl Aggregate for Person { type Error = PersonError; + type Context<'a> = &'a EventCounter; + fn init(id: &MyHandle, event: PersonInitEvent) -> Self { Person { id: id.clone(), @@ -313,6 +315,7 @@ impl Aggregate for Person { fn process_init_command( command: Self::InitCommand, + _context: Self::Context<'_>, ) -> Result { Ok(PersonInitEvent { name: command.into_details().name, @@ -337,6 +340,7 @@ impl Aggregate for Person { fn process_command( &self, command: Self::Command, + _context: Self::Context<'_>, ) -> Result, Self::Error> { match command.into_details() { PersonCommandDetails::ChangeName(name) => { @@ -353,6 +357,12 @@ impl Aggregate for Person { } } } + + fn post_save_events( + &self, events: &[Self::Event], context: Self::Context<'_> + ) { + context.counter.write().unwrap().total += events.len(); + } } @@ -381,12 +391,6 @@ impl EventCounter { } } -impl PostSaveEventListener for EventCounter { - fn listen(&self, _agg: &A, events: &[A::Event]) { - self.counter.write().unwrap().total += events.len(); - } -} - //------------ Test Function ------------------------------------------------- @@ -394,22 +398,21 @@ impl PostSaveEventListener for EventCounter { fn event_sourcing_framework() { let storage_uri = mem_storage(); - let counter = Arc::new(EventCounter::default()); + let counter = EventCounter::default(); - let mut manager = AggregateStore::::create( + let manager = AggregateStore::::create( &storage_uri, const { Ident::make("person") }, false, ) .unwrap(); - manager.add_post_save_listener(counter.clone()); let alice_name = "alice smith".to_string(); let alice_handle = MyHandle::from_str("alice").unwrap(); let alice_init_cmd = PersonInitCommand::make(alice_handle.clone(), alice_name); - manager.add(alice_init_cmd).unwrap(); + manager.add_with_context(alice_init_cmd, &counter).unwrap(); let mut alice = manager.get_latest(&alice_handle).unwrap(); assert_eq!("alice smith", alice.name()); @@ -420,7 +423,7 @@ fn event_sourcing_framework() { let get_older = PersonCommand::go_around_sun( alice_handle.clone(), None ); - alice = manager.command(get_older).unwrap(); + alice = manager.command_with_context(get_older, &counter).unwrap(); age += 1; if age == 21 { @@ -436,7 +439,7 @@ fn event_sourcing_framework() { Some(22), "alice smith-doe", ); - let alice = manager.command(change_name).unwrap(); + let alice = manager.command_with_context(change_name, &counter).unwrap(); assert_eq!("alice smith-doe", alice.name()); assert_eq!(21, alice.age()); diff --git a/src/commons/eventsourcing/wal.rs b/src/commons/eventsourcing/wal.rs index 2b951da4e..6439194b1 100644 --- a/src/commons/eventsourcing/wal.rs +++ b/src/commons/eventsourcing/wal.rs @@ -43,8 +43,8 @@ use super::store::Storable; /// # Use within Krill /// /// Within Krill, write-ahead logging is currently used by the -/// [`Scheduler`][crate::daemon::scheduler::Scheduler] and -/// [`RepositoryContent`][crate::pubd::RepositoryContent]. +/// [`TaskQueue`][crate::server::mq::TaskQueue] and +/// [`RepositoryContent`][crate::server::pubd::RepositoryContent]. pub trait WalSupport: Storable { /// The type representing a command. type Command: WalCommand; diff --git a/src/commons/file.rs b/src/commons/file.rs index 706f871ec..8ffa66a54 100644 --- a/src/commons/file.rs +++ b/src/commons/file.rs @@ -55,7 +55,10 @@ pub fn remove_dir_all(dir: &Path) -> Result<(), KrillIoError> { /// Creates a new File or opens an exiting one. If the file did not exist, the /// path will be created if it did not exist yet. pub fn create_file_with_path(path: &Path) -> Result { - if !path.exists() && let Some(parent) = path.parent() { + if + !path.exists() + && let Some(parent) = path.parent() + { trace!("Creating path: {}", parent.to_string_lossy()); fs::create_dir_all(parent).map_err(|e| { KrillIoError::new( diff --git a/src/commons/httpclient.rs b/src/commons/httpclient.rs index 842a8cb92..dc823358c 100644 --- a/src/commons/httpclient.rs +++ b/src/commons/httpclient.rs @@ -7,6 +7,7 @@ use reqwest::{ header::{HeaderMap, HeaderValue, CONTENT_TYPE, USER_AGENT}, Response, StatusCode, }; +use rpki::ca::idexchange::ServiceUri; use serde::de::DeserializeOwned; use serde::ser::Serialize; @@ -261,11 +262,12 @@ async fn do_empty_post( /// Note: Bytes may be empty if the post was successful, but the response was /// empty. pub async fn post_binary_with_full_ua( - uri: &str, - data: &Bytes, + uri: ServiceUri, + data: Bytes, content_type: &str, timeout: u64, ) -> Result { + let uri = uri.as_str(); let body = data.to_vec(); let mut headers = HeaderMap::new(); @@ -472,7 +474,7 @@ pub enum Error { RequestBuild(ErrorUri, ErrorMessage), RequestBuildHttpsCert(RootCertPath, ErrorMessage), - RequestExecute(ErrorUri, ErrorMessage), + RequestExecute(ErrorUri, reqwest::Error), Response(ErrorUri, ErrorMessage), Forbidden(ErrorUri), @@ -481,52 +483,6 @@ pub enum Error { ErrorResponseWithJson(ErrorUri, StatusCode, Box), } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::RequestBuild(uri, msg) => write!( - f, - "Issue creating request for URI: {uri}, error: {msg}" - ), - Error::RequestBuildHttpsCert(path, msg) => { - write!( - f, - "Cannot use configured HTTPS root cert '{path}'. Error: {msg}" - ) - } - - Error::RequestExecute(uri, msg) => { - write!(f, "Issue accessing URI: {uri}, error: {msg}") - } - - Error::Response(uri, msg) => write!( - f, - "Issue processing response from URI: {uri}, error: {msg}" - ), - Error::Forbidden(uri) => { - write!(f, "Got 'Forbidden' response for URI: {uri}") - } - Error::ErrorResponse(uri, code) => { - write!( - f, - "Issue processing response from URI: {uri}, \ - error: unexpected status code {code}" - ) - } - Error::ErrorResponseWithBody(uri, code, e) => { - write!( - f, - "Error response from URI: {uri}, Status: {code}, Error: {e}" - ) - } - Error::ErrorResponseWithJson(uri, code, res) => write!( - f, - "Error response from URI: {uri}, Status: {code}, ErrorResponse: {res}" - ), - } - } -} - impl Error { pub fn request_build(uri: &str, msg: impl fmt::Display) -> Self { Error::RequestBuild(uri.to_string(), msg.to_string()) @@ -546,8 +502,8 @@ impl Error { Error::RequestBuildHttpsCert(path.to_string(), msg.to_string()) } - pub fn execute(uri: &str, msg: impl fmt::Display) -> Self { - Error::RequestExecute(uri.to_string(), msg.to_string()) + pub fn execute(uri: &str, msg: reqwest::Error) -> Self { + Error::RequestExecute(uri.to_string(), msg) } pub fn response(uri: &str, msg: impl fmt::Display) -> Self { @@ -602,3 +558,58 @@ impl Error { } } } + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::RequestBuild(uri, msg) => write!( + f, + "Issue creating request for URI: {uri}, error: {msg}" + ), + Error::RequestBuildHttpsCert(path, msg) => { + write!( + f, + "Cannot use configured HTTPS root cert '{path}'. Error: {msg}" + ) + } + + Error::RequestExecute(uri, msg) => { + use std::error::Error as _; + + write!(f, "Issue accessing URI: {uri}, error: {msg}")?; + let mut cause = msg.source(); + while let Some(err) = cause { + write!(f, " - {err}")?; + cause = err.source(); + } + Ok(()) + } + + Error::Response(uri, msg) => write!( + f, + "Issue processing response from URI: {uri}, error: {msg}" + ), + Error::Forbidden(uri) => { + write!(f, "Got 'Forbidden' response for URI: {uri}") + } + Error::ErrorResponse(uri, code) => { + write!( + f, + "Issue processing response from URI: {uri}, \ + error: unexpected status code {code}" + ) + } + Error::ErrorResponseWithBody(uri, code, e) => { + write!( + f, + "Error response from URI: {uri}, Status: {code}, Error: {e}" + ) + } + Error::ErrorResponseWithJson(uri, code, res) => write!( + f, + "Error response from URI: {uri}, Status: {code}, ErrorResponse: {res}" + ), + } + } +} + diff --git a/src/commons/storage/backends/disk.rs b/src/commons/storage/backends/disk.rs index 624d11c68..9c13ceaa7 100644 --- a/src/commons/storage/backends/disk.rs +++ b/src/commons/storage/backends/disk.rs @@ -263,10 +263,10 @@ impl Store { )); } }; - if + if file_type.is_file() - && let Some(name) - = item.file_name().into_string().ok().and_then(|name| { + && let Some(name) = + item.file_name().into_string().ok().and_then(|name| { Ident::boxed_from_string(name).ok() }) { diff --git a/src/config.rs b/src/config.rs index d2e10d3a7..d0f2b759e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -225,7 +225,7 @@ impl ConfigDefaults { } pub fn roa_deaggregate_threshold() -> usize { - if + if let Ok(from_env) = env::var("KRILL_ROA_DEAGGREGATE_THRESHOLD") && let Ok(nr) = usize::from_str(&from_env) { @@ -577,6 +577,8 @@ pub struct Config { #[serde(default = "ConfigDefaults::syslog_facility")] pub syslog_facility: String, + pub num_threads: Option, + #[serde(default = "ConfigDefaults::admin_token", alias = "auth_token")] pub admin_token: Token, @@ -682,7 +684,7 @@ pub struct Config { pub ta_timing: TaTimingConfig, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Copy, Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct IssuanceTimingConfig { #[serde(default = "ConfigDefaults::timing_publish_next_hours")] @@ -1131,6 +1133,10 @@ impl Config { self.testbed.as_ref() } + pub fn testbed_enabled(&self) -> bool { + self.testbed.is_some() + } + /// Returns a reference to the default signer configuration. /// /// Assumes that the configuration is valid. Will panic otherwise. @@ -1301,6 +1307,7 @@ impl Config { log_type, log_file: None, syslog_facility, + num_threads: None, admin_token, auth_type, #[cfg(feature = "multi-user")] @@ -1617,10 +1624,9 @@ impl Config { )); } - if + if let Some(threshold) = self.suspend_child_after_inactive_hours - && threshold < CA_SUSPEND_MIN_HOURS - { + && threshold < CA_SUSPEND_MIN_HOURS { return Err(ConfigError::Other(format!( "suspend_child_after_inactive_hours must be {CA_SUSPEND_MIN_HOURS} or higher (or not set at all)" ))); diff --git a/src/constants.rs b/src/constants.rs index 747a7ce08..2ab4629c8 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -56,7 +56,7 @@ pub const KRILL_ENV_LOG_LEVEL: &str = "KRILL_LOG_LEVEL"; /// The environment variable with the log target. /// /// The variable should contain the name of a -/// [`LogType`][crate::daemon::config::LogType]. It will be overwritten by +/// [`LogType`][crate::config::LogType]. It will be overwritten by /// the config file. The default is “file.” pub const KRILL_ENV_LOG_TYPE: &str = "KRILL_LOG_TYPE"; diff --git a/src/daemon/http/auth/authorizer.rs b/src/daemon/http/auth/authorizer.rs index fe967b4d5..388422962 100644 --- a/src/daemon/http/auth/authorizer.rs +++ b/src/daemon/http/auth/authorizer.rs @@ -39,6 +39,7 @@ use super::providers::{config_file, openid_connect}; /// /// This type is a wrapper around the available backend specific auth /// providers that can be found in the [super::providers] module. +#[allow(clippy::large_enum_variant)] enum AuthProvider { Token(admin_token::AuthProvider), @@ -202,25 +203,23 @@ impl Authorizer { /// /// The authorizer will be created according to information provided via /// `config`. - pub fn new( - config: Arc, - ) -> KrillResult { + pub fn new(config: &Config) -> KrillResult { let (primary_provider, legacy_provider) = match config.auth_type { AuthType::AdminToken => { - (admin_token::AuthProvider::new(config.clone()).into(), None) + (admin_token::AuthProvider::new(config).into(), None) } #[cfg(feature = "multi-user")] AuthType::ConfigFile => { ( - config_file::AuthProvider::new(&config)?.into(), - Some(admin_token::AuthProvider::new(config.clone())) + config_file::AuthProvider::new(config)?.into(), + Some(admin_token::AuthProvider::new(config)) ) } #[cfg(feature = "multi-user")] AuthType::OpenIDConnect => { ( - openid_connect::AuthProvider::new(config.clone())?.into(), - Some(admin_token::AuthProvider::new(config.clone())) + openid_connect::AuthProvider::new(config)?.into(), + Some(admin_token::AuthProvider::new(config)) ) } }; @@ -229,7 +228,7 @@ impl Authorizer { primary_provider, legacy_provider, #[cfg(unix)] - unix_socket_provider: unix_user::AuthProvider::new(config.clone())? + unix_socket_provider: unix_user::AuthProvider::new(config)? }) } diff --git a/src/daemon/http/auth/mod.rs b/src/daemon/http/auth/mod.rs index 40316795c..68adf83ad 100644 --- a/src/daemon/http/auth/mod.rs +++ b/src/daemon/http/auth/mod.rs @@ -1,4 +1,4 @@ - +//! User authentication and authorization for the HTTP server. pub use self::authorizer::{AuthInfo, Authorizer, LoggedInUser}; pub use self::permission::{Permission, PermissionSet}; diff --git a/src/daemon/http/auth/providers/admin_token.rs b/src/daemon/http/auth/providers/admin_token.rs index 546323558..c0e12571e 100644 --- a/src/daemon/http/auth/providers/admin_token.rs +++ b/src/daemon/http/auth/providers/admin_token.rs @@ -41,7 +41,7 @@ pub struct AuthProvider { impl AuthProvider { /// Creates a new admin token auth provider from the given config. - pub fn new(config: Arc) -> Self { + pub fn new(config: &Config) -> Self { AuthProvider { required_token: config.admin_token.clone(), user_id: "admin-token".into(), diff --git a/src/daemon/http/auth/providers/openid_connect/provider.rs b/src/daemon/http/auth/providers/openid_connect/provider.rs index e0383049f..09aab2166 100644 --- a/src/daemon/http/auth/providers/openid_connect/provider.rs +++ b/src/daemon/http/auth/providers/openid_connect/provider.rs @@ -49,6 +49,7 @@ use openidconnect::{ CoreRevocableToken, } }; +use rpki::uri; use serde::{Deserialize, Serialize}; use tokio::runtime; use urlparse::{urlparse, GetQuery}; @@ -77,7 +78,7 @@ use crate::{ }, }, session::*, - AuthInfo, LoggedInUser, Permission, + AuthInfo, LoggedInUser, Permission, RoleMap, }, http::util::url_encode, }, @@ -168,20 +169,33 @@ type Session = ClientSession; //------------ AuthProvider -------------------------------------------------- pub struct AuthProvider { - config: Arc, + oidc_conf: ConfigAuthOpenIDConnect, + + /// The role directory. + roles: Arc, + + /// The URI we provide our service under. + service_uri: uri::Https, + session_cache: SessionCache, session_key: CryptState, conn: Arc>>, } impl AuthProvider { - pub fn new( - config: Arc, - ) -> KrillResult { - let session_key = Self::init_session_key(&config)?; + pub fn new(config: &Config) -> KrillResult { + let session_key = Self::init_session_key(config)?; + + let Some(oidc_conf) = config.auth_openidconnect.as_ref() else { + return Err(Error::ConfigError( + "Missing [auth_openidconnect] config section!".into(), + )); + }; Ok(Self { - config, + oidc_conf: oidc_conf.clone(), + roles: config.auth_roles.clone(), + service_uri: config.service_uri(), session_cache: SessionCache::new(), session_key, conn: Arc::new(RwLock::new(None)), @@ -238,9 +252,9 @@ impl AuthProvider { // URL. Strip off /.well-known/openid-configuration because // the openid-connect crate wants to add this itself and will // fail if it is already present in the URL. - let issuer = self.oidc_conf()?.issuer_url.clone(); - let issuer = - issuer.trim_end_matches("/.well-known/openid-configuration"); + let issuer = self.oidc_conf.issuer_url.trim_end_matches( + "/.well-known/openid-configuration" + ); let issuer = IssuerUrl::new(issuer.to_string())?; info!( @@ -387,12 +401,12 @@ impl AuthProvider { // | | | supported // -------------------|-------------------|------------------|--------------------------------------------- - let config_file_url = self.oidc_conf()?.logout_url.as_ref(); + let config_file_url = self.oidc_conf.logout_url.as_ref(); let mut rp_initiated_logout_url = meta.additional_metadata().end_session_endpoint.as_ref(); let mut revocation_url = meta.additional_metadata().revocation_endpoint.as_ref(); - let service_uri = self.config.service_uri().as_str().to_string(); + let service_uri = self.service_uri.as_str().to_string(); if let Some(rev_url) = revocation_url { // From: https://tools.ietf.org/html/rfc7009#section-2 @@ -472,10 +486,9 @@ impl AuthProvider { // been obtained by the Krill operator when they created a // registration for their Krill instance with their identity // provider. - let oidc_conf = self.oidc_conf()?; - let client_id = ClientId::new(oidc_conf.client_id.clone()); + let client_id = ClientId::new(self.oidc_conf.client_id.clone()); let client_secret = - ClientSecret::new(oidc_conf.client_secret.clone()); + ClientSecret::new(self.oidc_conf.client_secret.clone()); // Create a client we can use to communicate with the provider based // on what we just learned and using the credentials we read @@ -497,8 +510,7 @@ impl AuthProvider { // that we can exchange the temporary code for access and id // tokens. let redirect_uri = RedirectUrl::new( - self.config - .service_uri() + self.service_uri .join(AUTH_CALLBACK_ENDPOINT.as_bytes()) .unwrap() .to_string(), @@ -747,15 +759,6 @@ impl AuthProvider { crypt::crypt_init(config) } - fn oidc_conf(&self) -> KrillResult<&ConfigAuthOpenIDConnect> { - match &self.config.auth_openidconnect { - Some(oidc_conf) => Ok(oidc_conf), - None => Err(Error::ConfigError( - "Missing [auth_openidconnect] config section!".into(), - )), - } - } - fn extract_cookie( &self, request: &HyperRequest, @@ -1009,7 +1012,7 @@ impl AuthProvider { let mut id_token_verifier: CoreIdTokenVerifier = conn.client.id_token_verifier(); - if self.oidc_conf()?.insecure { + if self.oidc_conf.insecure { // This is NOT a good idea. It was needed when testing with // one provider and so may be of use to others in future // too. @@ -1113,7 +1116,7 @@ impl AuthProvider { ) -> KrillResult { Ok(AuthInfo::user( session.user_id.clone(), - self.config.auth_roles.get(&session.secrets.role).ok_or_else(|| { + self.roles.get(&session.secrets.role).ok_or_else(|| { ApiAuthError::ApiAuthPermanentError( format!( "user '{}' with undefined role '{}' \ @@ -1415,10 +1418,6 @@ impl AuthProvider { || Nonce::new(URL_BASE64_ENGINE.encode(nonce_hash)), ); - // This unwrap is safe as we check in new() that the OpenID Connect - // config exists. - let oidc_conf = self.oidc_conf()?; - // From https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest: // "prompt: login - The Authorization Server SHOULD prompt the // End-User for re-authentication. If it cannot re-authenticate the @@ -1430,7 +1429,7 @@ impl AuthProvider { // it has some notion of an existing login session. // https://github.com/NLnetLabs/krill/issues/614 - if oidc_conf.prompt_for_login { + if self.oidc_conf.prompt_for_login { request = request.add_prompt(CoreAuthPrompt::Login); } @@ -1452,11 +1451,11 @@ impl AuthProvider { // TODO: use request.set_pkce_challenge() ? - for scope in &oidc_conf.extra_login_scopes { + for scope in &self.oidc_conf.extra_login_scopes { request = request.add_scope(Scope::new(scope.clone())); } - for (k, v) in oidc_conf.extra_login_params.iter() { + for (k, v) in self.oidc_conf.extra_login_params.iter() { request = request.add_extra_param(k, v); } @@ -1669,7 +1668,7 @@ impl AuthProvider { id_token_claims, user_info_claims ); let id = claims.extract_claims( - &self.oidc_conf()?.id_claims + &self.oidc_conf.id_claims )?.ok_or_else(|| { Self::internal_error( "OpenID Connect: cannot determine user ID.", @@ -1677,14 +1676,14 @@ impl AuthProvider { ) })?; let role_name = claims.extract_claims( - &self.oidc_conf()?.role_claims + &self.oidc_conf.role_claims )?.ok_or_else(|| { Self::internal_error( "OpenID Connect: cannot determine user's role.", None ) })?; - let role = self.config.auth_roles.get( + let role = self.roles.get( &role_name ).ok_or_else(|| { let reason = format!( diff --git a/src/daemon/http/auth/providers/unix_user.rs b/src/daemon/http/auth/providers/unix_user.rs index 58a606dde..8ad34b8e1 100644 --- a/src/daemon/http/auth/providers/unix_user.rs +++ b/src/daemon/http/auth/providers/unix_user.rs @@ -32,7 +32,7 @@ pub struct AuthProvider { impl AuthProvider { /// Creates a new unix user auth provider from the given config. - pub fn new(config: Arc) -> KrillResult { + pub fn new(config: &Config) -> KrillResult { let mut unix_users = HashMap::new(); for (k, v) in config.unix_users().iter() { if let Some(role) = config.auth_roles.get(v) { diff --git a/src/daemon/http/auth/roles.rs b/src/daemon/http/auth/roles.rs index 8137ed44d..3973fa439 100644 --- a/src/daemon/http/auth/roles.rs +++ b/src/daemon/http/auth/roles.rs @@ -19,7 +19,7 @@ use super::{Permission, PermissionSet}; /// do not operate on resources. /// /// Currently, roles are given names and are defined in -/// [Config::auth_roles][crate::daemon::config::Config::auth_roles] and +/// [Config::auth_roles][crate::config::Config::auth_roles] and /// referenced by authorization providers through those names. #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[serde(from = "RoleConf")] diff --git a/src/daemon/http/dispatch/bulk.rs b/src/daemon/http/dispatch/bulk.rs index 539ae62c9..4983a7ac5 100644 --- a/src/daemon/http/dispatch/bulk.rs +++ b/src/daemon/http/dispatch/bulk.rs @@ -23,11 +23,11 @@ async fn cas( ) -> Result { match path.next() { Some("import") => cas_import(request, path).await, - Some("issues") => cas_issues(request, path), - Some("sync") => cas_sync(request, path), - Some("publish") => cas_publish(request, path), - Some("force_publish") => cas_force_publish(request, path), - Some("suspend") => cas_suspend(request, path), + Some("issues") => cas_issues(request, path).await, + Some("sync") => cas_sync(request, path).await, + Some("publish") => cas_publish(request, path).await, + Some("force_publish") => cas_force_publish(request, path).await, + Some("suspend") => cas_suspend(request, path).await, _ => Ok(HttpResponse::not_found()) } } @@ -44,7 +44,7 @@ async fn cas_import( Ok(HttpResponse::ok()) } -fn cas_issues( +async fn cas_issues( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -54,9 +54,9 @@ fn cas_issues( let server = request.empty()?; let mut all_issues = AllCertAuthIssues::default(); - for ca in server.krill().ca_handles()? { + for ca in server.krill().ca_handles().await? { if auth.has_permission(Permission::CaRead, Some(&ca)) { - let issues = server.krill().ca_issues(&ca)?; + let issues = server.krill().ca_issues(ca.clone()).await?; if !issues.is_empty() { all_issues.cas.insert(ca, issues); } @@ -66,18 +66,18 @@ fn cas_issues( Ok(HttpResponse::json(&all_issues)) } -fn cas_sync( +async fn cas_sync( request: Request<'_>, mut path: PathIter<'_>, ) -> Result { match path.next() { - Some("parent") => cas_sync_parent(request, path), - Some("repo") => cas_sync_repo(request, path), + Some("parent") => cas_sync_parent(request, path).await, + Some("repo") => cas_sync_repo(request, path).await, _ => Ok(HttpResponse::not_found()) } } -fn cas_sync_parent( +async fn cas_sync_parent( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -85,11 +85,11 @@ fn cas_sync_parent( request.check_post()?; let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?; let server = request.empty()?; - server.krill().cas_refresh_all()?; + server.krill().cas_refresh_all().await?; Ok(HttpResponse::ok()) } -fn cas_sync_repo( +async fn cas_sync_repo( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -97,11 +97,11 @@ fn cas_sync_repo( request.check_post()?; let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?; let server = request.empty()?; - server.krill().cas_repo_sync_all()?; + server.krill().cas_repo_sync_all().await?; Ok(HttpResponse::ok()) } -fn cas_publish( +async fn cas_publish( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -109,11 +109,11 @@ fn cas_publish( request.check_post()?; let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?; let server = request.empty()?; - server.krill().republish_all(false)?; + server.krill().republish_all(false).await?; Ok(HttpResponse::ok()) } -fn cas_force_publish( +async fn cas_force_publish( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -121,11 +121,11 @@ fn cas_force_publish( request.check_post()?; let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?; let server = request.empty()?; - server.krill().republish_all(true)?; + server.krill().republish_all(true).await?; Ok(HttpResponse::ok()) } -fn cas_suspend( +async fn cas_suspend( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -133,7 +133,7 @@ fn cas_suspend( request.check_post()?; let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?; let server = request.empty()?; - server.krill().cas_schedule_suspend_all()?; + server.krill().cas_schedule_suspend_all().await?; Ok(HttpResponse::ok()) } diff --git a/src/daemon/http/dispatch/cas.rs b/src/daemon/http/dispatch/cas.rs index adf792b40..57b1f6c82 100644 --- a/src/daemon/http/dispatch/cas.rs +++ b/src/daemon/http/dispatch/cas.rs @@ -14,7 +14,6 @@ use crate::api::import::ImportChild; use crate::api::history::CommandHistoryCriteria; use crate::api::roa::RoaConfigurationUpdates; use crate::commons::error::Error; -use crate::commons::eventsourcing::AggregateStoreError; use super::super::auth::Permission; use super::super::request::{PathIter, Request}; use super::super::response::HttpResponse; @@ -37,13 +36,13 @@ async fn index( request: Request<'_>, ) -> Result { match *request.method() { - Method::GET => index_get(request), + Method::GET => index_get(request).await, Method::POST => index_post(request).await, _ => Ok(HttpResponse::method_not_allowed()) } } -fn index_get( +async fn index_get( request: Request<'_>, ) -> Result { let (request, auth) = request.proceed_unchecked(); @@ -52,11 +51,13 @@ fn index_get( Ok(HttpResponse::json( &CertAuthList { cas: { - server.krill().ca_handles()?.filter_map(|handle| { - auth.has_permission( - Permission::CaRead, Some(&handle) - ).then_some(CertAuthSummary { handle }) - }).collect() + server.krill().ca_handles().await?.into_iter().filter_map( + |handle| { + auth.has_permission( + Permission::CaRead, Some(&handle) + ).then_some(CertAuthSummary { handle }) + } + ).collect() } } )) @@ -69,7 +70,7 @@ async fn index_post( Permission::CaCreate, None )?; let (server, init) = request.read_json().await?; - server.krill().ca_init(init)?; + server.krill().ca_init(init).await?; Ok(HttpResponse::ok()) } @@ -88,15 +89,15 @@ pub async fn ca( Some("aspas") => aspas(request, path, ca).await, Some("bgpsec") => bgpsec(request, path, ca).await, Some("children") => children(request, path, ca).await, - Some("history") => history(request, path, ca), - Some("id") => id(request, path, ca), - Some("issues") => issues(request, path, ca), - Some("keys") => keys(request, path, ca), + Some("history") => history(request, path, ca).await, + Some("id") => id(request, path, ca).await, + Some("issues") => issues(request, path, ca).await, + Some("keys") => keys(request, path, ca).await, Some("parents") => parents(request, path, ca).await, Some("repo") => repo(request, path, ca).await, Some("routes") => routes(request, path, ca).await, - Some("stats") => stats(request, path, ca), - Some("sync") => sync(request, path, ca), + Some("stats") => stats(request, path, ca).await, + Some("sync") => sync(request, path, ca).await, _ => Ok(HttpResponse::not_found()) } } @@ -112,7 +113,7 @@ async fn ca_index( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_info(&ca)? + &server.krill().ca_info(ca).await? )) } Method::DELETE => { @@ -120,7 +121,7 @@ async fn ca_index( Permission::CaDelete, Some(&ca) )?; let server = request.empty()?; - server.krill().ca_delete(&ca, auth.actor()).await?; + server.krill().ca_delete(ca, auth.into_actor()).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) @@ -153,7 +154,7 @@ async fn aspas_index( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_aspas_definitions_show(&ca)? + &server.krill().ca_aspas_definitions_show(ca).await? )) } Method::POST => { @@ -162,8 +163,8 @@ async fn aspas_index( )?; let (server, updates) = request.read_json().await?; server.krill().ca_aspas_definitions_update( - ca, updates, auth.actor(), - )?; + ca, updates, auth.into_actor(), + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) @@ -184,8 +185,8 @@ async fn aspas_as( )?; let (server, update) = request.read_json().await?; server.krill().ca_aspas_update_aspa( - ca, customer, update, auth.actor() - )?; + ca, customer, update, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } Method::DELETE => { @@ -199,8 +200,8 @@ async fn aspas_as( add_or_replace: Vec::new(), remove: vec![customer] }, - auth.actor(), - )?; + auth.into_actor(), + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) @@ -223,7 +224,7 @@ async fn bgpsec( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_bgpsec_definitions_show(&ca)? + &server.krill().ca_bgpsec_definitions_show(ca).await? )) } Method::POST => { @@ -232,8 +233,8 @@ async fn bgpsec( )?; let (server, updates) = request.read_json().await?; server.krill().ca_bgpsec_definitions_update( - ca, updates, auth.actor() - )?; + ca, updates, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) @@ -264,7 +265,9 @@ async fn children_index( )?; let (server, child_req) = request.read_json().await?; Ok(HttpResponse::json( - &server.krill().ca_add_child(&ca, child_req, auth.actor())? + &server.krill().ca_add_child( + ca, child_req, auth.into_actor() + ).await? )) } @@ -277,12 +280,14 @@ async fn children_child( match path.next() { None => children_child_index(request, ca, child).await, Some("contact") | Some("parent_response.json") => { - children_child_contact(request, path, ca, child) + children_child_contact(request, path, ca, child).await } Some("parent_response.xml") => { - children_child_contact_xml(request, path, ca, child) + children_child_contact_xml(request, path, ca, child).await + } + Some("export") => { + children_child_export(request, path, ca, child).await } - Some("export") => children_child_export(request, path, ca, child), Some("import") => { children_child_import(request, path, ca, child).await } @@ -302,7 +307,7 @@ async fn children_child_index( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_child_show(&ca, &child)? + &server.krill().ca_child_show(ca, child).await? )) } Method::POST => { @@ -311,8 +316,8 @@ async fn children_child_index( )?; let (server, child_req) = request.read_json().await?; server.krill().ca_child_update( - &ca, child, child_req, auth.actor() - )?; + ca, child, child_req, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } Method::DELETE => { @@ -320,14 +325,16 @@ async fn children_child_index( Permission::CaUpdate, Some(&ca) )?; let server = request.empty()?; - server.krill().ca_child_remove(&ca, child, auth.actor())?; + server.krill().ca_child_remove( + ca, child, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) } } -fn children_child_contact( +async fn children_child_contact( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -340,11 +347,11 @@ fn children_child_contact( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_parent_response(&ca, child)? + &server.krill().ca_parent_response(ca, child).await? )) } -fn children_child_contact_xml( +async fn children_child_contact_xml( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -356,11 +363,11 @@ fn children_child_contact_xml( Permission::CaRead, Some(&ca) )?; let server = request.empty()?; - let res = server.krill().ca_parent_response(&ca, child)?; + let res = server.krill().ca_parent_response(ca, child).await?; Ok(HttpResponse::xml(res.to_xml_vec())) } -fn children_child_export( +async fn children_child_export( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -373,7 +380,7 @@ fn children_child_export( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_child_export(&ca, &child)? + &server.krill().ca_child_export(ca, child).await? )) } @@ -396,26 +403,26 @@ async fn children_child_import( } )) } - server.krill().ca_child_import(&ca, import, auth.actor())?; + server.krill().ca_child_import(ca, import, auth.into_actor()).await?; Ok(HttpResponse::ok()) } //------------ /api/v1/cas/{ca}/history -------------------------------------- -fn history( +async fn history( request: Request<'_>, mut path: PathIter<'_>, ca: CaHandle, ) -> Result { match path.next() { - Some("commands") => history_commands(request, path, ca), - Some("details") => history_details(request, path, ca), + Some("commands") => history_commands(request, path, ca).await, + Some("details") => history_details(request, path, ca).await, _ => Ok(HttpResponse::not_found()) } } -fn history_commands( +async fn history_commands( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -434,16 +441,16 @@ fn history_commands( Ok(HttpResponse::json( &server.krill().ca_history( - &ca, + ca, CommandHistoryCriteria { before, after, offset, rows_limit, .. Default::default() } - )? + ).await? )) } -fn history_details( +async fn history_details( request: Request<'_>, mut path: PathIter<'_>, ca: CaHandle, @@ -456,47 +463,42 @@ fn history_details( )?; let server = request.empty()?; - Ok(HttpResponse::json( - &server.krill().ca_command_details(&ca, version).map_err(|err| { - match err { - Error::AggregateStoreError( - AggregateStoreError::UnknownCommand(..) - ) => { - HttpResponse::not_found() - }, - err => { - HttpResponse::response_from_error(err) - } - } - })? - )) + let res = match server.krill().ca_command_details(ca, version).await { + Ok(Some(res)) => res, + Ok(None) => return Err(HttpResponse::not_found().into()), + Err(err) => return Err(err.into()) + }; + + Ok(HttpResponse::json(&res)) } //------------ /api/v1/cas/{ca}/id ------------------------------------------- -fn id( +async fn id( request: Request<'_>, mut path: PathIter<'_>, ca: CaHandle, ) -> Result { match path.next() { - None => id_index(request, ca), + None => id_index(request, ca).await, Some("child_request.json") => { - id_child_request_json(request, path, ca) + id_child_request_json(request, path, ca).await + } + Some("child_request.xml") => { + id_child_request_xml(request, path, ca).await } - Some("child_request.xml") => id_child_request_xml(request, path, ca), Some("publisher_request.json") => { - id_publisher_request_json(request, path, ca) + id_publisher_request_json(request, path, ca).await } Some("publisher_request.xml") => { - id_publisher_request_xml(request, path, ca) + id_publisher_request_xml(request, path, ca).await } _ => Ok(HttpResponse::not_found()) } } -fn id_index( +async fn id_index( request: Request<'_>, ca: CaHandle, ) -> Result { @@ -505,11 +507,11 @@ fn id_index( Permission::CaUpdate, Some(&ca) )?; let server = request.empty()?; - server.krill().ca_update_id(ca, auth.actor())?; + server.krill().ca_update_id(ca, auth.into_actor()).await?; Ok(HttpResponse::ok()) } -fn id_child_request_json( +async fn id_child_request_json( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -521,11 +523,11 @@ fn id_child_request_json( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_child_req(&ca)? + &server.krill().ca_child_req(ca).await? )) } -fn id_child_request_xml( +async fn id_child_request_xml( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -537,12 +539,12 @@ fn id_child_request_xml( )?; let server = request.empty()?; Ok(HttpResponse::xml( - server.krill().ca_child_req(&ca)?.to_xml_vec() + server.krill().ca_child_req(ca).await?.to_xml_vec() )) } -fn id_publisher_request_json( - request: Request, +async fn id_publisher_request_json( + request: Request<'_>, path: PathIter<'_>, ca: CaHandle, ) -> Result { @@ -553,11 +555,11 @@ fn id_publisher_request_json( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_publisher_req(&ca)? + &server.krill().ca_publisher_req(ca).await? )) } -fn id_publisher_request_xml( +async fn id_publisher_request_xml( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -569,14 +571,14 @@ fn id_publisher_request_xml( )?; let server = request.empty()?; Ok(HttpResponse::xml( - server.krill().ca_publisher_req(&ca)?.to_xml_vec() + server.krill().ca_publisher_req(ca).await?.to_xml_vec() )) } //------------ /api/v1/cas/{ca}/issues --------------------------------------- -fn issues( +async fn issues( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -588,26 +590,26 @@ fn issues( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_issues(&ca)? + &server.krill().ca_issues(ca).await? )) } //------------ /api/v1/cas/{ca}/keys ----------------------------------------- -fn keys( +async fn keys( request: Request<'_>, mut path: PathIter<'_>, ca: CaHandle, ) -> Result { match path.next() { - Some("roll_init") => keys_roll_init(request, path, ca), - Some("roll_activate") => keys_roll_activate(request, path, ca), + Some("roll_init") => keys_roll_init(request, path, ca).await, + Some("roll_activate") => keys_roll_activate(request, path, ca).await, _ => Ok(HttpResponse::not_found()) } } -fn keys_roll_init( +async fn keys_roll_init( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -618,11 +620,11 @@ fn keys_roll_init( Permission::CaUpdate, Some(&ca) )?; let server = request.empty()?; - server.krill().ca_keyroll_init(ca, auth.actor())?; + server.krill().ca_keyroll_init(ca, auth.into_actor()).await?; Ok(HttpResponse::ok()) } -fn keys_roll_activate( +async fn keys_roll_activate( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -633,7 +635,7 @@ fn keys_roll_activate( Permission::CaUpdate, Some(&ca) )?; let server = request.empty()?; - server.krill().ca_keyroll_activate(ca, auth.actor())?; + server.krill().ca_keyroll_activate(ca, auth.into_actor()).await?; Ok(HttpResponse::ok()) } @@ -662,7 +664,7 @@ async fn parents_index( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_status(&ca)?.into_parents() + &server.krill().ca_parent_status(ca).await? )) } Method::POST => { @@ -672,7 +674,7 @@ async fn parents_index( let (server, bytes) = request.read_bytes().await?; let parent_req = extract_parent_ca_req(&ca, bytes, None)?; server.krill().ca_parent_add_or_update( - ca, parent_req, auth.actor() + ca, parent_req, auth.into_actor() ).await?; Ok(HttpResponse::ok()) } @@ -694,7 +696,7 @@ async fn parents_parent( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_my_parent_contact(&ca, &parent)? + &server.krill().ca_parent_contact(ca, parent).await? )) } Method::POST => { @@ -706,7 +708,7 @@ async fn parents_parent( &ca, bytes, Some(parent) )?; server.krill().ca_parent_add_or_update( - ca, parent_req, auth.actor() + ca, parent_req, auth.into_actor() ).await?; Ok(HttpResponse::ok()) } @@ -715,7 +717,9 @@ async fn parents_parent( Permission::CaUpdate, Some(&ca) )?; let server = request.empty()?; - server.krill().ca_parent_remove(ca, parent, auth.actor()).await?; + server.krill().ca_parent_remove( + ca, parent, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) @@ -773,7 +777,7 @@ async fn repo( ) -> Result { match path.next() { None => repo_index(request, ca).await, - Some("status") => repo_status(request, path, ca), + Some("status") => repo_status(request, path, ca).await, _ => Ok(HttpResponse::not_found()) } } @@ -789,7 +793,7 @@ async fn repo_index( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_repo_details(&ca)? + &server.krill().ca_repo_details(ca).await? )) } Method::POST => { @@ -798,7 +802,9 @@ async fn repo_index( )?; let (server, update) = request.read_bytes().await?; let update = extract_repository_contact(&ca, update)?; - server.krill().ca_repo_update(ca, update, auth.actor()).await?; + server.krill().ca_repo_update( + ca, update, auth.into_actor(), + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) @@ -831,7 +837,7 @@ pub fn extract_repository_contact( } } -fn repo_status( +async fn repo_status( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -843,7 +849,7 @@ fn repo_status( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_status(&ca)?.into_repo() + &server.krill().ca_repo_status(ca).await? )) } @@ -874,7 +880,7 @@ async fn routes_index( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_routes_show(&ca)? + &server.krill().ca_routes_show(ca).await? )) } Method::POST => { @@ -882,7 +888,9 @@ async fn routes_index( Permission::RoutesUpdate, Some(&ca) )?; let (server, updates) = request.read_json().await?; - server.krill().ca_routes_update(ca, updates, auth.actor())?; + server.krill().ca_routes_update( + ca, updates, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) @@ -902,14 +910,14 @@ async fn routes_try( let (server, mut updates) = request.read_json::().await?; let effect = server.krill().ca_routes_bgp_dry_run( - &ca, updates.clone() - )?; + ca.clone(), updates.clone() + ).await?; if effect.contains_invalids() { updates.set_explicit_max_length(); let resources = updates.affected_prefixes(); let suggestion = server.krill().ca_routes_bgp_suggest( - &ca, Some(resources) - )?; + ca, Some(resources) + ).await?; Ok(HttpResponse::json( &BgpAnalysisAdvice { effect, suggestion, @@ -917,7 +925,9 @@ async fn routes_try( )) } else { - server.krill().ca_routes_update(ca, updates, auth.actor())?; + server.krill().ca_routes_update( + ca, updates, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } } @@ -928,14 +938,14 @@ async fn routes_analysis( ca: CaHandle, ) -> Result { match path.next() { - Some("full") => routes_analysis_full(request, path, ca), + Some("full") => routes_analysis_full(request, path, ca).await, Some("dryrun") => routes_analysis_dryrun(request, path, ca).await, Some("suggest") => routes_analysis_suggest(request, path, ca).await, _ => Ok(HttpResponse::not_found()) } } -fn routes_analysis_full( +async fn routes_analysis_full( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -947,7 +957,7 @@ fn routes_analysis_full( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_routes_bgp_analysis(&ca)? + &server.krill().ca_routes_bgp_analysis(ca).await? )) } @@ -963,7 +973,7 @@ async fn routes_analysis_dryrun( )?; let (server, updates) = request.read_json().await?; Ok(HttpResponse::json( - &server.krill().ca_routes_bgp_dry_run(&ca, updates)? + &server.krill().ca_routes_bgp_dry_run(ca, updates).await? )) } @@ -980,7 +990,7 @@ async fn routes_analysis_suggest( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_routes_bgp_suggest(&ca, None)? + &server.krill().ca_routes_bgp_suggest(ca, None).await? )) } Method::POST => { @@ -990,8 +1000,8 @@ async fn routes_analysis_suggest( let (server, resources) = request.read_json().await?; Ok(HttpResponse::json( &server.krill().ca_routes_bgp_suggest( - &ca, Some(resources) - )? + ca, Some(resources) + ).await? )) } _ => Ok(HttpResponse::method_not_allowed()) @@ -1001,29 +1011,31 @@ async fn routes_analysis_suggest( //------------ /api/v1/cas/{ca}/stats ---------------------------------------- -fn stats( +async fn stats( request: Request<'_>, mut path: PathIter<'_>, ca: CaHandle, ) -> Result { match path.next() { - Some("children") => stats_children(request, path, ca), + Some("children") => stats_children(request, path, ca).await, _ => Ok(HttpResponse::not_found()) } } -fn stats_children( +async fn stats_children( request: Request<'_>, mut path: PathIter<'_>, ca: CaHandle, ) -> Result { match path.next() { - Some("connections") => stats_children_connections(request, path, ca), + Some("connections") => { + stats_children_connections(request, path, ca).await + }, _ => Ok(HttpResponse::not_found()) } } -fn stats_children_connections( +async fn stats_children_connections( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -1035,26 +1047,26 @@ fn stats_children_connections( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_stats_child_connections(&ca)? + &server.krill().ca_stats_child_connections(ca).await? )) } //------------ /api/v1/cas/{ca}/sync ----------------------------------------- -fn sync( +async fn sync( request: Request<'_>, mut path: PathIter<'_>, ca: CaHandle, ) -> Result { match path.next() { - Some("parents") => sync_parents(request, path, ca), - Some("repo") => sync_repo(request, path, ca), + Some("parents") => sync_parents(request, path, ca).await, + Some("repo") => sync_repo(request, path, ca).await, _ => Ok(HttpResponse::not_found()) } } -fn sync_parents( +async fn sync_parents( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -1065,11 +1077,11 @@ fn sync_parents( Permission::CaUpdate, Some(&ca) )?; let server = request.empty()?; - server.krill().cas_refresh_single(ca)?; + server.krill().cas_refresh_single(ca).await?; Ok(HttpResponse::ok()) } -fn sync_repo( +async fn sync_repo( request: Request<'_>, path: PathIter<'_>, ca: CaHandle, @@ -1080,7 +1092,7 @@ fn sync_repo( Permission::CaUpdate, Some(&ca) )?; let server = request.empty()?; - server.krill().cas_repo_sync_single(&ca)?; + server.krill().ca_sync_repo(ca).await?; Ok(HttpResponse::ok()) } diff --git a/src/daemon/http/dispatch/error.rs b/src/daemon/http/dispatch/error.rs index 47f5e028c..78cc6002f 100644 --- a/src/daemon/http/dispatch/error.rs +++ b/src/daemon/http/dispatch/error.rs @@ -1,6 +1,7 @@ //! Dispatch error handling. use crate::commons::error::{Error, FatalError}; +use crate::server::manager::RunError; use super::super::response::HttpResponse; @@ -30,6 +31,12 @@ impl From for DispatchError { } } +impl From for DispatchError { + fn from(src: RunError) -> Self { + Self::Response(src.into()) + } +} + impl From for DispatchError { fn from(src: Error) -> Self { Self::Response(HttpResponse::response_from_error(src)) diff --git a/src/daemon/http/dispatch/metrics.rs b/src/daemon/http/dispatch/metrics.rs index 5b1dab510..79b9dcb3e 100644 --- a/src/daemon/http/dispatch/metrics.rs +++ b/src/daemon/http/dispatch/metrics.rs @@ -59,7 +59,7 @@ pub async fn dispatch( server.authorizer().login_session_cache_size().await, ); - if let Ok(cas_stats) = server.krill().cas_stats() { + if let Ok(cas_stats) = server.krill().cas_stats().await { target.single( Metric::gauge("cas", "number of CAs in Krill"), cas_stats.len() @@ -67,13 +67,8 @@ pub async fn dispatch( if !server.config().metrics.metrics_hide_ca_details { - let mut ca_status_map = HashMap::new(); - - for ca in cas_stats.keys() { - if let Ok(ca_status) = server.krill().ca_status(ca) { - ca_status_map.insert(ca.clone(), ca_status); - } - } + let ca_status_map = + server.krill().cas_status_map().await.unwrap_or_default(); let metric = Metric::gauge( "ca_parent_success", @@ -386,7 +381,7 @@ pub async fn dispatch( } } - if let Ok(stats) = server.krill().repo_stats() { + if let Ok(stats) = server.krill().repo_stats().await { target.single( Metric::gauge( "repo_publisher", diff --git a/src/daemon/http/dispatch/pubd.rs b/src/daemon/http/dispatch/pubd.rs index cc58385d4..d7c72cb30 100644 --- a/src/daemon/http/dispatch/pubd.rs +++ b/src/daemon/http/dispatch/pubd.rs @@ -20,7 +20,7 @@ pub async fn dispatch( Some("delete") => delete(request, path).await, Some("init") => init(request, path).await, Some("publishers") => publishers(request, path).await, - Some("session_reset") => session_reset(request, path), + Some("session_reset") => session_reset(request, path).await, Some("stale") => stale(request, path).await, _ => Ok(HttpResponse::not_found()) } @@ -39,7 +39,7 @@ async fn delete( Permission::PubAdmin, None )?; let (server, criteria) = request.read_json().await?; - server.krill().delete_matching_files(criteria)?; + server.krill().delete_matching_files(criteria).await?; Ok(HttpResponse::ok()) } @@ -57,7 +57,7 @@ async fn init( Permission::PubAdmin, None )?; let (server, uris) = request.read_json().await?; - server.krill().repository_init(uris)?; + server.krill().repository_init(uris).await?; Ok(HttpResponse::ok()) } Method::DELETE => { @@ -65,7 +65,7 @@ async fn init( Permission::PubAdmin, None )?; let server = request.empty()?; - server.krill().repository_clear()?; + server.krill().repository_clear().await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) @@ -82,7 +82,7 @@ async fn publishers( match path.parse_opt_next()? { None => publishers_index(request).await, Some(publisher) => { - publishers_publisher(request, path, publisher) + publishers_publisher(request, path, publisher).await } } } @@ -99,7 +99,7 @@ async fn publishers_index( Ok(HttpResponse::json( &PublisherList { publishers: { - server.krill().publishers()?.into_iter().map( + server.krill().publishers().await?.into_iter().map( PublisherSummary::from_handle ).collect() } @@ -112,31 +112,33 @@ async fn publishers_index( )?; let (server, pbl) = request.read_json().await?; Ok(HttpResponse::json( - &server.krill().add_publisher(pbl, auth.actor())? + &server.krill().add_publisher( + pbl, auth.into_actor() + ).await? )) } _ => Ok(HttpResponse::method_not_allowed()) } } -fn publishers_publisher( +async fn publishers_publisher( request: Request<'_>, mut path: PathIter<'_>, publisher: PublisherHandle, ) -> Result { match path.next() { - None => publishers_publisher_index(request, publisher), + None => publishers_publisher_index(request, publisher).await, Some("response.json") => { - publishers_publisher_response(request, path, publisher) + publishers_publisher_response(request, path, publisher).await } Some("response.xml") => { - publishers_publisher_response_xml(request, path, publisher) + publishers_publisher_response_xml(request, path, publisher).await } _ => Ok(HttpResponse::not_found()) } } -fn publishers_publisher_index( +async fn publishers_publisher_index( request: Request<'_>, publisher: PublisherHandle, ) -> Result { @@ -147,7 +149,7 @@ fn publishers_publisher_index( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().get_publisher(publisher)? + &server.krill().get_publisher(publisher).await? )) } Method::DELETE => { @@ -155,14 +157,16 @@ fn publishers_publisher_index( Permission::PubDelete, None )?; let server = request.empty()?; - server.krill().remove_publisher(publisher, auth.actor())?; + server.krill().remove_publisher( + publisher, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) } } -fn publishers_publisher_response( +async fn publishers_publisher_response( request: Request<'_>, path: PathIter<'_>, publisher: PublisherHandle, @@ -172,11 +176,11 @@ fn publishers_publisher_response( let (request, _) = request.proceed_permitted(Permission::PubRead, None)?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().repository_response(&publisher)? + &server.krill().repository_response(publisher).await? )) } -fn publishers_publisher_response_xml( +async fn publishers_publisher_response_xml( request: Request<'_>, path: PathIter<'_>, publisher: PublisherHandle, @@ -186,14 +190,14 @@ fn publishers_publisher_response_xml( let (request, _) = request.proceed_permitted(Permission::PubRead, None)?; let server = request.empty()?; Ok(HttpResponse::xml( - server.krill().repository_response(&publisher)?.to_xml_vec() + server.krill().repository_response(publisher).await?.to_xml_vec() )) } //------------ /api/v1/pubd/session_reset ------------------------------------ -fn session_reset( +async fn session_reset( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -201,7 +205,7 @@ fn session_reset( request.check_post()?; let (request, _) = request.proceed_permitted(Permission::PubAdmin, None)?; let server = request.empty()?; - server.krill().repository_session_reset()?; + server.krill().repository_session_reset().await?; Ok(HttpResponse::ok()) } @@ -217,7 +221,7 @@ async fn stale( request.check_get()?; let (request, _) = request.proceed_permitted( Permission::PubList, None)?; let server = request.empty()?; - let stats = server.krill().repo_stats()?; + let stats = server.krill().repo_stats().await?; Ok(HttpResponse::json( &PublisherList { publishers: { diff --git a/src/daemon/http/dispatch/root.rs b/src/daemon/http/dispatch/root.rs index f9a85003a..ecaf4fd36 100644 --- a/src/daemon/http/dispatch/root.rs +++ b/src/daemon/http/dispatch/root.rs @@ -21,10 +21,10 @@ pub async fn dispatch_request( Some("metrics") => super::metrics::dispatch(request, path).await, Some("rfc8181") => rfc8181(request, path).await, Some("rfc6492") => rfc6492(request, path).await, - Some("rrdp") => rrdp(request, path), + Some("rrdp") => rrdp(request, path).await, Some("stats") => super::stats::dispatch(request, path).await, - Some("ta") => ta(request, path), - Some("testbed.tal") => tal(request, path), + Some("ta") => ta(request, path).await, + Some("testbed.tal") => tal(request, path).await, Some("testbed") => super::testbed::dispatch(request, path).await, Some("ui") => ui(request, path), @@ -74,7 +74,7 @@ async fn rfc8181( let (request, _) = request.proceed_unchecked(); let (server, bytes) = request.read_rfc8181_bytes().await?; Ok(HttpResponse::rfc8181( - server.krill().rfc8181(publisher, bytes)? + server.krill().rfc8181(publisher, bytes).await? )) } @@ -97,24 +97,26 @@ async fn rfc6492( // always be the anonymous actor. Maybe the CA manager should // determine the actor when looking at the ID certificate? Ok(HttpResponse::rfc6492( - server.krill().rfc6492(ca , bytes, user_agent, auth.actor())? + server.krill().rfc6492( + ca , bytes, user_agent, auth.into_actor() + ).await? )) } //------------ /ta ----------------------------------------------------------- -fn ta( +async fn ta( request: Request<'_>, mut path: PathIter<'_> ) -> Result { match path.next() { - Some("ta.tal") => tal(request, path), - Some("ta.cer") => ta_cer(request, path), + Some("ta.tal") => tal(request, path).await, + Some("ta.cer") => ta_cer(request, path).await, _ => Ok(HttpResponse::not_found()) } } -fn tal( +async fn tal( request: Request<'_>, path: PathIter<'_> ) -> Result { path.check_exhausted()?; @@ -122,26 +124,24 @@ fn tal( let (request, _) = request.proceed_unchecked(); let server = request.empty()?; Ok(HttpResponse::text( - server.krill().ta_cert_details()?.tal.to_string() + server.krill().ta_tal().await? )) } -fn ta_cer( +async fn ta_cer( request: Request<'_>, path: PathIter<'_> ) -> Result { path.check_exhausted()?; request.check_get()?; let (request, _) = request.proceed_unchecked(); let server = request.empty()?; - Ok(HttpResponse::cert( - server.krill().ta_cert_details()?.cert.to_bytes() - )) + Ok(HttpResponse::cert(server.krill().ta_cer().await?)) } //------------ /rrdp --------------------------------------------------------- -fn rrdp( +async fn rrdp( request: Request<'_>, path: PathIter<'_> ) -> Result { request.check_get()?; @@ -150,7 +150,9 @@ fn rrdp( let Some(remaining) = path.remaining() else { return Ok(HttpResponse::not_found()) }; - let path = match server.krill().resolve_rrdp_request_path(remaining)? { + let path = match server.krill().resolve_rrdp_request_path( + remaining.into() + ).await? { Some(path) => path, None => { return Ok(HttpResponse::not_found()) diff --git a/src/daemon/http/dispatch/stats.rs b/src/daemon/http/dispatch/stats.rs index aa5914d7e..dad54e96f 100644 --- a/src/daemon/http/dispatch/stats.rs +++ b/src/daemon/http/dispatch/stats.rs @@ -13,7 +13,7 @@ pub async fn dispatch( ) -> Result { match path.next() { Some("info") => info(request, path), - Some("repo") => repo(request, path), + Some("repo") => repo(request, path).await, Some("cas") => cas(request, path).await, _ => Ok(HttpResponse::not_found()) } @@ -36,7 +36,7 @@ fn info( //------------ /stats/repo --------------------------------------------------- -fn repo( +async fn repo( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -44,7 +44,7 @@ fn repo( request.check_get()?; let (request, _) = request.proceed_unchecked(); let server = request.empty()?; - Ok(HttpResponse::json(&server.krill().repo_stats()?)) + Ok(HttpResponse::json(&server.krill().repo_stats().await?)) } @@ -58,6 +58,6 @@ async fn cas( request.check_get()?; let (request, _) = request.proceed_unchecked(); let server = request.empty()?; - Ok(HttpResponse::json(&server.krill().cas_stats()?)) + Ok(HttpResponse::json(&server.krill().cas_stats().await?)) } diff --git a/src/daemon/http/dispatch/ta.rs b/src/daemon/http/dispatch/ta.rs index 7755b4dbc..759b672a3 100644 --- a/src/daemon/http/dispatch/ta.rs +++ b/src/daemon/http/dispatch/ta.rs @@ -31,8 +31,8 @@ async fn proxy( ) -> Result { match path.next() { Some("children") => proxy_children(request, path).await, - Some("id") => proxy_id(request, path), - Some("init") => proxy_init(request, path), + Some("id") => proxy_id(request, path).await, + Some("init") => proxy_init(request, path).await, Some("repo") => proxy_repo(request, path).await, Some("signer") => proxy_signer(request, path).await, _ => Ok(HttpResponse::not_found()) @@ -48,7 +48,7 @@ async fn proxy_children( ) -> Result { match path.parse_opt_next()? { None => proxy_children_index(request).await, - Some(child) => proxy_children_child(request, path, child), + Some(child) => proxy_children_child(request, path, child).await, } } @@ -69,14 +69,16 @@ async fn proxy_children_index( )?; let (server, child) = request.read_json().await?; Ok(HttpResponse::json( - &server.krill().ta_proxy_children_add(child, auth.actor())? + &server.krill().ta_proxy_children_add( + child, auth.into_actor() + ).await? )) } _ => Ok(HttpResponse::method_not_allowed()) } } -fn proxy_children_child( +async fn proxy_children_child( request: Request<'_>, mut path: PathIter<'_>, child: ChildHandle, @@ -84,10 +86,10 @@ fn proxy_children_child( match path.next() { None => proxy_children_child_index(request, child), Some("parent_response.json") => { - proxy_children_child_response(request, path, child) + proxy_children_child_response(request, path, child).await } Some("parent_response.xml") => { - proxy_children_child_response_xml(request, path, child) + proxy_children_child_response_xml(request, path, child).await } _ => Ok(HttpResponse::not_found()) } @@ -114,7 +116,7 @@ fn proxy_children_child_index( } } -fn proxy_children_child_response( +async fn proxy_children_child_response( request: Request<'_>, path: PathIter<'_>, child: ChildHandle, @@ -126,11 +128,11 @@ fn proxy_children_child_response( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ca_parent_response(&ta_handle(), child)? + &server.krill().ca_parent_response(ta_handle(), child).await? )) } -fn proxy_children_child_response_xml( +async fn proxy_children_child_response_xml( request: Request<'_>, path: PathIter<'_>, child: ChildHandle, @@ -142,14 +144,16 @@ fn proxy_children_child_response_xml( )?; let server = request.empty()?; Ok(HttpResponse::xml( - server.krill().ca_parent_response(&ta_handle(), child)?.to_xml_vec() + server.krill().ca_parent_response( + ta_handle(), child + ).await?.to_xml_vec() )) } //------------ /api/v1/proxy/init -------------------------------------------- -fn proxy_init( +async fn proxy_init( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -159,14 +163,14 @@ fn proxy_init( Permission::CaAdmin, None )?; let server = request.empty()?; - server.krill().ta_proxy_init()?; + server.krill().ta_proxy_init().await?; Ok(HttpResponse::ok()) } //------------ /api/v1/proxy/id ---------------------------------------------- -fn proxy_id( +async fn proxy_id( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -177,7 +181,7 @@ fn proxy_id( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ta_proxy_id()? + &server.krill().ta_proxy_id().await? )) } @@ -190,8 +194,8 @@ async fn proxy_repo( ) -> Result { match path.next() { None => proxy_repo_index(request).await, - Some("request.json") => proxy_repo_request(request, path), - Some("request.xml") => proxy_repo_request_xml(request, path), + Some("request.json") => proxy_repo_request(request, path).await, + Some("request.xml") => proxy_repo_request_xml(request, path).await, _ => Ok(HttpResponse::not_found()) } } @@ -206,7 +210,7 @@ async fn proxy_repo_index( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ta_proxy_repository_contact()? + &server.krill().ta_proxy_repository_contact().await? )) } Method::POST => { @@ -217,14 +221,16 @@ async fn proxy_repo_index( let update = super::cas::extract_repository_contact( &ta_handle(), update )?; - server.krill().ta_proxy_repository_update(update, auth.actor())?; + server.krill().ta_proxy_repository_update( + update, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } _ => Ok(HttpResponse::method_not_allowed()) } } -fn proxy_repo_request( +async fn proxy_repo_request( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -233,11 +239,11 @@ fn proxy_repo_request( let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ta_proxy_publisher_request()? + &server.krill().ta_proxy_publisher_request().await? )) } -fn proxy_repo_request_xml( +async fn proxy_repo_request_xml( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -246,7 +252,7 @@ fn proxy_repo_request_xml( let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?; let server = request.empty()?; Ok(HttpResponse::xml( - server.krill().ta_proxy_publisher_request()?.to_xml_vec() + server.krill().ta_proxy_publisher_request().await?.to_xml_vec() )) } @@ -259,7 +265,7 @@ async fn proxy_signer( ) -> Result { match path.next() { Some("add") => proxy_signer_add(request, path).await, - Some("request") => proxy_signer_request(request, path), + Some("request") => proxy_signer_request(request, path).await, Some("response") => proxy_signer_response(request, path).await, Some("update") => proxy_signer_update(request, path).await, _ => Ok(HttpResponse::not_found()) @@ -276,11 +282,11 @@ async fn proxy_signer_add( Permission::CaAdmin, None )?; let (server, info) = request.read_json().await?; - server.krill().ta_proxy_signer_add(info, auth.actor())?; + server.krill().ta_proxy_signer_add(info, auth.into_actor()).await?; Ok(HttpResponse::ok()) } -fn proxy_signer_request( +async fn proxy_signer_request( request: Request<'_>, path: PathIter<'_>, ) -> Result { @@ -292,7 +298,7 @@ fn proxy_signer_request( )?; let server = request.empty()?; Ok(HttpResponse::json( - &server.krill().ta_proxy_signer_get_request()? + &server.krill().ta_proxy_signer_get_request().await? )) } Method::POST => { @@ -302,8 +308,8 @@ fn proxy_signer_request( let server = request.empty()?; Ok(HttpResponse::json( &server.krill().ta_proxy_signer_make_request( - auth.actor() - )? + auth.into_actor() + ).await? )) } _ => Ok(HttpResponse::method_not_allowed()) @@ -320,7 +326,9 @@ async fn proxy_signer_response( Permission::CaAdmin, None )?; let (server, response) = request.read_json().await?; - server.krill().ta_proxy_signer_process_response(response, auth.actor())?; + server.krill().ta_proxy_signer_process_response( + response, auth.into_actor() + ).await?; Ok(HttpResponse::ok()) } @@ -334,7 +342,7 @@ async fn proxy_signer_update( Permission::CaAdmin, None )?; let (server, info) = request.read_json().await?; - server.krill().ta_proxy_signer_update(info, auth.actor())?; + server.krill().ta_proxy_signer_update(info, auth.into_actor()).await?; Ok(HttpResponse::ok()) } diff --git a/src/daemon/http/dispatch/testbed.rs b/src/daemon/http/dispatch/testbed.rs index 907d9acc9..6b3ad12fd 100644 --- a/src/daemon/http/dispatch/testbed.rs +++ b/src/daemon/http/dispatch/testbed.rs @@ -72,7 +72,7 @@ async fn children( ) -> Result { match path.parse_opt_next()? { None => children_index(request).await, - Some(child) => children_child(request, path, child), + Some(child) => children_child(request, path, child).await, } } @@ -84,26 +84,26 @@ async fn children_index( let (server, child) = request.read_json().await?; Ok(HttpResponse::json( &server.krill().ca_add_child( - &testbed_ca_handle(), child, &Actor::anonymous() - )? + testbed_ca_handle(), child, Actor::anonymous() + ).await? )) } -fn children_child( +async fn children_child( request: Request<'_>, mut path: PathIter<'_>, child: ChildHandle, ) -> Result { match path.next() { - None => children_child_index(request, child), + None => children_child_index(request, child).await, Some("parent_response.xml") => { - children_child_response(request, path, child) + children_child_response(request, path, child).await } _ => Ok(HttpResponse::not_found()) } } -fn children_child_index( +async fn children_child_index( request: Request<'_>, child: ChildHandle, ) -> Result { @@ -111,12 +111,12 @@ fn children_child_index( let (request, _) = request.proceed_unchecked(); let server = request.empty()?; server.krill().ca_child_remove( - &testbed_ca_handle(), child, &Actor::anonymous() - )?; + testbed_ca_handle(), child, Actor::anonymous() + ).await?; Ok(HttpResponse::ok()) } -fn children_child_response( +async fn children_child_response( request: Request<'_>, path: PathIter<'_>, child: ChildHandle, @@ -127,8 +127,8 @@ fn children_child_response( let server = request.empty()?; Ok(HttpResponse::xml( server.krill().ca_parent_response( - &testbed_ca_handle(), child - )?.to_xml_vec() + testbed_ca_handle(), child + ).await?.to_xml_vec() )) } @@ -141,7 +141,9 @@ async fn publishers( ) -> Result { match path.parse_opt_next()? { None => publishers_index(request).await, - Some(publisher) => publishers_publisher(request, path, publisher), + Some(publisher) => { + publishers_publisher(request, path, publisher).await + } } } @@ -152,38 +154,36 @@ async fn publishers_index( let (request, _) = request.proceed_unchecked(); let (server, pbl) = request.read_json().await?; Ok(HttpResponse::json( - &server.krill().add_publisher(pbl, &Actor::anonymous())? + &server.krill().add_publisher(pbl, Actor::anonymous()).await? )) } -fn publishers_publisher( +async fn publishers_publisher( request: Request<'_>, mut path: PathIter<'_>, publisher: PublisherHandle, ) -> Result { match path.next() { - None => publishers_publisher_index(request, publisher), + None => publishers_publisher_index(request, publisher).await, Some("response.xml") => { - publishers_publisher_response(request, path, publisher) + publishers_publisher_response(request, path, publisher).await } _ => Ok(HttpResponse::not_found()) } } -fn publishers_publisher_index( +async fn publishers_publisher_index( request: Request<'_>, publisher: PublisherHandle, ) -> Result { request.check_delete()?; let (request, _) = request.proceed_unchecked(); let server = request.empty()?; - server.krill().remove_publisher( - publisher, &Actor::anonymous() - )?; + server.krill().remove_publisher(publisher, Actor::anonymous()).await?; Ok(HttpResponse::ok()) } -fn publishers_publisher_response( +async fn publishers_publisher_response( request: Request<'_>, path: PathIter<'_>, publisher: PublisherHandle, @@ -193,7 +193,7 @@ fn publishers_publisher_response( let (request, _) = request.proceed_unchecked(); let server = request.empty()?; Ok(HttpResponse::xml( - server.krill().repository_response(&publisher)?.to_xml_vec() + server.krill().repository_response(publisher).await?.to_xml_vec() )) } diff --git a/src/daemon/http/request.rs b/src/daemon/http/request.rs index 05ceb364b..136480431 100644 --- a/src/daemon/http/request.rs +++ b/src/daemon/http/request.rs @@ -59,7 +59,7 @@ impl<'a> Request<'a> { /// Returns whether testbed mode is enabled. pub fn testbed_enabled(&self) -> bool { - self.server.krill().testbed_enabled() + self.server.config().testbed_enabled() } /// Returns the method of this request. diff --git a/src/daemon/http/response.rs b/src/daemon/http/response.rs index 5e6535e9a..9db768b8a 100644 --- a/src/daemon/http/response.rs +++ b/src/daemon/http/response.rs @@ -8,6 +8,7 @@ use serde::Serialize; use crate::api::admin::Token; use crate::api::status::ErrorResponse; use crate::commons::error::Error; +use crate::server::manager::RunError; //----------- ContentType ---------------------------------------------------- @@ -375,3 +376,18 @@ impl HttpResponse { } } +impl From for HttpResponse { + fn from(src: RunError) -> Self { + let body = serde_json::to_string( + &src.to_error_response() + ).unwrap().into(); + Response { + status: src.status(), + content_type: ContentType::Json.as_str(), + max_age: None, + body, + cause: Some(src.into()), + }.finalize() + } +} + diff --git a/src/daemon/http/server.rs b/src/daemon/http/server.rs index 478eacc0f..c6b89b063 100644 --- a/src/daemon/http/server.rs +++ b/src/daemon/http/server.rs @@ -1,5 +1,7 @@ +//! The core of the HTTP server. + use std::env; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use clap::crate_version; use hyper::StatusCode; use log::{error, info, warn, trace}; @@ -21,16 +23,14 @@ use super::response::{HyperResponse, HttpResponse}; //------------ HttpServer ---------------------------------------------------- /// The Krill HTTP server. +/// pub struct HttpServer { - /// The Krill “business logic.” + /// The Krill server. krill: KrillManager, /// The component responsible for API authorization checks authorizer: Authorizer, - /// A copy of the configuration. - config: Arc, - /// Time this server was started started: Timestamp, } @@ -39,29 +39,36 @@ impl HttpServer { /// Creates a new server from a Krill manager and the configuration. pub fn new( krill: KrillManager, - config: Arc, runtime: &runtime::Handle, ) -> KrillResult> { - let authorizer = Authorizer::new(config.clone())?; + let authorizer = Authorizer::new(krill.config())?; authorizer.spawn_sweep(runtime); Ok(Self { krill, authorizer, - config, started: Timestamp::now(), }.into()) } /// Processes an HTTP request. pub async fn process_request( - &self, request: HyperRequest + this: Weak, request: HyperRequest ) -> Result { + // If we can’t upgrade the weak this, return a 503. + let Some(this) = this.upgrade() else { + return Ok(HttpResponse::error( + StatusCode::SERVICE_UNAVAILABLE, + ("sys-unavailable", "Service Unavailable"), + ).into_hyper()) + }; + let logger = RequestLogger::begin(&request); - let (auth, new_token) = self.authorizer.authenticate_request( + let (auth, new_token) = this.authorizer.authenticate_request( &request ).await; let request = Request::new( - request, self, auth, BodyLimits::from_config(&self.config) + request, &this, auth, + BodyLimits::from_config(this.krill.config()) ); let path = match request.path() { Ok(path) => path, @@ -94,7 +101,7 @@ impl HttpServer { } impl HttpServer { - /// Returns a reference to the Krill manager. + /// Returns a reference to the Krill server. pub(super) fn krill(&self) -> &KrillManager { &self.krill } @@ -105,8 +112,8 @@ impl HttpServer { } /// Returns a reference to the configuration. - pub(super) fn config(&self) -> &Config { - &self.config + pub fn config(&self) -> &Config { + self.krill.config() } pub(super) fn server_info(&self) -> ServerInfo { diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 04c2f5eb0..3e59077c0 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -2,6 +2,24 @@ //! //! This module contains the code actually driving the daemon including //! processing HTTP requests for the API. +//! +//! Everything related to processing HTTP requests can be found in the +//! [`http`] module. The [`start`] module contains the actual socket +//! listeners and connection handlers as well as the start-up code for +//! the daemon. +//! +//! # _Refactoring to be done_ +//! +//! * Turn the current [`HttpServer`][http::server::HttpServer] into the +//! `KrillDaemon` and attach all the things currently done in [`start`] +//! to it. +//! * Then move everything from [`http`] up here. +//! * Limit what needs to be `pub`. Ideally, only the new `KrillDaemon` +//! needs to be, but there are a few things that are referred to in the +//! config. Consider moving those to the [`config`][crate::config] module +//! and refer to them from here instead. +//! * Improve the error flow within the daemon to allow fatal errors to make +//! Krill exit. pub mod http; pub mod start; diff --git a/src/daemon/start.rs b/src/daemon/start.rs index 822da4680..42402c715 100644 --- a/src/daemon/start.rs +++ b/src/daemon/start.rs @@ -1,21 +1,32 @@ +//! Starts and then runs the Krill daemon. +//! +//! Despite its name, this module contains the core game loop of Krill. It +//! provides both the socket listeners and connection handlers for the HTTP +//! server and sets everything up. All of this is provided via the +//! [`start_krill_daemon`] function. + use std::{env, process}; -use std::net::SocketAddr; +use std::net::{SocketAddr, TcpListener as StdTcpListener}; use std::path::Path; use std::sync::Arc; -use log::error; -use hyper::service::service_fn; +use std::time::Duration; +use clap::crate_version; +use log::{error, info, warn}; use hyper_util::rt::{TokioExecutor, TokioIo}; -use tokio::{runtime, select}; -use tokio::net::TcpListener; -use tokio::sync::oneshot; +use hyper_util::server::conn; +use hyper_util::server::graceful::GracefulShutdown; +use tokio::net::{TcpListener as TokioTcpListener}; +use tokio::runtime::{Handle as TokioHandle}; +use tokio::sync::{oneshot, watch}; +use tokio::task::JoinSet; use tokio_rustls::TlsAcceptor; use crate::commons::file; -use crate::commons::error::Error; +use crate::commons::error::{Error, Error as KrillError}; use crate::commons::version::KrillVersion; use crate::config::Config; -use crate::constants::KRILL_ENV_UPGRADE_ONLY; +use crate::constants::{KRILL_ENV_UPGRADE_ONLY, KRILL_SERVER_APP}; use crate::server::properties::PropertiesManager; -use crate::server::manager::KrillManager; +use crate::server::manager::StartupManager; use crate::upgrades::{ finalise_data_migration, post_start_upgrade, prepare_upgrade_data_migrations, UpgradeError, UpgradeMode, @@ -24,10 +35,29 @@ use super::http::{tls, tls_keys}; use super::http::server::HttpServer; -pub async fn start_krill_daemon( - config: Arc, +//------------ start_krill_daemon -------------------------------------------- + +/// Starts the Krill daemon and blocks until it exits. +/// +/// The configuration of the Krill daemon and server is taken from `config`. +/// +/// If `signal_running` is given, a `()` will be sent to it once the first +/// listener socket is ready to process requests. +/// +/// If `signal_exit` is given, the daemon will exit when a `()` is sent to +/// the channel. Otherwise it waits for a SIGINT or SIGTERM or a fatal +/// error happening. +/// +/// The function will return an error if something goes wrong during startup. +/// Once it blocks waiting for an exit, it will return `Ok(())` even if +/// something went wrong. In this case, an error message will be logged, +pub fn start_krill_daemon( + config: Config, mut signal_running: Option>, + signal_exit: Option>, ) -> Result<(), Error> { + info!("Starting {} v{}", KRILL_SERVER_APP, crate_version!()); + write_pid_file_or_die(&config); test_data_dirs_or_die(&config); @@ -80,15 +110,22 @@ pub async fn start_krill_daemon( properties_manager.init(KrillVersion::code_version())?; } - // Create the Krill manager, this will create the necessary data - // sub-directories if needed - let krill = KrillManager::build(config.clone()).await?; + let tokio = tokio::runtime::Runtime::new().map_err(|err| { + KrillError::custom( + format!("Failed to create Tokio runtime: {err}") + ) + })?; + + let mut krill = StartupManager::new(config, tokio.handle().clone())?; + + // Setup testbed if necessary. + krill.prepare_testbed()?; // Call post-start upgrades to trigger any upgrade related runtime // actions, such as re-issuing ROAs because subject name strategy has // changed. if let Some(report) = upgrade_report { - post_start_upgrade(report, &krill).await?; + post_start_upgrade(report, &krill)?; } // If the operator wanted to do the upgrade only, now is a good time to @@ -98,194 +135,421 @@ pub async fn start_krill_daemon( std::process::exit(0); } - // Build the scheduler which will be responsible for executing - // planned/triggered tasks - let scheduler = krill.build_scheduler(); - let scheduler_future = scheduler.run(); + krill.run_scheduler()?; + let (krill, pool) = krill.promote()?; // Create the HTTP server. - let server = HttpServer::new( - krill, config.clone(), &runtime::Handle::current() - )?; + let server = HttpServer::new(krill, tokio.handle())?; // Create self-signed HTTPS cert if configured and not generated earlier. - if config.https_mode().is_generate_https_cert() { - tls_keys::create_key_cert_if_needed(config.tls_keys_dir()) + if server.config().https_mode().is_generate_https_cert() { + tls_keys::create_key_cert_if_needed(server.config().tls_keys_dir()) .map_err(|e| Error::HttpsSetup(format!("{e}")))?; } + let (exit_tx, exit_rx) = watch::channel(false); + let mut join = JoinSet::new(); + // Start a hyper server for the configured http sockets. - let http_server_futures = futures_util::future::select_all( - config.socket_addresses().into_iter().map(|socket_addr| { - tokio::spawn(single_http_listener( - server.clone(), - socket_addr, - config.clone(), - signal_running.take(), - )) - }), - ); + for socket_addr in server.config().socket_addresses().into_iter() { + single_http_listener( + server.clone(), + socket_addr, + signal_running.take(), + exit_rx.clone(), + &mut join, + tokio.handle(), + )?; + } // Start a hyper server for the configured unix sockets. - // We do not await these, as they are not required #[cfg(unix)] - if config.unix_socket_enabled() { - config.unix_socket().map(|path| { - tokio::spawn(single_unix_listener( - server.clone(), - path.clone(), - config.clone(), - signal_running.take(), - )) - }); + if + server.config().unix_socket_enabled() + && let Some(path) = server.config().unix_socket() + { + single_unix_listener( + server.clone(), + path.clone(), + signal_running.take(), + exit_rx.clone(), + &mut join, + tokio.handle(), + )?; } - select!( - _ = http_server_futures => error!("http server stopped unexpectedly"), - _ = scheduler_future => error!("scheduler stopped unexpectedly"), - ); + tokio.block_on(async { + if let Some(exit) = signal_exit { + let _ = exit.await; + } + else { + exit_signalled().await; + } + let _ = exit_tx.send(true); + let _ = join.join_all().await; + }); - Err(Error::custom("stopping krill process")) + drop(server); + pool.terminate(); + + Ok(()) } -/// Runs an HTTP listener on a single socket. -async fn single_http_listener( + +//------------ single_http_listener ------------------------------------------ + +/// Creates and listens on a TCP socket for the Krill API. +/// +/// The socket will listening on the given `addr`. Unless TLS is disabled in +/// the config associated with `server`, then listener will start a TLS +/// handshake on connections. +/// +/// Requests will be dispatched to `server`. +/// +/// If `signal_running` is given, a signal is sent when the listener is ready +/// to receive connections. +/// +/// The listener will shut down after `true` is sent to `signal_exit`. This +/// will also initate closing of all currently open connections. The function +/// will return when both the listener and all connections are closed or after +/// then seconds. +fn single_http_listener( server: Arc, addr: SocketAddr, - config: Arc, signal_running: Option>, -) { - let listener = match TcpListener::bind(addr).await { + mut signal_exit: watch::Receiver, + join: &mut JoinSet<()>, + handle: &TokioHandle, +) -> Result<(), Error> { + let listener = match StdTcpListener::bind(addr) { Ok(listener) => listener, Err(err) => { - error!("Could not bind to {addr}: {err}"); - return; + return Err(Error::Custom(format!( + "Failed to create TCP socket '{addr}': {err}" + ))); + } + }; + if let Err(err) = listener.set_nonblocking(true) { + return Err(Error::Custom(format!( + "Failed to configure TCP socket '{addr}': {err}" + ))); + } + let listener = match TokioTcpListener::from_std(listener) { + Ok(listener) => listener, + Err(err) => { + return Err(Error::Custom(format!( + "Failed to prepare TCP socket '{addr}': {err}" + ))); } }; - let tls = if config.https_mode().is_disable_https() { + let tls = if server.config().https_mode().is_disable_https() { None } else { match tls::create_server_config( - &tls_keys::key_file_path(config.tls_keys_dir()), - &tls_keys::cert_file_path(config.tls_keys_dir()), + &tls_keys::key_file_path(server.config().tls_keys_dir()), + &tls_keys::cert_file_path(server.config().tls_keys_dir()), ) { Ok(config) => Some(TlsAcceptor::from(Arc::new(config))), Err(err) => { - error!("{err}"); - return; + return Err(Error::Custom(format!( + "Failed to create TLS server config: {err}" + ))); } } }; + let conn_builder = conn::auto::Builder::new(TokioExecutor::new()); + let graceful = GracefulShutdown::new(); + + let weak_server = Arc::downgrade(&server); + drop(server); + if let Some(tx) = signal_running { let _ = tx.send(()); } - loop { - let stream = match listener.accept().await { - Ok((stream, _addr)) => { - tls::MaybeTlsTcpStream::new(stream, tls.as_ref()) + join.spawn_on( + async move { + loop { + // Break here already if `signal_exit` is true. + if *signal_exit.borrow_and_update() { + drop(listener); + break; + } + + tokio::select! { + conn = listener.accept() => { + let (stream, _addr) = match conn { + Ok(conn) => conn, + Err(e) => { + warn!("TCP socket accept error: {}", e); + tokio::time::sleep( + Duration::from_millis(100) + ).await; + continue; + } + }; + + let stream = TokioIo::new( + tls::MaybeTlsTcpStream::new( + stream, tls.as_ref() + ) + ); + + let server = weak_server.clone(); + let conn = conn_builder.serve_connection_with_upgrades( + stream, + hyper::service::service_fn(move |req| { + HttpServer::process_request(server.clone(), req) + }) + ); + let conn = graceful.watch(conn.into_owned()); + + tokio::spawn(async move { + if let Err(err) = conn.await { + warn!("TCP connection error: {}", err); + } + }); + }, + + res = signal_exit.changed() => { + // Break if the channel is closed or the new value is + // `true`. + if res.is_err() || *signal_exit.borrow() { + drop(listener); + break; + } + } + } } - Err(err) => { - error!("Fatal error in HTTP server {addr}: {err}"); - return; + + tokio::select! { + _ = graceful.shutdown() => { }, + _ = tokio::time::sleep(Duration::from_secs(10)) => { + warn!( + "Waited 10 seconds for TCP listener to shutdown, \ + aborting..." + ); + } } - }; - let server = server.clone(); - tokio::task::spawn(async move { - let _ = hyper_util::server::conn::auto::Builder::new( - TokioExecutor::new(), - ) - .serve_connection( - TokioIo::new(stream), - service_fn(move |req| { - let server = server.clone(); - async move { server.process_request(req).await } - }), - ) - .await; - }); - } + }, + handle + ); + Ok(()) } + +//------------ single_unix_listener ------------------------------------------ + +/// Creates and listens on a Unix socket for the Krill API. +/// +/// The socket will listening on the given `addr`. It will dispatch requests +/// to `server`. +/// +/// If `signal_running` is given, a signal is sent when the listener is ready +/// to receive connections. +/// +/// The listener will shut down after `true` is sent to `signal_exit`. This +/// will also initate closing of all currently open connections. The function +/// will return when both the listener and all connections are closed or after +/// then seconds. #[cfg(unix)] -/// Runs an UNIX listener on a single socket. -async fn single_unix_listener( +fn single_unix_listener( server: Arc, path: std::path::PathBuf, - _config: Arc, signal_running: Option>, -) { - use tokio::net::UnixListener; + mut signal_exit: watch::Receiver, + join: &mut JoinSet<()>, + handle: &TokioHandle, +) -> Result<(), Error> { + use std::os::unix::net::{UnixListener as StdUnixListener}; + use nix::unistd::{Uid, User}; + use tokio::net::{UnixListener as TokioUnixListener}; if path.exists() && let Err(err) = std::fs::remove_file(&path) { - error!("Fatal error in UNIX socket: {err}"); - return; + return Err(Error::Custom(format!( + "Failed to remove existing Unix socket file: {err}" + ))); } - let listener = match UnixListener::bind(&path) { + let listener = match StdUnixListener::bind(&path) { Ok(listener) => listener, Err(err) => { - error!("Could not bind to {}: {}", &path.to_string_lossy(), err); - return; + return Err(Error::custom(format!( + "Could not bind to Unix socket '{}': {}", + path.display(), err + ))); } }; + if let Err(err) = listener.set_nonblocking(true) { + return Err(Error::Custom(format!( + "Failed to configure Unix socket '{}': {}", + path.display(), err, + ))); + } + let listener = match TokioUnixListener::from_std(listener) { + Ok(listener) => listener, + Err(err) => { + return Err(Error::Custom(format!( + "Failed to prepare Unix socket '{}': {}", + path.display(), err, + ))); + } + }; + + let conn_builder = conn::auto::Builder::new(TokioExecutor::new()); + let graceful = GracefulShutdown::new(); + + let weak_server = Arc::downgrade(&server); + drop(server); if let Some(tx) = signal_running { let _ = tx.send(()); } - loop { - use tokio::net::unix; - - let (stream, _addr) = match listener.accept().await { - Ok(stream) => stream, - Err(err) => { - error!("Fatal UNIX socket error: {err}"); - continue; + join.spawn_on( + async move { + loop { + // Break here already if `signal_exit` is true. + if *signal_exit.borrow_and_update() { + drop(listener); + break; + } + + tokio::select! { + conn = listener.accept() => { + let (stream, _addr) = match conn { + Ok(stream) => stream, + Err(err) => { + warn!("Unix socket accept error: {}", err); + tokio::time::sleep( + Duration::from_millis(100) + ).await; + continue; + } + }; + + + let uid = match stream.peer_cred() { + Ok(cred) => Uid::from_raw(cred.uid()), + Err(err) => { + warn!( + "Unix socket could not obtain peer credentials: \ + {err}" + ); + continue; + } + }; + let user = match User::from_uid(uid) { + Ok(Some(user)) => user, + Ok(None) => { + error!( + "Unix socket could not obtain user details: \ + unknown user ID." + ); + continue; + } + Err(err) => { + error!( + "Unix socket could not obtain user details: {err}" + ); + continue; + }, + }; + + let server = weak_server.clone(); + let conn = conn_builder.serve_connection_with_upgrades( + TokioIo::new(stream), + hyper::service::service_fn(move |mut req| { + let extensions = req.extensions_mut(); + extensions.insert(user.clone()); + HttpServer::process_request(server.clone(), req) + }) + ); + let conn = graceful.watch(conn.into_owned()); + + tokio::spawn(async move { + if let Err(err) = conn.await { + warn!("Unix connection error: {}", err); + } + }); + }, + + res = signal_exit.changed() => { + // Break if the channel is closed or the new value is `true`. + if res.is_err() || *signal_exit.borrow() { + drop(listener); + break; + } + } + } } - }; - let uid: unix::uid_t = match stream.peer_cred() { - Ok(cred) => cred.uid(), - Err(err) => { - error!("Could not obtain peer credentials: {err}"); - continue; - } - }; - let user: nix::unistd::User = match nix::unistd::User::from_uid( - nix::unistd::Uid::from_raw(uid) - ) { - Ok(Some(user)) => user, - Err(err) => { - error!("Could not obtain user details for UNIX socket: {err}"); - continue; - }, - _ => { - error!("Could not obtain user details for UNIX socket"); - continue; + + tokio::select! { + _ = graceful.shutdown() => { }, + _ = tokio::time::sleep(Duration::from_secs(10)) => { + warn!( + "Waited 10 seconds for Unix listener to \ + shutdown, aborting..." + ); + } } - }; - - let server = server.clone(); - tokio::task::spawn(async move { - let _ = hyper_util::server::conn::auto::Builder::new( - TokioExecutor::new(), - ) - .serve_connection( - TokioIo::new(stream), - service_fn(move |mut req| { - let extensions = req.extensions_mut(); - extensions.insert(user.clone()); - let server = server.clone(); - async move { server.process_request(req).await } - }), - ) - .await; - }); + }, + handle + ); + Ok(()) +} + + +//------------ exit_signalled ------------------------------------------------ + +/// Returns when an exit signal was received. +/// +/// This non-Unix implementation returns when the equivalent of a Ctrl+C is +/// received. +#[cfg(not(unix))] +async fn exit_signalled() { + tokio::signal::ctrl_c().await +} + +/// Returns when an exit signal was received. +/// +/// Returns when either a SIGINT or SIGTERM was received. +/// +#[cfg(unix)] +async fn exit_signalled() { + use tokio::signal::unix::{SignalKind, signal}; + + let mut sigterm = match signal(SignalKind::terminate()) { + Ok(sig) => sig, + Err(err) => { + error!("Failed to install SIGTERM handler: {err}."); + return; + } + }; + let mut sigint = match signal(SignalKind::interrupt()) { + Ok(sig) => sig, + Err(err) => { + error!("Failed to install SIGINT handler: {err}."); + return; + } + }; + + tokio::select! { + _ = sigterm.recv() => { + info!("Received SIGTERM. Shutting down."); + } + _ = sigint.recv() => { + info!("Received SIGINT. Shutting down."); + } } } + +//------------ Helper functions ---------------------------------------------- + +/// Writes the process ID to the configured PID file or dies trying. fn write_pid_file_or_die(config: &Config) { if let Err(e) = file::save( process::id().to_string().as_bytes(), config.pid_file() @@ -296,6 +560,7 @@ fn write_pid_file_or_die(config: &Config) { } } +/// Checks that all the configured test directories are present or dies. fn test_data_dirs_or_die(config: &Config) { test_data_dir_or_die("tls_keys_dir", config.tls_keys_dir()); test_data_dir_or_die("repo_dir", config.repo_dir()); @@ -307,6 +572,9 @@ fn test_data_dirs_or_die(config: &Config) { } } +/// Checks that the given directory can be written to. +/// +/// Does so by writing to a file “test” and deleting it thereafter. fn test_data_dir_or_die(config_item: &str, dir: &Path) { let test_file = dir.join("test"); @@ -330,7 +598,10 @@ fn test_data_dir_or_die(config_item: &str, dir: &Path) { } } - +/// Writes an error message and a hint how to proceeed. +// +// XXX Doesn’t actually die? +// fn print_write_error_hint_and_die(error_msg: String) { eprintln!("{error_msg}"); eprintln!(); diff --git a/src/server/bgp/analyser.rs b/src/server/bgp/analyser.rs index 8bf586c8a..483117dfa 100644 --- a/src/server/bgp/analyser.rs +++ b/src/server/bgp/analyser.rs @@ -15,6 +15,7 @@ use crate::api::roa::{ AsNumber, ConfiguredRoa, Ipv4Prefix, Ipv6Prefix, RoaPayload, TypedPrefix, }; use crate::config::Config; +use crate::server::runtime::KrillRuntime; use super::riswhois::{ RisWhois, RisWhoisError, RisWhoisLoader, RouteOrigin, RouteOriginSet, RoutePrefix, @@ -79,7 +80,9 @@ impl BgpAnalyser { /// /// Returns `Ok(true)` if it did do a download, `Ok(false)` if no download /// was necessary, or an error if downloading was attempted but failed. - pub async fn update(&self) -> Result { + pub fn update( + &self, krill: &KrillRuntime + ) -> Result { let Some(loader) = self.loader.as_ref() else { return Ok(false) }; @@ -96,7 +99,7 @@ impl BgpAnalyser { return Ok(false) } - self.riswhois.store(Some(Arc::new(loader.load().await?))); + self.riswhois.store(Some(Arc::new(loader.load(krill)?))); self.last_checked.store(Time::now().timestamp(), Ordering::Relaxed); Ok(true) } @@ -125,7 +128,7 @@ impl BgpAnalyser { // neither goes directly into the `entries` as ‘not held.’ let mut roas_held = Vec::new(); for roa in roas { - if + if let Some(limit) = limited_scope.as_ref() && !limit.contains_roa_address( &roa.roa_configuration.payload.as_roa_ip_address() diff --git a/src/server/bgp/riswhois.rs b/src/server/bgp/riswhois.rs index 5fb422fb9..dc8645beb 100644 --- a/src/server/bgp/riswhois.rs +++ b/src/server/bgp/riswhois.rs @@ -11,9 +11,11 @@ compile_error!("cannot build on 16 bit systems"); use std::{cmp, error, fmt, io}; use std::io::BufReader; use std::str::FromStr; +use std::sync::Arc; use libflate::gzip; use crate::api::roa::{AsNumber, Ipv4Prefix, Ipv6Prefix, TypedPrefix}; use crate::api::bgp::Announcement; +use crate::server::runtime::KrillRuntime; //------------ Configuration ------------------------------------------------- @@ -35,38 +37,47 @@ const MINIMUM_SEEN_BY: u32 = 13; /// A type that knows where RISwhois data lives and download it. pub struct RisWhoisLoader { /// The HTTP(S) URL of the location of IPv4 data set. - v4_url: String, + v4_url: Arc, /// The HTTP(S) URL of the location of IPv6 data set. - v6_url: String, + v6_url: Arc, } impl RisWhoisLoader { /// Creates a new loader from the URLS of the IPv4 and IPv6 data sets. pub fn new(v4_url: String, v6_url: String) -> Self { - Self { v4_url, v6_url } + Self { + v4_url: v4_url.into(), + v6_url: v6_url.into(), + } } /// Downloads and processes a new data set. - pub async fn load(&self) -> Result { + pub fn load( + &self, krill: &KrillRuntime + ) -> Result { Ok(RisWhois::new( - Self::load_tree(&self.v4_url).await?, - Self::load_tree(&self.v6_url).await?, + Self::load_tree(self.v4_url.clone(), krill)?, + Self::load_tree(self.v6_url.clone(), krill)?, )) } /// Downloads and process the tree for one address family. - async fn load_tree( - uri: &str + fn load_tree( + uri: Arc, krill: &KrillRuntime ) -> Result, RisWhoisError> where

::Err: error::Error + Send + Sync + 'static { - Self::parse_gz_data( - &reqwest::get(uri).await.map_err(|err| { - RisWhoisError::new(uri, io::Error::other(err)) + let uri_clone = uri.clone(); + let data = krill.exec_async(async move { + reqwest::get(uri_clone.as_ref()).await.map_err(|err| { + RisWhoisError::new(&uri_clone, io::Error::other(err)) })?.bytes().await.map_err(|err| { - RisWhoisError::new(uri, io::Error::other(err)) - })? - ).map_err(|err| RisWhoisError::new(uri, err)) + RisWhoisError::new(&uri_clone, io::Error::other(err)) + }) + }).map_err(|err| RisWhoisError::new(&uri, io::Error::other(err)))??; + Self::parse_gz_data(&data).map_err(|err| { + RisWhoisError::new(&uri, err) + }) } /// Parses the gzipped data. diff --git a/src/server/ca/certauth.rs b/src/server/ca/certauth.rs index 56c2208b6..587d82245 100644 --- a/src/server/ca/certauth.rs +++ b/src/server/ca/certauth.rs @@ -2,8 +2,6 @@ use std::vec; use std::collections::HashMap; -use std::ops::Deref; -use std::sync::Arc; use bytes::Bytes; use chrono::Duration; use log::{debug, info, trace, warn}; @@ -48,6 +46,7 @@ use crate::commons::error::Error; use crate::commons::eventsourcing::Aggregate; use crate::constants::test_mode_enabled; use crate::config::{Config, IssuanceTimingConfig}; +use crate::server::runtime::KrillRuntime; use super::aspa::AspaDefinitions; use super::bgpsec::BgpSecDefinitions; use super::child::{ChildDetails, ChildCertificateUpdates, UsedKeyState}; @@ -132,6 +131,8 @@ impl Aggregate for CertAuth { type Error = Error; + type Context<'a> = &'a KrillRuntime; + fn init(handle: &MyHandle, event: CertAuthInitEvent) -> Self { CertAuth { handle: handle.clone(), @@ -155,11 +156,10 @@ impl Aggregate for CertAuth { } fn process_init_command( - command: CertAuthInitCommand, + _command: CertAuthInitCommand, + krill: &KrillRuntime, ) -> Result { - Rfc8183Id::generate( - &command.details().signer - ).map(|id| CertAuthInitEvent { id }) + Rfc8183Id::generate(krill.signer()).map(|id| CertAuthInitEvent { id }) } fn version(&self) -> u64 { @@ -173,6 +173,7 @@ impl Aggregate for CertAuth { fn process_command( &self, command: CertAuthCommand, + krill: &KrillRuntime, ) -> Result, Error> { trace!( "Sending command to CA '{}', version: {}: {}", @@ -187,9 +188,11 @@ impl Aggregate for CertAuth { } CertAuthCommandDetails::ChildImport( - import_child, config, signer, + import_child ) => { - self.process_child_import(import_child, &config, signer) + self.process_child_import( + import_child, krill.config(), krill.signer(), + ) } CertAuthCommandDetails::ChildUpdateResources(child, res) => { @@ -207,9 +210,11 @@ impl Aggregate for CertAuth { } CertAuthCommandDetails::ChildCertify( - child, request, config, signer, + child, request, ) => { - self.process_child_certify( child, request, &config, signer) + self.process_child_certify( + child, request, krill.config(), krill.signer() + ) } CertAuthCommandDetails::ChildRevokeKey(child, request) => { @@ -224,15 +229,17 @@ impl Aggregate for CertAuth { self.process_child_suspend_inactive(&child) } - CertAuthCommandDetails::ChildUnsuspend(child, config, signer) => { - self.process_child_unsuspend(&child, &config, signer) + CertAuthCommandDetails::ChildUnsuspend(child) => { + self.process_child_unsuspend( + &child, krill.config(), krill.signer() + ) } // Parent commands - CertAuthCommandDetails::GenerateNewIdKey(signer) => { - self.process_generate_new_id_key(signer) + CertAuthCommandDetails::GenerateNewIdKey => { + self.process_generate_new_id_key(krill.signer()) } CertAuthCommandDetails::AddParent(parent, info) => { @@ -248,35 +255,33 @@ impl Aggregate for CertAuth { } CertAuthCommandDetails::UpdateEntitlements( - parent, entitlements, signer, + parent, entitlements ) => { - self.process_update_entitlements(parent, entitlements, signer) + self.process_update_entitlements( + parent, entitlements, krill.signer(), + ) } - CertAuthCommandDetails::UpdateRcvdCert( - class_name, rcvd_cert, config, signer, - ) => { + CertAuthCommandDetails::UpdateRcvdCert(class_name, rcvd_cert) => { self.process_update_received_cert( - class_name, rcvd_cert, &config, &signer + class_name, rcvd_cert, krill.config(), krill.signer() ) } - CertAuthCommandDetails::DropResourceClass( - rcn, reason, signer, - ) => { - self.process_drop_resource_class(rcn, reason, signer) + CertAuthCommandDetails::DropResourceClass(rcn, reason) => { + self.process_drop_resource_class(rcn, reason, krill.signer()) } // Key rolls - CertAuthCommandDetails::KeyRollInitiate(duration, signer) => { - self.process_keyroll_initiate(duration, signer) + CertAuthCommandDetails::KeyRollInitiate(duration) => { + self.process_keyroll_initiate(duration, krill.signer()) } - CertAuthCommandDetails::KeyRollActivate( - duration, config, signer, - ) => { - self.process_keyroll_activate(duration, config, signer) + CertAuthCommandDetails::KeyRollActivate(duration) => { + self.process_keyroll_activate( + duration, krill.config(), krill.signer() + ) } CertAuthCommandDetails::KeyRollFinish(rcn, response) => { @@ -285,83 +290,73 @@ impl Aggregate for CertAuth { // Publishing - CertAuthCommandDetails::RepoUpdate(contact, signer) => { - self.process_update_repo(contact, &signer) + CertAuthCommandDetails::RepoUpdate(contact) => { + self.process_update_repo(contact, krill.signer()) } // ROAs - CertAuthCommandDetails::RouteAuthorizationsUpdate( - updates, config, signer, - ) => { + CertAuthCommandDetails::RouteAuthorizationsUpdate(updates) => { self.process_route_authorizations_update( - updates, &config, &signer + updates, krill.config(), krill.signer() ) } - CertAuthCommandDetails::RouteAuthorizationsRenew( - config, signer, - ) => { + CertAuthCommandDetails::RouteAuthorizationsRenew => { self.process_route_authorizations_renew( - false, &config, &signer + false, krill.config(), krill.signer() ) } - CertAuthCommandDetails::RouteAuthorizationsForceRenew( - config, signer, - ) => { + CertAuthCommandDetails::RouteAuthorizationsForceRenew=> { self.process_route_authorizations_renew( - true, &config, &signer + true, krill.config(), krill.signer() ) } // ASPA - CertAuthCommandDetails::AspasUpdate(updates, config, signer) => { + CertAuthCommandDetails::AspasUpdate(updates) => { self.process_aspas_update( - updates, &config, &signer + updates, krill.config(), krill.signer() ) } CertAuthCommandDetails::AspasUpdateExisting( - customer, update, config, signer, + customer, update, ) => { self.process_aspas_update_existing( - customer, update, &config, &signer + customer, update, krill.config(), krill.signer(), ) } - CertAuthCommandDetails::AspasRenew(config, signer) => { - self.process_aspas_renew(&config, &signer) + CertAuthCommandDetails::AspasRenew => { + self.process_aspas_renew(krill.config(), krill.signer()) } // BGPsec router keys - CertAuthCommandDetails::BgpSecUpdateDefinitions( - updates, config, signer, - ) => { + CertAuthCommandDetails::BgpSecUpdateDefinitions(updates) => { self.process_bgpsec_definitions_update( - updates, &config, &signer + updates, krill.config(), krill.signer(), ) } - CertAuthCommandDetails::BgpSecRenew(config, signer) => { - self.process_bgpsec_renew(&config, &signer) + CertAuthCommandDetails::BgpSecRenew => { + self.process_bgpsec_renew(krill.config(), krill.signer()) } // RTA - CertAuthCommandDetails::RtaMultiPrepare( - name, request, signer, - ) => { - self.process_rta_multi_prep(name, request, &signer) + CertAuthCommandDetails::RtaMultiPrepare(name, request) => { + self.process_rta_multi_prep(name, request, krill.signer()) } - CertAuthCommandDetails::RtaCoSign(name, rta, signer) => { - self.process_rta_cosign(name, rta, signer.deref()) + CertAuthCommandDetails::RtaCoSign(name, rta) => { + self.process_rta_cosign(name, rta, krill.signer()) } - CertAuthCommandDetails::RtaSign(name, request, signer) => { - self.process_rta_sign(name, request, signer.deref()) + CertAuthCommandDetails::RtaSign(name, request) => { + self.process_rta_sign(name, request, krill.signer()) } } } @@ -676,6 +671,47 @@ impl Aggregate for CertAuth { } } } + + fn pre_save_events( + &self, events: &[Self::Event], krill: &KrillRuntime, + ) -> Result<(), Self::Error> { + // Let the object store update its ROAs and issued + // certificates and/or generate manifests and CRLs when relevant + // changes occur in a `CertAuth`. + krill.ca_manager().ca_objects_store().cert_auth_pre_save_events( + self, events, krill + )?; + + // Let the [`TaskQueue`] handle events pre-save so + // that relevant changes in a `CertAuth` can trigger follow-up + // actions. This is done as pre-save listener, because commands + // that would result in a follow-up should fail, if the task cannot be + // planned. + // + // Tasks will typically be picked up after the CA changes are + // committed, but they may also be picked up sooner by another + // thread. Because of that the tasks will remember which minimal + // version of the CA they are intended for, so that they can + // be rescheduled should they have been picked up too soon. + // + // An example of a triggered task: schedule a synchronisation with the + // repository (publication server) in case ROAs have been + // updated. + krill.tasks().cert_auth_pre_save_events(self, events)?; + + Ok(()) + } + + fn post_save_events( + &self, events: &[Self::Event], krill: &KrillRuntime, + ) { + // Also let the [`TaskQueue`] handle events post-save. We + // use this to send best-effort post-save signals to children + // in case a certificate was updated or a child key was revoked. + // This is a no-op for remote children (we cannot send a signal over + // RFC 6492). + krill.tasks().cert_auth_post_save_events(self, events); + } } /// # Data presentation @@ -1101,7 +1137,7 @@ impl CertAuth { &self, import_child: ImportChild, config: &Config, - signer: Arc, + signer: &KrillSigner, ) -> KrillResult> { // overview: // - perform checks (e.g. not supported in case we have multiple RCs) @@ -1142,7 +1178,7 @@ impl CertAuth { )?; // Add a resource class name mapping if applicable - if + if let Some(name_for_child) = class_name_override && name_for_child != my_rcn { @@ -1304,7 +1340,7 @@ impl CertAuth { child_handle: ChildHandle, request: IssuanceRequest, config: &Config, - signer: Arc, + signer: &KrillSigner, ) -> KrillResult> { let (child_rcn, limit, csr) = request.unpack(); @@ -1336,7 +1372,7 @@ impl CertAuth { csr_info: CsrInfo, limit: RequestResourceLimit, config: &Config, - signer: Arc, + signer: &KrillSigner, events: &mut Vec, ) -> KrillResult<()> { if !csr_info.global_uris() && !test_mode_enabled() { @@ -1354,7 +1390,7 @@ impl CertAuth { resources, limit, &config.issuance_timing, - &signer, + signer, )?; let cert_name = ObjectName::from_key(&issued.key_identifier(), "cer"); @@ -1553,8 +1589,8 @@ impl CertAuth { fn process_child_unsuspend( &self, child_handle: &ChildHandle, - config: &Arc, - signer: Arc, + config: &Config, + signer: &KrillSigner, ) -> KrillResult> { let child = self.get_child(child_handle)?; @@ -1591,7 +1627,7 @@ impl CertAuth { suspended.csr_info.clone(), suspended.limit.clone(), config, - signer.clone(), + signer, &mut res, )?; } @@ -1727,9 +1763,9 @@ impl CertAuth { /// Processes the “generate new ID key” command. fn process_generate_new_id_key( &self, - signer: Arc, + signer: &KrillSigner, ) -> KrillResult> { - let id = Rfc8183Id::generate(&signer)?; + let id = Rfc8183Id::generate(signer)?; info!( "CA '{}' generated new ID certificate with key id: {}", @@ -1842,7 +1878,7 @@ impl CertAuth { &self, parent_handle: ParentHandle, entitlements: ResourceClassListResponse, - signer: Arc, + signer: &KrillSigner, ) -> KrillResult> { let mut res = Vec::new(); @@ -1867,7 +1903,7 @@ impl CertAuth { && !entitled_classes.contains(&class.parent_rc_name()) }) { - let revoke_requests = rc.revoke(signer.deref())?; + let revoke_requests = rc.revoke(signer)?; info!( "Updating Entitlements for CA: {}, Removing RC: {}", @@ -1896,7 +1932,7 @@ impl CertAuth { self.handle(), ent, &self.repository_contact()?.repo_info, - &signer, + signer, &mut res, )?; } @@ -1934,7 +1970,7 @@ impl CertAuth { self.handle(), ent, &self.repository_contact()?.repo_info, - &signer, + signer, &mut res )?; } @@ -2007,7 +2043,7 @@ impl CertAuth { &self, rcn: ResourceClassName, reason: DropReason, - signer: Arc, + signer: &KrillSigner, ) -> KrillResult> { warn!( "Dropping resource class '{rcn}' because of reason: {reason}" @@ -2016,7 +2052,7 @@ impl CertAuth { Error::ResourceClassUnknown(rcn.clone()) })?; - rc.revoke(signer.deref()).map(|revoke_requests| { + rc.revoke(signer).map(|revoke_requests| { vec![CertAuthEvent::ResourceClassRemoved { resource_class_name: rcn, parent: rc.parent_handle().clone(), @@ -2032,14 +2068,14 @@ impl CertAuth { fn process_keyroll_initiate( &self, duration: Duration, - signer: Arc, + signer: &KrillSigner, ) -> KrillResult> { let mut res = Vec::new(); for (rcn, rc) in self.resources.iter() { let repo = self.repository_contact()?; if rc.append_keyroll_initiate( - &repo.repo_info, duration, &signer, &mut res + &repo.repo_info, duration, signer, &mut res )? { info!( "Started key roll for ca: {}, rc: {}, under parent: {}", @@ -2057,14 +2093,14 @@ impl CertAuth { fn process_keyroll_activate( &self, staging_time: Duration, - config: Arc, - signer: Arc, + config: &Config, + signer: &KrillSigner, ) -> KrillResult> { let mut res = vec![]; for (rcn, rc) in self.resources.iter() { if rc.append_keyroll_activate( - staging_time, &config.issuance_timing, &signer, &mut res + staging_time, &config.issuance_timing, signer, &mut res )? { info!( "Activated key for ca: {}, rc: {}, under parent: {}", @@ -2515,8 +2551,8 @@ impl CertAuth { let mut keys = HashMap::new(); for (rcn, rc) in self.resources.iter() { - if - let Some(rc_resources) = rc.current_resources() + if + let Some(rc_resources) = rc.current_resources() && !rc_resources.intersection(&request.resources).is_empty() { let key = signer.create_key()?; diff --git a/src/server/ca/child.rs b/src/server/ca/child.rs index 8d644d717..06b3fcd5a 100644 --- a/src/server/ca/child.rs +++ b/src/server/ca/child.rs @@ -132,7 +132,7 @@ impl ChildDetails { let mut res = vec![]; for (ki, used_key_state) in self.used_keys.iter() { - if + if let UsedKeyState::InUse(found_rcn) = used_key_state && found_rcn == parent_rcn { @@ -396,8 +396,10 @@ pub struct ChildCertificateUpdates { #[serde(skip_serializing_if = "Vec::is_empty", default)] pub suspended: Vec, - /// The certificates that have been unsuspended. This is no longer used - /// as of Krill 0.16.0, but kept because it is in stored state. + /// The certificates that have been unsuspended. + /// + /// This is no longer used as of Krill 0.16.0, but kept because it is in + /// stored state. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub unsuspended: Vec, } diff --git a/src/server/ca/commands.rs b/src/server/ca/commands.rs index 830922d64..702c14aaf 100644 --- a/src/server/ca/commands.rs +++ b/src/server/ca/commands.rs @@ -1,7 +1,6 @@ //! The commands issued to an RPKI CA. use std::fmt; -use std::sync::Arc; use chrono::Duration; use rpki::ca::idexchange::{ChildHandle, ParentHandle, ServiceUri}; use rpki::ca::provisioning::{ @@ -29,12 +28,10 @@ use crate::api::roa::RoaConfigurationUpdates; use crate::api::rta::{ ResourceTaggedAttestation, RtaContentRequest, RtaPrepareRequest, }; -use crate::commons::crypto::KrillSigner; use crate::commons::eventsourcing::{ self, InitCommandDetails, SentCommand, SentInitCommand, WithStorableDetails, }; -use crate::config::Config; use super::events::CertAuthEvent; use super::rc::DropReason; @@ -48,10 +45,7 @@ pub type CertAuthInitCommand = SentInitCommand; /// The details for the init command for a `CertAuth` instance. #[derive(Clone, Debug)] -pub struct CertAuthInitCommandDetails { - /// The signer to use for initializing the CA. - pub signer: Arc, -} +pub struct CertAuthInitCommandDetails; impl InitCommandDetails for CertAuthInitCommandDetails { type StorableDetails = CertAuthStorableCommand; @@ -85,7 +79,7 @@ pub enum CertAuthCommandDetails { ChildAdd(ChildHandle, IdCertInfo, ResourceSet), /// Import a child under this parent CA - ChildImport(ImportChild, Arc, Arc), + ChildImport(ImportChild), /// Update the resource entitlements for an existing child. ChildUpdateResources(ChildHandle, ResourceSet), @@ -100,7 +94,7 @@ pub enum CertAuthCommandDetails { ), /// Process an issuance request sent by an existing child. - ChildCertify(ChildHandle, IssuanceRequest, Arc, Arc), + ChildCertify(ChildHandle, IssuanceRequest), /// Process a revoke request by an existing child. ChildRevokeKey(ChildHandle, RevocationRequest), @@ -126,7 +120,7 @@ pub enum CertAuthCommandDetails { /// once again and republish existing certificates provided that they /// are not expired, or about to expire, and do not claim resources no /// longer associated with this child. - ChildUnsuspend(ChildHandle, Arc, Arc), + ChildUnsuspend(ChildHandle), //--- Parent commands // @@ -139,7 +133,7 @@ pub enum CertAuthCommandDetails { /// this ID for parents, and children. In practice however, one may not /// want to use this until RFC8183 is extended with some words/ on how /// to re-do the ID exchange. - GenerateNewIdKey(Arc), + GenerateNewIdKey, /// Add a parent to this CA. /// @@ -158,20 +152,18 @@ pub enum CertAuthCommandDetails { /// /// Remove/create/update resource classes and certificate requests or key /// revocation requests as needed. - UpdateEntitlements(ParentHandle, Entitlements, Arc), + UpdateEntitlements(ParentHandle, Entitlements), /// Process a new certificate received from a parent. UpdateRcvdCert( ResourceClassName, ReceivedCert, - Arc, - Arc, ), /// Drop a resource class under a parent. /// /// This is usually done because of issues obtaining a certificate for it. - DropResourceClass(ResourceClassName, DropReason, Arc), + DropResourceClass(ResourceClassName, DropReason), //--- Key rolls @@ -180,7 +172,7 @@ pub enum CertAuthCommandDetails { /// A key roll is only initiated for resource classes where there is a /// current active key only, i.e. there is no roll in progress, and this /// key's age exceeds the given duration. - KeyRollInitiate(Duration, Arc), + KeyRollInitiate(Duration), /// Activate a rolled key. /// @@ -196,7 +188,7 @@ pub enum CertAuthCommandDetails { /// RFC6489 dictates that 24 hours must be observed. However, shorter /// time frames can be used for testing, and in case of emergency /// rolls. - KeyRollActivate(Duration, Arc, Arc), + KeyRollActivate(Duration), /// Finish the keyroll. /// @@ -212,11 +204,7 @@ pub enum CertAuthCommandDetails { /// Note: ROA *objects* will be created by the CA itself. The command /// just contains the intent for which announcements should be /// authorized. - RouteAuthorizationsUpdate( - RoaConfigurationUpdates, - Arc, - Arc, - ), + RouteAuthorizationsUpdate(RoaConfigurationUpdates), /// Re-issue all ROA objects which would otherwise expire soon. /// @@ -224,23 +212,18 @@ pub enum CertAuthCommandDetails { /// Note that this command is intended to be sent by the scheduler - /// once a day is fine - and will only be stored if there are any /// updates to be done. - RouteAuthorizationsRenew(Arc, Arc), + RouteAuthorizationsRenew, /// Re-issue all ROA objects regardless of their expiration time. - RouteAuthorizationsForceRenew(Arc, Arc), + RouteAuthorizationsForceRenew, //--- ASPA /// Update ASPA definitions - AspasUpdate(AspaDefinitionUpdates, Arc, Arc), + AspasUpdate(AspaDefinitionUpdates), /// Update an existing AspaProviders for the given AspaCustomer - AspasUpdateExisting( - CustomerAsn, - AspaProvidersUpdate, - Arc, - Arc, - ), + AspasUpdateExisting(CustomerAsn, AspaProvidersUpdate), /// Re-issue any and all ASPA objects which would otherwise expire soon. /// @@ -248,38 +231,34 @@ pub enum CertAuthCommandDetails { /// /// This command is intended to be sent by the scheduler – once a day is /// fine – and will only be stored if there are any updates to be done. - AspasRenew(Arc, Arc), + AspasRenew, //--- BGPsec router keys /// Update BgpSecDefinitions - BgpSecUpdateDefinitions( - BgpSecDefinitionUpdates, - Arc, - Arc, - ), + BgpSecUpdateDefinitions(BgpSecDefinitionUpdates), /// Re-issue any and all BGPsec certificates which are soon to expire. - BgpSecRenew(Arc, Arc), + BgpSecRenew, //--- Publishing // Update the repository where this CA publishes. - RepoUpdate(RepositoryContact, Arc), + RepoUpdate(RepositoryContact), //--- RTA /// Sign a new RTA - RtaSign(RtaName, RtaContentRequest, Arc), + RtaSign(RtaName, RtaContentRequest), /// Prepare a multi-signed RTA - RtaMultiPrepare(RtaName, RtaPrepareRequest, Arc), + RtaMultiPrepare(RtaName, RtaPrepareRequest), /// Co-sign an existing multi-signed RTA - RtaCoSign(RtaName, ResourceTaggedAttestation, Arc), + RtaCoSign(RtaName, ResourceTaggedAttestation), } impl eventsourcing::CommandDetails for CertAuthCommandDetails { @@ -422,7 +401,7 @@ impl From for CertAuthStorableCommand { resources, } } - CertAuthCommandDetails::ChildImport(import_child, _, _) => { + CertAuthCommandDetails::ChildImport(import_child) => { CertAuthStorableCommand::ChildImport { child: import_child.name, ski: import_child @@ -455,7 +434,7 @@ impl From for CertAuthStorableCommand { mapping, } } - CertAuthCommandDetails::ChildCertify(child, req, _, _) => { + CertAuthCommandDetails::ChildCertify(child, req) => { let (resource_class_name, limit, csr) = req.unpack(); let ki = csr.public_key().key_identifier(); CertAuthStorableCommand::ChildCertify { @@ -474,10 +453,10 @@ impl From for CertAuthStorableCommand { CertAuthCommandDetails::ChildSuspendInactive(child) => { CertAuthStorableCommand::ChildSuspendInactive { child } } - CertAuthCommandDetails::ChildUnsuspend(child, _, _) => { + CertAuthCommandDetails::ChildUnsuspend(child) => { CertAuthStorableCommand::ChildUnsuspend { child } } - CertAuthCommandDetails::GenerateNewIdKey(_) => { + CertAuthCommandDetails::GenerateNewIdKey => { CertAuthStorableCommand::GenerateNewIdKey } CertAuthCommandDetails::AddParent(parent, contact) => { @@ -496,9 +475,7 @@ impl From for CertAuthStorableCommand { CertAuthStorableCommand::RemoveParent { parent } } CertAuthCommandDetails::UpdateEntitlements( - parent, - cmd_entitlements, - _, + parent, cmd_entitlements, ) => { let mut entitlements = vec![]; for entitlement in cmd_entitlements.classes() { @@ -514,28 +491,23 @@ impl From for CertAuthStorableCommand { } } CertAuthCommandDetails::UpdateRcvdCert( - resource_class_name, - rcvd_cert, - _, - _, + resource_class_name, rcvd_cert, ) => CertAuthStorableCommand::UpdateRcvdCert { resource_class_name, resources: rcvd_cert.resources.clone(), }, CertAuthCommandDetails::DropResourceClass( - resource_class_name, - reason, - _, + resource_class_name, reason, ) => CertAuthStorableCommand::DropResourceClass { resource_class_name, reason, }, - CertAuthCommandDetails::KeyRollInitiate(older_than, _) => { + CertAuthCommandDetails::KeyRollInitiate(older_than) => { CertAuthStorableCommand::KeyRollInitiate { older_than_seconds: older_than.num_seconds(), } } - CertAuthCommandDetails::KeyRollActivate(staged_for, _, _) => { + CertAuthCommandDetails::KeyRollActivate(staged_for) => { CertAuthStorableCommand::KeyRollActivate { staged_for_seconds: staged_for.num_seconds(), } @@ -544,51 +516,46 @@ impl From for CertAuthStorableCommand { CertAuthStorableCommand::KeyRollFinish { resource_class_name, } + } CertAuthCommandDetails::RouteAuthorizationsUpdate(updates) => { + CertAuthStorableCommand::RoaDefinitionUpdates { updates } } - CertAuthCommandDetails::RouteAuthorizationsUpdate( - updates, - _, - _, - ) => CertAuthStorableCommand::RoaDefinitionUpdates { updates }, - CertAuthCommandDetails::RouteAuthorizationsRenew(_, _) => { + CertAuthCommandDetails::RouteAuthorizationsRenew => { CertAuthStorableCommand::ReissueBeforeExpiring } - CertAuthCommandDetails::RouteAuthorizationsForceRenew(_, _) => { + CertAuthCommandDetails::RouteAuthorizationsForceRenew => { CertAuthStorableCommand::ForceReissue } - CertAuthCommandDetails::AspasUpdate(updates, _, _) => { + CertAuthCommandDetails::AspasUpdate(updates) => { CertAuthStorableCommand::AspasUpdate { updates } } CertAuthCommandDetails::AspasUpdateExisting( - customer, - update, - _, - _, - ) => CertAuthStorableCommand::AspasUpdateExisting { - customer, - update, - }, - CertAuthCommandDetails::AspasRenew(_, _) => { + customer, update, + ) => { + CertAuthStorableCommand::AspasUpdateExisting { + customer, update + } + } + CertAuthCommandDetails::AspasRenew => { CertAuthStorableCommand::ReissueBeforeExpiring } - CertAuthCommandDetails::BgpSecUpdateDefinitions(_, _, _) => { + CertAuthCommandDetails::BgpSecUpdateDefinitions(_) => { CertAuthStorableCommand::BgpSecDefinitionUpdates } - CertAuthCommandDetails::BgpSecRenew(_, _) => { + CertAuthCommandDetails::BgpSecRenew => { CertAuthStorableCommand::ReissueBeforeExpiring } - CertAuthCommandDetails::RepoUpdate(contact, _) => { + CertAuthCommandDetails::RepoUpdate(contact) => { CertAuthStorableCommand::RepoUpdate { service_uri: contact.server_info.service_uri.clone(), } } - CertAuthCommandDetails::RtaMultiPrepare(name, _, _) => { + CertAuthCommandDetails::RtaMultiPrepare(name, _) => { CertAuthStorableCommand::RtaPrepare { name } } - CertAuthCommandDetails::RtaSign(name, _, _) => { + CertAuthCommandDetails::RtaSign(name, _) => { CertAuthStorableCommand::RtaSign { name } } - CertAuthCommandDetails::RtaCoSign(name, _, _) => { + CertAuthCommandDetails::RtaCoSign(name, _) => { CertAuthStorableCommand::RtaCoSign { name } } } diff --git a/src/server/ca/manager.rs b/src/server/ca/manager.rs index a3bfea93b..fd2830113 100644 --- a/src/server/ca/manager.rs +++ b/src/server/ca/manager.rs @@ -32,7 +32,7 @@ use crate::api::aspa::{ }; use crate::api::bgpsec::{BgpSecCsrInfoList, BgpSecDefinitionUpdates}; use crate::api::ca::{ - CertAuthIssues, CertAuthList, CertAuthSummary, ChildCaInfo, IdCertInfo, + CertAuthIssues, ChildCaInfo, IdCertInfo, ParentStatuses, ReceivedCert, RepoStatus, RtaName, Timestamp, }; use crate::api::history::{ @@ -51,17 +51,15 @@ use crate::commons::httpclient; use crate::commons::KrillResult; use crate::commons::actor::Actor; use crate::commons::cmslogger::CmsLogger; -use crate::commons::crypto::KrillSigner; use crate::commons::error::{Error, Error as KrillError}; use crate::commons::eventsourcing::{Aggregate, AggregateStore, SentCommand}; use crate::constants::{ CASERVER_NS, STATUS_NS, TA_PROXY_SERVER_NS, TA_SIGNER_SERVER_NS, TA_NAME, ta_handle, }; -use crate::daemon::http::auth::{AuthInfo, Permission}; // XXX remove use crate::config::Config; -use crate::server::mq::{now, Task, TaskQueue}; -use crate::server::pubd::RepositoryManager; +use crate::server::mq::{now, Task}; +use crate::server::runtime::KrillRuntime; use crate::server::taproxy::{ TrustAnchorProxy, TrustAnchorProxyCommand, TrustAnchorProxyInitCommand, }; @@ -110,41 +108,18 @@ pub struct CaManager { /// without the need for user interactions through the API and /// TA signer CLI. ta_signer_store: Option>, - - /// The task queue. - /// - /// This queue: - /// - listens for events in the ca_store, - /// - is processed by the Scheduler, - /// - can be used here to schedule tasks through the API. - tasks: Arc, - - /// The server configuration. - config: Arc, - - /// The signer. - signer: Arc, - - /// The actor used for all thing Krill does itself. - /// - /// This actor is used for (scheduled or triggered) system actions where - /// we have no operator actor context. - system_actor: Actor, } impl CaManager { /// Builds a new CA manager. /// /// Return an error if any of the various stores cannot be initialized. - pub async fn build( - config: Arc, - tasks: Arc, - signer: Arc, - system_actor: Actor, + pub fn new( + config: &Config, ) -> KrillResult { // Create the AggregateStore for the event-sourced `CertAuth` // structures that handle most CA functions. - let mut ca_store = AggregateStore::::create( + let ca_store = AggregateStore::::create( &config.storage_uri, CASERVER_NS, config.use_history_cache, @@ -177,60 +152,16 @@ impl CaManager { // for manifests and CRL generation. let ca_objects_store = Arc::new(CaObjectsStore::create( &config.storage_uri, - config.issuance_timing.clone(), - signer.clone(), + config.issuance_timing, )?); - // Register the `CaObjectsStore` as a pre-save listener to the - // 'ca_store' so that it can update its ROAs and issued - // certificates and/or generate manifests and CRLs when relevant - // changes occur in a `CertAuth`. - ca_store.add_pre_save_listener(ca_objects_store.clone()); - - // Register the `MessageQueue` as a pre-save listener to 'ca_store' so - // that relevant changes in a `CertAuth` can trigger follow-up - // actions. This is done as pre-save listener, because commands - // that would result in a follow-up should fail, if the task cannot be - // planned. - // - // Tasks will typically be picked up after the CA changes are - // committed, but they may also be picked up sooner by another - // thread. Because of that the tasks will remember which minimal - // version of the CA they are intended for, so that they can - // be rescheduled should they have been picked up too soon. - // - // An example of a triggered task: schedule a synchronisation with the - // repository (publication server) in case ROAs have been - // updated. - ca_store.add_pre_save_listener(tasks.clone()); - - // Now also register the `MessageQueue` as a post-save listener. We - // use this to send best-effort post-save signals to children - // in case a certificate was updated or a child key was revoked. - // This is a no-op for remote children (we cannot send a signal over - // RFC 6492). - ca_store.add_post_save_listener(tasks.clone()); - // Create TA proxy store if we need it. let ta_proxy_store = if config.ta_proxy_enabled() { - let mut store = AggregateStore::::create( + Some(AggregateStore::::create( &config.storage_uri, TA_PROXY_SERVER_NS, config.use_history_cache, - )?; - - // We need a pre-save listener so that we can schedule: - // - publication on updates - // - signing by the Trust Anchor Signer when there are requests - // [in testbed mode] - store.add_pre_save_listener(tasks.clone()); - - // We need a post-save listener so that we can schedule: - // - re-sync for local children when the proxy has new responses - // AND is saved - store.add_post_save_listener(tasks.clone()); - - Some(store) + )?) } else { None @@ -260,18 +191,9 @@ impl CaManager { status_store, ta_proxy_store, ta_signer_store, - tasks, - config, - signer, - system_actor, }) } - /// Returns whether testbed mode is enabled. - pub fn testbed_enabled(&self) -> bool { - self.config.testbed().is_some() - } - /// Processes a command for a CA. /// /// The command will be processed on the latest version of the CA. @@ -282,8 +204,12 @@ impl CaManager { handle: CaHandle, actor: &Actor, command: CertAuthCommandDetails, + krill: &KrillRuntime, ) -> Result, KrillError> { - self.ca_store.command(SentCommand::new(handle, None, command, actor)) + self.ca_store.command_with_context( + SentCommand::new(handle, None, command, actor), + krill + ) } /// Republish the embedded TA and CAs if needed. @@ -295,10 +221,11 @@ impl CaManager { pub fn republish_all( &self, force: bool, + krill: &KrillRuntime ) -> KrillResult> { let mut res = vec![]; for ca in self.ca_store.list()? { - match self.ca_objects_store.reissue_if_needed(force, &ca) { + match self.ca_objects_store.reissue_if_needed(force, &ca, krill) { Err(e) => { error!( "Could not reissue manifest and crl for {ca}.\ @@ -320,6 +247,15 @@ impl CaManager { } } +/// # Private access to components +/// +impl CaManager { + pub(super) fn ca_objects_store(&self) -> &CaObjectsStore { + &self.ca_objects_store + } +} + + /// # Trust Anchor Support /// impl CaManager { @@ -329,10 +265,11 @@ impl CaManager { fn send_ta_proxy_command( &self, cmd: TrustAnchorProxyCommand, + krill: &KrillRuntime, ) -> KrillResult> { self.ta_proxy_store.as_ref().ok_or_else(|| { Error::custom("ta_support_enabled is false") - })?.command(cmd) + })?.command_with_context(cmd, krill.into()) } /// Sends a command to the TA signer. @@ -341,10 +278,11 @@ impl CaManager { fn send_ta_signer_command( &self, cmd: TrustAnchorSignerCommand, + krill: &KrillRuntime, ) -> KrillResult> { self.ta_signer_store.as_ref().ok_or_else(|| { Error::custom("ta_signer_enabled is false") - })?.command(cmd) + })?.command_with_context(cmd, krill.into()) } /// Returns the TA proxy. @@ -375,7 +313,9 @@ impl CaManager { /// /// Returns an error if TA proxy support is not enabled or the proxy is /// alreay initialized. - pub fn ta_proxy_init(&self) -> KrillResult<()> { + pub fn ta_proxy_init( + &self, krill: &KrillRuntime + ) -> KrillResult<()> { let ta_handle = ta_handle(); let ta_proxy_store = self.ta_proxy_store.as_ref().ok_or_else(|| { @@ -386,12 +326,12 @@ impl CaManager { return Err(Error::TaAlreadyInitialized) } - ta_proxy_store.add( + ta_proxy_store.add_with_context( TrustAnchorProxyInitCommand::make( ta_handle, - self.signer.clone(), - &self.system_actor, - ) + krill.system_actor(), + ), + krill.into(), )?; Ok(()) } @@ -405,6 +345,7 @@ impl CaManager { tal_https: Vec, tal_rsync: uri::Rsync, private_key_pem: Option, + krill: &KrillRuntime, ) -> KrillResult<()> { let handle = ta_handle(); @@ -427,16 +368,14 @@ impl CaManager { tal_rsync, private_key_pem, ta_mft_nr_override: None, - timing: self.config.ta_timing, - signer: self.signer.clone(), }; let cmd = TrustAnchorSignerInitCommand::new( handle, details, - &self.system_actor, + krill.system_actor(), ); - ta_signer_store.add(cmd)?; + ta_signer_store.add_with_context(cmd, krill.into())?; Ok(()) } @@ -463,13 +402,15 @@ impl CaManager { &self, contact: RepositoryContact, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.send_ta_proxy_command( TrustAnchorProxyCommand::add_repo( &ta_handle(), contact, actor, - ) + ), + krill, )?; Ok(()) } @@ -493,9 +434,11 @@ impl CaManager { &self, info: TrustAnchorSignerInfo, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.send_ta_proxy_command( - TrustAnchorProxyCommand::add_signer(&ta_handle(), info, actor) + TrustAnchorProxyCommand::add_signer(&ta_handle(), info, actor), + krill )?; Ok(()) } @@ -507,9 +450,11 @@ impl CaManager { &self, info: TrustAnchorSignerInfo, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.send_ta_proxy_command( - TrustAnchorProxyCommand::update_signer(&ta_handle(), info, actor) + TrustAnchorProxyCommand::update_signer(&ta_handle(), info, actor), + krill )?; Ok(()) } @@ -520,18 +465,20 @@ impl CaManager { pub fn ta_proxy_signer_make_request( &self, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { self.send_ta_proxy_command( - TrustAnchorProxyCommand::make_signer_request(&ta_handle(), actor) - )?.get_signer_request(self.config.ta_timing, &self.signer) + TrustAnchorProxyCommand::make_signer_request(&ta_handle(), actor), + krill + )?.get_signer_request(krill.config().ta_timing, krill.signer()) } /// Returns the current request for the signer. pub fn ta_proxy_signer_get_request( - &self, + &self, krill: &KrillRuntime, ) -> KrillResult { self.get_trust_anchor_proxy()?.get_signer_request( - self.config.ta_timing, &self.signer + krill.config().ta_timing, krill.signer() ) } @@ -540,65 +487,74 @@ impl CaManager { &self, response: TrustAnchorSignedResponse, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.send_ta_proxy_command( TrustAnchorProxyCommand::process_signer_response( &ta_handle(), response, actor, - ) + ), + krill )?; Ok(()) } /// Initializes an embedded trust anchor with all resources. - pub async fn ta_init_fully_embedded( + pub fn ta_init_fully_embedded( &self, ta_aia: uri::Rsync, ta_uris: Vec, ta_key_pem: Option, - repo_manager: &Arc, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { let ta_handle = ta_handle(); // Initialise proxy - self.ta_proxy_init()?; + self.ta_proxy_init(krill)?; // Add repository let pub_req = self.ta_proxy_publisher_request()?; // Create publisher - repo_manager.create_publisher(pub_req, actor)?; - let repository_response = - repo_manager.repository_response(&ta_handle.convert())?; + krill.repo_manager().create_publisher(pub_req, actor)?; + let repository_response = krill.repo_manager().repository_response( + &ta_handle.convert(), krill + )?; // Add repository to proxy let contact = RepositoryContact::try_from_response( repository_response ).map_err(Error::rfc8183)?; - self.ta_proxy_repository_update(contact, &self.system_actor)?; + self.ta_proxy_repository_update( + contact, krill.system_actor(), krill + )?; // Initialise signer - self.ta_signer_init(ta_uris, ta_aia, ta_key_pem)?; + self.ta_signer_init(ta_uris, ta_aia, ta_key_pem, krill)?; // Add signer to proxy let signer_info = self.get_trust_anchor_signer()?.get_signer_info(); - self.ta_proxy_signer_add(signer_info, &self.system_actor)?; + self.ta_proxy_signer_add( + signer_info, krill.system_actor(), krill + )?; - self.sync_ta_proxy_signer_if_possible()?; - self.cas_repo_sync_single(repo_manager, &ta_handle, 0).await?; + self.sync_ta_proxy_signer_if_possible(krill)?; + self.cas_repo_sync_single(&ta_handle, 0, krill)?; Ok(()) } /// Renews the embedded testbed TA; - pub fn ta_renew_testbed_ta(&self) -> KrillResult<()> { - if self.testbed_enabled() { + pub fn ta_renew_testbed_ta( + &self, krill: &KrillRuntime, + ) -> KrillResult<()> { + if krill.is_testbed_enabled() { let proxy = self.get_trust_anchor_proxy()?; if !proxy.has_open_request() { info!("Renew the testbed TA"); - self.sync_ta_proxy_signer_if_possible()?; + self.sync_ta_proxy_signer_if_possible(krill)?; } } Ok(()) @@ -609,7 +565,9 @@ impl CaManager { /// impl CaManager { /// Initializes a CA without a repo, no parents, no children, no nothing - pub fn init_ca(&self, handle: CaHandle) -> KrillResult<()> { + pub fn init_ca( + &self, handle: CaHandle, krill: &KrillRuntime, + ) -> KrillResult<()> { if handle == ta_handle() || handle.as_str() == "version" { return Err(Error::TaNameReserved) } @@ -621,12 +579,12 @@ impl CaManager { // need to create a new CA entry in // self.ca_objects_store or self.status_store, because they will // generate empty default entries if needed. - self.ca_store.add( + self.ca_store.add_with_context( CertAuthInitCommand::new( handle, - CertAuthInitCommandDetails { signer: self.signer.clone() }, - &self.system_actor, - ) + CertAuthInitCommandDetails, + krill.system_actor(), + ), krill )?; Ok(()) } @@ -643,12 +601,12 @@ impl CaManager { &self, handle: CaHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( handle, actor, - CertAuthCommandDetails::GenerateNewIdKey( - self.signer.clone(), - ) + CertAuthCommandDetails::GenerateNewIdKey, + krill )?; Ok(()) } @@ -658,24 +616,6 @@ impl CaManager { Ok(self.ca_store.list()?) } - /// Returns the CAs that the given policy allows read access to. - pub fn ca_list( - &self, auth: &AuthInfo, - ) -> KrillResult { - Ok(CertAuthList { - cas: self.ca_store - .list()? - .into_iter() - .filter(|handle| { - auth.check_permission( - Permission::CaRead, Some(handle) - ).is_ok() - }) - .map(|handle| CertAuthSummary { handle }) - .collect(), - }) - } - /// Returns the CA by the given handle. /// /// Returns an error if the CA does not exist. @@ -745,11 +685,11 @@ impl CaManager { /// Does best effort revocation requests and withdraws all its objects /// first. Note that any children of this CA will be left orphaned, and /// they will only learn of this sad fact when they choose to call home. - pub async fn delete_ca( + pub fn delete_ca( &self, - repo_manager: &RepositoryManager, ca_handle: &CaHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { warn!("Deleting CA '{ca_handle}' as requested by: {actor}"); @@ -761,7 +701,7 @@ impl CaManager { before removing it." ); for parent in ca.parents() { - if let Err(e) = self.ca_parent_revoke(ca_handle, parent).await { + if let Err(e) = self.ca_parent_revoke(ca_handle, parent, krill) { warn!( "Removing CA '{ca_handle}', but could not send revoke request \ to parent '{parent}': {e}" @@ -787,12 +727,12 @@ impl CaManager { for repo_contact in repos { if self.ca_repo_sync( - repo_manager, ca_handle, ca.id_cert(), &repo_contact, vec![], - ).await.is_err() { + krill + ).is_err() { info!( "Could not clean up deprecated repository. This is \ fine - objects there are no longer referenced." @@ -843,8 +783,8 @@ impl CaManager { &self, ca: &CaHandle, req: AddChildRequest, - service_uri: &uri::Https, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { info!("CA '{}' process add child request: {}", &ca, &req); if ca.as_str() != TA_NAME { @@ -853,16 +793,17 @@ impl CaManager { req.handle.clone(), req.id_cert.into(), req.resources - ) + ), + krill )?; - self.ca_parent_response(ca, req.handle, service_uri) + self.ca_parent_response(ca, req.handle, krill.service_uri()) } else { let child_handle = req.handle.clone(); let add_child_cmd = TrustAnchorProxyCommand::add_child(ca, req, actor); - self.send_ta_proxy_command(add_child_cmd)?; - self.ca_parent_response(ca, child_handle, service_uri) + self.send_ta_proxy_command(add_child_cmd, krill)?; + self.ca_parent_response(ca, child_handle, krill.service_uri()) } } @@ -908,14 +849,14 @@ impl CaManager { ca: &CaHandle, import_child: ImportChild, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { trace!("Importing CA: {} under parent: {}", import_child.name, ca); self.process_ca_command(ca.clone(), actor, CertAuthCommandDetails::ChildImport( - import_child, - self.config.clone(), - self.signer.clone(), - ) + import_child + ), + krill, )?; Ok(()) } @@ -990,13 +931,15 @@ impl CaManager { child: ChildHandle, req: UpdateChildRequest, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { if let Some(id) = req.id_cert { self.process_ca_command(ca.clone(), actor, CertAuthCommandDetails::ChildUpdateId( child.clone(), id.into(), - ) + ), + krill, )?; } if let Some(resources) = req.resources { @@ -1005,6 +948,7 @@ impl CaManager { child.clone(), resources, ), + krill, )?; } if let Some(suspend) = req.suspend { @@ -1013,16 +957,16 @@ impl CaManager { ca.clone(), actor, CertAuthCommandDetails::ChildSuspendInactive( child.clone() - ) + ), + krill, )?; } else { self.process_ca_command( ca.clone(), actor, CertAuthCommandDetails::ChildUnsuspend( child.clone(), - self.config.clone(), - self.signer.clone(), - ) + ), + krill, )?; } } @@ -1030,7 +974,8 @@ impl CaManager { self.process_ca_command(ca.clone(), actor, CertAuthCommandDetails::ChildUpdateResourceClassNameMapping( child, mapping, - ) + ), + krill, )?; } Ok(()) @@ -1045,10 +990,13 @@ impl CaManager { ca: &CaHandle, child: ChildHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.status_store.remove_child(ca, &child)?; - self.process_ca_command(ca.clone(), actor, - CertAuthCommandDetails::ChildRemove(child) + self.process_ca_command( + ca.clone(), actor, + CertAuthCommandDetails::ChildRemove(child), + krill, )?; Ok(()) } @@ -1063,6 +1011,7 @@ impl CaManager { msg_bytes: Bytes, user_agent: Option, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { if ca_handle.as_str() == TA_NAME { return Err(Error::custom( @@ -1076,18 +1025,18 @@ impl CaManager { // Create a logger for CMS (avoid cloning recipient) let cms_logger = CmsLogger::for_rfc6492_rcvd( - self.config.rfc6492_log_dir.as_ref(), + krill.config().rfc6492_log_dir.as_ref(), req_msg.recipient(), req_msg.sender(), ); match self.rfc6492_process_request( - ca_handle, req_msg, user_agent, actor + ca_handle, req_msg, user_agent, actor, krill ) { Ok(msg) => { let should_log_cms = !msg.is_list_response(); let reply_bytes = ca.sign_rfc6492_response( - msg, &self.signer + msg, krill.signer() )?; if should_log_cms { @@ -1113,6 +1062,7 @@ impl CaManager { req_msg: provisioning::Message, user_agent: Option, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { let (sender, _recipient, payload) = req_msg.unpack(); @@ -1139,6 +1089,7 @@ impl CaManager { child_handle.clone(), UpdateChildRequest::unsuspend(), actor, + krill, )?; } } @@ -1146,15 +1097,15 @@ impl CaManager { let res_msg = match payload { provisioning::Payload::Revoke(req) => { self.rfc6492_revoke( - ca_handle, child_handle.clone(), req, actor + ca_handle, child_handle.clone(), req, actor, krill, ) } provisioning::Payload::List => { - self.rfc6492_list(ca_handle, &child_handle) + self.rfc6492_list(ca_handle, &child_handle, krill) } provisioning::Payload::Issue(req) => { self.rfc6492_issue( - ca_handle, child_handle.clone(), req, actor + ca_handle, child_handle.clone(), req, actor, krill, ) } _ => Err(Error::custom("Unsupported RFC6492 message")), @@ -1205,13 +1156,16 @@ impl CaManager { &self, ca_handle: &CaHandle, child: &ChildHandle, + krill: &KrillRuntime, ) -> KrillResult { let list_response = if ca_handle.as_str() != TA_NAME { - self.get_ca(ca_handle)?.list(child, &self.config.issuance_timing) + self.get_ca(ca_handle)?.list( + child, &krill.config().issuance_timing + ) } else { self.get_trust_anchor_proxy()?.entitlements( - child, &self.config.ta_timing + child, &krill.config().ta_timing ).map(|entitlements| { ResourceClassListResponse::new(vec![entitlements]) }) @@ -1233,6 +1187,7 @@ impl CaManager { child_handle: ChildHandle, issue_req: IssuanceRequest, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { if ca_handle.as_str() == TA_NAME { let request = ProvisioningRequest::Issuance(issue_req); @@ -1241,6 +1196,7 @@ impl CaManager { child_handle, request, actor, + krill, ) } else { @@ -1252,9 +1208,8 @@ impl CaManager { CertAuthCommandDetails::ChildCertify( child_handle.clone(), issue_req.clone(), - self.config.clone(), - self.signer.clone(), - ) + ), + krill )?; // The updated CA will now include the newly issued certificate. @@ -1265,7 +1220,7 @@ impl CaManager { &child_handle, &my_rcn, pub_key, - &self.config.issuance_timing, + &krill.config().issuance_timing, )?; Ok(provisioning::Message::issue_response( @@ -1283,10 +1238,13 @@ impl CaManager { child: ChildHandle, revoke_request: RevocationRequest, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { if ca_handle.as_str() == TA_NAME { let request = ProvisioningRequest::Revocation(revoke_request); - self.ta_slow_rfc6492_request(ca_handle, child, request, actor) + self.ta_slow_rfc6492_request( + ca_handle, child, request, actor, krill + ) } else { let res = RevocationResponse::from(&revoke_request); @@ -1296,7 +1254,8 @@ impl CaManager { res, ); self.process_ca_command(ca_handle.clone(), actor, - CertAuthCommandDetails::ChildRevokeKey(child, revoke_request) + CertAuthCommandDetails::ChildRevokeKey(child, revoke_request), + krill, )?; Ok(msg) } @@ -1312,6 +1271,7 @@ impl CaManager { child: ChildHandle, request: ProvisioningRequest, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { let proxy = self.get_trust_anchor_proxy()?; if let Some(response) = proxy.response_for_child(&child, &request)? { @@ -1328,7 +1288,8 @@ impl CaManager { child, request.key_identifier(), actor, - ) + ), + krill, )?; Ok(response) @@ -1356,7 +1317,8 @@ impl CaManager { child.clone(), request, actor, - ) + ), + krill, )?; provisioning::Message::not_performed_response( @@ -1386,6 +1348,7 @@ impl CaManager { handle: CaHandle, parent_req: ParentCaReq, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { let ca = self.get_ca(&handle)?; @@ -1404,7 +1367,7 @@ impl CaManager { parent_req.handle, contact, ) }; - self.process_ca_command(handle.clone(), actor, cmd)?; + self.process_ca_command(handle.clone(), actor, cmd, krill)?; Ok(()) } @@ -1414,15 +1377,16 @@ impl CaManager { /// this parent are requested. Any resource classes under the parent will /// be removed and all relevant content will be withdrawn from the /// repository. - pub async fn ca_parent_remove( + pub fn ca_parent_remove( &self, handle: CaHandle, parent: ParentHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { // Best effort, request revocations for any remaining keys under this // parent. - if let Err(e) = self.ca_parent_revoke(&handle, &parent).await { + if let Err(e) = self.ca_parent_revoke(&handle, &parent, krill) { warn!( "Removing parent '{parent}' from CA '{handle}', but could not send \ revoke requests: {e}" @@ -1433,20 +1397,21 @@ impl CaManager { self.process_ca_command( handle.clone(), actor, CertAuthCommandDetails::RemoveParent(parent), + krill )?; Ok(()) } /// Sends revocation requests for a parent of a CA. - async fn ca_parent_revoke( + fn ca_parent_revoke( &self, handle: &CaHandle, parent: &ParentHandle, + krill: &KrillRuntime, ) -> KrillResult<()> { let ca = self.get_ca(handle)?; - let revoke_requests = ca.revoke_under_parent(parent, &self.signer)?; - self.send_revoke_requests(handle, parent, revoke_requests) - .await?; + let revoke_requests = ca.revoke_under_parent(parent, krill.signer())?; + self.send_revoke_requests(handle, parent, revoke_requests, krill)?; Ok(()) } @@ -1455,10 +1420,12 @@ impl CaManager { /// Note: this function can be called manually through the API, but /// normally the CA refresh process is replanned on the task /// queue automatically. - pub fn cas_schedule_refresh_all(&self) -> KrillResult<()> { + pub fn cas_schedule_refresh_all( + &self, krill: &KrillRuntime + ) -> KrillResult<()> { if let Ok(cas) = self.ca_store.list() { for ca_handle in cas { - self.cas_schedule_refresh_single(ca_handle)?; + self.cas_schedule_refresh_single(ca_handle, krill)?; } } Ok(()) @@ -1468,10 +1435,9 @@ impl CaManager { /// /// This possibly also suspend inactive children. pub fn cas_schedule_refresh_single( - &self, - ca_handle: CaHandle, + &self, ca_handle: CaHandle, krill: &KrillRuntime ) -> KrillResult<()> { - self.ca_schedule_sync_parents(&ca_handle) + self.ca_schedule_sync_parents(&ca_handle, krill) } /// Schedules an immediate check suspending all inactive children. @@ -1482,13 +1448,15 @@ impl CaManager { /// While this function can be called manually through the API, it is /// normally replanned on the task queue automatically if suspension is /// enabled. - pub fn cas_schedule_suspend_all(&self) -> KrillResult<()> { - if - self.config.suspend_child_after_inactive_seconds().is_some() + pub fn cas_schedule_suspend_all( + &self, krill: &KrillRuntime + ) -> KrillResult<()> { + if + krill.config().suspend_child_after_inactive_seconds().is_some() && let Ok(cas) = self.ca_store.list() { for ca in cas { - self.tasks.schedule( + krill.tasks().schedule( Task::SuspendChildrenIfNeeded { ca_handle: ca }, now(), )?; @@ -1504,7 +1472,8 @@ impl CaManager { // // XXX PANICS pub fn ca_suspend_inactive_children( - &self, ca_handle: &CaHandle, started: Timestamp, actor: &Actor + &self, ca_handle: &CaHandle, started: Timestamp, actor: &Actor, + krill: &KrillRuntime, ) { // Set threshold hours if it was configured AND this server has been // started longer ago than the hours specified. Otherwise we @@ -1512,7 +1481,7 @@ impl CaManager { // suspended on upgrade, or that *all* children are suspended // if the server had been down for more than the threshold hours. let threshold_seconds = - self.config.suspend_child_after_inactive_seconds() + krill.config().suspend_child_after_inactive_seconds() .filter(|secs| started < Timestamp::now_minus_seconds(*secs)); // suspend inactive children, if so configured @@ -1550,7 +1519,7 @@ impl CaManager { let req = UpdateChildRequest::suspend(); if let Err(e) = self.ca_child_update( - ca_handle, child, req, actor + ca_handle, child, req, actor, krill, ) { error!( "Could not suspend inactive child, error: {e}" @@ -1566,16 +1535,17 @@ impl CaManager { fn ca_schedule_sync_parents( &self, ca_handle: &CaHandle, + krill: &KrillRuntime, ) -> KrillResult<()> { let Ok(ca) = self.get_ca(ca_handle) else { return Ok(()) }; - if ca.nr_parents() <= self.config.ca_refresh_parents_batch_size { + if ca.nr_parents() <= krill.config().ca_refresh_parents_batch_size { // Nr of parents is below batch size, so just process all // of them for parent in ca.parents() { - self.tasks.schedule( + krill.tasks().schedule( Task::SyncParent { ca_handle: ca_handle.clone(), ca_version: 0, @@ -1593,9 +1563,9 @@ impl CaManager { for parent in status.parents().sync_candidates( ca.parents().collect(), - self.config.ca_refresh_parents_batch_size, + krill.config().ca_refresh_parents_batch_size, ) { - self.tasks.schedule( + krill.tasks().schedule( Task::SyncParent { ca_handle: ca_handle.clone(), ca_version: 0, @@ -1626,12 +1596,13 @@ impl CaManager { /// /// This method is called by the scheduler in response to the scheduled /// sync as well as `KrillServer` when importing a CA. - pub async fn ca_sync_parent( + pub fn ca_sync_parent( &self, handle: &CaHandle, min_ca_version: u64, // set this 0 if it does not matter parent: &ParentHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { let ca = self.get_ca(handle)?; @@ -1645,10 +1616,12 @@ impl CaManager { } else { if ca.has_pending_requests(parent) { - self.send_requests(handle, parent, actor).await?; + self.send_requests(handle, parent, actor, krill)?; } else { - self.get_updates_from_parent(handle, parent, actor).await?; + self.get_updates_from_parent( + handle, parent, actor, krill, + )?; } Ok(true) } @@ -1659,7 +1632,9 @@ impl CaManager { /// If the TA signer is remote, logs a warning suggesting doing a /// manual synchronization, assuming that this method is only ever called /// if the TA proxy requires synchronization. - pub fn sync_ta_proxy_signer_if_possible(&self) -> KrillResult<()> { + pub fn sync_ta_proxy_signer_if_possible( + &self, krill: &KrillRuntime, + ) -> KrillResult<()> { let ta_handle = ta_handle(); if self.get_trust_anchor_proxy().is_err() { @@ -1682,14 +1657,15 @@ impl CaManager { let proxy = self.send_ta_proxy_command( TrustAnchorProxyCommand::make_signer_request( &ta_handle, - &self.system_actor, - ) + krill.system_actor(), + ), + krill, )?; // Get sign request for signer. let signed_request = proxy.get_signer_request( - self.config.ta_timing, - &self.signer, + krill.config().ta_timing, + krill.signer(), )?; // Remember the noce of the request so we can retrieve it. @@ -1700,11 +1676,10 @@ impl CaManager { TrustAnchorSignerCommand::make_process_request_command( &ta_handle, signed_request.into(), - self.config.ta_timing, None, // do not override next manifest number - self.signer.clone(), - &self.system_actor, - ) + krill.system_actor(), + ), + krill, )?; // Get the response from the signer and give it to the proxy. @@ -1713,8 +1688,9 @@ impl CaManager { TrustAnchorProxyCommand::process_signer_response( &ta_handle, exchange.clone().response, - &self.system_actor, - ) + krill.system_actor(), + ), + krill, )?; Ok(()) } @@ -1722,11 +1698,12 @@ impl CaManager { /// Tries to get updates from a specific parent of a CA. /// /// Quietly does nothing for the TA CA. - async fn get_updates_from_parent( + fn get_updates_from_parent( &self, handle: &CaHandle, parent: &ParentHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { if handle == &ta_handle() { return Ok(()) @@ -1741,11 +1718,11 @@ impl CaManager { let ca = self.get_ca(handle)?; let parent_contact = ca.parent(parent)?; let entitlements = self.get_entitlements_from_contact( - handle, parent, parent_contact, true, - ).await?; + handle, parent, parent_contact, true, krill, + )?; self.update_entitlements( - handle, parent.clone(), entitlements, actor, + handle, parent.clone(), entitlements, actor, krill, )?; Ok(()) @@ -1755,27 +1732,29 @@ impl CaManager { /// /// First sends all open revoke requests, then sends all open /// certificate requests. - async fn send_requests( + fn send_requests( &self, handle: &CaHandle, parent: &ParentHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.send_revoke_requests_handle_responses( - handle, parent, actor - ).await?; + handle, parent, actor, krill, + )?; self.send_cert_requests_handle_responses( - handle, parent, actor - ).await + handle, parent, actor, krill, + ) } /// Sends all open revocation requests and handles the responses. - async fn send_revoke_requests_handle_responses( + fn send_revoke_requests_handle_responses( &self, handle: &CaHandle, parent: &ParentHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { let child = self.get_ca(handle)?; let requests = child.revoke_requests(parent); let revoke_responses = self.send_revoke_requests( - handle, parent, requests - ).await?; + handle, parent, requests, krill, + )?; for (rcn, revoke_responses) in revoke_responses { for response in revoke_responses { @@ -1784,7 +1763,8 @@ impl CaManager { CertAuthCommandDetails::KeyRollFinish( rcn.clone(), response, - ) + ), + krill, )?; } } @@ -1795,11 +1775,12 @@ impl CaManager { /// Sends the given revoke requests to a parent. /// /// Returns the responses for the requests. - pub async fn send_revoke_requests( + pub fn send_revoke_requests( &self, handle: &CaHandle, parent: &ParentHandle, revoke_requests: HashMap>, + krill: &KrillRuntime, ) -> KrillResult>> { let child = self.get_ca(handle)?; let server_info = child.parent(parent)?.parent_server_info(); @@ -1808,7 +1789,8 @@ impl CaManager { revoke_requests, &child.id_cert().public_key.key_identifier(), server_info, - ) .await { + krill, + ) { Err(e) => { self.status_store.set_parent_failure( handle, parent, &server_info.service_uri, &e @@ -1825,11 +1807,12 @@ impl CaManager { } /// Sends a revoke request for an unexpected key. - pub async fn send_revoke_unexpected_key( + pub fn send_revoke_unexpected_key( &self, handle: &CaHandle, rcn: ResourceClassName, revocation: RevocationRequest, + krill: &KrillRuntime, ) -> KrillResult>> { let child = self.ca_store.get_latest(handle)?; @@ -1837,15 +1820,16 @@ impl CaManager { let mut requests = HashMap::new(); requests.insert(rcn, vec![revocation]); - self.send_revoke_requests(handle, parent, requests).await + self.send_revoke_requests(handle, parent, requests, krill) } /// Sends revoke requests using the provisioning protocol. - async fn send_revoke_requests_rfc6492( + fn send_revoke_requests_rfc6492( &self, revoke_requests: HashMap>, signing_key: &KeyIdentifier, server_info: &ParentServerInfo, + krill: &KrillRuntime, ) -> KrillResult>> { let mut revoke_map = HashMap::new(); @@ -1860,8 +1844,8 @@ impl CaManager { ); let response = self.send_rfc6492_and_validate_response( - revoke, server_info, signing_key - ) .await?; + revoke, server_info, signing_key, krill + )?; let payload = response.into_payload(); let payload_type = payload.payload_type(); @@ -1939,8 +1923,9 @@ impl CaManager { } /// Sends certification requests to a parent CA and proceses the response. - async fn send_cert_requests_handle_responses( + fn send_cert_requests_handle_responses( &self, ca_handle: &CaHandle, parent: &ParentHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { let ca = self.get_ca(ca_handle)?; let requests = ca.cert_requests(parent); @@ -1971,7 +1956,8 @@ impl CaManager { ), server_info, &signing_key, - ).await { + krill + ) { Err(e) => { // If any of the requests for an RC results in an // error, then record the @@ -1986,7 +1972,7 @@ impl CaManager { } Ok(response) => { if let Err(err) = self.handle_cert_response( - ca_handle, parent, &rcn, actor, response + ca_handle, parent, &rcn, actor, response, krill, ) { errors.push(err); break; @@ -2024,6 +2010,7 @@ impl CaManager { rcn: &ResourceClassName, actor: &Actor, response: provisioning::Message, + krill: &KrillRuntime, ) -> KrillResult<()> { let payload = response.into_payload(); let payload_type = payload.payload_type(); @@ -2094,11 +2081,9 @@ impl CaManager { if let Err(e) = self.process_ca_command( ca_handle.clone(), actor, CertAuthCommandDetails::UpdateRcvdCert( - rcn.clone(), - rcvd_cert, - self.config.clone(), - self.signer.clone(), - ) + rcn.clone(), rcvd_cert, + ), + krill, ) { // Note that sending the command to update a received // certificate cannot fail unless there are bigger issues @@ -2121,8 +2106,8 @@ impl CaManager { CertAuthCommandDetails::DropResourceClass( rcn.clone(), reason.clone(), - self.signer.clone(), - ) + ), + krill, )?; return Err(Error::CaParentSyncError( @@ -2177,8 +2162,8 @@ impl CaManager { CertAuthCommandDetails::DropResourceClass( rcn.clone(), reason.to_string(), - self.signer.clone(), - ) + ), + krill, )?; // Push the error for reporting, this will also @@ -2216,8 +2201,8 @@ impl CaManager { CertAuthCommandDetails::DropResourceClass( rcn.clone(), reason.to_string(), - self.signer.clone(), - ) + ), + krill, )?; // Push the error for reporting, this will also @@ -2281,6 +2266,7 @@ impl CaManager { parent: ParentHandle, entitlements: ResourceClassListResponse, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult { let current_version = self.get_ca(ca)?.version(); let new_version = self.process_ca_command( @@ -2288,24 +2274,27 @@ impl CaManager { CertAuthCommandDetails::UpdateEntitlements( parent, entitlements, - self.signer.clone(), ), + krill, )?.version(); Ok(new_version > current_version) } /// Requests the entitlements from the parent. - pub async fn get_entitlements_from_contact( + pub fn get_entitlements_from_contact( &self, ca: &CaHandle, parent: &ParentHandle, contact: &ParentCaContact, existing_parent: bool, + krill: &KrillRuntime, ) -> KrillResult { let server_info = contact.parent_server_info(); let uri = &server_info.service_uri; - let result = self.get_entitlements_rfc6492(ca, server_info).await; + let result = self.get_entitlements_rfc6492( + ca, server_info, krill + ); match &result { Err(error) => { @@ -2332,10 +2321,11 @@ impl CaManager { } /// Performs the provisioning protocol exchange for entitlements. - async fn get_entitlements_rfc6492( + fn get_entitlements_rfc6492( &self, handle: &CaHandle, server_info: &ParentServerInfo, + krill: &KrillRuntime, ) -> KrillResult { debug!( "Getting entitlements for CA '{}' from parent '{}'", @@ -2355,7 +2345,8 @@ impl CaManager { list, server_info, &child.id_cert().public_key.key_identifier(), - ).await?; + krill, + )?; let payload = response.into_payload(); let payload_type = payload.payload_type(); @@ -2374,15 +2365,16 @@ impl CaManager { } /// Sends a provisioning message and validates the response. - async fn send_rfc6492_and_validate_response( + fn send_rfc6492_and_validate_response( &self, message: provisioning::Message, server_info: &ParentServerInfo, signing_key: &KeyIdentifier, + krill: &KrillRuntime, ) -> KrillResult { let service_uri = &server_info.service_uri; if let Some(parent) = Self::local_parent( - service_uri, &self.config.service_uri() + service_uri, krill.service_uri() ) { let ca_handle = parent.into_converted(); let user_agent = Some("local-child".to_string()); @@ -2391,7 +2383,8 @@ impl CaManager { &ca_handle, message, user_agent, - &self.system_actor, + krill.system_actor(), + krill, ) } else { @@ -2403,21 +2396,22 @@ impl CaManager { let recipient = message.recipient().clone(); let cms_logger = CmsLogger::for_rfc6492_sent( - self.config.rfc6492_log_dir.as_ref(), + krill.config().rfc6492_log_dir.as_ref(), &sender, &recipient, ); - let cms = self.signer.create_rfc6492_cms( + let cms = krill.signer().create_rfc6492_cms( message, signing_key )?.to_bytes(); let res_bytes = self.post_protocol_cms_binary( - &cms, - service_uri, + cms, + service_uri.clone(), provisioning::CONTENT_TYPE, &cms_logger, - ).await?; + krill, + )?; match ProvisioningCms::decode(&res_bytes) { Err(e) => { @@ -2450,30 +2444,43 @@ impl CaManager { } /// Posts a protocol message via HTTP and receives a response. - async fn post_protocol_cms_binary( + fn post_protocol_cms_binary( &self, - msg: &Bytes, - service_uri: &ServiceUri, - content_type: &str, + msg: Bytes, + service_uri: ServiceUri, + content_type: &'static str, cms_logger: &CmsLogger, + krill: &KrillRuntime, ) -> KrillResult { - cms_logger.sent(msg)?; - - let timeout = self.config.post_protocol_msg_timeout_seconds; - - match httpclient::post_binary_with_full_ua( - service_uri.as_str(), - msg, - content_type, - timeout, - ).await { - Err(e) => { + cms_logger.sent(&msg)?; + + let timeout = krill.config().post_protocol_msg_timeout_seconds; + let http_uri = service_uri.clone(); + let res = krill.exec_async( + httpclient::post_binary_with_full_ua( + http_uri, + msg, + content_type, + timeout, + ) + ); + match res { + Err(_) => { + cms_logger.err(format!( + "Error posting CMS to {service_uri}: \ + internal error: HTTP task disappeared." + ))?; + Err(KrillError::InternalError( + "HTPP task disappeared".into() + )) + } + Ok(Err(err)) => { cms_logger.err(format!( - "Error posting CMS to {service_uri}: {e}" + "Error posting CMS to {service_uri}: {err}" ))?; - Err(Error::HttpClientError(e)) + Err(Error::HttpClientError(err)) } - Ok(bytes) => { + Ok(Ok(bytes)) => { cms_logger.reply(&bytes)?; Ok(bytes) } @@ -2512,22 +2519,21 @@ impl CaManager { impl CaManager { /// Schedules synchronizing all CAs with their repositories. pub fn cas_schedule_repo_sync_all( - &self, + &self, krill: &KrillRuntime ) -> KrillResult<()> { for ca in self.ca_handles()? { - self.cas_schedule_repo_sync(ca)?; + self.cas_schedule_repo_sync(ca, krill)?; } Ok(()) } /// Schedules synchronizing a CA with its repositories. pub fn cas_schedule_repo_sync( - &self, - ca_handle: CaHandle, + &self, ca_handle: CaHandle, krill: &KrillRuntime ) -> KrillResult<()> { // no need to wait for an updated CA to be committed. let ca_version = 0; - self.tasks.schedule( + krill.tasks().schedule( Task::SyncRepo { ca_handle, ca_version, @@ -2554,11 +2560,11 @@ impl CaManager { /// attempts, then the old repository is assumed to be unreachable and /// it will be dropped - i.e. the CA will no longer try to clean up /// objects. - pub async fn cas_repo_sync_single( + pub fn cas_repo_sync_single( &self, - repo_manager: &RepositoryManager, ca_handle: &CaHandle, ca_version: u64, + krill: &KrillRuntime, ) -> KrillResult { // Note that this is a no-op for new CAs which do not yet have any // repository configured. @@ -2574,9 +2580,7 @@ impl CaManager { )?; let objects = proxy.get_trust_anchor_objects()? .publish_elements()?; - self.ca_repo_sync( - repo_manager, ca_handle, id, repo, objects - ).await?; + self.ca_repo_sync(ca_handle, id, repo, objects, krill)?; Ok(true) } } @@ -2598,12 +2602,12 @@ impl CaManager { self.ca_repo_elements(ca_handle)? { self.ca_repo_sync( - repo_manager, ca_handle, ca.id_cert(), &repo_contact, objects, - ).await?; + krill, + )?; } // Clean-up of old repos @@ -2616,12 +2620,12 @@ impl CaManager { ); if let Err(e) = self.ca_repo_sync( - repo_manager, ca_handle, ca.id_cert(), deprecated.contact(), vec![], - ).await { + krill, + ) { warn!( "Could not clean up deprecated repository: {e}" ); @@ -2647,21 +2651,21 @@ impl CaManager { } /// Synchronizes with the repository. - async fn ca_repo_sync( + fn ca_repo_sync( &self, - repo_manager: &RepositoryManager, ca_handle: &CaHandle, id_cert: &IdCertInfo, repo_contact: &RepositoryContact, publish_elements: Vec, + krill: &KrillRuntime, ) -> KrillResult<()> { debug!("CA '{ca_handle}' sends list query to repo"); let list_reply = self.send_rfc8181_list( - repo_manager, ca_handle, id_cert, &repo_contact.server_info, - ).await?; + krill, + )?; // XXX Do we really need hash maps here? In particular, this will // quietly overwrite double URLs which we should probably catch? @@ -2695,12 +2699,12 @@ impl CaManager { if !delta.is_empty() { debug!("CA '{ca_handle}' sends delta"); self.send_rfc8181_delta( - repo_manager, ca_handle, id_cert, &repo_contact.server_info, delta, - ).await?; + krill + )?; debug!("CA '{ca_handle}' sent delta"); } else { @@ -2766,56 +2770,54 @@ impl CaManager { /// /// If `check_repo` is `true`, checks that the repository can be reached /// and returns an error if not. - pub async fn update_repo( + pub fn update_repo( &self, - repo_manager: &RepositoryManager, ca_handle: CaHandle, new_contact: RepositoryContact, check_repo: bool, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { let ca = self.get_ca(&ca_handle)?; if check_repo { // First verify that this repository can be reached and responds // to a list request. self.send_rfc8181_list( - repo_manager, &ca_handle, ca.id_cert(), &new_contact.server_info, - ).await.map_err(|e| { + krill + ).map_err(|e| { Error::CaRepoIssue(ca_handle.clone(), e.to_string()) })?; } self.process_ca_command( ca_handle, actor, - CertAuthCommandDetails::RepoUpdate( - new_contact, - self.signer.clone(), - ) + CertAuthCommandDetails::RepoUpdate(new_contact), + krill )?; Ok(()) } /// Sends a publication protocol list request and returns the reply. - async fn send_rfc8181_list( + fn send_rfc8181_list( &self, - repo_manager: &RepositoryManager, ca_handle: &CaHandle, id_cert: &IdCertInfo, server_info: &PublicationServerInfo, + krill: &KrillRuntime, ) -> KrillResult { let signing_key = id_cert.public_key.key_identifier(); let message = publication::Message::list_query(); let reply = match self.send_rfc8181_and_validate_response( - repo_manager, message, server_info, ca_handle, &signing_key, - ).await { + krill, + ) { Ok(reply) => reply, Err(e) => { self.status_store.set_status_repo_failure( @@ -2856,25 +2858,25 @@ impl CaManager { } /// Sends a publication protocol delta request. - async fn send_rfc8181_delta( + fn send_rfc8181_delta( &self, - repo_manager: &RepositoryManager, ca_handle: &CaHandle, id_cert: &IdCertInfo, server_info: &PublicationServerInfo, delta: PublishDelta, + krill: &KrillRuntime, ) -> KrillResult<()> { let signing_key = id_cert.public_key.key_identifier(); let message = publication::Message::delta(delta.clone()); let reply = match self.send_rfc8181_and_validate_response( - repo_manager, message, server_info, ca_handle, &signing_key, - ).await { + krill, + ) { Ok(reply) => reply, Err(e) => { self.status_store.set_status_repo_failure( @@ -2917,44 +2919,45 @@ impl CaManager { } /// Sends a publication protocol request and validates the response. - async fn send_rfc8181_and_validate_response( + fn send_rfc8181_and_validate_response( &self, - repo_manager: &RepositoryManager, message: publication::Message, server_info: &PublicationServerInfo, ca_handle: &CaHandle, signing_key: &KeyIdentifier, + krill: &KrillRuntime, ) -> KrillResult { let repo_service_uri = &server_info.service_uri; if repo_service_uri.as_str().starts_with( - self.config.service_uri().as_str() + krill.service_uri().as_str() ) { // this maps back to *this* Krill instance let query = message.as_query()?; let publisher_handle = ca_handle.convert(); - let response = repo_manager.rfc8181_message( - &publisher_handle, query + let response = krill.repo_manager().rfc8181_message( + &publisher_handle, query, krill )?; response.as_reply().map_err(Error::Rfc8181) } else { // Set up a logger for CMS exchanges. let cms_logger = CmsLogger::for_rfc8181_sent( - self.config.rfc8181_log_dir.as_ref(), + krill.config().rfc8181_log_dir.as_ref(), ca_handle, ); - let cms = self.signer.create_rfc8181_cms( + let cms = krill.signer().create_rfc8181_cms( message, signing_key )?.to_bytes(); let res_bytes = self.post_protocol_cms_binary( - &cms, - repo_service_uri, + cms, + repo_service_uri.clone(), publication::CONTENT_TYPE, &cms_logger, - ).await?; + krill, + )?; match publication::PublicationCms::decode(&res_bytes) { Err(e) => { @@ -3000,14 +3003,12 @@ impl CaManager { ca: CaHandle, updates: AspaDefinitionUpdates, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( ca, actor, - CertAuthCommandDetails::AspasUpdate( - updates, - self.config.clone(), - self.signer.clone(), - ), + CertAuthCommandDetails::AspasUpdate(updates), + krill, )?; Ok(()) } @@ -3019,15 +3020,14 @@ impl CaManager { customer: CustomerAsn, update: AspaProvidersUpdate, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( ca.clone(), actor, CertAuthCommandDetails::AspasUpdateExisting( - customer, - update, - self.config.clone(), - self.signer.clone(), - ) + customer, update, + ), + krill, )?; Ok(()) } @@ -3049,14 +3049,12 @@ impl CaManager { ca: CaHandle, updates: BgpSecDefinitionUpdates, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::BgpSecUpdateDefinitions( - updates, - self.config.clone(), - self.signer.clone(), - ), + CertAuthCommandDetails::BgpSecUpdateDefinitions(updates), + krill, )?; Ok(()) } @@ -3080,14 +3078,12 @@ impl CaManager { ca: CaHandle, updates: RoaConfigurationUpdates, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::RouteAuthorizationsUpdate( - updates, - self.config.clone(), - self.signer.clone(), - ), + CertAuthCommandDetails::RouteAuthorizationsUpdate(updates), + krill, )?; Ok(()) } @@ -3103,15 +3099,13 @@ impl CaManager { /// CAs are expected to note extended validity eligibility and request /// updated certificates themselves. pub fn renew_objects_all( - &self, actor: &Actor + &self, actor: &Actor, krill: &KrillRuntime, ) -> KrillResult<()> { for ca in self.ca_store.list()? { if let Err(e) = self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::RouteAuthorizationsRenew( - self.config.clone(), - self.signer.clone(), - ) + CertAuthCommandDetails::RouteAuthorizationsRenew, + krill, ) { error!( "Renewing ROAs for CA '{ca}' failed with error: {e}" @@ -3120,10 +3114,8 @@ impl CaManager { if let Err(e) = self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::AspasRenew( - self.config.clone(), - self.signer.clone(), - ), + CertAuthCommandDetails::AspasRenew, + krill, ) { error!( "Renewing ASPAs for CA '{ca}' failed with error: {e}" @@ -3132,10 +3124,8 @@ impl CaManager { if let Err(e) = self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::BgpSecRenew( - self.config.clone(), - self.signer.clone(), - ), + CertAuthCommandDetails::BgpSecRenew, + krill, ) { error!( "Renewing BGPsec certificates for CA '{ca}' \ @@ -3156,14 +3146,13 @@ impl CaManager { pub fn force_renew_roas_all( &self, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { for ca in self.ca_store.list()? { if let Err(e) = self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::RouteAuthorizationsForceRenew( - self.config.clone(), - self.signer.clone(), - ), + CertAuthCommandDetails::RouteAuthorizationsForceRenew, + krill, ) { error!( "Renewing ROAs for CA '{ca}' failed with error: {e}" @@ -3184,14 +3173,12 @@ impl CaManager { name: RtaName, request: RtaContentRequest, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::RtaSign( - name, - request, - self.signer.clone(), - ) + CertAuthCommandDetails::RtaSign(name, request), + krill, )?; Ok(()) } @@ -3203,14 +3190,12 @@ impl CaManager { name: RtaName, request: RtaPrepareRequest, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::RtaMultiPrepare( - name, - request, - self.signer.clone(), - ) + CertAuthCommandDetails::RtaMultiPrepare(name, request), + krill, )?; Ok(()) } @@ -3222,14 +3207,12 @@ impl CaManager { name: RtaName, rta: ResourceTaggedAttestation, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( ca.clone(), actor, - CertAuthCommandDetails::RtaCoSign( - name, - rta, - self.signer.clone(), - ) + CertAuthCommandDetails::RtaCoSign(name, rta), + krill, )?; Ok(()) } @@ -3246,13 +3229,12 @@ impl CaManager { handle: CaHandle, max_age: Duration, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( handle.clone(), actor, - CertAuthCommandDetails::KeyRollInitiate( - max_age, - self.signer.clone(), - ) + CertAuthCommandDetails::KeyRollInitiate(max_age), + krill, )?; Ok(()) } @@ -3268,14 +3250,12 @@ impl CaManager { handle: CaHandle, staging: Duration, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.process_ca_command( handle.clone(), actor, - CertAuthCommandDetails::KeyRollActivate( - staging, - self.config.clone(), - self.signer.clone(), - ) + CertAuthCommandDetails::KeyRollActivate(staging), + krill, )?; Ok(()) } diff --git a/src/server/ca/publishing.rs b/src/server/ca/publishing.rs index 79ad6d365..d74255129 100644 --- a/src/server/ca/publishing.rs +++ b/src/server/ca/publishing.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::str::FromStr; -use std::sync::Arc; use chrono::Duration; use log::debug; use rpki::{rrdp, uri}; @@ -25,10 +24,10 @@ use crate::api::roa::RoaInfo; use crate::commons::KrillResult; use crate::commons::crypto::KrillSigner; use crate::commons::error::Error; -use crate::commons::eventsourcing::PreSaveEventListener; use crate::commons::storage::{Ident, KeyValueStore}; use crate::constants::CA_OBJECTS_NS; use crate::config::IssuanceTimingConfig; +use crate::server::runtime::KrillRuntime; use super::aspa::{AspaInfo, AspaObjectsUpdates}; use super::bgpsec::{BgpSecCertInfo, BgpSecCertificateUpdates}; use super::certauth::CertAuth; @@ -64,9 +63,6 @@ pub struct CaObjectsStore { /// The key-value store where objects are stored. store: KeyValueStore, - /// The signer used when generate objects. - signer: Arc, - /// Configuration for timing of object creation. issuance_timing: IssuanceTimingConfig, } @@ -76,23 +72,20 @@ impl CaObjectsStore { pub fn create( storage_uri: &Url, issuance_timing: IssuanceTimingConfig, - signer: Arc, ) -> KrillResult { let store = KeyValueStore::create(storage_uri, CA_OBJECTS_NS)?; Ok(CaObjectsStore { store, - signer, issuance_timing, }) } -} -/// React to any events on a CA that cause the set of object to change. -impl PreSaveEventListener for CaObjectsStore { - fn listen( + /// React to any events on a CA that cause the set of object to change. + pub(super) fn cert_auth_pre_save_events( &self, ca: &CertAuth, events: &[CertAuthEvent], + krill: &KrillRuntime, ) -> KrillResult<()> { // Note that the `CertAuth` which is passed in has already been // updated with the state changes contained in the event. @@ -141,7 +134,7 @@ impl PreSaveEventListener for CaObjectsStore { resource_class_name, current_key, &self.issuance_timing, - &self.signer, + krill.signer(), )?; } CertAuthEvent::KeyPendingToNew { @@ -152,7 +145,7 @@ impl PreSaveEventListener for CaObjectsStore { resource_class_name, new_key, &self.issuance_timing, - &self.signer, + krill.signer(), )?; } CertAuthEvent::KeyRollActivated { @@ -199,7 +192,7 @@ impl PreSaveEventListener for CaObjectsStore { } } objects.re_issue( - force_reissue, &self.issuance_timing, &self.signer + force_reissue, &self.issuance_timing, krill.signer() )?; Ok(()) }) @@ -298,13 +291,14 @@ impl CaObjectsStore { &self, force: bool, ca_handle: &CaHandle, + krill: &KrillRuntime, ) -> KrillResult { debug!("Re-issue for CA {ca_handle} using force: {force}"); self.with_ca_objects(ca_handle, |objects| { objects.re_issue( force, &self.issuance_timing, - &self.signer, + krill.signer(), ) }) } @@ -1331,9 +1325,9 @@ impl KeyObjectSet { // unsuspended, so this does nothing anymore except for migrations. for cert in &cert_updates.unsuspended { let published_object = PublishedObject::for_cert_info(cert); - self - .published_objects - .insert(cert.name.clone(), published_object); + self.published_objects.insert( + cert.name.clone(), published_object + ); } for suspended in &cert_updates.suspended { diff --git a/src/server/ca/upgrades/data_migration.rs b/src/server/ca/upgrades/data_migration.rs index 6063f2f61..1630ae0f8 100644 --- a/src/server/ca/upgrades/data_migration.rs +++ b/src/server/ca/upgrades/data_migration.rs @@ -1,6 +1,4 @@ -use std::sync::Arc; use log::{debug, warn}; -use crate::commons::crypto::KrillSignerBuilder; use crate::constants::CASERVER_NS; use crate::server::ca::certauth::CertAuth; use crate::server::ca::publishing::CaObjectsStore; @@ -12,25 +10,9 @@ use crate::upgrades::data_migration::check_agg_store; pub fn check_ca_objects(config: &Config) -> UpgradeResult<()> { let ca_store = check_agg_store::(config, CASERVER_NS, "CAs")?; - // make a dummy Signer to use for the CaObjectsStore - it won't be used, - // but it's needed for construction. - let probe_interval = - std::time::Duration::from_secs(config.signer_probe_retry_seconds); - let signer = Arc::new( - KrillSignerBuilder::new( - &config.storage_uri, - probe_interval, - &config.signers, - ) - .with_default_signer(config.default_signer()) - .with_one_off_signer(config.one_off_signer()) - .build()?, - ); - let ca_objects_store = CaObjectsStore::create( &config.storage_uri, - config.issuance_timing.clone(), - signer, + config.issuance_timing, )?; let cas_with_objects = ca_objects_store.cas()?; diff --git a/src/server/ca/upgrades/pre_0_14_0/migration.rs b/src/server/ca/upgrades/pre_0_14_0/migration.rs index 6edc4a244..37927dc82 100644 --- a/src/server/ca/upgrades/pre_0_14_0/migration.rs +++ b/src/server/ca/upgrades/pre_0_14_0/migration.rs @@ -200,7 +200,7 @@ impl UpgradeAggregateStorePre0_14 for CasMigration { // actually migrated, then return // CommandMigrationEffect::Nothing if - let Some(events) = new_command.events() + let Some(events) = new_command.events() && events.is_empty() { return Ok(CommandMigrationEffect::Nothing); diff --git a/src/server/manager.rs b/src/server/manager.rs index 24b6d5e85..f566925a0 100644 --- a/src/server/manager.rs +++ b/src/server/manager.rs @@ -1,1278 +1,1459 @@ -//! An RPKI publication protocol server. +//! The public part of the Krill RPKI server. +//! + +use std::{error, fmt}; use std::collections::HashMap; use std::path::PathBuf; use std::str::FromStr; -use std::sync::Arc; use bytes::Bytes; -use clap::crate_version; use chrono::Duration; -use futures_util::future::try_join_all; +use hyper::StatusCode; use log::info; - -use rpki::{ - ca::{ - idexchange, - idexchange::{CaHandle, ChildHandle, ParentHandle, PublisherHandle}, - }, - repository::resources::ResourceSet, - uri, -}; - -use crate::daemon::http::auth::AuthInfo; -use crate::{ - commons::{ - actor::Actor, - crypto::KrillSignerBuilder, - error::Error, - KrillEmptyResult, KrillResult, - }, - constants::*, - config::Config, - server::{ - ca::{ - self, CaManager, CaStatus, - }, - mq::{now, Task, TaskQueue}, - pubd::RepositoryManager, - scheduler::Scheduler, - }, -}; +use rpki::ca::{idexchange, publication}; +use rpki::repository::resources::ResourceSet; +use tokio::runtime::{Handle as TokioHandle}; +use tokio::sync::oneshot; use crate::api; -use crate::api::admin::{ - AddChildRequest, CertAuthInit, ParentCaContact, ParentCaReq, - PublicationServerUris, PublisherDetails, RepoFileDeleteCriteria, - RepositoryContact, UpdateChildRequest, -}; -use crate::api::aspa::{ - AspaDefinitionList, AspaDefinitionUpdates, AspaProvidersUpdate, - CustomerAsn, -}; -use crate::api::bgp::{BgpAnalysisReport, BgpAnalysisSuggestion}; -use crate::api::bgpsec::{BgpSecCsrInfoList, BgpSecDefinitionUpdates}; -use crate::api::ca::{ - CaRepoDetails, CertAuthInfo, CertAuthIssues, - CertAuthList, CertAuthStats, ChildCaInfo, ChildrenConnectionStats, - IdCertInfo, RtaList, RtaName, - RtaPrepResponse, -}; -use crate::api::history::{ - CommandDetails, CommandHistory, CommandHistoryCriteria -}; -use crate::api::import::ImportChild; -use crate::api::pubd::RepoStats; -use crate::api::roa::{ - ConfiguredRoa, RoaConfiguration, RoaConfigurationUpdates, RoaPayload, -}; -use crate::api::rta::{ - ResourceTaggedAttestation, RtaContentRequest, RtaPrepareRequest, -}; -use crate::api::ta::{ - ApiTrustAnchorSignedRequest, TaCertDetails, TrustAnchorSignedResponse, - TrustAnchorSignerInfo, -}; -use crate::constants::{TA_NAME, ta_handle}; -use crate::server::bgp::BgpAnalyser; - - -//------------ KrillManager --------------------------------------------------- - -/// This is the Krill server that is doing all the orchestration for all -/// components. -pub struct KrillManager { - // The base URI for this service - service_uri: uri::Https, - - // Publication server, with configured publishers - repo_manager: Arc, - - // Handles the internal TA and/or CAs - ca_manager: Arc, - - // Handles the internal TA and/or CAs - bgp_analyser: Arc, +use crate::api::status::ErrorResponse; +use crate::commons::actor::Actor; +use crate::commons::error::KrillError; +use crate::commons::eventsourcing::AggregateStoreError; +use crate::config::Config; +use crate::constants::{TA_NAME, ta_handle, testbed_ca_handle}; +use crate::server::ca::CaStatus; +use super::scheduler; +use super::mq::{Task, now}; +use super::runtime::{KrillRuntime, SpawnError, ThreadPool, ThreadPoolHandle}; + + +//------------ StartupManager ------------------------------------------------ + +/// The Krill manager during the start-up phase. +/// +/// This manager provides functionality that is only available before the +/// HTTP server is started and we are still running sync in a single thread. +pub struct StartupManager { + runtime: KrillRuntime, + thread_pool: ThreadPool, +} - // Shared message queue - mq: Arc, +impl StartupManager { + /// Creates a new manager from the provided config. + /// + /// While the Tokio runtime provided isn’t used, we need the handle + /// already to pass it to the Krill runtime. + pub fn new( + config: Config, tokio: TokioHandle + ) -> Result { + Ok(Self { + thread_pool: ThreadPool::new(&config)?, + runtime: KrillRuntime::new(config, tokio)?, + }) + } + + /// Promotes the startup manager into a full Krill manager. + pub fn promote( + self + ) -> Result<(KrillManager, ThreadPool), KrillError> { + Ok(( + KrillManager { + krill_runtime: self.runtime, + thread_pool: self.thread_pool.handle(), + }, + self.thread_pool, + )) + } - // System actor - system_actor: Actor, + /// Starts the scheduler in a separate thread. + pub fn run_scheduler(&mut self) -> Result<(), KrillError> { + // When multi-node set ups with a shared queue are + // supported then we can no longer safely reschedule + // ALL running tests. See issue: #1112 + self.runtime.tasks().reschedule_tasks_at_startup()?; + self.runtime.tasks().schedule(Task::QueueStartTasks, now())?; - pub config: Arc, -} + let krill = self.runtime.clone(); + self.thread_pool.spawn(|rx| scheduler::run(krill, rx)); + Ok(()) + } -/// # Set up and initialization -impl KrillManager { - /// Creates a new publication server. Note that state is preserved - /// in the data storage. - pub async fn build(config: Arc) -> KrillResult { - let service_uri = config.service_uri(); - - info!("Starting {} v{}", KRILL_SERVER_APP, crate_version!()); - info!("{KRILL_SERVER_APP} uses service uri: {service_uri}"); - - // Assumes that Config::verify() has already ensured that the signer - // configuration is valid and that Config::resolve() has been - // used to update signer name references to resolve to the - // corresponding signer configurations. - let probe_interval = - std::time::Duration::from_secs(config.signer_probe_retry_seconds); - let signer = KrillSignerBuilder::new( - &config.storage_uri, - probe_interval, - &config.signers, + /// Re-issue ROA objects so that they will use short subjects. + /// + /// See issue #700. + pub fn force_renew_roas(&self) -> Result<(), KrillError> { + self.runtime.ca_manager().force_renew_roas_all( + self.runtime.system_actor(), &self.runtime ) - .with_default_signer(config.default_signer()) - .with_one_off_signer(config.one_off_signer()) - .build()?; - let signer = Arc::new(signer); - - let system_actor = ACTOR_DEF_KRILL; - - // Task queue Arc is shared between ca_manager, repo_manager and the - // scheduler. - let mq = Arc::new(TaskQueue::new(&config.storage_uri)?); - - // for now, support that existing embedded repositories are still - // supported. this should be removed in future after people - // have had a chance to separate. - let repo_manager = Arc::new(RepositoryManager::build( - config.clone(), - mq.clone(), - signer.clone(), - )?); - - let ca_manager = Arc::new( - ca::CaManager::build( - config.clone(), - mq.clone(), - signer, - system_actor.clone(), - ) - .await?, - ); + } - let bgp_analyser = Arc::new(BgpAnalyser::new(&config)); + /// Updates the APSA definitions of a CA. + pub fn ca_aspas_definitions_update( + &self, + ca: idexchange::CaHandle, + updates: api::aspa::AspaDefinitionUpdates, + ) -> Result<(), KrillError> { + self.runtime.ca_manager().ca_aspas_definitions_update( + ca, updates, self.runtime.system_actor(), &self.runtime + ) + } - // When multi-node set ups with a shared queue are - // supported then we can no longer safely reschedule - // ALL running tests. See issue: #1112 - mq.reschedule_tasks_at_startup()?; - - mq.schedule(Task::QueueStartTasks, now())?; - - let server = KrillManager { - service_uri, - repo_manager, - ca_manager, - bgp_analyser, - mq, - system_actor, - config: config.clone(), + pub fn prepare_testbed(&self) -> Result<(), KrillError> { + let Some(testbed) = self.runtime.config().testbed() else { + return Ok(()); }; - // Check if we need to do any testbed or benchmarking set up. let testbed_handle = testbed_ca_handle(); - if let Some(testbed) = config.testbed() { - if server.ca_manager.has_ca(&testbed_handle)? { - if config.benchmark.is_some() { - info!("Resuming BENCHMARK mode - will NOT recreate CAs. If you wanted this, then wipe the data dir and restart."); - } else { - info!("Resuming TESTBED mode - ONLY USE THIS FOR TESTING AND TRAINING!"); - } + if self.runtime.ca_manager().has_ca(&testbed_handle)? { + if self.runtime.config().benchmark.is_some() { + info!( + "Resuming BENCHMARK mode - will NOT recreate CAs. If \ + you want to recreate CAs, please wipe the data dir \ + and restart." + ); } else { - // Will do some set up. Both TESTBED and BENCHMARK (which - // implies TESTBED and adds to it) will need a - // testbed ca to be set up first. We will re-use the import - // functionality to do all this. - let testbed_ca = api::import::ImportCa { - handle: testbed_handle, - parents: vec![api::import::ImportParent { - handle: ta_handle().into_converted(), - resources: ResourceSet::all(), - }], - roas: vec![], - }; - - let mut import_cas = vec![testbed_ca]; - - match config.benchmark.as_ref() { - None => { - info!("Enabling TESTBED mode - ONLY USE THIS FOR TESTING AND TRAINING!"); - } - Some(benchmark) => { - info!( - "Enabling BENCHMARK mode with {} CAs with {} ROas each - ONLY USE THIS FOR TESTING!", - benchmark.cas, benchmark.ca_roas - ); - - let testbed_parent: ParentHandle = - testbed_ca_handle().into_converted(); - for nr in 0..benchmark.cas { - let handle = CaHandle::new( - format!("benchmark-{nr}").into(), - ); - - // derive resources for benchmark ca - let byte_2_ipv4 = nr / 256; - let byte_3_ipv4 = nr % 256; - - let prefix_str = format!( - "10.{byte_2_ipv4}.{byte_3_ipv4}.0/24" - ); - let resources = - ResourceSet::from_strs("", &prefix_str, "") - .map_err(|e| { - Error::ResourceSetError(format!( - "cannot parse resources: {e}" - )) - })?; - - // Create ROA configs - let mut roas: Vec = vec![]; - let asn_range_start = 64512; - for asn in asn_range_start - ..asn_range_start + benchmark.ca_roas - { - let payload = RoaPayload::from_str(&format!( - "{prefix_str} => {asn}" - )) - .unwrap(); - roas.push(payload.into()); - } - - import_cas.push(api::import::ImportCa { - handle, - parents: vec![api::import::ImportParent { - handle: testbed_parent.clone(), - resources, - }], - roas, - }) - } - } - } + info!( + "Resuming TESTBED mode - ONLY USE THIS FOR TESTING \n + AND TRAINING!" + ); + } + return Ok(()); + } + + // Will do some set up. Both TESTBED and BENCHMARK (which + // implies TESTBED and adds to it) will need a + // testbed ca to be set up first. We will re-use the import + // functionality to do all this. + let testbed_ca = api::import::ImportCa { + handle: testbed_handle, + parents: vec![api::import::ImportParent { + handle: ta_handle().into_converted(), + resources: ResourceSet::all(), + }], + roas: vec![], + }; + + let mut import_cas = vec![testbed_ca]; - let startup_structure = api::import::Structure::for_testbed( - testbed.ta_aia().clone(), - testbed.ta_uri().clone(), - testbed.publication_server_uris(), - import_cas, + match self.runtime.config().benchmark.as_ref() { + None => { + info!( + "Enabling TESTBED mode - ONLY USE THIS FOR TESTING \ + AND TRAINING!" ); - server.cas_import(startup_structure).await?; + } + Some(benchmark) => { + info!( + "Enabling BENCHMARK mode with {} CAs with {} ROas \ + each - ONLY USE THIS FOR TESTING!", + benchmark.cas, benchmark.ca_roas + ); + + let testbed_parent: idexchange::ParentHandle = + testbed_ca_handle().into_converted(); + for nr in 0..benchmark.cas { + let handle = idexchange::CaHandle::new( + format!("benchmark-{nr}").into(), + ); + + // derive resources for benchmark ca + let byte_2_ipv4 = nr / 256; + let byte_3_ipv4 = nr % 256; + + let prefix_str = format!( + "10.{byte_2_ipv4}.{byte_3_ipv4}.0/24" + ); + let resources = + ResourceSet::from_strs("", &prefix_str, "") + .map_err(|e| { + KrillError::ResourceSetError(format!( + "cannot parse resources: {e}" + )) + })?; + + // Create ROA configs + let mut roas: Vec = vec![]; + let asn_range_start = 64512; + for asn in + asn_range_start..asn_range_start + benchmark.ca_roas + { + let payload = api::roa::RoaPayload::from_str( + &format!("{prefix_str} => {asn}") + ).unwrap(); + roas.push(payload.into()); + } + + import_cas.push(api::import::ImportCa { + handle, + parents: vec![api::import::ImportParent { + handle: testbed_parent.clone(), + resources, + }], + roas, + }) + } } } - Ok(server) - } + let startup_structure = api::import::Structure::for_testbed( + testbed.ta_aia().clone(), + testbed.ta_uri().clone(), + testbed.publication_server_uris(), + import_cas, + ); - pub fn build_scheduler(&self) -> Scheduler { - Scheduler::build( - self.mq.clone(), - self.ca_manager.clone(), - self.repo_manager.clone(), - self.bgp_analyser.clone(), - self.config.clone(), - self.system_actor.clone(), - ) + cas_import(startup_structure, &self.runtime) } +} - pub fn service_base_uri(&self) -> &uri::Https { - &self.service_uri - } + +//------------ KrillManager -------------------------------------------------- + +pub struct KrillManager { + krill_runtime: KrillRuntime, + thread_pool: ThreadPoolHandle, } -/// # Access to components impl KrillManager { - pub fn system_actor(&self) -> &Actor { - &self.system_actor + /// Returns a reference to the config. + pub fn config(&self) -> &Config { + self.krill_runtime.config() } - pub fn testbed_enabled(&self) -> bool { - self.ca_manager.testbed_enabled() + /// Returns the system actor. + pub fn system_actor(&self) -> &Actor { + self.krill_runtime.system_actor() } +} - /// Converts the RRDP path portion of a HTTP request URI to a path. + +/// # Low-level flow control +/// +/// The two methods in this section are hidden from users by the public +/// methods. +impl KrillManager { + /// Runs a sync closure which provides an immediate result. /// - /// The `path` should contain everything after the `/rrdp/` portion of - /// the URI’s path. If the path is in principle valid, i.e., could - /// represent an RRDP resource generated by this RRDP sever, the method - /// will return a file system path representing this path. This does not - /// mean there will actually be a file there. The file may have been - /// deleted or may have never existed at all. This is necessary since - /// the RRDP server doesn’t track past files, only the currently valid - /// set of resources. + /// The closure `op` is run on the sync runtime. It has access to the + /// [`KrillManager`] via its sole argument. Whatever the closure returns + /// is what this async method resolves into. /// - /// If the path is definitely not valid, returns `Ok(None)`. This should - /// probably be translated into a 404 Not Found response. - pub fn resolve_rrdp_request_path( - &self, path: &str - ) -> KrillResult> { - self.repo_manager.resolve_rrdp_request_path(path) + /// If, for whatever reason, the closure does not run to completion, + /// an error is returned. + async fn run( + &self, op: F + ) -> Result + where + F: FnOnce(&KrillRuntime) -> Result + Send + 'static, + T: Send + 'static, + { + let (tx, rx) = oneshot::channel(); + let runtime = self.krill_runtime.clone(); + self.thread_pool.spawn(move || { + let _ = tx.send(op(&runtime)); + }).await?; + rx.await? } } -/// # Configure publishers -impl KrillManager { - /// Returns the repository server stats - pub fn repo_stats(&self) -> KrillResult { - self.repo_manager.repo_stats() - } - /// Returns all current publishers. - pub fn publishers(&self) -> KrillResult> { - self.repo_manager.publishers() +/// # Managing all CAs +/// +impl KrillManager { + /// Returns the handles of all CAs. + pub async fn ca_handles( + &self + ) -> Result, RunError> { + self.run(|runtime| Ok(runtime.ca_manager().ca_handles()?)).await + } + + /// Triggers republising of all CAs that need it. + pub async fn republish_all(&self, force: bool) -> Result<(), RunError> { + self.run(move |runtime| -> Result<_, RunError> { + let cas = runtime.ca_manager().republish_all(force, runtime)?; + for ca in cas { + runtime.ca_manager().cas_schedule_repo_sync(ca, runtime)?; + } + Ok(()) + }).await } - /// Adds the publishers, blows up if it already existed. - pub fn add_publisher( - &self, - req: idexchange::PublisherRequest, - actor: &Actor, - ) -> KrillResult { - let publisher_handle = req.publisher_handle().clone(); - self.repo_manager.create_publisher(req, actor)?; - self.repository_response(&publisher_handle) + /// Triggers all CAs to re-sync with their repositories + pub async fn cas_repo_sync_all(&self) -> Result<(), RunError> { + self.run(|runtime| { + Ok(runtime.ca_manager().cas_schedule_repo_sync_all(runtime)?) + }).await } - /// Removes a publisher, blows up if it didn't exist. - pub fn remove_publisher( - &self, - publisher: PublisherHandle, - actor: &Actor, - ) -> KrillEmptyResult { - self.repo_manager.remove_publisher(publisher, actor) + /// Triggers all CAs to re-sync with their parent CAs. + pub async fn cas_refresh_all(&self) -> Result<(), RunError> { + self.run(|runtime| { + Ok(runtime.ca_manager().cas_schedule_refresh_all(runtime)?) + }).await } - /// Removes a publisher, blows up if it didn't exist. - pub fn delete_matching_files( - &self, - criteria: RepoFileDeleteCriteria, - ) -> KrillEmptyResult { - self.repo_manager.delete_matching_files(criteria) + /// Schedules a check to suspend children for all CAs + pub async fn cas_schedule_suspend_all(&self) -> Result<(), RunError> { + self.run(|runtime| { + Ok(runtime.ca_manager().cas_schedule_suspend_all(runtime)?) + }).await } - /// Returns a publisher. - pub fn get_publisher( + /// Returns statistics for all CAs. + pub async fn cas_stats( &self, - publisher: PublisherHandle, - ) -> KrillResult { - self.repo_manager.get_publisher_details(publisher) - } + ) -> Result< + HashMap, + RunError + > { + self.run(|runtime| { + let mut res = HashMap::new(); + + for handle in runtime.ca_manager().ca_handles()? { + // can't fail really, but to be sure + if let Ok(ca) = runtime.ca_manager().get_ca(&handle) { + let roas = ca.configured_roas(); + let roa_count = roas.len(); + let child_count = ca.children().count(); + + let bgp_report = if ca.handle().as_str() == "ta" + || ca.handle().as_str() == "testbed" + { + api::bgp::BgpAnalysisReport::new(vec![]) + } + else { + runtime.bgp_analyser().analyse( + roas.as_slice(), &ca.all_resources(), None + ) + }; + + res.insert( + ca.handle().clone(), + api::ca::CertAuthStats { + roa_count, + child_count, + bgp_stats: bgp_report.into(), + }, + ); + } + } - pub fn rrdp_base_path(&self) -> PathBuf { - let mut path = self.config.repo_dir().to_path_buf(); - path.push("rrdp"); - path.to_path_buf() + Ok(res) + }).await } -} -/// # Manage RFC8181 clients -impl KrillManager { - pub fn repository_response( + /// Returns the parent status for the given CA. + pub async fn cas_status_map( &self, - publisher: &PublisherHandle, - ) -> KrillResult { - self.repo_manager.repository_response(publisher) + ) -> Result, RunError> { + self.run(|runtime| { + let mut res = HashMap::new(); + + for handle in runtime.ca_manager().ca_handles()? { + if let Ok(ca_status) = runtime.ca_manager().get_ca_status( + &handle + ) { + res.insert(handle, ca_status); + } + } + + Ok(res) + }).await } - pub fn rfc8181( + pub async fn cas_import( &self, - publisher: PublisherHandle, - msg_bytes: Bytes, - ) -> KrillResult { - self.repo_manager.rfc8181(publisher, msg_bytes) + structure: api::import::Structure, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(cas_import(structure, runtime)?) + }).await } } -/// # TA Support -impl KrillManager { - pub fn ta_proxy_enabled(&self) -> bool { - self.config.ta_proxy_enabled() - } - pub fn ta_proxy_init(&self) -> KrillResult<()> { - self.ca_manager.ta_proxy_init() +/// # Managing a single CA +/// +impl KrillManager { + /// Initialises a new CA. + pub async fn ca_init( + &self, init: api::admin::CertAuthInit + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().init_ca(init.handle, runtime)?) + }).await + } + + /// Returns the public information for a CA. + pub async fn ca_info( + &self, ca: idexchange::CaHandle + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().get_ca(&ca).map(|ca| ca.as_ca_info())?) + }).await + } + + /// Creates a new identity certificate for the CA. + pub async fn ca_update_id( + &self, ca: idexchange::CaHandle, actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_update_id(ca, &actor, runtime)?) + }).await + } + + /// Initiates a key roll for the given CA. + pub async fn ca_keyroll_init( + &self, ca: idexchange::CaHandle, actor: Actor + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_keyroll_init( + ca, Duration::seconds(0), &actor, runtime + )?) + }).await + } + + /// Activates an initiated key roll. + pub async fn ca_keyroll_activate( + &self, ca: idexchange::CaHandle, actor: Actor + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_keyroll_activate( + ca, Duration::seconds(0), &actor, runtime + )?) + }).await + } + + /// Returns the publisher request for a CA. + pub async fn ca_publisher_req( + &self, + ca: idexchange::CaHandle, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().get_ca(&ca)?.publisher_request()) + }).await } - pub fn ta_proxy_id(&self) -> KrillResult { - self.ca_manager.ta_proxy_id() + /// Return informatiuon about the configured repository for a given CA. + pub async fn ca_repo_details( + &self, ca_handle: idexchange::CaHandle + ) -> Result { + self.run(move |runtime| { + let ca = runtime.ca_manager().get_ca(&ca_handle)?; + let contact = ca.repository_contact()?; + Ok(api::ca::CaRepoDetails { contact: contact.clone() }) + }).await } - pub fn ta_proxy_publisher_request( + /// Updates the repository for a CA. + pub async fn ca_repo_update( &self, - ) -> KrillResult { - self.ca_manager.ta_proxy_publisher_request() - } - - pub fn ta_proxy_repository_update( + ca: idexchange::CaHandle, + contact: api::admin::RepositoryContact, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().update_repo( + ca, contact, true, &actor, runtime + )?) + }).await + } + + /// Trigger re-syncing with the repository. + pub async fn ca_sync_repo( + &self, ca: idexchange::CaHandle + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().cas_schedule_repo_sync(ca, runtime)?) + }).await + } + + /// Returns the repository status for the given CA. + pub async fn ca_repo_status( + &self, ca: idexchange::CaHandle + ) -> Result { + self.run(move |runtime| -> Result<_, RunError> { + Ok(runtime.ca_manager().get_ca_status(&ca)?.into_repo()) + }).await + } + + /// Returns the parent status for the given CA. + pub async fn ca_parent_status( + &self, ca: idexchange::CaHandle + ) -> Result { + self.run(move |runtime| -> Result<_, RunError> { + Ok(runtime.ca_manager().get_ca_status(&ca)?.into_parents()) + }).await + } + + /// Triggers re-syncing with the parent CAs. + pub async fn cas_refresh_single( + &self, ca_handle: idexchange::CaHandle + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().cas_schedule_refresh_single( + ca_handle, runtime + )?) + }).await + } + + pub async fn ca_issues( &self, - contact: RepositoryContact, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager - .ta_proxy_repository_update(contact, actor) + ca: idexchange::CaHandle, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().get_ca_issues(&ca)?) + }).await } - pub fn ta_proxy_repository_contact( + /// Returns the history of a CA. + pub async fn ca_history( &self, - ) -> KrillResult { - self.ca_manager.ta_proxy_repository_contact() + ca: idexchange::CaHandle, + crit: api::history::CommandHistoryCriteria, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_history(&ca, crit)?) + }).await } - pub fn ta_proxy_signer_add( + /// Returns the details for the given CA command. + pub async fn ca_command_details( &self, - info: TrustAnchorSignerInfo, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager.ta_proxy_signer_add(info, actor) + ca: idexchange::CaHandle, + version: u64, + ) -> Result, RunError> { + self.run(move |runtime| { + match runtime.ca_manager().ca_command_details(&ca, version) { + Ok(res) => Ok(Some(res)), + Err(KrillError::AggregateStoreError( + AggregateStoreError::UnknownCommand(..) + )) => Ok(None), + Err(err) => Err(err.into()), + } + }).await } - pub fn ta_proxy_signer_update( + /// Deletes a CA. + /// + /// A best effort to send revocation requests and withdraw all objects + /// is done first. Note that any children of this CA will be left + /// orphaned, and they will only learn of this sad fact when they choose + /// to call home. + pub async fn ca_delete( &self, - info: TrustAnchorSignerInfo, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager.ta_proxy_signer_update(info, actor) + ca: idexchange::CaHandle, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().delete_ca(&ca, &actor, runtime)?) + }).await } +} - pub fn ta_proxy_signer_make_request( - &self, - actor: &Actor, - ) -> KrillResult { - self.ca_manager.ta_proxy_signer_make_request(actor) - } - pub fn ta_proxy_signer_get_request( - &self, - ) -> KrillResult { - self.ca_manager.ta_proxy_signer_get_request() +/// # Managing parent CAs +/// +impl KrillManager { + /// Returns the child request. + /// + /// This request is passed to a potential parent CA to register this CA. + pub async fn ca_child_req( + &self, ca: idexchange::CaHandle + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().get_ca(&ca)?.child_request()) + }).await } - pub fn ta_proxy_signer_process_response( + /// Updates a parent contact for a CA + pub async fn ca_parent_add_or_update( &self, - response: TrustAnchorSignedResponse, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager - .ta_proxy_signer_process_response(response, actor) - } + ca: idexchange::CaHandle, + parent_req: api::admin::ParentCaReq, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + // Verify that we can get entitlements from the new parent before + // adding/updating it. + let contact = + api::admin::ParentCaContact::try_from_rfc8183_parent_response( + parent_req.response.clone(), + ) + .map_err(|e| { + KrillError::CaParentResponseInvalid(ca.clone(), e.to_string()) + })?; + runtime.ca_manager().get_entitlements_from_contact( + &ca, &parent_req.handle, &contact, false, runtime, + )?; - pub fn ta_proxy_children_add( - &self, - child_request: AddChildRequest, - actor: &Actor, - ) -> KrillResult { - // TA as parent is handled a special case in the following - self.ca_manager.ca_add_child( - &ta_handle().convert(), - child_request, - &self.config.service_uri(), - actor, - ) + // Seems good. Add/update the parent. + Ok(runtime.ca_manager().ca_parent_add_or_update( + ca, parent_req, &actor, runtime + )?) + }).await } - pub fn ta_cert_details(&self) -> KrillResult { - let proxy = self.ca_manager.get_trust_anchor_proxy()?; - Ok(proxy.get_ta_details()?.clone()) + pub async fn ca_parent_remove( + &self, + handle: idexchange::CaHandle, + parent: idexchange::ParentHandle, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_parent_remove( + handle, parent, &actor, runtime + )?) + }).await + } + + /// Returns the parent contact for a CA’s parent. + pub async fn ca_parent_contact( + &self, + ca: idexchange::CaHandle, + parent: idexchange::ParentHandle, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().get_ca(&ca)?.parent(&parent)?.clone()) + }).await } } -/// # Being a parent + +/// # Managing child CAs +/// impl KrillManager { - /// Adds a child to a CA and returns the ParentCaInfo that the child - /// will need to contact this CA for resource requests. - pub fn ca_add_child( + /// Adds a child to a CA. + /// + /// Returns the parent response that the child will need to contact this + /// CA for resource requests. + pub async fn ca_add_child( &self, - ca: &CaHandle, - req: AddChildRequest, - actor: &Actor, - ) -> KrillResult { - self.ca_manager.ca_add_child(ca, req, &self.service_uri, actor) + ca: idexchange::CaHandle, + req: api::admin::AddChildRequest, + actor: Actor + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_add_child(&ca, req, &actor, runtime)?) + }).await } - /// Shows the parent contact for a child. - pub async fn ca_parent_contact( + /// Return the parent response for a child CA. + pub async fn ca_parent_response( &self, - ca: &CaHandle, - child: ChildHandle, - ) -> KrillResult { - self.ca_manager.ca_parent_contact(ca, child, &self.service_uri) - } - - /// Shows the parent contact for a child. - pub fn ca_parent_response( + ca: idexchange::CaHandle, + child: idexchange::ChildHandle, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_parent_response( + &ca, child, runtime.service_uri() + )?) + }).await + } + + /// Updates the identity certificate or resources of a child CA. + pub async fn ca_child_update( &self, - ca: &CaHandle, - child: ChildHandle, - ) -> KrillResult { - self.ca_manager.ca_parent_response(ca, child, &self.service_uri) - } - - /// Update IdCert or resources of a child. - pub fn ca_child_update( + ca: idexchange::CaHandle, + child: idexchange::ChildHandle, + req: api::admin::UpdateChildRequest, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_child_update( + &ca, child, req, &actor, runtime + )?) + }).await + } + + /// Removes a child CA. + pub async fn ca_child_remove( &self, - ca: &CaHandle, - child: ChildHandle, - req: UpdateChildRequest, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager.ca_child_update(ca, child, req, actor) - } - - /// Update IdCert or resources of a child. - pub fn ca_child_remove( + ca: idexchange::CaHandle, + child: idexchange::ChildHandle, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_child_remove( + &ca, child, &actor, runtime + )?) + }).await + } + + /// Returns details for a child CA. + pub async fn ca_child_show( &self, - ca: &CaHandle, - child: ChildHandle, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager.ca_child_remove(ca, child, actor) + ca: idexchange::CaHandle, + child: idexchange::ChildHandle, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_show_child(&ca, &child)?) + }).await } - /// Show details for a child under the CA. - pub fn ca_child_show( + /// Exports a child CA. + pub async fn ca_child_export( &self, - ca: &CaHandle, - child: &ChildHandle, - ) -> KrillResult { - self.ca_manager.ca_show_child(ca, child) + ca: idexchange::CaHandle, + child: idexchange::ChildHandle, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_child_export(&ca, &child)?) + }).await } - /// Export a child under the CA. - pub fn ca_child_export( + /// Imports a child CA. + pub async fn ca_child_import( &self, - ca: &CaHandle, - child: &ChildHandle, - ) -> KrillResult { - self.ca_manager.ca_child_export(ca, child) - } - - /// Import a child under the CA. - pub fn ca_child_import( + ca: idexchange::CaHandle, + child: api::import::ImportChild, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_child_import( + &ca, child, &actor, runtime + )?) + }).await + } + + /// Returns child CA statistics. + pub async fn ca_stats_child_connections( &self, - ca: &CaHandle, - child: ImportChild, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager.ca_child_import(ca, child, actor) + ca: idexchange::CaHandle, + ) -> Result { + self.run(move |runtime| { + Ok( + runtime.ca_manager().get_ca_status( + &ca + )?.get_children_connection_stats() + ) + }).await } - /// Show children stats under the CA. - pub fn ca_stats_child_connections( + /// Handles a synchronization request by a child CA. + pub async fn rfc6492( &self, - ca: &CaHandle, - ) -> KrillResult { - self.ca_manager - .get_ca_status(ca) - .map(|status| status.get_children_connection_stats()) + ca: idexchange::CaHandle, + msg_bytes: Bytes, + user_agent: Option, + actor: Actor, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().rfc6492( + &ca, msg_bytes, user_agent, &actor, runtime + )?) + }).await } } -/// # Being a child +/// # Managing ASPAs +/// impl KrillManager { - /// Returns the child request for a CA, or NONE if the CA cannot be found. - pub fn ca_child_req( + /// Returns the current ASPA definitions for a CA. + pub async fn ca_aspas_definitions_show( &self, - ca: &CaHandle, - ) -> KrillResult { - self.ca_manager - .get_ca(ca) - .map(|ca| ca.child_request()) + ca: idexchange::CaHandle, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_aspas_definitions_show(&ca)?) + }).await } - /// Updates a parent contact for a CA - pub async fn ca_parent_add_or_update( + /// Updates the APSA definitions of a CA. + pub async fn ca_aspas_definitions_update( &self, - ca: CaHandle, - parent_req: ParentCaReq, - actor: &Actor, - ) -> KrillEmptyResult { - // Verify that we can get entitlements from the new parent before - // adding/updating it. - let contact = ParentCaContact::try_from_rfc8183_parent_response( - parent_req.response.clone(), - ) - .map_err(|e| { - Error::CaParentResponseInvalid(ca.clone(), e.to_string()) - })?; - self.ca_manager.get_entitlements_from_contact( - &ca, &parent_req.handle, &contact, false - ).await?; - - // Seems good. Add/update the parent. - self.ca_manager.ca_parent_add_or_update(ca, parent_req, actor) - } - - pub async fn ca_parent_remove( + ca: idexchange::CaHandle, + updates: api::aspa::AspaDefinitionUpdates, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_aspas_definitions_update( + ca, updates, &actor, runtime + )?) + }).await + } + + /// Updates the ASPA provider set for a single customer ASN. + pub async fn ca_aspas_update_aspa( &self, - handle: CaHandle, - parent: ParentHandle, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager - .ca_parent_remove(handle, parent, actor) - .await + ca: idexchange::CaHandle, + customer: api::aspa::CustomerAsn, + update: api::aspa::AspaProvidersUpdate, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_aspas_update_aspa_providers( + ca, customer, update, &actor, runtime + )?) + }).await } } -/// # Stats and status of CAS -impl KrillManager { - pub fn cas_stats( - &self, - ) -> KrillResult> { - let mut res = HashMap::new(); - - for handle in self.ca_manager.ca_handles()? { - // can't fail really, but to be sure - if let Ok(ca) = self.ca_manager.get_ca(&handle) { - let roas = ca.configured_roas(); - let roa_count = roas.len(); - let child_count = ca.children().count(); - - let bgp_report = if ca.handle().as_str() == "ta" - || ca.handle().as_str() == "testbed" - { - BgpAnalysisReport::new(vec![]) - } - else { - self.bgp_analyser.analyse( - roas.as_slice(), &ca.all_resources(), None - ) - }; - - res.insert( - ca.handle().clone(), - CertAuthStats { - roa_count, - child_count, - bgp_stats: bgp_report.into(), - }, - ); - } - } - Ok(res) +/// # Managing BGPsec router keys +/// +impl KrillManager { + /// Lists the currently configured BGPsec router keys for a CA. + pub async fn ca_bgpsec_definitions_show( + &self, ca: idexchange::CaHandle + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_bgpsec_definitions_show(&ca)?) + }).await } - pub async fn cas_import( + /// Updates the BGPsec router key definitions for a CA. + pub async fn ca_bgpsec_definitions_update( &self, - structure: api::import::Structure, - ) -> KrillResult<()> { - let actor = Arc::new(self.system_actor().clone()); - - // We need to know which CAs already exist. They should not be - // imported again, but can serve as parents. - let mut existing_cas = HashMap::new(); - for handle in self.ca_manager.ca_handles()? { - let parent_handle = handle.convert(); - let resources = - self.ca_manager.get_ca(&handle)?.all_resources(); - existing_cas.insert(parent_handle, resources); - } - structure.validate_ca_hierarchy(existing_cas)?; - - if let Some(publication_server_uris) = - structure.publication_server.clone() - { - info!("Initialising publication server"); - self.repo_manager.init(publication_server_uris)?; - } - - if let Some(import_ta) = structure.ta.clone() { - if self.config.ta_proxy_enabled() - && self.config.ta_signer_enabled() - { - info!("Creating embedded Trust Anchor"); - self.ca_manager - .ta_init_fully_embedded( - import_ta.ta_aia, - vec![import_ta.ta_uri], - import_ta.ta_key_pem, - &self.repo_manager, - &actor, - ) - .await?; - } else { - return Err(Error::custom( - "Import TA requires ta_support_enabled = true and ta_signer_enabled = true", - )); - } - } - - info!("Bulk import {} CAs", structure.cas.len()); - // Set up each online TA child with local repo, do this in parallel. - let mut import_fns = vec![]; - let service_uri = Arc::new(self.config.service_uri()); - for ca in structure.cas { - import_fns.push(tokio::spawn(Self::import_ca( - ca, - self.ca_manager.clone(), - self.repo_manager.clone(), - service_uri.clone(), - actor.clone(), - ))); - } - try_join_all(import_fns).await.map_err(|e| { - Error::Custom(format!("Could not import CAs: {e}")) - })?; - - Ok(()) + ca: idexchange::CaHandle, + updates: api::bgpsec::BgpSecDefinitionUpdates, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_bgpsec_definitions_update( + ca, updates, &actor, runtime + )?) + }).await } +} - async fn import_ca( - import: api::import::ImportCa, - ca_manager: Arc, - repo_manager: Arc, - service_uri: Arc, - actor: Arc, - ) -> KrillEmptyResult { - // outline: - // - init ca - // - set up under repo - // - set up under parent - // - wait for resources - // - recurse for children - info!("Importing CA: '{}'", import.handle); - - // init CA - ca_manager.init_ca(import.handle.clone())?; - - // Get Publisher Request - let pub_req = { - let ca = ca_manager.get_ca(&import.handle)?; - idexchange::PublisherRequest::new( - ca.id_cert().base64.clone(), - import.handle.convert(), - None, - ) - }; - - // Add Publisher - repo_manager.create_publisher(pub_req, &actor)?; - - // Get Repository Contact for CA - let repo_contact = { - let repo_response = - repo_manager.repository_response(&import.handle.convert())?; - RepositoryContact::try_from_response(repo_response) - .map_err(Error::rfc8183)? - }; - - // Add Repository to CA - ca_manager - .update_repo( - &repo_manager, - import.handle.clone(), - repo_contact, - false, - &actor, - ) - .await?; - - for import_parent in import.parents { - // The parent should have been created. If it wasn't created yet, - // then we will need to wait for it. Note that we can - // be sure that it will be created because we verified - // that all parents are either "ta" (which is always created) or - // another CA that appeared on the list before this CA. - // - // But.. you know.. just to be safe, let's not hang in here - // forever.. - let wait_ms = 100; - let max_tries = 3000; // *100ms -> 5 mins, should be enough even on slow systems - let mut tried = 0; - let parent_as_ca: CaHandle = import_parent.handle.convert(); - - // If the parent is the TA, then there is no need to wait. - if import_parent.handle.as_str() != TA_NAME { - loop { - tried += 1; - if let Ok(parent) = ca_manager.get_ca(&parent_as_ca) - { - if parent.all_resources().contains( - &import_parent.resources - ) { - break; - } - else { - info!( - "Parent {} does not (yet) have resources for {}. Will wait a bit and try again", - parent.handle(), - import.handle - ); - } - } else { - info!( - "Parent {} for CA {} is not yet created. Will wait a bit and try again", - parent_as_ca, import.handle - ); - } - tokio::time::sleep(std::time::Duration::from_millis( - wait_ms, - )) - .await; - if tried >= max_tries { - return Err(Error::Custom(format!( - "Could not import CA {}. Parent: {} is not created", - import.handle, parent_as_ca - ))); - } - } - } - - // Add the CA as the child of parent and get the parent response - let response = { - let ca = ca_manager.get_ca(&import.handle)?; - let id_cert = - ca.child_request().validate().map_err(Error::rfc8183)?; - let child_req = AddChildRequest { - handle: import.handle.convert(), - resources: import_parent.resources, - id_cert, - }; - - ca_manager - .ca_add_child( - &import_parent.handle.convert(), - child_req, - &service_uri, - &actor, - )? - }; - - // Add the parent to the child and force sync - { - let parent_req = ParentCaReq { - handle: import_parent.handle.clone(), - response - }; - ca_manager.ca_parent_add_or_update( - import.handle.clone(), - parent_req, - &actor, - )?; - - // First sync will inform child of its entitlements and - // trigger that CSR is created. - ca_manager.ca_sync_parent( - &import.handle, 0, &import_parent.handle, &actor - ).await?; - - // Second sync will send that CSR to the parent - ca_manager.ca_sync_parent( - &import.handle, 0, &import_parent.handle, &actor - ).await?; - - // If the parent is a TA, then we will need to push a bit - // more.. Normally this should be handled by - // triggered tasks, but the task scheduler is - // not running when we do this at startup. - if import_parent.handle.as_str() == TA_NAME { - ca_manager.sync_ta_proxy_signer_if_possible()?; - ca_manager.ca_sync_parent( - &import.handle, 0, &import_parent.handle, &actor - ).await?; - } - } - } - // Add ROA definitions - let roa_updates = RoaConfigurationUpdates { - added: import.roas, - removed: vec![] - }; - ca_manager.ca_routes_update(import.handle, roa_updates, &actor)?; - - Ok(()) +/// # Managing ROAs +/// +impl KrillManager { + /// Returns the list of current ROA definitions for a CA. + pub async fn ca_routes_show( + &self, handle: idexchange::CaHandle + ) -> Result, RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().get_ca(&handle)?.configured_roas()) + }).await } - pub fn ca_issues( + /// Updates the ROA definitions of a CA. + pub async fn ca_routes_update( &self, - ca: &CaHandle, - ) -> KrillResult { - self.ca_manager.get_ca_issues(ca) - } -} - -/// # Synchronization operations for CAS -impl KrillManager { - /// Republish all CAs that need it. - pub fn republish_all(&self, force: bool) -> KrillEmptyResult { - let cas = self.ca_manager.republish_all(force)?; - for ca in cas { - self.cas_repo_sync_single(&ca)?; - } - - Ok(()) - } + ca: idexchange::CaHandle, + updates: api::roa::RoaConfigurationUpdates, + actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ca_routes_update( + ca, updates, &actor, runtime + )?) + }).await + } + + /// Produces the BGP analysis for a CA. + pub async fn ca_routes_bgp_analysis( + &self, + handle: idexchange::CaHandle, + ) -> Result { + self.run(move |runtime| { + let ca = runtime.ca_manager().get_ca(&handle)?; + let definitions = ca.configured_roas(); + let resources_held = ca.all_resources(); + Ok(runtime.bgp_analyser().analyse( + definitions.as_slice(), &resources_held, None + )) + }).await + } + + /// Performs a BGP analysis for the given changes to a CA. + pub async fn ca_routes_bgp_dry_run( + &self, + handle: idexchange::CaHandle, + mut updates: api::roa::RoaConfigurationUpdates, + ) -> Result { + self.run(move |runtime| { + let ca = runtime.ca_manager().get_ca(&handle)?; - /// Re-sync all CAs with their repositories - pub fn cas_repo_sync_all(&self) -> KrillEmptyResult { - self.ca_manager.cas_schedule_repo_sync_all() - } + updates.set_explicit_max_length(); + let resources_held = ca.all_resources(); + let limit = Some(updates.affected_prefixes()); - /// Re-sync a specific CA with its repository - pub fn cas_repo_sync_single(&self, ca: &CaHandle) -> KrillEmptyResult { - self.ca_manager.cas_schedule_repo_sync(ca.clone()) - } + let would_be_routes = ca.get_updated_authorizations(&updates)?; + let would_be_configurations = would_be_routes.roa_configurations(); + let configured_roas = + ca.configured_roas_for_configs(would_be_configurations); - /// Refresh all CAs: ask for updates and shrink as needed. - pub fn cas_refresh_all(&self) -> KrillEmptyResult { - self.ca_manager.cas_schedule_refresh_all() + Ok(runtime.bgp_analyser().analyse( + &configured_roas, &resources_held, limit + )) + }).await } - /// Refresh a specific CA with its parents - pub fn cas_refresh_single( + /// Produces suggestions for updates based on a BGP analysis. + pub async fn ca_routes_bgp_suggest( &self, - ca_handle: CaHandle, - ) -> KrillEmptyResult { - self.ca_manager.cas_schedule_refresh_single(ca_handle) - } + handle: idexchange::CaHandle, + limit: Option, + ) -> Result { + self.run(move |runtime| { + let ca = runtime.ca_manager().get_ca(&handle)?; + let configured_roas = ca.configured_roas(); + let resources_held = ca.all_resources(); - /// Schedule check suspend children for all CAs - pub fn cas_schedule_suspend_all(&self) -> KrillEmptyResult { - self.ca_manager.cas_schedule_suspend_all() + Ok(runtime.bgp_analyser().suggest( + configured_roas.as_slice(), &resources_held, limit + )) + }).await } } -/// # Admin CAS + +/// # Publication server +/// impl KrillManager { - pub fn ca_handles(&self) -> KrillResult> { - self.ca_manager.ca_handles().map(Vec::into_iter) + /// Creates the publication server. + /// + /// Fails if there is an initialized publication server already. + pub async fn repository_init( + &self, + uris: api::admin::PublicationServerUris, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.repo_manager().init(uris, runtime)?) + }).await } - pub fn ca_list(&self, auth: &AuthInfo) -> KrillResult { - self.ca_manager.ca_list(auth) + /// Clears the publication server. + /// + /// This will fail if the server still has publishers or if it hasn’t + /// been intialized yet. + pub async fn repository_clear(&self) -> Result<(), RunError> { + self.run(|runtime| { + Ok(runtime.repo_manager().repository_clear()?) + }).await } - /// Returns the public CA info for a CA, or NONE if the CA cannot be - /// found. - pub fn ca_info(&self, ca: &CaHandle) -> KrillResult { - self.ca_manager.get_ca(ca).map(|ca| ca.as_ca_info()) + /// Performs an RRDP session reset. + /// + /// This is useful after a restart of the server as we can never be + /// certain whether the previous state was the last public state seen + /// by validators, or when the server was started using a back up. + pub async fn repository_session_reset(&self) -> Result<(), RunError> { + self.run(|runtime| { + Ok(runtime.repo_manager().rrdp_session_reset()?) + }).await } - pub fn ca_status(&self, ca: &CaHandle) -> KrillResult { - self.ca_manager.get_ca_status(ca) + /// Converts the RRDP path portion of a HTTP request URI to a path. + /// + /// The `path` should contain everything after the `/rrdp/` portion of + /// the URI’s path. If the path is in principle valid, i.e., could + /// represent an RRDP resource generated by this RRDP sever, the method + /// will return a file system path representing this path. This does not + /// mean there will actually be a file there. The file may have been + /// deleted or may have never existed at all. This is necessary since + /// the RRDP server doesn’t track past files, only the currently valid + /// set of resources. + /// + /// If the path is definitely not valid, returns `Ok(None)`. This should + /// probably be translated into a 404 Not Found response. + pub async fn resolve_rrdp_request_path( + &self, path: String + ) -> Result, RunError> { + self.run(move |runtime| { + Ok(runtime.repo_manager().resolve_rrdp_request_path(&path)?) + }).await } - /// Delete a CA. Let it do best effort revocation requests and withdraw - /// all its objects first. Note that any children of this CA will be left - /// orphaned, and they will only learn of this sad fact when they choose - /// to call home. - pub async fn ca_delete( + /// Processes an RFC 8181 publisher request. + pub async fn rfc8181( &self, - ca: &CaHandle, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager - .delete_ca(self.repo_manager.as_ref(), ca, actor) - .await + publisher: idexchange::PublisherHandle, + msg_bytes: Bytes, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.repo_manager().rfc8181(publisher, msg_bytes, runtime)?) + }).await } +} - /// Returns the parent contact for a CA and parent, or NONE if either the - /// CA or the parent cannot be found. - pub fn ca_my_parent_contact( - &self, - ca: &CaHandle, - parent: &ParentHandle, - ) -> KrillResult { - let ca = self.ca_manager.get_ca(ca)?; - ca.parent(parent).cloned() +/// # Managing the publication server +/// +impl KrillManager { + /// Returns the repository server stats + pub async fn repo_stats( + &self + ) -> Result { + self.run(|runtime| { + Ok(runtime.repo_manager().repo_stats()?) + }).await + } + + /// Returns all list of the handles of all current publishers. + pub async fn publishers( + &self + ) -> Result, RunError> { + self.run(|runtime| { + Ok(runtime.repo_manager().publishers()?) + }).await + } + + /// Returns details for the publisher with the given handle. + pub async fn get_publisher( + &self, publisher: idexchange::PublisherHandle, + ) -> Result { + self.run(|runtime| { + Ok(runtime.repo_manager().get_publisher_details(publisher)?) + }).await + } + + pub async fn repository_response( + &self, publisher: idexchange::PublisherHandle, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.repo_manager().repository_response( + &publisher, runtime + )?) + }).await + } + + /// Adds a new publishers: + /// + /// This errors out if the publisher already exists. + pub async fn add_publisher( + &self, req: idexchange::PublisherRequest, actor: Actor, + ) -> Result { + self.run(move |runtime| { + let publisher_handle = req.publisher_handle().clone(); + runtime.repo_manager().create_publisher(req, &actor)?; + Ok(runtime.repo_manager().repository_response( + &publisher_handle, runtime + )?) + }).await + } + + /// Removes the publisher with the given handle. + /// + /// Returns an error if no publisher with such a handle exists. + pub async fn remove_publisher( + &self, publisher: idexchange::PublisherHandle, actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.repo_manager().remove_publisher( + publisher, &actor, runtime + )?) + }).await + } + + /// Deletes files matching the given criteria. + pub async fn delete_matching_files( + &self, criteria: api::admin::RepoFileDeleteCriteria, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.repo_manager().delete_matching_files(criteria)?) + }).await } +} - /// Returns the history for a CA. - pub fn ca_history( +/// # Managing the trust anchor +/// +impl KrillManager { + /// Initialises the trust anchor proxy. + pub async fn ta_proxy_init(&self) -> Result<(), RunError> { + self.run(|runtime| { + Ok(runtime.ca_manager().ta_proxy_init(runtime)?) + }).await + } + + /// Returns the TAL for the trust anchor. + pub async fn ta_tal( + &self + ) -> Result { + self.run(|runtime| { + let proxy = runtime.ca_manager().get_trust_anchor_proxy()?; + Ok(proxy.get_ta_details()?.tal.to_string()) + }).await + } + + /// Returns the certificate of the trust anchor. + pub async fn ta_cer( + &self + ) -> Result { + self.run(|runtime| { + let proxy = runtime.ca_manager().get_trust_anchor_proxy()?; + Ok(proxy.get_ta_details()?.cert.to_bytes()) + }).await + } + + /// Returns the trust anchor proxy ID certificate. + pub async fn ta_proxy_id(&self) -> Result { + self.run(|runtime| { + Ok(runtime.ca_manager().ta_proxy_id()?) + }).await + } + + /// Returns the trust anchor proxy publisher request. + pub async fn ta_proxy_publisher_request( &self, - ca: &CaHandle, - crit: CommandHistoryCriteria, - ) -> KrillResult { - self.ca_manager.ca_history(ca, crit) + ) -> Result { + self.run(|runtime| { + Ok(runtime.ca_manager().ta_proxy_publisher_request()?) + }).await } - pub fn ca_command_details( - &self, - ca: &CaHandle, - version: u64, - ) -> KrillResult { - self.ca_manager.ca_command_details(ca, version) + /// Updates the trust anchor repository contact. + pub async fn ta_proxy_repository_update( + &self, contact: api::admin::RepositoryContact, actor: Actor + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ta_proxy_repository_update( + contact, &actor, runtime + )?) + }).await } - /// Returns the publisher request for a CA, or NONE of the CA cannot be - /// found. - pub fn ca_publisher_req( + /// Returns the current trust anchor repository contact. + pub async fn ta_proxy_repository_contact( + &self, + ) -> Result { + self.run(|runtime| { + Ok(runtime.ca_manager().ta_proxy_repository_contact()?) + }).await + } + + /// Adds a trust anchor signer to the trust anchor proxy. + pub async fn ta_proxy_signer_add( + &self, info: api::ta::TrustAnchorSignerInfo, actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ta_proxy_signer_add( + info, &actor, runtime + )?) + }).await + } + + /// Updates the trust anchor signer connected to a trust anchor proxy. + pub async fn ta_proxy_signer_update( + &self, info: api::ta::TrustAnchorSignerInfo, actor: Actor, + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ta_proxy_signer_update( + info, &actor, runtime + )?) + }).await + } + + /// Creates a new trust anchor signer request. + /// + /// Returns an error if there is a pending request. + pub async fn ta_proxy_signer_make_request( + &self, actor: Actor, + ) -> Result { + self.run(move |runtime| { + Ok(runtime.ca_manager().ta_proxy_signer_make_request( + &actor, runtime + )?) + }).await + } + + /// Returns a currently pending trust anchor signer request. + pub async fn ta_proxy_signer_get_request( &self, - ca: &CaHandle, - ) -> KrillResult { - self.ca_manager - .get_ca(ca) - .map(|ca| ca.publisher_request()) + ) -> Result { + self.run(|runtime| { + Ok(runtime.ca_manager().ta_proxy_signer_get_request(runtime)?) + }).await } - pub fn ca_init(&self, init: CertAuthInit) -> KrillEmptyResult { - self.ca_manager.init_ca(init.handle) + /// Processes a trust anchor signer response. + pub async fn ta_proxy_signer_process_response( + &self, response: api::ta::TrustAnchorSignedResponse, actor: Actor + ) -> Result<(), RunError> { + self.run(move |runtime| { + Ok(runtime.ca_manager().ta_proxy_signer_process_response( + response, &actor, runtime + )?) + }).await } - /// Return the info about the CONFIGured repository server for a given Ca. - /// and the actual objects published there, as reported by a list reply. - pub fn ca_repo_details( + /// Adds a child CA to the trust anchor proxy. + pub async fn ta_proxy_children_add( &self, - ca_handle: &CaHandle, - ) -> KrillResult { - let ca = self.ca_manager.get_ca(ca_handle)?; - let contact = ca.repository_contact()?; - Ok(CaRepoDetails { contact: contact.clone() }) + child_request: api::admin::AddChildRequest, + actor: Actor, + ) -> Result { + self.run(move |runtime| { + // TA as parent is handled a special case in the following + Ok(runtime.ca_manager().ca_add_child( + &ta_handle().convert(), + child_request, + &actor, + runtime + )?) + }).await } +} - /// Update the repository for a CA, or return an error. (see - /// `CertAuth::repo_update`) - pub async fn ca_repo_update( - &self, - ca: CaHandle, - contact: RepositoryContact, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager - .update_repo(self.repo_manager.as_ref(), ca, contact, true, actor) - .await - } - pub fn ca_update_id( - &self, - ca: CaHandle, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager.ca_update_id(ca, actor) - } +//------------ cas_import ---------------------------------------------------- - pub fn ca_keyroll_init( - &self, - ca: CaHandle, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager.ca_keyroll_init(ca, Duration::seconds(0), actor) - } +fn cas_import( + structure: api::import::Structure, + krill: &KrillRuntime, +) -> Result<(), KrillError> { + let actor = krill.system_actor().clone(); - pub fn ca_keyroll_activate( - &self, - ca: CaHandle, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager.ca_keyroll_activate(ca, Duration::seconds(0), actor) + // We need to know which CAs already exist. They should not be + // imported again, but can serve as parents. + let mut existing_cas = HashMap::new(); + for handle in krill.ca_manager().ca_handles()? { + let parent_handle = handle.convert(); + let resources = krill.ca_manager().get_ca( + &handle + )?.all_resources(); + existing_cas.insert(parent_handle, resources); } + structure.validate_ca_hierarchy(existing_cas)?; - pub fn rfc6492( - &self, - ca: CaHandle, - msg_bytes: Bytes, - user_agent: Option, - actor: &Actor, - ) -> KrillResult { - self.ca_manager.rfc6492(&ca, msg_bytes, user_agent, actor) + if let Some(publication_server_uris) = + structure.publication_server.clone() + { + info!("Initialising publication server"); + krill.repo_manager().init(publication_server_uris, krill)?; } -} -/// # Handle ASPA requests -impl KrillManager { - pub fn ca_aspas_definitions_show( - &self, - ca: &CaHandle, - ) -> KrillResult { - self.ca_manager.ca_aspas_definitions_show(ca) + if let Some(import_ta) = structure.ta.clone() { + if krill.config().ta_proxy_enabled() + && krill.config().ta_signer_enabled() + { + info!("Creating embedded Trust Anchor"); + krill.ca_manager().ta_init_fully_embedded( + import_ta.ta_aia, + vec![import_ta.ta_uri], + import_ta.ta_key_pem, + &actor, + krill + )?; + } else { + return Err(KrillError::custom( + "Import TA requires ta_support_enabled = true \ + and ta_signer_enabled = true", + )); + } } - pub fn ca_aspas_definitions_update( - &self, - ca: CaHandle, - updates: AspaDefinitionUpdates, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager.ca_aspas_definitions_update(ca, updates, actor) - } + info!("Bulk import {} CAs", structure.cas.len()); - pub fn ca_aspas_update_aspa( - &self, - ca: CaHandle, - customer: CustomerAsn, - update: AspaProvidersUpdate, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager.ca_aspas_update_aspa_providers( - ca, customer, update, actor - ) + // XXX This used to be done in parallel. However, doing it in + // serial means we can be sure that a CA’s parents and the + // necessary resources already exist. We could potentially + // speed this up by creating sets of CAs that can be added + // in parallel, but import is rare enough that it probably + // doesn’t matter. + + for ca in structure.cas { + import_ca(ca, krill)?; } + Ok(()) } -/// # Handle BGPSec requests -impl KrillManager { - pub fn ca_bgpsec_definitions_show( - &self, - ca: &CaHandle, - ) -> KrillResult { - self.ca_manager.ca_bgpsec_definitions_show(ca) - } +fn import_ca( + import: api::import::ImportCa, + krill: &KrillRuntime, +) -> Result<(), KrillError> { + // outline: + // - init ca + // - set up under repo + // - set up under parent + // - wait for resources + // - recurse for children + info!("Importing CA: '{}'", import.handle); + + let actor = krill.system_actor(); + + // init CA + krill.ca_manager().init_ca(import.handle.clone(), krill)?; + + // Get Publisher Request + let pub_req = { + let ca = krill.ca_manager().get_ca(&import.handle)?; + idexchange::PublisherRequest::new( + ca.id_cert().base64.clone(), + import.handle.convert(), + None, + ) + }; + + // Add Publisher + krill.repo_manager().create_publisher(pub_req, actor)?; + + // Get Repository Contact for CA + let repo_contact = { + let repo_response = krill.repo_manager().repository_response( + &import.handle.convert(), krill + )?; + api::admin::RepositoryContact::try_from_response( + repo_response + ).map_err(KrillError::rfc8183)? + }; + + // Add Repository to CA + krill.ca_manager().update_repo( + import.handle.clone(), + repo_contact, + false, + actor, + krill, + )?; + + for import_parent in import.parents { + // The parent should have been created. We can be sure of that + // because we verified that all parents are either "ta" (which + // is always created) or another CA that appeared on the list + // before this CA. + let parent_as_ca: idexchange::CaHandle = + import_parent.handle.convert(); + + // If the parent is the TA, then there is no need to wait. + if import_parent.handle.as_str() != TA_NAME { + let Ok(parent) = krill.ca_manager().get_ca( + &parent_as_ca + ) else { + return Err(KrillError::Custom(format!( + "Could not import CA {}. Parent: {} is not created", + import.handle, parent_as_ca + ))) + }; - pub fn ca_bgpsec_definitions_update( - &self, - ca: CaHandle, - updates: BgpSecDefinitionUpdates, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager.ca_bgpsec_definitions_update(ca, updates, actor) - } -} + if !parent.all_resources().contains( + &import_parent.resources + ) { + return Err(KrillError::Custom(format!( + "Could not import CA {}. \ + Parent: {} does not contain all resources.", + import.handle, parent_as_ca + ))) + } + } -/// # Handle route authorization requests -impl KrillManager { - pub fn ca_routes_update( - &self, - ca: CaHandle, - updates: RoaConfigurationUpdates, - actor: &Actor, - ) -> KrillEmptyResult { - self.ca_manager.ca_routes_update(ca, updates, actor) - } + // Add the CA as the child of parent and get the parent response + let response = { + let ca = krill.ca_manager().get_ca(&import.handle)?; + let id_cert = ca.child_request().validate().map_err( + KrillError::rfc8183 + )?; + let child_req = api::admin::AddChildRequest { + handle: import.handle.convert(), + resources: import_parent.resources, + id_cert, + }; - pub fn ca_routes_show( - &self, - handle: &CaHandle, - ) -> KrillResult> { - let ca = self.ca_manager.get_ca(handle)?; + krill.ca_manager().ca_add_child( + &import_parent.handle.convert(), + child_req, + actor, + krill, + )? + }; - Ok(ca.configured_roas()) + // Add the parent to the child and force sync + { + let parent_req = api::admin::ParentCaReq { + handle: import_parent.handle.clone(), + response + }; + krill.ca_manager().ca_parent_add_or_update( + import.handle.clone(), + parent_req, + actor, + krill, + )?; + + // First sync will inform child of its entitlements and + // trigger that CSR is created. + krill.ca_manager().ca_sync_parent( + &import.handle, 0, &import_parent.handle, actor, krill, + )?; + + // Second sync will send that CSR to the parent + krill.ca_manager().ca_sync_parent( + &import.handle, 0, &import_parent.handle, actor, krill, + )?; + + // If the parent is a TA, then we will need to push a bit + // more.. Normally this should be handled by + // triggered tasks, but the task scheduler is + // not running when we do this at startup. + if import_parent.handle.as_str() == TA_NAME { + krill.ca_manager().sync_ta_proxy_signer_if_possible( + krill + )?; + krill.ca_manager().ca_sync_parent( + &import.handle, 0, &import_parent.handle, actor, + krill, + )?; + } + } } - pub fn ca_routes_bgp_analysis( - &self, - handle: &CaHandle, - ) -> KrillResult { - let ca = self.ca_manager.get_ca(handle)?; - let definitions = ca.configured_roas(); - let resources_held = ca.all_resources(); - Ok(self.bgp_analyser.analyse( - definitions.as_slice(), &resources_held, None - )) - } + // Add ROA definitions + let roa_updates = api::roa::RoaConfigurationUpdates { + added: import.roas, + removed: vec![] + }; + krill.ca_manager().ca_routes_update( + import.handle, roa_updates, actor, krill + )?; - pub fn ca_routes_bgp_dry_run( - &self, - handle: &CaHandle, - mut updates: RoaConfigurationUpdates, - ) -> KrillResult { - let ca = self.ca_manager.get_ca(handle)?; - - updates.set_explicit_max_length(); - let resources_held = ca.all_resources(); - let limit = Some(updates.affected_prefixes()); - - let would_be_routes = ca.get_updated_authorizations(&updates)?; - let would_be_configurations = would_be_routes.roa_configurations(); - let configured_roas = - ca.configured_roas_for_configs(would_be_configurations); - - Ok(self.bgp_analyser.analyse( - &configured_roas, &resources_held, limit - )) - } + Ok(()) +} - pub fn ca_routes_bgp_suggest( - &self, - handle: &CaHandle, - limit: Option, - ) -> KrillResult { - let ca = self.ca_manager.get_ca(handle)?; - let configured_roas = ca.configured_roas(); - let resources_held = ca.all_resources(); - Ok(self.bgp_analyser.suggest( - configured_roas.as_slice(), &resources_held, limit - )) - } +//------------ RunError ------------------------------------------------------ - /// Re-issue ROA objects so that they will use short subjects (see issue - /// #700) - pub async fn force_renew_roas(&self) -> KrillResult<()> { - self.ca_manager.force_renew_roas_all(self.system_actor()) - } -} +/// An error happened when running an operation. +// +// This is a separate type in preparation for refactoring error handling. For +// now, it just wraps a `KrillError`. +#[derive(Debug)] +pub struct RunError(KrillError); -/// # Handle Repository Server requests -impl KrillManager { - /// Create the publication server, will fail if it was already created. - pub fn repository_init( - &self, - uris: PublicationServerUris, - ) -> KrillResult<()> { - self.repo_manager.init(uris) +impl RunError { + pub fn status(&self) -> StatusCode { + self.0.status() } - /// Clear the publication server. Will fail if it still has publishers. Or - /// if it does not exist - pub fn repository_clear(&self) -> KrillResult<()> { - self.repo_manager.repository_clear() + pub fn to_error_response(&self) -> ErrorResponse { + self.0.to_error_response() } - /// Perform an RRDP session reset. Useful after a restart of the server as - /// we can never be certain whether the previous state was the last - /// public state seen by validators, or.. the server was started using - /// a back up. - pub fn repository_session_reset(&self) -> KrillResult<()> { - self.repo_manager.rrdp_session_reset() + pub fn to_rfc8181_error_code(&self) -> publication::ReportErrorCode { + self.0.to_rfc8181_error_code() } } -/// # Handle Resource Tagged Attestation requests -impl KrillManager { - /// List all known RTAs - pub fn rta_list(&self, ca: CaHandle) -> KrillResult { - let ca = self.ca_manager.get_ca(&ca)?; - Ok(ca.rta_list()) +impl From for RunError { + fn from(src: KrillError) -> Self { + Self(src) } +} - /// Show RTA - pub fn rta_show( - &self, - ca: CaHandle, - name: RtaName, - ) -> KrillResult { - let ca = self.ca_manager.get_ca(&ca)?; - ca.rta_show(&name) +impl From for KrillError { + fn from(src: RunError) -> Self { + src.0 } +} - /// Sign an RTA - either a new, or a prepared RTA - pub async fn rta_sign( - &self, - ca: CaHandle, - name: RtaName, - request: RtaContentRequest, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager.rta_sign(ca, name, request, actor) +impl From for RunError { + fn from(err: SpawnError) -> Self { + Self(KrillError::internal(err)) + } +} +impl From for RunError { + fn from(_: tokio::sync::oneshot::error::RecvError) -> Self { + Self(KrillError::internal("task panicked")) } +} - /// Prepare a multi - pub async fn rta_multi_prep( - &self, - ca: CaHandle, - name: RtaName, - request: RtaPrepareRequest, - actor: &Actor, - ) -> KrillResult { - self.ca_manager.rta_multi_prep(&ca, name.clone(), request, actor)?; - let ca = self.ca_manager.get_ca(&ca)?; - ca.rta_prep_response(&name) - } - - /// Co-sign an existing RTA - pub async fn rta_multi_cosign( - &self, - ca: CaHandle, - name: RtaName, - rta: ResourceTaggedAttestation, - actor: &Actor, - ) -> KrillResult<()> { - self.ca_manager.rta_multi_cosign(ca, name, rta, actor) +impl fmt::Display for RunError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) } } -// Tested through integration tests +impl error::Error for RunError { } + diff --git a/src/server/mod.rs b/src/server/mod.rs index fb9d9dbcc..7d9a1a71c 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,8 +1,48 @@ +//! The Krill server. +//! +//! This module contains all the components that implement the Krill server +//! itself, its business logic, if you will. The server is controlled via +//! the [`daemon`][super::daemon] which primarily provides the HTTP server. +//! +//! Nearly everything in the Krill server is sync, with the notable exception +//! of the HTTP client user to talk to remote parents and publication servers. +//! However, the server can be run in parallel in multiple threads. The +//! translation between the async HTTP server code and the sync Krill server +//! happens in [`daemon`][super::daemon] as well. +//! +//! Primarily, interaction with the server should happen through the types +//! in the [`manager`] module only. +//! +//! The additional modules contain the individual components of the server. +//! The main components are: +//! +//! * [`ca`]: the RPKI certification authority which collects the +//! configuration for each CA and translates it into objects. +//! * [`pubd`]: the publication server which manages the data that is +//! published by a Krill instance. +//! * [`taproxy`]: the server-side of managing a trust anchor which interacts +//! with the [`tasigner`][crate::tasigner]. +//! +//! In addition, there are a number of helper components: +//! +//! * [`bgp`]: the BGP analyser which uses RISwhois data to check ROA +//! configurations and suggests changes. +//! * [`mq`] and [`scheduler`]: a task queue which is used to schedule and +//! then execute follow-up or recurring tasks. +//! * [`properties`]: a place to store and update certain properties of a +//! Krill instance. +//! +//! All of this is tied together through the [`runtime`] module which makes +//! all components available to all other components, avoiding complicated +//! side-ways relationships. + pub mod bgp; pub mod ca; pub mod manager; pub mod mq; pub mod properties; pub mod pubd; +pub mod runtime; pub mod scheduler; pub mod taproxy; + diff --git a/src/server/mq.rs b/src/server/mq.rs index 22dcab4fb..8608bb8b0 100644 --- a/src/server/mq.rs +++ b/src/server/mq.rs @@ -12,7 +12,6 @@ use rpki::repository::x509::Time; use serde::{Deserialize, Serialize}; use url::Url; use crate::api::ca::Timestamp; -use crate::commons::eventsourcing; use crate::commons::{Error, KrillResult}; use crate::commons::eventsourcing::Aggregate; use crate::commons::queue::{Queue, ScheduleMode}; @@ -308,7 +307,6 @@ impl TaskQueue { None } Ok(Some((key, value))) => { - trace!("found task: {key}"); Some((key, value)) } } @@ -415,8 +413,20 @@ impl TaskQueue { } } -/// Implement listening for CertAuth events. -impl TaskQueue { +/// # Handling of [`CertAuth`] events. +/// +impl TaskQueue { + pub(super) fn cert_auth_pre_save_events( + &self, + ca: &CertAuth, + events: &[CertAuthEvent], + ) -> KrillResult<()> { + for event in events { + self.schedule_for_ca_event(ca, ca.version(), event)?; + } + Ok(()) + } + fn schedule_for_ca_event( &self, ca: &CertAuth, @@ -580,28 +590,14 @@ impl TaskQueue { _ => Ok(()), } } -} - -/// Implement pre-save listening for CertAuth events. -impl eventsourcing::PreSaveEventListener for TaskQueue { - fn listen( - &self, - ca: &CertAuth, - events: &[CertAuthEvent], - ) -> KrillResult<()> { - for event in events { - self.schedule_for_ca_event(ca, ca.version(), event)?; - } - Ok(()) - } -} -/// Implement post-save listening for CertAuth events. -/// -/// Used for best effort signaling to local child CAs that a sync with -/// their parent is needed. -impl eventsourcing::PostSaveEventListener for TaskQueue { - fn listen(&self, ca: &CertAuth, events: &[CertAuthEvent]) { + /// Implement post-save listening for CertAuth events. + /// + /// Used for best effort signaling to local child CAs that a sync with + /// their parent is needed. + pub(super) fn cert_auth_post_save_events( + &self, ca: &CertAuth, events: &[CertAuthEvent] + ) { for event in events { match event { CertAuthEvent::ChildUpdatedResources { child, .. } @@ -633,9 +629,10 @@ impl eventsourcing::PostSaveEventListener for TaskQueue { } } -/// Implement pre-save listening for TrustAnchorProxy events. -impl eventsourcing::PreSaveEventListener for TaskQueue { - fn listen( +/// # Handling of [`TrustAnchorProxy`] events. +/// +impl TaskQueue { + pub(super) fn ta_proxy_pre_save_events( &self, proxy: &TrustAnchorProxy, events: &[TrustAnchorProxyEvent], @@ -675,11 +672,9 @@ impl eventsourcing::PreSaveEventListener for TaskQueue { } Ok(()) } -} -/// Implement post-save listening for TrustAnchorProxy events. -impl eventsourcing::PostSaveEventListener for TaskQueue { - fn listen( + /// Implement post-save listening for TrustAnchorProxy events. + pub(super) fn ta_proxy_post_save_events( &self, _proxy: &TrustAnchorProxy, events: &[TrustAnchorProxyEvent], diff --git a/src/server/properties/mod.rs b/src/server/properties/mod.rs index dd1787d1b..27d4c3b00 100644 --- a/src/server/properties/mod.rs +++ b/src/server/properties/mod.rs @@ -218,6 +218,8 @@ impl Aggregate for Properties { type Error = Error; + type Context<'a> = (); + fn init(handle: &MyHandle, event: PropertiesInitEvent) -> Self { Properties { handle: handle.clone(), @@ -228,6 +230,7 @@ impl Aggregate for Properties { fn process_init_command( command: PropertiesInitCommand, + _context: Self::Context<'_>, ) -> Result { Ok(PropertiesInitEvent { krill_version: command.into_details().krill_version, @@ -253,6 +256,7 @@ impl Aggregate for Properties { fn process_command( &self, command: Self::Command, + _context: Self::Context<'_>, ) -> Result, Self::Error> { if log_enabled!(log::Level::Trace) { trace!( diff --git a/src/server/pubd/access.rs b/src/server/pubd/access.rs index caa632ac1..197deebbb 100644 --- a/src/server/pubd/access.rs +++ b/src/server/pubd/access.rs @@ -94,20 +94,21 @@ impl RepositoryAccessProxy { pub fn init( &self, uris: PublicationServerUris, - signer: Arc, + signer: &KrillSigner, ) -> KrillResult<()> { if self.is_initialized()? { return Err(Error::RepositoryServerAlreadyInitialized) }; let actor = ACTOR_DEF_KRILL; + let id_cert = signer.create_self_signed_id_cert()?.into(); let cmd = RepositoryAccessInitCommand::new( self.key.clone(), RepositoryAccessInitCommandDetails { rrdp_base_uri: uris.rrdp_base_uri, rsync_jail: uris.rsync_jail, - signer, + id_cert, }, &actor, ); @@ -294,6 +295,8 @@ impl Aggregate for RepositoryAccess { type InitEvent = RepositoryAccessInitEvent; type Error = Error; + type Context<'a> = (); + fn init(handle: &MyHandle, event: Self::InitEvent) -> Self { RepositoryAccess { handle: handle.clone(), @@ -307,15 +310,14 @@ impl Aggregate for RepositoryAccess { fn process_init_command( command: Self::InitCommand, + _context: Self::Context<'_>, ) -> Result { let details = command.into_details(); - let id_cert_info = details.signer.create_self_signed_id_cert()?.into(); - Ok(RepositoryAccessInitEvent { - id_cert: id_cert_info, rrdp_base_uri: details.rrdp_base_uri, rsync_jail: details.rsync_jail, + id_cert: details.id_cert, }) } @@ -341,6 +343,7 @@ impl Aggregate for RepositoryAccess { fn process_command( &self, command: Self::Command, + _context: Self::Context<'_>, ) -> Result, Self::Error> { info!( "Processing command for publisher '{}', version: {}: {}", @@ -490,8 +493,8 @@ pub struct RepositoryAccessInitCommandDetails { /// The base URI of the rsync server used by the repository. pub rsync_jail: uri::Rsync, - /// A Krill signer to use for signing. - pub signer: Arc, + /// The identity certificate of the repository. + pub id_cert: IdCertInfo, } impl InitCommandDetails for RepositoryAccessInitCommandDetails { diff --git a/src/server/pubd/content.rs b/src/server/pubd/content.rs index 1fffed833..e02a9e5c6 100644 --- a/src/server/pubd/content.rs +++ b/src/server/pubd/content.rs @@ -3,7 +3,7 @@ use std::fmt; use std::borrow::Cow; use std::path::{Path, PathBuf}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use log::{debug, info}; use rpki::uri; use rpki::ca::idexchange::{MyHandle, PublisherHandle}; @@ -39,6 +39,9 @@ pub struct RepositoryContentProxy { /// The handle for the repository content aggregate. default_handle: MyHandle, + + /// A lock for updating the repository. + update_lock: Mutex<()>, } impl RepositoryContentProxy { @@ -52,7 +55,9 @@ impl RepositoryContentProxy { let default_handle = MyHandle::new("0".into()); Ok(RepositoryContentProxy { - store, default_handle, + store, + default_handle, + update_lock: Mutex::new(()), }) } @@ -217,7 +222,31 @@ impl RepositoryContentProxy { &self, rrdp_updates_config: RrdpUpdatesConfig, ) -> KrillResult<()> { - self.read()?.write_repository(rrdp_updates_config) + let content = self.read()?; + self.write_repository_content(content, rrdp_updates_config) + } + + /// Writes the repository using the given content instance. + /// + /// This is similar to [`write_repository`](Self::write_repository) but + /// avoids loading the [`RepositoryContent`] instance when you already + /// have it available. + pub fn write_repository_content( + &self, + content: Arc, + rrdp_updates_config: RrdpUpdatesConfig, + ) -> KrillResult<()> { + // Acquire the guard. Since we don’t actually look at the data, we + // can simply clear a poisoned lock and try locking again. + let _guard = loop { + match self.update_lock.lock() { + Ok(guard) => break guard, + Err(_) => { + self.update_lock.clear_poison(); + } + } + }; + content.write_repository(rrdp_updates_config) } /// Resets the RRDP session if it is initialized. @@ -233,7 +262,7 @@ impl RepositoryContentProxy { self.default_handle.clone(), ) )?; - content.write_repository(rrdp_updates_config) + self.write_repository_content(content, rrdp_updates_config) } else { // repository server was not initialized on this Krill instance. @@ -425,9 +454,8 @@ impl RepositoryContent { } /// Writes the repository content to disk. - pub fn write_repository( - &self, - config: RrdpUpdatesConfig, + fn write_repository( + &self, config: RrdpUpdatesConfig, ) -> KrillResult<()> { self.rrdp.update_rrdp_files(config)?; self.rsync.write(self.rrdp.serial(), self.rrdp.snapshot()) diff --git a/src/server/pubd/manager.rs b/src/server/pubd/manager.rs index 1d1d876ff..61f8cf415 100644 --- a/src/server/pubd/manager.rs +++ b/src/server/pubd/manager.rs @@ -1,7 +1,6 @@ //! The manager for the publication server. use std::path::PathBuf; -use std::sync::Arc; use bytes::Bytes; use log::{debug, info}; use rpki::ca::publication; @@ -17,10 +16,10 @@ use crate::api::pubd::RepoStats; use crate::commons::KrillResult; use crate::commons::actor::Actor; use crate::commons::cmslogger::CmsLogger; -use crate::commons::crypto::KrillSigner; use crate::commons::error::Error; -use crate::config::Config; -use crate::server::mq::{now, Task, TaskQueue}; +use crate::config::{Config, RrdpUpdatesConfig}; +use crate::server::mq::{now, Task}; +use crate::server::runtime::KrillRuntime; use super::access::RepositoryAccessProxy; use super::content::RepositoryContentProxy; use super::rrdp::RrdpUpdateNeeded; @@ -33,41 +32,22 @@ use super::rrdp::RrdpUpdateNeeded; /// * publish content to RRDP and rsync pub struct RepositoryManager { /// The repository access manager portion. - access: Arc, + access: RepositoryAccessProxy, /// The repository content manager portion. - content: Arc, + content: RepositoryContentProxy, - /// Shared task queue. - /// - /// Used to schedule RRDP updates when content is updated. - tasks: Arc, - - /// Shared server config. - config: Arc, - - /// Shared signer. - signer: Arc, + /// The configuration for RRDP update details. + rrdp_updates_config: RrdpUpdatesConfig, } impl RepositoryManager { /// Builds the repository manager. - pub fn build( - config: Arc, - tasks: Arc, - signer: Arc, - ) -> Result { - let access_proxy = Arc::new(RepositoryAccessProxy::create(&config)?); - let content_proxy = Arc::new( - RepositoryContentProxy::create(&config)? - ); - + pub fn new(config: &Config) -> Result { Ok(RepositoryManager { - access: access_proxy, - content: content_proxy, - tasks, - config, - signer, + access: RepositoryAccessProxy::create(config)?, + content: RepositoryContentProxy::create(config)?, + rrdp_updates_config: config.rrdp_updates_config, }) } @@ -77,12 +57,14 @@ impl RepositoryManager { } /// Create the publication server, will fail if it was already created. - pub fn init(&self, uris: PublicationServerUris) -> KrillResult<()> { + pub fn init( + &self, uris: PublicationServerUris, krill: &KrillRuntime, + ) -> KrillResult<()> { info!("Initializing repository"); - self.access.init(uris.clone(), self.signer.clone())?; - self.content.init(self.config.repo_dir(), uris)?; + self.access.init(uris.clone(), krill.signer())?; + self.content.init(krill.config().repo_dir(), uris)?; self.content - .write_repository(self.config.rrdp_updates_config)?; + .write_repository(krill.config().rrdp_updates_config)?; Ok(()) } @@ -127,9 +109,10 @@ impl RepositoryManager { &self, publisher_handle: PublisherHandle, msg_bytes: Bytes, + krill: &KrillRuntime, ) -> KrillResult { let cms_logger = CmsLogger::for_rfc8181_rcvd( - self.config.rfc8181_log_dir.as_ref(), + krill.config().rfc8181_log_dir.as_ref(), &publisher_handle, ); @@ -145,7 +128,9 @@ impl RepositoryManager { let is_list_query = query == publication::Query::List; - let response_result = self.rfc8181_message(&publisher_handle, query); + let response_result = self.rfc8181_message( + &publisher_handle, query, krill + ); let should_log_cms = response_result.is_err() || !is_list_query; @@ -163,7 +148,7 @@ impl RepositoryManager { }; let response_bytes = self.access.create_response( - response, &self.signer + response, krill.signer(), )?.to_bytes(); if should_log_cms { @@ -179,6 +164,7 @@ impl RepositoryManager { &self, publisher_handle: &PublisherHandle, query: publication::Query, + krill: &KrillRuntime, ) -> KrillResult { match query { publication::Query::List => { @@ -192,7 +178,7 @@ impl RepositoryManager { debug!( "Received RFC 8181 delta query for {publisher_handle}" ); - self.publish(publisher_handle, delta)?; + self.publish(publisher_handle, delta, krill)?; Ok(publication::Message::success()) } } @@ -200,7 +186,7 @@ impl RepositoryManager { /// Performs an RRDP session reset. pub fn rrdp_session_reset(&self) -> KrillResult<()> { - self.content.session_reset(self.config.rrdp_updates_config) + self.content.session_reset(self.rrdp_updates_config) } /// Lets a known publisher publish in a repository. @@ -208,6 +194,7 @@ impl RepositoryManager { &self, publisher_handle: &PublisherHandle, delta: PublishDelta, + krill: &KrillRuntime, ) -> KrillResult<()> { let publisher = self.access.get_publisher(publisher_handle)?; @@ -217,7 +204,7 @@ impl RepositoryManager { publisher.base_uri(), )?; - self.tasks.schedule(Task::RrdpUpdateIfNeeded, now()) + krill.tasks().schedule(Task::RrdpUpdateIfNeeded, now()) } /// Updates RRDP and makes new delta if needed. @@ -227,7 +214,7 @@ impl RepositoryManager { /// time for the next update is returned. pub fn update_rrdp_if_needed(&self) -> KrillResult> { match self.content.rrdp_update_needed( - self.config.rrdp_updates_config)? + self.rrdp_updates_config)? { RrdpUpdateNeeded::No => return Ok(None), RrdpUpdateNeeded::Later(time) => return Ok(Some(time)), @@ -235,9 +222,11 @@ impl RepositoryManager { } let content = self.content.update_rrdp( - self.config.rrdp_updates_config + self.rrdp_updates_config + )?; + self.content.write_repository_content( + content, self.rrdp_updates_config )?; - content.write_repository(self.config.rrdp_updates_config)?; Ok(None) } @@ -248,7 +237,7 @@ impl RepositoryManager { criteria: RepoFileDeleteCriteria, ) -> KrillResult<()> { // update RRDP first so we apply any staged deltas. - self.content.update_rrdp(self.config.rrdp_updates_config)?; + self.content.update_rrdp(self.rrdp_updates_config)?; // delete matching files using the updated snapshot and stage a delta // if needed. @@ -256,10 +245,12 @@ impl RepositoryManager { // update RRDP again to make the delta effective immediately. let content = - self.content.update_rrdp(self.config.rrdp_updates_config)?; + self.content.update_rrdp(self.rrdp_updates_config)?; - // Write the updated repository - NOTE: we no longer lock it. - content.write_repository(self.config.rrdp_updates_config)?; + // Write the updated repository. + self.content.write_repository_content( + content, self.rrdp_updates_config + )?; Ok(()) } @@ -301,8 +292,9 @@ impl RepositoryManager { pub fn repository_response( &self, publisher: &PublisherHandle, + krill: &KrillRuntime, ) -> KrillResult { - let rfc8181_uri = self.config.rfc8181_uri(publisher); + let rfc8181_uri = krill.config().rfc8181_uri(publisher); self.access.repository_response(rfc8181_uri, publisher) } @@ -326,11 +318,12 @@ impl RepositoryManager { &self, name: PublisherHandle, actor: &Actor, + krill: &KrillRuntime, ) -> KrillResult<()> { self.content.remove_publisher(name.clone())?; self.access.remove_publisher(name, actor)?; - self.tasks.schedule(Task::RrdpUpdateIfNeeded, now()) + krill.tasks().schedule(Task::RrdpUpdateIfNeeded, now()) } } @@ -339,7 +332,7 @@ impl RepositoryManager { /// Updates the RRDP files and rsync content on disk. pub fn write_repository(&self) -> KrillResult<()> { self.content - .write_repository(self.config.rrdp_updates_config) + .write_repository(self.rrdp_updates_config) } } @@ -347,13 +340,14 @@ impl RepositoryManager { //============ Tests ========================================================= #[cfg(test)] +#[allow(unused)] // XXX TODO mod tests { use std::fs; use std::path::{Path, PathBuf}; use std::str::{from_utf8, FromStr}; use std::time::Duration; + use std::thread::sleep; use bytes::Bytes; - use tokio::time::sleep; use url::Url; use rpki::uri; use rpki::ca::idexchange::Handle; @@ -414,66 +408,65 @@ mod tests { ) } - fn make_server( - storage_uri: &Url - ) -> (RepositoryManager, tempfile::TempDir) { - let data_dir = tempfile::tempdir().unwrap(); - - enable_test_mode(); - let mut config = Config::test( - storage_uri, - Some(data_dir.path()), - true, - false, - false, - false, - ); - let _ = config.init_logging(); - config.process().unwrap(); - - let signer = KrillSignerBuilder::new( - storage_uri, - Duration::from_secs(1), - &config.signers, - ) - .with_default_signer(config.default_signer()) - .with_one_off_signer(config.one_off_signer()) - .build() - .unwrap(); - - let signer = Arc::new(signer); - let config = Arc::new(config); - let mq = Arc::new(TaskQueue::new(&config.storage_uri).unwrap()); - let repository_manager = - RepositoryManager::build(config, mq, signer).unwrap(); - - let uris = PublicationServerUris { - rrdp_base_uri: https("https://localhost/repo/rrdp/"), - rsync_jail: rsync("rsync://localhost/repo/"), - }; - - repository_manager.init(uris).unwrap(); + struct TestServer { + krill: KrillRuntime, + storage_uri: Url, + data_dir: tempfile::TempDir, + tokio: tokio::runtime::Runtime, + } + + impl TestServer { + fn new() -> Self { + let storage_uri = test::mem_storage(); + let data_dir = tempfile::tempdir().unwrap(); + let tokio = tokio::runtime::Runtime::new().unwrap(); + + enable_test_mode(); + let mut config = Config::test( + &storage_uri, + Some(data_dir.path()), + true, + false, + false, + false, + ); + let _ = config.init_logging(); + config.process().unwrap(); + + let krill = KrillRuntime::new( + config, tokio.handle().clone() + ).unwrap(); + let uris = PublicationServerUris { + rrdp_base_uri: https("https://localhost/repo/rrdp/"), + rsync_jail: rsync("rsync://localhost/repo/"), + }; + + krill.repo_manager().init(uris, &krill).unwrap(); + + Self { krill, storage_uri, data_dir, tokio } + } - (repository_manager, data_dir) + fn repo(&self) -> &RepositoryManager { + self.krill.repo_manager() + } } #[test] fn should_add_publisher() { - // we need a disk, as repo_dir, etc. use data_dir by default - let storage_uri = test::mem_storage(); - let (server, _data_dir) = make_server(&storage_uri); + let server = TestServer::new(); - let alice = publisher_alice(&storage_uri); + let alice = publisher_alice(&server.storage_uri); let alice_handle = Handle::from_str("alice").unwrap(); let publisher_req = make_publisher_req(alice_handle.as_str(), alice.id_cert()); let actor = ACTOR_DEF_TEST; - server.create_publisher(publisher_req, &actor).unwrap(); + server.repo().create_publisher(publisher_req, &actor).unwrap(); - let alice_found = - server.get_publisher_details(alice_handle).unwrap(); + let alice_found = server.repo().get_publisher_details( + alice_handle + ).unwrap(); assert_eq!(alice_found.base_uri, alice.base_uri()); assert_eq!(alice_found.id_cert, *alice.id_cert()); @@ -482,21 +475,20 @@ mod tests { #[test] fn should_not_add_publisher_twice() { - let storage_uri = test::mem_storage(); - let (server, _data_dir) = make_server(&storage_uri); + let server = TestServer::new(); - let alice = publisher_alice(&storage_uri); + let alice = publisher_alice(&server.storage_uri); let alice_handle = Handle::from_str("alice").unwrap(); let publisher_req = make_publisher_req(alice_handle.as_str(), alice.id_cert()); let actor = ACTOR_DEF_TEST; - server - .create_publisher(publisher_req.clone(), &actor) - .unwrap(); + server.repo().create_publisher( + publisher_req.clone(), &actor + ).unwrap(); - match server.create_publisher(publisher_req, &actor) { + match server.repo().create_publisher(publisher_req, &actor) { Err(Error::PublisherDuplicate(name)) => { assert_eq!(name, alice_handle) } @@ -506,29 +498,26 @@ mod tests { #[test] fn should_list_files() { - let storage_uri = test::mem_storage(); - let (server, _data_dir) = make_server(&storage_uri); + let server = TestServer::new(); - let alice = publisher_alice(&storage_uri); + let alice = publisher_alice(&server.storage_uri); let alice_handle = Handle::from_str("alice").unwrap(); let publisher_req = make_publisher_req(alice_handle.as_str(), alice.id_cert()); let actor = ACTOR_DEF_TEST; - server.create_publisher(publisher_req, &actor).unwrap(); + server.repo().create_publisher(publisher_req, &actor).unwrap(); - let list_reply = server.list(&alice_handle).unwrap(); + let list_reply = server.repo().list(&alice_handle).unwrap(); assert_eq!(0, list_reply.elements().len()); } - #[tokio::test] - async fn should_publish_files() { - // we need a disk, as repo_dir, etc. use data_dir by default - let storage_uri = test::mem_storage(); - let (server, data_dir) = make_server(&storage_uri); + #[test] + fn should_publish_files() { + let server = TestServer::new(); - let session = session_dir(data_dir.path()); + let session = session_dir(server.data_dir.path()); // Check that the server starts with dir for serial 1 for RRDP // and does not use 0 (RFC 8182) @@ -536,14 +525,14 @@ mod tests { assert!(session_dir_contains_serial(&session, RRDP_FIRST_SERIAL)); // set up server with default repository, and publisher alice - let alice = publisher_alice(&storage_uri); + let alice = publisher_alice(&server.storage_uri); let alice_handle = Handle::from_str("alice").unwrap(); let publisher_req = make_publisher_req(alice_handle.as_str(), alice.id_cert()); let actor = ACTOR_DEF_TEST; - server.create_publisher(publisher_req, &actor).unwrap(); + server.repo().create_publisher(publisher_req, &actor).unwrap(); // get the file out of a list_reply fn find_in_reply<'a>( @@ -568,12 +557,12 @@ mod tests { delta.add_publish(file1.as_publish()); delta.add_publish(file2.as_publish()); - server.publish(&alice_handle, delta).unwrap(); - server.update_rrdp_if_needed().unwrap(); - server.write_repository().unwrap(); + server.repo().publish(&alice_handle, delta, &server.krill).unwrap(); + server.repo().update_rrdp_if_needed().unwrap(); + server.repo().write_repository().unwrap(); // Two files should now appear in the list - let list_reply = server.list(&alice_handle).unwrap(); + let list_reply = server.repo().list(&alice_handle).unwrap(); assert_eq!(2, list_reply.elements().len()); assert!(find_in_reply( &list_reply, @@ -586,7 +575,7 @@ mod tests { ) .is_some()); - sleep(Duration::from_secs(2)).await; + sleep(Duration::from_secs(2)); // Update // - update file @@ -609,12 +598,12 @@ mod tests { delta.add_withdraw(file2.as_withdraw()); delta.add_publish(file3.as_publish()); - server.publish(&alice_handle, delta).unwrap(); - server.update_rrdp_if_needed().unwrap(); - server.write_repository().unwrap(); + server.repo().publish(&alice_handle, delta, &server.krill).unwrap(); + server.repo().update_rrdp_if_needed().unwrap(); + server.repo().write_repository().unwrap(); // Two files should now appear in the list - let list_reply = server.list(&alice_handle).unwrap(); + let list_reply = server.repo().list(&alice_handle).unwrap(); assert_eq!(2, list_reply.elements().len()); assert!(find_in_reply( @@ -645,7 +634,7 @@ mod tests { let mut delta = PublishDelta::empty(); delta.add_publish(file_outside.as_publish()); - match server.publish(&alice_handle, delta) { + match server.repo().publish(&alice_handle, delta, &server.krill) { Err(Error::Rfc8181Delta( PublicationDeltaError::UriOutsideJail(_, _), )) => {} // ok @@ -660,7 +649,7 @@ mod tests { let mut delta = PublishDelta::empty(); delta.add_update(file2_update.as_update(file2.hash())); - match server.publish(&alice_handle, delta) { + match server.repo().publish(&alice_handle, delta, &server.krill) { Err(Error::Rfc8181Delta( PublicationDeltaError::NoObjectForHashAndOrUri(_), )) => {} @@ -671,7 +660,7 @@ mod tests { let mut delta = PublishDelta::empty(); delta.add_withdraw(file2.as_withdraw()); - match server.publish(&alice_handle, delta) { + match server.repo().publish(&alice_handle, delta, &server.krill) { Err(Error::Rfc8181Delta( PublicationDeltaError::NoObjectForHashAndOrUri(_), )) => {} // ok @@ -684,7 +673,7 @@ mod tests { let mut delta = PublishDelta::empty(); delta.add_publish(file3.as_publish()); - match server.publish(&alice_handle, delta) { + match server.repo().publish(&alice_handle, delta, &server.krill) { Err(Error::Rfc8181Delta( PublicationDeltaError::ObjectAlreadyPresent(uri), )) => { @@ -717,9 +706,9 @@ mod tests { let mut delta = PublishDelta::empty(); delta.add_publish(file4.as_publish()); - server.publish(&alice_handle, delta).unwrap(); - server.update_rrdp_if_needed().unwrap(); - server.write_repository().unwrap(); + server.repo().publish(&alice_handle, delta, &server.krill).unwrap(); + server.repo().update_rrdp_if_needed().unwrap(); + server.repo().write_repository().unwrap(); // Should include new snapshot and delta assert!(session_dir_contains_serial(&session, RRDP_FIRST_SERIAL + 3)); @@ -733,9 +722,11 @@ mod tests { assert!(session_dir_contains_delta(&session, RRDP_FIRST_SERIAL + 2)); // Removing the publisher should remove its contents - server.remove_publisher(alice_handle, &actor).unwrap(); - server.update_rrdp_if_needed().unwrap(); - server.write_repository().unwrap(); + server.repo().remove_publisher( + alice_handle, &actor, &server.krill + ).unwrap(); + server.repo().update_rrdp_if_needed().unwrap(); + server.repo().write_repository().unwrap(); // new snapshot should be published, and should be empty now assert!(session_dir_contains_snapshot( @@ -768,18 +759,17 @@ mod tests { #[test] pub fn repository_session_reset() { - let storage_uri = test::mem_storage(); - let (server, data_dir) = make_server(&storage_uri); + let server = TestServer::new(); // set up server with default repository, and publisher alice - let alice = publisher_alice(&storage_uri); + let alice = publisher_alice(&server.storage_uri); let alice_handle = Handle::from_str("alice").unwrap(); let publisher_req = make_publisher_req(alice_handle.as_str(), alice.id_cert()); let actor = ACTOR_DEF_TEST; - server.create_publisher(publisher_req, &actor).unwrap(); + server.repo().create_publisher(publisher_req, &actor).unwrap(); // get the file out of a list_reply fn find_in_reply<'a>( @@ -804,12 +794,12 @@ mod tests { delta.add_publish(file1.as_publish()); delta.add_publish(file2.as_publish()); - server.publish(&alice_handle, delta).unwrap(); - server.update_rrdp_if_needed().unwrap(); - server.write_repository().unwrap(); + server.repo().publish(&alice_handle, delta, &server.krill).unwrap(); + server.repo().update_rrdp_if_needed().unwrap(); + server.repo().write_repository().unwrap(); // Two files should now appear in the list - let list_reply = server.list(&alice_handle).unwrap(); + let list_reply = server.repo().list(&alice_handle).unwrap(); assert_eq!(2, list_reply.elements().len()); assert!(find_in_reply( &list_reply, @@ -823,10 +813,10 @@ mod tests { .is_some()); // Find RRDP files on disk - let stats_before = server.repo_stats().unwrap(); + let stats_before = server.repo().repo_stats().unwrap(); let session_before = stats_before.session; let snapshot_before_session_reset = find_in_session_and_serial_dir( - data_dir.path(), + server.data_dir.path(), session_before, RRDP_FIRST_SERIAL + 1, "snapshot.xml", @@ -835,14 +825,14 @@ mod tests { assert!(snapshot_before_session_reset.is_some()); // Now test that a session reset works... - server.rrdp_session_reset().unwrap(); + server.repo().rrdp_session_reset().unwrap(); // Should write new session and snapshot - let stats_after = server.repo_stats().unwrap(); + let stats_after = server.repo().repo_stats().unwrap(); let session_after = stats_after.session; let snapshot_after_session_reset = find_in_session_and_serial_dir( - data_dir.path(), + server.data_dir.path(), session_after, RRDP_FIRST_SERIAL, "snapshot.xml", @@ -856,7 +846,7 @@ mod tests { // and clean up old dir let snapshot_before_session_reset = find_in_session_and_serial_dir( - data_dir.path(), + server.data_dir.path(), session_before, RRDP_FIRST_SERIAL + 1, "snapshot.xml", diff --git a/src/server/pubd/rrdp.rs b/src/server/pubd/rrdp.rs index e30ff98c5..63da5c65a 100644 --- a/src/server/pubd/rrdp.rs +++ b/src/server/pubd/rrdp.rs @@ -651,11 +651,20 @@ impl RrdpServer { KrillIoError::new( format!( "could not write new notification file to {}", - notification_path_new.to_string_lossy() + notification_path_new.display() ), e, ) })?; + if let Err(err) = notification_file_new.sync_all() { + return Err(KrillIoError::new( + format!( + "failed to write new notification file '{}'", + notification_path_new.display() + ), + err + ).into()); + } // Rename the new file so it becomes current. let notification_path = self.notification_path(); @@ -787,8 +796,9 @@ impl RrdpServer { if let Ok(Some(snapshot_file_to_remove)) = Self::session_dir_snapshot(&session_dir, serial) - && let Err(e) = - fs::remove_file(&snapshot_file_to_remove) + && let Err(e) = fs::remove_file( + &snapshot_file_to_remove + ) { warn!( "Could not delete snapshot file '{}'. \ @@ -1409,11 +1419,12 @@ impl CurrentObjects { for (uri_key, base64) in self.iter() { // Add all manifests - as long as they are syntactically correct - // do not crash on incorrect objects. - if - uri_key.as_str().ends_with("mft") + if + uri_key.as_str().ends_with("mft") && let Ok(mft) = Manifest::decode( base64.to_bytes().as_ref(), false - ) && let Ok(stats) = PublisherManifestStats::try_from(&mft) + ) + && let Ok(stats) = PublisherManifestStats::try_from(&mft) { manifests.push(stats) } diff --git a/src/server/runtime.rs b/src/server/runtime.rs new file mode 100644 index 000000000..f9961436c --- /dev/null +++ b/src/server/runtime.rs @@ -0,0 +1,467 @@ +//! The Krill server runtime. +//! +//! The runtime contains all the components of a Krill server in one central +//! place and allows access to them. A reference to it is being passed around +//! when performing actions that may require access to other compontents. +//! +//! In addition, this module also provides the [`ThreadPool`] that is used +//! by the daemon to run its jobs on. + +use std::{cmp, error, fmt, thread}; +use std::mem::drop; +use std::sync::{mpsc as std_mpsc}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use log::{error, info}; +use rpki::uri; +use tokio::runtime; +use tokio::sync::{mpsc as tokio_mpsc, oneshot}; +use crate::commons::actor::Actor; +use crate::commons::crypto::{KrillSigner, KrillSignerBuilder}; +use crate::commons::error::KrillError; +use crate::config::Config; +use crate::constants::{ACTOR_DEF_KRILL, KRILL_SERVER_APP}; +use super::bgp::BgpAnalyser; +use super::ca::CaManager; +use super::mq::TaskQueue; +use super::pubd::RepositoryManager; + + +//------------ KrillRuntime -------------------------------------------------- + +/// The Krill runtime. +/// +/// The runtime contains all the components of the Krill server and provides +/// access to them. It is keeps them behind an arc, so it can be cloned and +/// passed around cheaply. +/// +/// Many methods of the various components expect a reference to the runtime +/// so they can initiate follow-up operations on other Krill components. +#[derive(Clone)] +pub struct KrillRuntime(Arc); + +impl KrillRuntime { + /// Creates a new Krill runtime. + /// + /// The runtime and all the components will be configured using `config`. + /// The `tokio` runtime handle will be used by the + /// [`exec_async`][Self::exec_async] method as the runtime to spawn + /// async tasks onto. + pub fn new( + config: Config, + tokio: runtime::Handle, + ) -> Result { + let service_uri = config.service_uri(); + + info!("{KRILL_SERVER_APP} uses service uri: {service_uri}"); + + // Assumes that Config::verify() has already ensured that the signer + // configuration is valid and that Config::resolve() has been + // used to update signer name references to resolve to the + // corresponding signer configurations. + let signer = KrillSignerBuilder::new( + &config.storage_uri, + Duration::from_secs(config.signer_probe_retry_seconds), + &config.signers, + ).with_default_signer( + config.default_signer() + ).with_one_off_signer( + config.one_off_signer() + ).build()?; + + let tasks = TaskQueue::new(&config.storage_uri)?; + let repo_manager = RepositoryManager::new(&config)?; + let ca_manager = CaManager::new(&config)?; + let bgp_analyser = BgpAnalyser::new(&config); + + Ok(Self(Arc::new(Components { + config, + service_uri, + repo_manager, + ca_manager, + tasks, + signer, + bgp_analyser, + system_actor: ACTOR_DEF_KRILL, + tokio, + }))) + } + + /// Returns the config used to create the runtime. + pub fn config(&self) -> &Config { + &self.0.config + } + + /// Returns the service URI of this Krill server instance. + pub fn service_uri(&self) -> &uri::Https { + &self.0.service_uri + } + + /// Returns the repository manager. + pub fn repo_manager(&self) -> &RepositoryManager { + &self.0.repo_manager + } + + /// Returns the CA manager. + pub fn ca_manager(&self) -> &CaManager { + &self.0.ca_manager + } + + /// Returns the task queue. + pub fn tasks(&self) -> &TaskQueue { + &self.0.tasks + } + + /// Returns the signer. + pub fn signer(&self) -> &KrillSigner { + &self.0.signer + } + + /// Returns the BGP analyser. + pub fn bgp_analyser(&self) -> &BgpAnalyser { + &self.0.bgp_analyser + } + + /// Returns the actor to be used for sytem tasks. + pub fn system_actor(&self) -> &Actor { + &self.0.system_actor + } + + /// Returns whether testbed mode is enabled. + pub fn is_testbed_enabled(&self) -> bool { + self.config().testbed().is_some() + } + + /// Runs a future on a Tokio runtime and blocks until it resolves. + /// + /// This method blocks the current thread. + pub fn exec_async( + &self, future: F + ) -> Result + where + F: Future + Send + 'static, + F::Output: Send + 'static, + { + let (tx, rx) = oneshot::channel(); + let join = self.0.tokio.spawn(async move { + let _ = tx.send(future.await); + }); + drop(join); // explicitly drop to avoid warning + rx.blocking_recv().map_err(|_| ExecAsyncError(())) + } +} + + +//------------ Components ---------------------------------------------------- + +/// All the components of a Krill server. +/// +/// A value of this type is kept by [`KrillRuntime`] behind an arc. +struct Components { + /// The server configuration. + config: Config, + + /// The base URI for communicating with this server. + /// + /// We keep it separately because the config only keeps the configured + /// value which may be missing. + service_uri: uri::Https, + + /// Publication server with configured publishers + repo_manager: RepositoryManager, + + /// The manager for all our CAs. + ca_manager: CaManager, + + /// The task queue. + tasks: TaskQueue, + + /// The signer. + signer: KrillSigner, + + /// The BGP analyser. + bgp_analyser: BgpAnalyser, + + /// The actor used for actions initiated by the server itself. + system_actor: Actor, + + /// The Tokio runtime to spawn async tasks onto. + tokio: runtime::Handle, +} + + +//------------ ThreadPool ---------------------------------------------------- + +/// A thread pool to run jobs on. +/// +/// This type represents the thread pool itself and should be kept around +/// during the entire lifetime of the pool. +/// +/// Jobs are spawned onto the pool through a +/// [`ThreadPoolHandle`] which can be obtained via the +/// [`handle`][Self::handle] method. +/// +/// Additional, non-worker threads can be created using the +/// [`spawn`][Self::spawn] method. This feature is used for the +/// scheduler thread. Spawning a thread via the thread pool differs regular +/// threads in that it provides a means to signal that the thread should +/// exit. +/// +/// This becomes relevant when it is time to shut down the application. In +/// this case, the [`terminate`][Self::terminate] method is called. The +/// imminent shutdown is signalled to all the worker threads and the +/// additional threads and then the method blocks and waits for all threads +/// to exit. +/// +/// During shutdown, the thread pool will not accept new jobs but the +/// worker threads will process all already queued jobs. This is slightly +/// theoretical as the queue capacity is 1, so there should be at most one +/// queued job. +pub struct ThreadPool { + /// The sending end of the job queue. + /// + /// The receiving end of this queue is shared between all worker threads. + worker_tx: tokio_mpsc::Sender, + + /// The sending ends of all shutdown queues for regular threads. + /// + /// Each thread spawned via the `spawn` method gets the receiving end + /// of one of these. During shutdown, a `()` is sent to each of them. + thread_tx: Vec>, + + /// The join handles of all child threads. + /// + /// We will wait for all of them during shutdown. + join: Vec>, +} + +impl ThreadPool { + /// Creates a new thread pool based on the config. + /// + /// Currently, we only use [`config.num_threads`][Config::num_threads] + /// to allow users to configure the number of worker threads. By default, + /// the number is the available parallelism as reported by the standard + /// library. + pub fn new( + config: &Config + ) -> Result { + let (worker_tx, rx) = tokio_mpsc::channel(1); + let rx = Arc::new(Mutex::new(rx)); + + let thread_count = match config.num_threads { + Some(num) => num, + None => { + match thread::available_parallelism() { + Ok(num) => num.into(), + Err(err) => { + return Err(KrillError::internal( + format_args!( + "failed to determine thread number. Please \ + specify `num_threads` in config. \ + ({err})" + ) + )); + } + } + } + }; + let thread_count = cmp::max(thread_count, 1); + + let mut join = Vec::new(); + for _ in 0..thread_count { + let rx = rx.clone(); + join.push( + thread::Builder::new().name( + "thread-pool".into() + ).spawn(move || { + Self::worker_thread(rx) + }).map_err(|err| { + KrillError::internal( + format_args!("failed to spawn worker thread: {err}") + ) + })? + ); + } + + info!("Created thread pool with {thread_count} threads"); + + Ok(Self { + worker_tx, + thread_tx: Vec::new(), + join + }) + } + + /// The thread function of each worker thread. + /// + /// The function receives a copy of the receiving end of the job queue + /// behind a mutex which allows the thread to acquire new work. + /// + /// The work distribution mechanism is extremely simple: When it is out + /// of work, a thread will try to acquire the lock on the receiver. When + /// it acquires the lock, it will the perform a blocking read on the + /// queue. + /// + /// If the received message is a new job, it will drop the lock and + /// perform the job. If the message signals a shutdown, it will call + /// `close` on the queue – which will switch the queue into shutdown + /// mode, drop the lock and start again at the top. + /// + /// When the queue is in shutdown mode, trying to receive a message will + /// return `None` once the queue has been exhausted. This is the signal + /// for the thread to exit. + fn worker_thread( + rx: Arc>>, + ) { + loop { + let job = { + let mut queue = match rx.lock() { + Ok(queue) => queue, + Err(err) => { + error!( + "Fatal: worker thread failed to aquire lock: {err}" + ); + return; + } + }; + let Some(job) = queue.blocking_recv() else { + // None is returned when the queue is closed or when + // all the senders are gone. + return; + }; + match job { + ThreadPoolMessage::Job(job) => job, + ThreadPoolMessage::Shutdown => { + // Close the queue. If there are any tasks left, + // we want to still process those, so continue here. + queue.close(); + continue; + } + } + }; + (job)(); + } + } + + /// Creates a new handle to the thread pool + pub fn handle(&self) -> ThreadPoolHandle { + ThreadPoolHandle { tx: self.worker_tx.clone() } + } + + /// Spawns a new additional thread on the thread pool. + /// + /// The method expects a closure which will receive the receiver for a + /// standard library MPSC queue. When the thread pool is being shut down, + /// a single `()` is sent to this queue. + /// + /// Additional threads are waited upon when the thread pool is terminated + /// and there currently is no timeout for that, so make sure your thread + /// actually listens to the shutdown signal and terminates eventually. + pub fn spawn( + &mut self, f: impl FnOnce(std_mpsc::Receiver<()>) + Send + 'static + ) { + let (tx, rx) = std_mpsc::sync_channel(1); + self.thread_tx.push(tx); + self.join.push(thread::spawn(|| f(rx))); + } + + /// Terminates the thread pool. + /// + /// The method sends signals to all threads to initiate their own shutdown + /// and then blocks until all threads have terminated. + pub fn terminate(self) { + let _ = self.worker_tx.blocking_send(ThreadPoolMessage::Shutdown); + for tx in self.thread_tx { + let _ = tx.send(()); + } + for join in self.join { + // `join` returns an error if the thread panicked. We can + // consider it done in this case. + let _ = join.join(); + } + } +} + + +//------------ ThreadPoolHandle ---------------------------------------------- + +/// A handle to a thread pool, allowing to spawn jobs onto it. +/// +/// The sole purpose of this type is to allow spawning jobs onto the thread +/// pool it is connected to via the [`spawn`][Self::spawn] method. +/// +/// Handles can be cloned relatively cheaply. +#[derive(Clone)] +pub struct ThreadPoolHandle { + /// The sending end of the job queue. + tx: tokio_mpsc::Sender, +} + +impl ThreadPoolHandle { + /// Spawns a job onto the thread pool. + /// + /// The job is represented by the closure. As this closure will be run + /// on a different thread, it needs to be `Send + 'static`. + /// + /// Returns an error if the thread pool does not accept new jobs any more. + /// + /// This is an async function that will only return once the job has been + /// dipatched of. + pub async fn spawn( + &self, job: impl FnOnce() + Send + 'static + ) -> Result<(), SpawnError> { + self.tx.send( + ThreadPoolMessage::Job(Box::new(job)) + ).await.map_err(|_| SpawnError(())) + } +} + + +//------------ ThreadPoolMessage --------------------------------------------- + +/// The message sent to the queue of the thread pool. +enum ThreadPoolMessage { + /// A job to run. + Job(Box), + + /// The thread pool is shutting down. + Shutdown, +} + + +//============ SpawnError ==================================================== + +//------------ SpawnError ---------------------------------------------------- + +/// An error happened while trying to spawn a job. +/// +/// This error means that the thread pool does not accept new jobs any more. +#[derive(Clone, Debug)] +pub struct SpawnError(()); + +impl fmt::Display for SpawnError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("all worker threads disappeared") + } +} + +impl error::Error for SpawnError { } + + +//------------ ExecAsyncError ------------------------------------------------ + +/// An error happened while waiting for a future to resolve. +/// +/// This error means that the executed future was dropped before being +/// resolved. +#[derive(Clone, Debug)] +pub struct ExecAsyncError(()); + +impl fmt::Display for ExecAsyncError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("the future was dropped before resolving") + } +} + +impl error::Error for ExecAsyncError { } + diff --git a/src/server/scheduler.rs b/src/server/scheduler.rs index 564aa59a0..348f25b38 100644 --- a/src/server/scheduler.rs +++ b/src/server/scheduler.rs @@ -1,21 +1,17 @@ //! Deal with asynchronous scheduled processes, either triggered by an //! event that occurred, or planned (e.g. re-publishing). -use std::{collections::HashMap, sync::Arc, time::Duration}; - -use tokio::time::sleep; - +use std::collections::HashMap; +use std::sync::mpsc; +use std::time::Duration; use log::{debug, error, info, warn}; -use rpki::ca::{ - idexchange::{CaHandle, ParentHandle}, - provisioning::{ResourceClassName, RevocationRequest}, -}; +use rpki::ca::idexchange::{CaHandle, ParentHandle}; +use rpki::ca::provisioning::{ResourceClassName, RevocationRequest}; use url::Url; use crate::{ api::ca::Timestamp, commons::{ - actor::Actor, crypto::dispatch::signerinfo::SignerInfo, error::{Error, FatalError}, eventsourcing::{Aggregate, AggregateStore, WalStore, WalSupport}, @@ -28,706 +24,690 @@ use crate::{ SCHEDULER_RESYNC_REPO_CAS_THRESHOLD, SCHEDULER_USE_JITTER_CAS_THRESHOLD, SIGNERS_NS, }, - config::Config, server::{ - ca::{CaManager, CertAuth}, - bgp::BgpAnalyser, + ca::CertAuth, mq::{ - in_hours, in_minutes, in_seconds, in_weeks, now, Task, TaskQueue, + in_hours, in_minutes, in_seconds, in_weeks, now, Task, }, properties::Properties, - pubd::{RepositoryAccess, RepositoryContent, RepositoryManager}, + pubd::{RepositoryAccess, RepositoryContent}, + runtime::KrillRuntime, }, }; use super::mq::TaskResult; -pub struct Scheduler { - tasks: Arc, - ca_manager: Arc, - repo_manager: Arc, - bgp_analyser: Arc, - config: Arc, - system_actor: Actor, - started: Timestamp, -} -impl Scheduler { - pub fn build( - tasks: Arc, - ca_manager: Arc, - repo_manager: Arc, - bgp_analyser: Arc, - config: Arc, - system_actor: Actor, - ) -> Self { - Scheduler { - tasks, - ca_manager, - repo_manager, - bgp_analyser, - config, - system_actor, - started: Timestamp::now(), - } - } +//------------ run ----------------------------------------------------------- - /// Run the scheduler in the background. It will sweep the message queue - /// for tasks and re-schedule new tasks as needed. - pub async fn run(&self) { - loop { - while let Some((task_key, value)) = self.tasks.pop() { - match serde_json::from_value(value) { - Err(e) => { - // If we cannot parse the value of this task, then we - // have a major - // issue. Essentially, this can only happen if we did - // a Krill upgrade - // to a new version that no longer understands - // existing tasks. - // - // So, if we ever change the content of tasks then we - // should make sure that Krill - // is either backward compatible, or the task queue is - // migrated on upgrade. - error!("Fatal error parsing task: {}. Krill will now stop! This may be because this task is not for this Krill version ({}). If this issue persists, then try deleting this task from storage, it will appear in the 'tasks' dir if you use disk storage. The error was {}", task_key, KrillVersion::code_version(), e); - std::process::exit(1); - } - Ok(task) => match self.process_task(task).await { - Ok(result) => { - if let Err(e) = match result { - TaskResult::Done => { - self.tasks.finish(&task_key) - } - TaskResult::FollowUp(task, priority) => { - self.tasks.schedule_and_finish_existing( - task, priority, - ) - } - TaskResult::Reschedule(priority) => { - self.tasks.reschedule(&task_key, priority) - } - } { - error!("Error finishing / scheduling task {task_key}. Krill will stop as there is no good way to recover from this. When Krill starts it will try to reschedule any missing tasks. Error was: {e}"); - std::process::exit(1); +pub(super) fn run( + krill: KrillRuntime, + shutdown: mpsc::Receiver<()>, +) { + let started = Timestamp::now(); + + // Outer loop: Waits half a second if the task queue is empty. + loop { + // Inner loop: Breaks when the the task queue becomes empty. + while + shutdown.try_recv() == Err(mpsc::TryRecvError::Empty) + && let Some((task_key, value)) = krill.tasks().pop() + { + match serde_json::from_value(value) { + Err(e) => { + // If we cannot parse the value of this task, then we + // have a major issue. Essentially, this can only happen + // if we did a Krill upgrade to a new version that no + // longer understands existing tasks. + // + // So, if we ever change the content of tasks then we + // should make sure that Krill is either backward + // compatible, or the task queue ismigrated on upgrade. + error!( + "Fatal error parsing task: {}. Krill will now stop! \ + This may be because this task is not for this \ + Krill version ({}). If this issue persists, then \ + try deleting this task from storage, it will \ + appear in the 'tasks' dir if you use disk storage. \ + The error was {}", + task_key, KrillVersion::code_version(), e + ); + std::process::exit(1); + } + Ok(task) => match process_task(&krill, task, started) { + Ok(result) => { + if let Err(e) = match result { + TaskResult::Done => { + krill.tasks().finish(&task_key) } - } - Err(e) => { - error!("Error processing task: {task_key}. Tasks are only allowed to return fatal errors. Krill will stop as there is no good way to recover from this. When Krill starts it will try to reschedule any missing tasks. Error was: {e}"); + TaskResult::FollowUp(task, priority) => { + krill.tasks().schedule_and_finish_existing( + task, priority, + ) + } + TaskResult::Reschedule(priority) => { + krill.tasks().reschedule(&task_key, priority) + } + } { + error!( + "Error finishing / scheduling task \ + {task_key}. Krill will stop as there is no \ + good way to recover from this. When Krill \ + starts it will try to reschedule any \ + missing tasks. Error was: {e}" + ); std::process::exit(1); } - }, - } + } + Err(e) => { + error!( + "Error processing task: {task_key}. Tasks are \ + only allowed to return fatal errors. Krill will \ + stop as there is no good way to recover from \ + this. When Krill starts it will try to \ + reschedule any missing tasks. Error was: {e}" + ); + std::process::exit(1); + } + }, } + } - sleep(Duration::from_millis(500)).await; + match shutdown.recv_timeout(Duration::from_millis(500)) { + Ok(()) | Err(mpsc::RecvTimeoutError::Disconnected) => { + // Shutdown requested or the sender went away: Quit. + break; + } + Err(mpsc::RecvTimeoutError::Timeout) => { + // Timeout: continue. + } } } +} - /// Process a single task - /// - /// May only return fatal errors. Temporary, or suspected temporary, - /// issues such as not being able to contact a parent CA should result - /// in an Ok(TaskResult::Reschedule) instead. - async fn process_task( - &self, - task: Task, - ) -> Result { - match task { - Task::QueueStartTasks => self.queue_start_tasks().await, /* return error and stop server on failure */ - Task::SyncRepo { - ca_handle: ca, - ca_version, - } => self.sync_repo(ca, ca_version).await, +/// Process a single task +/// +/// May only return fatal errors. Temporary, or suspected temporary, +/// issues such as not being able to contact a parent CA should result +/// in an Ok(TaskResult::Reschedule) instead. +fn process_task( + krill: &KrillRuntime, task: Task, started: Timestamp, +) -> Result { + match task { + Task::QueueStartTasks => { + queue_start_tasks(krill) + } - Task::SyncParent { - ca_handle: ca, - ca_version, - parent, - } => self.sync_parent(ca, ca_version, parent).await, + Task::SyncRepo { ca_handle, ca_version } => { + sync_repo(krill, ca_handle, ca_version) + } - Task::RenewTestbedTa => self.renew_testbed_ta().await, + Task::SyncParent { ca_handle, ca_version, parent } => { + sync_parent(krill, ca_handle, ca_version, parent) + } - Task::SyncTrustAnchorProxySignerIfPossible => { - self.sync_ta_proxy_signer_if_possible().await - } + Task::RenewTestbedTa => renew_testbed_ta(krill), - Task::SuspendChildrenIfNeeded { ca_handle: ca } => { - self.suspend_children_if_needed(ca).await - } + Task::SyncTrustAnchorProxySignerIfPossible => { + sync_ta_proxy_signer_if_possible(krill) + } - Task::RepublishIfNeeded => self.republish_if_needed().await, + Task::SuspendChildrenIfNeeded { ca_handle: ca } => { + suspend_children_if_needed(krill, ca, started) + } - Task::RenewObjectsIfNeeded => { - self.renew_objects_if_needed().await - } + Task::RepublishIfNeeded => republish_if_needed(krill), - Task::UpdateSnapshots => self.update_snapshots(), + Task::RenewObjectsIfNeeded => { + renew_objects_if_needed(krill) + } - Task::RrdpUpdateIfNeeded => self.update_rrdp_if_needed(), + Task::UpdateSnapshots => update_snapshots(krill), - Task::ResourceClassRemoved { - ca_handle: ca, - ca_version, - parent, - rcn, - revocation_requests, - } => { - self.resource_class_removed( - ca, - ca_version, - parent, - rcn, - revocation_requests, - ) - .await - } + Task::RrdpUpdateIfNeeded => update_rrdp_if_needed(krill), - Task::UnexpectedKey { - ca_handle: ca, - ca_version, - rcn, - revocation_request, - } => { - self.unexpected_key(ca, ca_version, rcn, revocation_request) - .await - } + Task::ResourceClassRemoved { + ca_handle, ca_version, parent, rcn, revocation_requests, + } => { + resource_class_removed( + krill, ca_handle, ca_version, parent, rcn, revocation_requests, + ) + } - Task::RefreshAnnouncementsInfo => { - self.announcements_refresh().await - } + Task::UnexpectedKey { + ca_handle, ca_version, rcn, revocation_request, + } => { + unexpected_key( + krill, ca_handle, ca_version, rcn, revocation_request + ) + } - Task::SweepLoginCache => { - // Don’t do anything. These are deprecated. - Ok(TaskResult::Done) - } + Task::RefreshAnnouncementsInfo => { + announcements_refresh(krill) + } + + Task::SweepLoginCache => { + // Don’t do anything. These are deprecated. + Ok(TaskResult::Done) } } +} - /// Queues missing tasks for background jobs when the server is started - async fn queue_start_tasks(&self) -> Result { - // The task queue is persistent starting with Krill 0.14.0 - // - // Tasks should not disappear. But.. to make sure that: - // a) krill is self-healing - // b) this works on the first upgrade to 0.14.0 - // - // We will add all MISSING tasks that we think will be needed. - // - // This works simplest by adding all task with the Existing::KeepOld - // option of the queue. +/// Queues missing tasks for background jobs when the server is started +fn queue_start_tasks(krill: &KrillRuntime) -> Result { + // The task queue is persistent starting with Krill 0.14.0 + // + // Tasks should not disappear. But.. to make sure that: + // a) krill is self-healing + // b) this works on the first upgrade to 0.14.0 + // + // We will add all MISSING tasks that we think will be needed. + // + // This works simplest by adding all task with the Existing::KeepOld + // option of the queue. + + // If there are only a few CAs in this Krill instance, then we + // will just want to re-sync them with their parents and repository + // on start up. + // + // If there are many, then we apply some random delays (jitter) + // to avoid a thundering herd. Note that the operator can always + // choose to run bulk operations manually if they know that they + // cannot wait. + let cas = krill.ca_manager().ca_handles().map_err(FatalError)?; + debug!("Adding missing tasks at start up"); + + // If we have many CAs then we need to apply some jitter + // in the priority of CA to parent and CA to repository + // syncs to avoid generating a thundering herd. + + let use_jitter = cas.len() >= SCHEDULER_USE_JITTER_CAS_THRESHOLD; + + for handle in &cas { + let ca = krill.ca_manager().get_ca(handle).map_err(FatalError)?; + let ca_handle = ca.handle(); + let ca_version = ca.version(); + + debug!( + "Adding tasks for CA {}, using jitter: {}", + ca.handle(), + use_jitter + ); + + for parent in ca.parents() { + krill.tasks().schedule_missing( + Task::SyncParent { + ca_handle: ca_handle.clone(), + ca_version, + parent: parent.clone(), + }, + krill.config().ca_refresh_start_up(use_jitter), + ) + .map_err(FatalError)?; + } - // If there are only a few CAs in this Krill instance, then we - // will just want to re-sync them with their parents and repository - // on start up. + // Plan a sync with the repo. But only in case we only have a + // handful of CAs. // - // If there are many, then we apply some random delays (jitter) - // to avoid a thundering herd. Note that the operator can always - // choose to run bulk operations manually if they know that they - // cannot wait. - let cas = self.ca_manager.ca_handles().map_err(FatalError)?; - debug!("Adding missing tasks at start up"); - - // If we have many CAs then we need to apply some jitter - // in the priority of CA to parent and CA to repository - // syncs to avoid generating a thundering herd. - - let use_jitter = cas.len() >= SCHEDULER_USE_JITTER_CAS_THRESHOLD; - - for handle in &cas { - let ca = self - .ca_manager - .get_ca(handle) - .map_err(FatalError)?; - let ca_handle = ca.handle(); - let ca_version = ca.version(); + // Note: if circumstances dictate a sync e.g. because ROAs are + // changed, then it will be scheduled accordingly. + // Furthermore, users can use the 'bulk' function to + // explicitly force schedule a sync. + if cas.len() <= SCHEDULER_RESYNC_REPO_CAS_THRESHOLD { + krill.tasks().schedule_missing( + Task::SyncRepo { + ca_handle: ca_handle.clone(), + ca_version, + }, + now(), + ).map_err(FatalError)?; + } - debug!( - "Adding tasks for CA {}, using jitter: {}", - ca.handle(), - use_jitter - ); + // If suspension is enabled then plan a task for it. Since this is + // a cheap no-op in most cases, we do not need jitter. If we do + // not add this task then it will not be executed + // (obviously), but more importantly.. by adding this + // task we ensure that it will keep being re-scheduled + // when it's done. + if krill.config().suspend_child_after_inactive_seconds().is_some() { + krill.tasks().schedule_missing( + Task::SuspendChildrenIfNeeded { + ca_handle: ca_handle.clone(), + }, + now(), + ).map_err(FatalError)?; + } + } - for parent in ca.parents() { - self.tasks - .schedule_missing( - Task::SyncParent { - ca_handle: ca_handle.clone(), - ca_version, - parent: parent.clone(), - }, - self.config.ca_refresh_start_up(use_jitter), - ) - .map_err(FatalError)?; - } + krill.tasks().schedule_missing( + Task::RepublishIfNeeded, now() + ).map_err(FatalError)?; + krill.tasks().schedule_missing( + Task::RenewObjectsIfNeeded, now() + ).map_err(FatalError)?; + + // BGP announcement info is only kept in-memory, so it + // is lost after a restart, so schedule refreshing this + // immediately. + if krill.config().bgp_riswhois_enabled { + krill.tasks().schedule( + Task::RefreshAnnouncementsInfo, now() + ).map_err(FatalError)?; + } - // Plan a sync with the repo. But only in case we only have a - // handful of CAs. - // - // Note: if circumstances dictate a sync e.g. because ROAs are - // changed, then it will be scheduled accordingly. - // Furthermore, users can use the 'bulk' function to - // explicitly force schedule a sync. - if cas.len() <= SCHEDULER_RESYNC_REPO_CAS_THRESHOLD { - self.tasks - .schedule_missing( - Task::SyncRepo { - ca_handle: ca_handle.clone(), - ca_version, - }, - now(), - ) - .map_err(FatalError)?; - } + // Plan updating snapshots soon after a restart. + // This also ensures that this task gets triggered in long + // running tests, such as functional_parent_child.rs. + krill.tasks().schedule_missing( + Task::UpdateSnapshots, now() + ).map_err(FatalError)?; + + if krill.config().testbed().is_some() { + krill.tasks().schedule_missing( + Task::RenewTestbedTa, now() + ).map_err(FatalError)?; + } - // If suspension is enabled then plan a task for it. Since this is - // a cheap no-op in most cases, we do not need jitter. If we do - // not add this task then it will not be executed - // (obviously), but more importantly.. by adding this - // task we ensure that it will keep being re-scheduled - // when it's done. - if self.config.suspend_child_after_inactive_seconds().is_some() { - self.tasks - .schedule_missing( - Task::SuspendChildrenIfNeeded { - ca_handle: ca_handle.clone(), - }, - now(), - ) - .map_err(FatalError)?; - } - } + Ok(TaskResult::Done) +} - self.tasks - .schedule_missing(Task::RepublishIfNeeded, now()) - .map_err(FatalError)?; - self.tasks - .schedule_missing(Task::RenewObjectsIfNeeded, now()) - .map_err(FatalError)?; +fn sync_repo( + krill: &KrillRuntime, + ca: CaHandle, + version: u64, +) -> Result { + info!("Synchronize CA {ca} with repository"); - // BGP announcement info is only kept in-memory, so it - // is lost after a restart, so schedule refreshing this - // immediately. - if self.config.bgp_riswhois_enabled { - self.tasks - .schedule(Task::RefreshAnnouncementsInfo, now()) - .map_err(FatalError)?; - } + match krill.ca_manager().cas_repo_sync_single(&ca, version, krill) { + Err(e) => { + let next = krill.config().requeue_remote_failed(); - // Plan updating snapshots soon after a restart. - // This also ensures that this task gets triggered in long - // running tests, such as functional_parent_child.rs. - self.tasks - .schedule_missing(Task::UpdateSnapshots, now()) - .map_err(FatalError)?; + error!( + "Failed to publish for '{ca}'. \ + Will reschedule to: '{next}'. Error: {e}" + ); - if self.config.testbed().is_some() { - self.tasks - .schedule_missing(Task::RenewTestbedTa, now()) - .map_err(FatalError)?; + Ok(TaskResult::Reschedule(next)) + } + Ok(true) => Ok(TaskResult::Done), + Ok(false) => { + debug!("sync was premature, reschedule"); + let next = in_seconds(1); + Ok(TaskResult::Reschedule(next)) } - - Ok(TaskResult::Done) } +} - async fn sync_repo( - &self, - ca: CaHandle, - version: u64, - ) -> Result { - info!("Synchronize CA {ca} with repository"); - - match self - .ca_manager - .cas_repo_sync_single(self.repo_manager.as_ref(), &ca, version) - .await - { +/// Try to synchronize a CA with a specific parent, reschedule if this +/// fails +fn sync_parent( + krill: &KrillRuntime, + ca: CaHandle, + ca_version: u64, + parent: ParentHandle, +) -> Result { + if krill.ca_manager().has_ca(&ca).map_err(FatalError)? { + info!("Synchronize CA '{ca}' with its parent '{parent}'"); + match krill.ca_manager().ca_sync_parent( + &ca, ca_version, &parent, krill.system_actor(), krill, + ) { Err(e) => { - let next = self.config.requeue_remote_failed(); + let next = krill.config().requeue_remote_failed(); - error!( - "Failed to publish for '{ca}'. Will reschedule to: '{next}'. Error: {e}" - ); + if let Error::CaParentUnknown(..) = e { + warn!( + "CA '{ca}' tried to sync with parent '{parent}'. + Parent is unknown. Not rescheduling. Error: {e} + " + ); - Ok(TaskResult::Reschedule(next)) + Ok(TaskResult::Done) + } + else { + error!( + "Failed to synchronize CA '{ca}' with its parent \ + '{parent}'. Will reschedule to: '{next}'. Error: {e}" + ); + Ok(TaskResult::Reschedule(next)) + } + } + Ok(true) => { + let next = krill.config().ca_refresh_next(); + Ok(TaskResult::FollowUp( + Task::SyncParent { + ca_handle: ca, + ca_version, + parent, + }, + next, + )) } - Ok(true) => Ok(TaskResult::Done), Ok(false) => { - debug!("sync was premature, reschedule"); + debug!("reschedule premature task"); let next = in_seconds(1); Ok(TaskResult::Reschedule(next)) } } } - - /// Try to synchronize a CA with a specific parent, reschedule if this - /// fails - async fn sync_parent( - &self, - ca: CaHandle, - ca_version: u64, - parent: ParentHandle, - ) -> Result { - if self.ca_manager.has_ca(&ca).map_err(FatalError)? { - info!("Synchronize CA '{ca}' with its parent '{parent}'"); - match self - .ca_manager - .ca_sync_parent(&ca, ca_version, &parent, &self.system_actor) - .await - { - Err(e) => { - let next = self.config.requeue_remote_failed(); - - if let Error::CaParentUnknown(..) = e { - warn!( - "CA '{ca}' tried to sync with parent '{parent}'. Parent is unknown. Not rescheduling. Error: {e}" - ); - Ok(TaskResult::Done) - } else { - error!( - "Failed to synchronize CA '{ca}' with its parent '{parent}'. Will reschedule to: '{next}'. Error: {e}" - ); - Ok(TaskResult::Reschedule(next)) - } - } - Ok(true) => { - let next = self.config.ca_refresh_next(); - Ok(TaskResult::FollowUp( - Task::SyncParent { - ca_handle: ca, - ca_version, - parent, - }, - next, - )) - } - Ok(false) => { - debug!("reschedule premature task"); - let next = in_seconds(1); - Ok(TaskResult::Reschedule(next)) - } - } - } else { - // Note: if one day we can have a notification extension to RFC - // 6492 then we will also be able to alert - // remote children. - debug!( - "Skipping parent sync fo CA '{ca}'. It is either a remote child, or a local CA that has been removed" - ); - Ok(TaskResult::Done) - } - } - - /// Resync the testbed TA signer and proxy - async fn renew_testbed_ta(&self) -> Result { - if let Err(e) = self.ca_manager.ta_renew_testbed_ta() { - error!("There was an issue renewing the testbed TA: {e}"); - } - let weeks_to_resync = self.config.ta_timing.mft_next_update_weeks / 2; - Ok(TaskResult::FollowUp( - Task::RenewTestbedTa, - in_weeks(weeks_to_resync), - )) - } - - /// Try to synchronise the Trust Anchor Proxy with the *local* Signer - if - /// it exists in this server. - async fn sync_ta_proxy_signer_if_possible( - &self, - ) -> Result { - debug!("Synchronise Trust Anchor Proxy with Signer - if Signer is local."); - if let Err(e) = - self.ca_manager.sync_ta_proxy_signer_if_possible() - { - error!("There was an issue synchronising the TA Proxy and Signer: {e}"); - } + else { + // Note: if one day we can have a notification extension to RFC + // 6492 then we will also be able to alert + // remote children. + debug!( + "Skipping parent sync fo CA '{ca}'. It is either a remote \ + child, or a local CA that has been removed" + ); Ok(TaskResult::Done) } +} - /// Try to suspend children for a CA - async fn suspend_children_if_needed( - &self, - ca_handle: CaHandle, - ) -> Result { - if self.ca_manager.has_ca(&ca_handle).map_err(FatalError)? { - debug!( - "Verify if CA '{ca_handle}' has children that need to be suspended" - ); - self.ca_manager.ca_suspend_inactive_children( - &ca_handle, self.started, &self.system_actor, - ); - - Ok(TaskResult::FollowUp( - Task::SuspendChildrenIfNeeded { ca_handle }, - in_hours(1), - )) - } else { - debug!( - "Drop task to suspend children for removed CA {ca_handle}" - ); - Ok(TaskResult::Done) - } +/// Resync the testbed TA signer and proxy +fn renew_testbed_ta(krill: &KrillRuntime) -> Result { + if let Err(e) = krill.ca_manager().ta_renew_testbed_ta(krill) { + error!("There was an issue renewing the testbed TA: {e}"); } + let weeks_to_resync = krill.config().ta_timing.mft_next_update_weeks / 2; + Ok(TaskResult::FollowUp( + Task::RenewTestbedTa, + in_weeks(weeks_to_resync), + )) +} - /// Let CAs that need it republish their CRL/MFT - async fn republish_if_needed(&self) -> Result { - // Note that CRL/MFT re-issuance is handled by the `CaObjects` - // companion struct, rather than the event-sourced `CertAuth`. - // Meaning... that we do not get to see an event in case there - // is an actual update and therefore we get no triggered task - // to synchronise with the repository. - // - // Instead we get back a list of CAs that had changes, and we need to - // schedule a synchronisation for each of them here. - let cas = self - .ca_manager - .republish_all(false) - .map_err(FatalError)?; - - for ca_handle in cas { - info!("Re-issued MFT and CRL for CA: {ca_handle}"); - - let ca_version = 0; // we use 0 because we don't need to wait for an updated CertAuth - self.tasks - .schedule( - Task::SyncRepo { - ca_handle, - ca_version, - }, - now(), - ) - .map_err(FatalError)?; - } +/// Try to synchronise the Trust Anchor Proxy with the *local* Signer - if +/// it exists in this server. +fn sync_ta_proxy_signer_if_possible( + krill: &KrillRuntime +) -> Result { + debug!("Synchronise Trust Anchor Proxy with Signer - if Signer is local."); + if let Err(e) = krill.ca_manager().sync_ta_proxy_signer_if_possible(krill) { + error!("There was an issue synchronising the TA Proxy and Signer: {e}"); + } + Ok(TaskResult::Done) +} - // check again in a short while.. no jitter needed as this is a cheap - // operation which is often a no-op. +/// Try to suspend children for a CA +fn suspend_children_if_needed( + krill: &KrillRuntime, ca_handle: CaHandle, started: Timestamp +) -> Result { + if krill.ca_manager().has_ca(&ca_handle).map_err(FatalError)? { + debug!( + "Verify if CA '{ca_handle}' has children that need to be suspended" + ); + krill.ca_manager().ca_suspend_inactive_children( + &ca_handle, started, krill.system_actor(), krill + ); Ok(TaskResult::FollowUp( - Task::RepublishIfNeeded, - in_minutes(SCHEDULER_INTERVAL_REPUBLISH_MINS), + Task::SuspendChildrenIfNeeded { ca_handle }, + in_hours(1), )) + } else { + debug!( + "Drop task to suspend children for removed CA {ca_handle}" + ); + Ok(TaskResult::Done) } +} - /// Update announcement info - async fn announcements_refresh(&self) -> Result { - if let Err(e) = self.bgp_analyser.update().await { - error!("Failed to update BGP announcements: {}", e) - } - - // check again in 10 minutes, note.. this is a no-op in case the - // actual update was less then 1 hour ago. - // See BGP_RIS_REFRESH_MINUTES constant. - Ok(TaskResult::FollowUp( - Task::RefreshAnnouncementsInfo, in_minutes(10) - )) +/// Let CAs that need it republish their CRL/MFT +fn republish_if_needed( + krill: &KrillRuntime +) -> Result { + // Note that CRL/MFT re-issuance is handled by the `CaObjects` + // companion struct, rather than the event-sourced `CertAuth`. + // Meaning... that we do not get to see an event in case there + // is an actual update and therefore we get no triggered task + // to synchronise with the repository. + // + // Instead we get back a list of CAs that had changes, and we need to + // schedule a synchronisation for each of them here. + let cas = krill.ca_manager().republish_all( + false, krill + ).map_err(FatalError)?; + + for ca_handle in cas { + info!("Re-issued MFT and CRL for CA: {ca_handle}"); + + let ca_version = 0; // we use 0 because we don't need to wait for + // an updated CertAuth + krill.tasks().schedule( + Task::SyncRepo { + ca_handle, + ca_version, + }, + now(), + ).map_err(FatalError)?; } - /// Let CAs that need it re-issue signed objects - async fn renew_objects_if_needed( - &self, - ) -> Result { - self.ca_manager.renew_objects_all(&self.system_actor).map_err( - FatalError - )?; + // check again in a short while.. no jitter needed as this is a cheap + // operation which is often a no-op. - // check again in a short while.. note that this is usually a cheap - // no-op - Ok(TaskResult::FollowUp( - Task::RenewObjectsIfNeeded, - in_minutes(SCHEDULER_INTERVAL_RENEW_MINS), - )) + Ok(TaskResult::FollowUp( + Task::RepublishIfNeeded, + in_minutes(SCHEDULER_INTERVAL_REPUBLISH_MINS), + )) +} + +/// Update announcement info +fn announcements_refresh( + krill: &KrillRuntime +) -> Result { + if let Err(e) = krill.bgp_analyser().update(krill) { + error!("Failed to update BGP announcements: {}", e) } - // Call update_snapshots on all AggregateStores and WalStores - fn update_snapshots(&self) -> Result { - fn update_aggregate_store_snapshots( - storage_uri: &Url, - namespace: &Ident, - ) { - match AggregateStore::::create(storage_uri, namespace, false) { - Err(e) => { - // Note: this is highly unlikely.. probably something else - // is broken and Krill would - // have panicked as a result already. + // check again in 10 minutes, note.. this is a no-op in case the + // actual update was less then 1 hour ago. + // See BGP_RIS_REFRESH_MINUTES constant. + Ok(TaskResult::FollowUp( + Task::RefreshAnnouncementsInfo, in_minutes(10) + )) +} + +/// Let CAs that need it re-issue signed objects +fn renew_objects_if_needed( + krill: &KrillRuntime +) -> Result { + krill.ca_manager().renew_objects_all( + krill.system_actor(), krill + ).map_err(FatalError)?; + + // check again in a short while.. note that this is usually a cheap + // no-op + Ok(TaskResult::FollowUp( + Task::RenewObjectsIfNeeded, + in_minutes(SCHEDULER_INTERVAL_RENEW_MINS), + )) +} + +// Call update_snapshots on all AggregateStores and WalStores +fn update_snapshots(krill: &KrillRuntime) -> Result { + fn update_aggregate_store_snapshots( + storage_uri: &Url, + namespace: &Ident, + ) { + match AggregateStore::::create(storage_uri, namespace, false) { + Err(e) => { + // Note: this is highly unlikely.. probably something else + // is broken and Krill would + // have panicked as a result already. + error!( + "Could not update snapshots for {namespace} will try \ + again in 24 hours. Error: {e}" + ); + } + Ok(store) => { + if let Err(e) = store.update_snapshots() { + // Note: this is highly unlikely.. probably something + // else is broken and Krill + // would have panicked as a result already. error!( - "Could not update snapshots for {namespace} will try again in 24 hours. Error: {e}" + "Could not update snapshots for {namespace} will \ + try again in 24 hours. Error: {e}" ); } - Ok(store) => { - if let Err(e) = store.update_snapshots() { - // Note: this is highly unlikely.. probably something - // else is broken and Krill - // would have panicked as a result already. - error!( - "Could not update snapshots for {namespace} will try again in 24 hours. Error: {e}" - ); - } else { - info!("Updated snapshots for {namespace}"); - } + else { + info!("Updated snapshots for {namespace}"); } } } + } - fn update_wal_store_snapshots( - storage_uri: &Url, - namespace: &Ident, - ) { - match WalStore::::create(storage_uri, namespace) { - Err(e) => { - // Note: this is highly unlikely.. probably something else - // is broken and Krill would - // have panicked as a result already. + fn update_wal_store_snapshots( + storage_uri: &Url, + namespace: &Ident, + ) { + match WalStore::::create(storage_uri, namespace) { + Err(e) => { + // Note: this is highly unlikely.. probably something else + // is broken and Krill would + // have panicked as a result already. + error!( + "Could not update snapshots for {namespace} will \ + try again in 24 hours. Error: {e}" + ); + } + Ok(store) => { + if let Err(e) = store.update_snapshots() { + // Note: this is highly unlikely.. probably something + // else is broken and Krill + // would have panicked as a result already. error!( - "Could not update snapshots for {namespace} will try again in 24 hours. Error: {e}" + "Could not update snapshots for {namespace} will \ + try again in 24 hours. Error: {e}" ); } - Ok(store) => { - if let Err(e) = store.update_snapshots() { - // Note: this is highly unlikely.. probably something - // else is broken and Krill - // would have panicked as a result already. - error!( - "Could not update snapshots for {namespace} will try again in 24 hours. Error: {e}" - ); - } - } } } + } - update_aggregate_store_snapshots::( - &self.config.storage_uri, - CASERVER_NS, - ); - update_aggregate_store_snapshots::( - &self.config.storage_uri, - SIGNERS_NS, - ); - update_aggregate_store_snapshots::( - &self.config.storage_uri, - PROPERTIES_NS, - ); - update_aggregate_store_snapshots::( - &self.config.storage_uri, - PUBSERVER_NS, - ); - - update_wal_store_snapshots::( - &self.config.storage_uri, - PUBSERVER_CONTENT_NS, - ); + update_aggregate_store_snapshots::( + &krill.config().storage_uri, CASERVER_NS, + ); + update_aggregate_store_snapshots::( + &krill.config().storage_uri, SIGNERS_NS, + ); + update_aggregate_store_snapshots::( + &krill.config().storage_uri, PROPERTIES_NS, + ); + update_aggregate_store_snapshots::( + &krill.config().storage_uri, PUBSERVER_NS, + ); + update_wal_store_snapshots::( + &krill.config().storage_uri, PUBSERVER_CONTENT_NS, + ); + + Ok(TaskResult::FollowUp(Task::UpdateSnapshots, in_hours(24))) +} - Ok(TaskResult::FollowUp(Task::UpdateSnapshots, in_hours(24))) +fn update_rrdp_if_needed( + krill: &KrillRuntime +) -> Result { + match krill.repo_manager().update_rrdp_if_needed() { + Err(e) => { + error!("Could not update RRDP deltas! Error: {e}"); + // Should we panic in this case? For now, just keep trying, + // this may be an issue that gets resolved + // (permission? disk space?) + Ok(TaskResult::Reschedule(in_hours(1))) + } + Ok(None) => { + // update was done, or there were no staged changes + Ok(TaskResult::Done) + } + Ok(Some(later_time)) => { + // Update was NOT done. There are staged changes, but the rrdp + // update interval has not yet passed. It can + // be done at later_time. + Ok(TaskResult::Reschedule(later_time.into())) + } } +} - fn update_rrdp_if_needed(&self) -> Result { - match self.repo_manager.update_rrdp_if_needed() { - Err(e) => { - error!("Could not update RRDP deltas! Error: {e}"); - // Should we panic in this case? For now, just keep trying, - // this may be an issue that gets resolved - // (permission? disk space?) - Ok(TaskResult::Reschedule(in_hours(1))) - } - Ok(None) => { - // update was done, or there were no staged changes - Ok(TaskResult::Done) - } - Ok(Some(later_time)) => { - // Update was NOT done. There are staged changes, but the rrdp - // update interval has not yet passed. It can - // be done at later_time. - Ok(TaskResult::Reschedule(later_time.into())) - } +fn resource_class_removed( + krill: &KrillRuntime, + ca_handle: CaHandle, + ca_version: u64, + parent: ParentHandle, + rcn: ResourceClassName, + revocation_requests: Vec, +) -> Result { + info!( + "Trigger send revoke requests for removed RC for '{ca_handle}' \ + under '{parent}'" + ); + + let requests = HashMap::from([(rcn, revocation_requests)]); + + if krill.ca_manager().has_ca(&ca_handle).map_err(FatalError)? { + let ca = krill.ca_manager().get_ca(&ca_handle).map_err(FatalError)?; + if ca.version() < ca_version { + // premature, we need to wait for the CA to be committed. + Ok(TaskResult::Reschedule(in_seconds(1))) } + else if krill.ca_manager().send_revoke_requests( + &ca_handle, &parent, requests, krill, + ).is_err() { + debug!( + "Could not revoke key for resource class removed by \ + parent - most likely already revoked." + ); + Ok(TaskResult::Done) + } + else { + debug!( + "Revoked keys for CA '{ca_handle}' under parent '{parent}'" + ); + Ok(TaskResult::Done) + } + } + else { + debug!( + "Dropping task for removed resource class of removed \ + CA {ca_handle}" + ); + Ok(TaskResult::Done) } +} - async fn resource_class_removed( - &self, - ca_handle: CaHandle, - ca_version: u64, - parent: ParentHandle, - rcn: ResourceClassName, - revocation_requests: Vec, - ) -> Result { +fn unexpected_key( + krill: &KrillRuntime, + ca_handle: CaHandle, + ca_version: u64, + rcn: ResourceClassName, + revocation_request: RevocationRequest, +) -> Result { + if krill.ca_manager().has_ca(&ca_handle).map_err(FatalError)? { info!( - "Trigger send revoke requests for removed RC for '{ca_handle}' under '{parent}'" + "Trigger sending revocation requests for unexpected key \ + with id '{}' in RC '{}'", + revocation_request.key(), + rcn ); + let ca = krill.ca_manager().get_ca(&ca_handle).map_err(FatalError)?; - let requests = HashMap::from([(rcn, revocation_requests)]); - - if self.ca_manager.has_ca(&ca_handle).map_err(FatalError)? { - let ca = self - .ca_manager - .get_ca(&ca_handle) - .map_err(FatalError)?; - if ca.version() < ca_version { - // premature, we need to wait for the CA to be committed. - Ok(TaskResult::Reschedule(in_seconds(1))) - } else if self - .ca_manager - .send_revoke_requests(&ca_handle, &parent, requests) - .await - .is_err() - { - debug!("Could not revoke key for resource class removed by parent - most likely already revoked."); - Ok(TaskResult::Done) - } else { - debug!( - "Revoked keys for CA '{ca_handle}' under parent '{parent}'" + if ca.version() < ca_version { + debug!("reschedule premature task"); + let next = in_seconds(100); + Ok(TaskResult::Reschedule(next)) + } + else { + if let Err(e) = krill.ca_manager().send_revoke_unexpected_key( + &ca_handle, rcn, revocation_request, krill + ) { + warn!( + "Could not revoke surplus key, most likely already \ + revoked by parent. Error was: {e}" ); - Ok(TaskResult::Done) } - } else { - debug!("Dropping task for removed resource class of removed CA {ca_handle}"); - Ok(TaskResult::Done) - } - } - async fn unexpected_key( - &self, - ca_handle: CaHandle, - ca_version: u64, - rcn: ResourceClassName, - revocation_request: RevocationRequest, - ) -> Result { - if self.ca_manager.has_ca(&ca_handle).map_err(FatalError)? { - info!( - "Trigger sending revocation requests for unexpected key with id '{}' in RC '{}'", - revocation_request.key(), - rcn - ); - let ca = self - .ca_manager - .get_ca(&ca_handle) - .map_err(FatalError)?; - - if ca.version() < ca_version { - debug!("reschedule premature task"); - let next = in_seconds(100); - Ok(TaskResult::Reschedule(next)) - } else { - if let Err(e) = self - .ca_manager - .send_revoke_unexpected_key( - &ca_handle, - rcn, - revocation_request, - ) - .await - { - warn!( - "Could not revoke surplus key, most likely already revoked by parent. Error was: {e}" - ); - } - - Ok(TaskResult::Done) - } - } else { - debug!( - "Dropping task for surplus key for removed CA {ca_handle}" - ); Ok(TaskResult::Done) } } + else { + debug!("Dropping task for surplus key for removed CA {ca_handle}"); + Ok(TaskResult::Done) + } } + diff --git a/src/server/taproxy.rs b/src/server/taproxy.rs index f64e1d6b9..7b61b4943 100644 --- a/src/server/taproxy.rs +++ b/src/server/taproxy.rs @@ -4,7 +4,8 @@ //! *except* for signing using the Trust Anchor private key. That //! function is handled by the Trust Anchor Signer instead. -use std::{collections::HashMap, fmt, sync::Arc}; +use std::fmt; +use std::collections::HashMap; use chrono::Duration; use log::{log_enabled, trace}; @@ -40,6 +41,8 @@ use crate::api::ta::{ }; use crate::constants::ta_resource_class_name; use crate::server::ca::UsedKeyState; +use crate::server::mq::TaskQueue; +use crate::server::runtime::KrillRuntime; use crate::tasigner::TaTimingConfig; @@ -114,6 +117,8 @@ impl eventsourcing::Aggregate for TrustAnchorProxy { type InitEvent = TrustAnchorProxyInitEvent; type Error = Error; + type Context<'a> = TrustAnchorProxyContext<'a>; + fn init( handle: &CaHandle, event: TrustAnchorProxyInitEvent, ) -> Self { @@ -129,12 +134,12 @@ impl eventsourcing::Aggregate for TrustAnchorProxy { } fn process_init_command( - command: TrustAnchorProxyInitCommand, + _command: TrustAnchorProxyInitCommand, + context: Self::Context<'_>, ) -> Result { Ok(TrustAnchorProxyInitEvent { id: { - command.into_details().signer.create_self_signed_id_cert()? - .into() + context.signer.create_self_signed_id_cert()?.into() } }) } @@ -238,6 +243,7 @@ impl eventsourcing::Aggregate for TrustAnchorProxy { fn process_command( &self, command: Self::Command, + _context: Self::Context<'_>, ) -> Result, Self::Error> { if log_enabled!(log::Level::Trace) { trace!( @@ -296,6 +302,29 @@ impl eventsourcing::Aggregate for TrustAnchorProxy { ) => self.process_give_child_response(child_handle, key), } } + + fn pre_save_events( + &self, events: &[Self::Event], context: TrustAnchorProxyContext, + ) -> Result<(), Self::Error> { + // We need to let the task queue handle events pre-save so that we + // can schedule: + // - publication on updates + // - signing by the Trust Anchor Signer when there are requests + // in testbed mode + context.tasks.ta_proxy_pre_save_events(self, events)?; + + Ok(()) + } + + fn post_save_events( + &self, events: &[Self::Event], context: TrustAnchorProxyContext, + ) { + // We need to let the task queue handle events post-save so that we + // can schedule: + // - re-sync for local children when the proxy has new responses + // AND is saved + context.tasks.ta_proxy_post_save_events(self, events); + } } // # Process command details @@ -328,7 +357,7 @@ impl TrustAnchorProxy { ) -> KrillResult> { if let Some(s) = &self.signer - && s.ta_cert_details.cert.key_identifier() == + && s.ta_cert_details.cert.key_identifier() == signer.ta_cert_details.cert.key_identifier() { // It is not possible to add a signer that has a different @@ -746,6 +775,29 @@ impl TrustAnchorProxy { } +//------------ TrustAnchorProxyContext --------------------------------------- + +/// The context for processing of trust anchor proxy commands. +/// +/// This is a separate type from [`KrillRuntime`] to simplify testing. This +/// shouldn’t be too bad, since it can be created from a `&KrillRuntime` via +/// a simple call to `into`. +#[derive(Clone, Copy)] +pub struct TrustAnchorProxyContext<'a> { + tasks: &'a TaskQueue, + signer: &'a KrillSigner, +} + +impl<'a> From<&'a KrillRuntime> for TrustAnchorProxyContext<'a> { + fn from(src: &'a KrillRuntime) -> Self { + Self { + tasks: src.tasks(), + signer: src.signer(), + } + } +} + + //------------ TrustAnchorProxyInitCommand ----------------------------------- pub type TrustAnchorProxyInitCommand = @@ -754,12 +806,11 @@ pub type TrustAnchorProxyInitCommand = impl TrustAnchorProxyInitCommand { pub fn make( id: MyHandle, - signer: Arc, actor: &Actor, ) -> Self { TrustAnchorProxyInitCommand::new( id, - TrustAnchorProxyInitCommandDetails { signer }, + TrustAnchorProxyInitCommandDetails, actor, ) } @@ -769,9 +820,7 @@ impl TrustAnchorProxyInitCommand { //------------ TrustAnchorProxyInitCommandDetails ---------------------------- #[derive(Clone, Debug)] -pub struct TrustAnchorProxyInitCommandDetails { - signer: Arc, -} +pub struct TrustAnchorProxyInitCommandDetails; impl fmt::Display for TrustAnchorProxyInitCommandDetails { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -1216,14 +1265,15 @@ impl eventsourcing::CommandDetails for TrustAnchorProxyCommandDetails { } -//----------------- TESTS ---------------------------------------------------- +//============ Tests ========================================================= + #[cfg(test)] mod tests { use rpki::ca::idexchange::{RepoInfo, ServiceUri}; use super::*; - use std::{sync::Arc, time::Duration}; + use std::time::Duration; use crate::{ api::admin::{PublicationServerInfo, RepositoryContact}, @@ -1236,8 +1286,9 @@ mod tests { config::ConfigDefaults, }; use crate::tasigner::{ - TrustAnchorSigner, TrustAnchorSignerInitCommand, - TrustAnchorSignerInitCommandDetails, TrustAnchorSignerCommand, + TrustAnchorSigner, TrustAnchorSignerContext, + TrustAnchorSignerInitCommand, TrustAnchorSignerInitCommandDetails, + TrustAnchorSignerCommand, }; #[test] @@ -1265,15 +1316,13 @@ mod tests { // We will import a TA key - this is only (supposed to be) // supported for the openssl signer let signers = ConfigDefaults::openssl_signer_only(); - let signer = Arc::new( - KrillSignerBuilder::new( - storage_uri, - Duration::from_secs(1), - &signers, - ) - .build() - .unwrap(), - ); + let signer = KrillSignerBuilder::new( + storage_uri, + Duration::from_secs(1), + &signers, + ).build().unwrap(); + + let tasks = TaskQueue::new(storage_uri).unwrap(); let timing = TaTimingConfig::default(); @@ -1282,11 +1331,20 @@ mod tests { let proxy_handle = CaHandle::new("proxy".into()); let proxy_init = TrustAnchorProxyInitCommand::make( proxy_handle.clone(), - signer.clone(), &actor, ); - ta_proxy_store.add(proxy_init).unwrap(); + let proxy_context = TrustAnchorProxyContext { + tasks: &tasks, + signer: &signer, + }; + let signer_context = TrustAnchorSignerContext::new( + &signer, timing + ); + + ta_proxy_store.add_with_context( + proxy_init, proxy_context + ).unwrap(); let repository = { let repo_info = RepoInfo::new( @@ -1313,7 +1371,9 @@ mod tests { repository, &actor, ); - let mut proxy = ta_proxy_store.command(add_repo_cmd).unwrap(); + let mut proxy = ta_proxy_store.command_with_context( + add_repo_cmd, proxy_context, + ).unwrap(); let signer_handle = CaHandle::new("signer".into()); let tal_https = @@ -1337,13 +1397,13 @@ mod tests { tal_rsync: tal_rsync.clone(), private_key_pem: Some(import_key_pem.to_string()), ta_mft_nr_override: Some(42), - timing, - signer: signer.clone(), }, &actor, ); - let mut ta_signer = ta_signer_store.add(signer_init_cmd).unwrap(); + let mut ta_signer = ta_signer_store.add_with_context( + signer_init_cmd, signer_context + ).unwrap(); let signer_info = ta_signer.get_signer_info(); let add_signer_cmd = TrustAnchorProxyCommand::add_signer( &proxy_handle, @@ -1351,7 +1411,9 @@ mod tests { &actor, ); - proxy = ta_proxy_store.command(add_signer_cmd).unwrap(); + proxy = ta_proxy_store.command_with_context( + add_signer_cmd, proxy_context, + ).unwrap(); // The initial signer starts off with a TA certificate // and a CRL and manifest with revision number 42, as specified in @@ -1370,7 +1432,9 @@ mod tests { &proxy_handle, &actor, ); - proxy = ta_proxy_store.command(make_publish_request_cmd).unwrap(); + proxy = ta_proxy_store.command_with_context( + make_publish_request_cmd, proxy_context, + ).unwrap(); let signed_request = proxy.get_signer_request(timing, &signer).unwrap(); @@ -1380,14 +1444,12 @@ mod tests { TrustAnchorSignerCommand::make_process_request_command( &signer_handle, signed_request.into(), - timing, Some(55), // override the next manifest number again - signer, &actor, ); - ta_signer = ta_signer_store - .command(ta_signer_process_request_command) - .unwrap(); + ta_signer = ta_signer_store.command_with_context( + ta_signer_process_request_command, signer_context, + ).unwrap(); let exchange = ta_signer.get_exchange(&request_nonce).unwrap(); let ta_proxy_process_signer_response_command = @@ -1397,9 +1459,9 @@ mod tests { &actor, ); - proxy = ta_proxy_store - .command(ta_proxy_process_signer_response_command) - .unwrap(); + proxy = ta_proxy_store.command_with_context( + ta_proxy_process_signer_response_command, proxy_context, + ).unwrap(); // The TA should have published again, the revision used for // manifest and crl will have been updated to the diff --git a/src/tasigner/config.rs b/src/tasigner/config.rs index e2ad1f962..44089c8d5 100644 --- a/src/tasigner/config.rs +++ b/src/tasigner/config.rs @@ -2,7 +2,6 @@ use std::{ fs::File, io::{self, Read}, path::PathBuf, - sync::Arc, }; use log::LevelFilter; @@ -229,7 +228,7 @@ impl Config { } // Signer support - pub fn signer(&self) -> Result, ConfigError> { + pub fn signer(&self) -> Result { // Assumes that Config::verify() has already ensured that the signer // configuration is valid and that Config::resolve() has been // used to update signer name references to resolve to the @@ -248,7 +247,7 @@ impl Config { ConfigError::Other(format!("Could not create KrillSigner: {e}")) })?; - Ok(Arc::new(signer)) + Ok(signer) } /// Returns a reference to the default signer configuration. diff --git a/src/tasigner/signer.rs b/src/tasigner/signer.rs index 418dfb6a3..97496b790 100644 --- a/src/tasigner/signer.rs +++ b/src/tasigner/signer.rs @@ -6,7 +6,7 @@ //! The proxy makes sign requests for the signer to sign. use super::*; -use std::{collections::HashMap, fmt, sync::Arc}; +use std::{collections::HashMap, fmt}; use chrono::SecondsFormat; use log::{log_enabled, trace}; @@ -46,6 +46,7 @@ use crate::api::ta::{ TrustAnchorSignerResponse, }; use crate::constants::ta_resource_class_name; +use crate::server::runtime::KrillRuntime; //------------ TrustAnchorSigner --------------------------------------------- @@ -85,6 +86,8 @@ impl eventsourcing::Aggregate for TrustAnchorSigner { type InitEvent = TrustAnchorSignerInitEvent; type Error = Error; + type Context<'a> = TrustAnchorSignerContext<'a>; + fn init(handle: &CaHandle, event: Self::InitEvent) -> Self { TrustAnchorSigner { handle: handle.clone(), @@ -99,27 +102,25 @@ impl eventsourcing::Aggregate for TrustAnchorSigner { fn process_init_command( command: TrustAnchorSignerInitCommand, + context: Self::Context<'_>, ) -> Result { let cmd = command.into_details(); - let timing = cmd.timing; - - let signer = cmd.signer; - let id = signer.create_self_signed_id_cert()?.into(); + let id = context.signer.create_self_signed_id_cert()?.into(); let proxy_id = cmd.proxy_id; let ta_cert_details = Self::create_ta_cert_details( cmd.repo_info, cmd.tal_https, cmd.tal_rsync, cmd.private_key_pem, - timing.certificate_validity_years, - &signer, + context.ta_timing_config.certificate_validity_years, + context.signer, )?; let objects = TrustAnchorObjects::create( &ta_cert_details.cert, cmd.ta_mft_nr_override.unwrap_or(1), - timing.mft_next_update_weeks, - &signer, + context.ta_timing_config.mft_next_update_weeks, + context.signer, )?; Ok(TrustAnchorSignerInitEvent { @@ -162,6 +163,7 @@ impl eventsourcing::Aggregate for TrustAnchorSigner { fn process_command( &self, command: Self::Command, + context: Self::Context<'_>, ) -> Result, Self::Error> { if log_enabled!(log::Level::Trace) { trace!( @@ -175,29 +177,26 @@ impl eventsourcing::Aggregate for TrustAnchorSigner { match command.into_details() { TrustAnchorSignerCommandDetails::TrustAnchorSignerRequest { signed_request, - ta_timing_config, ta_mft_number_override, - signer, } => self.process_signer_request( signed_request, - ta_timing_config, + context.ta_timing_config, ta_mft_number_override, - &signer, + context.signer, ), TrustAnchorSignerCommandDetails::TrustAnchorSignerReissueRequest { repo_info, tal_https, tal_rsync, - timing, - signer } => { - let years = timing.certificate_validity_years; + let years = + context.ta_timing_config.certificate_validity_years; let res = self.update_ta_cert_details( repo_info, tal_https, tal_rsync, years, - &signer + context.signer ); match res { Err(r) => Err(r), @@ -538,6 +537,33 @@ impl TrustAnchorSigner { } +//------------ TrustAnchorSignerContext -------------------------------------- + +#[derive(Clone, Copy)] +pub struct TrustAnchorSignerContext<'a> { + signer: &'a KrillSigner, + ta_timing_config: TaTimingConfig, +} + +impl<'a> TrustAnchorSignerContext<'a> { + pub fn new( + signer: &'a KrillSigner, + ta_timing_config: TaTimingConfig, + ) -> Self { + Self { signer, ta_timing_config } + } +} + +impl<'a> From<&'a KrillRuntime> for TrustAnchorSignerContext<'a> { + fn from(src: &'a KrillRuntime) -> Self { + Self { + signer: src.signer(), + ta_timing_config: src.config().ta_timing, + } + } +} + + //------------ TrustAnchorSignerInitCommand ---------------------------------- pub type TrustAnchorSignerInitCommand = @@ -554,8 +580,6 @@ pub struct TrustAnchorSignerInitCommandDetails { pub tal_rsync: uri::Rsync, pub private_key_pem: Option, pub ta_mft_nr_override: Option, - pub timing: TaTimingConfig, - pub signer: Arc, } impl fmt::Display for TrustAnchorSignerInitCommandDetails { @@ -582,9 +606,7 @@ impl TrustAnchorSignerCommand { pub fn make_process_request_command( id: &CaHandle, signed_request: TrustAnchorSignedRequest, - ta_timing_config: TaTimingConfig, ta_mft_number_override: Option, - signer: Arc, actor: &Actor, ) -> TrustAnchorSignerCommand { TrustAnchorSignerCommand::new( @@ -592,9 +614,7 @@ impl TrustAnchorSignerCommand { None, TrustAnchorSignerCommandDetails::TrustAnchorSignerRequest { signed_request, - ta_timing_config, ta_mft_number_override, - signer, }, actor, ) @@ -605,8 +625,6 @@ impl TrustAnchorSignerCommand { repo_info: RepoInfo, tal_https: Vec, tal_rsync: uri::Rsync, - ta_timing_config: TaTimingConfig, - signer: Arc, actor: &Actor, ) -> TrustAnchorSignerCommand { TrustAnchorSignerCommand::new( @@ -616,8 +634,6 @@ impl TrustAnchorSignerCommand { repo_info, tal_https, tal_rsync, - timing: ta_timing_config, - signer, }, actor ) @@ -631,16 +647,12 @@ impl TrustAnchorSignerCommand { pub enum TrustAnchorSignerCommandDetails { TrustAnchorSignerRequest { signed_request: TrustAnchorSignedRequest, - ta_timing_config: TaTimingConfig, ta_mft_number_override: Option, - signer: Arc, }, TrustAnchorSignerReissueRequest { repo_info: RepoInfo, tal_https: Vec, tal_rsync: uri::Rsync, - timing: TaTimingConfig, - signer: Arc, }, } diff --git a/src/upgrades/mod.rs b/src/upgrades/mod.rs index 52e6dd987..3cca8e6a3 100644 --- a/src/upgrades/mod.rs +++ b/src/upgrades/mod.rs @@ -34,7 +34,7 @@ use crate::{ }, config::Config, server::{ - manager::KrillManager, + manager::StartupManager, properties::PropertiesManager, }, upgrades::pre_0_14_0::{ @@ -643,7 +643,7 @@ pub trait UpgradeAggregateStorePre0_14 { let code_version = KrillVersion::code_version(); const VERSION: &Ident = Ident::make("version"); - if + if let Ok(Some(existing_migration_version)) = self.preparation_key_value_store().get::( None, VERSION @@ -1151,13 +1151,13 @@ fn record_preexisting_openssl_keys_in_signer_mapper( /// Should be called after the KrillServer is started, but before the web /// server is started and operators can make changes. -pub async fn post_start_upgrade( +pub fn post_start_upgrade( report: UpgradeReport, - server: &KrillManager, + server: &StartupManager, ) -> KrillResult<()> { if report.versions().from() < &KrillVersion::candidate(0, 9, 3, 2) { info!("Reissue ROAs on upgrade to force short EE certificate subjects in the objects"); - server.force_renew_roas().await?; + server.force_renew_roas()?; } for (ca, configs) in report.into_aspa_configs().into_iter() { @@ -1169,7 +1169,6 @@ pub async fn post_start_upgrade( server.ca_aspas_definitions_update( ca, aspa_updates, - server.system_actor(), )?; } diff --git a/tests/auth_check.rs b/tests/auth_check.rs index 16a1b5a40..35dc3ecf9 100644 --- a/tests/auth_check.rs +++ b/tests/auth_check.rs @@ -10,7 +10,7 @@ mod common; #[tokio::test] async fn auth_check() { - let (server, _tempdir) = common::KrillServer::start().await; + let server = common::KrillServer::start().await; // Get a client with a changed auth token. let client = KrillClient::new( @@ -40,7 +40,7 @@ async fn auth_check_unix() { use krill::cli::client::ServerUri; - let (mut config, _tempdir) = common::TestConfig::mem_storage() + let (mut config, tempdir) = common::TestConfig::mem_storage() .enable_testbed().enable_ca_refresh().finalize(); // The user that is executing the test gets read access to everything @@ -51,7 +51,11 @@ async fn auth_check_unix() { config.unix_socket = Some(file_sock.path().into()); config.unix_users = HashMap::from([(user.name, "readonly".to_string())]); - let _server = common::KrillServer::start_with_config_unix(config).await; + let _server = common::KrillServer::start_with_config_unix( + config, Some(tempdir) + ).await; + + crate::common::sleep_seconds(3).await; let client = KrillClient::new( ServerUri::try_from( @@ -73,4 +77,4 @@ async fn auth_check_unix() { ) ) ); -} \ No newline at end of file +} diff --git a/tests/benchmark.rs b/tests/benchmark.rs index 6019934d6..293ffb9da 100644 --- a/tests/benchmark.rs +++ b/tests/benchmark.rs @@ -17,7 +17,7 @@ async fn benchmark() { let ca_roas = 10; config.benchmark = Some(Benchmark { cas, ca_roas }); config.log_level = LevelFilter::Info; - let server = common::KrillServer::start_with_config(config).await; + let server = common::KrillServer::start_with_config(config, None).await; wait_for_nr_cas_under_testbed(server.client(), cas).await; // We expect all CAs, plus the testbed and the ta as publishers diff --git a/tests/client_coverage.rs b/tests/client_coverage.rs index 70f0fae81..12a4db61c 100644 --- a/tests/client_coverage.rs +++ b/tests/client_coverage.rs @@ -165,7 +165,7 @@ async fn client_coverage(server: KrillServer) { #[tokio::test] async fn http() { - let (server, _tempdir) = common::KrillServer::start_with_testbed().await; + let server = common::KrillServer::start_with_testbed().await; client_coverage(server).await; } @@ -174,7 +174,7 @@ async fn http() { async fn unix() { use std::collections::HashMap; - let (mut config, _tempdir) = common::TestConfig::mem_storage() + let (mut config, tempdir) = common::TestConfig::mem_storage() .enable_testbed().set_zero_port().enable_ca_refresh().finalize(); // The user that is executing the test gets access to everything @@ -184,6 +184,8 @@ async fn unix() { config.unix_socket_enabled = true; config.unix_socket = Some(file_sock.path().into()); config.unix_users = HashMap::from([(user.name, "admin".to_string())]); - let server = common::KrillServer::start_with_config_unix(config).await; + let server = common::KrillServer::start_with_config_unix( + config, Some(tempdir) + ).await; client_coverage(server).await; } diff --git a/tests/common.rs b/tests/common.rs index 9b054b137..d96a7c7a7 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -275,6 +275,7 @@ impl TestConfig { log_type, log_file: None, syslog_facility, + num_threads: None, admin_token, auth_type, #[cfg(feature = "multi-user")] @@ -321,10 +322,12 @@ impl TestConfig { /// A test Krill server. pub struct KrillServer { - join: JoinHandle<()>, + join: Option>, running: Option>, + exit: Option>, server_uri: ServerUri, client: KrillClient, + data_dir: Option, } impl KrillServer { @@ -332,50 +335,50 @@ impl KrillServer { /// /// The server will use memory storage. The function will start the /// server and wait for it to become ready. - pub async fn start() -> (Self, TempDir) { + pub async fn start() -> Self { let (config, data_dir) = TestConfig::mem_storage().finalize(); - (Self::start_with_config(config).await, data_dir) + Self::start_with_config(config, Some(data_dir)).await } /// Starts a test server with testbed enabled. /// /// The server will use memory storage. The function will start the /// server and wait for it to become ready. - pub async fn start_with_testbed() -> (Self, TempDir) { + pub async fn start_with_testbed() -> Self { let (config, data_dir) = TestConfig::mem_storage().enable_testbed().finalize(); - (Self::start_with_config(config).await, data_dir) + Self::start_with_config(config, Some(data_dir)).await } /// Starts a test server with testbed enabled and a modified config. pub async fn start_with_config_testbed( op: impl FnOnce(&mut Config) - ) -> (Self, TempDir) { + ) -> Self { let (mut config, data_dir) = TestConfig::mem_storage().enable_testbed().finalize(); op(&mut config); - (Self::start_with_config(config).await, data_dir) + Self::start_with_config(config, Some(data_dir)).await } /// Starts a test server with file storage with testbed enabled. /// /// The server will use memory storage. The function will start the /// server and wait for it to become ready. - pub async fn start_with_file_storage_and_testbed() -> (Self, TempDir) { + pub async fn start_with_file_storage_and_testbed() -> Self { let (config, data_dir) = TestConfig::file_storage().enable_testbed().finalize(); - (Self::start_with_config(config).await, data_dir) + Self::start_with_config(config, Some(data_dir)).await } /// Starts a second test server with testbed enabled. /// /// The server will use memory storage. The function will start the /// server and wait for it to become ready. - pub async fn start_second_with_testbed() -> (Self, TempDir) { + pub async fn start_second_with_testbed() -> Self { let (config, data_dir) = TestConfig::mem_storage() .alternative_port().enable_testbed().enable_second_signer() .finalize(); - (Self::start_with_config(config).await, data_dir) + Self::start_with_config(config, Some(data_dir)).await } /// Starts a publication daemon. @@ -384,7 +387,7 @@ impl KrillServer { /// udpates. pub async fn start_pubd( rrdp_delta_min_interval_seconds: u32 - ) -> (Self, TempDir) { + ) -> Self { let (mut config, data_dir) = TestConfig::mem_storage() .alternative_port() .enable_second_signer() // XXX Not sure why? @@ -392,67 +395,64 @@ impl KrillServer { config.rrdp_updates_config.rrdp_delta_interval_min_seconds = rrdp_delta_min_interval_seconds; let port = config.port; - let server = Self::start_with_config(config).await; + let server = Self::start_with_config(config, Some(data_dir)).await; server.pubserver_init(port).await; - (server, data_dir) + server } /// Starts a test server with the given config. /// /// This will start the server and wait for it to become ready. - pub async fn start_with_config(config: Config) -> Self { + pub async fn start_with_config( + config: Config, data_dir: Option, + ) -> Self { let server_uri = ServerUri::from_str( &format!( "https://{}:{}/", config.ip.first().unwrap(), config.port ) ).unwrap(); - let client = KrillClient::new( - server_uri.clone(), Some(config.admin_token.clone()) - ); - let (tx, running) = oneshot::channel(); - let mut res = Self { - join: tokio::spawn(async { - if let Err(err) = start_krill_daemon( - config.into(), Some(tx) - ).await { - error!("Krill failed to start: {err}"); - } - }), - running: Some(running), - server_uri, - client: client.unwrap(), - }; - res.ready().await; - res + Self::start_with_server_uri(config, server_uri, data_dir).await } /// Starts a test server with the given config over a UNIX socket /// /// This will start the server and wait for it to become ready. #[cfg(unix)] - pub async fn start_with_config_unix(config: Config) -> Self { + pub async fn start_with_config_unix( + config: Config, data_dir: Option + ) -> Self { let server_uri = ServerUri::from_str( &format!( "unix://{}", config.unix_socket().unwrap().display() ) ).unwrap(); + Self::start_with_server_uri(config, server_uri, data_dir).await + } + + /// Starts a test server with the given server URI. + async fn start_with_server_uri( + config: Config, server_uri: ServerUri, data_dir: Option + ) -> Self { let client = KrillClient::new( - server_uri.clone(), None + server_uri.clone(), Some(config.admin_token.clone()) ); - let (tx, running) = oneshot::channel(); + let (running_tx, running_rx) = oneshot::channel(); + let (exit_tx, exit_rx) = oneshot::channel(); let mut res = Self { - join: tokio::spawn(async { + join: Some(tokio::task::spawn_blocking(|| { if let Err(err) = start_krill_daemon( - config.into(), Some(tx) - ).await { + config, Some(running_tx), Some(exit_rx), + ) { error!("Krill failed to start: {err}"); } - }), - running: Some(running), + })), + running: Some(running_rx), + exit: Some(exit_tx), server_uri, client: client.unwrap(), + data_dir, }; res.ready().await; res @@ -462,6 +462,10 @@ impl KrillServer { &self.server_uri } + pub fn data_dir(&self) -> Option<&TempDir> { + self.data_dir.as_ref() + } + async fn ready(&mut self) { let running = match self.running.take() { Some(running) => running, @@ -489,9 +493,14 @@ impl KrillServer { } /// Aborts the server and waits for it to conclude cleanup. - pub async fn abort(self) { - self.join.abort(); - let _ = self.join.await; + pub async fn abort(mut self) { + if let Some(exit) = self.exit.take() { + exit.send(()).unwrap(); + } + if let Some(join) = self.join.take() { + join.abort(); + let _ = join.await; + } } /// Returns a Krill client for this server. @@ -507,6 +516,23 @@ impl KrillServer { } } +impl Drop for KrillServer { + fn drop(&mut self) { + if let Some(exit) = self.exit.take() { + exit.send(()).unwrap(); + } + if let Some(join) = self.join.take() { + join.abort(); + + // We can’t await the join handle here, so we will have to + // poll it. + while !join.is_finished() { + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + } +} + impl KrillServer { /// Creates a CA publishing in the built-in publisher. @@ -514,6 +540,12 @@ impl KrillServer { // Create the CA self.client.ca_add(ca.clone()).await.unwrap(); + // Add the CA as a publisher + self.register_ca_with_repo(ca).await; + } + + /// Registers a CA with the repository. + pub async fn register_ca_with_repo(&self, ca: &CaHandle) { // Add the CA as a publisher let request = self.client().repo_request(ca).await.unwrap(); self.client().publishers_add(request).await.unwrap(); @@ -530,6 +562,14 @@ impl KrillServer { /// Registers a CA with a parent managed by the same server. pub async fn register_ca_with_parent( &self, ca: &CaHandle, parent: &CaHandle, resources: &ResourceSet + ) { + self.register_ca_with_parent_nowait(ca, parent, resources).await; + assert!(self.wait_for_ca_resources(ca, resources).await); + } + + /// Registers a CA with a parent managed by the same server. + async fn register_ca_with_parent_nowait( + &self, ca: &CaHandle, parent: &CaHandle, resources: &ResourceSet ) { let request = self.client().child_request(ca).await.unwrap(); let response = self.add_child( @@ -538,7 +578,16 @@ impl KrillServer { self.client.parent_add( ca, api::admin::ParentCaReq { handle: parent.convert(), response } ).await.unwrap(); - assert!(self.wait_for_ca_resources(ca, resources).await); + } + + /// Creates a CA under testbed with the given resources. + pub async fn create_testbed_ca( + &self, ca: &CaHandle, resources: &ResourceSet + ) { + self.create_ca_with_repo(ca).await; + self.register_ca_with_parent_nowait( + ca, &ca_handle("testbed"), resources + ).await; } /// Add a child to the CA. diff --git a/tests/functional_aspa.rs b/tests/functional_aspa.rs index 96d385285..d8a34f9a3 100644 --- a/tests/functional_aspa.rs +++ b/tests/functional_aspa.rs @@ -31,7 +31,7 @@ mod common; /// ``` #[tokio::test] async fn functional_aspa() { - let (server, _tempdir) = common::KrillServer::start_with_testbed().await; + let server = common::KrillServer::start_with_testbed().await; let testbed = common::ca_handle("testbed"); let ca = common::ca_handle("CA"); diff --git a/tests/functional_bgpsec.rs b/tests/functional_bgpsec.rs index 59d18365e..87a87e2cd 100644 --- a/tests/functional_bgpsec.rs +++ b/tests/functional_bgpsec.rs @@ -25,7 +25,7 @@ mod common; /// ``` #[tokio::test] async fn functional_bgpsec() { - let (server, _tempdir) = common::KrillServer::start_with_testbed().await; + let server = common::KrillServer::start_with_testbed().await; let testbed = common::ca_handle("testbed"); let ca = common::ca_handle("CA"); diff --git a/tests/functional_ca_import.rs b/tests/functional_ca_import.rs index 847d29dc1..d684cef82 100644 --- a/tests/functional_ca_import.rs +++ b/tests/functional_ca_import.rs @@ -8,10 +8,12 @@ mod common; #[cfg(not(any(feature = "hsm-tests-kmip", feature = "hsm-tests-pkcs11")))] #[tokio::test] async fn functional_ca_import() { - let (mut config, _tempdir) = common::TestConfig::mem_storage().finalize(); + let (mut config, tempdir) = common::TestConfig::mem_storage().finalize(); config.ta_support_enabled = true; config.ta_signer_enabled = true; - let server = common::KrillServer::start_with_config(config).await; + let server = common::KrillServer::start_with_config( + config, Some(tempdir) + ).await; eprintln!(">>>> Import CA structure."); // We expect: diff --git a/tests/functional_delegated_ca_import.rs b/tests/functional_delegated_ca_import.rs index c2888f30f..1a7ed2b38 100644 --- a/tests/functional_delegated_ca_import.rs +++ b/tests/functional_delegated_ca_import.rs @@ -15,9 +15,8 @@ mod common; #[tokio::test] async fn functional_delegated_ca_import() { // Start two testbeds - let (server1, _tmp1) = common::KrillServer::start_with_testbed().await; - let (server2, _tmp2) - = common::KrillServer::start_second_with_testbed().await; + let server1 = common::KrillServer::start_with_testbed().await; + let server2 = common::KrillServer::start_second_with_testbed().await; let testbed = common::ca_handle("testbed"); let parent_1 = common::ca_handle("parent_1"); diff --git a/tests/functional_keyroll.rs b/tests/functional_keyroll.rs index 53a5a18e5..019b5c362 100644 --- a/tests/functional_keyroll.rs +++ b/tests/functional_keyroll.rs @@ -25,7 +25,7 @@ mod common; /// * revoke and retire old key, mft and crl #[tokio::test] async fn functional_keyroll() { - let (server, _tempdir) = common::KrillServer::start_with_testbed().await; + let server = common::KrillServer::start_with_testbed().await; let testbed = common::ca_handle("testbed"); let ca = common::ca_handle("CA"); diff --git a/tests/functional_old_data.rs b/tests/functional_old_data.rs index 871a7a7cb..fa1f9c9aa 100644 --- a/tests/functional_old_data.rs +++ b/tests/functional_old_data.rs @@ -47,11 +47,17 @@ async fn functional_old_data() { config.ta_timing = signer_config.ta_timing; eprintln!(">>>> Check whether Krill still starts."); - let server = common::KrillServer::start_with_config(config).await; + let server = common::KrillServer::start_with_config( + config, Some(tempdir) + ).await; eprintln!(">>>> Configure the TA signer."); let signer = TrustAnchorSignerManager::create(signer_config).unwrap(); + // XXX Wait for Krill to process pending tasks. + eprintln!(">>>> Wait a bit for Krill to catch up."); + common::sleep_millis(1000).await; + eprintln!(">>>> Make TA proxy signer request."); let request = server.client().ta_proxy_signer_make_request().await.unwrap(); @@ -67,5 +73,7 @@ async fn functional_old_data() { eprintln!(">>>> Fetch TAL and check it isn't empty."); assert!(!server.client().testbed_tal().await.unwrap().is_empty()); + + server.abort().await; } diff --git a/tests/functional_parent_child.rs b/tests/functional_parent_child.rs index 424d0938c..9a3f89e65 100644 --- a/tests/functional_parent_child.rs +++ b/tests/functional_parent_child.rs @@ -33,7 +33,7 @@ mod common; /// gracefully #[tokio::test] async fn functional_parent_child() { - let (server, tmpdir) + let server = common::KrillServer::start_with_file_storage_and_testbed().await; let testbed = common::ca_handle("testbed"); @@ -157,7 +157,7 @@ async fn functional_parent_child() { assert!(server.client().ca_details(&ca3).await.is_ok()); assert!( fs::metadata( - tmpdir.path().join("data/ca_objects/CA3.json") + server.data_dir().unwrap().path().join("data/ca_objects/CA3.json") ).unwrap().is_file() ); @@ -167,7 +167,7 @@ async fn functional_parent_child() { assert!(server.client().ca_details(&ca3).await.is_err()); assert_eq!( fs::metadata( - tmpdir.path().join("data/ca_objects/CA3.json") + server.data_dir().unwrap().path().join("data/ca_objects/CA3.json") ).unwrap_err().kind(), io::ErrorKind::NotFound ); diff --git a/tests/functional_roas.rs b/tests/functional_roas.rs index d474e96d0..6d9fb4f57 100644 --- a/tests/functional_roas.rs +++ b/tests/functional_roas.rs @@ -24,7 +24,7 @@ mod common; /// ``` #[tokio::test] async fn functional_roas() { - let (server, _tmpdir) = common::KrillServer::start_with_testbed().await; + let server = common::KrillServer::start_with_testbed().await; let testbed = common::ca_handle("testbed"); let ca = common::ca_handle("CA"); diff --git a/tests/functional_ta.rs b/tests/functional_ta.rs index 04e22368a..7d82b0b7a 100644 --- a/tests/functional_ta.rs +++ b/tests/functional_ta.rs @@ -23,11 +23,13 @@ mod common; /// [Krill as a Trust Anchor]: https://krill.docs.nlnetlabs.nl/en/stable/trust-anchor.html #[tokio::test] async fn functional_ta() { - let (mut config, _tempdir) = common::TestConfig::mem_storage() + let (mut config, tempdir) = common::TestConfig::mem_storage() .enable_second_signer().finalize(); let port = config.port; config.ta_support_enabled = true; - let server = common::KrillServer::start_with_config(config).await; + let server = common::KrillServer::start_with_config( + config, Some(tempdir) + ).await; eprintln!(">>>> Initialise TA proxy."); server.client().ta_proxy_init().await.unwrap(); diff --git a/tests/long_benchmark.rs b/tests/long_benchmark.rs new file mode 100644 index 000000000..3f77c8f29 --- /dev/null +++ b/tests/long_benchmark.rs @@ -0,0 +1,71 @@ +//! Performs a test with lots of CAs including re-signing all of them. +//! +//! This test is disabled by default because it tries to change the system +//! time. Do only run this on a dedicated VM or similar. + +use std::net::Ipv6Addr; +use std::time::SystemTime; +use futures_util::future::join_all; +use rpki::repository::resources::{ + AsBlocksBuilder, IpBlocksBuilder, Ipv4Blocks, Prefix, ResourceSet, +}; +use rpki::resources::Asn; + +mod common; + +#[tokio::test] +#[ignore] +async fn long_benchmark() { + let (config, data_dir) = common::TestConfig::file_storage() + .enable_testbed() + .finalize(); + let server = common::KrillServer::start_with_config( + config, Some(data_dir) + ).await; + let server = &server; + + eprintln!( + ">>>>> Storage directory: {}", + server.data_dir().unwrap().path().display() + ); + + let t0 = SystemTime::now(); + + eprintln!(">>>>> Creating CAs ..."); + + join_all((0..0x30u16).map(|i| async move { + let i = i << 8; + for j in 0..0xffu16 { + create_ca(server, i | j).await; + } + })).await; + + let t1 = SystemTime::now(); + eprintln!( + ">>>> CAs created in {:03} seconds.", + t1.duration_since(t0).unwrap().as_secs_f32() + ); + +} + + +/// Creates a CA based on a number. +/// +/// +async fn create_ca(server: &common::KrillServer, idx: u16) { + server.create_testbed_ca( + &common::ca_handle(&format!("ca_{idx}")), + &make_resources(idx), + ).await; +} + +/// Creates the resources based on a number. +fn make_resources(idx: u16) -> ResourceSet { + let mut asns = AsBlocksBuilder::new(); + asns.push(Asn::from_u32(idx.into())); + let asns = asns.finalize(); + let mut v6 = IpBlocksBuilder::new(); + v6.push(Prefix::new(Ipv6Addr::new(idx, 0, 0, 0, 0, 0, 0, 0), 48)); + let v6 = v6.finalize(); + ResourceSet::new(asns, Ipv4Blocks::empty(), v6.into()) +} diff --git a/tests/migrate_repository.rs b/tests/migrate_repository.rs index 1e7183e9b..9a312e546 100644 --- a/tests/migrate_repository.rs +++ b/tests/migrate_repository.rs @@ -25,14 +25,14 @@ async fn migrate_repository() { // Use a 5 second RRDP update interval for the Krill server, so that we // can also test here that the re-scheduling of delayed RRDP deltas // works. - let (server, _krilltmp) = common::KrillServer::start_with_config_testbed( + let server = common::KrillServer::start_with_config_testbed( |config| { config.rrdp_updates_config.rrdp_delta_interval_min_seconds = 5 } ).await; eprintln!(">>>> Start a secondary publication server."); - let (pubd, _pubtmp) = common::KrillServer::start_pubd(5).await; + let pubd = common::KrillServer::start_pubd(5).await; // Wait for the *testbed* CA to get its certificate, this means // that all CAs which are set up as part of krill_start under the diff --git a/tests/remote_parent_and_repo.rs b/tests/remote_parent_and_repo.rs index 67eea99b5..40218397b 100644 --- a/tests/remote_parent_and_repo.rs +++ b/tests/remote_parent_and_repo.rs @@ -13,9 +13,8 @@ mod common; #[tokio::test] async fn remote_parent_and_repo() { // Start two testbeds - let (server1, _tmp1) = common::KrillServer::start_with_testbed().await; - let (server2, _tmp2) - = common::KrillServer::start_second_with_testbed().await; + let server1 = common::KrillServer::start_with_testbed().await; + let server2 = common::KrillServer::start_second_with_testbed().await; let testbed = common::ca_handle("testbed"); let ca1 = common::ca_handle("CA1"); diff --git a/tests/suspend.rs b/tests/suspend.rs index 40e323cc7..d559f3de3 100644 --- a/tests/suspend.rs +++ b/tests/suspend.rs @@ -19,9 +19,11 @@ mod common; /// ``` #[tokio::test] async fn test_suspension() { - let (config, _tmpdir) = common::TestConfig::mem_storage() + let (config, tmpdir) = common::TestConfig::mem_storage() .enable_testbed().enable_suspend().finalize(); - let server = common::KrillServer::start_with_config(config).await; + let server = common::KrillServer::start_with_config( + config, Some(tmpdir) + ).await; let testbed = common::ca_handle("testbed"); let ca = common::ca_handle("CA"); diff --git a/tests/testbed.rs b/tests/testbed.rs index c92641854..2e80f3a08 100644 --- a/tests/testbed.rs +++ b/tests/testbed.rs @@ -12,9 +12,11 @@ mod common; #[tokio::test] async fn add_and_remove_certificate_authority() { - let (config, _tmpdir) = common::TestConfig::mem_storage() + let (config, tmpdir) = common::TestConfig::mem_storage() .enable_testbed().enable_ca_refresh().finalize(); - let server = common::KrillServer::start_with_config(config).await; + let server = common::KrillServer::start_with_config( + config, Some(tmpdir) + ).await; let ca = common::ca_handle("CA"); let ca_res = common::resources("AS1", "", "");