Skip to content

Releases: aafeher/nftui

nftui v1.1.0

21 Jun 17:54
b49f6ec

Choose a tag to compare

Makes the TUI usable on small terminals and hardens the supply chain. Adds an 80x24 minimum with frame clamping and a resize prompt, alternate-screen rendering, scroll-to-focus in the rule editor and scrolling in the rule view, and a compact chain header; fixes quitting flushing the live ruleset and the rule list rendering every rule twice; and lands a security/CI wave — OpenSSF Scorecard, CodeQL, Codecov, Go fuzz targets, SHA-pinned actions, and hardened workflow token permissions.

Added

  • Code coverage reporting via Codecov. The CI build-and-test job now writes a unit coverage profile (go test -race -covermode=atomic -coverprofile) and uploads it to Codecov (flag unit); the integration-test job uploads its live-netlink coverage profile too (flag integration). Uploads use the CODECOV_TOKEN repo secret and are non-fatal (fail_ci_if_error: false), so a Codecov hiccup never blocks the build gate. A coverage badge was added to the README header.
  • OpenSSF Scorecard supply-chain security analysis (.github/workflows/scorecard.yml). The ossf/scorecard-action scores the repo against security best-practice checks (pinned dependencies, signed releases, token-permission hygiene, dangerous-workflow patterns, SAST, branch protection, …), uploads a SARIF report to the GitHub code-scanning (Security) tab, and publishes the result to the OpenSSF API (publish_results: true) that backs the new README Scorecard badge. Runs on pushes to main, a weekly schedule, and branch-protection-rule changes.
  • CodeQL static analysis (SAST) for the Go code (.github/workflows/codeql.yml). GitHub's CodeQL engine analyzes nftui's own source (autobuild mode, Go toolchain from go.mod) and reports findings to the code-scanning (Security) tab — complementing the govulncheck job, which covers known-vulnerable dependency calls rather than first-party bug/security patterns. Runs on push / PR to main and develop plus a weekly schedule; workflow-level permissions are read-only, with security-events: write granted only on the analysis job. Closes the OpenSSF Scorecard SAST finding.
  • Go native fuzz targets for the package's least-trusted string parsers (nft/fuzz_test.go): FuzzValidateIdentifier asserts the injection-safety invariant (any accepted name is non-empty, within the length bound, letter-led, and free of nft-script metacharacters) and FuzzParseSetElementKey drives the set-element key parser across every key type, asserting it never panics and only ever returns correctly-sized element bytes (covering parseIP4 / parseIP6 / parseInetService / parseUintBE / cidrToRange / dashRangeToBytes). The seed corpus runs under the normal go test gate; extended runs use go test -run='^$' -fuzz=… ./nft/. Continues the E-series "don't crash on untrusted input" hardening and closes the OpenSSF Scorecard Fuzzing finding.
  • examples/example-host-firewall.conf — a hardened, good-practice single-host firewall (not a router): one inet table covering IPv4+IPv6, stateful filtering with invalid-drop, loopback anti-spoofing, ICMPv6 NDP allowed, rate-limited ping and SSH, exposed services (22/80/443) with default-deny inbound, unrestricted outbound, and forwarding denied. Verified with nft -c -f. Mentioned in the README "Example ruleset" section.

