20260727 修复缓存回放丢失reject reason(#132):缓存artifact增加reject_reason字段,fresh验证记录真实原因,VCIR/PP cache回放还原,旧缓存给后备文案

This commit is contained in:
yuyr 2026-07-27 17:29:26 +08:00
parent cf68abf642
commit 7d5454a729
13 changed files with 281 additions and 10 deletions

View File

@ -406,6 +406,7 @@ mod tests {
sha256: manifest_hash, sha256: manifest_hash,
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}], }],
summary: VcirSummary { summary: VcirSummary {
local_vrp_count: 0, local_vrp_count: 0,

View File

@ -729,6 +729,7 @@ mod tests {
sha256: hash, sha256: hash,
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: crate::storage::VcirArtifactValidationStatus::Accepted, validation_status: crate::storage::VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}], }],
summary: crate::storage::VcirSummary { summary: crate::storage::VcirSummary {
local_vrp_count: 0, local_vrp_count: 0,

View File

@ -267,6 +267,7 @@ mod tests {
sha256: hash, sha256: hash,
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}], }],
summary: VcirSummary { summary: VcirSummary {
local_vrp_count: 0, local_vrp_count: 0,

View File

@ -645,6 +645,7 @@ mod tests {
sha256: "ff".repeat(32), sha256: "ff".repeat(32),
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}], }],
summary: VcirSummary { summary: VcirSummary {
local_vrp_count: 0, local_vrp_count: 0,

View File

@ -1660,6 +1660,9 @@ pub struct PublicationPointCacheObject {
pub object_type: Option<String>, pub object_type: Option<String>,
#[serde(rename = "s")] #[serde(rename = "s")]
pub validation_status: VcirArtifactValidationStatus, pub validation_status: VcirArtifactValidationStatus,
/// See `VcirRelatedArtifact::reject_reason`.
#[serde(rename = "e", default, skip_serializing_if = "Option::is_none")]
pub reject_reason: Option<String>,
} }
impl PublicationPointCacheObject { impl PublicationPointCacheObject {
@ -1671,6 +1674,7 @@ impl PublicationPointCacheObject {
sha256: artifact.sha256.clone(), sha256: artifact.sha256.clone(),
object_type: artifact.object_type.clone(), object_type: artifact.object_type.clone(),
validation_status: artifact.validation_status, validation_status: artifact.validation_status,
reject_reason: artifact.reject_reason.clone(),
} }
} }
@ -1682,6 +1686,7 @@ impl PublicationPointCacheObject {
sha256: self.sha256.clone(), sha256: self.sha256.clone(),
object_type: self.object_type.clone(), object_type: self.object_type.clone(),
validation_status: self.validation_status, validation_status: self.validation_status,
reject_reason: self.reject_reason.clone(),
} }
} }
@ -2157,6 +2162,11 @@ pub struct VcirRelatedArtifact {
pub object_type: Option<String>, pub object_type: Option<String>,
#[serde(rename = "s")] #[serde(rename = "s")]
pub validation_status: VcirArtifactValidationStatus, pub validation_status: VcirArtifactValidationStatus,
/// Reject reason captured at fresh validation time for `Rejected` artifacts.
/// `None` for accepted/warning artifacts and for cache entries written
/// before this field existed.
#[serde(rename = "e", default, skip_serializing_if = "Option::is_none")]
pub reject_reason: Option<String>,
} }
impl VcirRelatedArtifact { impl VcirRelatedArtifact {

View File

@ -257,6 +257,7 @@ fn sample_vcir(manifest_rsync_uri: &str) -> ValidatedCaInstanceResult {
sha256: sha256_hex(b"manifest-object"), sha256: sha256_hex(b"manifest-object"),
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
VcirRelatedArtifact { VcirRelatedArtifact {
artifact_role: VcirArtifactRole::CurrentCrl, artifact_role: VcirArtifactRole::CurrentCrl,
@ -265,6 +266,7 @@ fn sample_vcir(manifest_rsync_uri: &str) -> ValidatedCaInstanceResult {
sha256: sha256_hex(b"current-crl"), sha256: sha256_hex(b"current-crl"),
object_type: Some("crl".to_string()), object_type: Some("crl".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
], ],
summary: VcirSummary { summary: VcirSummary {
@ -2394,3 +2396,35 @@ fn repository_blob_verification_detects_missing_external_blob() {
.expect_err("missing blob must fail"); .expect_err("missing blob must fail");
assert!(error.to_string().contains("missing.roa"), "{error}"); assert!(error.to_string().contains("missing.roa"), "{error}");
} }
#[test]
fn vcir_related_artifact_reject_reason_serde_backward_compatible() {
// Legacy cache JSON written before the reject_reason ("e") field existed.
let legacy = r#"{"r":"manifest","k":"mft","u":"rsync://example.test/a.mft","h":"0000000000000000000000000000000000000000000000000000000000000000","t":"mft","s":"accepted"}"#;
let artifact: VcirRelatedArtifact = serde_json::from_str(legacy).expect("legacy artifact");
assert_eq!(artifact.reject_reason, None);
// None reason is skipped on serialization, keeping cache entries compact.
let json_none = serde_json::to_string(&artifact).expect("serialize none");
assert!(!json_none.contains("\"e\""));
// A recorded reason round-trips through the "e" field.
let mut with_reason = artifact.clone();
with_reason.validation_status = VcirArtifactValidationStatus::Rejected;
with_reason.reject_reason = Some("bad object".to_string());
let json = serde_json::to_string(&with_reason).expect("serialize");
assert!(json.contains("\"e\":\"bad object\""));
let decoded: VcirRelatedArtifact = serde_json::from_str(&json).expect("deserialize");
assert_eq!(decoded.reject_reason.as_deref(), Some("bad object"));
// PublicationPointCacheObject conversion carries the reason both ways.
let cache_object = PublicationPointCacheObject::from_related_artifact(&with_reason);
assert_eq!(cache_object.reject_reason.as_deref(), Some("bad object"));
let back = cache_object.to_related_artifact();
assert_eq!(back.reject_reason.as_deref(), Some("bad object"));
// Legacy PublicationPointCacheObject JSON without "e" still deserializes.
let legacy_object: PublicationPointCacheObject =
serde_json::from_str(legacy).expect("legacy cache object");
assert_eq!(legacy_object.reject_reason, None);
}

View File

@ -1199,6 +1199,7 @@ mod tests {
sha256: manifest_sha256.to_string(), sha256: manifest_sha256.to_string(),
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
VcirRelatedArtifact { VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject, artifact_role: VcirArtifactRole::SignedObject,
@ -1207,6 +1208,7 @@ mod tests {
sha256: locked_object_sha256.to_string(), sha256: locked_object_sha256.to_string(),
object_type: Some("roa".to_string()), object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
], ],
summary: VcirSummary { summary: VcirSummary {

View File

@ -4023,6 +4023,7 @@ mod tests {
sha256: sha256_hex(issuer_der), sha256: sha256_hex(issuer_der),
object_type: Some("cer".to_string()), object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
VcirRelatedArtifact { VcirRelatedArtifact {
artifact_role: VcirArtifactRole::CurrentCrl, artifact_role: VcirArtifactRole::CurrentCrl,
@ -4031,6 +4032,7 @@ mod tests {
sha256: sha256_hex_from_32(&crl_hash), sha256: sha256_hex_from_32(&crl_hash),
object_type: Some("crl".to_string()), object_type: Some("crl".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
], ],
summary: VcirSummary { summary: VcirSummary {
@ -4543,6 +4545,7 @@ mod tests {
sha256: "00".repeat(32), sha256: "00".repeat(32),
object_type: Some("cer".to_string()), object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Rejected, validation_status: VcirArtifactValidationStatus::Rejected,
reject_reason: None,
}, },
); );
vcir.local_outputs.push(VcirLocalOutput { vcir.local_outputs.push(VcirLocalOutput {

View File

@ -3840,6 +3840,28 @@ fn audit_result_from_vcir_status(status: VcirArtifactValidationStatus) -> AuditO
} }
} }
/// Fallback detail for cached artifacts rejected by an earlier run whose cache
/// entry predates reject-reason recording (field added 2026-07-27).
const CACHED_REJECT_REASON_NOT_RECORDED: &str =
"rejected in an earlier validation run (reject reason not recorded in cache)";
/// Detail for an audit entry rebuilt from a cached artifact: the real reject
/// reason when the cache recorded one, an explicit fallback for legacy cache
/// entries, and `None` for non-rejected artifacts.
fn audit_detail_from_vcir_status(
status: VcirArtifactValidationStatus,
reject_reason: Option<&str>,
) -> Option<String> {
match status {
VcirArtifactValidationStatus::Rejected => Some(
reject_reason
.map(str::to_string)
.unwrap_or_else(|| CACHED_REJECT_REASON_NOT_RECORDED.to_string()),
),
VcirArtifactValidationStatus::Accepted | VcirArtifactValidationStatus::WarningOnly => None,
}
}
fn build_publication_point_audit_from_snapshot( fn build_publication_point_audit_from_snapshot(
ca: &CaInstanceHandle, ca: &CaInstanceHandle,
source: PublicationPointSource, source: PublicationPointSource,
@ -4059,7 +4081,10 @@ fn build_publication_point_audit_from_vcir(
sha256_hex: artifact.sha256.clone(), sha256_hex: artifact.sha256.clone(),
kind: kind_from_vcir_artifact_kind(artifact.artifact_kind), kind: kind_from_vcir_artifact_kind(artifact.artifact_kind),
result: audit_result_from_vcir_status(artifact.validation_status), result: audit_result_from_vcir_status(artifact.validation_status),
detail: None, detail: audit_detail_from_vcir_status(
artifact.validation_status,
artifact.reject_reason.as_deref(),
),
}, },
); );
} }
@ -4170,7 +4195,10 @@ fn build_publication_point_audit_from_publication_point_cache_projection(
sha256_hex: artifact.sha256.clone(), sha256_hex: artifact.sha256.clone(),
kind: kind_from_vcir_artifact_kind(artifact.artifact_kind), kind: kind_from_vcir_artifact_kind(artifact.artifact_kind),
result: audit_result_from_vcir_status(artifact.validation_status), result: audit_result_from_vcir_status(artifact.validation_status),
detail: None, detail: audit_detail_from_vcir_status(
artifact.validation_status,
artifact.reject_reason.as_deref(),
),
}, },
); );
} }
@ -6003,9 +6031,9 @@ fn build_vcir_related_artifacts(
objects: &crate::validation::objects::ObjectsOutput, objects: &crate::validation::objects::ObjectsOutput,
child_audits: &[ObjectAuditEntry], child_audits: &[ObjectAuditEntry],
) -> Vec<VcirRelatedArtifact> { ) -> Vec<VcirRelatedArtifact> {
let mut audit_by_uri: HashMap<&str, AuditObjectResult> = HashMap::new(); let mut audit_by_uri: HashMap<&str, &ObjectAuditEntry> = HashMap::new();
for entry in child_audits.iter().chain(objects.audit.iter()) { for entry in child_audits.iter().chain(objects.audit.iter()) {
audit_by_uri.insert(entry.rsync_uri.as_str(), entry.result.clone()); audit_by_uri.insert(entry.rsync_uri.as_str(), entry);
} }
let mut artifacts = Vec::with_capacity(pack.files.len() + 2); let mut artifacts = Vec::with_capacity(pack.files.len() + 2);
@ -6016,6 +6044,7 @@ fn build_vcir_related_artifacts(
sha256: sha256_hex(&pack.manifest_bytes), sha256: sha256_hex(&pack.manifest_bytes),
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}); });
artifacts.push(VcirRelatedArtifact { artifacts.push(VcirRelatedArtifact {
artifact_role: if ca.parent_manifest_rsync_uri.is_none() { artifact_role: if ca.parent_manifest_rsync_uri.is_none() {
@ -6035,13 +6064,20 @@ fn build_vcir_related_artifacts(
}), }),
object_type: Some("cer".to_string()), object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}); });
for file in &pack.files { for file in &pack.files {
let result = audit_by_uri let audit_entry = audit_by_uri.get(file.rsync_uri.as_str()).copied();
.get(file.rsync_uri.as_str()) let result = audit_entry
.cloned() .map(|entry| entry.result.clone())
.unwrap_or(AuditObjectResult::Ok); .unwrap_or(AuditObjectResult::Ok);
let validation_status = audit_result_to_vcir_status(&result);
let reject_reason = if validation_status == VcirArtifactValidationStatus::Rejected {
audit_entry.and_then(|entry| entry.detail.clone())
} else {
None
};
let (artifact_role, artifact_kind) = artifact_role_and_kind(file, current_crl_rsync_uri); let (artifact_role, artifact_kind) = artifact_role_and_kind(file, current_crl_rsync_uri);
artifacts.push(VcirRelatedArtifact { artifacts.push(VcirRelatedArtifact {
artifact_role, artifact_role,
@ -6049,7 +6085,8 @@ fn build_vcir_related_artifacts(
uri: Some(file.rsync_uri.clone()), uri: Some(file.rsync_uri.clone()),
sha256: sha256_hex_from_32(&file.sha256), sha256: sha256_hex_from_32(&file.sha256),
object_type: object_type_from_uri(file.rsync_uri.as_str()), object_type: object_type_from_uri(file.rsync_uri.as_str()),
validation_status: audit_result_to_vcir_status(&result), validation_status,
reject_reason,
}); });
} }

View File

@ -708,6 +708,7 @@ fn sample_vcir_for_projection(
sha256: manifest_hash, sha256: manifest_hash,
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
VcirRelatedArtifact { VcirRelatedArtifact {
artifact_role: VcirArtifactRole::CurrentCrl, artifact_role: VcirArtifactRole::CurrentCrl,
@ -716,6 +717,7 @@ fn sample_vcir_for_projection(
sha256: current_crl_hash, sha256: current_crl_hash,
object_type: Some("crl".to_string()), object_type: Some("crl".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
VcirRelatedArtifact { VcirRelatedArtifact {
artifact_role: VcirArtifactRole::ChildCaCert, artifact_role: VcirArtifactRole::ChildCaCert,
@ -724,6 +726,7 @@ fn sample_vcir_for_projection(
sha256: child_cert_hash.to_string(), sha256: child_cert_hash.to_string(),
object_type: Some("cer".to_string()), object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
VcirRelatedArtifact { VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject, artifact_role: VcirArtifactRole::SignedObject,
@ -732,6 +735,7 @@ fn sample_vcir_for_projection(
sha256: roa_hash, sha256: roa_hash,
object_type: Some("roa".to_string()), object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
VcirRelatedArtifact { VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject, artifact_role: VcirArtifactRole::SignedObject,
@ -740,6 +744,7 @@ fn sample_vcir_for_projection(
sha256: aspa_hash, sha256: aspa_hash,
object_type: Some("aspa".to_string()), object_type: Some("aspa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}, },
], ],
summary: VcirSummary { summary: VcirSummary {
@ -1455,10 +1460,12 @@ fn build_vcir_related_artifacts_classifies_snapshot_files_and_audit_statuses() {
&& artifact.artifact_role == VcirArtifactRole::ChildCaCert)); && artifact.artifact_role == VcirArtifactRole::ChildCaCert));
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref()
== Some("rsync://example.test/repo/issuer/a.roa") == Some("rsync://example.test/repo/issuer/a.roa")
&& artifact.validation_status == VcirArtifactValidationStatus::Rejected)); && artifact.validation_status == VcirArtifactValidationStatus::Rejected
&& artifact.reject_reason.as_deref() == Some("bad roa")));
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref()
== Some("rsync://example.test/repo/issuer/a.asa") == Some("rsync://example.test/repo/issuer/a.asa")
&& artifact.validation_status == VcirArtifactValidationStatus::WarningOnly)); && artifact.validation_status == VcirArtifactValidationStatus::WarningOnly
&& artifact.reject_reason.is_none()));
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref()
== Some("rsync://example.test/repo/issuer/a.gbr") == Some("rsync://example.test/repo/issuer/a.gbr")
&& artifact.artifact_kind == VcirArtifactKind::Gbr)); && artifact.artifact_kind == VcirArtifactKind::Gbr));
@ -4510,6 +4517,7 @@ fn build_objects_output_from_vcir_tracks_expired_and_invalid_cached_outputs() {
.to_string(), .to_string(),
), ),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}); });
} }
@ -4732,6 +4740,173 @@ fn build_publication_point_audit_from_vcir_uses_vcir_metadata_and_overlays_child
})); }));
} }
#[test]
fn build_publication_point_audit_from_vcir_restores_reject_reason_with_legacy_fallback() {
let now = time::OffsetDateTime::now_utc();
let child_cert_hash = sha256_hex(b"child-cert");
let mut vcir = sample_vcir_for_projection(now, &child_cert_hash);
vcir.related_artifacts.push(VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
artifact_kind: VcirArtifactKind::Roa,
uri: Some("rsync://example.test/repo/issuer/rejected-with-reason.roa".to_string()),
sha256: sha256_hex(b"rejected-with-reason"),
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Rejected,
reject_reason: Some("EE certificate path validation failed: test".to_string()),
});
vcir.related_artifacts.push(VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
artifact_kind: VcirArtifactKind::Roa,
uri: Some("rsync://example.test/repo/issuer/rejected-legacy.roa".to_string()),
sha256: sha256_hex(b"rejected-legacy"),
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Rejected,
reject_reason: None,
});
let ca = CaInstanceHandle {
depth: 0,
tal_id: "test-tal".to_string(),
parent_manifest_rsync_uri: None,
ca_certificate: CaCertificateRef::inline_der(Vec::new()),
ca_certificate_rsync_uri: None,
effective_ip_resources: None,
effective_as_resources: None,
rsync_base_uri: "rsync://example.test/repo/issuer/".to_string(),
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
rrdp_notification_uri: None,
};
let objects = crate::validation::objects::ObjectsOutput {
vrps: Vec::new(),
aspas: Vec::new(),
router_keys: Vec::new(),
local_outputs_cache: Vec::new(),
warnings: Vec::new(),
stats: crate::validation::objects::ObjectsStats::default(),
audit: Vec::new(),
roa_cache_stats: crate::validation::objects::RoaValidationCacheStats::default(),
roa_cache_object_meta: Vec::new(),
};
let audit = build_publication_point_audit_from_vcir(
&ca,
PublicationPointSource::VcirCurrentInstance,
None,
None,
None,
None,
Some(&vcir),
None,
&[],
&objects,
&[],
&[],
);
assert!(audit.objects.iter().any(|entry| {
entry.rsync_uri == "rsync://example.test/repo/issuer/rejected-with-reason.roa"
&& matches!(entry.result, AuditObjectResult::Error)
&& entry.detail.as_deref()
== Some("EE certificate path validation failed: test")
}));
assert!(audit.objects.iter().any(|entry| {
entry.rsync_uri == "rsync://example.test/repo/issuer/rejected-legacy.roa"
&& matches!(entry.result, AuditObjectResult::Error)
&& entry.detail.as_deref() == Some(CACHED_REJECT_REASON_NOT_RECORDED)
}));
assert!(audit.objects.iter().all(|entry| {
!matches!(entry.result, AuditObjectResult::Ok) || entry.detail.is_none()
}));
}
#[test]
fn build_publication_point_audit_from_pp_cache_projection_restores_reject_reason() {
let now = time::OffsetDateTime::now_utc();
let child_cert_hash = sha256_hex(b"child-cert");
let mut vcir = sample_vcir_for_projection(now, &child_cert_hash);
vcir.related_artifacts.push(VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
artifact_kind: VcirArtifactKind::Roa,
uri: Some("rsync://example.test/repo/issuer/rejected-with-reason.roa".to_string()),
sha256: sha256_hex(b"rejected-with-reason"),
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Rejected,
reject_reason: Some("EE certificate path validation failed: test".to_string()),
});
vcir.related_artifacts.push(VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
artifact_kind: VcirArtifactKind::Roa,
uri: Some("rsync://example.test/repo/issuer/rejected-legacy.roa".to_string()),
sha256: sha256_hex(b"rejected-legacy"),
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Rejected,
reject_reason: None,
});
let projection = PublicationPointCacheProjection::from_vcir_with_context(
&vcir,
"rsync://example.test/repo/issuer/".to_string(),
None,
[0x11; 32],
[0x22; 32],
[0x33; 32],
[0x44; 32],
[0x55; 32],
)
.expect("build publication point projection");
let ca = CaInstanceHandle {
depth: 0,
tal_id: "test-tal".to_string(),
parent_manifest_rsync_uri: None,
ca_certificate: CaCertificateRef::inline_der(Vec::new()),
ca_certificate_rsync_uri: None,
effective_ip_resources: None,
effective_as_resources: None,
rsync_base_uri: "rsync://example.test/repo/issuer/".to_string(),
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
rrdp_notification_uri: None,
};
let objects = crate::validation::objects::ObjectsOutput {
vrps: Vec::new(),
aspas: Vec::new(),
router_keys: Vec::new(),
local_outputs_cache: Vec::new(),
warnings: Vec::new(),
stats: crate::validation::objects::ObjectsStats::default(),
audit: Vec::new(),
roa_cache_stats: crate::validation::objects::RoaValidationCacheStats::default(),
roa_cache_object_meta: Vec::new(),
};
let audit = build_publication_point_audit_from_publication_point_cache_projection(
&ca,
PublicationPointSource::PublicationPointCache,
None,
None,
None,
None,
&projection,
now,
&[],
&objects,
&[],
);
assert!(audit.objects.iter().any(|entry| {
entry.rsync_uri == "rsync://example.test/repo/issuer/rejected-with-reason.roa"
&& matches!(entry.result, AuditObjectResult::Error)
&& entry.detail.as_deref()
== Some("EE certificate path validation failed: test")
}));
assert!(audit.objects.iter().any(|entry| {
entry.rsync_uri == "rsync://example.test/repo/issuer/rejected-legacy.roa"
&& matches!(entry.result, AuditObjectResult::Error)
&& entry.detail.as_deref() == Some(CACHED_REJECT_REASON_NOT_RECORDED)
}));
}
#[test] #[test]
fn build_publication_point_audit_from_vcir_failed_no_cache_keeps_current_reject_only() { fn build_publication_point_audit_from_vcir_failed_no_cache_keeps_current_reject_only() {
let now = time::OffsetDateTime::now_utc(); let now = time::OffsetDateTime::now_utc();
@ -4948,6 +5123,7 @@ fn reconstruct_snapshot_from_vcir_reports_missing_manifest_and_related_raw_bytes
sha256: sha256_hex(b"dup-roa-1"), sha256: sha256_hex(b"dup-roa-1"),
object_type: Some("roa".to_string()), object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}); });
vcir.related_artifacts.push(VcirRelatedArtifact { vcir.related_artifacts.push(VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject, artifact_role: VcirArtifactRole::SignedObject,
@ -4956,6 +5132,7 @@ fn reconstruct_snapshot_from_vcir_reports_missing_manifest_and_related_raw_bytes
sha256: sha256_hex(b"dup-roa-2"), sha256: sha256_hex(b"dup-roa-2"),
object_type: Some("roa".to_string()), object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}); });
vcir.related_artifacts.push(VcirRelatedArtifact { vcir.related_artifacts.push(VcirRelatedArtifact {
artifact_role: VcirArtifactRole::IssuerCert, artifact_role: VcirArtifactRole::IssuerCert,
@ -4964,6 +5141,7 @@ fn reconstruct_snapshot_from_vcir_reports_missing_manifest_and_related_raw_bytes
sha256: sha256_hex(b"issuer-cert"), sha256: sha256_hex(b"issuer-cert"),
object_type: Some("cer".to_string()), object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}); });
let ca = CaInstanceHandle { let ca = CaInstanceHandle {

View File

@ -244,6 +244,7 @@ fn verify_against_vcir_store_matches_manifest_hashes() {
sha256: manifest_hash.clone(), sha256: manifest_hash.clone(),
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}], }],
summary: VcirSummary { summary: VcirSummary {
local_vrp_count: 0, local_vrp_count: 0,

View File

@ -109,6 +109,7 @@ fn store_validated_manifest_baseline(
sha256: manifest_sha256, sha256: manifest_sha256,
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}], }],
summary: VcirSummary { summary: VcirSummary {
local_vrp_count: 0, local_vrp_count: 0,

View File

@ -112,6 +112,7 @@ fn store_validated_manifest_baseline(
sha256: manifest_sha256, sha256: manifest_sha256,
object_type: Some("mft".to_string()), object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted, validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
}], }],
summary: VcirSummary { summary: VcirSummary {
local_vrp_count: 0, local_vrp_count: 0,