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

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

View File

@@ -708,6 +708,7 @@ fn sample_vcir_for_projection(
sha256: manifest_hash,
object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::CurrentCrl,
@@ -716,6 +717,7 @@ fn sample_vcir_for_projection(
sha256: current_crl_hash,
object_type: Some("crl".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::ChildCaCert,
@@ -724,6 +726,7 @@ fn sample_vcir_for_projection(
sha256: child_cert_hash.to_string(),
object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
@@ -732,6 +735,7 @@ fn sample_vcir_for_projection(
sha256: roa_hash,
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
},
VcirRelatedArtifact {
artifact_role: VcirArtifactRole::SignedObject,
@@ -740,6 +744,7 @@ fn sample_vcir_for_projection(
sha256: aspa_hash,
object_type: Some("aspa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
},
],
summary: VcirSummary {
@@ -1455,10 +1460,12 @@ fn build_vcir_related_artifacts_classifies_snapshot_files_and_audit_statuses() {
&& artifact.artifact_role == VcirArtifactRole::ChildCaCert));
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref()
== 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()
== 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()
== Some("rsync://example.test/repo/issuer/a.gbr")
&& artifact.artifact_kind == VcirArtifactKind::Gbr));
@@ -4510,6 +4517,7 @@ fn build_objects_output_from_vcir_tracks_expired_and_invalid_cached_outputs() {
.to_string(),
),
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]
fn build_publication_point_audit_from_vcir_failed_no_cache_keeps_current_reject_only() {
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"),
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
});
vcir.related_artifacts.push(VcirRelatedArtifact {
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"),
object_type: Some("roa".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
});
vcir.related_artifacts.push(VcirRelatedArtifact {
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"),
object_type: Some("cer".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
reject_reason: None,
});
let ca = CaInstanceHandle {