20260731 完善verification-only 回放 (#118/#137)
This commit is contained in:
@@ -135,6 +135,11 @@ impl PublicationPointData for PublicationPointSnapshot {
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ManifestFreshError {
|
||||
#[error(
|
||||
"verification-only fallback replay selected current-instance VCIR: {manifest_rsync_uri}"
|
||||
)]
|
||||
VerificationOnlyForcedCurrentInstanceVcir { manifest_rsync_uri: String },
|
||||
|
||||
#[error("repo sync failed: {detail} (RFC 8182 §3.4.5; RFC 9286 §6.6)")]
|
||||
RepoSyncFailed { detail: String },
|
||||
|
||||
@@ -222,7 +227,8 @@ impl ManifestFreshError {
|
||||
pub(crate) fn should_warn_when_current_instance_reused(&self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
ManifestFreshError::RepoSyncFailed { .. }
|
||||
ManifestFreshError::VerificationOnlyForcedCurrentInstanceVcir { .. }
|
||||
| ManifestFreshError::RepoSyncFailed { .. }
|
||||
| ManifestFreshError::MissingManifest { .. }
|
||||
| ManifestFreshError::MissingFile { .. }
|
||||
)
|
||||
|
||||
@@ -807,6 +807,21 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
|
||||
repo_sync_ok: bool,
|
||||
repo_sync_err: Option<&str>,
|
||||
) -> Result<FreshPublicationPointStage, FreshPublicationPointStageError> {
|
||||
if self
|
||||
.policy
|
||||
.verification_forced_vcir_reuse_manifest_uris
|
||||
.contains(&ca.manifest_rsync_uri)
|
||||
{
|
||||
if let Some(timing) = self.timing.as_ref() {
|
||||
timing.record_count("verification_forced_vcir_reuse_publication_points", 1);
|
||||
}
|
||||
return Err(FreshPublicationPointStageError {
|
||||
error: ManifestFreshError::VerificationOnlyForcedCurrentInstanceVcir {
|
||||
manifest_rsync_uri: ca.manifest_rsync_uri.clone(),
|
||||
},
|
||||
snapshot_prepare_ms: 0,
|
||||
});
|
||||
}
|
||||
let snapshot_prepare_started = std::time::Instant::now();
|
||||
let issuer_ca_der = ca_certificate_der_for_validation(ca, self.store, self.timing.as_ref())
|
||||
.map_err(|detail| FreshPublicationPointStageError {
|
||||
|
||||
@@ -3426,6 +3426,45 @@ fn runner_when_repo_sync_fails_uses_current_instance_vcir_and_keeps_children_emp
|
||||
.any(|w| w.message.contains("repo sync failed")),
|
||||
"expected warning about repo sync failure"
|
||||
);
|
||||
|
||||
// Verification-only replays have the frozen repository bytes available,
|
||||
// so a fresh read would succeed and never naturally enter the fallback
|
||||
// branch. A contract-selected URI must therefore select the same VCIR
|
||||
// projection without attempting fresh manifest/object validation.
|
||||
let mut forced_policy = policy.clone();
|
||||
forced_policy
|
||||
.verification_forced_vcir_reuse_manifest_uris
|
||||
.insert(manifest_rsync_uri.clone());
|
||||
let forced_runner = Rpkiv1PublicationPointRunner {
|
||||
store: &store,
|
||||
policy: &forced_policy,
|
||||
http_fetcher: &NeverHttpFetcher,
|
||||
rsync_fetcher: &LocalDirRsyncFetcher::new(&fixture_dir),
|
||||
validation_time,
|
||||
timing: None,
|
||||
download_log: None,
|
||||
replay_archive_index: None,
|
||||
replay_delta_index: None,
|
||||
rrdp_dedup: false,
|
||||
rrdp_repo_cache: Mutex::new(HashMap::new()),
|
||||
rsync_dedup: false,
|
||||
rsync_repo_cache: Mutex::new(HashMap::new()),
|
||||
current_repo_index: None,
|
||||
repo_sync_runtime: None,
|
||||
parallel_phase2_config: None,
|
||||
parallel_roa_worker_pool: None,
|
||||
ccr_accumulator: None,
|
||||
persist_vcir: true,
|
||||
enable_roa_validation_cache: false,
|
||||
enable_child_certificate_validation_cache: false,
|
||||
publication_point_cache_observe_only: false,
|
||||
enable_publication_point_validation_cache: false,
|
||||
};
|
||||
let forced = forced_runner
|
||||
.run_publication_point(&handle)
|
||||
.expect("contract-selected fallback should reuse current-instance VCIR");
|
||||
assert_eq!(forced.source, PublicationPointSource::VcirCurrentInstance);
|
||||
assert!(forced.discovered_children.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -4807,17 +4846,18 @@ fn build_publication_point_audit_from_vcir_restores_reject_reason_with_legacy_fa
|
||||
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")
|
||||
&& 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()
|
||||
}));
|
||||
assert!(
|
||||
audit.objects.iter().all(|entry| {
|
||||
!matches!(entry.result, AuditObjectResult::Ok) || entry.detail.is_none()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -4897,8 +4937,7 @@ fn build_publication_point_audit_from_pp_cache_projection_restores_reject_reason
|
||||
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")
|
||||
&& 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"
|
||||
|
||||
Reference in New Issue
Block a user