mod common;
use std::sync::Arc;
use acme_proxy_core::config::Config;
use acme_proxy_server::RoleSet;
use acme_proxy_server::reload::ReloadError;
use acme_proxy_server::reload::ReloadHandle;
use acme_proxy_server::serve_on_with_reloads;
use acme_proxy_server::sockets::Sockets as ServerSockets;
use acme_proxy_store::db::Database;
use common::TempDir;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
struct Server {
acme: std::net::SocketAddr,
admin: Option<std::net::SocketAddr>,
reload: ReloadHandle,
shutdown: Option<tokio::sync::oneshot::Sender<()>>,
handle: tokio::task::JoinHandle<anyhow::Result<()>>,
}
impl Server {
async fn stop(mut self) {
if let Some(shutdown) = self.shutdown.take() {
let _ = shutdown.send(());
}
let _ = tokio::time::timeout(std::time::Duration::from_secs(10), self.handle).await;
}
}
fn write_config(dir: &TempDir, tls: bool, website: &str, extra: &str) {
write_config_with_admin(dir, tls, false, website, extra);
}
fn write_config_with_admin(dir: &TempDir, tls: bool, admin: bool, website: &str, extra: &str) {
write_config_on(
dir,
Sockets {
server: "127.0.0.1:0",
admin: "127.0.0.1:3001",
admin_enabled: admin,
},
tls,
website,
extra,
);
}
struct Sockets<'a> {
server: &'a str,
admin: &'a str,
admin_enabled: bool,
}
fn write_config_on(dir: &TempDir, sockets: Sockets<'_>, tls: bool, website: &str, extra: &str) {
let ca = dir.join("ca");
let Sockets {
server,
admin,
admin_enabled,
} = sockets;
let body = format!(
r#"
[database]
url = "sqlite://{dir}/reload.db"
[server]
bind_address = "{server}"
base_url = "http://localhost:3000"
[server.tls]
enabled = {tls}
cert_path = "{dir}/server.pem"
key_path = "{dir}/server.key"
[admin]
enabled = {admin_enabled}
bind_address = "{admin}"
login_max_attempts = 2
[meta]
website = "{website}"
[profiles.default]
signer.local_ca.cert_path = "{ca}.pem"
signer.local_ca.key_path = "{ca}.key"
signer.local_ca.crl_path = "{ca}.crl"
{extra}
"#,
dir = dir.path().display(),
ca = ca.display(),
);
std::fs::write(dir.join("config.toml"), body).unwrap();
}
async fn free_port() -> u16 {
TcpListener::bind("127.0.0.1:0")
.await
.unwrap()
.local_addr()
.unwrap()
.port()
}
async fn wait_until_refused(port: u16) -> bool {
for _ in 0..100 {
match TcpStream::connect(("127.0.0.1", port)).await {
Err(_) => return true,
Ok(_) => tokio::time::sleep(std::time::Duration::from_millis(20)).await,
}
}
false
}
fn load_from(dir: &TempDir) -> Config {
unsafe {
std::env::set_var("ACME_PROXY_CONFIG", dir.join("config").to_str().unwrap());
}
Config::load().expect("the configuration must load")
}
async fn boot(config: Config, with_admin: bool) -> Server {
boot_as(RoleSet::default(), config, with_admin).await
}
async fn boot_as(roles: RoleSet, config: Config, with_admin: bool) -> Server {
let database = Arc::new(
Database::connect_and_migrate(&config.database.url)
.await
.expect("the database must open"),
);
let acme_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let acme = acme_listener.local_addr().unwrap();
let (admin_listener, admin) = match with_admin {
false => (None, None),
true => {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
(Some(listener), Some(addr))
}
};
let (reload, reloads) = acme_proxy_server::reload::channel();
let (shutdown, rx) = tokio::sync::oneshot::channel::<()>();
let handle = tokio::spawn(serve_on_with_reloads(
roles,
Arc::new(config),
database,
ServerSockets {
acme: Some(acme_listener),
admin: admin_listener,
metrics: None,
},
async {
let _ = rx.await;
},
reloads,
));
Server {
acme,
admin,
reload,
shutdown: Some(shutdown),
handle,
}
}
async fn status_of(addr: std::net::SocketAddr, path: &str) -> String {
let mut stream = TcpStream::connect(addr).await.unwrap();
stream
.write_all(
format!("GET {path} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
.as_bytes(),
)
.await
.unwrap();
let mut response = Vec::new();
stream.read_to_end(&mut response).await.unwrap();
String::from_utf8_lossy(&response[..response.len().min(64)])
.lines()
.next()
.unwrap_or_default()
.to_string()
}
async fn get(addr: std::net::SocketAddr, path: &str) -> String {
let mut stream = TcpStream::connect(addr).await.unwrap();
stream
.write_all(
format!("GET {path} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
.as_bytes(),
)
.await
.unwrap();
let mut response = String::new();
stream.read_to_string(&mut response).await.unwrap();
response
}
#[tokio::test]
async fn a_reload_changes_what_the_running_socket_answers() {
let dir = TempDir::new("reload-live");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
let before = get(server.acme, "/profile/default/directory").await;
assert!(before.contains("https://before.example"), "{before}");
write_config(&dir, false, "https://after.example", "");
let report = server.reload.reload().await.expect("the reload must apply");
assert_eq!(report.generation, 2, "the first reload is generation two");
assert_eq!(report.profiles, vec!["default".to_string()]);
let after = get(server.acme, "/profile/default/directory").await;
assert!(
after.contains("https://after.example"),
"the socket must serve the new configuration: {after}"
);
assert!(
!after.contains("https://before.example"),
"and not the old one: {after}"
);
write_config(&dir, false, "https://third.example", "");
assert_eq!(server.reload.reload().await.unwrap().generation, 3);
server.stop().await;
}
#[tokio::test]
async fn a_jobs_change_reloads_and_rebuilds_the_registry() {
let dir = TempDir::new("reload-jobs");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
write_config(
&dir,
false,
"https://before.example",
"[jobs]\n\
poll_interval_ms = 250\n\
max_concurrent = 3\n\
max_attempts = 9\n\
retry_base_seconds = 15\n\
retry_max_seconds = 900\n\
lease_seconds = 120\n\
retention_days = 0\n",
);
let report = server
.reload
.reload()
.await
.expect("no `[jobs]` key is frozen any more");
assert_eq!(report.generation, 2);
assert!(
!report.job_kinds.contains(&"job_retention_sweep"),
"`retention_days = 0` unregisters the sweep, so the new registry must \
not carry it: {:?}",
report.job_kinds,
);
write_config(
&dir,
false,
"https://before.example",
"[jobs]\nretention_days = 3\n",
);
let report = server
.reload
.reload()
.await
.expect("and back the other way");
assert_eq!(report.generation, 3);
assert!(
report.job_kinds.contains(&"job_retention_sweep"),
"{:?}",
report.job_kinds,
);
server.stop().await;
}
#[tokio::test]
async fn a_logging_change_reloads_and_moves_the_level() {
use tracing::level_filters::LevelFilter;
unsafe { std::env::remove_var("RUST_LOG") };
let dir = TempDir::new("reload-logging");
write_config(
&dir,
false,
"https://before.example",
r#"
[logging]
filter = "acme_proxy=info"
target = "stderr"
"#,
);
let config = load_from(&dir);
acme_proxy_server::logging::init_logging(&config.logging, None)
.expect("the subscriber must install");
assert_eq!(LevelFilter::current(), LevelFilter::INFO);
let server = boot(config, false).await;
write_config(
&dir,
false,
"https://after.example",
r#"
[logging]
filter = "acme_proxy=debug"
target = "stderr"
json_format = true
"#,
);
let report = server.reload.reload().await.expect("the reload must apply");
assert_eq!(report.generation, 2);
assert!(
report.logging_reloaded,
"the handle is installed, so the swap must have taken",
);
assert_eq!(
LevelFilter::current(),
LevelFilter::DEBUG,
"the raised level must reach callsites that already exist",
);
let after = get(server.acme, "/profile/default/directory").await;
assert!(after.contains("https://after.example"), "{after}");
server.stop().await;
}
#[tokio::test]
async fn a_frozen_key_is_refused_and_the_old_configuration_keeps_serving() {
let dir = TempDir::new("reload-frozen");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
let body = std::fs::read_to_string(dir.join("config.toml"))
.unwrap()
.replace("reload.db", "somewhere-else.db")
.replace("https://before.example", "https://after.example");
std::fs::write(dir.join("config.toml"), body).unwrap();
let error = server
.reload
.reload()
.await
.expect_err("a changed database.url must be refused");
match &error {
ReloadError::Frozen { key, .. } => assert_eq!(key, "database.url"),
other => panic!("expected a frozen-key refusal, got {other}"),
}
assert_eq!(error.kind(), "frozen_key");
let after = get(server.acme, "/profile/default/directory").await;
assert!(
after.contains("https://before.example"),
"a refused reload must change nothing at all: {after}"
);
let body = std::fs::read_to_string(dir.join("config.toml"))
.unwrap()
.replace("somewhere-else.db", "reload.db");
std::fs::write(dir.join("config.toml"), body).unwrap();
let report = server
.reload
.reload()
.await
.expect("the fixed file applies");
assert_eq!(report.generation, 2);
server.stop().await;
}
#[tokio::test]
async fn a_configuration_that_does_not_build_leaves_the_old_one_running() {
let dir = TempDir::new("reload-unbuildable");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
write_config(
&dir,
false,
"https://after.example",
r#"
[profiles.default.filter]
rules = ["only"]
[profiles.default.filter.rule.only]
when = "nothing-defines-this"
then = "allow"
"#,
);
let error = server
.reload
.reload()
.await
.expect_err("an unbuildable filter must not be applied");
assert_eq!(error.kind(), "build_failed");
let after = get(server.acme, "/profile/default/directory").await;
assert!(
after.contains("https://before.example"),
"the old generation must still be serving: {after}"
);
server.stop().await;
}
#[tokio::test]
async fn a_reloaded_certificate_reaches_the_next_connection() {
use tokio_rustls::TlsConnector;
use tokio_rustls::rustls::pki_types::ServerName;
async fn presented_certificate(addr: std::net::SocketAddr) -> Vec<u8> {
let client =
acme_proxy_net::challenge::tls_alpn_01::accept_any_client_config(&[b"http/1.1"])
.expect("a client config");
let stream = TcpStream::connect(addr).await.unwrap();
let tls = TlsConnector::from(client)
.connect(ServerName::try_from("localhost").unwrap(), stream)
.await
.unwrap();
tls.get_ref()
.1
.peer_certificates()
.expect("the server presented a certificate")[0]
.to_vec()
}
let dir = TempDir::new("reload-tls");
write_config(&dir, true, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
let port = server.acme.port();
let before = presented_certificate(server.acme).await;
std::fs::remove_file(dir.join("server.pem")).unwrap();
std::fs::remove_file(dir.join("server.key")).unwrap();
let report = server.reload.reload().await.expect("the reload must apply");
assert!(report.tls_reloaded, "the TLS cell was republished");
let after = presented_certificate(server.acme).await;
assert_ne!(
before, after,
"a connection after the reload must see the renewed certificate"
);
assert_eq!(
port,
server.acme.port(),
"and the listener must not have moved"
);
server.stop().await;
}
#[tokio::test]
async fn the_admin_listener_reloads_and_keeps_its_login_lockout() {
let dir = TempDir::new("reload-admin");
write_config_with_admin(&dir, false, true, "https://before.example", "");
let server = boot(load_from(&dir), true).await;
let admin = server.admin.expect("the admin listener is enabled");
async fn sign_in(addr: std::net::SocketAddr) -> String {
let body = r#"{"username":"nobody","password":"wrong"}"#;
let mut stream = TcpStream::connect(addr).await.unwrap();
stream
.write_all(
format!(
"POST /api/session HTTP/1.1\r\nHost: localhost\r\n\
Origin: http://localhost:3001\r\n\
Content-Type: application/json\r\nContent-Length: {}\r\n\
Connection: close\r\n\r\n{body}",
body.len()
)
.as_bytes(),
)
.await
.unwrap();
let mut response = String::new();
stream.read_to_string(&mut response).await.unwrap();
response
}
for _ in 0..2 {
assert!(sign_in(admin).await.starts_with("HTTP/1.1 401"));
}
let limited = sign_in(admin).await;
assert!(
limited.starts_with("HTTP/1.1 429"),
"the third attempt is rate limited: {limited}"
);
server.reload.reload().await.expect("the reload must apply");
let after = sign_in(admin).await;
assert!(
after.starts_with("HTTP/1.1 429"),
"a reload must not hand an attacker a fresh budget: {after}"
);
assert!(get(admin, "/health").await.starts_with("HTTP/1.1 200"));
server.stop().await;
}
#[tokio::test]
async fn the_admin_filter_reloads_and_a_broken_one_is_refused() {
let dir = TempDir::new("reload-admin-filter");
write_config_with_admin(&dir, false, true, "https://before.example", "");
let server = boot(load_from(&dir), true).await;
let admin = server.admin.expect("the admin listener is enabled");
assert!(get(admin, "/health").await.starts_with("HTTP/1.1 200"));
let only_elsewhere = r#"
[admin.filter]
rules = ["mgmt"]
[admin.filter.check.net]
type = "allowed_ip"
allow = ["192.0.2.0/24"]
[admin.filter.rule.mgmt]
when = "net"
then = "allow"
"#;
write_config_with_admin(&dir, false, true, "https://before.example", only_elsewhere);
server.reload.reload().await.expect("the reload must apply");
let refused = get(admin, "/health").await;
assert!(refused.starts_with("HTTP/1.1 403"), "{refused}");
write_config_with_admin(
&dir,
false,
true,
"https://before.example",
&only_elsewhere.replace("allowed_ip", "identifiers"),
);
let error = server
.reload
.reload()
.await
.expect_err("an identifier-stage admin.filter must not be applied");
assert!(
error.to_string().contains("admin.filter.check.net"),
"{error}"
);
let still = get(admin, "/health").await;
assert!(
still.starts_with("HTTP/1.1 403"),
"the policy already serving must stay: {still}"
);
server.stop().await;
}
#[tokio::test]
async fn a_keep_alive_connection_sees_the_new_router_on_its_next_request() {
let dir = TempDir::new("reload-keepalive");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
let mut stream = TcpStream::connect(server.acme).await.unwrap();
let before = read_one_response(&mut stream, "/profile/default/directory").await;
assert!(
before.contains("https://before.example"),
"the first request on this connection: {before}"
);
write_config(&dir, false, "https://after.example", "");
server.reload.reload().await.expect("the reload must apply");
let after = read_one_response(&mut stream, "/profile/default/directory").await;
assert!(
after.contains("https://after.example"),
"a keep-alive connection must not pin the generation it was opened on: {after}"
);
assert!(
!after.contains("https://before.example"),
"and must not still be answering from the old router: {after}"
);
server.stop().await;
}
async fn read_one_response(stream: &mut TcpStream, path: &str) -> String {
stream
.write_all(format!("GET {path} HTTP/1.1\r\nHost: localhost\r\n\r\n").as_bytes())
.await
.unwrap();
let mut buffer = Vec::new();
let mut chunk = [0u8; 1024];
let headers_end = loop {
let read = stream.read(&mut chunk).await.unwrap();
assert!(read > 0, "the connection closed mid-response");
buffer.extend_from_slice(&chunk[..read]);
if let Some(at) = buffer.windows(4).position(|window| window == b"\r\n\r\n") {
break at + 4;
}
};
let headers = String::from_utf8_lossy(&buffer[..headers_end]).to_string();
let length: usize = headers
.lines()
.find_map(|line| {
let (name, value) = line.split_once(':')?;
name.eq_ignore_ascii_case("content-length")
.then(|| value.trim().parse().ok())?
})
.expect("the directory response carries a Content-Length");
while buffer.len() < headers_end + length {
let read = stream.read(&mut chunk).await.unwrap();
assert!(read > 0, "the connection closed mid-body");
buffer.extend_from_slice(&chunk[..read]);
}
String::from_utf8_lossy(&buffer).to_string()
}
#[tokio::test]
async fn a_moved_bind_address_rebinds_the_socket() {
let dir = TempDir::new("reload-rebind");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
let old_port = server.acme.port();
assert!(
get(server.acme, "/health")
.await
.starts_with("HTTP/1.1 200")
);
let moved = free_port().await;
write_config_on(
&dir,
Sockets {
server: &format!("127.0.0.1:{moved}"),
admin: "127.0.0.1:3001",
admin_enabled: false,
},
false,
"https://after.example",
"",
);
let report = server.reload.reload().await.expect("the reload must apply");
assert_eq!(report.generation, 2);
assert_eq!(
report.listeners_rebound,
vec!["acme"],
"the ACME socket moved and nothing else did",
);
let new_addr = std::net::SocketAddr::from(([127, 0, 0, 1], moved));
let directory = get(new_addr, "/profile/default/directory").await;
assert!(
directory.contains("https://after.example"),
"the new socket serves the new generation: {directory}"
);
assert!(
wait_until_refused(old_port).await,
"the old socket must be released, not left accepting"
);
server.stop().await;
}
#[tokio::test]
async fn an_unbindable_address_refuses_the_reload_and_the_socket_keeps_serving() {
let dir = TempDir::new("reload-rebind-refused");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
let taken = TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = taken.local_addr().unwrap().port();
write_config_on(
&dir,
Sockets {
server: &format!("127.0.0.1:{port}"),
admin: "127.0.0.1:3001",
admin_enabled: false,
},
false,
"https://after.example",
"",
);
let error = server
.reload
.reload()
.await
.expect_err("a port already in use must refuse the reload");
assert_eq!(error.kind(), "build_failed");
let rendered = error.to_string();
assert!(rendered.contains("server.bind_address"), "{rendered}");
let after = get(server.acme, "/profile/default/directory").await;
assert!(
after.contains("https://before.example"),
"the original socket must still be serving the original generation: {after}"
);
drop(taken);
server.stop().await;
}
#[tokio::test]
async fn the_panel_can_be_switched_on_and_off_by_a_reload() {
let dir = TempDir::new("reload-panel");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
assert!(server.admin.is_none(), "the panel starts switched off");
let port = free_port().await;
let admin = format!("127.0.0.1:{port}");
let on = Sockets {
server: "127.0.0.1:0",
admin: &admin,
admin_enabled: true,
};
write_config_on(&dir, on, false, "https://before.example", "");
let report = server.reload.reload().await.expect("the reload must apply");
assert_eq!(
report.listeners_rebound,
vec!["admin"],
"only the panel's socket appeared; the ACME one never moved",
);
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
let unauthenticated = get(addr, "/api/accounts").await;
assert!(
unauthenticated.starts_with("HTTP/1.1 401"),
"the panel is up and asking for a session: {unauthenticated}"
);
write_config_on(
&dir,
Sockets {
server: "127.0.0.1:0",
admin: &admin,
admin_enabled: false,
},
false,
"https://before.example",
"",
);
let report = server.reload.reload().await.expect("the reload must apply");
assert!(
report.listeners_rebound.is_empty(),
"switching a listener off binds nothing",
);
assert!(
wait_until_refused(port).await,
"a panel switched off must stop answering"
);
server.stop().await;
}
#[tokio::test]
async fn tls_can_be_switched_on_without_the_socket_moving() {
use tokio_rustls::TlsConnector;
use tokio_rustls::rustls::pki_types::ServerName;
let dir = TempDir::new("reload-tls-flip");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
assert!(
get(server.acme, "/health")
.await
.starts_with("HTTP/1.1 200")
);
write_config(&dir, true, "https://before.example", "");
let report = server.reload.reload().await.expect("the reload must apply");
assert!(report.tls_reloaded, "the ACME listener is speaking TLS now");
assert!(
report.listeners_rebound.is_empty(),
"a protocol flip on an unchanged address rebinds nothing",
);
let client = acme_proxy_net::challenge::tls_alpn_01::accept_any_client_config(&[b"http/1.1"])
.expect("a client config");
let stream = TcpStream::connect(server.acme).await.unwrap();
let mut tls = TlsConnector::from(client)
.connect(ServerName::try_from("localhost").unwrap(), stream)
.await
.expect("the same port must now complete a TLS handshake");
tls.write_all(b"GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let mut response = String::new();
tls.read_to_string(&mut response).await.unwrap();
assert!(response.starts_with("HTTP/1.1 200"), "{response}");
server.stop().await;
}
#[tokio::test]
async fn a_profile_can_be_mounted_and_unmounted_by_a_reload() {
let dir = TempDir::new("reload-profiles");
let staging = format!(
r#"
[profiles.staging]
signer.local_ca.cert_path = "{dir}/staging.pem"
signer.local_ca.key_path = "{dir}/staging.key"
signer.local_ca.crl_path = "{dir}/staging.crl"
"#,
dir = dir.path().display(),
);
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
let missing = get(server.acme, "/profile/staging/directory").await;
assert!(missing.contains("404"), "not mounted yet: {missing}");
write_config(&dir, false, "https://before.example", &staging);
let report = server.reload.reload().await.expect("mounting must apply");
assert_eq!(report.generation, 2);
let mut mounted = report.profiles.clone();
mounted.sort();
assert_eq!(mounted, vec!["default".to_string(), "staging".to_string()]);
let now = get(server.acme, "/profile/staging/directory").await;
assert!(
now.contains("/profile/staging/newOrder"),
"the new endpoint must serve its own directory: {now}"
);
assert!(
get(server.acme, "/profile/default/directory")
.await
.contains("/profile/default/newOrder"),
"and the endpoint that was already up must be untouched"
);
write_config(&dir, false, "https://before.example", "");
let report = server.reload.reload().await.expect("unmounting must apply");
assert_eq!(report.profiles, vec!["default".to_string()]);
let gone = get(server.acme, "/profile/staging/directory").await;
assert!(
gone.contains("404"),
"the endpoint is no longer served: {gone}"
);
assert!(
get(server.acme, "/profile/default/directory")
.await
.contains("/profile/default/newOrder"),
"the survivor is still serving"
);
server.stop().await;
}
#[tokio::test]
async fn a_signer_edit_reloads_without_losing_a_revocation() {
let dir = TempDir::new("reload-signer");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
let ca_dir = dir.join("ca");
let cfg = acme_proxy_core::config::LocalCaConfig {
cert_path: format!("{}.pem", ca_dir.display()),
key_path: format!("{}.key", ca_dir.display()),
crl_path: format!("{}.crl", ca_dir.display()),
..acme_proxy_core::config::LocalCaConfig::default()
};
let before = status_of(server.acme, "/profile/default/crl").await;
assert!(before.contains("200 OK"), "the CRL is served: {before}");
write_config(
&dir,
false,
"https://before.example",
"profiles.default.signer.local_ca.leaf_validity_days = 30",
);
let report = server
.reload
.reload()
.await
.expect("an edited [signer] must apply rather than be refused");
assert_eq!(report.generation, 2);
assert_eq!(report.profiles, vec!["default".to_string()]);
let after = status_of(server.acme, "/profile/default/crl").await;
assert!(after.contains("200 OK"), "{after}");
assert!(
get(server.acme, "/profile/default/directory")
.await
.contains("/profile/default/newOrder"),
);
acme_proxy_signer::local_ca::LocalCa::load_or_generate(
&cfg,
Arc::new(Database::connect_in_memory().await.unwrap()),
)
.expect("the CA material must survive an edited [signer]");
server.stop().await;
}
#[tokio::test]
async fn the_egress_sections_reload_where_they_used_to_be_refused() {
let dir = TempDir::new("reload-egress");
write_config(&dir, false, "https://before.example", "");
let server = boot(load_from(&dir), false).await;
write_config(
&dir,
false,
"https://after.example",
r#"
[dns]
resolver = "127.0.0.1:5353"
[proxy]
no_proxy = ["*"]
"#,
);
let report = server
.reload
.reload()
.await
.expect("[dns] and [proxy] must apply rather than be refused");
assert_eq!(report.generation, 2);
let after = get(server.acme, "/profile/default/directory").await;
assert!(after.contains("https://after.example"), "{after}");
server.stop().await;
}
#[tokio::test]
async fn a_process_without_the_admin_role_reloads_over_an_admin_section() {
let dir = TempDir::new("reload-roles");
write_config(&dir, false, "https://before.example", "");
let server = boot_as(
RoleSet::parse(Some("acme,worker")).unwrap(),
load_from(&dir),
false,
)
.await;
write_config_on(
&dir,
Sockets {
server: "127.0.0.1:0",
admin: "0.0.0.0:0",
admin_enabled: true,
},
false,
"https://after.example",
"",
);
let report = server
.reload
.reload()
.await
.expect("a role this process does not run must not refuse its reloads");
assert_eq!(report.generation, 2);
let after = get(server.acme, "/profile/default/directory").await;
assert!(after.contains("https://after.example"), "{after}");
server.stop().await;
}