Полный продакшн-фикс: parity с old bash, drift-recovery, status/runtime и builder separation
This commit is contained in:
@@ -11,6 +11,12 @@ struct FirewallActual {
|
||||
counter_exists: bool,
|
||||
chain_exists: bool,
|
||||
rule_matches: bool,
|
||||
hook_forward_present: bool,
|
||||
policy_accept_present: bool,
|
||||
ct_state_new_present: bool,
|
||||
meter_present: bool,
|
||||
counter_name_used: bool,
|
||||
drop_present: bool,
|
||||
}
|
||||
|
||||
pub fn apply_firewall(cfg: &Config) -> anyhow::Result<()> {
|
||||
@@ -25,7 +31,7 @@ pub fn apply_firewall(cfg: &Config) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
let actual = inspect_firewall(transport.as_ref(), cfg)?;
|
||||
if actual.table_exists && actual.counter_exists && actual.chain_exists && actual.rule_matches {
|
||||
if !firewall_has_drift(&actual) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -57,23 +63,44 @@ pub fn apply_firewall(cfg: &Config) -> anyhow::Result<()> {
|
||||
)?;
|
||||
|
||||
let verify = inspect_firewall(transport.as_ref(), cfg)?;
|
||||
if !(verify.table_exists && verify.counter_exists && verify.chain_exists && verify.rule_matches)
|
||||
{
|
||||
anyhow::bail!("firewall verify-after-apply failed");
|
||||
if firewall_has_drift(&verify) {
|
||||
anyhow::bail!(
|
||||
"firewall verify-after-apply failed: {}",
|
||||
firewall_drift_reason(&verify)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inspect_firewall(transport: &dyn NsTransport, cfg: &Config) -> anyhow::Result<FirewallActual> {
|
||||
let table = inspect_cmd(
|
||||
transport,
|
||||
&["nft", "list", "table", "inet", "awg2_harness"],
|
||||
"firewall table",
|
||||
)?;
|
||||
let table = match transport.run(&["nft", "list", "table", "inet", "awg2_harness"]) {
|
||||
Ok(out) => out,
|
||||
Err(e) if is_nft_not_found(&e.to_string()) => {
|
||||
return Ok(FirewallActual {
|
||||
table_exists: false,
|
||||
counter_exists: false,
|
||||
chain_exists: false,
|
||||
rule_matches: false,
|
||||
hook_forward_present: false,
|
||||
policy_accept_present: false,
|
||||
ct_state_new_present: false,
|
||||
meter_present: false,
|
||||
counter_name_used: false,
|
||||
drop_present: false,
|
||||
});
|
||||
}
|
||||
Err(e) => return Err(anyhow::anyhow!("nft table inspect failed: {e}")),
|
||||
};
|
||||
|
||||
let table_exists = !table.trim().is_empty();
|
||||
let counter_exists = table.contains("counter antitorrent_dropped_v4");
|
||||
let chain_exists = table.contains("chain forward_antitorrent");
|
||||
let hook_forward_present = table.contains("hook forward");
|
||||
let policy_accept_present = table.contains("policy accept");
|
||||
let ct_state_new_present = table.contains("ct state new");
|
||||
let meter_present = table.contains("meter awg2_peer_connlimit_v4");
|
||||
let counter_name_used = table.contains("counter name antitorrent_dropped_v4");
|
||||
let drop_present = table.contains(" drop") || table.contains("\ndrop");
|
||||
|
||||
let rule_matches = firewall_rule_contract_matches(
|
||||
&table,
|
||||
@@ -86,6 +113,12 @@ fn inspect_firewall(transport: &dyn NsTransport, cfg: &Config) -> anyhow::Result
|
||||
counter_exists,
|
||||
chain_exists,
|
||||
rule_matches,
|
||||
hook_forward_present,
|
||||
policy_accept_present,
|
||||
ct_state_new_present,
|
||||
meter_present,
|
||||
counter_name_used,
|
||||
drop_present,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -179,10 +212,59 @@ fn run_allow_notfound(transport: &dyn NsTransport, args: &[&str]) -> anyhow::Res
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect_cmd(transport: &dyn NsTransport, args: &[&str], what: &str) -> anyhow::Result<String> {
|
||||
transport
|
||||
.run(args)
|
||||
.map_err(|e| anyhow::anyhow!("{what} inspect failed: {e}"))
|
||||
fn is_nft_not_found(msg: &str) -> bool {
|
||||
let s = msg.to_ascii_lowercase();
|
||||
s.contains("no such file")
|
||||
|| s.contains("not found")
|
||||
|| s.contains("not exist")
|
||||
|| s.contains("no such table")
|
||||
}
|
||||
|
||||
fn firewall_has_drift(actual: &FirewallActual) -> bool {
|
||||
!(actual.table_exists
|
||||
&& actual.counter_exists
|
||||
&& actual.chain_exists
|
||||
&& actual.rule_matches
|
||||
&& actual.hook_forward_present
|
||||
&& actual.policy_accept_present
|
||||
&& actual.ct_state_new_present
|
||||
&& actual.meter_present
|
||||
&& actual.counter_name_used
|
||||
&& actual.drop_present)
|
||||
}
|
||||
|
||||
fn firewall_drift_reason(actual: &FirewallActual) -> String {
|
||||
if !actual.table_exists {
|
||||
return "missing nft table".to_string();
|
||||
}
|
||||
if !actual.counter_exists {
|
||||
return "missing nft counter".to_string();
|
||||
}
|
||||
if !actual.chain_exists {
|
||||
return "missing nft chain".to_string();
|
||||
}
|
||||
if !actual.hook_forward_present {
|
||||
return "missing nft forward hook".to_string();
|
||||
}
|
||||
if !actual.policy_accept_present {
|
||||
return "missing nft policy accept".to_string();
|
||||
}
|
||||
if !actual.ct_state_new_present {
|
||||
return "missing ct state new".to_string();
|
||||
}
|
||||
if !actual.meter_present {
|
||||
return "missing nft meter".to_string();
|
||||
}
|
||||
if !actual.counter_name_used {
|
||||
return "missing nft counter name usage".to_string();
|
||||
}
|
||||
if !actual.drop_present {
|
||||
return "missing drop verdict".to_string();
|
||||
}
|
||||
if !actual.rule_matches {
|
||||
return "wrong anti-torrent threshold".to_string();
|
||||
}
|
||||
"unknown firewall drift".to_string()
|
||||
}
|
||||
|
||||
fn firewall_rule_contract_matches(table: &str, cidr: &str, max_conn: u32) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user