Skip to main content

zlayer_overlay/firewall/
mod.rs

1//! Inbound firewall-rule management for the overlay + API + Raft ports.
2//!
3//! On Windows this module installs three inbound-allow rules in Windows
4//! Defender Firewall via the `INetFwPolicy2` COM API:
5//!
6//! - `ZLayer Overlay (UDP)` — the Wintun/boringtun listen port
7//! - `ZLayer API (TCP)`     — the daemon HTTP/gRPC port
8//! - `ZLayer Raft (TCP)`    — the scheduler Raft port
9//!
10//! Rules are scoped to the **Private + Domain** profiles only. Public profile
11//! is intentionally excluded so laptops on untrusted networks (coffee-shop
12//! Wi-Fi, airport, etc.) do not start accepting inbound cluster traffic.
13//!
14//! [`ensure_overlay_rules`] is idempotent: if a rule with the same name
15//! already exists it is left in place rather than duplicated.
16//!
17//! On non-Windows targets both functions are no-ops that return `Ok(())`.
18//! Linux nodes are expected to manage their own `iptables`/`nftables` or
19//! `firewalld` state out-of-band, and macOS has its own model (`pfctl` /
20//! Application Firewall) that isn't in scope for this phase.
21
22use thiserror::Error;
23
24/// Errors produced while installing or removing Windows firewall rules.
25#[derive(Error, Debug)]
26pub enum FirewallError {
27    /// A COM call failed. Includes the underlying `HRESULT` message.
28    #[error("Windows COM call failed: {0}")]
29    Com(String),
30
31    /// `CoInitializeEx` returned a failure status.
32    #[error("CoInitializeEx failed: {0}")]
33    ComInit(String),
34
35    /// The `INetFwPolicy2` interface could not be instantiated.
36    #[error("INetFwPolicy2 not available: {0}")]
37    PolicyUnavailable(String),
38
39    /// Adding a firewall rule failed. Includes the rule name.
40    #[error("Failed to add firewall rule '{name}': {reason}")]
41    AddRule {
42        /// Display name of the rule that could not be created.
43        name: String,
44        /// Underlying error message from the Windows API.
45        reason: String,
46    },
47
48    /// Removing a firewall rule failed. Includes the rule name.
49    #[error("Failed to remove firewall rule '{name}': {reason}")]
50    RemoveRule {
51        /// Display name of the rule that could not be removed.
52        name: String,
53        /// Underlying error message from the Windows API.
54        reason: String,
55    },
56
57    /// A string could not be converted to the `BSTR` / wide-string form
58    /// required by the Windows COM API.
59    #[error("String conversion failed: {0}")]
60    StringConversion(String),
61}
62
63#[cfg(windows)]
64mod windows;
65
66/// Display name of the inbound overlay (`WireGuard` UDP) firewall rule.
67pub const OVERLAY_RULE_NAME: &str = "ZLayer Overlay (UDP)";
68
69/// Display name of the inbound API (HTTP/gRPC TCP) firewall rule.
70pub const API_RULE_NAME: &str = "ZLayer API (TCP)";
71
72/// Display name of the inbound Raft (TCP) firewall rule.
73pub const RAFT_RULE_NAME: &str = "ZLayer Raft (TCP)";
74
75/// All three rule names that this module manages, in the order they are
76/// installed / removed.
77pub const MANAGED_RULE_NAMES: &[&str] = &[OVERLAY_RULE_NAME, API_RULE_NAME, RAFT_RULE_NAME];
78
79/// Ensure the three inbound allow-rules exist in Windows Defender Firewall
80/// for the overlay UDP, API TCP, and Raft TCP ports.
81///
82/// Idempotent: if a rule with the expected name already exists it is left
83/// untouched. Rules are scoped to the Private + Domain profiles only.
84///
85/// On non-Windows targets this is a no-op that returns `Ok(())`.
86///
87/// # Arguments
88///
89/// * `wg_port`   — UDP inbound port for the overlay (boringtun)
90/// * `api_port`  — TCP inbound port for the daemon API
91/// * `raft_port` — TCP inbound port for the Raft scheduler
92///
93/// # Errors
94///
95/// Returns a [`FirewallError`] if COM initialization fails, the
96/// `INetFwPolicy2` service is unavailable, or the Windows Firewall API
97/// rejects a rule creation (typically because the process lacks
98/// administrator privileges). On non-Windows targets this cannot fail.
99pub fn ensure_overlay_rules(
100    wg_port: u16,
101    api_port: u16,
102    raft_port: u16,
103) -> Result<(), FirewallError> {
104    #[cfg(windows)]
105    {
106        self::windows::ensure_overlay_rules(wg_port, api_port, raft_port)
107    }
108    #[cfg(not(windows))]
109    {
110        let _ = (wg_port, api_port, raft_port);
111        Ok(())
112    }
113}
114
115/// Remove any ZLayer-managed inbound firewall rules that this module would
116/// otherwise install.
117///
118/// Safe to call when the rules do not exist — missing rules are treated as
119/// a successful no-op. On non-Windows targets this is a no-op that returns
120/// `Ok(())`.
121///
122/// # Errors
123///
124/// Returns a [`FirewallError`] if COM initialization fails, the
125/// `INetFwPolicy2` service is unavailable, or the Windows Firewall API
126/// rejects the remove call. "Rule not found" is not treated as an error.
127/// On non-Windows targets this cannot fail.
128pub fn remove_overlay_rules() -> Result<(), FirewallError> {
129    #[cfg(windows)]
130    {
131        self::windows::remove_overlay_rules()
132    }
133    #[cfg(not(windows))]
134    {
135        Ok(())
136    }
137}