mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-03-26 19:03:08 +00:00
Harden registry installs: DNS SSRF checks, timeout, size cap
This commit is contained in:
parent
dc6647efbf
commit
0c99fa253d
1 changed files with 99 additions and 9 deletions
|
|
@ -16,6 +16,8 @@ use sqlx::PgPool;
|
|||
use sqlx::Row;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::net::lookup_host;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::auth::AuthUser;
|
||||
|
|
@ -443,7 +445,10 @@ fn verify_signature_if_required(
|
|||
Ok(Some(sig_arr.to_vec()))
|
||||
}
|
||||
|
||||
fn enforce_registry_allowlist(url: &Url, allowlist: &[String]) -> Result<(), (StatusCode, String)> {
|
||||
async fn enforce_registry_allowlist(
|
||||
url: &Url,
|
||||
allowlist: &[String],
|
||||
) -> Result<(), (StatusCode, String)> {
|
||||
let host = url.host_str().ok_or((
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Registry URL must include host".to_string(),
|
||||
|
|
@ -477,11 +482,63 @@ fn enforce_registry_allowlist(url: &Url, allowlist: &[String]) -> Result<(), (St
|
|||
));
|
||||
}
|
||||
|
||||
if !allowlist.is_empty() && !allowlist.iter().any(|h| h == host) {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
"Registry host not in allowlist".to_string(),
|
||||
));
|
||||
if !allowlist.is_empty() {
|
||||
let allowed = allowlist.iter().any(|pattern| {
|
||||
if pattern == "*" {
|
||||
return true;
|
||||
}
|
||||
|
||||
if pattern.starts_with("*.") {
|
||||
let suffix = &pattern[1..];
|
||||
host.ends_with(suffix) || host.eq_ignore_ascii_case(&pattern[2..])
|
||||
} else {
|
||||
host.eq_ignore_ascii_case(pattern)
|
||||
}
|
||||
});
|
||||
|
||||
if !allowed {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
"Registry host not in allowlist".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let port = url.port_or_known_default().unwrap_or(443);
|
||||
let lookup = tokio::time::timeout(Duration::from_secs(3), lookup_host((host, port)))
|
||||
.await
|
||||
.map_err(|_| {
|
||||
(
|
||||
StatusCode::BAD_GATEWAY,
|
||||
"Registry host DNS lookup timed out".to_string(),
|
||||
)
|
||||
})
|
||||
.and_then(|res| {
|
||||
res.map_err(|_| {
|
||||
(
|
||||
StatusCode::BAD_GATEWAY,
|
||||
"Registry host DNS lookup failed".to_string(),
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
for addr in lookup {
|
||||
let ip = addr.ip();
|
||||
let is_disallowed = match ip {
|
||||
IpAddr::V4(v4) => {
|
||||
v4.is_loopback() || v4.is_private() || v4.is_link_local() || v4.is_unspecified()
|
||||
}
|
||||
IpAddr::V6(v6) => {
|
||||
v6.is_loopback() || v6.is_unique_local() || v6.is_unicast_link_local() || v6.is_unspecified()
|
||||
}
|
||||
};
|
||||
|
||||
if is_disallowed {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
"Registry host is not allowed".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -1049,9 +1106,18 @@ async fn install_registry_plugin_package(
|
|||
}
|
||||
}
|
||||
|
||||
enforce_registry_allowlist(&url, ®istry_allowlist)?;
|
||||
enforce_registry_allowlist(&url, ®istry_allowlist).await?;
|
||||
|
||||
let res = reqwest::get(url.clone())
|
||||
const MAX_REGISTRY_BUNDLE_BYTES: u64 = 2 * 1024 * 1024;
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let mut res = client
|
||||
.get(url.clone())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
|
||||
|
||||
|
|
@ -1059,7 +1125,31 @@ async fn install_registry_plugin_package(
|
|||
return Err((StatusCode::BAD_GATEWAY, "Registry fetch failed".to_string()));
|
||||
}
|
||||
|
||||
let bundle: UploadPluginPackageRequest = res.json().await.map_err(|_| {
|
||||
if let Some(len) = res.content_length() {
|
||||
if len > MAX_REGISTRY_BUNDLE_BYTES {
|
||||
return Err((
|
||||
StatusCode::BAD_GATEWAY,
|
||||
"Registry response too large".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut body: Vec<u8> = Vec::new();
|
||||
while let Some(chunk) = res
|
||||
.chunk()
|
||||
.await
|
||||
.map_err(|_| (StatusCode::BAD_GATEWAY, "Registry fetch failed".to_string()))?
|
||||
{
|
||||
body.extend_from_slice(&chunk);
|
||||
if body.len() as u64 > MAX_REGISTRY_BUNDLE_BYTES {
|
||||
return Err((
|
||||
StatusCode::BAD_GATEWAY,
|
||||
"Registry response too large".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let bundle: UploadPluginPackageRequest = serde_json::from_slice(&body).map_err(|_| {
|
||||
(
|
||||
StatusCode::BAD_GATEWAY,
|
||||
"Invalid registry response".to_string(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue