Documentation
¶
Overview ¶
internal/firewall/backend.go — runtime.GOOS dispatcher.
Each platform contributes a `newBackend` constructor in its build-tagged file. The dispatcher picks the right one at compile-time based on the build tags, then DefaultBackend returns the constructed instance.
A package-level override (SetDefaultBackend) lets tests inject a fake without touching runtime.GOOS — used by the serve hook integration test to assert the warning flow without spawning PowerShell.
Package firewall — cross-platform host-firewall configuration for clawtool's LAN peer discovery surface.
The Windows defender firewall blocks inbound UDP 5353 (mDNS) and inbound TCP on the daemon port by default; without rules, two clawtool daemons on the same LAN cannot find each other even though both are dutifully announcing on multicast. Asking users to copy-paste `New-NetFirewallRule` from a doc is a UX failure — the operator's directive ("kullanıcılar beceremezler") is that the first run of `clawtool serve --listen 0.0.0.0 --allow-lan` installs the rules itself, with a single UAC prompt, default-on.
This package is the platform-agnostic interface. The Windows backend (`windows.go`) composes a PowerShell batch and elevates it via `Start-Process -Verb RunAs`. The Linux backend (`linux.go`) wraps `ufw allow` when ufw is active and no-ops otherwise. The macOS backend (`darwin.go`) is a no-op because pf is default-allow for unprivileged inbound ports and the Application Firewall already prompts the user once on first bind.
Lifecycle: `DefaultBackend()` returns the runtime.GOOS-specific implementation. Callers (the serve hook + the `clawtool firewall` subcommands) work against the Backend interface.
internal/firewall/linux.go — Linux backend.
Targets `ufw` (Uncomplicated Firewall) — the de-facto desktop Linux firewall on Ubuntu / Debian / Pop!_OS. When ufw is installed AND active, we add `ufw allow` rules for UDP 5353 + the daemon's TCP port. When ufw is absent OR inactive, we no-op: the host has no firewall gating inbound traffic, so there's nothing to configure.
iptables direct rules are intentionally out of scope. They cover the ~5% of advanced Linux desktops that disable ufw in favour of nftables / iptables-restore configs, and adding a rule via `iptables -A INPUT` on those systems would conflict with the existing rule chains. Operators on that path know their setup well enough to add the rules themselves.
internal/firewall/runner.go — shared exec wrapper used by the per-platform backends. Lives in a non-build-tagged file so both linux.go and windows.go can reference commandRunner/defaultRunner without each redeclaring the same types.
Tests inject a fakeRunner via newLinuxBackendFor / newWindowsBackendFor; production callers use defaultRunner.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrElevationDeclined = errors.New("firewall: elevation declined")
ErrElevationDeclined is returned by AddRules when the user declined the platform's elevation prompt (UAC "No" on Windows, `sudo -n` failure on Linux). Callers should treat this as a non-fatal degraded state: the daemon still starts, just without the LAN-reachable surface.
var ErrNotSupported = errors.New("firewall: backend not supported on this platform")
ErrNotSupported is returned by backends that have no implementation for the current platform (e.g. exotic Unix distros without ufw + without root). Callers should treat this as a non-fatal "no-op" — there's nothing to configure.
Functions ¶
func SetDefaultBackend ¶
func SetDefaultBackend(b Backend)
SetDefaultBackend installs a backend override. Tests use this to inject a fake; pass nil to restore the platform default.
Types ¶
type AddOptions ¶
type AddOptions struct {
// Elevated, when true, asks the backend to perform the rule
// install with administrative privileges (Windows: UAC prompt
// via Start-Process -Verb RunAs; Linux: sudo). When false,
// the backend may still elevate if the platform requires it
// — the flag is a hint, not a guarantee.
Elevated bool
}
AddOptions controls the AddRules call.
type AddResult ¶
type AddResult struct {
// AddedRules lists the Rule.Name values that were freshly
// created. Already-present rules are skipped (idempotent
// install) and do NOT appear here.
AddedRules []string
// SkippedRules lists rules that were already present at the
// requested name. Reported separately so the CLI can show a
// neutral "already configured" message instead of a misleading
// "added" success.
SkippedRules []string
}
AddResult reports what the backend did during AddRules.
type Backend ¶
type Backend interface {
// Detect probes the host firewall and reports whether
// clawtool needs to install rules.
Detect() (BackendState, error)
// HasRule returns true when a rule with the given name is
// currently installed on the host firewall.
HasRule(name string) (bool, error)
// AddRules installs every rule in `rules` that isn't already
// present. Idempotent: already-present rules are reported in
// SkippedRules, not re-added. Returns ErrElevationDeclined
// when the platform required elevation and the user declined
// (e.g. UAC "No" on Windows).
AddRules(rules []Rule, opts AddOptions) (AddResult, error)
// RemoveRules deletes the named rules. Missing rules are
// silently skipped — RemoveRules is best-effort cleanup.
RemoveRules(names []string) error
// List returns every clawtool-* rule currently installed.
// Used by `clawtool firewall status`. Returns an empty slice
// (not nil) when no rules are present.
List() ([]Rule, error)
}
Backend is the platform-specific implementation surface.
func DefaultBackend ¶
func DefaultBackend() Backend
DefaultBackend returns the firewall backend for the current platform. Tests can override via SetDefaultBackend.
type BackendState ¶
type BackendState struct {
// Active is true when the platform's firewall is enabled
// and would block unknown inbound traffic. False on macOS
// (default-allow pf) and on Linux when ufw is inactive.
Active bool
// NeedsRules is true when Active is true AND at least one of
// the expected clawtool rules is missing. The serve auto-hook
// keys off this — false skips the UAC prompt entirely.
NeedsRules bool
// Reason is a short human-readable explanation of the state,
// surfaced by `clawtool firewall status`.
Reason string
}
BackendState is the result of Detect — describes whether the host firewall is actively gating inbound traffic and whether clawtool's rules are already present.
type Rule ¶
type Rule struct {
// Name is the canonical clawtool-* identifier the backend
// uses to look the rule up later. Must be unique across
// clawtool rules; the `clawtool firewall remove` path treats
// every rule with the `clawtool-` prefix as in scope.
Name string
// Description is a human-readable string shown in the
// platform's firewall UI. Optional.
Description string
// Protocol is "TCP" or "UDP". Other values are rejected at
// AddRules time.
Protocol string
// LocalPort is the inbound port the rule allows. Always
// >0; zero means "rule is malformed".
LocalPort int
// RemoteCIDR is the source CIDR allowed to reach LocalPort.
// Empty string means "any source" (UDP 5353 mDNS). For the
// daemon TCP rule the backend fills in the detected LAN
// range (e.g. 192.168.1.0/24) so the listener isn't reachable
// from arbitrary internet sources.
RemoteCIDR string
// Profile is the Windows firewall profile selector
// ("Private", "Domain", or "Private,Domain"). Ignored on
// non-Windows backends. Public is intentionally excluded by
// default — clawtool's LAN surface shouldn't fire on a
// coffee-shop Wi-Fi the operator marked Public.
Profile string
}
Rule describes a single inbound firewall rule. Platforms map this to their native primitive (Windows: New-NetFirewallRule; Linux ufw: ufw allow; macOS: no-op).
func DefaultRules ¶
DefaultRules returns the canonical rule set for a given daemon port + detected LAN CIDR. Both the serve auto-hook and the manual `clawtool firewall add` subcommand call this so the rule list stays consistent.
func GatewayRule ¶
GatewayRule returns the canonical clawtool HTTP gateway rule for the given port + LAN CIDR. Port is the daemon's bound TCP port; lanCIDR scopes the rule to the operator's local network (auto-detected by the Windows backend, manually overridable via the `--lan-cidr` CLI flag).