20260828 收口Feature151 TA constraints优化与验证

This commit is contained in:
2026-08-28 17:42:04 +08:00
parent b59adc65a8
commit 74cbebbd33
18 changed files with 3854 additions and 40 deletions

View File

@@ -961,6 +961,38 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
timing: Option<&TimingHandle>,
collect_vcir_local_outputs: bool,
roa_cache: RoaValidationCacheInput<'_>,
) -> ObjectsOutput {
process_publication_point_for_issuer_with_cache_options_and_ta_constraints(
publication_point,
policy,
issuer_ca_der,
issuer_ca_rsync_uri,
issuer_effective_ip,
issuer_effective_as,
validation_time,
timing,
collect_vcir_local_outputs,
roa_cache,
None,
)
}
/// Serial signed-object processing with an optional, locally configured
/// constraint set for the TA that owns this publication-point tree.
pub fn process_publication_point_for_issuer_with_cache_options_and_ta_constraints<
P: PublicationPointData,
>(
publication_point: &P,
policy: &Policy,
issuer_ca_der: &[u8],
issuer_ca_rsync_uri: Option<&str>,
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
validation_time: time::OffsetDateTime,
timing: Option<&TimingHandle>,
collect_vcir_local_outputs: bool,
roa_cache: RoaValidationCacheInput<'_>,
ta_constraints: Option<&crate::ta_constraints::TaConstraints>,
) -> ObjectsOutput {
let manifest_rsync_uri = publication_point.manifest_rsync_uri();
let manifest_bytes = publication_point.manifest_bytes();
@@ -1271,6 +1303,7 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
policy.strict.cms_der,
policy.strict.name,
policy.resource_validation_mode,
ta_constraints,
)
}
blocked @ (RoaCacheLookupResult::HashBlocked
@@ -1297,6 +1330,7 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
policy.strict.cms_der,
policy.strict.name,
policy.resource_validation_mode,
ta_constraints,
)
}
}
@@ -1320,6 +1354,7 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
policy.strict.cms_der,
policy.strict.name,
policy.resource_validation_mode,
ta_constraints,
)
};
match result.outcome {
@@ -1437,6 +1472,7 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
policy.strict.cms_der,
policy.strict.name,
policy.resource_validation_mode,
ta_constraints,
) {
Ok((att, local_output)) => {
stats.aspa_ok += 1;
@@ -1790,6 +1826,10 @@ pub(crate) struct RoaTaskShared {
issuer_effective_ip: Option<Arc<crate::data_model::rc::IpResourceSet>>,
issuer_effective_as: Option<Arc<crate::data_model::rc::AsResourceSet>>,
resource_validation_mode: ResourceValidationMode,
/// Immutable constraints snapshot selected by the owning TAL. Every
/// ROA task for a publication point shares this Arc, so workers do not
/// need a mutable/global policy lookup or a copy of the interval rules.
ta_constraints: Option<Arc<crate::ta_constraints::TaConstraints>>,
}
#[derive(Clone)]
@@ -1911,6 +1951,7 @@ fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskRe
task.strict_cms_der,
task.strict_name,
task.resource_validation_mode,
shared.ta_constraints.as_deref(),
)
.map(|(vrps, local_outputs, cache_object_meta)| RoaTaskOk {
vrps,
@@ -2009,6 +2050,40 @@ pub(crate) fn prepare_publication_point_for_parallel_roa_with_cache<P: Publicati
validation_time: time::OffsetDateTime,
collect_vcir_local_outputs: bool,
roa_cache: RoaValidationCacheInput<'_>,
) -> ParallelObjectsPrepare {
prepare_publication_point_for_parallel_roa_with_cache_and_ta_constraints(
publication_point_id,
publication_point,
policy,
issuer_ca_der,
issuer_ca_rsync_uri,
issuer_effective_ip,
issuer_effective_as,
validation_time,
collect_vcir_local_outputs,
roa_cache,
None,
)
}
/// Prepare a publication point for parallel ROA validation with the
/// immutable constraints snapshot belonging to its TAL. The snapshot is
/// moved into the stage-owned shared payload and therefore remains available
/// to detached ROA workers after the scoped phase-2 stage worker returns.
pub(crate) fn prepare_publication_point_for_parallel_roa_with_cache_and_ta_constraints<
P: PublicationPointData,
>(
publication_point_id: u64,
publication_point: &P,
policy: &Policy,
issuer_ca_der: &[u8],
issuer_ca_rsync_uri: Option<&str>,
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
validation_time: time::OffsetDateTime,
collect_vcir_local_outputs: bool,
roa_cache: RoaValidationCacheInput<'_>,
ta_constraints: Option<Arc<crate::ta_constraints::TaConstraints>>,
) -> ParallelObjectsPrepare {
let manifest_rsync_uri = publication_point.manifest_rsync_uri();
let manifest_bytes = publication_point.manifest_bytes();
@@ -2323,6 +2398,7 @@ pub(crate) fn prepare_publication_point_for_parallel_roa_with_cache<P: Publicati
issuer_effective_ip: issuer_effective_ip.cloned().map(Arc::new),
issuer_effective_as: issuer_effective_as.cloned().map(Arc::new),
resource_validation_mode: policy.resource_validation_mode,
ta_constraints,
}),
validation_time,
collect_vcir_local_outputs,
@@ -2441,6 +2517,7 @@ pub(crate) fn reduce_parallel_roa_stage(
strict_cms_der,
strict_name,
shared.resource_validation_mode,
shared.ta_constraints.as_deref(),
) {
Ok((att, local_output)) => {
stats.aspa_ok += 1;
@@ -2660,6 +2737,9 @@ pub(crate) enum ObjectValidateError {
"EE certificate resources are not a subset of issuer effective resources (RFC 6487 §7.2; RFC 3779)"
)]
EeResourcesNotSubset,
#[error("EE certificate violates locally configured TA constraints: {0}")]
TaConstraints(#[from] crate::ta_constraints::TaConstraintsViolation),
}
pub(crate) fn validate_roa_task_serial(
@@ -2679,6 +2759,7 @@ pub(crate) fn validate_roa_task_serial(
strict_cms_der: bool,
strict_name: bool,
resource_validation_mode: ResourceValidationMode,
ta_constraints: Option<&crate::ta_constraints::TaConstraints>,
) -> RoaTaskResult {
let outcome = process_roa_with_issuer(
task.file,
@@ -2697,6 +2778,7 @@ pub(crate) fn validate_roa_task_serial(
strict_cms_der,
strict_name,
resource_validation_mode,
ta_constraints,
)
.map(|(vrps, local_outputs, cache_object_meta)| RoaTaskOk {
vrps,
@@ -2732,6 +2814,7 @@ fn process_roa_with_issuer(
strict_cms_der: bool,
strict_name: bool,
resource_validation_mode: ResourceValidationMode,
ta_constraints: Option<&crate::ta_constraints::TaConstraints>,
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>, Option<RoaCacheObjectMeta>), ObjectValidateError> {
let _decode = timing
.as_ref()
@@ -2792,6 +2875,10 @@ fn process_roa_with_issuer(
)?;
drop(_subset);
if let Some(ta_constraints) = ta_constraints {
ta_constraints.validate_ee_certificate(&ee.resource_cert)?;
}
let vrps = roa_to_vrps_with_vrs(&roa, ee_vrs.ip.as_ref())?;
let cache_object_meta = RoaCacheObjectMeta {
source_object_uri: file.rsync_uri.clone(),
@@ -2861,6 +2948,7 @@ fn process_roa_with_issuer_parallel_cached(
strict_cms_der: bool,
strict_name: bool,
resource_validation_mode: ResourceValidationMode,
ta_constraints: Option<&crate::ta_constraints::TaConstraints>,
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>, Option<RoaCacheObjectMeta>), ObjectValidateError> {
let _decode = timing
.as_ref()
@@ -2927,6 +3015,10 @@ fn process_roa_with_issuer_parallel_cached(
)?;
drop(_subset);
if let Some(ta_constraints) = ta_constraints {
ta_constraints.validate_ee_certificate(&ee.resource_cert)?;
}
let vrps = roa_to_vrps_with_vrs(&roa, ee_vrs.ip.as_ref())?;
let cache_object_meta = RoaCacheObjectMeta {
source_object_uri: file.rsync_uri.clone(),
@@ -2996,6 +3088,7 @@ fn process_aspa_with_issuer(
strict_cms_der: bool,
strict_name: bool,
resource_validation_mode: ResourceValidationMode,
ta_constraints: Option<&crate::ta_constraints::TaConstraints>,
) -> Result<(AspaAttestation, Option<VcirLocalOutput>), ObjectValidateError> {
let _decode = timing
.as_ref()
@@ -3056,6 +3149,10 @@ fn process_aspa_with_issuer(
)?;
drop(_subset);
if let Some(ta_constraints) = ta_constraints {
ta_constraints.validate_ee_certificate(&ee.resource_cert)?;
}
validate_aspa_customer_in_vrs(&aspa, ee_vrs.asn.as_ref())?;
let attestation = AspaAttestation {
@@ -5505,6 +5602,13 @@ mod tests {
#[test]
fn parallel_stage_roa_tasks_share_stage_owned_payloads() {
let ta_constraints = Arc::new(
crate::ta_constraints::TaConstraints::from_file(
&std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/ta_constraints/afrinic-current-ipv4-deny.constraints"),
)
.expect("load constraints fixture"),
);
let stage = ParallelObjectsStage {
publication_point_id: 7,
shared: Arc::new(RoaTaskShared {
@@ -5535,6 +5639,7 @@ mod tests {
issuer_effective_ip: None,
issuer_effective_as: None,
resource_validation_mode: ResourceValidationMode::default(),
ta_constraints: Some(Arc::clone(&ta_constraints)),
}),
validation_time: OffsetDateTime::now_utc(),
collect_vcir_local_outputs: false,
@@ -5554,6 +5659,14 @@ mod tests {
let tasks = stage.build_roa_tasks();
assert_eq!(tasks.len(), 2);
assert!(Arc::ptr_eq(&tasks[0].shared, &tasks[1].shared));
assert!(Arc::ptr_eq(
tasks[0]
.shared
.ta_constraints
.as_ref()
.expect("task constraints snapshot"),
&ta_constraints
));
}
#[test]

View File

@@ -901,6 +901,10 @@ where
H: Fetcher + Clone + 'static,
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
{
// Constraints are an immutable per-run policy snapshot. The phase-2
// ready-stage binds the snapshot to each CA's TAL and moves an Arc into
// ROA/ASPA worker state, so constrained multi-TAL runs retain the same
// parallel scheduler as unconstrained runs.
let phase2_enabled = phase2_config.is_some();
if tal_inputs.is_empty() {
return Err(RunTreeFromTalError::Replay(

View File

@@ -16,7 +16,8 @@ use crate::validation::manifest::PublicationPointData;
use crate::validation::manifest::PublicationPointSource;
use crate::validation::objects::{
ObjectsOutput, OwnedRoaTask, ParallelObjectsPrepare, ParallelObjectsStage,
RoaValidationCacheInput, prepare_publication_point_for_parallel_roa_with_cache,
RoaValidationCacheInput,
prepare_publication_point_for_parallel_roa_with_cache_and_ta_constraints,
reduce_parallel_roa_stage,
};
use crate::validation::tree::{
@@ -1367,7 +1368,16 @@ fn compute_ready_publication_point_stage(
} else {
RoaValidationCacheInput::disabled()
};
match prepare_publication_point_for_parallel_roa_with_cache(
let ta_constraints = runner
.policy
.ta_constraints
.shared_for_tal(&ready.node.handle.tal_id);
if ta_constraints.is_some() {
if let Some(timing) = runner.timing.as_ref() {
timing.record_count("ta_constraints_parallel_publication_points", 1);
}
}
match prepare_publication_point_for_parallel_roa_with_cache_and_ta_constraints(
ready.node.id,
&fresh_stage.fresh_point,
runner.policy,
@@ -1378,6 +1388,7 @@ fn compute_ready_publication_point_stage(
runner.validation_time,
runner.persist_vcir,
roa_cache,
ta_constraints,
) {
ParallelObjectsPrepare::Complete(mut objects) => {
metrics.prepare_ms = elapsed_ms(prepare_started);

View File

@@ -53,6 +53,7 @@ use crate::validation::objects::{
AspaAttestation, ParallelRoaWorkerPool, RoaValidationCacheInput, RoaValidationCacheView,
RouterKeyPayload, Vrp, process_publication_point_for_issuer_parallel_roa_with_cache_options,
process_publication_point_for_issuer_parallel_roa_with_pool_cache_options,
process_publication_point_for_issuer_with_cache_options_and_ta_constraints,
};
use crate::validation::publication_point::PublicationPointSnapshot;
use crate::validation::tree::{
@@ -1551,12 +1552,30 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
RoaValidationCacheInput::disabled()
};
let objects_processing_started = std::time::Instant::now();
let ta_constraints = self.policy.ta_constraints.for_tal(&ca.tal_id);
let mut objects = {
let _objects_total = self
.timing
.as_ref()
.map(|t| t.span_phase("objects_processing_total"));
if let Some(phase2_pool) = self.parallel_roa_worker_pool.as_ref() {
if let Some(ta_constraints) = ta_constraints {
// This method is the serial/fallback publication-point path. The
// phase-2 scheduler uses the stage-specific parallel prepare path,
// which carries the same immutable per-TAL snapshot into workers.
process_publication_point_for_issuer_with_cache_options_and_ta_constraints(
&fresh_point,
self.policy,
issuer_ca_der.as_ref(),
ca.ca_certificate_rsync_uri.as_deref(),
ca.effective_ip_resources.as_ref(),
ca.effective_as_resources.as_ref(),
self.validation_time,
self.timing.as_ref(),
false,
roa_cache,
Some(ta_constraints),
)
} else if let Some(phase2_pool) = self.parallel_roa_worker_pool.as_ref() {
process_publication_point_for_issuer_parallel_roa_with_pool_cache_options(
&fresh_point,
self.policy,
@@ -1971,13 +1990,13 @@ fn sha256_digest_32(bytes: impl AsRef<[u8]>) -> [u8; 32] {
out
}
fn hash_serialized_parts(parts: &[(&str, Vec<u8>)]) -> [u8; 32] {
fn hash_serialized_parts(parts: &[(&str, &[u8])]) -> [u8; 32] {
let mut hasher = sha2::Sha256::new();
for (label, value) in parts {
hasher.update((label.len() as u64).to_be_bytes());
hasher.update(label.as_bytes());
hasher.update((value.len() as u64).to_be_bytes());
hasher.update(value);
hasher.update(*value);
}
let digest = hasher.finalize();
let mut out = [0u8; 32];
@@ -1991,41 +2010,34 @@ fn cbor_or_debug_bytes<T: serde::Serialize + std::fmt::Debug>(value: &T) -> Vec<
fn ta_context_digest_for_ca(ca: &CaInstanceHandle) -> [u8; 32] {
hash_serialized_parts(&[
("version", b"publication-point-cache-ta-v1".to_vec()),
("tal_id", ca.tal_id.as_bytes().to_vec()),
("version", b"publication-point-cache-ta-v1"),
("tal_id", ca.tal_id.as_bytes()),
])
}
pub(crate) fn ca_validation_context_digest_for_ca(ca: &CaInstanceHandle) -> [u8; 32] {
let parent_manifest = ca
.parent_manifest_rsync_uri
.as_deref()
.unwrap_or("")
.as_bytes();
let effective_ip = cbor_or_debug_bytes(&ca.effective_ip_resources);
let effective_as = cbor_or_debug_bytes(&ca.effective_as_resources);
hash_serialized_parts(&[
(
"version",
b"publication-point-cache-parent-context-v1".to_vec(),
),
("tal_id", ca.tal_id.as_bytes().to_vec()),
(
"parent_manifest",
ca.parent_manifest_rsync_uri
.as_deref()
.unwrap_or("")
.as_bytes()
.to_vec(),
),
(
"effective_ip",
cbor_or_debug_bytes(&ca.effective_ip_resources),
),
(
"effective_as",
cbor_or_debug_bytes(&ca.effective_as_resources),
),
("version", b"publication-point-cache-parent-context-v1"),
("tal_id", ca.tal_id.as_bytes()),
("parent_manifest", parent_manifest),
("effective_ip", effective_ip.as_slice()),
("effective_as", effective_as.as_slice()),
])
}
pub(crate) fn publication_point_cache_policy_fingerprint(policy: &Policy) -> [u8; 32] {
let policy_bytes = cbor_or_debug_bytes(policy);
hash_serialized_parts(&[
("version", b"publication-point-cache-policy-v1".to_vec()),
("policy", cbor_or_debug_bytes(policy)),
("version", b"publication-point-cache-policy-v3"),
("policy", policy_bytes.as_slice()),
("ta_constraints", policy.ta_constraints.fingerprint_bytes()),
])
}
@@ -2084,16 +2096,13 @@ fn child_certificate_cache_key_sha256_hex(
policy_fingerprint: &[u8; 32],
) -> String {
let digest = hash_serialized_parts(&[
("version", b"child-certificate-cache-key-v1".to_vec()),
("child_cert_uri", child_cert_uri.as_bytes().to_vec()),
("child_cert_sha256", child_cert_sha256.to_vec()),
("issuer_ca_sha256", issuer_ca_sha256.to_vec()),
("version", b"child-certificate-cache-key-v1"),
("child_cert_uri", child_cert_uri.as_bytes()),
("child_cert_sha256", child_cert_sha256),
("issuer_ca_sha256", issuer_ca_sha256),
// Keep the persisted cache-key label stable; this change only renames Rust identifiers.
(
"parent_context_digest",
ca_validation_context_digest.to_vec(),
),
("policy_fingerprint", policy_fingerprint.to_vec()),
("parent_context_digest", ca_validation_context_digest),
("policy_fingerprint", policy_fingerprint),
]);
sha256_hex_from_32(&digest)
}
@@ -2986,6 +2995,24 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
match router_result {
Ok(router) => {
if let Some(ta_constraints) = policy.ta_constraints.for_tal(&issuer.tal_id)
{
if let Err(error) =
ta_constraints.validate_ee_certificate(&router.resource_cert)
{
router_error = router_error.saturating_add(1);
audits.push(ObjectAuditEntry {
rsync_uri: f.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&f.sha256),
kind: AuditObjectKind::RouterCertificate,
result: AuditObjectResult::Error,
detail: Some(format!(
"router certificate violates TA constraints: {error}"
)),
});
continue;
}
}
let router_asns = match router_asns_for_resource_mode(
&router.asns,
issuer.effective_as_resources.as_ref(),