#![cfg(test)]
#![allow(clippy::unwrap_used)]
use crate::test_helpers::{body_bytes, create_test_context_with_config, send};
use axum::http::{Method, StatusCode};
const BLACKHOLE: &str = "http://127.0.0.1:1";
#[tokio::test]
async fn pypi_internal_metadata_blocked_not_proxied() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.pypi.proxy = Some(BLACKHOLE.to_string());
});
let resp = send(&ctx.app, Method::GET, "/simple/internalpkg/", "").await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn pypi_internal_locally_published_served_hole5() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.pypi.proxy = Some(BLACKHOLE.to_string());
});
ctx.state
.storage
.put(
"pypi/internalpkg/internalpkg-1.0-py3-none-any.whl",
b"wheel-bytes",
)
.await
.unwrap();
let resp = send(&ctx.app, Method::GET, "/simple/internalpkg/", "").await;
assert_eq!(
resp.status(),
StatusCode::OK,
"locally-published internal package must be served, not 403"
);
let body = String::from_utf8(body_bytes(resp).await.to_vec()).unwrap();
assert!(
body.contains("internalpkg-1.0-py3-none-any.whl"),
"listing must contain the locally-published file"
);
}
#[tokio::test]
async fn pypi_public_metadata_not_blocked() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
let resp = send(&ctx.app, Method::GET, "/simple/publicpkg/", "").await;
assert_eq!(
resp.status(),
StatusCode::NOT_FOUND,
"a non-internal package must not be namespace-blocked"
);
}
#[tokio::test]
async fn maven_internal_metadata_blocked() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["com.internal.**".to_string()];
c.maven.proxies = vec![];
});
let resp = send(
&ctx.app,
Method::GET,
"/maven2/com/internal/lib/maven-metadata.xml",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn nuget_internal_version_list_blocked() {
let ctx = create_test_context_with_config(|c| {
c.nuget.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
let resp = send(
&ctx.app,
Method::GET,
"/nuget/v3/flatcontainer/internalpkg/index.json",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn nuget_internal_search_not_forwarded() {
let ctx = create_test_context_with_config(|c| {
c.nuget.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.nuget.proxy = Some(BLACKHOLE.to_string());
c.nuget.serve_stale = false; });
let resp = send(&ctx.app, Method::GET, "/nuget/v3/query?q=internalpkg", "").await;
assert_eq!(
resp.status(),
StatusCode::OK,
"internal search term must be served locally, not forwarded to upstream"
);
}
#[tokio::test]
async fn conan_internal_recipe_blocked() {
let ctx = create_test_context_with_config(|c| {
c.conan.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
let resp = send(
&ctx.app,
Method::GET,
"/conan/v2/conans/internalpkg/1.0/user/channel/latest",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn conan_internal_search_not_forwarded() {
let ctx = create_test_context_with_config(|c| {
c.conan.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
let resp = send(
&ctx.app,
Method::GET,
"/conan/v2/conans/search?q=internalpkg",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::OK);
let body = String::from_utf8(body_bytes(resp).await.to_vec()).unwrap();
assert!(
body.contains("\"results\""),
"internal search must return an empty result set, not forward upstream"
);
}
#[tokio::test]
async fn pubdart_internal_version_metadata_blocked() {
let ctx = create_test_context_with_config(|c| {
c.pub_dart.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
let resp = send(
&ctx.app,
Method::GET,
"/pub/api/packages/internalpkg/versions/1.0.0",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn terraform_internal_provider_versions_blocked() {
let ctx = create_test_context_with_config(|c| {
c.terraform.enabled = true;
c.curation.internal_namespaces = vec!["acme/**".to_string()];
});
let resp = send(
&ctx.app,
Method::GET,
"/terraform/v1/providers/acme/aws/versions",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn ansible_internal_collection_blocked() {
let ctx = create_test_context_with_config(|c| {
c.ansible.enabled = true;
c.curation.internal_namespaces = vec!["acme.**".to_string()];
});
let resp = send(
&ctx.app,
Method::GET,
"/ansible/v3/collections/acme/internal/",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn gems_internal_gemspec_blocked() {
let ctx = create_test_context_with_config(|c| {
c.gems.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
let resp = send(
&ctx.app,
Method::GET,
"/gems/quick/Marshal.4.8/internalgem-1.0.gemspec.rz",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn go_internal_metadata_blocked() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.go.proxy = Some(BLACKHOLE.to_string()); });
let resp = send(&ctx.app, Method::GET, "/go/internal.corp/lib/@v/list", "").await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn go_internal_stale_cached_served() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.go.proxy = Some(BLACKHOLE.to_string());
c.go.metadata_ttl = 0; });
ctx.state
.storage
.put("go/internal.corp/lib/@v/list", b"v1.0.0\n")
.await
.unwrap();
let resp = send(&ctx.app, Method::GET, "/go/internal.corp/lib/@v/list", "").await;
assert_eq!(
resp.status(),
StatusCode::OK,
"cached internal go module must be served stale, not re-proxied (Shape B)"
);
}
#[tokio::test]
async fn npm_internal_stale_not_refetched() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.npm.proxy = Some(BLACKHOLE.to_string());
c.npm.metadata_ttl = 0; c.npm.serve_stale = false; });
ctx.state
.storage
.put(
"npm/internalpkg/metadata.json",
br#"{"name":"internalpkg","versions":{}}"#,
)
.await
.unwrap();
let resp = send(&ctx.app, Method::GET, "/npm/internalpkg", "").await;
assert_eq!(
resp.status(),
StatusCode::OK,
"stale internal packument must serve cached copy, not re-proxy (PR #725 residual)"
);
let body = String::from_utf8(body_bytes(resp).await.to_vec()).unwrap();
assert!(body.contains("internalpkg"));
}
#[tokio::test]
async fn pypi_internal_download_served_local() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.pypi.proxy = Some(BLACKHOLE.to_string());
});
ctx.state
.storage
.put("pypi/internalpkg/internalpkg-1.0.tar.gz", b"sdist")
.await
.unwrap();
let resp = send(
&ctx.app,
Method::GET,
"/simple/internalpkg/internalpkg-1.0.tar.gz",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::OK,
"locally-published internal sdist must be downloadable, not 403'd (#733)"
);
}
#[tokio::test]
async fn pypi_internal_download_no_local_blocked() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.pypi.proxy = Some(BLACKHOLE.to_string());
});
let resp = send(
&ctx.app,
Method::GET,
"/simple/internalpkg/internalpkg-9.9.tar.gz",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::FORBIDDEN,
"internal download with no local copy is blocked, never proxied"
);
}
#[tokio::test]
async fn cargo_internal_download_served_local() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.cargo.proxy = Some(BLACKHOLE.to_string());
});
ctx.state
.storage
.put(
"cargo/internalcrate/1.0.0/internalcrate-1.0.0.crate",
b"crate-bytes",
)
.await
.unwrap();
let resp = send(
&ctx.app,
Method::GET,
"/cargo/api/v1/crates/internalcrate/1.0.0/download",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::OK,
"locally-published internal crate must be downloadable (#733)"
);
}
#[tokio::test]
async fn raw_internal_served_local() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
ctx.state
.storage
.put("raw/internal/secret.txt", b"data")
.await
.unwrap();
let resp = send(&ctx.app, Method::GET, "/raw/internal/secret.txt", "").await;
assert_eq!(
resp.status(),
StatusCode::OK,
"locally-stored internal raw file must be served (#733; raw has no upstream)"
);
}
#[tokio::test]
async fn nuget_internal_registration_index_served_local() {
let ctx = create_test_context_with_config(|c| {
c.nuget.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
ctx.state
.storage
.put(
"nuget/registration/internalpkg/index.json",
br#"{"count":0,"items":[]}"#,
)
.await
.unwrap();
let resp = send(
&ctx.app,
Method::GET,
"/nuget/v3/registration/internalpkg/index.json",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::OK,
"internal registration index must be served from local (#733 over-block fix)"
);
}
#[tokio::test]
async fn pubdart_internal_listing_served_local() {
let ctx = create_test_context_with_config(|c| {
c.pub_dart.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
ctx.state
.storage
.put(
"pub/api/packages/internalpkg.json",
br#"{"name":"internalpkg","versions":[]}"#,
)
.await
.unwrap();
let resp = send(&ctx.app, Method::GET, "/pub/api/packages/internalpkg", "").await;
assert_eq!(
resp.status(),
StatusCode::OK,
"internal package listing must be served from local (#733 over-block fix)"
);
}
#[tokio::test]
async fn gems_internal_compact_index_served_local() {
let ctx = create_test_context_with_config(|c| {
c.gems.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
ctx.state
.storage
.put("gems/info/internalgem", b"---\n1.0.0 |checksum:abc\n")
.await
.unwrap();
let resp = send(&ctx.app, Method::GET, "/gems/info/internalgem", "").await;
assert_eq!(
resp.status(),
StatusCode::OK,
"internal gem compact-index must be served from local (#733 over-block fix)"
);
}
#[tokio::test]
async fn conan_internal_recipe_file_served_local() {
let ctx = create_test_context_with_config(|c| {
c.conan.enabled = true;
c.curation.internal_namespaces = vec!["internal*".to_string()];
});
ctx.state
.storage
.put(
"conan/internalpkg/1.0/user/stable/revisions/rev1/files/conanfile.py",
b"from conan import ConanFile",
)
.await
.unwrap();
let resp = send(
&ctx.app,
Method::GET,
"/conan/v2/conans/internalpkg/1.0/user/stable/revisions/rev1/files/conanfile.py",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::OK,
"locally-cached internal conan recipe file must be served (#733)"
);
}
#[tokio::test]
async fn docker_internal_manifest_miss_get_404_not_403() {
use crate::config::DockerUpstream;
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal/**".to_string()];
c.docker.upstreams = vec![DockerUpstream {
url: BLACKHOLE.to_string(),
auth: None,
namespace: None,
prefix: None,
}];
});
let resp = send(
&ctx.app,
Method::GET,
"/v2/internal/backend/manifests/3.19.2",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::NOT_FOUND,
"internal-ns manifest miss must be 404, not 403 (#821)"
);
let body = body_bytes(resp).await;
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(
json["errors"][0]["code"], "MANIFEST_UNKNOWN",
"must be OCI MANIFEST_UNKNOWN — proves the internal branch fired, upstream never contacted"
);
}
#[tokio::test]
async fn docker_internal_manifest_miss_head_404_not_403() {
use crate::config::DockerUpstream;
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal/**".to_string()];
c.docker.upstreams = vec![DockerUpstream {
url: BLACKHOLE.to_string(),
auth: None,
namespace: None,
prefix: None,
}];
});
let resp = send(
&ctx.app,
Method::HEAD,
"/v2/internal/backend/manifests/3.19.2",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::NOT_FOUND,
"internal-ns manifest HEAD miss must be 404, not the fatal-to-docker 403 (#821)"
);
}
#[tokio::test]
async fn docker_internal_referrers_tag_miss_head_404() {
use crate::config::DockerUpstream;
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal/**".to_string()];
c.docker.upstreams = vec![DockerUpstream {
url: BLACKHOLE.to_string(),
auth: None,
namespace: None,
prefix: None,
}];
});
let resp = send(
&ctx.app,
Method::HEAD,
"/v2/internal/app/manifests/sha256-1111111111111111111111111111111111111111111111111111111111111111",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::NOT_FOUND,
"internal-ns referrers-tag miss must be 404 (#821)"
);
}
#[tokio::test]
async fn docker_internal_blob_miss_get_404_and_head_agrees() {
use crate::config::DockerUpstream;
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal/**".to_string()];
c.docker.upstreams = vec![DockerUpstream {
url: BLACKHOLE.to_string(),
auth: None,
namespace: None,
prefix: None,
}];
});
let digest = "sha256:2222222222222222222222222222222222222222222222222222222222222222";
let uri = format!("/v2/internal/backend/blobs/{digest}");
let get = send(&ctx.app, Method::GET, &uri, "").await;
assert_eq!(
get.status(),
StatusCode::NOT_FOUND,
"internal-ns blob GET miss must be 404, not 403 (#821)"
);
let body = body_bytes(get).await;
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["errors"][0]["code"], "BLOB_UNKNOWN");
let head = send(&ctx.app, Method::HEAD, &uri, "").await;
assert_eq!(
head.status(),
StatusCode::NOT_FOUND,
"internal-ns blob HEAD and GET must agree (both 404)"
);
}
#[tokio::test]
async fn docker_internal_manifest_served_local_still_ok() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal/**".to_string()];
});
let manifest = serde_json::json!({
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 0,
"digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
},
"layers": []
});
ctx.state
.storage
.put(
"docker/internal/backend/manifests/1.0.json",
&serde_json::to_vec(&manifest).unwrap(),
)
.await
.unwrap();
let resp = send(
&ctx.app,
Method::GET,
"/v2/internal/backend/manifests/1.0",
"",
)
.await;
assert_eq!(
resp.status(),
StatusCode::OK,
"locally-hosted internal manifest must still be served, not blocked (#733 serve-local)"
);
}
#[tokio::test]
async fn docker_internal_miss_increments_isolation_refused_metric() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal/**".to_string()];
});
let before = crate::metrics::NAMESPACE_ISOLATION_REFUSED_TOTAL
.with_label_values(&["docker"])
.get();
let resp = send(
&ctx.app,
Method::GET,
"/v2/internal/backend/manifests/9.9.9",
"",
)
.await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let after = crate::metrics::NAMESPACE_ISOLATION_REFUSED_TOTAL
.with_label_values(&["docker"])
.get();
assert!(
after > before,
"a docker internal-namespace refusal must increment nora_namespace_isolation_refused_total{{registry=docker}}"
);
}
#[tokio::test]
async fn pypi_internal_refusal_increments_isolation_refused_metric() {
let ctx = create_test_context_with_config(|c| {
c.curation.internal_namespaces = vec!["internal*".to_string()];
c.pypi.proxy = Some(BLACKHOLE.to_string());
});
let before = crate::metrics::NAMESPACE_ISOLATION_REFUSED_TOTAL
.with_label_values(&["pypi"])
.get();
let resp = send(&ctx.app, Method::GET, "/simple/internalpkg-metric/", "").await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
let after = crate::metrics::NAMESPACE_ISOLATION_REFUSED_TOTAL
.with_label_values(&["pypi"])
.get();
assert!(
after > before,
"a non-docker internal-namespace refusal (check_namespace_isolation) must increment the isolation-refused metric"
);
}