20260514 rsync默认限定发布点scope

This commit is contained in:
2026-05-15 17:17:41 +08:00
parent 137b3516d0
commit 615f8709af
4 changed files with 196 additions and 10 deletions

View File

@@ -13,7 +13,7 @@ use crate::audit::{
use crate::ccr::canonical_vrp_prefix;
use crate::fetch::http::{BlockingHttpFetcher, HttpFetcherConfig};
use crate::fetch::rsync::LocalDirRsyncFetcher;
use crate::fetch::rsync_system::{SystemRsyncConfig, SystemRsyncFetcher};
use crate::fetch::rsync_system::{RsyncScopePolicy, SystemRsyncConfig, SystemRsyncFetcher};
use crate::parallel::config::{ParallelPhase1Config, ParallelPhase2Config};
use crate::parallel::types::TalInputSpec;
use crate::policy::{Policy, StrictPolicy};
@@ -99,6 +99,7 @@ pub struct CliArgs {
pub http_timeout_secs: u64,
pub rsync_timeout_secs: u64,
pub rsync_mirror_root: Option<PathBuf>,
pub rsync_scope_policy: RsyncScopePolicy,
pub max_depth: Option<usize>,
pub max_instances: Option<usize>,
@@ -175,6 +176,7 @@ Options:
--http-timeout-secs <n> HTTP fetch timeout seconds (default: 20)
--rsync-timeout-secs <n> rsync I/O timeout seconds (default: 60)
--rsync-mirror-root <path> Persist rsync mirrors under this directory (default: disabled)
--rsync-scope <policy> rsync scope policy: publication-point or module-root (default: publication-point)
--max-depth <n> Max CA instance depth (0 = root only)
--max-instances <n> Max number of CA instances to process
--validation-time <rfc3339> Validation time in RFC3339 (default: now UTC)
@@ -225,6 +227,7 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
let mut http_timeout_secs: u64 = 30;
let mut rsync_timeout_secs: u64 = 30;
let mut rsync_mirror_root: Option<PathBuf> = None;
let mut rsync_scope_policy = RsyncScopePolicy::default();
let mut max_depth: Option<usize> = None;
let mut max_instances: Option<usize> = None;
let mut validation_time: Option<time::OffsetDateTime> = None;
@@ -525,6 +528,11 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
let v = argv.get(i).ok_or("--rsync-mirror-root requires a value")?;
rsync_mirror_root = Some(PathBuf::from(v));
}
"--rsync-scope" => {
i += 1;
let v = argv.get(i).ok_or("--rsync-scope requires a value")?;
rsync_scope_policy = RsyncScopePolicy::parse_cli_value(v)?;
}
"--max-depth" => {
i += 1;
let v = argv.get(i).ok_or("--max-depth requires a value")?;
@@ -838,6 +846,7 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
http_timeout_secs,
rsync_timeout_secs,
rsync_mirror_root,
rsync_scope_policy,
max_depth,
max_instances,
validation_time,
@@ -1712,6 +1721,7 @@ pub fn run(argv: &[String]) -> Result<(), String> {
.unwrap_or_else(|| PathBuf::from("rsync")),
timeout: std::time::Duration::from_secs(args.rsync_timeout_secs.max(1)),
mirror_root: args.rsync_mirror_root.clone(),
scope_policy: args.rsync_scope_policy,
..SystemRsyncConfig::default()
});
run_online_validation_with_fetchers(
@@ -1990,6 +2000,7 @@ mod tests {
assert!(err.contains("Usage:"), "{err}");
assert!(err.contains("--db"), "{err}");
assert!(err.contains("--rsync-mirror-root"), "{err}");
assert!(err.contains("--rsync-scope"), "{err}");
assert!(err.contains("--parallel-phase2-object-workers"), "{err}");
assert!(!err.contains("--parallel-phase1"), "{err}");
assert!(!err.contains("--parallel-phase2 "), "{err}");
@@ -2084,6 +2095,36 @@ mod tests {
assert!(args.report_json_compact);
}
#[test]
fn parse_accepts_rsync_scope_policy() {
let argv = vec![
"rpki".to_string(),
"--db".to_string(),
"db".to_string(),
"--tal-url".to_string(),
"https://example.test/x.tal".to_string(),
"--rsync-scope".to_string(),
"module-root".to_string(),
];
let args = parse_args(&argv).expect("parse args");
assert_eq!(args.rsync_scope_policy, RsyncScopePolicy::ModuleRoot);
}
#[test]
fn parse_rejects_invalid_rsync_scope_policy() {
let argv = vec![
"rpki".to_string(),
"--db".to_string(),
"db".to_string(),
"--tal-url".to_string(),
"https://example.test/x.tal".to_string(),
"--rsync-scope".to_string(),
"wide".to_string(),
];
let err = parse_args(&argv).expect_err("invalid rsync scope should fail");
assert!(err.contains("invalid --rsync-scope"), "{err}");
}
#[test]
fn parse_accepts_strict_policy_list() {
let argv = vec![

View File

@@ -13,6 +13,30 @@ use crate::fetch::rsync::{
RsyncFetchError, RsyncFetchResult, RsyncFetcher, normalize_rsync_base_uri,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RsyncScopePolicy {
PublicationPoint,
ModuleRoot,
}
impl Default for RsyncScopePolicy {
fn default() -> Self {
Self::PublicationPoint
}
}
impl RsyncScopePolicy {
pub fn parse_cli_value(value: &str) -> Result<Self, String> {
match value {
"publication-point" => Ok(Self::PublicationPoint),
"module-root" => Ok(Self::ModuleRoot),
_ => Err(format!(
"invalid --rsync-scope: {value}; expected publication-point or module-root"
)),
}
}
}
#[derive(Clone, Debug)]
pub struct SystemRsyncConfig {
pub rsync_bin: PathBuf,
@@ -27,6 +51,7 @@ pub struct SystemRsyncConfig {
///
/// Note: actual mirror behavior is implemented separately from config wiring.
pub mirror_root: Option<PathBuf>,
pub scope_policy: RsyncScopePolicy,
}
impl Default for SystemRsyncConfig {
@@ -37,6 +62,7 @@ impl Default for SystemRsyncConfig {
timeout: Duration::from_secs(30),
extra_args: Vec::new(),
mirror_root: None,
scope_policy: RsyncScopePolicy::default(),
}
}
}
@@ -268,9 +294,12 @@ impl SystemRsyncFetcher {
}
}
fn module_fetch_uri(&self, rsync_base_uri: &str) -> String {
rsync_module_root_uri(rsync_base_uri)
.unwrap_or_else(|| normalize_rsync_base_uri(rsync_base_uri))
fn scope_fetch_uri(&self, rsync_base_uri: &str) -> String {
match self.config.scope_policy {
RsyncScopePolicy::PublicationPoint => normalize_rsync_base_uri(rsync_base_uri),
RsyncScopePolicy::ModuleRoot => rsync_module_root_uri(rsync_base_uri)
.unwrap_or_else(|| normalize_rsync_base_uri(rsync_base_uri)),
}
}
}
@@ -289,7 +318,7 @@ impl RsyncFetcher for SystemRsyncFetcher {
rsync_base_uri: &str,
visitor: &mut dyn FnMut(String, Vec<u8>) -> Result<(), String>,
) -> RsyncFetchResult<(usize, u64)> {
let base = self.module_fetch_uri(rsync_base_uri);
let base = self.scope_fetch_uri(rsync_base_uri);
let mut count = 0usize;
let mut bytes_total = 0u64;
let mut wrapped = |uri: String, bytes: Vec<u8>| -> Result<(), String> {
@@ -317,7 +346,7 @@ impl RsyncFetcher for SystemRsyncFetcher {
}
fn dedup_key(&self, rsync_base_uri: &str) -> String {
self.module_fetch_uri(rsync_base_uri)
self.scope_fetch_uri(rsync_base_uri)
}
}
@@ -506,8 +535,20 @@ mod tests {
}
#[test]
fn system_rsync_dedup_key_uses_module_root() {
fn system_rsync_dedup_key_uses_publication_point_scope_by_default() {
let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig::default());
assert_eq!(
fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"),
"rsync://example.net/repo/ta/ca/publication-point/"
);
}
#[test]
fn system_rsync_dedup_key_uses_module_root_when_configured() {
let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig {
scope_policy: RsyncScopePolicy::ModuleRoot,
..SystemRsyncConfig::default()
});
assert_eq!(
fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"),
"rsync://example.net/repo/"
@@ -525,6 +566,7 @@ mod tests {
timeout: Duration::from_secs(1),
extra_args: Vec::new(),
mirror_root: None,
scope_policy: RsyncScopePolicy::default(),
});
let e = f
.run_rsync("rsync://example.net/repo/", dst.path())
@@ -538,6 +580,7 @@ mod tests {
timeout: Duration::from_secs(1),
extra_args: Vec::new(),
mirror_root: None,
scope_policy: RsyncScopePolicy::default(),
});
let e = f
.run_rsync("rsync://example.net/repo/", dst.path())
@@ -558,6 +601,7 @@ mod tests {
timeout: Duration::from_secs(1),
extra_args: Vec::new(),
mirror_root: Some(root_file.clone()),
scope_policy: RsyncScopePolicy::default(),
});
let err = fetcher
@@ -585,6 +629,7 @@ mod tests {
timeout: Duration::from_secs(1),
extra_args: Vec::new(),
mirror_root: Some(root.clone()),
scope_policy: RsyncScopePolicy::default(),
});
let err = fetcher
@@ -645,6 +690,7 @@ mod tests {
timeout: Duration::from_secs(60),
extra_args: Vec::new(),
mirror_root: None,
scope_policy: RsyncScopePolicy::default(),
});
fetcher
@@ -683,6 +729,7 @@ mod tests {
timeout: Duration::from_secs(60),
extra_args: Vec::new(),
mirror_root: None,
scope_policy: RsyncScopePolicy::default(),
});
let err = fetcher
@@ -726,6 +773,7 @@ mod tests {
timeout: Duration::from_secs(60),
extra_args: Vec::new(),
mirror_root: None,
scope_policy: RsyncScopePolicy::default(),
});
let err = fetcher
@@ -771,6 +819,7 @@ mod tests {
timeout: Duration::from_secs(30),
extra_args: Vec::new(),
mirror_root: None,
scope_policy: RsyncScopePolicy::default(),
});
fetcher