将fetch pp cache改成使用vcir结构,跑通apnic全量同步

This commit is contained in:
2026-03-13 14:45:41 +08:00
parent e3339533b8
commit cf764c35bb
53 changed files with 8931 additions and 2201 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ pub mod cert_path;
pub mod from_tal;
pub mod manifest;
pub mod objects;
pub mod publication_point;
pub mod run;
pub mod run_tree_from_tal;
pub mod tree;

View File

@@ -10,8 +10,10 @@ use crate::data_model::roa::{IpPrefix, RoaAfi, RoaDecodeError, RoaObject, RoaVal
use crate::data_model::signed_object::SignedObjectVerifyError;
use crate::policy::{Policy, SignedObjectFailurePolicy};
use crate::report::{RfcRef, Warning};
use crate::storage::{FetchCachePpPack, PackFile};
use crate::storage::PackFile;
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_prevalidated_issuer};
use crate::validation::manifest::PublicationPointData;
use crate::validation::publication_point::PublicationPointSnapshot;
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
@@ -81,10 +83,10 @@ pub struct ObjectsStats {
pub publication_point_dropped: bool,
}
/// Process objects from a fetch_cache_pp publication point pack using a known issuer CA certificate
/// Process objects from a publication point snapshot using a known issuer CA certificate
/// and its effective resources (resolved via the resource-path, RFC 6487 §7.2).
pub fn process_fetch_cache_pp_pack_for_issuer(
pack: &FetchCachePpPack,
pub fn process_publication_point_for_issuer<P: PublicationPointData>(
publication_point: &P,
policy: &Policy,
issuer_ca_der: &[u8],
issuer_ca_rsync_uri: Option<&str>,
@@ -93,23 +95,24 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
validation_time: time::OffsetDateTime,
timing: Option<&TimingHandle>,
) -> ObjectsOutput {
let manifest_rsync_uri = publication_point.manifest_rsync_uri();
let manifest_bytes = publication_point.manifest_bytes();
let locked_files = publication_point.files();
let mut warnings: Vec<Warning> = Vec::new();
let mut stats = ObjectsStats::default();
stats.roa_total = pack
.files
stats.roa_total = locked_files
.iter()
.filter(|f| f.rsync_uri.ends_with(".roa"))
.count();
stats.aspa_total = pack
.files
stats.aspa_total = locked_files
.iter()
.filter(|f| f.rsync_uri.ends_with(".asa"))
.count();
let mut audit: Vec<ObjectAuditEntry> = Vec::new();
// Enforce that `manifest_bytes` is actually a manifest object.
let _manifest =
ManifestObject::decode_der(&pack.manifest_bytes).expect("fetch_cache_pp manifest decodes");
let _manifest = ManifestObject::decode_der(manifest_bytes)
.expect("publication point snapshot manifest decodes");
// Decode issuer CA once; if it fails we cannot validate ROA/ASPA EE certificates.
let issuer_ca = match ResourceCertificate::decode_der(issuer_ca_der) {
@@ -121,9 +124,9 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
"dropping publication point: issuer CA decode failed: {e}"
))
.with_rfc_refs(&[RfcRef("RFC 6487 §7.2"), RfcRef("RFC 5280 §6.1")])
.with_context(&pack.manifest_rsync_uri),
.with_context(manifest_rsync_uri),
);
for f in &pack.files {
for f in locked_files {
if f.rsync_uri.ends_with(".roa") {
audit.push(ObjectAuditEntry {
rsync_uri: f.rsync_uri.clone(),
@@ -163,7 +166,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
rem.len()
))
.with_rfc_refs(&[RfcRef("RFC 5280 §4.1.2.7")])
.with_context(&pack.manifest_rsync_uri),
.with_context(manifest_rsync_uri),
);
return ObjectsOutput {
vrps: Vec::new(),
@@ -180,7 +183,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
"dropping publication point: issuer SPKI parse failed: {e}"
))
.with_rfc_refs(&[RfcRef("RFC 5280 §4.1.2.7")])
.with_context(&pack.manifest_rsync_uri),
.with_context(manifest_rsync_uri),
);
return ObjectsOutput {
vrps: Vec::new(),
@@ -192,8 +195,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
}
};
let mut crl_cache: std::collections::HashMap<String, CachedIssuerCrl> = pack
.files
let mut crl_cache: std::collections::HashMap<String, CachedIssuerCrl> = locked_files
.iter()
.filter(|f| f.rsync_uri.ends_with(".crl"))
.map(|f| {
@@ -207,23 +209,26 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
let issuer_resources_index =
build_issuer_resources_index(issuer_effective_ip, issuer_effective_as);
// If the pack has signed objects but no CRLs at all, we cannot validate any embedded EE
// certificate paths deterministically (EE CRLDP must reference an rsync URI in the pack).
// If the snapshot has signed objects but no CRLs at all, we cannot validate any embedded EE
// certificate paths deterministically (EE CRLDP must reference an rsync URI in the snapshot).
if crl_cache.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) {
stats.publication_point_dropped = true;
warnings.push(
Warning::new("dropping publication point: no CRL files in fetch_cache_pp")
Warning::new("dropping publication point: no CRL files in validated publication point")
.with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")])
.with_context(&pack.manifest_rsync_uri),
.with_context(manifest_rsync_uri),
);
for f in &pack.files {
for f in locked_files {
if f.rsync_uri.ends_with(".roa") {
audit.push(ObjectAuditEntry {
rsync_uri: f.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&f.sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Skipped,
detail: Some("skipped due to missing CRL files in fetch_cache_pp".to_string()),
detail: Some(
"skipped due to missing CRL files in validated publication point"
.to_string(),
),
});
} else if f.rsync_uri.ends_with(".asa") {
audit.push(ObjectAuditEntry {
@@ -231,7 +236,10 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
sha256_hex: sha256_hex_from_32(&f.sha256),
kind: AuditObjectKind::Aspa,
result: AuditObjectResult::Skipped,
detail: Some("skipped due to missing CRL files in fetch_cache_pp".to_string()),
detail: Some(
"skipped due to missing CRL files in validated publication point"
.to_string(),
),
});
}
}
@@ -247,7 +255,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
let mut vrps: Vec<Vrp> = Vec::new();
let mut aspas: Vec<AspaAttestation> = Vec::new();
for (idx, file) in pack.files.iter().enumerate() {
for (idx, file) in locked_files.iter().enumerate() {
if file.rsync_uri.ends_with(".roa") {
let _t = timing.as_ref().map(|t| t.span_phase("objects_roa_total"));
match process_roa_with_issuer(
@@ -300,7 +308,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
result: AuditObjectResult::Error,
detail: Some(e.to_string()),
});
for f in pack.files.iter().skip(idx + 1) {
for f in locked_files.iter().skip(idx + 1) {
if f.rsync_uri.ends_with(".roa") {
audit.push(ObjectAuditEntry {
rsync_uri: f.rsync_uri.clone(),
@@ -333,7 +341,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
file.rsync_uri
))
.with_rfc_refs(&refs)
.with_context(&pack.manifest_rsync_uri),
.with_context(manifest_rsync_uri),
);
return ObjectsOutput {
vrps: Vec::new(),
@@ -397,7 +405,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
result: AuditObjectResult::Error,
detail: Some(e.to_string()),
});
for f in pack.files.iter().skip(idx + 1) {
for f in locked_files.iter().skip(idx + 1) {
if f.rsync_uri.ends_with(".roa") {
audit.push(ObjectAuditEntry {
rsync_uri: f.rsync_uri.clone(),
@@ -430,7 +438,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
file.rsync_uri
))
.with_rfc_refs(&refs)
.with_context(&pack.manifest_rsync_uri),
.with_context(manifest_rsync_uri),
);
return ObjectsOutput {
vrps: Vec::new(),
@@ -453,6 +461,28 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
audit,
}
}
/// Compatibility wrapper that processes a publication point snapshot.
pub fn process_publication_point_snapshot_for_issuer(
pack: &PublicationPointSnapshot,
policy: &Policy,
issuer_ca_der: &[u8],
issuer_ca_rsync_uri: Option<&str>,
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
validation_time: time::OffsetDateTime,
timing: Option<&TimingHandle>,
) -> ObjectsOutput {
process_publication_point_for_issuer(
pack,
policy,
issuer_ca_der,
issuer_ca_rsync_uri,
issuer_effective_ip,
issuer_effective_as,
validation_time,
timing,
)
}
#[derive(Debug, thiserror::Error)]
enum ObjectValidateError {
@@ -480,12 +510,12 @@ enum ObjectValidateError {
MissingCrlDpUris,
#[error(
"no CRL available in fetch_cache_pp (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)"
"no CRL available in publication point snapshot (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)"
)]
MissingCrlInPack,
#[error(
"CRL referenced by CRLDistributionPoints not found in fetch_cache_pp: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)"
"CRL referenced by CRLDistributionPoints not found in publication point snapshot: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)"
)]
CrlNotFound(String),
@@ -1283,7 +1313,7 @@ mod tests {
}
#[test]
fn choose_crl_for_certificate_reports_missing_crl_in_pack() {
fn choose_crl_for_certificate_reports_missing_crl_in_snapshot() {
let roa_der =
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
@@ -1335,7 +1365,7 @@ mod tests {
}
#[test]
fn choose_crl_for_certificate_reports_not_found_when_crldp_does_not_match_pack() {
fn choose_crl_for_certificate_reports_not_found_when_crldp_does_not_match_snapshot() {
let roa_der =
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
@@ -1421,4 +1451,225 @@ mod tests {
let err = validate_ee_resources_subset(ee, Some(&issuer_ip), None, &idx).unwrap_err();
assert!(matches!(err, ObjectValidateError::EeResourcesNotSubset));
}
#[test]
fn extra_rfc_refs_for_crl_selection_distinguishes_crl_errors() {
assert_eq!(
extra_rfc_refs_for_crl_selection(&ObjectValidateError::MissingCrlDpUris),
RFC_CRLDP
);
assert_eq!(
extra_rfc_refs_for_crl_selection(&ObjectValidateError::CrlNotFound(
"rsync://example.test/x.crl".to_string(),
)),
RFC_CRLDP_AND_LOCKED_PACK
);
assert!(
extra_rfc_refs_for_crl_selection(&ObjectValidateError::MissingCrlInPack).is_empty()
);
}
#[test]
fn as_subset_helpers_cover_success_and_failure_paths() {
let child = AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Id(5),
AsIdOrRange::Range { min: 7, max: 9 },
]);
let parent =
AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range { min: 1, max: 10 }]);
let parent_intervals = [(1, 10)];
assert!(as_choice_subset(None, Some(&parent)));
assert!(!as_choice_subset(Some(&child), None));
assert!(!as_choice_subset(
Some(&AsIdentifierChoice::Inherit),
Some(&parent)
));
assert!(!as_choice_subset(
Some(&child),
Some(&AsIdentifierChoice::Inherit)
));
assert!(as_choice_subset(Some(&child), Some(&parent)));
assert!(as_choice_subset_indexed(None, Some(&parent_intervals)));
assert!(!as_choice_subset_indexed(Some(&child), None));
assert!(!as_choice_subset_indexed(
Some(&AsIdentifierChoice::Inherit),
Some(&parent_intervals),
));
assert!(as_choice_subset_indexed(
Some(&child),
Some(&parent_intervals)
));
assert!(!as_choice_subset_indexed(
Some(&AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range { min: 11, max: 12 }
])),
Some(&parent_intervals),
));
let child_set = AsResourceSet {
asnum: Some(child.clone()),
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(42)])),
};
let parent_set = AsResourceSet {
asnum: Some(parent.clone()),
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range { min: 40, max: 50 },
])),
};
assert!(as_resources_is_subset(&child_set, &parent_set));
assert!(as_resources_is_subset_indexed(
&child_set,
&parent_set,
&IssuerResourcesIndex {
asnum: Some(vec![(1, 10)]),
rdi: Some(vec![(40, 50)]),
..IssuerResourcesIndex::default()
},
));
}
#[test]
fn ip_subset_helpers_cover_strict_and_indexed_paths() {
let parent = IpResourceSet {
families: vec![
IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![
IpAddressOrRange::Prefix(IpPrefix {
afi: Afi::Ipv4,
prefix_len: 8,
addr: vec![10, 0, 0, 0],
}),
IpAddressOrRange::Range(IpAddressRange {
min: vec![192, 0, 2, 0],
max: vec![192, 0, 2, 255],
}),
]),
},
IpAddressFamily {
afi: Afi::Ipv6,
choice: IpAddressChoice::Inherit,
},
],
};
let child = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![
IpAddressOrRange::Prefix(IpPrefix {
afi: Afi::Ipv4,
prefix_len: 16,
addr: vec![10, 1, 0, 0],
}),
IpAddressOrRange::Range(IpAddressRange {
min: vec![192, 0, 2, 10],
max: vec![192, 0, 2, 20],
}),
]),
}],
};
let strict_bad = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::Inherit,
}],
};
assert!(ip_resources_is_subset(&child, &parent));
assert!(ip_resources_to_merged_intervals(&parent).contains_key(&AfiKey::V4));
assert!(ip_resources_to_merged_intervals_strict(&child).is_ok());
assert!(ip_resources_to_merged_intervals_strict(&strict_bad).is_err());
let idx = build_issuer_resources_index(
Some(&parent),
Some(&AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range {
min: 64500,
max: 64510,
},
])),
rdi: Some(AsIdentifierChoice::Inherit),
}),
);
assert!(idx.ip_v4.is_some());
assert!(idx.ip_v6.is_none());
assert!(idx.asnum.is_some());
assert!(idx.rdi.is_none());
assert!(ip_resources_is_subset_indexed(&child, &parent, &idx));
assert!(!ip_resources_is_subset_indexed(&strict_bad, &parent, &idx));
}
#[test]
fn interval_and_byte_helpers_cover_edge_cases() {
let parent = vec![(vec![0, 0, 0, 0], vec![0, 0, 0, 10])];
assert!(interval_is_covered(&parent, &[0, 0, 0, 1], &[0, 0, 0, 2]));
assert!(!interval_is_covered(
&parent,
&[0, 0, 0, 11],
&[0, 0, 0, 12]
));
assert!(intervals_are_covered(
&parent,
&[(vec![0, 0, 0, 1], vec![0, 0, 0, 2])]
));
assert!(!intervals_are_covered(
&parent,
&[(vec![0, 0, 0, 9], vec![0, 0, 0, 11])],
));
let prefix = RcIpPrefix {
afi: Afi::Ipv4,
prefix_len: 24,
addr: vec![203, 0, 113, 7],
};
assert_eq!(
prefix_to_range(&prefix),
(vec![203, 0, 113, 0], vec![203, 0, 113, 255])
);
assert_eq!(increment_bytes(&[0, 0, 0, 255]), vec![0, 0, 1, 0]);
assert!(bytes_is_next(&[0, 0, 1, 0], &[0, 0, 0, 255]));
assert!(!bytes_is_next(&[1, 2], &[1]));
}
#[test]
fn merged_interval_helpers_cover_empty_and_break_paths() {
let mut empty: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
merge_ip_intervals_in_place(&mut empty);
assert!(empty.is_empty());
let mut v = vec![
(vec![0, 0, 0, 20], vec![0, 0, 0, 30]),
(vec![0, 0, 0, 0], vec![0, 0, 0, 10]),
(vec![0, 0, 0, 11], vec![0, 0, 0, 19]),
];
v.sort_by(|(a, _), (b, _)| a.cmp(b));
merge_ip_intervals_in_place(&mut v);
assert_eq!(v, vec![(vec![0, 0, 0, 0], vec![0, 0, 0, 30])]);
assert_eq!(
as_choice_to_merged_intervals(&AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Id(1),
AsIdOrRange::Range { min: 2, max: 3 },
AsIdOrRange::Range { min: 7, max: 9 },
])),
vec![(1, 3), (7, 9)]
);
assert!(as_interval_is_covered(&[(1, 3), (7, 9)], 2, 3));
assert!(!as_interval_is_covered(&[(7, 9)], 2, 3));
}
#[test]
fn roa_output_helpers_cover_vrps_and_afi_strings() {
let roa_der =
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
let vrps = roa_to_vrps(&roa);
assert!(!vrps.is_empty());
assert!(vrps.iter().all(|vrp| vrp.asn == roa.roa.as_id));
assert_eq!(roa_afi_to_string(RoaAfi::Ipv4), "ipv4");
assert_eq!(roa_afi_to_string(RoaAfi::Ipv6), "ipv6");
}
}

