20260707 支持本地HTTP root证书
This commit is contained in:
parent
b0f76738e5
commit
63ac3ee254
19
src/cli.rs
19
src/cli.rs
@ -163,6 +163,7 @@ pub struct CliArgs {
|
|||||||
pub rsync_command: Option<PathBuf>,
|
pub rsync_command: Option<PathBuf>,
|
||||||
|
|
||||||
pub http_timeout_secs: u64,
|
pub http_timeout_secs: u64,
|
||||||
|
pub http_root_cert_paths: Vec<PathBuf>,
|
||||||
pub rsync_timeout_secs: u64,
|
pub rsync_timeout_secs: u64,
|
||||||
pub rsync_mirror_root: Option<PathBuf>,
|
pub rsync_mirror_root: Option<PathBuf>,
|
||||||
pub rsync_scope_policy: RsyncScopePolicy,
|
pub rsync_scope_policy: RsyncScopePolicy,
|
||||||
@ -254,6 +255,7 @@ Options:
|
|||||||
--disable-rrdp Disable RRDP and synchronize only via rsync
|
--disable-rrdp Disable RRDP and synchronize only via rsync
|
||||||
--rsync-command <path> Use this rsync command instead of the default rsync binary
|
--rsync-command <path> Use this rsync command instead of the default rsync binary
|
||||||
--http-timeout-secs <n> HTTP fetch timeout seconds (default: 20)
|
--http-timeout-secs <n> HTTP fetch timeout seconds (default: 20)
|
||||||
|
--http-root-cert <path> Extra PEM root certificate trusted by HTTPS fetches (repeatable)
|
||||||
--rsync-timeout-secs <n> rsync I/O timeout seconds (default: 60)
|
--rsync-timeout-secs <n> rsync I/O timeout seconds (default: 60)
|
||||||
--rsync-mirror-root <path> Persist rsync mirrors under this directory (default: disabled)
|
--rsync-mirror-root <path> Persist rsync mirrors under this directory (default: disabled)
|
||||||
--rsync-scope <policy> rsync scope policy: host, publication-point, or module-root (default: module-root)
|
--rsync-scope <policy> rsync scope policy: host, publication-point, or module-root (default: module-root)
|
||||||
@ -313,6 +315,7 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
|||||||
let mut disable_rrdp: bool = false;
|
let mut disable_rrdp: bool = false;
|
||||||
let mut rsync_command: Option<PathBuf> = None;
|
let mut rsync_command: Option<PathBuf> = None;
|
||||||
let mut http_timeout_secs: u64 = 30;
|
let mut http_timeout_secs: u64 = 30;
|
||||||
|
let mut http_root_cert_paths: Vec<PathBuf> = Vec::new();
|
||||||
let mut rsync_timeout_secs: u64 = 30;
|
let mut rsync_timeout_secs: u64 = 30;
|
||||||
let mut rsync_mirror_root: Option<PathBuf> = None;
|
let mut rsync_mirror_root: Option<PathBuf> = None;
|
||||||
let mut rsync_scope_policy = RsyncScopePolicy::default();
|
let mut rsync_scope_policy = RsyncScopePolicy::default();
|
||||||
@ -636,6 +639,11 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
|||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map_err(|_| format!("invalid --http-timeout-secs: {v}"))?;
|
.map_err(|_| format!("invalid --http-timeout-secs: {v}"))?;
|
||||||
}
|
}
|
||||||
|
"--http-root-cert" => {
|
||||||
|
i += 1;
|
||||||
|
let v = argv.get(i).ok_or("--http-root-cert requires a value")?;
|
||||||
|
http_root_cert_paths.push(PathBuf::from(v));
|
||||||
|
}
|
||||||
"--rsync-timeout-secs" => {
|
"--rsync-timeout-secs" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let v = argv.get(i).ok_or("--rsync-timeout-secs requires a value")?;
|
let v = argv.get(i).ok_or("--rsync-timeout-secs requires a value")?;
|
||||||
@ -977,6 +985,7 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
|||||||
disable_rrdp,
|
disable_rrdp,
|
||||||
rsync_command,
|
rsync_command,
|
||||||
http_timeout_secs,
|
http_timeout_secs,
|
||||||
|
http_root_cert_paths,
|
||||||
rsync_timeout_secs,
|
rsync_timeout_secs,
|
||||||
rsync_mirror_root,
|
rsync_mirror_root,
|
||||||
rsync_scope_policy,
|
rsync_scope_policy,
|
||||||
@ -2001,6 +2010,14 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
let validation_time = args
|
let validation_time = args
|
||||||
.validation_time
|
.validation_time
|
||||||
.unwrap_or_else(time::OffsetDateTime::now_utc);
|
.unwrap_or_else(time::OffsetDateTime::now_utc);
|
||||||
|
let http_root_certificates_pem = args
|
||||||
|
.http_root_cert_paths
|
||||||
|
.iter()
|
||||||
|
.map(|path| {
|
||||||
|
std::fs::read(path)
|
||||||
|
.map_err(|e| format!("read HTTP root certificate failed: {}: {e}", path.display()))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let store = if args.raw_store_db.is_some() || args.repo_bytes_db.is_some() {
|
let store = if args.raw_store_db.is_some() || args.repo_bytes_db.is_some() {
|
||||||
Arc::new(
|
Arc::new(
|
||||||
@ -2221,6 +2238,7 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
} else if let Some(dir) = args.rsync_local_dir.as_ref() {
|
} else if let Some(dir) = args.rsync_local_dir.as_ref() {
|
||||||
let http = BlockingHttpFetcher::new(HttpFetcherConfig {
|
let http = BlockingHttpFetcher::new(HttpFetcherConfig {
|
||||||
timeout: std::time::Duration::from_secs(args.http_timeout_secs.max(1)),
|
timeout: std::time::Duration::from_secs(args.http_timeout_secs.max(1)),
|
||||||
|
extra_root_certificates_pem: http_root_certificates_pem.clone(),
|
||||||
..HttpFetcherConfig::default()
|
..HttpFetcherConfig::default()
|
||||||
})
|
})
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
@ -2239,6 +2257,7 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
} else {
|
} else {
|
||||||
let http = BlockingHttpFetcher::new(HttpFetcherConfig {
|
let http = BlockingHttpFetcher::new(HttpFetcherConfig {
|
||||||
timeout: std::time::Duration::from_secs(args.http_timeout_secs.max(1)),
|
timeout: std::time::Duration::from_secs(args.http_timeout_secs.max(1)),
|
||||||
|
extra_root_certificates_pem: http_root_certificates_pem.clone(),
|
||||||
..HttpFetcherConfig::default()
|
..HttpFetcherConfig::default()
|
||||||
})
|
})
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|||||||
@ -289,6 +289,29 @@ fn parse_accepts_host_rsync_scope_policy() {
|
|||||||
assert_eq!(args.rsync_scope_policy, RsyncScopePolicy::Host);
|
assert_eq!(args.rsync_scope_policy, RsyncScopePolicy::Host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_accepts_repeatable_http_root_cert_paths() {
|
||||||
|
let argv = vec![
|
||||||
|
"rpki".to_string(),
|
||||||
|
"--db".to_string(),
|
||||||
|
"db".to_string(),
|
||||||
|
"--tal-url".to_string(),
|
||||||
|
"https://example.test/x.tal".to_string(),
|
||||||
|
"--http-root-cert".to_string(),
|
||||||
|
"local-ca-1.pem".to_string(),
|
||||||
|
"--http-root-cert".to_string(),
|
||||||
|
"local-ca-2.pem".to_string(),
|
||||||
|
];
|
||||||
|
let args = parse_args(&argv).expect("parse args");
|
||||||
|
assert_eq!(
|
||||||
|
args.http_root_cert_paths,
|
||||||
|
vec![
|
||||||
|
PathBuf::from("local-ca-1.pem"),
|
||||||
|
PathBuf::from("local-ca-2.pem")
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_rejects_invalid_rsync_scope_policy() {
|
fn parse_rejects_invalid_rsync_scope_policy() {
|
||||||
let argv = vec![
|
let argv = vec![
|
||||||
|
|||||||
@ -29,6 +29,8 @@ pub struct HttpFetcherConfig {
|
|||||||
/// Larger timeout used for RRDP snapshot / delta bodies.
|
/// Larger timeout used for RRDP snapshot / delta bodies.
|
||||||
pub large_body_timeout: Duration,
|
pub large_body_timeout: Duration,
|
||||||
pub user_agent: String,
|
pub user_agent: String,
|
||||||
|
/// Extra PEM trust anchors for HTTPS transport tests or private RRDP endpoints.
|
||||||
|
pub extra_root_certificates_pem: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HttpFetcherConfig {
|
impl Default for HttpFetcherConfig {
|
||||||
@ -38,6 +40,7 @@ impl Default for HttpFetcherConfig {
|
|||||||
timeout: Duration::from_secs(30),
|
timeout: Duration::from_secs(30),
|
||||||
large_body_timeout: Duration::from_secs(180),
|
large_body_timeout: Duration::from_secs(180),
|
||||||
user_agent: "rpki-dev/0.1 (stage2)".to_string(),
|
user_agent: "rpki-dev/0.1 (stage2)".to_string(),
|
||||||
|
extra_root_certificates_pem: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,22 +64,28 @@ impl BlockingHttpFetcher {
|
|||||||
let short_timeout = config.timeout;
|
let short_timeout = config.timeout;
|
||||||
let large_body_timeout = std::cmp::max(config.large_body_timeout, config.timeout);
|
let large_body_timeout = std::cmp::max(config.large_body_timeout, config.timeout);
|
||||||
let connect_timeout = std::cmp::min(config.connect_timeout, config.timeout);
|
let connect_timeout = std::cmp::min(config.connect_timeout, config.timeout);
|
||||||
let short_client = Client::builder()
|
let short_client = Self::client_builder(
|
||||||
.connect_timeout(connect_timeout)
|
&config,
|
||||||
.timeout(config.timeout)
|
connect_timeout,
|
||||||
.user_agent(config.user_agent.clone())
|
config.timeout,
|
||||||
|
config.user_agent.clone(),
|
||||||
|
)?
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let large_body_client = Client::builder()
|
let large_body_client = Self::client_builder(
|
||||||
.connect_timeout(connect_timeout)
|
&config,
|
||||||
.timeout(large_body_timeout)
|
connect_timeout,
|
||||||
.user_agent(config.user_agent)
|
large_body_timeout,
|
||||||
|
config.user_agent.clone(),
|
||||||
|
)?
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let retry_short_client = Client::builder()
|
let retry_short_client = Self::client_builder(
|
||||||
.connect_timeout(Duration::from_secs(1))
|
&config,
|
||||||
.timeout(Duration::from_secs(1))
|
Duration::from_secs(1),
|
||||||
.user_agent("rpki-dev/0.1 (stage2)")
|
Duration::from_secs(1),
|
||||||
|
"rpki-dev/0.1 (stage2)".to_string(),
|
||||||
|
)?
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -88,11 +97,29 @@ impl BlockingHttpFetcher {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn client_builder(
|
||||||
|
config: &HttpFetcherConfig,
|
||||||
|
connect_timeout: Duration,
|
||||||
|
timeout: Duration,
|
||||||
|
user_agent: String,
|
||||||
|
) -> Result<reqwest::blocking::ClientBuilder, String> {
|
||||||
|
let mut builder = Client::builder()
|
||||||
|
.connect_timeout(connect_timeout)
|
||||||
|
.timeout(timeout)
|
||||||
|
.user_agent(user_agent);
|
||||||
|
for (idx, pem) in config.extra_root_certificates_pem.iter().enumerate() {
|
||||||
|
let certificate = reqwest::Certificate::from_pem(pem)
|
||||||
|
.map_err(|e| format!("parse HTTP root certificate #{idx} failed: {e}"))?;
|
||||||
|
builder = builder.add_root_certificate(certificate);
|
||||||
|
}
|
||||||
|
Ok(builder)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fetch_bytes(&self, uri: &str) -> Result<Vec<u8>, String> {
|
pub fn fetch_bytes(&self, uri: &str) -> Result<Vec<u8>, String> {
|
||||||
let started = std::time::Instant::now();
|
let started = std::time::Instant::now();
|
||||||
let (client, timeout_profile, timeout_value) = self.client_for_uri(uri);
|
let (client, timeout_profile, timeout_value) = self.client_for_uri(uri);
|
||||||
let resp = client.get(uri).send().map_err(|e| {
|
let resp = client.get(uri).send().map_err(|e| {
|
||||||
let msg = format!("http request failed: {e}");
|
let msg = format!("http request failed: {e:?}");
|
||||||
crate::progress_log::emit(
|
crate::progress_log::emit(
|
||||||
"http_fetch_failed",
|
"http_fetch_failed",
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
@ -222,7 +249,7 @@ impl Fetcher for BlockingHttpFetcher {
|
|||||||
let started = std::time::Instant::now();
|
let started = std::time::Instant::now();
|
||||||
let (client, timeout_profile, timeout_value) = self.client_for_uri(uri);
|
let (client, timeout_profile, timeout_value) = self.client_for_uri(uri);
|
||||||
let resp = client.get(uri).send().map_err(|e| {
|
let resp = client.get(uri).send().map_err(|e| {
|
||||||
let msg = format!("http request failed: {e}");
|
let msg = format!("http request failed: {e:?}");
|
||||||
crate::progress_log::emit(
|
crate::progress_log::emit(
|
||||||
"http_fetch_failed",
|
"http_fetch_failed",
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user