server/lib/tests/a_promotion_gate_refuses_over_grpc.rs
//! ★ **The promotion gate refuses over the real gRPC door** (lane OX-WIRED2): a running holger
//! with a two-rung ladder (`mirror` → `release`, the `osv` gate over an advisory feed in the
//! config) is asked `PromotionService.PromoteArtifact` by a real tonic client:
//!
//! * the vulnerable `serde@1.0.0` is refused by the gate with the frozen `holger-promote-2`,
//! and the release repository serves nothing of it;
//! * the clean `serde@1.0.1` is promoted and sealed into the PROD store (its ref names the
//! content address, the blob is the bytes);
//! * a Reader is refused the promotion.
//!
//! Before, the gate's refusal was driven only by the local `holger-server promote` verb
//! (server/cli tests/promote_cli.rs) and the gRPC door only for a missing source
//! (grpc_error_codes_on_the_wire.rs).
use std::io::Write;
use holger_server_lib::grpc::holger_proto::promotion_service_client::PromotionServiceClient;
use holger_server_lib::grpc::holger_proto::repository_service_client::RepositoryServiceClient;
use holger_server_lib::grpc::holger_proto::{
ArtifactId as ProtoId, FetchArtifactRequest, PromoteArtifactRequest,
};
use holger_server_lib::{read_ron_config, wire_holger, ArtifactId, Holger};
use traits::test_support::hold_port;
const ADMIN: &str = "rel-admin-token-of-sufficient-length-for-holger";
const READER: &str = "rita-token-of-sufficient-length-for-holger";
const OSV_FEED: &str = r#"{
"id": "OSV-TEST-0001",
"summary": "test advisory that must block the promotion",
"affected": [ { "package": { "ecosystem": "crates.io", "name": "serde" }, "versions": ["1.0.0"] } ],
"severity": [ { "type": "CVSS_V3", "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" } ]
}"#;
struct Running {
_data: tempfile::TempDir,
_cfg: tempfile::NamedTempFile,
_holger: Holger,
grpc: String,
}
fn ron(d: &str, grpc: u16, http: u16) -> String {
let repo = |name: &str| {
format!(
r#"(ron_name: "{name}", ron_repo_type: "rust", ron_data_dir: Some("{d}/{name}"), ron_base_url: Some("http://x"), ron_upstreams: [], ron_in: None,
ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "door"))),"#
)
};
format!(
r#"(
repositories: [ {mirror} {release} ],
exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:{grpc}", ron_http_url: Some("127.0.0.1:{http}")) ],
storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "{d}") ],
audit: (dir: "{d}/audit", enabled: true),
auth: Some((
methods: [ StaticToken(subject: "rel", token: Some("{ADMIN}")), StaticToken(subject: "rita", token: Some("{READER}")) ],
roles: {{"rel": Admin, "rita": Reader}},
)),
promotion: Some((
ladder_name: "rust",
stages: [ (name: "dev", repo: "mirror"), (name: "prod", repo: "release", gated: true, gates: ["osv"]) ],
advisory_db: Some("{d}/advisories.json"),
// The seeded crates are bare bytes with no licence; the gate under test is the CVE's.
unknown_license: Pass,
)),
)"#,
mirror = repo("mirror"),
release = repo("release"),
)
}
fn start() -> Running {
let mut last = String::new();
for _ in 0..8 {
let (grpc, http) = (hold_port(), hold_port());
let data = tempfile::tempdir().unwrap();
let d = data.path().display().to_string();
std::fs::write(data.path().join("advisories.json"), OSV_FEED).unwrap();
let mut cfg = tempfile::NamedTempFile::new().unwrap();
cfg.write_all(ron(&d, grpc, http).as_bytes()).unwrap();
let go = || -> Result<Holger, String> {
let mut h = read_ron_config(cfg.path()).map_err(|e| format!("config: {e:#}"))?;
h.instantiate_backends()
.map_err(|e| format!("backends: {e:#}"))?;
for v in ["1.0.0", "1.0.1"] {
let id = ArtifactId {
namespace: None,
name: "serde".into(),
version: v.into(),
};
h.put("mirror", &id, format!("serde-{v}-bytes").as_bytes())
.map_err(|e| format!("seed: {e:#}"))?;
}
wire_holger(&mut h).map_err(|e| format!("wire: {e:#}"))?;
h.start().map_err(|e| format!("start: {e:#}"))?;
Ok(h)
};
match go() {
Ok(h) => {
return Running {
_data: data,
_cfg: cfg,
_holger: h,
grpc: format!("http://127.0.0.1:{grpc}"),
}
}
Err(e) if e.starts_with("config") => panic!("{e}"),
Err(e) => last = e,
}
}
panic!("server never started; last error: {last}");
}
/// The PROD store's ref for `serde@version` in `release` (`<data root>/holger-prod-store`).
fn sealed_ref(r: &Running, version: &str) -> std::path::PathBuf {
r._data
.path()
.join("holger-prod-store/refs/release")
.join(format!("__serde__{version}.ref"))
}
fn as_<T>(token: &str, body: T) -> tonic::Request<T> {
let mut r = tonic::Request::new(body);
r.metadata_mut()
.insert("authorization", format!("Bearer {token}").parse().unwrap());
r
}
fn serde(version: &str) -> Option<ProtoId> {
Some(ProtoId {
namespace: String::new(),
name: "serde".into(),
version: version.into(),
})
}
fn promote(version: &str) -> PromoteArtifactRequest {
PromoteArtifactRequest {
from_repo: "mirror".into(),
to_repo: "release".into(),
artifact: serde(version),
..Default::default()
}
}
async fn clients(
r: &Running,
) -> (
PromotionServiceClient<tonic::transport::Channel>,
RepositoryServiceClient<tonic::transport::Channel>,
) {
for _ in 0..200 {
if let (Ok(p), Ok(c)) = (
PromotionServiceClient::connect(r.grpc.clone()).await,
RepositoryServiceClient::connect(r.grpc.clone()).await,
) {
return (p, c);
}
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
}
panic!("holger never answered at {}", r.grpc);
}
#[tokio::test(flavor = "multi_thread")]
async fn the_osv_gate_refuses_a_vulnerable_promotion_over_grpc_and_passes_a_clean_one() {
let r = start();
let (mut p, mut c) = clients(&r).await;
let st = p
.promote_artifact(as_(READER, promote("1.0.1")))
.await
.expect_err("a Reader does not promote");
assert!(
matches!(
st.code(),
tonic::Code::PermissionDenied | tonic::Code::Unauthenticated
),
"{st:?}"
);
let st = p
.promote_artifact(as_(ADMIN, promote("1.0.0")))
.await
.expect_err("the osv gate refuses serde@1.0.0");
assert!(
st.message()
.contains(holger_server_lib::errcode::PROMOTE_POLICY_BLOCKED.code),
"refused by the frozen code: {st:?}"
);
assert!(
st.message().contains("OSV-TEST-0001"),
"refused by the advisory, named: {st:?}"
);
assert!(
!sealed_ref(&r, "1.0.0").exists(),
"nothing of the refused version is sealed"
);
let served = c
.fetch_artifact(as_(
ADMIN,
FetchArtifactRequest {
repository: "release".into(),
id: serde("1.0.0"),
},
))
.await;
assert!(
served.is_err(),
"release serves nothing of the refused version: {served:?}"
);
let ok = p
.promote_artifact(as_(ADMIN, promote("1.0.1")))
.await
.expect("the clean version is promoted")
.into_inner();
assert!(ok.success && !ok.content_address.is_empty(), "{ok:?}");
// The seal: the ref names the content address, and the blob is the promoted bytes.
let record = std::fs::read_to_string(sealed_ref(&r, "1.0.1")).expect("the sealed ref");
assert!(record.contains(&ok.content_address), "{record}");
let blob = r
._data
.path()
.join("holger-prod-store/blobs")
.join(&ok.content_address);
assert_eq!(
std::fs::read(&blob).expect("the sealed blob"),
b"serde-1.0.1-bytes",
"the promoted bytes, sealed"
);
}