Files
orca/internal/emitter/nft_test.go
T
Jon Chery 4b70e31cf4 fix(P02): input validation + injection hardening — 11 vectors (REQ-150)
Critical fixes:
- logs --job: validate ^[A-Za-z0-9_-]+$ + shellQuote (was %q backtick RCE)
- pprof: isLoopback treats empty host as bind-all (was :6060 bypass)
- backup restore: filepath.Rel containment check (was tar-slip via a/../..)
- WebAuthn reg auth deferred to P04 (requires session infra)

High fixes:
- txn rollback/show/apply: validate ^T-[0-9a-f]{16}$ + shellQuote
- nft diff --against: validate txn ID before filepath.Join
- drain stopAlloc: validate allocID ^[A-Za-z0-9_-]+$
- cluster_compat: shellQuote peer dir name
- podman image: shellQuote (was %q backtick injection)
- nft TrustedProbes: net.ParseIP/CIDR validation + split v4/v6 sets
- sudoers: validate --proxmox-user/--proxmox-role ^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$
  fixed path /etc/sudoers.d/orca; shellQuote pveum/useradd; validateSudoers
  checks actual file
- nft country block: validate ^[A-Z]{2}$ (was len==2 only)

New file: internal/cli/validate.go (shared validators + shellQuote)
All 38 Go test packages pass. go vet + gofmt clean.

---ci---
project: orca
phase: 2
milestone: v0.13
status: complete
requirements:
  covered: [150]
---/ci---
2026-08-07 19:28:01 +00:00

138 lines
4.1 KiB
Go

package emitter
import (
"strings"
"testing"
)
func TestNftEmitter_RenderBasic(t *testing.T) {
files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{})
if err != nil {
t.Fatalf("RenderNftConfig: %v", err)
}
if len(files) != 1 {
t.Fatalf("got %d files, want 1", len(files))
}
f := files[0]
if f.Path != "/etc/nftables.d/orca.nft" {
t.Errorf("Path = %q, want /etc/nftables.d/orca.nft", f.Path)
}
if f.Mode != "0644" {
t.Errorf("Mode = %q, want 0644", f.Mode)
}
c := f.Content
for _, want := range []string{
"#!/usr/sbin/nft -f",
"table inet orca-ingress",
"set orca_trusted_probes",
"type ipv4_addr",
"flags interval",
"127.0.0.1",
"::1",
"chain input",
"tcp dport 443 tcp-flags != syn,rst,ack,fin notrack drop",
"chain prerouting",
"tcp dport 443 dnat to 127.0.0.1:8443",
"tcp dport 80 dnat to 127.0.0.1:8080",
"chain forward",
"rate 100/second burst 200 packets",
"ora_rl",
} {
if !strings.Contains(c, want) {
t.Errorf("content missing %q:\n%s", want, c)
}
}
}
func TestNftEmitter_DefaultsApplied(t *testing.T) {
files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{RateLimit: 0, RateBurst: 0})
if err != nil {
t.Fatalf("Render: %v", err)
}
if !strings.Contains(files[0].Content, "rate 100/second burst 200 packets") {
t.Errorf("defaults not applied:\n%s", files[0].Content)
}
}
func TestNftEmitter_CustomRate(t *testing.T) {
files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{RateLimit: 50, RateBurst: 150})
if err != nil {
t.Fatalf("Render: %v", err)
}
if !strings.Contains(files[0].Content, "rate 50/second burst 150 packets") {
t.Errorf("custom rate not rendered:\n%s", files[0].Content)
}
}
func TestNftEmitter_CustomTrustedProbes(t *testing.T) {
files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{TrustedProbes: []string{"10.0.0.5", "192.168.1.1"}})
if err != nil {
t.Fatalf("Render: %v", err)
}
c := files[0].Content
if !strings.Contains(c, "10.0.0.5, 192.168.1.1") {
t.Errorf("custom probes not rendered:\n%s", c)
}
if strings.Contains(c, "::1") {
t.Errorf("default ::1 should not be present when custom probes set:\n%s", c)
}
}
func TestNftEmitter_NegativeRateRejected(t *testing.T) {
if _, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{RateLimit: -1}); err == nil {
t.Fatal("expected error for negative rate, got nil")
}
}
func TestNftEmitter_ShebangFirst(t *testing.T) {
files, _ := (NftEmitter{}).RenderNftConfig(NftClusterConfig{})
if !strings.HasPrefix(files[0].Content, "#!/usr/sbin/nft -f\n") {
t.Errorf("shebang not first:\n%s", files[0].Content[:40])
}
}
// TestNftEmitter_RejectsInvalidTrustedProbe verifies that a TrustedProbes
// entry that is not a valid IP or CIDR is rejected (F9: ruleset injection
// guard). An unvalidated entry written raw into the ruleset could inject
// arbitrary nft syntax.
func TestNftEmitter_RejectsInvalidTrustedProbe(t *testing.T) {
bad := []string{
"not-an-ip",
"127.0.0.1; flush ruleset",
"$(whoami)",
"10.0.0.0/33", // invalid CIDR prefix
}
for _, b := range bad {
_, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{TrustedProbes: []string{"127.0.0.1", b}})
if err == nil {
t.Errorf("expected error for invalid trusted probe %q, got nil", b)
}
}
}
// TestNftEmitter_TrustedProbesSplitV4V6 verifies that IPv4 and IPv6
// probes are rendered into separate typed sets (F9: the prior single
// ipv4_addr set included ::1, an IPv6 address — a type mismatch).
func TestNftEmitter_TrustedProbesSplitV4V6(t *testing.T) {
files, err := (NftEmitter{}).RenderNftConfig(NftClusterConfig{TrustedProbes: []string{"10.0.0.5", "::1"}})
if err != nil {
t.Fatalf("Render: %v", err)
}
c := files[0].Content
if !strings.Contains(c, "set orca_trusted_probes_v4") {
t.Errorf("missing v4 set:\n%s", c)
}
if !strings.Contains(c, "set orca_trusted_probes_v6") {
t.Errorf("missing v6 set:\n%s", c)
}
if !strings.Contains(c, "type ipv6_addr") {
t.Errorf("missing ipv6_addr type:\n%s", c)
}
if !strings.Contains(c, "10.0.0.5") {
t.Errorf("missing 10.0.0.5 in v4 set:\n%s", c)
}
if !strings.Contains(c, "::1") {
t.Errorf("missing ::1 in v6 set:\n%s", c)
}
}