20260624 修复future notBefore PP缓存并固化231发布

This commit is contained in:
2026-06-24 13:36:39 +08:00
parent adb68fd469
commit af25316d68
6 changed files with 1064 additions and 47 deletions

View File

@@ -26,11 +26,11 @@ use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
use crate::report::{RfcRef, Warning};
use crate::storage::{
PackFile, PackTime, PublicationPointCacheChild, PublicationPointCacheOutput,
PublicationPointCacheProjection, RawByHashEntry, RoaCacheProjectionContext, RocksStore,
ValidatedCaInstanceResult, VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus,
VcirAuditSummary, VcirCcrManifestProjection, VcirChildEntry, VcirInstanceGate, VcirLocalOutput,
VcirLocalOutputPayload, VcirOutputType, VcirRelatedArtifact, VcirReplaceTimingBreakdown,
VcirSourceObjectType, VcirSummary,
PublicationPointCacheProjection, PublicationPointCacheProjectionWriteAction, RawByHashEntry,
RoaCacheProjectionContext, RocksStore, ValidatedCaInstanceResult, VcirArtifactKind,
VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary, VcirCcrManifestProjection,
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,
@@ -90,6 +90,7 @@ pub(crate) struct PersistVcirTimingBreakdown {
pub(crate) embedded_store_ms: u64,
pub(crate) build_vcir_ms: u64,
pub(crate) replace_vcir_ms: u64,
pub(crate) publication_point_cache_future_notbefore_guarded: bool,
pub(crate) build_vcir: BuildVcirTimingBreakdown,
pub(crate) replace_vcir: VcirReplaceTimingBreakdown,
}
@@ -1021,6 +1022,9 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
.related_artifacts_ms
.saturating_mul(1_000_000),
);
if persist_vcir_timing.publication_point_cache_future_notbefore_guarded {
timing.record_count("publication_point_cache_future_notbefore_guarded", 1);
}
}
self.record_publication_point_step_ms(
&ca.manifest_rsync_uri,
@@ -1585,6 +1589,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
"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,
"publication_point_cache_future_notbefore_guarded": persist_vcir_timing.publication_point_cache_future_notbefore_guarded,
"ccr_projection_build_ms": ccr_projection_build_ms,
"ccr_append_ms": ccr_append_ms,
"audit_build_ms": audit_build_ms,
@@ -1630,6 +1635,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
"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,
"publication_point_cache_future_notbefore_guarded": persist_vcir_timing.publication_point_cache_future_notbefore_guarded,
"ccr_projection_build_ms": ccr_projection_build_ms,
"ccr_append_ms": ccr_append_ms,
"audit_build_ms": audit_build_ms,
@@ -4181,22 +4187,45 @@ fn persist_vcir_for_fresh_result_with_timing(
timing.build_vcir = build_vcir_timing;
let replace_vcir_started = std::time::Instant::now();
let publication_point_cache_projection = if write_publication_point_cache_projection {
Some(build_publication_point_cache_projection_from_fresh(
policy, ca, pack, &vcir,
)?)
let future_not_before_cache_guard = write_publication_point_cache_projection
&& publication_point_cache_has_future_not_before_risk(
pack,
objects,
child_audits,
validation_time,
policy,
);
timing.publication_point_cache_future_notbefore_guarded = future_not_before_cache_guard;
let publication_point_cache_projection =
if write_publication_point_cache_projection && !future_not_before_cache_guard {
Some(build_publication_point_cache_projection_from_fresh(
policy, ca, pack, &vcir,
)?)
} else {
None
};
let publication_point_cache_projection_action = if !write_publication_point_cache_projection {
PublicationPointCacheProjectionWriteAction::Keep
} else if future_not_before_cache_guard {
PublicationPointCacheProjectionWriteAction::Delete {
manifest_rsync_uri: &vcir.manifest_rsync_uri,
}
} else {
None
PublicationPointCacheProjectionWriteAction::Write(
publication_point_cache_projection
.as_ref()
.expect("publication point projection must exist when guard is not active"),
)
};
let replace_timing = store
.replace_vcir_manifest_replay_meta_and_projections(
.replace_vcir_manifest_replay_meta_and_projection_action(
&vcir,
Some(&RoaCacheProjectionContext {
parent_context_digest: parent_context_digest_for_ca(ca),
policy_fingerprint: publication_point_cache_policy_fingerprint(policy),
object_meta: objects.roa_cache_object_meta.clone(),
}),
publication_point_cache_projection.as_ref(),
publication_point_cache_projection_action,
)
.map_err(|e| format!("store VCIR and manifest replay meta failed: {e}"))?;
timing.replace_vcir_ms = replace_vcir_started.elapsed().as_millis() as u64;
@@ -4226,6 +4255,108 @@ fn build_publication_point_cache_projection_from_fresh(
.map_err(|e| e.to_string())
}
fn publication_point_cache_has_future_not_before_risk(
pack: &PublicationPointSnapshot,
objects: &crate::validation::objects::ObjectsOutput,
child_audits: &[ObjectAuditEntry],
validation_time: time::OffsetDateTime,
policy: &Policy,
) -> bool {
let mut files_by_uri: HashMap<&str, &PackFile> = HashMap::new();
for file in &pack.files {
files_by_uri.insert(file.rsync_uri.as_str(), file);
}
objects
.audit
.iter()
.chain(child_audits.iter())
.any(|entry| {
audit_entry_has_certificate_time_error(entry)
&& files_by_uri
.get(entry.rsync_uri.as_str())
.map(|file| {
audit_entry_has_future_not_before(entry, file, validation_time, policy)
})
.unwrap_or(true)
})
}
fn audit_entry_has_certificate_time_error(entry: &ObjectAuditEntry) -> bool {
entry.result == AuditObjectResult::Error
&& entry
.detail
.as_deref()
.is_some_and(|detail| detail.contains("certificate not valid at validation_time"))
}
fn audit_entry_has_future_not_before(
entry: &ObjectAuditEntry,
file: &PackFile,
validation_time: time::OffsetDateTime,
policy: &Policy,
) -> bool {
match entry.kind {
AuditObjectKind::Roa => signed_object_ee_not_before(file, policy, SignedObjectKind::Roa)
.map(|not_before| validation_time < not_before)
.unwrap_or(true),
AuditObjectKind::Aspa => signed_object_ee_not_before(file, policy, SignedObjectKind::Aspa)
.map(|not_before| validation_time < not_before)
.unwrap_or(true),
AuditObjectKind::Certificate | AuditObjectKind::RouterCertificate => file
.bytes()
.ok()
.and_then(|bytes| ResourceCertificate::decode_der(bytes).ok())
.map(|cert| validation_time < cert.tbs.validity_not_before)
.unwrap_or(true),
_ => true,
}
}
#[derive(Clone, Copy)]
enum SignedObjectKind {
Roa,
Aspa,
}
fn signed_object_ee_not_before(
file: &PackFile,
policy: &Policy,
kind: SignedObjectKind,
) -> Option<time::OffsetDateTime> {
let bytes = file.bytes().ok()?;
match kind {
SignedObjectKind::Roa => {
let object = RoaObject::decode_der_with_strict_options(
bytes,
policy.strict.cms_der,
policy.strict.name,
)
.ok()?;
Some(
object.signed_object.signed_data.certificates[0]
.resource_cert
.tbs
.validity_not_before,
)
}
SignedObjectKind::Aspa => {
let object = AspaObject::decode_der_with_strict_options(
bytes,
policy.strict.cms_der,
policy.strict.name,
)
.ok()?;
Some(
object.signed_object.signed_data.certificates[0]
.resource_cert
.tbs
.validity_not_before,
)
}
}
}
fn build_vcir_from_fresh_result_with_timing(
ca: &CaInstanceHandle,
pack: &PublicationPointSnapshot,

View File

@@ -4,7 +4,8 @@ use crate::data_model::roa::RoaAfi;
use crate::fetch::rsync::LocalDirRsyncFetcher;
use crate::fetch::rsync::{RsyncFetchError, RsyncFetcher};
use crate::storage::{
PackFile, PackTime, PublicationPointCacheProjection, RawByHashEntry, RepositoryViewEntry,
PackFile, PackTime, PublicationPointCacheProjection,
PublicationPointCacheProjectionWriteAction, RawByHashEntry, RepositoryViewEntry,
RepositoryViewState, RocksStore, ValidatedCaInstanceResult, ValidatedManifestMeta,
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
VcirChildEntry, VcirInstanceGate, VcirLocalOutput, VcirLocalOutputPayload, VcirOutputType,
@@ -1914,6 +1915,89 @@ fn seed_publication_point_cache_projection(
vcir
}
#[test]
fn publication_point_cache_future_notbefore_guard_detects_future_roa_notbefore_only() {
let uri = "rsync://example.test/repo/issuer/future.roa";
let bytes = std::fs::read(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa"),
)
.expect("read ROA fixture");
let roa = RoaObject::decode_der(&bytes).expect("decode ROA fixture");
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
let file = PackFile::from_bytes_compute_sha256(uri, bytes);
let pack = dummy_pack_with_files(vec![file]);
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: Default::default(),
audit: vec![ObjectAuditEntry {
rsync_uri: uri.to_string(),
sha256_hex: sha256_hex_from_32(&pack.files[0].sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Error,
detail: Some(
"EE certificate path validation failed: certificate not valid at validation_time"
.to_string(),
),
}],
roa_cache_stats: Default::default(),
roa_cache_object_meta: Vec::new(),
};
assert!(publication_point_cache_has_future_not_before_risk(
&pack,
&objects,
&[],
ee.tbs.validity_not_before - time::Duration::seconds(1),
&Policy::default(),
));
assert!(!publication_point_cache_has_future_not_before_risk(
&pack,
&objects,
&[],
ee.tbs.validity_not_after + time::Duration::seconds(1),
&Policy::default(),
));
}
#[test]
fn publication_point_cache_delete_action_removes_existing_projection() {
let store_dir = tempfile::tempdir().expect("store dir");
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
let policy = Policy::default();
let validation_time = time::OffsetDateTime::UNIX_EPOCH + time::Duration::minutes(1);
let ca = publication_point_cache_fixture_ca();
let vcir = seed_publication_point_cache_projection(&store, &policy, &ca, validation_time);
assert!(
store
.get_publication_point_cache_projection_cached(&ca.manifest_rsync_uri)
.expect("load projection")
.is_some()
);
store
.replace_vcir_manifest_replay_meta_and_projection_action(
&vcir,
None,
PublicationPointCacheProjectionWriteAction::Delete {
manifest_rsync_uri: &vcir.manifest_rsync_uri,
},
)
.expect("delete projection");
assert!(
store
.get_publication_point_cache_projection_cached(&ca.manifest_rsync_uri)
.expect("load projection after delete")
.is_none()
);
}
fn publication_point_cache_fixture_ca() -> CaInstanceHandle {
CaInstanceHandle {
depth: 1,