Fixed

  • Views no longer scroll their top off-screen on short terminals. nftui uses no alternate screen, so a frame taller than the terminal made it scroll and pushed the title/header/tabs off the top (a chain view measured 27 lines at a 24-row terminal). MainWindow.View now clamps every frame to the terminal size (MaxWidth/MaxHeight), so the top always stays visible, and below the supported minimum of 80x24 a centered "Terminal too small — resize" prompt replaces the broken layout. The global quit-confirmation dialog is now rendered as its own terminal-sized frame (quitConfirmView) so the clamp can't hide it — previously it was stacked below the base view in a two-screen-tall frame, which the clamp would have truncated away. A new ## Requirements note documents the minimum. Pinned by TestMainWindow_TooSmallGuard, TestMainWindow_FrameFitsTerminal, and TestMainWindow_QuitConfirmVisible.
  • The chain view fits the terminal; its footer is no longer clipped at the minimum size. The chain's metadata header was 14 fixed lines (Chain / Table / Type / Hook / Priority / Default policy on their own lines + a multi-line "Rules by type" block), which at 80x24 left room for only one rule and overflowed the frame by ~3 lines. The header is now compact — name on one line, all metadata (table·family·type·hook·priority·policy) on a second, and the rule-type counts on a third — and the content box is budgeted so the frame fits exactly (3 rules visible at 80x24 instead of 1). headerLines() and maxVisibleRules() were updated to match. Pinned by TestChainView_FitsTerminal (plus updated TestChainView_MaxVisibleRules / TestChainView_HeaderLines).
  • The TUI now runs on the alternate screen. tea.WithAltScreen() makes nftui a proper full-screen app that restores the terminal and the user's scrollback on exit instead of leaving its last frame behind. Frames are clamped to the terminal size, so nothing overflows the alternate buffer.
  • The rule editor's tall tabs now scroll to keep the focused field visible. On a short terminal the General (and other) tabs render more fields than fit, and the field area didn't scroll — only the top was visible and Tab could move focus to a field below the fold that you couldn't see. The editor now keeps the title and tab bar fixed and scrolls the field area so the Tab / Shift+Tab-focused field always stays fully on screen (the focused field is located via an invisible sentinel injected by a new fview helper; the body is windowed to the box height). Pinned by TestRuleEdit_ScrollToFocus.
  • The read-only rule view scrolls for rules taller than the screen. It has no focus to follow, so it gained explicit /k and /j scroll keys (shown in the footer): the title and tab bar stay fixed while the detail body scrolls, with the offset clamped at both ends and reset on tab switch, so a large rule no longer hides its lower half. Pinned by TestRuleView_Scroll.
  • Quitting no longer flushes the live ruleset (data loss). Confirming the "Are you sure you want to quit?" dialog called nft.FlushRules() before exiting, wiping the entire kernel ruleset on every quit — a direct violation of nftui's contract to never mutate state the user didn't explicitly change. Exit now simply quits; no kernel call. Regression-pinned by TestMainWindow_QuitDoesNotFlush (bug B-2 in ROADMAP).
  • Chain view no longer renders every rule twice. The rule list printed each rule on two lines — once via nft.RuleToHumanReadable and once via nftserializer.SerializeRule. Only the canonical RuleToHumanReadable form (the one the rule view and the filter already use, with set resolution) is kept; ruleEntryLines shrank 4 → 3 so the visible-window math stays correct. Pinned by TestChainView_RuleRenderedOnce (bug B-1 in ROADMAP).

Security

  • Hardened GitHub Actions token permissions in the release workflow: the workflow level now defaults to read-only (contents: read) and the write scopes (contents / id-token / attestations) are granted only on the goreleaser job that needs them, shrinking the token blast radius. No behavior change — the single release job still gets exactly the scopes it requires (closes the OpenSSF Scorecard Token-Permissions finding).
  • Pinned all build dependencies by digest. Every GitHub Action across ci.yml / release.yml / scorecard.yml / codeql.yml is now referenced by full commit SHA with a # vN comment (so a moving tag can no longer change what runs), the Dockerfile base images are pinned by digest (golang:1.25.8-alpine@sha256:…, alpine:3.22@sha256:…), and the CI govulncheck install is pinned to @v1.4.0 instead of @latest. Dependabot gains a docker ecosystem so the digest pins still receive update PRs (it already bumps the SHA-pinned actions via the github-actions ecosystem). Closes the OpenSSF Scorecard Pinned-Dependencies finding. sigstore/cosign-installer is held at v3 (cosign 2.x) and excluded from Dependabot major bumps — cosign 4.x's sign-blob defaults break Goreleaser's signs: block — until that block is made cosign-4 compatible.
  • Expanded SECURITY.md with a concrete response and disclosure policy: a direct "Report a vulnerability" link, response SLAs (acknowledgement within 3 business days, triage within 7 days), a 90-day coordinated-disclosure window, a published-advisory link, and a supported-versions table. Strengthens the OpenSSF Scorecard Security-Policy signal (it scores a policy higher when it contains links and concrete disclosure timelines).

