go-firewalld/README.md
2026-07-02 14:08:37 -05:00

106 lines
4.5 KiB
Markdown

# go-firewalld
[![Go Reference](https://pkg.go.dev/badge/github.com/grmrgecko/go-firewalld.svg)](https://pkg.go.dev/github.com/grmrgecko/go-firewalld)
A comprehensive Go client for the [firewalld](https://firewalld.org) D-Bus API,
built on [`github.com/godbus/dbus/v5`](https://github.com/godbus/dbus).
- **Runtime + permanent** operations, mirroring `firewall-cmd` (transient vs `--permanent`).
- **Version/capability aware:** one binary serves both old (EL7, firewalld 0.6.x)
and new (EL9+, firewalld 1.x/2.x) servers. The dict (`a{sv}`) settings path and
zone-to-zone policies are used where supported and transparently fall back to
the v1 tuple path where they are not.
- **Typed, `errors.Is`-able errors** mapped from firewalld exceptions
(`ErrAlreadyEnabled`, `ErrNotEnabled`, `ErrInvalidZone`, …).
## Install
```sh
go get github.com/grmrgecko/go-firewalld
```
## Usage
```go
ctx := context.Background()
conn, err := firewalld.Connect(ctx)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Println(conn.Version()) // e.g. "1.3.4"
fmt.Println(conn.Supports(firewalld.Policies)) // false on EL7, true on EL9+
// Permanent edits (apply after a reload).
zone := conn.Permanent().Zone("public")
if err := zone.AddPort(ctx, firewalld.Port{Port: "4242", Protocol: "udp"}); err != nil {
if errors.Is(err, firewalld.ErrAlreadyEnabled) {
// idempotent no-op
} else {
log.Fatal(err)
}
}
_ = zone.SetTarget(ctx, firewalld.TargetDROP)
_ = conn.Reload(ctx)
// Read settings (transport chosen automatically by server capability).
s, _ := conn.Permanent().Zone("public").Settings(ctx)
fmt.Println(s.Services, s.Ports, s.Forward)
// Runtime (transient) edits, with an optional timeout.
_ = conn.Runtime().Zone("public").AddRichRule(ctx,
`rule family="ipv4" source address="10.0.0.0/8" reject`, 30*time.Second)
// Low-level escape hatch for anything not yet wrapped.
var out []string
_ = conn.Call(ctx, "/org/fedoraproject/FirewallD1",
"org.fedoraproject.FirewallD1.zone", "getZones", []any{&out})
```
## Coverage
| Area | Types / handles |
|---|---|
| Connection & lifecycle | `Connect`, `Open`, `Version`, `Supports`, `Reload`, `CompleteReload`, `RuntimeToPermanent`, `ResetToDefaults`, `CheckPermanentConfig`, panic mode, log-denied, default zone |
| Zones (permanent) | `Permanent().Zone(name)`: settings, update, add/remove/query for port/source/source-port/service/protocol/forward-port/masquerade/icmp-block/icmp-block-inversion/interface/rich-rule, target/short/description/version, rename, remove, load-defaults |
| Zones (runtime) | `Runtime().Zone(name)`: same element set with timeouts; change-interface/change-source (move between zones); active zones, zone-of-interface/source; wholesale `Runtime().SetSettings` (dict servers) |
| Config management | zone names/paths, add-zone, zone-of-interface/source |
| Daemon properties | `RuntimeInfo` (state, IPv4/IPv6/IPSet/bridge support, ipset & icmp types), `DaemonConfig` (backend, rp-filter, cleanup, …) get/set |
| IPSets | runtime + permanent: settings, entries, options, existence query |
| Services | list, read (tuple/dict), permanent editor incl. includes |
| ICMP types | list, read, permanent editor |
| Helpers (conntrack) | list, read, permanent editor |
| Policies (≥ 0.9) | runtime + permanent, capability-gated |
| Direct interface | runtime chains/rules/passthroughs + permanent direct config blob |
| Lockdown | enable/disable/query + whitelist (command/context/user/uid) |
| Signals | `WatchSignals`, `WatchReloaded` |
Deliberately omitted as redundant with `Settings()`/`Update()`: the per-field bulk
`setX` list-setters and scalar `getX` getters on config objects. Legacy/no-op
methods (`authorizeAll`, `isImmutable`, `*AutomaticHelpers`, the `changeZone`
alias) are also skipped.
## Testing
Unit tests need no bus and cover encode/decode round-trips plus
`dbus.SignatureOf` assertions for every compound type:
```sh
go test ./...
```
Integration tests run against a live firewalld and are gated behind a build tag.
They operate only on throwaway zones/ipsets/policies they create and remove, so a
failure cannot disturb the default zone or an SSH session. Build a test binary and
run it on the target host (no Go toolchain needed there):
```sh
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go test -c -tags firewalld_integration -o firewalld.itest .
scp firewalld.itest root@host:/tmp/
ssh root@host '/tmp/firewalld.itest -test.v'
```
Verified against firewalld **0.6.3** (CentOS 7, iptables backend) and **1.3.4**
(Rocky 9, nftables backend); signature tests additionally cover **2.4.3**.