20260324 完成和routinator对齐snapshot delta replay correctness一致
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
use std::process::Command;
|
||||
|
||||
use rpki::validation::cert_path::{CertPathError, validate_ee_cert_path};
|
||||
use rpki::validation::cert_path::{
|
||||
CertPathError, validate_ee_cert_path, validate_ee_cert_path_with_predecoded_ee,
|
||||
};
|
||||
|
||||
fn openssl_available() -> bool {
|
||||
Command::new("openssl")
|
||||
@@ -295,6 +297,57 @@ fn validate_ee_cert_path_with_prevalidated_issuer_covers_success_and_error_paths
|
||||
assert!(matches!(err, CertPathError::EeRevoked), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_cert_path_with_predecoded_ee_matches_prevalidated_path_rules() {
|
||||
use rpki::data_model::common::BigUnsigned;
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::rc::ResourceCertificate;
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
let g = generate_issuer_ca_ee_and_crl(
|
||||
"keyUsage = critical, digitalSignature
|
||||
",
|
||||
);
|
||||
let issuer = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
|
||||
let ee = ResourceCertificate::decode_der(&g.ee_der).expect("decode ee");
|
||||
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
|
||||
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
|
||||
.expect("parse issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
|
||||
validate_ee_cert_path_with_predecoded_ee(
|
||||
&ee,
|
||||
&g.ee_der,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
Some("rsync://example.test/repo/issuer/issuer.crl"),
|
||||
now,
|
||||
)
|
||||
.expect("predecoded ee path ok");
|
||||
|
||||
let mut revoked = HashSet::new();
|
||||
revoked.insert(BigUnsigned::from_biguint(&ee.tbs.serial_number).bytes_be);
|
||||
let err = validate_ee_cert_path_with_predecoded_ee(
|
||||
&ee,
|
||||
&g.ee_der,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&revoked,
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
Some("rsync://example.test/repo/issuer/issuer.crl"),
|
||||
now,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::EeRevoked), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_cert_path_with_prevalidated_issuer_rejects_non_ee_and_non_ca_issuer() {
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
|
||||
fn multi_rir_bundle_root() -> std::path::PathBuf {
|
||||
@@ -71,6 +72,94 @@ fn multi_rir_case_info_resolves_all_five_rirs_and_timings() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_rir_case_info_prefers_lock_validation_time_over_replay_started_at() {
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let bundle_root = td.path();
|
||||
let rir_root = bundle_root.join("apnic");
|
||||
fs::create_dir_all(rir_root.join("base-payload-archive")).expect("base archive dir");
|
||||
fs::create_dir_all(rir_root.join("payload-delta-archive")).expect("delta archive dir");
|
||||
fs::create_dir_all(rir_root.join("timings")).expect("timings dir");
|
||||
|
||||
fs::write(
|
||||
bundle_root.join("timing-summary.json"),
|
||||
r#"{"apnic":{"durations":{"base-replay":1.5,"delta-replay":2.5}}}"#,
|
||||
)
|
||||
.expect("write timing summary");
|
||||
fs::write(
|
||||
rir_root.join("base-locks.json"),
|
||||
r#"{"version":1,"capture":"base-cap","validationTime":"2026-03-16T11:49:15+08:00","rrdp":{},"rsync":{}}"#,
|
||||
)
|
||||
.expect("write base locks");
|
||||
fs::write(
|
||||
rir_root.join("locks-delta.json"),
|
||||
r#"{"version":1,"capture":"delta-cap","baseCapture":"base-cap","baseLocksSha256":"deadbeef","validationTime":"2026-03-16T12:14:10+08:00","rrdp":{},"rsync":{}}"#,
|
||||
)
|
||||
.expect("write delta locks");
|
||||
fs::write(
|
||||
rir_root.join("timings/base-replay.json"),
|
||||
r#"{"startedAt":"2099-01-01T00:00:00Z","durationSeconds":1.5}"#,
|
||||
)
|
||||
.expect("write base timing");
|
||||
fs::write(
|
||||
rir_root.join("timings/delta-replay.json"),
|
||||
r#"{"startedAt":"2099-01-02T00:00:00Z","durationSeconds":2.5}"#,
|
||||
)
|
||||
.expect("write delta timing");
|
||||
for rel in [
|
||||
"base-vrps.csv",
|
||||
"record-delta.csv",
|
||||
"replay-delta.csv",
|
||||
"verification.json",
|
||||
"README.md",
|
||||
] {
|
||||
fs::write(rir_root.join(rel), "placeholder
|
||||
").expect("write required file");
|
||||
}
|
||||
|
||||
let repo_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let out = Command::new("python3")
|
||||
.arg(helper_script())
|
||||
.args([
|
||||
"--bundle-root",
|
||||
bundle_root.to_string_lossy().as_ref(),
|
||||
"--repo-root",
|
||||
repo_root.to_string_lossy().as_ref(),
|
||||
"--rir",
|
||||
"apnic",
|
||||
])
|
||||
.output()
|
||||
.expect("run helper script");
|
||||
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"helper failed: status={}
|
||||
stdout={}
|
||||
stderr={}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
|
||||
let json: serde_json::Value = serde_json::from_slice(&out.stdout).expect("parse helper json");
|
||||
assert_eq!(
|
||||
json["validation_times"]["snapshot"].as_str(),
|
||||
Some("2026-03-16T11:49:15+08:00")
|
||||
);
|
||||
assert_eq!(
|
||||
json["validation_times"]["delta"].as_str(),
|
||||
Some("2026-03-16T12:14:10+08:00")
|
||||
);
|
||||
assert_eq!(
|
||||
json["timing_started_at"]["snapshot_replay"].as_str(),
|
||||
Some("2099-01-01T00:00:00Z")
|
||||
);
|
||||
assert_eq!(
|
||||
json["timing_started_at"]["delta_replay"].as_str(),
|
||||
Some("2099-01-02T00:00:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_rir_wrapper_describe_mode_works_for_ripe() {
|
||||
let bundle_root = multi_rir_bundle_root();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rpki::fetch::rsync::LocalDirRsyncFetcher;
|
||||
use rpki::policy::{Policy, SignedObjectFailurePolicy, SyncPreference};
|
||||
use rpki::storage::{PackFile, PackTime, RocksStore};
|
||||
use rpki::storage::{PackFile, PackTime, RocksStore, VcirOutputType};
|
||||
use rpki::sync::repo::sync_publication_point;
|
||||
use rpki::sync::rrdp::Fetcher;
|
||||
use rpki::validation::manifest::process_manifest_publication_point;
|
||||
@@ -403,4 +403,45 @@ fn process_snapshot_for_issuer_drop_publication_point_on_invalid_aspa_bytes() {
|
||||
assert!(!out.warnings.is_empty());
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn process_snapshot_for_issuer_populates_local_outputs_cache_from_real_cernet_fixture() {
|
||||
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
|
||||
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
|
||||
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
|
||||
|
||||
let pack = build_publication_point_snapshot_from_local_rsync_fixture(
|
||||
&dir,
|
||||
&rsync_base_uri,
|
||||
&manifest_rsync_uri,
|
||||
validation_time,
|
||||
);
|
||||
|
||||
let issuer_ca_der = issuer_ca_fixture();
|
||||
let issuer_ca = rpki::data_model::rc::ResourceCertificate::decode_der(&issuer_ca_der)
|
||||
.expect("decode issuer ca");
|
||||
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&Policy::default(),
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!out.local_outputs_cache.is_empty(), "expected cached VCIR local outputs");
|
||||
assert_eq!(out.local_outputs_cache.len(), out.vrps.len());
|
||||
assert!(out
|
||||
.local_outputs_cache
|
||||
.iter()
|
||||
.all(|entry| entry.output_type == VcirOutputType::Vrp));
|
||||
assert!(out
|
||||
.local_outputs_cache
|
||||
.iter()
|
||||
.all(|entry| entry.source_object_type == "roa"));
|
||||
}
|
||||
|
||||
// NOTE: DN-based issuer resolution and pack-local CA indexing have been removed for determinism.
|
||||
|
||||
@@ -284,3 +284,28 @@ fn write_multi_rir_summary_aggregates_case_reports() {
|
||||
assert!(md.contains("Multi-RIR Replay Summary"), "{md}");
|
||||
assert!(md.contains("| afrinic | true | 10.000 | 5.000 | 2.000 | true | 12.000 | 6.000 | 3.000 |"), "{md}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apnic_snapshot_profile_script_dry_run_builds_command() {
|
||||
let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts/payload_replay/run_apnic_snapshot_replay_profile.sh");
|
||||
let out = Command::new("bash")
|
||||
.env("DRY_RUN", "1")
|
||||
.arg(&script)
|
||||
.output()
|
||||
.expect("run profile script dry-run");
|
||||
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"script failed: status={}\nstdout={}\nstderr={}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
assert!(stdout.contains("--payload-replay-archive"), "{stdout}");
|
||||
assert!(stdout.contains("--payload-replay-locks"), "{stdout}");
|
||||
assert!(stdout.contains("--analyze"), "{stdout}");
|
||||
assert!(stdout.contains("--profile-cpu"), "{stdout}");
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ fn tree_continues_when_a_publication_point_fails() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -136,6 +137,7 @@ fn tree_continues_when_a_publication_point_fails() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
|
||||
@@ -122,6 +122,7 @@ fn tree_enqueues_children_for_fresh_and_current_instance_vcir_results() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -142,6 +143,7 @@ fn tree_enqueues_children_for_fresh_and_current_instance_vcir_results() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -162,6 +164,7 @@ fn tree_enqueues_children_for_fresh_and_current_instance_vcir_results() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -182,6 +185,7 @@ fn tree_enqueues_children_for_fresh_and_current_instance_vcir_results() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -239,6 +243,7 @@ fn tree_respects_max_depth_and_max_instances() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -259,6 +264,7 @@ fn tree_respects_max_depth_and_max_instances() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -308,6 +314,7 @@ fn tree_audit_includes_parent_and_discovered_from_for_non_root_nodes() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -328,6 +335,7 @@ fn tree_audit_includes_parent_and_discovered_from_for_non_root_nodes() {
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -380,6 +388,7 @@ fn tree_prefers_lexicographically_first_discovery_when_duplicate_manifest_is_que
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -400,6 +409,7 @@ fn tree_prefers_lexicographically_first_discovery_when_duplicate_manifest_is_que
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
|
||||
Reference in New Issue
Block a user