nftui v1.0.0

20 Jun 07:20

Choose a tag to compare

The first stable release. Broadens the installation paths (Debian / RPM, Alpine / Arch / OpenWrt packages, a Docker image, plus community Gentoo / AUR references), proves the reproducibility and Nix-flake claims v0.9.0 only asserted with dedicated CI lanes, makes the binary self-identifying (--version), ships a Go-module dependency tarball for offline source builds, and closes the last renderer gap (IPv6 source / destination addresses). Everything in v0.9.0 plus the full post-v0.9.0 candidate pool.

Added

  • --version CLI flag (v1.0.0 item V-1): nftui --version prints nftui <version> to stdout and exits 0. The release version is injected at build time via Goreleaser -ldflags '-X main.version={{ .Version }}'; a source build falls back to the Go build-info module version (set for go install <module>@vX.Y.Z), and finally to dev for a plain go build. Pre-scanned before flag.Parse (like --help), so it works regardless of other flags; surfaced in --help output and the man page OPTIONS. Pure resolveVersion / writeVersion seams in flags.go are unit-tested.
  • Debian / RPM packages (v1.0.0 item V-2): every release now attaches .deb and .rpm packages for amd64 and arm64, built from the same nftui binary as the archives via an nfpms: block in .goreleaser.yaml (nfpm, pure-Go — no rpmbuild/dpkg-dev needed). They install the binary to /usr/bin, the man page to /usr/share/man/man1, and the docs under /usr/share/doc/nftui, and declare a runtime dependency on nftables (nftui shells out to nft(8) for --config load and table/chain rename). The packages are folded into checksums.txt, so the existing keyless cosign signature over the checksum file covers them too. New README "Prebuilt packages" install subsection.
  • Alpine, Arch, and OpenWrt packages (v1.0.0 item V-3): the nfpms: block now also emits .apk (Alpine), .pkg.tar.zst (Arch — via nfpm's archlinux format, installable with pacman -U, no AUR account needed), and .ipk (OpenWrt/opkg) — five package formats total, all from the same nftui binary and folded into checksums.txt. OpenWrt's migration from opkg to apk means the .apk should also serve newer apk-based OpenWrt on matching architectures. nftui deliberately does not auto-publish to the AUR; instead a community-maintainable reference packaging/aur/PKGBUILD (a -bin package over the release tarball) is provided, and the release .pkg.tar.zst installs natively without the AUR. README "Prebuilt packages" subsection expanded with a per-format install table. Community-maintainable reference packaging for source distros is also provided on a not-published basis: packaging/aur/PKGBUILD (Arch -bin) and a Gentoo packaging set under packaging/gentoo/nftui-0.9.0.ebuild (from source via go-module.eclass) and nftui-bin-0.9.0.ebuild (prebuilt binary), which block each other since both install /usr/bin/nftui, a shared metadata.xml, and a README.md covering overlay setup and the source build's dependency-tarball requirement.
  • Reproducible-build CI check (v1.0.0 item V-4): a new reproducibility job in .github/workflows/ci.yml builds the release binaries twice with goreleaser build --snapshot and fails if the two differ, verifying that the mod_timestamp + -trimpath + CGO_ENABLED=0 build is byte-for-byte reproducible (only the compiled binaries are compared; archive / package containers are out of scope). Gated on the unit-test job so it skips a non-compiling commit. Validated locally: two independent builds produced identical amd64 and arm64 binaries.
  • Nix flake CI lane (v1.0.0 item V-5): a new nix job in .github/workflows/ci.yml installs Nix (cachix/install-nix-action) and runs nix flake check + nix build .#default, building flake.nix end-to-end on every push — closing the gap that the flake was hand-validated against the buildGoModule schema in v0.9.0 but never actually built. Gated on the unit-test job. One-time bootstrap: flake.nix ships vendorHash = lib.fakeHash, so the first CI run fails and prints the real sha256-... to pin into flake.nix; the lane then guards the hash against go.sum drift.
  • Go module dependency tarball in the release (v1.0.0 item V-7): .github/workflows/release.yml now generates a reproducible nftui-<ver>-deps.tar.xz (via scripts/gen-deps-tarball.sh — the GOMODCACHE from go mod download -modcacherw, packed with deterministic tar + single-threaded xz, mtime pinned to the tagged commit) and gh release uploads it to each release from v1.0.0 onward. It makes offline source builds work without a maintainer-hosted tarball — chiefly the from-source Gentoo ebuild, whose go-module.eclass forbids network access during the build. Integrity is rooted in go.sum (every module hash is pinned, and go.sum ships in the repo + source archive) and the tarball is covered by the SLSA build-provenance attestation (gh attestation verify); it is not added to checksums.txt (cosign has already signed that by then). Verified locally to be byte-reproducible across two builds.
  • IPv6 source/destination address rendering (v1.0.0 item V-6): the rule renderer's payloadToHumanReadable path now recognizes IPv6 saddr / daddr (the 16-byte network-header fields at offsets 8 / 24) and renders them with the ip6 qualifier and a proper v6 address / CIDR — previously these fell through to a raw payload[network header+8:16] == 0x… hex form. Covers exact addresses, byte-aligned / bitwise CIDRs, and anonymous-set forms (ip6 saddr { … }). Test-first: payloadToHumanReadable + ruleToHumanReadableWithSets unit cases, new manual-test fixtures in examples/example-nftables-01.conf (ip6 ip6_hdr_demo), and a root integration test (TestIntegration_IPv6AddressRoundtrip) round-tripping the forms through the live kernel. Resolves the v0.9.0 audit follow-up L-6.
  • Docker image: a root Dockerfile (multi-stage — golang:1.25.8-alpine build, alpine:3.22 runtime) produces a small (~17 MB) image that bundles the nft(8) CLI nftui shells out to at runtime; the version string is injectable via --build-arg VERSION=<tag> into the same -ldflags '-X main.version=…' used by the other release paths (defaults to dev). Because nftui manages the host ruleset, the container is run with --network host --cap-add NET_ADMIN and an interactive TTY. A docker-compose.yml wires the same options up (docker compose run --rm nftui), and a .dockerignore keeps the build context to the Go sources + man page. Verified locally: image builds, nftui --version prints the injected string, the bundled nft resolves, and docker compose config validates. New README "Docker" installation subsection. This was previously a declined packaging candidate; reconsidered and added at the user's request.

nftui v0.9.0

19 Jun 18:06

Choose a tag to compare

The release-infrastructure milestone: integration test harness for the live netlink path, GitHub Actions CI, a reproducible Goreleaser release pipeline, Nix flake packaging, and virtualized rule-list rendering for large chains. (Originally targeted as v1.0.0; renamed after a strict audit surfaced enough hardening items that bumping straight to a 1.0 stable was premature — v1.0.0 picks from the post-v0.9.0 candidate pool.)

Added

  • Integration test harness under the integration build tag (nft/integration_test.go). Skips when not root, applies a uniquely-named inet table via nft -f, reads it back through ListTables / ListChainsOfTable / ListRulesOfChain, and asserts on per-rule comment text (UserData TLV) plus rendered tokens. The table is torn down in t.Cleanup so a failing assertion still leaves the host clean. New README "Integration tests" subsection covers the invocation (sudo -E go test -tags=integration ./nft/ -v). The first-run output uncovered the anonymous-set renderer bug fixed below.
  • GitHub Actions CI workflow (.github/workflows/ci.yml). Two jobs run on every push / PR to main and develop: build-and-test (gofmt check, go vet for the default and integration build tag sets, go build, go test -race ./..., timeout-minutes: 15) and integration-test (apt-installs the nftables package, then sudo -E env PATH=$PATH go test -tags=integration -v ./nft/ so the harness gets CAP_NET_ADMIN and the elevated process can still resolve the setup-go-provisioned toolchain, timeout-minutes: 20). The integration job is gated by needs: build-and-test so it doesn't burn runner minutes on a commit that fails the cheap checks. actions/setup-go@v5 reads the Go version from go.mod (go-version-file), keeping CI and the module's declared toolchain in lockstep. A concurrency group with cancel-in-progress: true retires superseded runs on the same ref. README's new "Continuous integration" subsection documents what the workflow runs.
  • Goreleaser configuration (.goreleaser.yaml) and matching release pipeline (.github/workflows/release.yml). Builds reproducible Linux amd64 / arm64 binaries (CGO_ENABLED=0, -trimpath, -ldflags='-s -w', mod_timestamp pinned to the commit time so byte-identical rebuilds are possible given the same Go toolchain version), bundles each binary with LICENSE, README.md, CHANGELOG.md, and man/nftui.1 into a tar.gz, and emits a SHA-256 checksums.txt. The release workflow fires on v* tags (timeout-minutes: 30), extracts the matching ## [X.Y.Z] section from CHANGELOG.md via a literal-match awk one-liner (index($0, header) == 1, never a regex — so . in 1.0.0 can't match an arbitrary character), and passes it to Goreleaser via --release-notes so the published GitHub Release body is the curated changelog text (fallback placeholder uses ${GITHUB_REPOSITORY} so the URL is correct on any fork). The pre-build hook is go mod verify (read-only checksum assertion) — go mod tidy was deliberately rejected because it could rewrite go.sum mid-release and silently diverge the published binary from committed source state. release.github.{owner,name} is omitted so Goreleaser auto-detects upstream from the git remote; forks can release without editing the config. goreleaser-action is pinned to version: '~> v2' (tracks v2.x, immune to a v3 breaking change ambushing release day), and the config uses the current archives.formats / archives.ids schema (migrated from the deprecated singular format / builds after the second pre-tag audit; validated with goreleaser check against v2.16.0 — the same major series the workflow pulls). dist/ added to .gitignore so snapshot builds don't pollute the working tree. README's new "Release process" subsection documents the tag → publish flow and the local goreleaser check / goreleaser release --snapshot --clean --skip=publish validation commands.
  • Nix flake (flake.nix) packaging nftui for Nix users. packages.default is a buildGoModule derivation for x86_64-linux and aarch64-linux matching the Goreleaser shape (CGO_ENABLED=0 by default, -s -w), runs unit tests via doCheck = true, and installs man/nftui.1 to $out/share/man/man1/ so man nftui works after nix profile install. Version derivation is timestamp-based (0-YYYYMMDD-shortRev from self.lastModifiedDate + self.shortRev) so the Nix store path is at least date-informative; falls back to 0-00000000-dirty when neither field is set. A proper semver source (VERSION file or --argstr version) is tracked as a post-v0.9.0 candidate. apps.default exposes nix run. devShells.default mirrors the CI toolchain — go, gopls, goreleaser, nftables (for integration tests), mandoc (for mandoc -Tlint man/nftui.1). vendorHash is lib.fakeHash on first commit per the standard pattern: the initial nix build fails with the real sha256-… to paste in; the Nix path is intentionally independent of the Goreleaser release pipeline, so an outdated vendorHash never blocks publishing. Pinned to nixpkgs/nixos-unstable for current Go toolchain; users on stable channels can --override-input nixpkgs <ref>. README's "Installation" section gains a "Nix flake" subsection covering nix build / nix run / nix develop and the first-build hash-pinning step.
  • Test-coverage pass ahead of tagging — overall unit-statement coverage lifted from 16.0% to 45.5%. Four pieces: (1) nft/nftserializer gained its first test file (ruleset_test.go, 0% → 38.6%): SerializeRule was split into a netlink-free serializeRuleExprs core (sets injected by the caller) so the expression dispatch is unit-testable, with table-driven cases for chain/set declaration rendering, the common expression classes, lookup fallbacks, comment TLV append, and the unknown-expr marker. (2) A generic FieldEditor contract harness (ui/field_editor_harness_test.go) drives every editor registered in the rule editor's tabs (~140) through the full FocusSlots / Focus / Update / View / Blur / Changed / Save interface, constructed from both a populated and an empty rule (ui 6.6% → 42.3%), plus a whole-editor walk (F6 tab cycling, focus wrap-around, F2 save validation — the kernel-apply cmd is returned, never executed) and a value-pinning test for the verdict editor. (3) The CI integration job now writes a coverage profile (-coverpkg=./nft/...) and prints the total in the job log — the live netlink path is invisible to the unit profile, and measured 35.6% over the nft tree in a local root run. (4) Update state-machine tests for the chain create / edit dialogs (ui/chain_dialog_test.go, following the set-dialog test pattern): focus cycling with wrap-around, the kind toggle growing/shrinking the slot count, hook-option resync on chain-type change (including the invalid-hook fallback being flagged as a change), empty-name rejection, the no-op-save short-circuit that skips the kernel, and spec building for base vs regular chains — chain_create.go 0% → 95.4%, chain_edit.go 1.9% → 90.6%. The kernel-touching create/update cmds are returned by the dialogs but never executed in tests.
  • Second test-coverage wave on top of the first — total lifted to 56.5%. Rule-view render tests (ui/rule_view_test.go: all four tabs, every action type, the CT value-type switch, network payload/meta/exthdr/SCTP blocks, the reject/log/verdict/set-action/objref/set-lookup helpers — rule_view.go 2.3% → 90.1%). Table-tree state-machine tests (ui/table_tree_test.go: navigation, expand/collapse, scroll-follow, incremental search incl. match cycling, read-only guards, delete-confirm flow, row-selection messages — main_window_table_list.go 17.7% → 74.7%). MainWindow router tests (ui/main_window_route_test.go: view-switch messages, reload-batch messages, tree-refresh fan-out with --table filter preservation, quit-confirm overlay). chainView Update/View tests (ui/chain_view_update_test.go) through a new netlink-free newChainViewWithRules seam — chain_view.go 24.0% → 80.3%. RuleToHumanReadable gained the same injected-sets seam (ruleToHumanReadableWithSets) plus render tests; the pure parse helpers (natToAction / redirToAction / rejectToAction / queueToAction / extractValueFromCt / exthdrProtoToType) and the CT string converters (events / state / status / label-bit masks) got direct table tests; SerializeRedirect / SerializeReject covered in-package — nft/rule.go 55.4% → 73.8%, nft/expr/ct.go 53.4% → 63.5%.
  • Third test-coverage wave — total lifted to 61.8% (nft/expr 58.2% → 73.5%, nft 51.7% → 57.2%, ui 57.8% → 61.7%). The 22 zero-coverage pure serializers in nft/expr/ (dup / notrack / tproxy / flow_offload / hash / secmark / target / connlimit / match / socket / immediate / synproxy / queue / dynset / rt / numgen / quota / exthdr / fib / bitwise / nat / log, plus formatElement and the objref pair) got exact-output or token tests (serializers_misc_test.go, objref_test.go) — writing them surfaced the connlimit polarity bug fixed below. The table-create dialog got the same state-machine treatment as the chain dialogs (ui/table_create_test.go: focus cycle, name validation, family mapping, input routing — table_create.go 0% → 85.7%). RejectField got targeted tests beyond the generic harness (type-change code-rebuild with round-trip code restore, all four Save variants against the wire constants, family option/code mappings — field_reject.go 38.3% → 95.8%; field_ct_helpers.go 0% → 100%). setView gained a netlink-free newSetViewWithElements seam (same pattern as newChainViewWithRules) and a full Update/View suite (ui/set_view_test.go: add-prompt loop incl. parse errors and the map key/value tab flow, delete confirm, read-only guards, element/flag/hint formatting, interval-range rendering — set_view.go 27.6% → 78.9%). The pure nft helpers got direct table...
Read more