This commit is contained in:
Mateusz Gruszczyński committed 2026-10-05 09:30:21 +02:00
1 parent 1dea7f55c8
commit c819f0a335
151 files changed
+7610 -4264

No files matched your search

+12 -3
View File
@@ -72,8 +72,17 @@ impl Manager {
id: format!("{:032x}", rand::random::<u128>()),
certificate_id: cert.id.clone(),
status: "running",
kind: if dry_run { "renewal-dry-run" } else { "renewal" },
log_directory: certbot_log_directory(&directory, &issuer.provider, &issuer.environment, &cert.id),
kind: if dry_run {
"renewal-dry-run"
} else {
"renewal"
},
log_directory: certbot_log_directory(
&directory,
&issuer.provider,
&issuer.environment,
&cert.id,
),
provider: issuer.provider.clone(),
domains,
environment: issuer.environment.clone(),
@@ -109,7 +118,7 @@ impl Manager {
.await
.map_err(|_| ApiError(StatusCode::CONFLICT, "acme.secret_missing".into()))?;
request["credential_ini_b64"] = Value::String(
base64::engine::general_purpose::STANDARD.encode(credential.as_slice())
base64::engine::general_purpose::STANDARD.encode(credential.as_slice()),
);
}
let raw = Zeroizing::new(request.to_string());
+124 -29
View File
@@ -29,8 +29,17 @@ pub struct Job {
pub certificate: Option<Certificate>,
}
pub(crate) fn certbot_log_directory(directory: &std::path::Path, provider: &str, environment: &str, certificate_id: &str) -> String {
let suffix = if provider == "rfc2136" { String::new() } else { format!("-{provider}") };
pub(crate) fn certbot_log_directory(
directory: &std::path::Path,
provider: &str,
environment: &str,
certificate_id: &str,
) -> String {
let suffix = if provider == "rfc2136" {
String::new()
} else {
format!("-{provider}")
};
directory
.join("acme")
.join(format!("{environment}-{certificate_id}{suffix}"))
@@ -38,26 +47,57 @@ pub(crate) fn certbot_log_directory(directory: &std::path::Path, provider: &str,
.to_string_lossy()
.into_owned()
}
fn provider_credential_bytes(request: &Value, provider: &str) -> Result<Zeroizing<Vec<u8>>, ApiError> {
fn provider_credential_bytes(
request: &Value,
provider: &str,
) -> Result<Zeroizing<Vec<u8>>, ApiError> {
fn credential<'a>(request: &'a Value, name: &str) -> Result<&'a str, ApiError> {
let invalid = || ApiError(StatusCode::BAD_REQUEST, "acme.invalid_api_credential".into());
let value = request.get(name).and_then(Value::as_str).ok_or_else(invalid)?;
let invalid = || {
ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_api_credential".into(),
)
};
let value = request
.get(name)
.and_then(Value::as_str)
.ok_or_else(invalid)?;
let valid = (8..=4096).contains(&value.len())
&& value.bytes().all(|byte| {
byte.is_ascii_alphanumeric()
|| matches!(byte, b'_' | b'.' | b'/' | b'+' | b'-' | b'=')
});
if valid { Ok(value) } else { Err(invalid()) }
if valid {
Ok(value)
} else {
Err(invalid())
}
}
let bytes = match provider {
"cloudflare" => format!("dns_cloudflare_api_token = {}\n", credential(request, "api_token")?).into_bytes(),
"cloudflare" => format!(
"dns_cloudflare_api_token = {}\n",
credential(request, "api_token")?
)
.into_bytes(),
"ovh" => {
if request.get("endpoint").is_some_and(|value| !value.is_string()) {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_ovh_endpoint".into()));
if request
.get("endpoint")
.is_some_and(|value| !value.is_string())
{
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_ovh_endpoint".into(),
));
}
let endpoint = request.get("endpoint").and_then(Value::as_str).unwrap_or("ovh-eu");
let endpoint = request
.get("endpoint")
.and_then(Value::as_str)
.unwrap_or("ovh-eu");
if !matches!(endpoint, "ovh-eu" | "ovh-ca") {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_ovh_endpoint".into()));
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_ovh_endpoint".into(),
));
}
format!(
"dns_ovh_endpoint = {}\ndns_ovh_application_key = {}\ndns_ovh_application_secret = {}\ndns_ovh_consumer_key = {}\n",
@@ -73,15 +113,32 @@ fn provider_credential_bytes(request: &Value, provider: &str) -> Result<Zeroizin
.get("dns_server")
.and_then(Value::as_str)
.and_then(|value| value.parse::<std::net::IpAddr>().ok())
.ok_or_else(|| ApiError(StatusCode::BAD_REQUEST, "acme.invalid_dns_server".into()))?;
if request.get("dns_port").is_some_and(|value| value.as_u64().is_none()) {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_limits".into()));
.ok_or_else(|| {
ApiError(StatusCode::BAD_REQUEST, "acme.invalid_dns_server".into())
})?;
if request
.get("dns_port")
.is_some_and(|value| value.as_u64().is_none())
{
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_limits".into(),
));
}
let dns_port = request.get("dns_port").and_then(Value::as_u64).unwrap_or(53);
let dns_port = request
.get("dns_port")
.and_then(Value::as_u64)
.unwrap_or(53);
if !(1..=65535).contains(&dns_port) {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_limits".into()));
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_limits".into(),
));
}
let raw_name = request.get("key_name").and_then(Value::as_str).unwrap_or("");
let raw_name = request
.get("key_name")
.and_then(Value::as_str)
.unwrap_or("");
let key_name = raw_name.to_ascii_lowercase();
let key_name = key_name.trim_end_matches('.');
let valid_name = !key_name.is_empty()
@@ -95,43 +152,81 @@ fn provider_credential_bytes(request: &Value, provider: &str) -> Result<Zeroizin
})
});
if !valid_name {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_domain".into()));
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_domain".into(),
));
}
if request.get("key_algorithm").is_some_and(|value| !value.is_string()) {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_key_algorithm".into()));
if request
.get("key_algorithm")
.is_some_and(|value| !value.is_string())
{
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_key_algorithm".into(),
));
}
let key_algorithm = request
.get("key_algorithm")
.and_then(Value::as_str)
.unwrap_or("HMAC-SHA256");
if !matches!(key_algorithm, "HMAC-SHA256" | "HMAC-SHA384" | "HMAC-SHA512") {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_key_algorithm".into()));
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_key_algorithm".into(),
));
}
let key_secret = request.get("key_secret").and_then(Value::as_str).ok_or_else(|| ApiError(StatusCode::BAD_REQUEST, "acme.invalid_api_credential".into()))?;
let key_secret = request
.get("key_secret")
.and_then(Value::as_str)
.ok_or_else(|| {
ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_api_credential".into(),
)
})?;
let decoded = Zeroizing::new(
base64::engine::general_purpose::STANDARD
.decode(key_secret)
.map_err(|_| ApiError(StatusCode::BAD_REQUEST, "acme.invalid_key_secret".into()))?,
.map_err(|_| {
ApiError(StatusCode::BAD_REQUEST, "acme.invalid_key_secret".into())
})?,
);
if key_secret.len() > 1024 || !(16..=512).contains(&decoded.len()) {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_key_secret".into()));
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_key_secret".into(),
));
}
if request.get("sign_query").is_some_and(|value| !value.is_boolean()) {
return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_sign_query".into()));
if request
.get("sign_query")
.is_some_and(|value| !value.is_boolean())
{
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_sign_query".into(),
));
}
let sign_query = request.get("sign_query").and_then(Value::as_bool).unwrap_or(false);
let sign_query = request
.get("sign_query")
.and_then(Value::as_bool)
.unwrap_or(false);
format!(
"dns_rfc2136_server = {dns_server}\ndns_rfc2136_port = {dns_port}\ndns_rfc2136_name = {key_name}.\ndns_rfc2136_secret = {key_secret}\ndns_rfc2136_algorithm = {key_algorithm}\ndns_rfc2136_sign_query = {sign_query}\n"
)
.into_bytes()
}
"http01" => Vec::new(),
_ => return Err(ApiError(StatusCode::BAD_REQUEST, "acme.invalid_provider".into())),
_ => {
return Err(ApiError(
StatusCode::BAD_REQUEST,
"acme.invalid_provider".into(),
))
}
};
Ok(Zeroizing::new(bytes))
}
pub struct Manager {
slot: Arc<Semaphore>,
job: Mutex<Option<Job>>,