Fix ACME contact email wiring and remove unused challenge config
This commit is contained in:
@@ -62,6 +62,8 @@ pub struct TlsConfig {
|
|||||||
#[serde(default = "default_acme_directory")]
|
#[serde(default = "default_acme_directory")]
|
||||||
pub acme_directory: String,
|
pub acme_directory: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub acme_contact: String,
|
||||||
|
#[serde(default)]
|
||||||
pub cert_path: String,
|
pub cert_path: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub key_path: String,
|
pub key_path: String,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub fn test_static_config() -> StaticConfig {
|
|||||||
acme_domains: vec!["test.local".to_string()],
|
acme_domains: vec!["test.local".to_string()],
|
||||||
acme_cache_dir: "/tmp/acme-cache".to_string(),
|
acme_cache_dir: "/tmp/acme-cache".to_string(),
|
||||||
acme_directory: "staging".to_string(),
|
acme_directory: "staging".to_string(),
|
||||||
|
acme_contact: "mailto:admin@test.local".to_string(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ pub enum ValidationError {
|
|||||||
UpstreamInvalid { host: String, upstream: String },
|
UpstreamInvalid { host: String, upstream: String },
|
||||||
#[error("site '{host}': upstream_scheme must be 'http' or 'https', got '{scheme}'")]
|
#[error("site '{host}': upstream_scheme must be 'http' or 'https', got '{scheme}'")]
|
||||||
UpstreamSchemeInvalid { host: String, scheme: String },
|
UpstreamSchemeInvalid { host: String, scheme: String },
|
||||||
|
#[error("listener {bind_addr}: ACME mode requires acme_contact to be a valid mailto: URI (e.g., \"mailto:admin@example.com\")")]
|
||||||
|
AcmeContactInvalid { bind_addr: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(
|
pub fn validate(
|
||||||
@@ -142,6 +144,12 @@ pub fn validate(
|
|||||||
bind_addr: listener.bind_addr.clone(),
|
bind_addr: listener.bind_addr.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let contact = &listener.tls.acme_contact;
|
||||||
|
if contact.is_empty() || !contact.starts_with("mailto:") {
|
||||||
|
errors.push(ValidationError::AcmeContactInvalid {
|
||||||
|
bind_addr: listener.bind_addr.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"manual" => {
|
"manual" => {
|
||||||
let cert_empty = listener.tls.cert_path.is_empty();
|
let cert_empty = listener.tls.cert_path.is_empty();
|
||||||
@@ -331,6 +339,7 @@ mod tests {
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: String::new(),
|
acme_cache_dir: String::new(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: String::new(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
},
|
},
|
||||||
@@ -386,6 +395,7 @@ mod tests {
|
|||||||
acme_domains: vec!["test.local".to_string()],
|
acme_domains: vec!["test.local".to_string()],
|
||||||
acme_cache_dir: "/tmp/acme-cache".to_string(),
|
acme_cache_dir: "/tmp/acme-cache".to_string(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: "mailto:admin@example.com".to_string(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
}
|
}
|
||||||
@@ -397,6 +407,7 @@ mod tests {
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: String::new(),
|
acme_cache_dir: String::new(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: String::new(),
|
||||||
cert_path: cert.to_string(),
|
cert_path: cert.to_string(),
|
||||||
key_path: key.to_string(),
|
key_path: key.to_string(),
|
||||||
}
|
}
|
||||||
@@ -497,6 +508,7 @@ mod tests {
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: "/tmp/cache".to_string(),
|
acme_cache_dir: "/tmp/cache".to_string(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: "mailto:admin@example.com".to_string(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
};
|
};
|
||||||
@@ -517,6 +529,7 @@ mod tests {
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: String::new(),
|
acme_cache_dir: String::new(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: String::new(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
};
|
};
|
||||||
@@ -917,6 +930,80 @@ mod tests {
|
|||||||
.any(|e| matches!(e, ValidationError::UpstreamSchemeInvalid { .. })));
|
.any(|e| matches!(e, ValidationError::UpstreamSchemeInvalid { .. })));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rule19_acme_contact_empty() {
|
||||||
|
let mut config = valid_static_config();
|
||||||
|
config.listeners[0].tls = TlsConfig {
|
||||||
|
mode: "acme".to_string(),
|
||||||
|
acme_domains: vec!["test.local".to_string()],
|
||||||
|
acme_cache_dir: "/tmp/cache".to_string(),
|
||||||
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: String::new(),
|
||||||
|
cert_path: String::new(),
|
||||||
|
key_path: String::new(),
|
||||||
|
};
|
||||||
|
config.listeners[0].sites = vec![SiteConfig {
|
||||||
|
host: "test.local".to_string(),
|
||||||
|
upstream: "127.0.0.1:8080".to_string(),
|
||||||
|
..valid_dynamic_config().sites[0].clone()
|
||||||
|
}];
|
||||||
|
let dynamic = valid_dynamic_config();
|
||||||
|
let result = validate(&config, &dynamic, false);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let errors = result.unwrap_err();
|
||||||
|
assert!(errors
|
||||||
|
.iter()
|
||||||
|
.any(|e| matches!(e, ValidationError::AcmeContactInvalid { .. })));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rule19_acme_contact_not_mailto() {
|
||||||
|
let mut config = valid_static_config();
|
||||||
|
config.listeners[0].tls = TlsConfig {
|
||||||
|
mode: "acme".to_string(),
|
||||||
|
acme_domains: vec!["test.local".to_string()],
|
||||||
|
acme_cache_dir: "/tmp/cache".to_string(),
|
||||||
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: "admin@example.com".to_string(),
|
||||||
|
cert_path: String::new(),
|
||||||
|
key_path: String::new(),
|
||||||
|
};
|
||||||
|
config.listeners[0].sites = vec![SiteConfig {
|
||||||
|
host: "test.local".to_string(),
|
||||||
|
upstream: "127.0.0.1:8080".to_string(),
|
||||||
|
..valid_dynamic_config().sites[0].clone()
|
||||||
|
}];
|
||||||
|
let dynamic = valid_dynamic_config();
|
||||||
|
let result = validate(&config, &dynamic, false);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let errors = result.unwrap_err();
|
||||||
|
assert!(errors
|
||||||
|
.iter()
|
||||||
|
.any(|e| matches!(e, ValidationError::AcmeContactInvalid { .. })));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rule19_acme_contact_valid_mailto() {
|
||||||
|
let mut config = valid_static_config();
|
||||||
|
config.listeners[0].tls = TlsConfig {
|
||||||
|
mode: "acme".to_string(),
|
||||||
|
acme_domains: vec!["test.local".to_string()],
|
||||||
|
acme_cache_dir: "/tmp/cache".to_string(),
|
||||||
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: "mailto:admin@example.com".to_string(),
|
||||||
|
cert_path: String::new(),
|
||||||
|
key_path: String::new(),
|
||||||
|
};
|
||||||
|
config.listeners[0].sites = vec![SiteConfig {
|
||||||
|
host: "test.local".to_string(),
|
||||||
|
upstream: "127.0.0.1:8080".to_string(),
|
||||||
|
..valid_dynamic_config().sites[0].clone()
|
||||||
|
}];
|
||||||
|
let dynamic = valid_dynamic_config();
|
||||||
|
let result = validate(&config, &dynamic, false);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid_config_passes() {
|
fn valid_config_passes() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -165,11 +165,7 @@ async fn run_server(loaded_config: cli::LoadedConfig, config_path: &str) -> Resu
|
|||||||
"Manual TLS configured"
|
"Manual TLS configured"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
TlsMode::Acme {
|
TlsMode::Acme { default_config, .. } => {
|
||||||
default_config,
|
|
||||||
challenge_config: _,
|
|
||||||
resolver: _,
|
|
||||||
} => {
|
|
||||||
let acceptor = TlsAcceptor::from(default_config);
|
let acceptor = TlsAcceptor::from(default_config);
|
||||||
tls_acceptors.push(acceptor);
|
tls_acceptors.push(acceptor);
|
||||||
info!(
|
info!(
|
||||||
|
|||||||
@@ -30,27 +30,12 @@ fn build_acme_server_config(
|
|||||||
Ok(Arc::new(config))
|
Ok(Arc::new(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_acme_challenge_config(
|
|
||||||
resolver: Arc<rustls_acme::ResolvesServerCertAcme>,
|
|
||||||
) -> Arc<ServerConfig> {
|
|
||||||
let provider = crypto_provider();
|
|
||||||
let mut config = ServerConfig::builder_with_provider(provider)
|
|
||||||
.with_protocol_versions(&[&TLS12, &TLS13])
|
|
||||||
.expect("valid protocol versions")
|
|
||||||
.with_no_client_auth()
|
|
||||||
.with_cert_resolver(resolver);
|
|
||||||
config.alpn_protocols = vec![ACME_TLS_ALPN_01.to_vec()];
|
|
||||||
Arc::new(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TlsMode {
|
pub enum TlsMode {
|
||||||
Manual(Arc<ServerConfig>),
|
Manual(Arc<ServerConfig>),
|
||||||
Acme {
|
Acme {
|
||||||
default_config: Arc<ServerConfig>,
|
default_config: Arc<ServerConfig>,
|
||||||
challenge_config: Arc<ServerConfig>,
|
|
||||||
resolver: Arc<rustls_acme::ResolvesServerCertAcme>,
|
resolver: Arc<rustls_acme::ResolvesServerCertAcme>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -83,13 +68,12 @@ pub fn setup_tls(tls_config: &TlsConfig) -> Result<TlsMode> {
|
|||||||
domains: tls_config.acme_domains.clone(),
|
domains: tls_config.acme_domains.clone(),
|
||||||
cache_dir: tls_config.acme_cache_dir.clone().into(),
|
cache_dir: tls_config.acme_cache_dir.clone().into(),
|
||||||
directory: tls_config.acme_directory.clone(),
|
directory: tls_config.acme_directory.clone(),
|
||||||
contact: vec![],
|
contact: vec![tls_config.acme_contact.clone()],
|
||||||
};
|
};
|
||||||
|
|
||||||
let super::acme::AcmeTlsSetup { resolver, state } = acme_tls_config.setup()?;
|
let super::acme::AcmeTlsSetup { resolver, state } = acme_tls_config.setup()?;
|
||||||
|
|
||||||
let default_config = build_acme_server_config(resolver.clone())?;
|
let default_config = build_acme_server_config(resolver.clone())?;
|
||||||
let challenge_config = build_acme_challenge_config(resolver.clone());
|
|
||||||
|
|
||||||
spawn_acme_state(state, tls_config.acme_domains.clone());
|
spawn_acme_state(state, tls_config.acme_domains.clone());
|
||||||
|
|
||||||
@@ -100,7 +84,6 @@ pub fn setup_tls(tls_config: &TlsConfig) -> Result<TlsMode> {
|
|||||||
|
|
||||||
Ok(TlsMode::Acme {
|
Ok(TlsMode::Acme {
|
||||||
default_config,
|
default_config,
|
||||||
challenge_config,
|
|
||||||
resolver,
|
resolver,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -142,14 +125,6 @@ mod tests {
|
|||||||
assert!(config.alpn_protocols.contains(&ACME_TLS_ALPN_01.to_vec()));
|
assert!(config.alpn_protocols.contains(&ACME_TLS_ALPN_01.to_vec()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_build_acme_challenge_config() {
|
|
||||||
let resolver = make_test_resolver();
|
|
||||||
let config = build_acme_challenge_config(resolver);
|
|
||||||
assert_eq!(config.alpn_protocols.len(), 1);
|
|
||||||
assert_eq!(config.alpn_protocols[0], ACME_TLS_ALPN_01);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_setup_tls_manual_missing_cert_path() {
|
fn test_setup_tls_manual_missing_cert_path() {
|
||||||
let tls_config = TlsConfig {
|
let tls_config = TlsConfig {
|
||||||
@@ -157,6 +132,7 @@ mod tests {
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: String::new(),
|
acme_cache_dir: String::new(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: String::new(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: "/some/key.pem".to_string(),
|
key_path: "/some/key.pem".to_string(),
|
||||||
};
|
};
|
||||||
@@ -173,6 +149,7 @@ mod tests {
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: String::new(),
|
acme_cache_dir: String::new(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: String::new(),
|
||||||
cert_path: "/some/cert.pem".to_string(),
|
cert_path: "/some/cert.pem".to_string(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
};
|
};
|
||||||
@@ -189,6 +166,7 @@ mod tests {
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: "/tmp/cache".to_string(),
|
acme_cache_dir: "/tmp/cache".to_string(),
|
||||||
acme_directory: "staging".to_string(),
|
acme_directory: "staging".to_string(),
|
||||||
|
acme_contact: "mailto:admin@example.com".to_string(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
};
|
};
|
||||||
@@ -205,6 +183,7 @@ mod tests {
|
|||||||
acme_domains: vec!["example.com".to_string()],
|
acme_domains: vec!["example.com".to_string()],
|
||||||
acme_cache_dir: String::new(),
|
acme_cache_dir: String::new(),
|
||||||
acme_directory: "staging".to_string(),
|
acme_directory: "staging".to_string(),
|
||||||
|
acme_contact: "mailto:admin@example.com".to_string(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
};
|
};
|
||||||
@@ -221,6 +200,7 @@ mod tests {
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: String::new(),
|
acme_cache_dir: String::new(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: String::new(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ fn make_redirect_listener_config(
|
|||||||
acme_domains: vec![],
|
acme_domains: vec![],
|
||||||
acme_cache_dir: String::new(),
|
acme_cache_dir: String::new(),
|
||||||
acme_directory: "production".to_string(),
|
acme_directory: "production".to_string(),
|
||||||
|
acme_contact: String::new(),
|
||||||
cert_path: String::new(),
|
cert_path: String::new(),
|
||||||
key_path: String::new(),
|
key_path: String::new(),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user