use serde::Deserialize;
#[expect(clippy::struct_excessive_bools, reason = "per-check skip flags")]
#[derive(Clone, Debug, Default, Deserialize, serde::Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SkipPipelineChecks {
pub conditional_security: bool,
pub conflicting_cluster_selectors: bool,
pub duplicate_load_balancers: bool,
pub duplicate_rewrite_filters: bool,
pub duplicate_routers: bool,
pub lb_without_router: bool,
pub misaligned_clusters: bool,
pub unreachable_filters: bool,
}
impl SkipPipelineChecks {
pub fn all() -> Self {
Self {
conditional_security: true,
conflicting_cluster_selectors: true,
duplicate_load_balancers: true,
duplicate_rewrite_filters: true,
duplicate_routers: true,
lb_without_router: true,
misaligned_clusters: true,
unreachable_filters: true,
}
}
pub fn any(&self) -> bool {
self.conditional_security
|| self.conflicting_cluster_selectors
|| self.duplicate_load_balancers
|| self.duplicate_rewrite_filters
|| self.duplicate_routers
|| self.lb_without_router
|| self.misaligned_clusters
|| self.unreachable_filters
}
}
macro_rules! insecure_flags {
($opts:expr; $($field:ident: $description:literal,)* ; $($skipped:ident),*) => {{
#[expect(clippy::unneeded_field_pattern, reason = "`..` would defeat the exhaustiveness check")]
let InsecureOptions { $($field,)* $($skipped: _,)* } = *$opts;
[$(InsecureFlag { name: stringify!($field), active: $field, description: $description },)*]
}};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct InsecureFlag {
pub name: &'static str,
pub active: bool,
pub description: &'static str,
}
#[expect(clippy::struct_excessive_bools, reason = "security override flags")]
#[derive(Clone, Debug, Default, Deserialize, serde::Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct InsecureOptions {
pub allow_open_security_filters: bool,
pub allow_private_endpoints: bool,
pub allow_private_health_checks: bool,
pub allow_private_upstreams: bool,
pub allow_public_admin: bool,
pub allow_root: bool,
pub allow_tls_no_verify: bool,
pub allow_tls_without_sni: bool,
pub allow_unbounded_body: bool,
pub csrf_log_only: bool,
pub skip_pipeline_checks: SkipPipelineChecks,
pub skip_pipeline_validation: bool,
}
impl InsecureOptions {
pub fn effective_pipeline_checks(&self) -> SkipPipelineChecks {
if self.skip_pipeline_validation {
SkipPipelineChecks::all()
} else {
self.skip_pipeline_checks.clone()
}
}
pub fn flags(&self) -> [InsecureFlag; 11] {
insecure_flags!(self;
allow_open_security_filters: "open failure_mode allowed on security filters",
allow_private_endpoints: "SSRF-sensitive endpoint addresses allowed",
allow_private_health_checks: "loopback health checks allowed",
allow_private_upstreams: "runtime SSRF protection disabled for upstream connections",
allow_public_admin: "admin may bind non-loopback addresses",
allow_root: "running as root (UID 0) allowed",
allow_tls_no_verify: "upstream TLS certificate verification may be disabled",
allow_tls_without_sni: "TLS hostname verification weakened",
allow_unbounded_body: "body size ceiling relaxed",
csrf_log_only: "CSRF violations logged, not rejected",
skip_pipeline_validation: "pipeline errors demoted to warnings",
; skip_pipeline_checks)
}
}
#[cfg(test)]
#[expect(clippy::allow_attributes, reason = "blanket test suppressions")]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::needless_raw_strings,
clippy::needless_raw_string_hashes,
clippy::too_many_lines,
reason = "tests use unwrap/expect/indexing/raw strings for brevity"
)]
mod tests {
use super::*;
#[test]
fn all_flags_default_to_false() {
let opts = InsecureOptions::default();
assert!(
!opts.allow_open_security_filters,
"allow_open_security_filters should default to false"
);
assert!(
!opts.allow_private_endpoints,
"allow_private_endpoints should default to false"
);
assert!(
!opts.allow_private_health_checks,
"allow_private_health_checks should default to false"
);
assert!(
!opts.allow_private_upstreams,
"allow_private_upstreams should default to false"
);
assert!(!opts.allow_public_admin, "allow_public_admin should default to false");
assert!(!opts.allow_root, "allow_root should default to false");
assert!(!opts.allow_tls_no_verify, "allow_tls_no_verify should default to false");
assert!(
!opts.allow_tls_without_sni,
"allow_tls_without_sni should default to false"
);
assert!(
!opts.allow_unbounded_body,
"allow_unbounded_body should default to false"
);
assert!(!opts.csrf_log_only, "csrf_log_only should default to false");
assert!(
!opts.skip_pipeline_validation,
"skip_pipeline_validation should default to false"
);
assert!(
!opts.skip_pipeline_checks.any(),
"skip_pipeline_checks should all default to false"
);
}
#[test]
fn deserializes_partial_overrides() {
let yaml = "allow_root: true\nskip_pipeline_validation: true\n";
let opts: InsecureOptions = serde_yaml::from_str(yaml).unwrap();
assert!(opts.allow_root, "allow_root should be true");
assert!(opts.skip_pipeline_validation, "skip_pipeline_validation should be true");
assert!(!opts.allow_public_admin, "allow_public_admin should still be false");
}
#[test]
fn deserializes_empty_to_defaults() {
let opts: InsecureOptions = serde_yaml::from_str("{}").unwrap();
assert!(!opts.allow_root, "empty YAML should produce defaults");
}
#[test]
fn skip_pipeline_checks_all_sets_every_flag() {
let checks = SkipPipelineChecks::all();
assert!(checks.conditional_security, "conditional_security should be true");
assert!(
checks.conflicting_cluster_selectors,
"conflicting_cluster_selectors should be true"
);
assert!(
checks.duplicate_load_balancers,
"duplicate_load_balancers should be true"
);
assert!(
checks.duplicate_rewrite_filters,
"duplicate_rewrite_filters should be true"
);
assert!(checks.duplicate_routers, "duplicate_routers should be true");
assert!(checks.lb_without_router, "lb_without_router should be true");
assert!(checks.misaligned_clusters, "misaligned_clusters should be true");
assert!(checks.unreachable_filters, "unreachable_filters should be true");
}
#[test]
fn skip_pipeline_checks_any_detects_single_flag() {
let mut checks = SkipPipelineChecks::default();
assert!(!checks.any(), "default checks should have no flags set");
checks.duplicate_routers = true;
assert!(checks.any(), "any() should detect single flag");
}
#[test]
fn effective_pipeline_checks_blanket_overrides_granular() {
let opts = InsecureOptions {
skip_pipeline_validation: true,
..Default::default()
};
let checks = opts.effective_pipeline_checks();
assert!(checks.lb_without_router, "blanket flag should set all checks");
assert!(checks.conditional_security, "blanket flag should set all checks");
assert!(checks.misaligned_clusters, "blanket flag should set all checks");
}
#[test]
fn effective_pipeline_checks_uses_granular_when_blanket_off() {
let opts = InsecureOptions {
skip_pipeline_checks: SkipPipelineChecks {
duplicate_routers: true,
..Default::default()
},
..Default::default()
};
let checks = opts.effective_pipeline_checks();
assert!(checks.duplicate_routers, "granular flag should be preserved");
assert!(!checks.lb_without_router, "other flags should remain false");
}
#[test]
fn deserializes_granular_pipeline_checks() {
let yaml = "skip_pipeline_checks:\n duplicate_routers: true\n misaligned_clusters: true\n";
let opts: InsecureOptions = serde_yaml::from_str(yaml).unwrap();
assert!(
opts.skip_pipeline_checks.duplicate_routers,
"duplicate_routers should be true"
);
assert!(
opts.skip_pipeline_checks.misaligned_clusters,
"misaligned_clusters should be true"
);
assert!(
!opts.skip_pipeline_checks.lb_without_router,
"lb_without_router should remain false"
);
assert!(!opts.skip_pipeline_validation, "blanket flag should remain false");
}
#[test]
fn flags_cover_every_bool_field_in_declaration_order() {
let value = serde_yaml::to_value(InsecureOptions::default()).unwrap();
let fields = value
.as_mapping()
.expect("InsecureOptions should serialize to a mapping");
let bool_fields: Vec<&str> = fields
.iter()
.filter(|(_, field)| field.is_bool())
.map(|(key, _)| key.as_str().unwrap())
.collect();
let names: Vec<&str> = InsecureOptions::default()
.flags()
.iter()
.map(|flag| flag.name)
.collect();
assert_eq!(
names, bool_fields,
"flags() must list every bool field in declaration order"
);
}
#[test]
fn flags_all_active_after_all_true_round_trip() {
let mut value = serde_yaml::to_value(InsecureOptions::default()).unwrap();
let fields = value
.as_mapping_mut()
.expect("InsecureOptions should serialize to a mapping");
for field in fields.values_mut().filter(|field| field.is_bool()) {
*field = serde_yaml::Value::Bool(true);
}
let all: InsecureOptions = serde_yaml::from_value(value).unwrap();
for flag in all.flags() {
assert!(flag.active, "{} should be active in an all-true config", flag.name);
}
}
#[test]
fn flags_all_inactive_by_default() {
for flag in InsecureOptions::default().flags() {
assert!(!flag.active, "{} should be inactive by default", flag.name);
}
}
#[test]
fn each_flag_reads_its_own_field() {
for name in InsecureOptions::default().flags().map(|flag| flag.name) {
let opts: InsecureOptions = serde_yaml::from_str(&format!("{name}: true")).unwrap();
let active: Vec<&str> = opts
.flags()
.iter()
.filter(|flag| flag.active)
.map(|flag| flag.name)
.collect();
assert_eq!(active, vec![name], "setting only {name} should activate only {name}");
}
}
#[test]
fn flags_ignore_granular_pipeline_checks() {
let opts = InsecureOptions {
skip_pipeline_checks: SkipPipelineChecks::all(),
..Default::default()
};
assert!(
opts.flags().iter().all(|flag| !flag.active),
"granular pipeline checks are reported separately, not as top-level flags"
);
}
#[test]
fn flags_have_descriptions() {
for flag in InsecureOptions::default().flags() {
assert!(!flag.description.is_empty(), "{} should have a description", flag.name);
}
}
#[test]
fn rejects_unknown_skip_pipeline_checks_field() {
let yaml = "skip_pipeline_checks:\n nonexistent_check: true\n";
let err = serde_yaml::from_str::<InsecureOptions>(yaml).unwrap_err();
assert!(
err.to_string().contains("nonexistent_check"),
"unknown skip_pipeline_checks field should be rejected: {err}"
);
}
}