fix(targets): explain webhook outbound allowlist failures (#5616)

This commit is contained in:
Zhengchao An
2026-08-02 22:33:05 +08:00
committed by GitHub
parent 3f716746cf
commit 54d8c02a2f
4 changed files with 66 additions and 21 deletions
+17 -2
View File
@@ -23,7 +23,7 @@ use rustfs_config::{
NATS_TLS_CLIENT_KEY, NATS_TOKEN, NATS_USERNAME, PULSAR_AUTH_TOKEN, PULSAR_PASSWORD, PULSAR_QUEUE_DIR, PULSAR_TLS_CA,
PULSAR_TOPIC, PULSAR_USERNAME,
};
use rustfs_utils::egress::OutboundPolicy;
use rustfs_utils::egress::{ENV_OUTBOUND_ALLOW_ORIGINS, OutboundPolicy, OutboundPolicyError};
use std::collections::HashSet;
use std::path::Path;
use std::str::FromStr;
@@ -223,7 +223,22 @@ pub(super) fn validate_outbound_http_url(value: &Url, field_label: &str) -> Resu
OutboundPolicy::from_env_cached().map_err(|err| TargetError::Configuration(format!("invalid outbound policy: {err}")))?;
policy
.validate_url(value)
.map_err(|e| TargetError::Configuration(format!("{field_label} is not allowed: {e}")))
.map_err(|err| TargetError::Configuration(format_outbound_http_url_error(field_label, value, &err)))
}
/// Formats an outbound-policy rejection and adds an exact-origin hint only when that origin can override the rejection.
pub fn format_outbound_http_url_error(field_label: &str, value: &Url, err: &OutboundPolicyError) -> String {
let base = format!("{field_label} is not allowed: {err}");
let origin = value.origin().ascii_serialization();
let can_allow_origin =
OutboundPolicy::from_allowed_origins(&origin).is_ok_and(|allowlisted| allowlisted.validate_url(value).is_ok());
if can_allow_origin {
format!(
"{base}; add {origin} to {ENV_OUTBOUND_ALLOW_ORIGINS} (comma-separated) and restart RustFS to allow this operator-owned endpoint (origin only, no path)"
)
} else {
base
}
}
#[cfg(test)]
+2
View File
@@ -17,6 +17,8 @@ mod instance;
mod loader;
mod target_args;
pub use common::format_outbound_http_url_error;
pub use instance::{
LegacyTargetInstanceDescriptor, TargetInstanceSourceClass, TargetInstanceSourceHints, TargetPluginInstance,
TargetPluginInstanceCompatDescriptor, TargetPluginInstanceRecord, normalize_legacy_target_instances,
+21 -2
View File
@@ -624,6 +624,7 @@ mod tests {
REDIS_CHANNEL, REDIS_CONNECTION_TIMEOUT, REDIS_MAX_RETRY_DELAY, REDIS_MIN_RETRY_DELAY, REDIS_PIPELINE_BUFFER_SIZE,
REDIS_RECONNECT_RETRY_ATTEMPTS, REDIS_RESPONSE_TIMEOUT, REDIS_TLS_ALLOW_INSECURE, REDIS_URL, WEBHOOK_ENDPOINT,
};
use rustfs_utils::egress::ENV_OUTBOUND_ALLOW_ORIGINS;
fn absolute_test_path(path: &str) -> String {
std::env::temp_dir().join(path).to_string_lossy().into_owned()
@@ -780,11 +781,29 @@ mod tests {
#[test]
fn build_webhook_args_rejects_loopback_endpoint() {
let mut config = webhook_base_config();
config.insert(WEBHOOK_ENDPOINT.to_string(), "https://127.0.0.1/hook".to_string());
config.insert(WEBHOOK_ENDPOINT.to_string(), "https://127.0.0.1:8443/hook".to_string());
let err = build_webhook_args(&config, "/tmp/webhook-queue", TargetType::NotifyEvent)
.expect_err("loopback endpoint should be rejected");
assert!(err.to_string().contains("not allowed"));
let message = err.to_string();
assert!(message.contains(&format!("add https://127.0.0.1:8443 to {ENV_OUTBOUND_ALLOW_ORIGINS}")));
assert!(message.contains("loopback address"));
assert!(message.contains("comma-separated"));
assert!(message.contains("restart RustFS"));
assert!(!message.contains("/hook"));
assert!(message.contains("origin only, no path"));
}
#[test]
fn build_webhook_args_does_not_offer_allowlist_for_metadata_endpoint() {
let mut config = webhook_base_config();
config.insert(WEBHOOK_ENDPOINT.to_string(), "http://169.254.169.254/latest/meta-data".to_string());
let err = build_webhook_args(&config, "/tmp/webhook-queue", TargetType::NotifyEvent)
.expect_err("metadata endpoint should be rejected");
let message = err.to_string();
assert!(message.contains("not allowed"));
assert!(!message.contains(ENV_OUTBOUND_ALLOW_ORIGINS));
}
#[test]