4b70e31cf4
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---
138 lines
4.1 KiB
Go
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)
|
|
}
|
|
}
|