Files
OrchestrAD/backend/internal/cli/firewall_windows.go
Alphaeus Mote 2befe64a0c feat(install): idempotent Windows firewall rule; skip the blank MSI EULA
Firewall:
- On service initialize/install, create an idempotent inbound allow rule
  ("OrchestrAD") for the configured listen port, scoped to RFC 1918 private
  ranges plus CGNAT (10/8, 172.16/12, 192.168/16, 100.64/10). The rule is
  deleted-then-added so it always reflects the current port, and removed on
  service uninstall. Best-effort (needs admin; the MSI custom action and
  service run elevated); no-op off Windows. Verified the netsh rule lands
  with the expected port and remote-address scoping.

MSI:
- Skip the license/EULA page (Welcome now goes straight to the install
  directory), since it was blank. A standard short notice is kept in
  license.rtf only so the stock license control resolves at build time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 21:46:08 -04:00

52 lines
1.7 KiB
Go

//go:build windows
package cli
import (
"fmt"
"os/exec"
"strconv"
"github.com/Grace-Solutions/OrchestrAD/internal/logging"
)
const firewallRuleName = "OrchestrAD"
// firewallRemoteIP scopes the inbound rule to RFC 1918 private ranges plus the
// carrier-grade NAT range (100.64.0.0/10). Loopback is exempt from the firewall
// so localhost is unaffected.
const firewallRemoteIP = "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10"
// EnsureFirewallRule idempotently creates the inbound allow rule for the service
// port. It first deletes any rule of the same name so the rule always reflects
// the current port, then adds it. Requires administrative rights (the MSI custom
// action and the service run elevated).
func EnsureFirewallRule(port int) error {
// Best-effort delete of a prior rule (ignore "no rules match").
_ = exec.Command("netsh", "advfirewall", "firewall", "delete", "rule",
"name="+firewallRuleName).Run()
out, err := exec.Command("netsh", "advfirewall", "firewall", "add", "rule",
"name="+firewallRuleName,
"dir=in", "action=allow", "protocol=TCP",
"localport="+strconv.Itoa(port),
"remoteip="+firewallRemoteIP,
"profile=any",
"description=OrchestrAD inbound (RFC1918 + CGNAT)",
).CombinedOutput()
if err != nil {
return fmt.Errorf("netsh add rule failed: %v: %s", err, out)
}
logging.Info("Firewall", "Inbound rule '%s' allows TCP %d from %s", firewallRuleName, port, firewallRemoteIP)
return nil
}
// RemoveFirewallRule idempotently deletes the inbound rule. A missing rule is
// not an error.
func RemoveFirewallRule() error {
_ = exec.Command("netsh", "advfirewall", "firewall", "delete", "rule",
"name="+firewallRuleName).Run()
logging.Info("Firewall", "Inbound rule '%s' removed", firewallRuleName)
return nil
}