View File

@@ -0,0 +1,18 @@
use crate::storage::{PackFile, PackTime};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicationPointSnapshot {
pub format_version: u32,
pub manifest_rsync_uri: String,
pub publication_point_rsync_uri: String,
pub manifest_number_be: Vec<u8>,
pub this_update: PackTime,
pub next_update: PackTime,
pub verified_at: PackTime,
pub manifest_bytes: Vec<u8>,
pub files: Vec<PackFile>,
}
impl PublicationPointSnapshot {
pub const FORMAT_VERSION_V1: u32 = 1;
}

View File

@@ -1,34 +1,35 @@
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
use crate::fetch::rsync::RsyncFetcher;
use crate::policy::Policy;
use crate::storage::{FetchCachePpKey, RocksStore};
use crate::sync::repo::{RepoSyncResult, sync_publication_point};
use crate::report::Warning;
use crate::storage::RocksStore;
use crate::sync::rrdp::Fetcher as HttpFetcher;
use crate::validation::manifest::{PublicationPointResult, process_manifest_publication_point};
use crate::validation::objects::{ObjectsOutput, process_fetch_cache_pp_pack_for_issuer};
use crate::validation::manifest::PublicationPointSource;
use crate::validation::objects::ObjectsOutput;
use crate::validation::tree::{CaInstanceHandle, PublicationPointRunner};
use crate::validation::tree_runner::Rpkiv1PublicationPointRunner;
use std::collections::HashMap;
use std::sync::Mutex;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RunOutput {
pub repo_sync: RepoSyncResult,
pub publication_point: PublicationPointResult,
pub publication_point_source: PublicationPointSource,
pub publication_point_warnings: Vec<Warning>,
pub objects: ObjectsOutput,
}
#[derive(Debug, thiserror::Error)]
pub enum RunError {
#[error("repo sync failed: {0}")]
RepoSync(#[from] crate::sync::repo::RepoSyncError),
#[error("manifest processing failed: {0}")]
Manifest(#[from] crate::validation::manifest::ManifestProcessError),
#[error("publication point runner failed: {0}")]
Runner(String),
}
/// v1 serial offline-friendly end-to-end execution for a single publication point.
/// Offline-friendly end-to-end execution for a single publication point.
///
/// This orchestrates:
/// 1) repo sync (RRDP or rsync fallback) into `raw_objects`
/// 2) manifest RP processing into a fetch_cache_pp pack (`fetch_cache_pp:<manifest-rsync-uri>`)
/// 3) signed object processing (ROA/ASPA) from the fetch_cache_pp pack
/// This reuses the same fresh-first runtime path as the tree runner:
/// 1) repository sync (RRDP or rsync fallback) into the current repository view
/// 2) manifest processing into a validated publication point result
/// 3) signed object processing (ROA/ASPA) and latest-VCIR persistence
pub fn run_publication_point_once(
store: &RocksStore,
policy: &Policy,
@@ -44,49 +45,41 @@ pub fn run_publication_point_once(
issuer_effective_as: Option<&AsResourceSet>,
validation_time: time::OffsetDateTime,
) -> Result<RunOutput, RunError> {
let repo_sync = sync_publication_point(
let handle = CaInstanceHandle {
depth: 0,
tal_id: "single-publication-point".to_string(),
parent_manifest_rsync_uri: None,
ca_certificate_der: issuer_ca_der.to_vec(),
ca_certificate_rsync_uri: issuer_ca_rsync_uri.map(str::to_string),
effective_ip_resources: issuer_effective_ip.cloned(),
effective_as_resources: issuer_effective_as.cloned(),
rsync_base_uri: rsync_base_uri.to_string(),
manifest_rsync_uri: manifest_rsync_uri.to_string(),
publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
rrdp_notification_uri: rrdp_notification_uri.map(str::to_string),
};
let runner = Rpkiv1PublicationPointRunner {
store,
policy,
rrdp_notification_uri,
rsync_base_uri,
http_fetcher,
rsync_fetcher,
None,
None,
)?;
let publication_point = process_manifest_publication_point(
store,
policy,
manifest_rsync_uri,
publication_point_rsync_uri,
issuer_ca_der,
issuer_ca_rsync_uri,
validation_time,
)?;
timing: None,
download_log: None,
rrdp_dedup: false,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: false,
rsync_repo_cache: Mutex::new(HashMap::new()),
};
let objects = process_fetch_cache_pp_pack_for_issuer(
&publication_point.pack,
policy,
issuer_ca_der,
issuer_ca_rsync_uri,
issuer_effective_ip,
issuer_effective_as,
validation_time,
None,
);
let result = runner
.run_publication_point(&handle)
.map_err(RunError::Runner)?;
Ok(RunOutput {
repo_sync,
publication_point,
objects,
publication_point_source: result.source,
publication_point_warnings: result.warnings,
objects: result.objects,
})
}
pub fn fetch_cache_pp_exists(store: &RocksStore, manifest_rsync_uri: &str) -> Result<bool, String> {
let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri);
store
.get_fetch_cache_pp(&key)
.map(|v| v.is_some())
.map_err(|e| e.to_string())
}

View File

@@ -17,6 +17,44 @@ use crate::validation::tree_runner::Rpkiv1PublicationPointRunner;
use std::collections::HashMap;
use std::sync::Mutex;
fn tal_id_from_url_like(s: &str) -> Option<String> {
let url = Url::parse(s).ok()?;
if let Some(last) = url
.path_segments()
.and_then(|segments| segments.filter(|seg| !seg.is_empty()).next_back())
{
let stem = last.rsplit_once('.').map(|(stem, _)| stem).unwrap_or(last);
let trimmed = stem.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
}
}
url.host_str().map(|host| host.to_string())
}
fn derive_tal_id(discovery: &DiscoveredRootCaInstance) -> String {
discovery
.tal_url
.as_deref()
.and_then(tal_id_from_url_like)
.or_else(|| {
discovery
.trust_anchor
.resolved_ta_uri
.as_ref()
.and_then(|uri| tal_id_from_url_like(uri.as_str()))
})
.or_else(|| {
discovery
.trust_anchor
.tal
.ta_uris
.first()
.and_then(|uri| tal_id_from_url_like(uri.as_str()))
})
.unwrap_or_else(|| "unknown-tal".to_string())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RunTreeFromTalOutput {
pub discovery: DiscoveredRootCaInstance,
@@ -43,12 +81,15 @@ pub enum RunTreeFromTalError {
pub fn root_handle_from_trust_anchor(
trust_anchor: &TrustAnchor,
tal_id: String,
ca_certificate_rsync_uri: Option<String>,
ca_instance: &crate::validation::ca_instance::CaInstanceUris,
) -> CaInstanceHandle {
let ta_rc = trust_anchor.ta_certificate.rc_ca.clone();
CaInstanceHandle {
depth: 0,
tal_id,
parent_manifest_rsync_uri: None,
ca_certificate_der: trust_anchor.ta_certificate.raw_der.clone(),
ca_certificate_rsync_uri,
effective_ip_resources: ta_rc.tbs.extensions.ip_resources.clone(),
@@ -79,14 +120,18 @@ pub fn run_tree_from_tal_url_serial(
validation_time,
timing: None,
download_log: None,
revalidate_only: config.revalidate_only,
rrdp_dedup: true,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: true,
rsync_repo_cache: Mutex::new(HashMap::new()),
};
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
let root = root_handle_from_trust_anchor(
&discovery.trust_anchor,
derive_tal_id(&discovery),
None,
&discovery.ca_instance,
);
let tree = run_tree_serial(root, &runner, config)?;
Ok(RunTreeFromTalOutput { discovery, tree })
@@ -112,14 +157,18 @@ pub fn run_tree_from_tal_url_serial_audit(
validation_time,
timing: None,
download_log: Some(download_log.clone()),
revalidate_only: config.revalidate_only,
rrdp_dedup: true,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: true,
rsync_repo_cache: Mutex::new(HashMap::new()),
};
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
let root = root_handle_from_trust_anchor(
&discovery.trust_anchor,
derive_tal_id(&discovery),
None,
&discovery.ca_instance,
);
let TreeRunAuditOutput {
tree,
publication_points,
@@ -159,14 +208,18 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
validation_time,
timing: Some(timing.clone()),
download_log: Some(download_log.clone()),
revalidate_only: config.revalidate_only,
rrdp_dedup: true,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: true,
rsync_repo_cache: Mutex::new(HashMap::new()),
};
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
let root = root_handle_from_trust_anchor(
&discovery.trust_anchor,
derive_tal_id(&discovery),
None,
&discovery.ca_instance,
);
let _tree = timing.span_phase("tree_run_total");
let TreeRunAuditOutput {
tree,
@@ -206,14 +259,18 @@ pub fn run_tree_from_tal_and_ta_der_serial(
validation_time,
timing: None,
download_log: None,
revalidate_only: config.revalidate_only,
rrdp_dedup: true,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: true,
rsync_repo_cache: Mutex::new(HashMap::new()),
};
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
let root = root_handle_from_trust_anchor(
&discovery.trust_anchor,
derive_tal_id(&discovery),
None,
&discovery.ca_instance,
);
let tree = run_tree_serial(root, &runner, config)?;
Ok(RunTreeFromTalOutput { discovery, tree })
@@ -242,14 +299,18 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
validation_time,
timing: None,
download_log: Some(download_log.clone()),
revalidate_only: config.revalidate_only,
rrdp_dedup: true,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: true,
rsync_repo_cache: Mutex::new(HashMap::new()),
};
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
let root = root_handle_from_trust_anchor(
&discovery.trust_anchor,
derive_tal_id(&discovery),
None,
&discovery.ca_instance,
);
let TreeRunAuditOutput {
tree,
publication_points,
@@ -292,14 +353,18 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
validation_time,
timing: Some(timing.clone()),
download_log: Some(download_log.clone()),
revalidate_only: config.revalidate_only,
rrdp_dedup: true,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: true,
rsync_repo_cache: Mutex::new(HashMap::new()),
};
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
let root = root_handle_from_trust_anchor(
&discovery.trust_anchor,
derive_tal_id(&discovery),
None,
&discovery.ca_instance,
);
let _tree = timing.span_phase("tree_run_total");
let TreeRunAuditOutput {
tree,

View File

@@ -1,10 +1,10 @@
use crate::audit::DiscoveredFrom;
use crate::audit::PublicationPointAudit;
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
use crate::report::{RfcRef, Warning};
use crate::storage::FetchCachePpPack;
use crate::report::Warning;
use crate::validation::manifest::PublicationPointSource;
use crate::validation::objects::{AspaAttestation, ObjectsOutput, Vrp};
use crate::validation::publication_point::PublicationPointSnapshot;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TreeRunConfig {
@@ -12,10 +12,6 @@ pub struct TreeRunConfig {
pub max_depth: Option<usize>,
/// Max number of CA instances to process.
pub max_instances: Option<usize>,
/// Skip RRDP/rsync fetch and re-validate from existing `fetch_cache_pp` packs in the DB.
///
/// This is primarily intended for profiling/analysis runs to remove network noise.
pub revalidate_only: bool,
}
impl Default for TreeRunConfig {
@@ -23,7 +19,6 @@ impl Default for TreeRunConfig {
Self {
max_depth: None,
max_instances: None,
revalidate_only: false,
}
}
}
@@ -31,6 +26,8 @@ impl Default for TreeRunConfig {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CaInstanceHandle {
pub depth: usize,
pub tal_id: String,
pub parent_manifest_rsync_uri: Option<String>,
/// DER bytes of the CA certificate for this CA instance.
pub ca_certificate_der: Vec<u8>,
/// rsync URI of this CA certificate object (where it is published).
@@ -59,15 +56,15 @@ impl CaInstanceHandle {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicationPointRunResult {
pub source: PublicationPointSource,
pub pack: FetchCachePpPack,
pub snapshot: Option<PublicationPointSnapshot>,
pub warnings: Vec<Warning>,
pub objects: ObjectsOutput,
pub audit: PublicationPointAudit,
/// Candidate child CA instances discovered from this publication point.
/// Candidate child CA instances to enqueue after this publication point completes.
///
/// RFC 9286 §6.6 restriction is enforced by the tree engine: if this
/// publication point used fetch_cache_pp due to failed fetch, children MUST NOT
/// be enqueued/processed in this run.
/// - For `Fresh`, these are discovered from the current validated publication point.
/// - For `VcirCurrentInstance`, these are restored from the current instance VCIR and then
/// processed fresh-first when their own turn arrives.
pub discovered_children: Vec<DiscoveredChildCaInstance>,
}
@@ -187,37 +184,25 @@ pub fn run_tree_serial_audit(
audit.discovered_from = node.discovered_from.clone();
publication_points.push(audit);
let enqueue_children =
res.source == PublicationPointSource::Fresh || config.revalidate_only;
if !enqueue_children && !res.discovered_children.is_empty() {
warnings.push(
Warning::new("skipping child CA discovery due to failed fetch cache use")
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
.with_context(&ca.manifest_rsync_uri),
);
}
if enqueue_children {
let mut children = res.discovered_children;
children.sort_by(|a, b| {
a.handle
.manifest_rsync_uri
.cmp(&b.handle.manifest_rsync_uri)
.then_with(|| {
a.discovered_from
.child_ca_certificate_rsync_uri
.cmp(&b.discovered_from.child_ca_certificate_rsync_uri)
})
let mut children = res.discovered_children;
children.sort_by(|a, b| {
a.handle
.manifest_rsync_uri
.cmp(&b.handle.manifest_rsync_uri)
.then_with(|| {
a.discovered_from
.child_ca_certificate_rsync_uri
.cmp(&b.discovered_from.child_ca_certificate_rsync_uri)
})
});
for child in children {
queue.push_back(QueuedCaInstance {
id: next_id,
handle: child.handle.with_depth(ca.depth + 1),
parent_id: Some(node.id),
discovered_from: Some(child.discovered_from),
});
for child in children {
queue.push_back(QueuedCaInstance {
id: next_id,
handle: child.handle.with_depth(ca.depth + 1),
parent_id: Some(node.id),
discovered_from: Some(child.discovered_from),
});
next_id += 1;
}
next_id += 1;
}
}

File diff suppressed because it is too large Load Diff