Various fixes and improvements.

- Configure Krill to run in test bed mode.
- Configure nginx to proxy specific Krill locations instead of arbitrary
routes.
- Configure nginx to serve Krill generated RRDP files.
- Configure Routinator to trust the nginx TLS certificate.
- Download the Krill TAL and install it in Routinator.
- Don't serve a test.txt file from nginx.
- Silence nginx warning about not being able to write to /var/log/.
- Add support for running Routinator in `vrps` mode and fetching the
results as JSON.
This commit is contained in:
Ximon Eighteen
2026-08-06 09:42:52 +02:00
parent c9067fdb82
commit d2da74487f
7 changed files with 310 additions and 97 deletions
Generated
+22 -11
View File
@@ -108,9 +108,9 @@ dependencies = [
[[package]]
name = "asn1-rs"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
@@ -241,7 +241,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
"bit-vec 0.6.3",
]
[[package]]
@@ -250,6 +250,15 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit-vec"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "2.11.0"
@@ -596,9 +605,9 @@ checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04"
[[package]]
name = "data-encoding"
version = "2.10.0"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
[[package]]
name = "der"
@@ -1638,6 +1647,7 @@ dependencies = [
name = "krilltest"
version = "0.1.0"
dependencies = [
"bytes",
"clap",
"hex",
"indoc",
@@ -1856,9 +1866,9 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
@@ -2422,9 +2432,9 @@ checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "rcgen"
version = "0.14.7"
version = "0.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e"
checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055"
dependencies = [
"pem",
"ring",
@@ -4278,10 +4288,11 @@ dependencies = [
[[package]]
name = "yasna"
version = "0.5.2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
dependencies = [
"bit-vec 0.9.1",
"time",
]
+6 -5
View File
@@ -8,14 +8,15 @@ rust-version.workspace = true
license.workspace = true
[dependencies]
krill = { path = ".." }
bytes = "1"
clap = { version = "*", features = ["cargo", "derive"]}
hex = "0.4.3"
indoc = "2.0.7"
krill = { path = ".." }
nix = { version = "*", features = ["signal"] }
rand = "0.10.2"
rcgen = "0.14"
reqwest = { version = "*", features = ["blocking"] }
tempfile = "*"
indoc = "2.0.7"
hex = "0.4.3"
rand = "0.10.2"
rpki = "0.19.3"
tempfile = "*"
+4 -7
View File
@@ -1,13 +1,11 @@
//! The test environment.
#![allow(unused)]
use std::collections::HashMap;
use std::net::IpAddr;
use std::path::PathBuf;
use super::krill::KrillServer;
use super::nginx::NginxServer;
use super::routinator::Routinator;
use std::collections::HashMap;
use std::net::IpAddr;
use std::path::PathBuf;
//------------ Environment ---------------------------------------------------
@@ -71,8 +69,7 @@ impl Environment {
krill_bin,
self.base_dir.join("krill"),
listen,
"https://testbed.example.com/rrdp/".into(),
"rsync://testbed.example.com/repo/".into(),
self.nginx.base_url(),
true,
);
let name = name.to_string();
+43 -20
View File
@@ -26,11 +26,16 @@ pub struct KrillServer {
/// The listen address for the server.
listen: (IpAddr, u16),
/// The URI for the RRDP server.
rrdp_uri: Https,
/// The listen address for the nginx proxy in front of Krill.
///
/// When Krill advertizes itself to the outside world, for example in a
/// TAL file, it needs to mention this address, not its own address.
public_base_url: String,
/// The URI for the rsync server.
rsync_uri: Rsync,
/// Whether or not this Krill instance should act as a testbed.
///
/// A testbed is a combined RPKI trust anchor and publication server.
is_testbed: bool,
/// The Krill process if it is running.
process: Option<process::Child>,
@@ -42,22 +47,19 @@ impl KrillServer {
krill_bin: String,
server_dir: PathBuf,
listen: (IpAddr, u16),
rrdp_uri: String,
rsync_uri: String,
enable_ta: bool,
public_base_url: String,
is_testbed: bool,
) -> Self {
let rrdp_uri = Https::from_string(rrdp_uri).unwrap();
let rsync_uri = Rsync::from_string(rsync_uri).unwrap();
let mut res = Self {
krill: krill_bin,
server_dir,
listen,
rrdp_uri,
rsync_uri,
public_base_url,
is_testbed,
process: None,
};
fs::create_dir_all(&res.server_dir).unwrap();
res.make_conf(enable_ta);
res.make_conf();
res.start();
res
}
@@ -84,15 +86,29 @@ impl KrillServer {
fn pid_file(&self) -> PathBuf {
self.server_dir.join("krill.pid")
}
/// Returns the base URL at which Krill can be contacted by clients.
///
/// If Krill is fronted by a proxy like nginx this will point to the
/// proxy rather than to Krill itself.
fn public_base_url(&self) -> &str {
&self.public_base_url
}
/// Returns the public URL at which the Trust Anchor Locator can be found.
pub fn tal_url(&self) -> String {
format!("{}ta/ta.tal", self.public_base_url())
}
}
/// # Setup
impl KrillServer {
/// Creates the Krill config.
fn make_conf(&self, enable_ta: bool) {
fn make_conf(&self) {
let mut conf = File::create(self.config_path()).unwrap();
// Create string representations of configuration values.
let service_uri = &self.public_base_url;
let storage_uri =
format!("memory://{}", hex::encode(rand::random::<[u8; 8]>()));
// tls_keys_dir, repo_dir and pid_file must be set because we are
@@ -122,16 +138,23 @@ impl KrillServer {
bgp_riswhois_enabled = false
post_protocol_msg_timeout_seconds = 10
unix_socket = "{unix_socket}"
service_uri = "{service_uri}"
"#
);
if enable_ta {
let rrdp_base_uri = &self.rrdp_uri;
let rsync_jail = &self.rsync_uri;
let ta_aia =
format!("rsync://{}/ta/ta.cer", rsync_jail.authority());
let ta_uri =
format!("https://{}/ta/ta.cer", rrdp_base_uri.authority());
if self.is_testbed {
// A note about rsync: we have to configure Krill with an rsync
// URI, but Krill itself is not capable of acting as an rsync
// server. In the rsync URIs below no port number is specified,
// thus multiple Krill instances created with is_testbed = true
// would refer to the same rsync server. However at the time of
// writing there is no rsync server in our test setup and RPs are
// expected to use RRDP rather than rsync, i.e. these URIs have to
// be specified but will not be used.
let rsync_jail = format!("rsync://{}/repo/", self.listen.0);
let rrdp_base_uri = format!("{}rrdp/", self.public_base_url());
let ta_aia = format!("rsync://{}/ta/ta.cer", self.listen.0);
let ta_uri = format!("{}ta/ta.cer", self.public_base_url());
writedoc!(
conf,
+65 -37
View File
@@ -1,10 +1,10 @@
//! Controlling an Nginx server.
//! Controlling an Nginx server.
use crate::utils::fmt::WriteOrPanic;
use std::{fs, process};
use std::fs::File;
use std::net::IpAddr;
use std::path::PathBuf;
use crate::utils::fmt::WriteOrPanic;
use std::{fs, process};
use indoc::writedoc;
@@ -21,9 +21,6 @@ pub struct NginxServer {
/// The listen address for the server.
listen: (IpAddr, u16),
/// A map between path prefixes and directories.
routes: Vec<(String, PathBuf)>,
/// The Nginx process if it is running.
process: Option<process::Child>,
}
@@ -39,22 +36,14 @@ impl NginxServer {
nginx: nginx_bin,
server_dir,
listen,
routes: Default::default(),
process: None,
};
fs::create_dir_all(res.tls_path()).unwrap();
res.make_tls();
fs::create_dir_all(res.root_path()).unwrap();
fs::write(
res.root_path().join("test.txt"),
"test"
).unwrap();
res.make_conf();
res.start();
res
}
}
@@ -75,7 +64,6 @@ impl Drop for NginxServer {
}
}
/// # Paths to things
impl NginxServer {
/// Returns the directory for the TLS configuration.
@@ -105,11 +93,14 @@ impl NginxServer {
/// Returns the server root path.
fn root_path(&self) -> PathBuf {
self.server_dir.join("http")
// TODO: Don't hard-code the location of a specific Krill instance
// RRDP data directory here, as we may want to serve data published by
// multiple Krill instances.
self.server_dir.join("../krill/data/repo/")
}
/// Returns the base URL of the server.
pub fn url(&self) -> String {
pub fn base_url(&self) -> String {
match self.listen.0 {
IpAddr::V4(addr) => {
format!("https://{}:{}/", addr, self.listen.1)
@@ -119,11 +110,6 @@ impl NginxServer {
}
}
}
/// Returns the URL of the test file.
pub fn test_url(&self) -> String {
format!("{}test.txt", self.url())
}
}
/// # Setup
@@ -154,19 +140,6 @@ impl NginxServer {
let ssl_certificate_key = self.tls_key_path().display().to_string();
let tmp = self.tmp_path().display().to_string();
let mut locations = String::new();
for (location, alias) in &self.routes {
let alias = alias.display().to_string();
writedoc!(
locations,
r#"
location {location} {{
alias {alias};
}}
"#
);
}
// Write the NGINX config file using the strings we just created.
writedoc!(
conf,
@@ -174,6 +147,12 @@ impl NginxServer {
events {{}}
daemon off;
pid {tmp}/pid;
# error_log set here occurs too late to prevent a warning
# during nginx startup about not being able to write to
# /var/log/nginx/error.log, to solve that we pass -e when
# launching nginx. We do however need to specify error_log
# here if we want to control the level at which nginx logs.
# error_log /dev/stdout debug;
http {{
proxy_temp_path {tmp};
fastcgi_temp_path {tmp};
@@ -187,7 +166,56 @@ impl NginxServer {
ssl_certificate {ssl_certificate};
ssl_certificate_key {ssl_certificate_key};
client_body_temp_path {tmp};
{locations}
# From Krill docs:
client_max_body_size 128m;
# TODO: Make the location blocks below dynamically
# generated rather than hard-coded, so that we can
# support multiple Krill instances behind the nginx.
# Proxy RFC 8181 publication server requests to Krill.
location /rfc8181 {{
proxy_pass https://127.0.0.1:3001/rfc8181;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# krill does not use a valid certificate/tls is handled by nginx
proxy_ssl_verify off;
}}
# Proxy Krill API requests to Krill.
location /api {{
proxy_pass https://127.0.0.1:3001/api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# allow IPv4 and IPv6 documentation ranges
# allow 192.0.2.0/24;
# allow 2001:0db8::/32;
# deny all;
# krill does not use a valid certificate/tls is handled by nginx
proxy_ssl_verify off;
}}
# Serve RRDP files generated by Krill
# This is handled by the 'root' directive above.
location /ta {{
proxy_pass https://127.0.0.1:3001/ta;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# krill does not use a valid certificate/tls is handled by nginx
proxy_ssl_verify off;
}}
}}
}}
"#
+96 -7
View File
@@ -1,9 +1,15 @@
//! Controlling a Routinator.
#![allow(unused)]
use std::fs;
use std::path::PathBuf;
use crate::utils::fmt::WriteOrPanic;
use std::fs::File;
use std::net::IpAddr;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::{fs, process};
use indoc::writedoc;
//------------ Routinator ----------------------------------------------------
@@ -15,8 +21,8 @@ pub struct Routinator {
/// The directory where Routinator will keep its stuff.
base_dir: PathBuf,
/// Path to the TLS certificate used by Nginx server.
tls_cert_path: PathBuf,
/// The path to the TLS certificate used by the RRDP server.
rrdp_tls_cert_path: PathBuf,
}
impl Routinator {
@@ -26,16 +32,99 @@ impl Routinator {
pub fn new(
routinator_bin: PathBuf,
base_dir: PathBuf,
tls_cert_path: PathBuf,
rrdp_tls_cert_path: PathBuf,
) -> Self {
let res = Self {
let mut res = Self {
routinator: routinator_bin,
base_dir,
tls_cert_path,
rrdp_tls_cert_path,
};
fs::create_dir(&res.base_dir).unwrap();
fs::create_dir(&res.extra_tals_dir()).unwrap();
res.make_conf();
res
}
}
/// # Paths and URLs
impl Routinator {
/// Returns the path to the Routinator config file.
fn config_path(&self) -> PathBuf {
self.base_dir.join("routinator.conf")
}
/// Returns the Routinator repository directory
fn repo_dir(&self) -> PathBuf {
self.base_dir.join("rpki-cache")
}
fn extra_tals_dir(&self) -> PathBuf {
self.base_dir.join("extra-tals")
}
}
/// # Setup
impl Routinator {
fn make_conf(&self) {
let mut conf = File::create(self.config_path()).unwrap();
// Create string representations of configuration values.
let repository_dir = self.repo_dir().display().to_string();
let rrdp_root_cert_path =
self.rrdp_tls_cert_path.display().to_string();
// Write the Routinator config file using the strings we just created.
writedoc!(
conf,
r#"
repository-dir = "{repository_dir}"
log = "stderr"
log-level = "info"
rrdp-root-certs = ["{rrdp_root_cert_path}"]
allow-dubious-hosts = true
#disable-rsync = true
"#
);
}
/// Store the given TAL bytes as a file in the correct location so that
/// Routinator will use it.
pub fn install_tal(&self, name: &str, bytes: &[u8]) {
let mut f = File::create_new(
self.extra_tals_dir().join(name).with_added_extension("tal"),
)
.unwrap();
std::io::Write::write_all(&mut f, bytes).unwrap();
}
/// Update the local repository, validate the ROAs and return the VRPs.
///
/// TODO: Deserialize the JSON into Rust data types.
pub fn vrps(&self) -> Vec<u8> {
let output = process::Command::new(&self.routinator)
.args([
// Tell Routinator where to find its config file.
"-c",
&self.config_path().display().to_string(),
// Don't use any of the default Trust Anchor Locators.
"--no-rir-tals",
// Use only the TAL of our Krill instance.
"--extra-tals-dir",
&self.extra_tals_dir().display().to_string(),
// Do a one time validation run.
"vrps",
// Output in JSON format.
"--format",
"json",
// Output to standard output.
"--output",
"-",
])
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
output.stdout
}
}
+74 -10
View File
@@ -4,12 +4,18 @@
//! scenarios involve setting up one or more Krill instances, issue commands
//! to them, and then verifying the results by checking the data set output
//! by a Routinator validation run.
use krilltest::environment::Environment;
use std::io::Read;
use std::io::Write;
use std::net::IpAddr;
use std::path::Path;
use std::path::PathBuf;
use std::thread::sleep;
use std::time::Duration;
use clap::Parser;
use clap::crate_version;
use krilltest::environment::Environment;
use std::net::IpAddr;
use std::path::PathBuf;
use tempfile::TempDir;
//------------ main ----------------------------------------------------------
@@ -31,15 +37,73 @@ fn main() {
args.routinator,
);
let some_free_port = args.rrdp_port + 1;
let krill_listen = (args.listen_addr, some_free_port);
let _krill = environment.add_krill("first", args.krill, krill_listen);
// Add a Krill test bed to the test environment and get the location of
// Trust Anchor Locator so that we can install it for Routinator to use.
let tal_url = {
// TODO: Allocate a port that isn't already in use, don't just do +1.
let some_free_port1 = args.rrdp_port + 1;
let krill_listen = (args.listen_addr, some_free_port1);
let krill = environment.add_krill("first", args.krill, krill_listen);
krill.tal_url()
};
eprintln!("Hit enter to quit.");
// Load the TLS certificate that can be used to verify that a TLS
// connection to Krill can be trusted. The alternative would be to use the
// reqwest `danger_accept_invalid_certs(true)` functionality but this is
// more correct, but also more verbose.
let tls_cert = load_tls_cert(&environment.nginx().tls_cert_path());
let mut buffer = String::new();
let stdin = std::io::stdin();
stdin.read_line(&mut buffer).unwrap();
// Fetch the Krill TAL and install it in the Routinator extra tals
// directory.
let tal_bytes = fetch_url(tal_url, tls_cert, 5);
// Configure Routinator to use the Krill TAL.
environment.routinator().install_tal("Krill", &tal_bytes);
// Do a Routinator validation run and fetch the available VRPs.
std::io::stdout()
.write_all(&environment.routinator().vrps())
.unwrap();
// TODO: Actually add ROAs to Krill and verify that the fetched VRPs are
// correct.
}
fn load_tls_cert(tls_cert_path: &Path) -> reqwest::Certificate {
let mut f = std::fs::File::open(tls_cert_path).unwrap();
let mut tls_ca_cert_pem_bytes = vec![];
f.read_to_end(&mut tls_ca_cert_pem_bytes).unwrap();
drop(f);
let tls_ca_cert =
reqwest::Certificate::from_pem(&tls_ca_cert_pem_bytes).unwrap();
tls_ca_cert
}
fn fetch_url(
url: String,
tls_cert: reqwest::Certificate,
max_tries: u8,
) -> bytes::Bytes {
let trusting_client = reqwest::blocking::Client::builder()
.tls_certs_only([tls_cert])
.build()
.unwrap();
let mut tries_left = max_tries;
while tries_left > 0 {
match trusting_client.get(&url).send() {
Ok(bytes) => {
return bytes.bytes().unwrap();
}
Err(err) => {
eprintln!(
"{url} not yet available, will retry in 1 second: {err}"
);
sleep(Duration::from_secs(1));
}
}
tries_left -= 1;
}
unreachable!();
}
//------------ Args ----------------------------------------------------------