feat(security): enforce trusted-registry image policy at the orchestrator pull sites

Catalog- and manifest-supplied image refs reached pull_image without
ever passing the RPC boundary's validator — a malicious catalog entry
or manifest could pull from an arbitrary registry. The allowlist now
lives in container::image_policy (the RPC check delegates to it) and
both orchestrator pull sites (install_fresh and
ensure_resolved_source_available) refuse refs that fail it.

The shared policy accepts trusted-registry refs and registry-less
Docker Hub shorthand (grafana/grafana etc., used by 8 shipped
manifests — a registry-less ref cannot name an attacker host), and
rejects explicit non-allowlisted hosts, shell metacharacters, and
malformed refs. §A of the 1.8.0 hardening plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-04 09:52:31 -04:00
co-authored by Claude Fable 5
parent 2f20ba8148
commit 4b4a1f88fb
5 changed files with 126 additions and 33 deletions
+4 -28
View File
@@ -94,35 +94,11 @@ async fn dynamic_app_config(
))
}
/// Trusted Docker registries. Only images from these sources are allowed.
#[allow(dead_code)]
pub(super) const TRUSTED_REGISTRIES: &[&str] = &[
"docker.io/",
"ghcr.io/",
"localhost/",
"git.tx1138.com/",
"146.59.87.168:3000/",
];
/// Validate Docker image against trusted registry allowlist.
/// Validate a Docker image reference. Delegates to the shared policy in
/// `container::image_policy` — the same rules the orchestrator enforces at
/// its pull sites, so the two layers can't drift apart.
pub(super) fn is_valid_docker_image(image: &str) -> bool {
if image.is_empty() || image.len() > 256 {
return false;
}
// Reject shell metacharacters
let dangerous_chars = ['&', '|', ';', '`', '$', '(', ')', '<', '>', '\n', '\r'];
if image.chars().any(|c| dangerous_chars.contains(&c)) {
return false;
}
// Must come from a trusted registry — match the exact domain, not just prefix
let registry = match image.split('/').next() {
Some(r) => r,
None => return false,
};
matches!(
registry,
"docker.io" | "ghcr.io" | "localhost" | "git.tx1138.com" | "146.59.87.168:3000"
)
crate::container::image_policy::is_valid_docker_image(image)
}
/// Per-app Linux capabilities needed beyond the default cap-drop=ALL.