20260605 移除audit rule index和DB trace

This commit is contained in:
2026-06-05 16:02:25 +08:00
parent 9579f65501
commit e597c7c124
29 changed files with 4032 additions and 2298 deletions

View File

@@ -14,7 +14,10 @@ use crate::parallel::object_worker::{
};
use crate::policy::{Policy, SignedObjectFailurePolicy};
use crate::report::{RfcRef, Warning};
use crate::storage::{PackFile, PackTime, VcirLocalOutput, VcirOutputType};
use crate::storage::{
PackFile, PackTime, VcirLocalOutput, VcirLocalOutputPayload, VcirOutputType,
VcirSourceObjectType,
};
use crate::validation::cert_path::{CertPathError, validate_signed_object_ee_cert_path_fast};
use crate::validation::manifest::PublicationPointData;
use crate::validation::publication_point::PublicationPointSnapshot;
@@ -28,6 +31,13 @@ const RFC_CRLDP: &[RfcRef] = &[RfcRef("RFC 6487 §4.8.6")];
const RFC_CRLDP_AND_LOCKED_PACK: &[RfcRef] =
&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §4.2.1")];
fn sha256_hex_to_32(hex_value: &str) -> [u8; 32] {
let bytes = hex::decode(hex_value).expect("internal sha256 hex should decode");
let mut out = [0u8; 32];
out.copy_from_slice(&bytes);
out
}
fn decode_resource_certificate_with_policy(
der: &[u8],
policy: &Policy,
@@ -132,8 +142,6 @@ pub(crate) struct RoaTaskResult {
pub(crate) worker_index: usize,
pub(crate) queue_wait_ms: u64,
pub(crate) worker_ms: u64,
pub(crate) rsync_uri: String,
pub(crate) sha256_hex: String,
pub(crate) outcome: Result<RoaTaskOk, ObjectValidateError>,
}
@@ -418,8 +426,8 @@ pub fn process_publication_point_for_issuer_with_options<P: PublicationPointData
local_outputs_cache.extend(ok.local_outputs);
}
audit.push(ObjectAuditEntry {
rsync_uri: result.rsync_uri,
sha256_hex: result.sha256_hex,
rsync_uri: file.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&file.sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Ok,
detail: None,
@@ -428,8 +436,8 @@ pub fn process_publication_point_for_issuer_with_options<P: PublicationPointData
Err(e) => match policy.signed_object_failure_policy {
SignedObjectFailurePolicy::DropObject => {
audit.push(ObjectAuditEntry {
rsync_uri: result.rsync_uri.clone(),
sha256_hex: result.sha256_hex,
rsync_uri: file.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&file.sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Error,
detail: Some(e.to_string()),
@@ -437,24 +445,21 @@ pub fn process_publication_point_for_issuer_with_options<P: PublicationPointData
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")];
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
warnings.push(
Warning::new(format!(
"dropping invalid ROA: {}: {e}",
result.rsync_uri
))
.with_rfc_refs(&refs)
.with_context(&result.rsync_uri),
Warning::new(format!("dropping invalid ROA: {}: {e}", file.rsync_uri))
.with_rfc_refs(&refs)
.with_context(&file.rsync_uri),
)
}
SignedObjectFailurePolicy::DropPublicationPoint => {
stats.publication_point_dropped = true;
audit.push(ObjectAuditEntry {
rsync_uri: result.rsync_uri.clone(),
sha256_hex: result.sha256_hex,
rsync_uri: file.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&file.sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Error,
detail: Some(e.to_string()),
});
for f in locked_files.iter().skip(result.index + 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(),
@@ -484,7 +489,7 @@ pub fn process_publication_point_for_issuer_with_options<P: PublicationPointData
warnings.push(
Warning::new(format!(
"dropping publication point due to invalid ROA: {}: {e}",
result.rsync_uri
file.rsync_uri
))
.with_rfc_refs(&refs)
.with_context(manifest_rsync_uri),
@@ -786,19 +791,24 @@ pub fn process_publication_point_for_issuer_parallel_roa_with_pool_options<
}
#[derive(Clone)]
pub(crate) struct OwnedRoaTask {
pub(crate) publication_point_id: u64,
index: usize,
file: PackFile,
manifest_rsync_uri: String,
pub(crate) struct RoaTaskShared {
locked_files: Arc<[PackFile]>,
manifest_rsync_uri: Arc<str>,
issuer_ca_der: Arc<[u8]>,
issuer_ca: Arc<ResourceCertificate>,
issuer_spki_der: Arc<[u8]>,
issuer_ca_rsync_uri: Option<String>,
issuer_ca_rsync_uri: Option<Arc<str>>,
crl_cache: Arc<Mutex<std::collections::HashMap<String, CachedIssuerCrl>>>,
issuer_resources_index: Arc<IssuerResourcesIndex>,
issuer_effective_ip: Option<crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<crate::data_model::rc::AsResourceSet>,
issuer_effective_ip: Option<Arc<crate::data_model::rc::IpResourceSet>>,
issuer_effective_as: Option<Arc<crate::data_model::rc::AsResourceSet>>,
}
#[derive(Clone)]
pub(crate) struct OwnedRoaTask {
pub(crate) publication_point_id: u64,
index: usize,
shared: Arc<RoaTaskShared>,
validation_time: time::OffsetDateTime,
collect_vcir_local_outputs: bool,
strict_cms_der: bool,
@@ -862,8 +872,13 @@ fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskRe
.map(|submitted_at| worker_started.saturating_duration_since(submitted_at))
.map(|duration| duration.as_millis() as u64)
.unwrap_or(0);
let sha256_hex = sha256_hex_from_32(&task.file.sha256);
let issuer_spki = match SubjectPublicKeyInfo::from_der(task.issuer_spki_der.as_ref()) {
let shared = task.shared.as_ref();
let file = task
.shared
.locked_files
.get(task.index)
.expect("ROA task index must reference locked file");
let issuer_spki = match SubjectPublicKeyInfo::from_der(shared.issuer_spki_der.as_ref()) {
Ok((rem, spki)) if rem.is_empty() => spki,
Ok((rem, _)) => {
return RoaTaskResult {
@@ -872,8 +887,6 @@ fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskRe
worker_index,
queue_wait_ms,
worker_ms: worker_started.elapsed().as_millis() as u64,
rsync_uri: task.file.rsync_uri,
sha256_hex,
outcome: Err(ObjectValidateError::CertPath(
CertPathError::IssuerSpkiTrailingBytes(rem.len()),
)),
@@ -886,8 +899,6 @@ fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskRe
worker_index,
queue_wait_ms,
worker_ms: worker_started.elapsed().as_millis() as u64,
rsync_uri: task.file.rsync_uri,
sha256_hex,
outcome: Err(ObjectValidateError::CertPath(
CertPathError::IssuerSpkiParse(e.to_string()),
)),
@@ -895,16 +906,16 @@ fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskRe
}
};
let outcome = process_roa_with_issuer_parallel_cached(
&task.file,
task.manifest_rsync_uri.as_str(),
task.issuer_ca_der.as_ref(),
task.issuer_ca.as_ref(),
file,
shared.manifest_rsync_uri.as_ref(),
shared.issuer_ca_der.as_ref(),
shared.issuer_ca.as_ref(),
&issuer_spki,
task.issuer_ca_rsync_uri.as_deref(),
task.crl_cache.as_ref(),
task.issuer_resources_index.as_ref(),
task.issuer_effective_ip.as_ref(),
task.issuer_effective_as.as_ref(),
shared.issuer_ca_rsync_uri.as_deref(),
shared.crl_cache.as_ref(),
shared.issuer_resources_index.as_ref(),
shared.issuer_effective_ip.as_deref(),
shared.issuer_effective_as.as_deref(),
task.validation_time,
None,
task.collect_vcir_local_outputs,
@@ -922,8 +933,6 @@ fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskRe
worker_index,
queue_wait_ms,
worker_ms: worker_started.elapsed().as_millis() as u64,
rsync_uri: task.file.rsync_uri,
sha256_hex,
outcome,
}
}
@@ -935,16 +944,7 @@ pub(crate) enum ParallelObjectsPrepare {
pub(crate) struct ParallelObjectsStage {
pub(crate) publication_point_id: u64,
pub(crate) locked_files: Vec<PackFile>,
pub(crate) manifest_rsync_uri: String,
issuer_ca_der: Arc<[u8]>,
issuer_ca: Arc<ResourceCertificate>,
issuer_spki_der: Arc<[u8]>,
issuer_ca_rsync_uri: Option<String>,
crl_cache: Arc<Mutex<std::collections::HashMap<String, CachedIssuerCrl>>>,
issuer_resources_index: Arc<IssuerResourcesIndex>,
issuer_effective_ip: Option<crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<crate::data_model::rc::AsResourceSet>,
shared: Arc<RoaTaskShared>,
validation_time: time::OffsetDateTime,
collect_vcir_local_outputs: bool,
strict_cms_der: bool,
@@ -955,31 +955,41 @@ pub(crate) struct ParallelObjectsStage {
}
impl ParallelObjectsStage {
#[cfg(test)]
pub(crate) fn build_roa_tasks(&self) -> Vec<OwnedRoaTask> {
self.locked_files
let mut tasks = Vec::with_capacity(self.roa_task_count());
self.extend_roa_tasks(|task| tasks.push(task));
tasks
}
pub(crate) fn append_roa_tasks_to(
&self,
pending: &mut std::collections::VecDeque<OwnedRoaTask>,
) {
self.extend_roa_tasks(|task| pending.push_back(task));
}
fn extend_roa_tasks<F>(&self, mut push: F)
where
F: FnMut(OwnedRoaTask),
{
let shared = self.shared.clone();
self.locked_files()
.iter()
.enumerate()
.filter(|(_, file)| file.rsync_uri.ends_with(".roa"))
.map(|(index, file)| OwnedRoaTask {
publication_point_id: self.publication_point_id,
index,
file: file.clone(),
manifest_rsync_uri: self.manifest_rsync_uri.clone(),
issuer_ca_der: self.issuer_ca_der.clone(),
issuer_ca: self.issuer_ca.clone(),
issuer_spki_der: self.issuer_spki_der.clone(),
issuer_ca_rsync_uri: self.issuer_ca_rsync_uri.clone(),
crl_cache: self.crl_cache.clone(),
issuer_resources_index: self.issuer_resources_index.clone(),
issuer_effective_ip: self.issuer_effective_ip.clone(),
issuer_effective_as: self.issuer_effective_as.clone(),
validation_time: self.validation_time,
collect_vcir_local_outputs: self.collect_vcir_local_outputs,
strict_cms_der: self.strict_cms_der,
strict_name: self.strict_name,
submitted_at: None,
})
.collect()
.for_each(|(index, _)| {
push(OwnedRoaTask {
publication_point_id: self.publication_point_id,
index,
shared: shared.clone(),
validation_time: self.validation_time,
collect_vcir_local_outputs: self.collect_vcir_local_outputs,
strict_cms_der: self.strict_cms_der,
strict_name: self.strict_name,
submitted_at: None,
});
});
}
pub(crate) fn roa_task_count(&self) -> usize {
@@ -991,7 +1001,11 @@ impl ParallelObjectsStage {
}
pub(crate) fn locked_file_count(&self) -> usize {
self.locked_files.len()
self.shared.locked_files.len()
}
fn locked_files(&self) -> &[PackFile] {
self.shared.locked_files.as_ref()
}
}
@@ -1212,19 +1226,21 @@ pub(crate) fn prepare_publication_point_for_parallel_roa<P: PublicationPointData
ParallelObjectsPrepare::Staged(ParallelObjectsStage {
publication_point_id,
locked_files: locked_files.to_vec(),
manifest_rsync_uri: manifest_rsync_uri.to_string(),
issuer_ca_der: Arc::<[u8]>::from(issuer_ca_der.to_vec()),
issuer_spki_der: Arc::<[u8]>::from(issuer_ca.tbs.subject_public_key_info.clone()),
issuer_ca: Arc::new(issuer_ca),
issuer_ca_rsync_uri: issuer_ca_rsync_uri.map(ToString::to_string),
crl_cache: Arc::new(Mutex::new(crl_cache)),
issuer_resources_index: Arc::new(build_issuer_resources_index(
issuer_effective_ip,
issuer_effective_as,
)),
issuer_effective_ip: issuer_effective_ip.cloned(),
issuer_effective_as: issuer_effective_as.cloned(),
shared: Arc::new(RoaTaskShared {
locked_files: Arc::<[PackFile]>::from(locked_files.to_vec()),
manifest_rsync_uri: Arc::<str>::from(manifest_rsync_uri),
issuer_ca_der: Arc::<[u8]>::from(issuer_ca_der.to_vec()),
issuer_spki_der: Arc::<[u8]>::from(issuer_ca.tbs.subject_public_key_info.clone()),
issuer_ca: Arc::new(issuer_ca),
issuer_ca_rsync_uri: issuer_ca_rsync_uri.map(Arc::<str>::from),
crl_cache: Arc::new(Mutex::new(crl_cache)),
issuer_resources_index: Arc::new(build_issuer_resources_index(
issuer_effective_ip,
issuer_effective_as,
)),
issuer_effective_ip: issuer_effective_ip.cloned().map(Arc::new),
issuer_effective_as: issuer_effective_as.cloned().map(Arc::new),
}),
validation_time,
collect_vcir_local_outputs,
strict_cms_der: policy.strict.cms_der,
@@ -1241,18 +1257,20 @@ pub(crate) fn reduce_parallel_roa_stage(
timing: Option<&TimingHandle>,
) -> Result<ObjectsOutput, String> {
roa_results.sort_by_key(|result| result.index);
let mut roa_by_index = roa_results
.into_iter()
.map(|result| (result.index, result))
.collect::<std::collections::HashMap<_, _>>();
let mut aspa_crl_cache = stage
let mut roa_results = roa_results.into_iter().peekable();
let shared = stage.shared.clone();
let mut aspa_crl_cache = shared
.crl_cache
.lock()
.expect("parallel ROA CRL cache lock")
.clone();
let issuer_spki = SubjectPublicKeyInfo::from_der(stage.issuer_spki_der.as_ref())
let issuer_spki = SubjectPublicKeyInfo::from_der(shared.issuer_spki_der.as_ref())
.map_err(|e| e.to_string())?
.1;
let collect_vcir_local_outputs = stage.collect_vcir_local_outputs;
let validation_time = stage.validation_time;
let strict_cms_der = stage.strict_cms_der;
let strict_name = stage.strict_name;
let mut stats = stage.stats;
let mut warnings = stage.warnings;
let mut audit = stage.audit;
@@ -1260,21 +1278,32 @@ pub(crate) fn reduce_parallel_roa_stage(
let mut aspas: Vec<AspaAttestation> = Vec::new();
let mut local_outputs_cache: Vec<VcirLocalOutput> = Vec::new();
for (idx, file) in stage.locked_files.iter().enumerate() {
for (idx, file) in shared.locked_files.iter().enumerate() {
if file.rsync_uri.ends_with(".roa") {
let result = roa_by_index
.remove(&idx)
.ok_or_else(|| format!("missing ROA task result for {}", file.rsync_uri))?;
let result = match roa_results.peek() {
Some(result) if result.index == idx => roa_results
.next()
.expect("peeked ROA task result must be present"),
Some(result) => {
return Err(format!(
"unexpected ROA task result index {} while reducing {} at index {}",
result.index, file.rsync_uri, idx
));
}
None => {
return Err(format!("missing ROA task result for {}", file.rsync_uri));
}
};
match result.outcome {
Ok(mut ok) => {
stats.roa_ok += 1;
vrps.append(&mut ok.vrps);
if stage.collect_vcir_local_outputs {
if collect_vcir_local_outputs {
local_outputs_cache.extend(ok.local_outputs);
}
audit.push(ObjectAuditEntry {
rsync_uri: result.rsync_uri,
sha256_hex: result.sha256_hex,
rsync_uri: file.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&file.sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Ok,
detail: None,
@@ -1282,8 +1311,8 @@ pub(crate) fn reduce_parallel_roa_stage(
}
Err(e) => {
audit.push(ObjectAuditEntry {
rsync_uri: result.rsync_uri.clone(),
sha256_hex: result.sha256_hex,
rsync_uri: file.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&file.sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Error,
detail: Some(e.to_string()),
@@ -1291,9 +1320,9 @@ pub(crate) fn reduce_parallel_roa_stage(
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")];
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
warnings.push(
Warning::new(format!("dropping invalid ROA: {}: {e}", result.rsync_uri))
Warning::new(format!("dropping invalid ROA: {}: {e}", file.rsync_uri))
.with_rfc_refs(&refs)
.with_context(&result.rsync_uri),
.with_context(&file.rsync_uri),
)
}
}
@@ -1301,20 +1330,20 @@ pub(crate) fn reduce_parallel_roa_stage(
let _t = timing.as_ref().map(|t| t.span_phase("objects_aspa_total"));
match process_aspa_with_issuer(
file,
&stage.manifest_rsync_uri,
stage.issuer_ca_der.as_ref(),
stage.issuer_ca.as_ref(),
shared.manifest_rsync_uri.as_ref(),
shared.issuer_ca_der.as_ref(),
shared.issuer_ca.as_ref(),
&issuer_spki,
stage.issuer_ca_rsync_uri.as_deref(),
shared.issuer_ca_rsync_uri.as_deref(),
&mut aspa_crl_cache,
stage.issuer_resources_index.as_ref(),
stage.issuer_effective_ip.as_ref(),
stage.issuer_effective_as.as_ref(),
stage.validation_time,
shared.issuer_resources_index.as_ref(),
shared.issuer_effective_ip.as_deref(),
shared.issuer_effective_as.as_deref(),
validation_time,
timing,
stage.collect_vcir_local_outputs,
stage.strict_cms_der,
stage.strict_name,
collect_vcir_local_outputs,
strict_cms_der,
strict_name,
) {
Ok((att, local_output)) => {
stats.aspa_ok += 1;
@@ -1349,6 +1378,12 @@ pub(crate) fn reduce_parallel_roa_stage(
}
}
}
if let Some(result) = roa_results.next() {
return Err(format!(
"unexpected trailing ROA task result at index {}",
result.index
));
}
Ok(ObjectsOutput {
vrps,
@@ -1388,8 +1423,9 @@ fn process_publication_point_for_issuer_parallel_roa_inner<P: PublicationPointDa
ParallelObjectsPrepare::Staged(stage) => stage,
};
let mut pending = std::collections::VecDeque::from(stage.build_roa_tasks());
let roa_task_count = stage.roa_task_count();
let mut pending = std::collections::VecDeque::with_capacity(roa_task_count);
stage.append_roa_tasks_to(&mut pending);
let mut worker_pool = pool
.pool
.lock()
@@ -1537,7 +1573,6 @@ pub(crate) fn validate_roa_task_serial(
strict_cms_der: bool,
strict_name: bool,
) -> RoaTaskResult {
let sha256_hex = sha256_hex_from_32(&task.file.sha256);
let outcome = process_roa_with_issuer(
task.file,
manifest_rsync_uri,
@@ -1566,15 +1601,13 @@ pub(crate) fn validate_roa_task_serial(
worker_index: 0,
queue_wait_ms: 0,
worker_ms: 0,
rsync_uri: task.file.rsync_uri.clone(),
sha256_hex,
outcome,
}
}
fn process_roa_with_issuer(
file: &PackFile,
manifest_rsync_uri: &str,
_manifest_rsync_uri: &str,
issuer_ca_der: &[u8],
issuer_ca: &ResourceCertificate,
issuer_spki: &SubjectPublicKeyInfo<'_>,
@@ -1659,12 +1692,6 @@ fn process_roa_with_issuer(
.iter()
.map(|vrp| {
let prefix = vrp_prefix_to_string(vrp);
let payload_json = serde_json::json!({
"asn": vrp.asn,
"prefix": prefix,
"max_length": vrp.max_length,
})
.to_string();
let rule_hash = crate::audit::sha256_hex(
format!(
"roa-rule:{}:{}:{}:{}",
@@ -1673,20 +1700,20 @@ fn process_roa_with_issuer(
.as_bytes(),
);
VcirLocalOutput {
output_id: rule_hash.clone(),
output_type: VcirOutputType::Vrp,
item_effective_until: item_effective_until.clone(),
source_object_uri: file.rsync_uri.clone(),
source_object_type: "roa".to_string(),
source_object_hash: source_object_hash.clone(),
source_ee_cert_hash: source_ee_cert_hash.clone(),
payload_json,
rule_hash,
validation_path_hint: vec![
manifest_rsync_uri.to_string(),
file.rsync_uri.clone(),
source_object_hash.clone(),
],
source_object_type: VcirSourceObjectType::Roa,
source_object_hash: file.sha256,
source_ee_cert_hash: sha256_hex_to_32(&source_ee_cert_hash),
payload: VcirLocalOutputPayload::Vrp {
asn: vrp.asn,
afi: vrp.prefix.afi,
prefix_len: vrp.prefix.prefix_len,
addr: vrp.prefix.addr,
max_length: vrp.max_length,
},
rule_hash: sha256_hex_to_32(&rule_hash),
}
})
.collect();
@@ -1696,7 +1723,7 @@ fn process_roa_with_issuer(
fn process_roa_with_issuer_parallel_cached(
file: &PackFile,
manifest_rsync_uri: &str,
_manifest_rsync_uri: &str,
issuer_ca_der: &[u8],
issuer_ca: &ResourceCertificate,
issuer_spki: &SubjectPublicKeyInfo<'_>,
@@ -1787,12 +1814,6 @@ fn process_roa_with_issuer_parallel_cached(
.iter()
.map(|vrp| {
let prefix = vrp_prefix_to_string(vrp);
let payload_json = serde_json::json!({
"asn": vrp.asn,
"prefix": prefix,
"max_length": vrp.max_length,
})
.to_string();
let rule_hash = crate::audit::sha256_hex(
format!(
"roa-rule:{}:{}:{}:{}",
@@ -1801,20 +1822,20 @@ fn process_roa_with_issuer_parallel_cached(
.as_bytes(),
);
VcirLocalOutput {
output_id: rule_hash.clone(),
output_type: VcirOutputType::Vrp,
item_effective_until: item_effective_until.clone(),
source_object_uri: file.rsync_uri.clone(),
source_object_type: "roa".to_string(),
source_object_hash: source_object_hash.clone(),
source_ee_cert_hash: source_ee_cert_hash.clone(),
payload_json,
rule_hash,
validation_path_hint: vec![
manifest_rsync_uri.to_string(),
file.rsync_uri.clone(),
source_object_hash.clone(),
],
source_object_type: VcirSourceObjectType::Roa,
source_object_hash: file.sha256,
source_ee_cert_hash: sha256_hex_to_32(&source_ee_cert_hash),
payload: VcirLocalOutputPayload::Vrp {
asn: vrp.asn,
afi: vrp.prefix.afi,
prefix_len: vrp.prefix.prefix_len,
addr: vrp.prefix.addr,
max_length: vrp.max_length,
},
rule_hash: sha256_hex_to_32(&rule_hash),
}
})
.collect();
@@ -1824,7 +1845,7 @@ fn process_roa_with_issuer_parallel_cached(
fn process_aspa_with_issuer(
file: &PackFile,
manifest_rsync_uri: &str,
_manifest_rsync_uri: &str,
issuer_ca_der: &[u8],
issuer_ca: &ResourceCertificate,
issuer_spki: &SubjectPublicKeyInfo<'_>,
@@ -1922,24 +1943,17 @@ fn process_aspa_with_issuer(
.as_bytes(),
);
let local_output = VcirLocalOutput {
output_id: rule_hash.clone(),
output_type: VcirOutputType::Aspa,
item_effective_until,
source_object_uri: file.rsync_uri.clone(),
source_object_type: "aspa".to_string(),
source_object_hash: source_object_hash.clone(),
source_ee_cert_hash,
payload_json: serde_json::json!({
"customer_as_id": attestation.customer_as_id,
"provider_as_ids": attestation.provider_as_ids,
})
.to_string(),
rule_hash,
validation_path_hint: vec![
manifest_rsync_uri.to_string(),
file.rsync_uri.clone(),
source_object_hash,
],
source_object_type: VcirSourceObjectType::Aspa,
source_object_hash: file.sha256,
source_ee_cert_hash: sha256_hex_to_32(&source_ee_cert_hash),
payload: VcirLocalOutputPayload::Aspa {
customer_as_id: attestation.customer_as_id,
provider_as_ids: attestation.provider_as_ids.clone(),
},
rule_hash: sha256_hex_to_32(&rule_hash),
};
Ok((attestation, Some(local_output)))
@@ -2965,6 +2979,54 @@ mod tests {
assert_eq!(roa_afi_to_string(RoaAfi::Ipv6), "ipv6");
}
#[test]
fn parallel_stage_roa_tasks_share_stage_owned_payloads() {
let stage = ParallelObjectsStage {
publication_point_id: 7,
shared: Arc::new(RoaTaskShared {
locked_files: Arc::<[PackFile]>::from(vec![
PackFile::from_bytes_with_sha256(
"rsync://example.test/repo/a.roa",
vec![1, 2, 3],
[1u8; 32],
),
PackFile::from_bytes_with_sha256(
"rsync://example.test/repo/b.roa",
vec![4, 5, 6],
[2u8; 32],
),
]),
manifest_rsync_uri: Arc::<str>::from("rsync://example.test/repo/manifest.mft"),
issuer_ca_der: Arc::from([0x01u8].as_slice()),
issuer_ca: Arc::new(
ResourceCertificate::decode_der(&fixture_bytes(
"tests/fixtures/ta/apnic-ta.cer",
))
.expect("decode fixture CA certificate"),
),
issuer_spki_der: Arc::from([0x02u8].as_slice()),
issuer_ca_rsync_uri: Some(Arc::<str>::from("rsync://example.test/repo/ca.cer")),
crl_cache: Arc::new(Mutex::new(HashMap::new())),
issuer_resources_index: Arc::new(IssuerResourcesIndex::default()),
issuer_effective_ip: None,
issuer_effective_as: None,
}),
validation_time: OffsetDateTime::now_utc(),
collect_vcir_local_outputs: false,
strict_cms_der: false,
strict_name: false,
warnings: Vec::new(),
stats: ObjectsStats {
roa_total: 2,
..ObjectsStats::default()
},
audit: Vec::new(),
};
let tasks = stage.build_roa_tasks();
assert_eq!(tasks.len(), 2);
assert!(Arc::ptr_eq(&tasks[0].shared, &tasks[1].shared));
}
#[test]
fn strict_name_manifest_decode_failure_drops_publication_point() {
let publication_point = PublicationPointSnapshot {

View File

@@ -55,8 +55,37 @@ struct InflightPublicationPoint {
}
struct FinishedPublicationPoint {
node: QueuedCaInstance,
result: Result<PublicationPointRunResult, String>,
node: FinishedPublicationPointNode,
result: FinishedPublicationPointResult,
}
#[derive(Clone, Debug)]
struct FinishedPublicationPointNode {
id: u64,
parent_id: Option<u64>,
discovered_from: Option<DiscoveredFrom>,
manifest_rsync_uri: String,
}
impl FinishedPublicationPointNode {
fn from_queued(node: QueuedCaInstance) -> Self {
Self {
id: node.id,
parent_id: node.parent_id,
discovered_from: node.discovered_from,
manifest_rsync_uri: node.handle.manifest_rsync_uri,
}
}
}
#[derive(Debug)]
enum FinishedPublicationPointResult {
Ok {
warnings: Vec<Warning>,
objects: ObjectsOutput,
audit: PublicationPointAudit,
},
Err(String),
}
struct FinalizeTask {
@@ -310,24 +339,28 @@ fn elapsed_ms(started: Instant) -> u64 {
fn compact_phase2_finished_result(
mut result: PublicationPointRunResult,
compact_audit: bool,
) -> PublicationPointRunResult {
// Phase2 only needs warnings, objects, audit, and traversal metadata after finalize.
// Dropping the snapshot here avoids retaining manifest/files/raw-byte caches until run end.
result.snapshot = None;
) -> FinishedPublicationPointResult {
result.objects.audit.clear();
result.objects.local_outputs_cache.clear();
if compact_audit {
result.audit.objects.clear();
result.audit.warnings.clear();
result.objects.audit.clear();
result.discovered_children.clear();
}
result
FinishedPublicationPointResult::Ok {
warnings: result.warnings,
objects: result.objects,
audit: result.audit,
}
}
fn compact_phase2_finished_result_result(
result: Result<PublicationPointRunResult, String>,
compact_audit: bool,
) -> Result<PublicationPointRunResult, String> {
result.map(|result| compact_phase2_finished_result(result, compact_audit))
) -> FinishedPublicationPointResult {
match result {
Ok(result) => compact_phase2_finished_result(result, compact_audit),
Err(err) => FinishedPublicationPointResult::Err(err),
}
}
pub fn run_tree_parallel_phase2_audit_multi_root(
@@ -712,8 +745,8 @@ fn start_queued_ca_instances(
}
Err(err) => {
finished.push(FinishedPublicationPoint {
node,
result: Err(err),
node: FinishedPublicationPointNode::from_queued(node),
result: FinishedPublicationPointResult::Err(err),
});
}
}
@@ -781,7 +814,7 @@ fn stage_ready_publication_point(
metrics.child_enqueue_ms = elapsed_ms(child_enqueue_started);
}
finished.push(FinishedPublicationPoint {
node: ready.node,
node: FinishedPublicationPointNode::from_queued(ready.node),
result: compact_phase2_finished_result_result(fallback, compact_audit),
});
metrics.total_ms = elapsed_ms(publication_point_started);
@@ -897,13 +930,10 @@ fn stage_ready_publication_point(
metrics.locked_files = objects_stage.locked_file_count();
metrics.aspa_objects = objects_stage.aspa_task_count();
let build_tasks_started = Instant::now();
let tasks = objects_stage.build_roa_tasks();
objects_stage.append_roa_tasks_to(pending_roa_dispatch);
metrics.build_roa_tasks_ms = elapsed_ms(build_tasks_started);
let task_count = objects_stage.roa_task_count();
metrics.roa_tasks = task_count;
for task in tasks {
pending_roa_dispatch.push_back(task);
}
if task_count == 0 {
metrics.zero_task_count = 1;
match reduce_parallel_roa_stage(objects_stage, Vec::new(), runner.timing.as_ref()) {
@@ -929,8 +959,8 @@ fn stage_ready_publication_point(
);
}
Err(err) => finished.push(FinishedPublicationPoint {
node: ready.node,
result: Err(err),
node: FinishedPublicationPointNode::from_queued(ready.node),
result: FinishedPublicationPointResult::Err(err),
}),
}
} else {
@@ -1022,7 +1052,7 @@ fn finalize_ready_objects(
)
.map(|out| out.result);
finished.push(FinishedPublicationPoint {
node,
node: FinishedPublicationPointNode::from_queued(node),
result: compact_phase2_finished_result_result(result, compact_audit),
});
}
@@ -1400,7 +1430,7 @@ fn finalize_publication_point_state(
elapsed_ms(finalize_started),
)
}
Err(err) => (Err(err), 0),
Err(err) => (FinishedPublicationPointResult::Err(err), 0),
};
let finalize_worker_ms = elapsed_ms(finalize_worker_started);
crate::progress_log::emit(
@@ -1438,7 +1468,10 @@ fn finalize_publication_point_state(
}),
);
FinalizeWorkerResult {
finished: FinishedPublicationPoint { node, result },
finished: FinishedPublicationPoint {
node: FinishedPublicationPointNode::from_queued(node),
result,
},
metrics: FinalizePublicationPointMetrics {
reduce_ms,
finalize_ms,
@@ -1541,25 +1574,29 @@ fn build_tree_output(mut finished: Vec<FinishedPublicationPoint>) -> TreeRunAudi
for item in finished {
match item.result {
Ok(result) => {
FinishedPublicationPointResult::Ok {
warnings: result_warnings,
objects,
audit,
} => {
instances_processed += 1;
warnings.extend(result.warnings);
warnings.extend(result.objects.warnings);
vrps.extend(result.objects.vrps);
aspas.extend(result.objects.aspas);
router_keys.extend(result.objects.router_keys);
warnings.extend(result_warnings);
warnings.extend(objects.warnings);
vrps.extend(objects.vrps);
aspas.extend(objects.aspas);
router_keys.extend(objects.router_keys);
let mut audit: PublicationPointAudit = result.audit;
let mut audit: PublicationPointAudit = audit;
audit.node_id = Some(item.node.id);
audit.parent_node_id = item.node.parent_id;
audit.discovered_from = item.node.discovered_from;
publication_points.push(audit);
}
Err(err) => {
FinishedPublicationPointResult::Err(err) => {
instances_failed += 1;
warnings.push(
Warning::new(format!("publication point failed: {err}"))
.with_context(&item.node.handle.manifest_rsync_uri),
.with_context(&item.node.manifest_rsync_uri),
);
}
}
@@ -1588,7 +1625,10 @@ pub fn run_tree_parallel_phase2_audit(
#[cfg(test)]
mod tests {
use super::{compact_phase2_finished_result, compact_phase2_finished_result_result};
use super::{
FinishedPublicationPointResult, compact_phase2_finished_result,
compact_phase2_finished_result_result,
};
use crate::audit::PublicationPointAudit;
use crate::storage::PackTime;
use crate::validation::manifest::PublicationPointSource;
@@ -1638,9 +1678,12 @@ mod tests {
#[test]
fn compact_phase2_finished_result_drops_snapshot() {
let result = compact_phase2_finished_result(sample_result(), false);
assert!(result.snapshot.is_none());
assert_eq!(result.source, PublicationPointSource::Fresh);
assert!(result.discovered_children.is_empty());
match result {
FinishedPublicationPointResult::Ok { warnings, .. } => {
assert!(warnings.is_empty());
}
FinishedPublicationPointResult::Err(err) => panic!("unexpected error: {err}"),
}
}
#[test]
@@ -1666,15 +1709,21 @@ mod tests {
detail: None,
});
let result = compact_phase2_finished_result(sample, true);
assert!(result.audit.objects.is_empty());
assert!(result.audit.warnings.is_empty());
assert!(result.objects.audit.is_empty());
match result {
FinishedPublicationPointResult::Ok { objects, audit, .. } => {
assert!(audit.objects.is_empty());
assert!(audit.warnings.is_empty());
assert!(objects.audit.is_empty());
}
FinishedPublicationPointResult::Err(err) => panic!("unexpected error: {err}"),
}
}
#[test]
fn compact_phase2_finished_result_result_preserves_err() {
let err = compact_phase2_finished_result_result(Err("boom".to_string()), false)
.expect_err("error should be preserved");
assert_eq!(err, "boom");
match compact_phase2_finished_result_result(Err("boom".to_string()), false) {
FinishedPublicationPointResult::Err(err) => assert_eq!(err, "boom"),
FinishedPublicationPointResult::Ok { .. } => panic!("error should be preserved"),
}
}
}

View File

@@ -27,8 +27,8 @@ use crate::report::{RfcRef, Warning};
use crate::storage::{
PackFile, PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult, VcirArtifactKind,
VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary, VcirCcrManifestProjection,
VcirChildEntry, VcirInstanceGate, VcirLocalOutput, VcirOutputType, VcirRelatedArtifact,
VcirSummary,
VcirChildEntry, VcirInstanceGate, VcirLocalOutput, VcirLocalOutputPayload, VcirOutputType,
VcirRelatedArtifact, VcirReplaceTimingBreakdown, VcirSourceObjectType, VcirSummary,
};
use crate::sync::repo::{
sync_publication_point, sync_publication_point_replay, sync_publication_point_replay_delta,
@@ -58,13 +58,17 @@ use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use base64::Engine as _;
use serde::Deserialize;
use serde_json::json;
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
use vcir_der::encode_access_description_der_for_vcir_ccr_projection;
fn sha256_hex_to_32(hex_value: &str) -> [u8; 32] {
let mut out = [0u8; 32];
hex::decode_to_slice(hex_value, &mut out).expect("internal sha256 hex should decode");
out
}
#[derive(Clone, Debug, Default)]
pub(crate) struct BuildVcirTimingBreakdown {
pub(crate) select_crl_ms: u64,
@@ -80,9 +84,9 @@ pub(crate) struct PersistVcirTimingBreakdown {
pub(crate) embedded_collect_ms: u64,
pub(crate) embedded_store_ms: u64,
pub(crate) build_vcir_ms: u64,
pub(crate) previous_load_ms: u64,
pub(crate) replace_vcir_ms: u64,
pub(crate) build_vcir: BuildVcirTimingBreakdown,
pub(crate) replace_vcir: VcirReplaceTimingBreakdown,
}
#[derive(Clone, Debug)]
@@ -311,7 +315,7 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
self.store,
ca,
&pack,
&objects,
&mut objects,
&warnings,
&child_audits,
&discovered_children,
@@ -666,7 +670,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
self.validation_time,
self.timing.as_ref(),
phase2_pool,
self.persist_vcir,
false,
)
} else if let Some(phase2_config) = self.parallel_phase2_config.as_ref() {
process_publication_point_for_issuer_parallel_roa_with_options(
@@ -679,7 +683,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
self.validation_time,
self.timing.as_ref(),
phase2_config,
self.persist_vcir,
false,
)
} else {
crate::validation::objects::process_publication_point_for_issuer_with_options(
@@ -691,7 +695,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
ca.effective_as_resources.as_ref(),
self.validation_time,
self.timing.as_ref(),
self.persist_vcir,
false,
)
}
};
@@ -747,7 +751,6 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
"persist_embedded_collect_ms": persist_vcir_timing.embedded_collect_ms,
"persist_embedded_store_ms": persist_vcir_timing.embedded_store_ms,
"persist_build_vcir_ms": persist_vcir_timing.build_vcir_ms,
"persist_previous_load_ms": persist_vcir_timing.previous_load_ms,
"persist_replace_vcir_ms": persist_vcir_timing.replace_vcir_ms,
"persist_select_crl_ms": persist_vcir_timing.build_vcir.select_crl_ms,
"persist_current_ca_decode_ms": persist_vcir_timing.build_vcir.current_ca_decode_ms,
@@ -755,6 +758,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
"persist_child_entries_ms": persist_vcir_timing.build_vcir.child_entries_ms,
"persist_related_artifacts_ms": persist_vcir_timing.build_vcir.related_artifacts_ms,
"persist_vcir_struct_ms": persist_vcir_timing.build_vcir.struct_build_ms,
"persist_replace_breakdown": &persist_vcir_timing.replace_vcir,
"audit_build_ms": audit_build_ms,
"warning_count": result.warnings.len(),
"vrp_count": result.objects.vrps.len(),
@@ -790,7 +794,6 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
"persist_embedded_collect_ms": persist_vcir_timing.embedded_collect_ms,
"persist_embedded_store_ms": persist_vcir_timing.embedded_store_ms,
"persist_build_vcir_ms": persist_vcir_timing.build_vcir_ms,
"persist_previous_load_ms": persist_vcir_timing.previous_load_ms,
"persist_replace_vcir_ms": persist_vcir_timing.replace_vcir_ms,
"persist_select_crl_ms": persist_vcir_timing.build_vcir.select_crl_ms,
"persist_current_ca_decode_ms": persist_vcir_timing.build_vcir.current_ca_decode_ms,
@@ -798,6 +801,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
"persist_child_entries_ms": persist_vcir_timing.build_vcir.child_entries_ms,
"persist_related_artifacts_ms": persist_vcir_timing.build_vcir.related_artifacts_ms,
"persist_vcir_struct_ms": persist_vcir_timing.build_vcir.struct_build_ms,
"persist_replace_breakdown": &persist_vcir_timing.replace_vcir,
"audit_build_ms": audit_build_ms,
}),
);
@@ -1012,26 +1016,6 @@ struct VcirReuseProjection {
warnings: Vec<Warning>,
}
#[derive(Debug, Deserialize)]
struct VcirVrpPayload {
asn: u32,
prefix: String,
max_length: u16,
}
#[derive(Debug, Deserialize)]
struct VcirAspaPayload {
customer_as_id: u32,
provider_as_ids: Vec<u32>,
}
#[derive(Debug, Deserialize)]
struct VcirRouterKeyPayload {
as_id: u32,
ski_hex: String,
spki_der_base64: String,
}
fn discover_children_from_fresh_snapshot_with_audit<P: PublicationPointData>(
issuer: &CaInstanceHandle,
publication_point: &P,
@@ -2299,7 +2283,7 @@ fn build_objects_output_from_vcir(
local.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: local.source_object_uri.clone(),
sha256_hex: local.source_object_hash.clone(),
sha256_hex: local.source_object_hash_hex(),
kind: audit_kind_for_vcir_output_type(local.output_type),
result: AuditObjectResult::Error,
detail: Some(
@@ -2315,7 +2299,7 @@ fn build_objects_output_from_vcir(
.entry(local.source_object_uri.clone())
.or_insert_with(|| ObjectAuditEntry {
rsync_uri: local.source_object_uri.clone(),
sha256_hex: local.source_object_hash.clone(),
sha256_hex: local.source_object_hash_hex(),
kind: audit_kind_for_vcir_output_type(local.output_type),
result: AuditObjectResult::Skipped,
detail: Some("skipped: cached local output expired".to_string()),
@@ -2332,7 +2316,7 @@ fn build_objects_output_from_vcir(
local.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: local.source_object_uri.clone(),
sha256_hex: local.source_object_hash.clone(),
sha256_hex: local.source_object_hash_hex(),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Ok,
detail: None,
@@ -2348,7 +2332,7 @@ fn build_objects_output_from_vcir(
local.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: local.source_object_uri.clone(),
sha256_hex: local.source_object_hash.clone(),
sha256_hex: local.source_object_hash_hex(),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Error,
detail: Some(format!("cached ROA local output parse failed: {e}")),
@@ -2364,7 +2348,7 @@ fn build_objects_output_from_vcir(
local.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: local.source_object_uri.clone(),
sha256_hex: local.source_object_hash.clone(),
sha256_hex: local.source_object_hash_hex(),
kind: AuditObjectKind::Aspa,
result: AuditObjectResult::Ok,
detail: None,
@@ -2380,7 +2364,7 @@ fn build_objects_output_from_vcir(
local.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: local.source_object_uri.clone(),
sha256_hex: local.source_object_hash.clone(),
sha256_hex: local.source_object_hash_hex(),
kind: AuditObjectKind::Aspa,
result: AuditObjectResult::Error,
detail: Some(format!("cached ASPA local output parse failed: {e}")),
@@ -2395,7 +2379,7 @@ fn build_objects_output_from_vcir(
local.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: local.source_object_uri.clone(),
sha256_hex: local.source_object_hash.clone(),
sha256_hex: local.source_object_hash_hex(),
kind: AuditObjectKind::RouterCertificate,
result: AuditObjectResult::Ok,
detail: Some("cached Router Key local output restored".to_string()),
@@ -2411,7 +2395,7 @@ fn build_objects_output_from_vcir(
local.source_object_uri.clone(),
ObjectAuditEntry {
rsync_uri: local.source_object_uri.clone(),
sha256_hex: local.source_object_hash.clone(),
sha256_hex: local.source_object_hash_hex(),
kind: AuditObjectKind::RouterCertificate,
result: AuditObjectResult::Error,
detail: Some(format!(
@@ -2435,68 +2419,55 @@ fn build_objects_output_from_vcir(
}
fn parse_vcir_vrp_output(local: &VcirLocalOutput) -> Result<Vrp, String> {
let payload: VcirVrpPayload = serde_json::from_str(&local.payload_json)
.map_err(|e| format!("invalid VRP payload JSON: {e}"))?;
Ok(Vrp {
asn: payload.asn,
prefix: parse_vcir_prefix(&payload.prefix)?,
max_length: payload.max_length,
})
match &local.payload {
VcirLocalOutputPayload::Vrp {
asn,
afi,
prefix_len,
addr,
max_length,
} => Ok(Vrp {
asn: *asn,
prefix: crate::data_model::roa::IpPrefix {
afi: *afi,
prefix_len: *prefix_len,
addr: *addr,
},
max_length: *max_length,
}),
_ => Err("VCIR local output payload is not VRP".to_string()),
}
}
fn parse_vcir_aspa_output(local: &VcirLocalOutput) -> Result<AspaAttestation, String> {
let payload: VcirAspaPayload = serde_json::from_str(&local.payload_json)
.map_err(|e| format!("invalid ASPA payload JSON: {e}"))?;
Ok(AspaAttestation {
customer_as_id: payload.customer_as_id,
provider_as_ids: payload.provider_as_ids,
})
match &local.payload {
VcirLocalOutputPayload::Aspa {
customer_as_id,
provider_as_ids,
} => Ok(AspaAttestation {
customer_as_id: *customer_as_id,
provider_as_ids: provider_as_ids.clone(),
}),
_ => Err("VCIR local output payload is not ASPA".to_string()),
}
}
fn parse_vcir_router_key_output(local: &VcirLocalOutput) -> Result<RouterKeyPayload, String> {
let payload: VcirRouterKeyPayload = serde_json::from_str(&local.payload_json)
.map_err(|e| format!("invalid Router Key payload JSON: {e}"))?;
let ski =
hex::decode(&payload.ski_hex).map_err(|e| format!("invalid Router Key SKI hex: {e}"))?;
let spki_der = base64::engine::general_purpose::STANDARD
.decode(&payload.spki_der_base64)
.map_err(|e| format!("invalid Router Key SPKI base64: {e}"))?;
Ok(RouterKeyPayload {
as_id: payload.as_id,
ski,
spki_der,
source_object_uri: local.source_object_uri.clone(),
source_object_hash: local.source_object_hash.clone(),
source_ee_cert_hash: local.source_ee_cert_hash.clone(),
item_effective_until: local.item_effective_until.clone(),
})
}
fn parse_vcir_prefix(prefix: &str) -> Result<crate::data_model::roa::IpPrefix, String> {
let (addr, len) = prefix
.split_once('/')
.ok_or_else(|| format!("prefix missing '/': {prefix}"))?;
let prefix_len = len
.parse::<u16>()
.map_err(|e| format!("invalid prefix length '{len}': {e}"))?;
let ip = addr
.parse::<std::net::IpAddr>()
.map_err(|e| format!("invalid IP address '{addr}': {e}"))?;
match ip {
std::net::IpAddr::V4(v4) => Ok(crate::data_model::roa::IpPrefix {
afi: RoaAfi::Ipv4,
prefix_len,
addr: {
let mut addr = [0u8; 16];
addr[..4].copy_from_slice(&v4.octets());
addr
},
}),
std::net::IpAddr::V6(v6) => Ok(crate::data_model::roa::IpPrefix {
afi: RoaAfi::Ipv6,
prefix_len,
addr: v6.octets(),
match &local.payload {
VcirLocalOutputPayload::RouterKey {
as_id,
ski,
spki_der,
} => Ok(RouterKeyPayload {
as_id: *as_id,
ski: ski.clone(),
spki_der: spki_der.clone(),
source_object_uri: local.source_object_uri.clone(),
source_object_hash: local.source_object_hash_hex(),
source_ee_cert_hash: local.source_ee_cert_hash_hex(),
item_effective_until: local.item_effective_until.clone(),
}),
_ => Err("VCIR local output payload is not Router Key".to_string()),
}
}
@@ -2582,7 +2553,7 @@ fn persist_vcir_for_fresh_result(
store: &RocksStore,
ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,
objects: &crate::validation::objects::ObjectsOutput,
objects: &mut crate::validation::objects::ObjectsOutput,
warnings: &[Warning],
child_audits: &[ObjectAuditEntry],
discovered_children: &[DiscoveredChildCaInstance],
@@ -2605,7 +2576,7 @@ fn persist_vcir_for_fresh_result_with_timing(
store: &RocksStore,
ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,
objects: &crate::validation::objects::ObjectsOutput,
objects: &mut crate::validation::objects::ObjectsOutput,
warnings: &[Warning],
child_audits: &[ObjectAuditEntry],
discovered_children: &[DiscoveredChildCaInstance],
@@ -2635,17 +2606,12 @@ fn persist_vcir_for_fresh_result_with_timing(
timing.build_vcir_ms = build_vcir_started.elapsed().as_millis() as u64;
timing.build_vcir = build_vcir_timing;
let previous_load_started = std::time::Instant::now();
let previous = store
.get_vcir(&pack.manifest_rsync_uri)
.map_err(|e| format!("load existing VCIR failed: {e}"))?;
timing.previous_load_ms = previous_load_started.elapsed().as_millis() as u64;
let replace_vcir_started = std::time::Instant::now();
store
.replace_vcir_and_audit_rule_indexes(previous.as_ref(), &vcir)
.map_err(|e| format!("store VCIR and audit rule index failed: {e}"))?;
let replace_timing = store
.replace_vcir_and_manifest_replay_meta(&vcir)
.map_err(|e| format!("store VCIR and manifest replay meta failed: {e}"))?;
timing.replace_vcir_ms = replace_vcir_started.elapsed().as_millis() as u64;
timing.replace_vcir = replace_timing;
Ok(timing)
}
@@ -2653,7 +2619,7 @@ fn persist_vcir_for_fresh_result_with_timing(
fn build_vcir_from_fresh_result(
ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,
objects: &crate::validation::objects::ObjectsOutput,
objects: &mut crate::validation::objects::ObjectsOutput,
warnings: &[Warning],
child_audits: &[ObjectAuditEntry],
discovered_children: &[DiscoveredChildCaInstance],
@@ -2674,7 +2640,7 @@ fn build_vcir_from_fresh_result(
fn build_vcir_from_fresh_result_with_timing(
ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,
objects: &crate::validation::objects::ObjectsOutput,
objects: &mut crate::validation::objects::ObjectsOutput,
warnings: &[Warning],
child_audits: &[ObjectAuditEntry],
discovered_children: &[DiscoveredChildCaInstance],
@@ -2692,7 +2658,7 @@ fn build_vcir_from_fresh_result_with_timing(
timing.current_ca_decode_ms = current_ca_decode_started.elapsed().as_millis() as u64;
let local_outputs_started = std::time::Instant::now();
let local_outputs = build_vcir_local_outputs(ca, pack, objects)?;
let local_outputs = take_or_build_vcir_local_outputs(ca, pack, objects)?;
timing.local_outputs_ms = local_outputs_started.elapsed().as_millis() as u64;
let child_entries_started = std::time::Instant::now();
@@ -2710,6 +2676,18 @@ fn build_vcir_from_fresh_result_with_timing(
timing.related_artifacts_ms = related_artifacts_started.elapsed().as_millis() as u64;
let ccr_manifest_projection =
build_vcir_ccr_manifest_projection_from_fresh(ca, pack, &child_entries)?;
let local_vrp_count = local_outputs
.iter()
.filter(|output| output.output_type == VcirOutputType::Vrp)
.count() as u32;
let local_aspa_count = local_outputs
.iter()
.filter(|output| output.output_type == VcirOutputType::Aspa)
.count() as u32;
let local_router_key_count = local_outputs
.iter()
.filter(|output| output.output_type == VcirOutputType::RouterKey)
.count() as u32;
let accepted_object_count = related_artifacts
.iter()
.filter(|artifact| artifact.validation_status == VcirArtifactValidationStatus::Accepted)
@@ -2768,21 +2746,12 @@ fn build_vcir_from_fresh_result_with_timing(
),
},
child_entries,
local_outputs: local_outputs.clone(),
related_artifacts: related_artifacts.clone(),
local_outputs,
related_artifacts,
summary: VcirSummary {
local_vrp_count: local_outputs
.iter()
.filter(|output| output.output_type == VcirOutputType::Vrp)
.count() as u32,
local_aspa_count: local_outputs
.iter()
.filter(|output| output.output_type == VcirOutputType::Aspa)
.count() as u32,
local_router_key_count: local_outputs
.iter()
.filter(|output| output.output_type == VcirOutputType::RouterKey)
.count() as u32,
local_vrp_count,
local_aspa_count,
local_router_key_count,
child_count: discovered_children.len() as u32,
accepted_object_count,
rejected_object_count,
@@ -2799,6 +2768,17 @@ fn build_vcir_from_fresh_result_with_timing(
Ok((vcir, timing))
}
fn take_or_build_vcir_local_outputs(
ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,
objects: &mut crate::validation::objects::ObjectsOutput,
) -> Result<Vec<VcirLocalOutput>, String> {
if !objects.local_outputs_cache.is_empty() {
return Ok(std::mem::take(&mut objects.local_outputs_cache));
}
build_vcir_local_outputs(ca, pack, objects)
}
fn build_vcir_ccr_manifest_projection_from_fresh(
ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,
@@ -2892,7 +2872,7 @@ fn select_manifest_current_crl_from_snapshot(
}
fn build_vcir_local_outputs(
ca: &CaInstanceHandle,
_ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,
objects: &crate::validation::objects::ObjectsOutput,
) -> Result<Vec<VcirLocalOutput>, String> {
@@ -2930,12 +2910,6 @@ fn build_vcir_local_outputs(
PackTime::from_utc_offset_datetime(ee.resource_cert.tbs.validity_not_after);
for vrp in roa_to_vrps_for_vcir(&roa) {
let prefix = vrp_prefix_to_string(&vrp);
let payload_json = json!({
"asn": vrp.asn,
"prefix": prefix,
"max_length": vrp.max_length,
})
.to_string();
let rule_hash = sha256_hex(
format!(
"roa-rule:{}:{}:{}:{}",
@@ -2944,20 +2918,20 @@ fn build_vcir_local_outputs(
.as_bytes(),
);
out.push(VcirLocalOutput {
output_id: rule_hash.clone(),
output_type: VcirOutputType::Vrp,
item_effective_until: item_effective_until.clone(),
source_object_uri: file.rsync_uri.clone(),
source_object_type: "roa".to_string(),
source_object_hash: source_object_hash.clone(),
source_ee_cert_hash: source_ee_cert_hash.clone(),
payload_json,
rule_hash,
validation_path_hint: vec![
ca.manifest_rsync_uri.clone(),
file.rsync_uri.clone(),
source_object_hash.clone(),
],
source_object_type: VcirSourceObjectType::Roa,
source_object_hash: file.sha256,
source_ee_cert_hash: sha256_hex_to_32(&source_ee_cert_hash),
payload: VcirLocalOutputPayload::Vrp {
asn: vrp.asn,
afi: vrp.prefix.afi,
prefix_len: vrp.prefix.prefix_len,
addr: vrp.prefix.addr,
max_length: vrp.max_length,
},
rule_hash: sha256_hex_to_32(&rule_hash),
});
}
} else if accepted_aspa_uris.contains(file.rsync_uri.as_str()) {
@@ -2970,11 +2944,6 @@ fn build_vcir_local_outputs(
let source_ee_cert_hash = sha256_hex(ee.raw_der.as_slice());
let item_effective_until =
PackTime::from_utc_offset_datetime(ee.resource_cert.tbs.validity_not_after);
let payload_json = json!({
"customer_as_id": aspa.aspa.customer_as_id,
"provider_as_ids": aspa.aspa.provider_as_ids,
})
.to_string();
let providers = aspa
.aspa
.provider_as_ids
@@ -2990,20 +2959,17 @@ fn build_vcir_local_outputs(
.as_bytes(),
);
out.push(VcirLocalOutput {
output_id: rule_hash.clone(),
output_type: VcirOutputType::Aspa,
item_effective_until,
source_object_uri: file.rsync_uri.clone(),
source_object_type: "aspa".to_string(),
source_object_hash: source_object_hash.clone(),
source_ee_cert_hash,
payload_json,
rule_hash,
validation_path_hint: vec![
ca.manifest_rsync_uri.clone(),
file.rsync_uri.clone(),
source_object_hash,
],
source_object_type: VcirSourceObjectType::Aspa,
source_object_hash: file.sha256,
source_ee_cert_hash: sha256_hex_to_32(&source_ee_cert_hash),
payload: VcirLocalOutputPayload::Aspa {
customer_as_id: aspa.aspa.customer_as_id,
provider_as_ids: aspa.aspa.provider_as_ids.clone(),
},
rule_hash: sha256_hex_to_32(&rule_hash),
});
}
}
@@ -3011,7 +2977,7 @@ fn build_vcir_local_outputs(
}
pub(crate) fn build_router_key_local_outputs(
ca: &CaInstanceHandle,
_ca: &CaInstanceHandle,
router_keys: &[RouterKeyPayload],
) -> Vec<VcirLocalOutput> {
router_keys
@@ -3028,25 +2994,18 @@ pub(crate) fn build_router_key_local_outputs(
.as_bytes(),
);
VcirLocalOutput {
output_id: rule_hash.clone(),
output_type: VcirOutputType::RouterKey,
item_effective_until: router_key.item_effective_until.clone(),
source_object_uri: router_key.source_object_uri.clone(),
source_object_type: "router_key".to_string(),
source_object_hash: router_key.source_object_hash.clone(),
source_ee_cert_hash: router_key.source_ee_cert_hash.clone(),
payload_json: json!({
"as_id": router_key.as_id,
"ski_hex": ski_hex,
"spki_der_base64": spki_der_base64,
})
.to_string(),
rule_hash,
validation_path_hint: vec![
ca.manifest_rsync_uri.clone(),
router_key.source_object_uri.clone(),
router_key.source_object_hash.clone(),
],
source_object_type: VcirSourceObjectType::RouterKey,
source_object_hash: sha256_hex_to_32(&router_key.source_object_hash),
source_ee_cert_hash: sha256_hex_to_32(&router_key.source_ee_cert_hash),
payload: VcirLocalOutputPayload::RouterKey {
as_id: router_key.as_id,
ski: router_key.ski.clone(),
spki_der: router_key.spki_der.clone(),
},
rule_hash: sha256_hex_to_32(&rule_hash),
}
})
.collect()

View File

@@ -1,12 +1,13 @@
use super::*;
use crate::data_model::rc::ResourceCertificate;
use crate::data_model::roa::RoaAfi;
use crate::fetch::rsync::LocalDirRsyncFetcher;
use crate::fetch::rsync::{RsyncFetchError, RsyncFetcher};
use crate::storage::{
PackFile, PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult,
ValidatedManifestMeta, VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus,
VcirAuditSummary, VcirChildEntry, VcirInstanceGate, VcirLocalOutput, VcirOutputType,
VcirRelatedArtifact, VcirSummary,
VcirAuditSummary, VcirChildEntry, VcirInstanceGate, VcirLocalOutput, VcirLocalOutputPayload,
VcirOutputType, VcirRelatedArtifact, VcirSourceObjectType, VcirSummary,
};
use crate::sync::rrdp::Fetcher;
use crate::validation::publication_point::PublicationPointSnapshot;
@@ -16,6 +17,16 @@ use std::process::Command;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
fn sha256_32(input: &[u8]) -> [u8; 32] {
sha256_hex_to_32(&sha256_hex(input))
}
fn ipv4_addr(octets: [u8; 4]) -> [u8; 16] {
let mut addr = [0u8; 16];
addr[..4].copy_from_slice(&octets);
addr
}
struct NeverHttpFetcher;
impl Fetcher for NeverHttpFetcher {
fn fetch(&self, _uri: &str) -> Result<Vec<u8>, String> {
@@ -534,138 +545,147 @@ fn sample_vcir_for_projection(
subordinate_skis: vec![vec![0x33; 20]],
};
ValidatedCaInstanceResult {
manifest_rsync_uri: manifest_uri.clone(),
parent_manifest_rsync_uri: None,
tal_id: "test-tal".to_string(),
ca_subject_name: "CN=Issuer".to_string(),
ca_ski: "11".repeat(20),
issuer_ski: "22".repeat(20),
last_successful_validation_time: PackTime::from_utc_offset_datetime(now),
current_manifest_rsync_uri: manifest_uri.clone(),
current_crl_rsync_uri: current_crl_uri.clone(),
validated_manifest_meta: ValidatedManifestMeta {
validated_manifest_number: vec![1],
validated_manifest_this_update: PackTime::from_utc_offset_datetime(now),
validated_manifest_next_update: gate_until.clone(),
manifest_rsync_uri: manifest_uri.clone(),
parent_manifest_rsync_uri: None,
tal_id: "test-tal".to_string(),
ca_subject_name: "CN=Issuer".to_string(),
ca_ski: "11".repeat(20),
issuer_ski: "22".repeat(20),
last_successful_validation_time: PackTime::from_utc_offset_datetime(now),
current_manifest_rsync_uri: manifest_uri.clone(),
current_crl_rsync_uri: current_crl_uri.clone(),
validated_manifest_meta: ValidatedManifestMeta {
validated_manifest_number: vec![1],
validated_manifest_this_update: PackTime::from_utc_offset_datetime(now),
validated_manifest_next_update: gate_until.clone(),
},
ccr_manifest_projection,
instance_gate: VcirInstanceGate {
manifest_next_update: gate_until.clone(),
current_crl_next_update: gate_until.clone(),
self_ca_not_after: PackTime::from_utc_offset_datetime(now + time::Duration::hours(2)),
instance_effective_until: gate_until.clone(),
},
child_entries: vec![VcirChildEntry {
child_manifest_rsync_uri: child_manifest_uri,
child_cert_rsync_uri: child_cert_uri.clone(),
child_cert_hash: child_cert_hash.to_string(),
child_ski: "33".repeat(20),
child_rsync_base_uri: "rsync://example.test/repo/child/".to_string(),
child_publication_point_rsync_uri: "rsync://example.test/repo/child/".to_string(),
child_rrdp_notification_uri: Some("https://example.test/child-notify.xml".to_string()),
child_effective_ip_resources: None,
child_effective_as_resources: None,
accepted_at_validation_time: PackTime::from_utc_offset_datetime(now),
}],
local_outputs: vec![
VcirLocalOutput {
output_type: VcirOutputType::Vrp,
item_effective_until: PackTime::from_utc_offset_datetime(
now + time::Duration::minutes(30),
),
source_object_uri: roa_uri.clone(),
source_object_type: VcirSourceObjectType::Roa,
source_object_hash: sha256_hex_to_32(&roa_hash),
source_ee_cert_hash: sha256_hex_to_32(&ee_hash),
payload: VcirLocalOutputPayload::Vrp {
asn: 64496,
afi: RoaAfi::Ipv4,
prefix_len: 24,
addr: ipv4_addr([203, 0, 113, 0]),
max_length: 24,
},
rule_hash: sha256_32(b"roa-rule"),
},
ccr_manifest_projection,
instance_gate: VcirInstanceGate {
manifest_next_update: gate_until.clone(),
current_crl_next_update: gate_until.clone(),
self_ca_not_after: PackTime::from_utc_offset_datetime(now + time::Duration::hours(2)),
instance_effective_until: gate_until.clone(),
VcirLocalOutput {
output_type: VcirOutputType::Aspa,
item_effective_until: PackTime::from_utc_offset_datetime(
now + time::Duration::minutes(30),
),
source_object_uri: aspa_uri.clone(),
source_object_type: VcirSourceObjectType::Aspa,
source_object_hash: sha256_hex_to_32(&aspa_hash),
source_ee_cert_hash: sha256_hex_to_32(&ee_hash),
payload: VcirLocalOutputPayload::Aspa {
customer_as_id: 64496,
provider_as_ids: vec![64497, 64498],
},
rule_hash: sha256_32(b"aspa-rule"),
},
child_entries: vec![VcirChildEntry {
child_manifest_rsync_uri: child_manifest_uri,
child_cert_rsync_uri: child_cert_uri.clone(),
child_cert_hash: child_cert_hash.to_string(),
child_ski: "33".repeat(20),
child_rsync_base_uri: "rsync://example.test/repo/child/".to_string(),
child_publication_point_rsync_uri: "rsync://example.test/repo/child/".to_string(),
child_rrdp_notification_uri: Some("https://example.test/child-notify.xml".to_string()),
child_effective_ip_resources: None,
child_effective_as_resources: None,
accepted_at_validation_time: PackTime::from_utc_offset_datetime(now),
}],
local_outputs: vec![
VcirLocalOutput {
output_id: sha256_hex(b"vrp-out"),
output_type: VcirOutputType::Vrp,
item_effective_until: PackTime::from_utc_offset_datetime(now + time::Duration::minutes(30)),
source_object_uri: roa_uri.clone(),
source_object_type: "roa".to_string(),
source_object_hash: roa_hash.clone(),
source_ee_cert_hash: ee_hash.clone(),
payload_json: serde_json::json!({"asn": 64496, "prefix": "203.0.113.0/24", "max_length": 24}).to_string(),
rule_hash: sha256_hex(b"roa-rule"),
validation_path_hint: vec![manifest_uri.clone(), roa_uri.clone()],
VcirLocalOutput {
output_type: VcirOutputType::RouterKey,
item_effective_until: PackTime::from_utc_offset_datetime(
now + time::Duration::minutes(30),
),
source_object_uri: router_uri.clone(),
source_object_type: VcirSourceObjectType::RouterKey,
source_object_hash: sha256_hex_to_32(&router_hash),
source_ee_cert_hash: sha256_hex_to_32(&router_hash),
payload: VcirLocalOutputPayload::RouterKey {
as_id: 64496,
ski: vec![0x11; 20],
spki_der: vec![0x30, 0x00],
},
VcirLocalOutput {
output_id: sha256_hex(b"aspa-out"),
output_type: VcirOutputType::Aspa,
item_effective_until: PackTime::from_utc_offset_datetime(now + time::Duration::minutes(30)),
source_object_uri: aspa_uri.clone(),
source_object_type: "aspa".to_string(),
source_object_hash: aspa_hash.clone(),
source_ee_cert_hash: ee_hash,
payload_json: serde_json::json!({"customer_as_id": 64496, "provider_as_ids": [64497, 64498]}).to_string(),
rule_hash: sha256_hex(b"aspa-rule"),
validation_path_hint: vec![manifest_uri.clone(), aspa_uri.clone()],
},
VcirLocalOutput {
output_id: sha256_hex(b"router-key-out"),
output_type: VcirOutputType::RouterKey,
item_effective_until: PackTime::from_utc_offset_datetime(now + time::Duration::minutes(30)),
source_object_uri: router_uri.clone(),
source_object_type: "router_key".to_string(),
source_object_hash: router_hash.clone(),
source_ee_cert_hash: router_hash.clone(),
payload_json: serde_json::json!({
"as_id": 64496,
"ski_hex": "11".repeat(20),
"spki_der_base64": base64::engine::general_purpose::STANDARD.encode([0x30u8, 0x00]),
}).to_string(),
rule_hash: sha256_hex(b"router-key-rule"),
validation_path_hint: vec![manifest_uri.clone(), router_uri.clone()],
},
],
related_artifacts: vec![
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::Manifest,
artifact_kind: VcirArtifactKind::Mft,
uri: Some(manifest_uri.clone()),
sha256: manifest_hash,
object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::CurrentCrl,
artifact_kind: VcirArtifactKind::Crl,
uri: Some(current_crl_uri),
sha256: current_crl_hash,
object_type: Some("crl".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::ChildCaCert,
artifact_kind: VcirArtifactKind::Cer,
uri: Some(child_cert_uri),
sha256: child_cert_hash.to_string(),
object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
artifact_kind: VcirArtifactKind::Roa,
uri: Some(roa_uri),
sha256: roa_hash,
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
artifact_kind: VcirArtifactKind::Aspa,
uri: Some(aspa_uri),
sha256: aspa_hash,
object_type: Some("aspa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
],
summary: VcirSummary {
local_vrp_count: 1,
local_aspa_count: 1,
local_router_key_count: 1,
child_count: 1,
accepted_object_count: 4,
rejected_object_count: 0,
rule_hash: sha256_32(b"router-key-rule"),
},
audit_summary: VcirAuditSummary {
failed_fetch_eligible: true,
last_failed_fetch_reason: None,
warning_count: 0,
audit_flags: Vec::new(),
],
related_artifacts: vec![
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::Manifest,
artifact_kind: VcirArtifactKind::Mft,
uri: Some(manifest_uri.clone()),
sha256: manifest_hash,
object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
}
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::CurrentCrl,
artifact_kind: VcirArtifactKind::Crl,
uri: Some(current_crl_uri),
sha256: current_crl_hash,
object_type: Some("crl".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::ChildCaCert,
artifact_kind: VcirArtifactKind::Cer,
uri: Some(child_cert_uri),
sha256: child_cert_hash.to_string(),
object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
artifact_kind: VcirArtifactKind::Roa,
uri: Some(roa_uri),
sha256: roa_hash,
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
artifact_kind: VcirArtifactKind::Aspa,
uri: Some(aspa_uri),
sha256: aspa_hash,
object_type: Some("aspa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
},
],
summary: VcirSummary {
local_vrp_count: 1,
local_aspa_count: 1,
local_router_key_count: 1,
child_count: 1,
accepted_object_count: 4,
rejected_object_count: 0,
},
audit_summary: VcirAuditSummary {
failed_fetch_eligible: true,
last_failed_fetch_reason: None,
warning_count: 0,
audit_flags: Vec::new(),
},
}
}
#[test]
@@ -716,16 +736,20 @@ fn build_vcir_local_outputs_prefers_cached_outputs() {
rrdp_notification_uri: None,
};
let cached = vec![VcirLocalOutput {
output_id: "cached-output".to_string(),
output_type: VcirOutputType::Vrp,
item_effective_until: pack.next_update.clone(),
source_object_uri: "rsync://example.test/repo/issuer/a.roa".to_string(),
source_object_type: "roa".to_string(),
source_object_hash: sha256_hex(b"cached-roa"),
source_ee_cert_hash: sha256_hex(b"cached-ee"),
payload_json: "{\"asn\":64500}".to_string(),
rule_hash: sha256_hex(b"cached-rule"),
validation_path_hint: vec![pack.manifest_rsync_uri.clone()],
source_object_type: VcirSourceObjectType::Roa,
source_object_hash: sha256_32(b"cached-roa"),
source_ee_cert_hash: sha256_32(b"cached-ee"),
payload: VcirLocalOutputPayload::Vrp {
asn: 64500,
afi: RoaAfi::Ipv4,
prefix_len: 24,
addr: ipv4_addr([203, 0, 113, 0]),
max_length: 24,
},
rule_hash: sha256_32(b"cached-rule"),
}];
let outputs = build_vcir_local_outputs(
&ca,
@@ -801,7 +825,7 @@ fn persist_vcir_non_repository_evidence_stores_current_ca_cert_only() {
.expect("first local output");
assert!(
store
.get_raw_by_hash_entry(&first_output.source_ee_cert_hash)
.get_raw_by_hash_entry(&first_output.source_ee_cert_hash_hex())
.expect("load source ee raw")
.is_none()
);
@@ -838,8 +862,11 @@ fn build_router_key_local_outputs_encodes_router_key_payloads() {
);
assert_eq!(outputs.len(), 1);
assert_eq!(outputs[0].output_type, VcirOutputType::RouterKey);
assert_eq!(outputs[0].source_object_type, "router_key");
assert!(outputs[0].payload_json.contains("spki_der_base64"));
assert_eq!(
outputs[0].source_object_type,
VcirSourceObjectType::RouterKey
);
assert!(outputs[0].payload_json().contains("spki_der_base64"));
}
#[test]
@@ -996,7 +1023,7 @@ fn finalize_fresh_publication_point_releases_local_outputs_cache_after_persist()
}
#[test]
fn persist_vcir_for_fresh_result_stores_vcir_and_audit_indexes_for_real_snapshot() {
fn persist_vcir_for_fresh_result_stores_vcir_and_replay_meta_for_real_snapshot() {
let (pack, issuer_ca_der, validation_time) = cernet_publication_point_snapshot_for_vcir_tests();
let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer ca");
let objects = crate::validation::objects::process_publication_point_snapshot_for_issuer(
@@ -1029,8 +1056,18 @@ fn persist_vcir_for_fresh_result_stores_vcir_and_audit_indexes_for_real_snapshot
rrdp_notification_uri: None,
};
persist_vcir_for_fresh_result(&store, &ca, &pack, &objects, &[], &[], &[], validation_time)
.expect("persist vcir for fresh result");
let mut objects = objects;
persist_vcir_for_fresh_result(
&store,
&ca,
&pack,
&mut objects,
&[],
&[],
&[],
validation_time,
)
.expect("persist vcir for fresh result");
let vcir = store
.get_vcir(&pack.manifest_rsync_uri)
@@ -1054,12 +1091,15 @@ fn persist_vcir_for_fresh_result_stores_vcir_and_audit_indexes_for_real_snapshot
vcir.ccr_manifest_projection.manifest_size,
pack.manifest_bytes.len() as u64
);
let first_output = vcir.local_outputs.first().expect("local outputs stored");
assert!(
store
.get_audit_rule_index_entry(crate::storage::AuditRuleKind::Roa, &first_output.rule_hash)
.expect("get audit rule index entry")
.is_some()
assert!(vcir.local_outputs.first().is_some(), "local outputs stored");
let replay_meta = store
.get_manifest_replay_meta(&pack.manifest_rsync_uri)
.expect("get replay meta")
.expect("replay meta exists");
assert_eq!(replay_meta.manifest_rsync_uri, pack.manifest_rsync_uri);
assert_eq!(
replay_meta.manifest_sha256,
sha2::Sha256::digest(&pack.manifest_bytes).to_vec()
);
}
@@ -1531,26 +1571,16 @@ fn runner_offline_rsync_fixture_produces_pack_and_warnings() {
.iter()
.find(|output| output.output_type == crate::storage::VcirOutputType::Vrp)
.expect("first VCIR VRP output");
let audit_rule = store
.get_audit_rule_index_entry(crate::storage::AuditRuleKind::Roa, &first_vrp.rule_hash)
.expect("get audit rule index")
.expect("audit rule index exists");
assert_eq!(audit_rule.manifest_rsync_uri, manifest_rsync_uri);
assert_eq!(audit_rule.output_id, first_vrp.output_id);
let trace = crate::audit_trace::trace_rule_to_root(
&store,
crate::storage::AuditRuleKind::Roa,
&first_vrp.rule_hash,
)
.expect("trace rule")
.expect("trace exists");
assert_eq!(trace.chain_leaf_to_root.len(), 1);
assert!(!first_vrp.rule_hash_hex().is_empty());
assert!(!first_vrp.output_id().is_empty());
let replay_meta = store
.get_manifest_replay_meta(&manifest_rsync_uri)
.expect("get replay meta")
.expect("replay meta exists");
assert_eq!(
trace.chain_leaf_to_root[0].manifest_rsync_uri,
replay_meta.manifest_rsync_uri,
manifest_rsync_uri
);
assert!(trace.source_object_raw.raw_present);
assert!(trace.source_ee_cert_raw.raw_present);
}
#[test]
@@ -2791,10 +2821,11 @@ fn fresh_and_reuse_paths_produce_equivalent_ccr_manifest_projection() {
let child_discovery =
discover_children_from_fresh_snapshot_with_audit(&ca, &pack, validation_time, None)
.expect("discover children");
let mut objects = empty_objects_output();
let fresh_vcir = build_vcir_from_fresh_result(
&ca,
&pack,
&empty_objects_output(),
&mut objects,
&[],
&child_discovery.audits,
&child_discovery.children,
@@ -2913,70 +2944,77 @@ fn build_objects_output_from_vcir_tracks_expired_and_invalid_cached_outputs() {
}
vcir.local_outputs.push(VcirLocalOutput {
output_id: sha256_hex(b"bad-time"),
output_type: VcirOutputType::Vrp,
item_effective_until: PackTime {
rfc3339_utc: "bad-time-value".to_string(),
},
source_object_uri: bad_time_uri.clone(),
source_object_type: "roa".to_string(),
source_object_hash: sha256_hex(b"bad-time-src"),
source_ee_cert_hash: sha256_hex(b"bad-time-ee"),
payload_json:
serde_json::json!({"asn": 64496, "prefix": "203.0.113.0/24", "max_length": 24})
.to_string(),
rule_hash: sha256_hex(b"bad-time-rule"),
validation_path_hint: vec![vcir.manifest_rsync_uri.clone(), bad_time_uri.clone()],
source_object_type: VcirSourceObjectType::Roa,
source_object_hash: sha256_32(b"bad-time-src"),
source_ee_cert_hash: sha256_32(b"bad-time-ee"),
payload: VcirLocalOutputPayload::Vrp {
asn: 64496,
afi: RoaAfi::Ipv4,
prefix_len: 24,
addr: ipv4_addr([203, 0, 113, 0]),
max_length: 24,
},
rule_hash: sha256_32(b"bad-time-rule"),
});
vcir.local_outputs.push(VcirLocalOutput {
output_id: sha256_hex(b"expired"),
output_type: VcirOutputType::Aspa,
item_effective_until: PackTime::from_utc_offset_datetime(now - time::Duration::minutes(1)),
source_object_uri: expired_uri.clone(),
source_object_type: "aspa".to_string(),
source_object_hash: sha256_hex(b"expired-src"),
source_ee_cert_hash: sha256_hex(b"expired-ee"),
payload_json: serde_json::json!({"customer_as_id": 64500, "provider_as_ids": [64501]})
.to_string(),
rule_hash: sha256_hex(b"expired-rule"),
validation_path_hint: vec![vcir.manifest_rsync_uri.clone(), expired_uri.clone()],
source_object_type: VcirSourceObjectType::Aspa,
source_object_hash: sha256_32(b"expired-src"),
source_ee_cert_hash: sha256_32(b"expired-ee"),
payload: VcirLocalOutputPayload::Aspa {
customer_as_id: 64500,
provider_as_ids: vec![64501],
},
rule_hash: sha256_32(b"expired-rule"),
});
vcir.local_outputs.push(VcirLocalOutput {
output_id: sha256_hex(b"bad-json"),
output_type: VcirOutputType::Vrp,
item_effective_until: PackTime::from_utc_offset_datetime(now + time::Duration::minutes(5)),
source_object_uri: bad_json_uri.clone(),
source_object_type: "roa".to_string(),
source_object_hash: sha256_hex(b"bad-json-src"),
source_ee_cert_hash: sha256_hex(b"bad-json-ee"),
payload_json: "{not-json".to_string(),
rule_hash: sha256_hex(b"bad-json-rule"),
validation_path_hint: vec![vcir.manifest_rsync_uri.clone(), bad_json_uri.clone()],
source_object_type: VcirSourceObjectType::Roa,
source_object_hash: sha256_32(b"bad-json-src"),
source_ee_cert_hash: sha256_32(b"bad-json-ee"),
payload: VcirLocalOutputPayload::Aspa {
customer_as_id: 64510,
provider_as_ids: vec![64511],
},
rule_hash: sha256_32(b"bad-json-rule"),
});
vcir.local_outputs.push(VcirLocalOutput {
output_id: sha256_hex(b"bad-prefix"),
output_type: VcirOutputType::Vrp,
item_effective_until: PackTime::from_utc_offset_datetime(now + time::Duration::minutes(5)),
source_object_uri: bad_prefix_uri.clone(),
source_object_type: "roa".to_string(),
source_object_hash: sha256_hex(b"bad-prefix-src"),
source_ee_cert_hash: sha256_hex(b"bad-prefix-ee"),
payload_json: serde_json::json!({"asn": 64510, "prefix": "203.0.113.0", "max_length": 24})
.to_string(),
rule_hash: sha256_hex(b"bad-prefix-rule"),
validation_path_hint: vec![vcir.manifest_rsync_uri.clone(), bad_prefix_uri.clone()],
source_object_type: VcirSourceObjectType::Roa,
source_object_hash: sha256_32(b"bad-prefix-src"),
source_ee_cert_hash: sha256_32(b"bad-prefix-ee"),
payload: VcirLocalOutputPayload::Aspa {
customer_as_id: 64512,
provider_as_ids: vec![64513],
},
rule_hash: sha256_32(b"bad-prefix-rule"),
});
vcir.local_outputs.push(VcirLocalOutput {
output_id: sha256_hex(b"bad-aspa"),
output_type: VcirOutputType::Aspa,
item_effective_until: PackTime::from_utc_offset_datetime(now + time::Duration::minutes(5)),
source_object_uri: bad_aspa_uri.clone(),
source_object_type: "aspa".to_string(),
source_object_hash: sha256_hex(b"bad-aspa-src"),
source_ee_cert_hash: sha256_hex(b"bad-aspa-ee"),
payload_json: serde_json::json!({"customer_as_id": 64520}).to_string(),
rule_hash: sha256_hex(b"bad-aspa-rule"),
validation_path_hint: vec![vcir.manifest_rsync_uri.clone(), bad_aspa_uri.clone()],
source_object_type: VcirSourceObjectType::Aspa,
source_object_hash: sha256_32(b"bad-aspa-src"),
source_ee_cert_hash: sha256_32(b"bad-aspa-ee"),
payload: VcirLocalOutputPayload::Vrp {
asn: 64520,
afi: RoaAfi::Ipv4,
prefix_len: 24,
addr: ipv4_addr([198, 51, 100, 0]),
max_length: 24,
},
rule_hash: sha256_32(b"bad-aspa-rule"),
});
let mut warnings = Vec::new();