Files
orca/internal/cli/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

253 lines
6.6 KiB
Go

package cli
import (
"bytes"
"context"
"errors"
"strings"
"testing"
"git.cloudinit.dev/coreci/orca/internal/emitter"
)
func TestNftCmd_Registered(t *testing.T) {
found := false
for _, c := range rootCmd.Commands() {
if c.Name() == "nft" {
found = true
break
}
}
if !found {
t.Fatal("nft command not registered on root")
}
}
func TestNftShowCmd(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
origTransport := nftTransportOverride
origPeer := nftLeadPeerOverride
defer func() {
nftTransportOverride = origTransport
nftLeadPeerOverride = origPeer
}()
nftLeadPeerOverride = "lead"
want := "table inet orca-ingress { dnat }"
nftTransportOverride = &mockNftTransport{
execOut: map[string][]byte{
"nft list table inet orca-ingress": []byte(want),
},
}
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "show"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("nft show: %v", err)
}
if !strings.Contains(buf.String(), want) {
t.Errorf("nft show output missing %q:\n%s", want, buf.String())
}
}
func TestNftShowCmd_PeerFlag(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
origTransport := nftTransportOverride
defer func() { nftTransportOverride = origTransport }()
mt := &mockNftTransport{
execOut: map[string][]byte{"nft list table inet orca-ingress": []byte("ok")},
}
nftTransportOverride = mt
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "show", "--peer", "node-2"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("nft show: %v", err)
}
}
func TestNftDiffCmd_NoDrift(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
origTransport := nftTransportOverride
origPeer := nftLeadPeerOverride
defer func() {
nftTransportOverride = origTransport
nftLeadPeerOverride = origPeer
}()
nftLeadPeerOverride = "lead"
// Use the full default nft render so the diff detects no drift
rendered, rerr := emitter.NftEmitter{}.RenderNftConfig(emitter.NftClusterConfig{})
if rerr != nil {
t.Fatalf("render: %v", rerr)
}
live := string(rendered[0].Content)
nftTransportOverride = &mockNftTransport{
execOut: map[string][]byte{
"nft list table inet orca-ingress": []byte(live),
},
}
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "diff", "--against", "T-abcdef0123456789"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("nft diff: %v", err)
}
if !strings.Contains(buf.String(), "no drift") {
t.Errorf("expected no drift, got:\n%s", buf.String())
}
}
func TestNftDiffCmd_RequiresAgainst(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "diff"})
if err := rootCmd.Execute(); err == nil {
t.Fatal("expected error for missing --against, got nil")
}
}
func TestNftRateLimitSetCmd(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
origTransport := nftTransportOverride
origPeer := nftLeadPeerOverride
defer func() {
nftTransportOverride = origTransport
nftLeadPeerOverride = origPeer
}()
nftLeadPeerOverride = "lead"
mt := &mockNftTransport{
execOut: map[string][]byte{},
}
nftTransportOverride = mt
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "rate", "limit", "set", "--rate", "250"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("nft rate limit set: %v", err)
}
if !strings.Contains(buf.String(), "rate-limit set to 250/s burst 500") {
t.Errorf("unexpected output:\n%s", buf.String())
}
}
func TestNftRateLimitSetCmd_RequiresRate(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "rate", "limit", "set"})
if err := rootCmd.Execute(); err == nil {
t.Fatal("expected error for missing --rate, got nil")
}
}
func TestNftCountryBlockAddCmd(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
origTransport := nftTransportOverride
origPeer := nftLeadPeerOverride
defer func() {
nftTransportOverride = origTransport
nftLeadPeerOverride = origPeer
}()
nftLeadPeerOverride = "lead"
mt := &mockNftTransport{execOut: map[string][]byte{}}
nftTransportOverride = mt
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "country", "block", "add", "RU,CN"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("nft country block add: %v", err)
}
if !strings.Contains(buf.String(), "added 2 country code(s)") {
t.Errorf("unexpected output:\n%s", buf.String())
}
}
func TestNftCountryBlockAddCmd_BadCode(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "country", "block", "add", "USA"})
if err := rootCmd.Execute(); err == nil {
t.Fatal("expected error for bad country code, got nil")
}
}
func TestNftDoctorAliasCmd(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
origTransport := nftTransportOverride
origPeer := nftLeadPeerOverride
defer func() {
nftTransportOverride = origTransport
nftLeadPeerOverride = origPeer
}()
nftLeadPeerOverride = "lead"
nftTransportOverride = &mockNftTransport{
execOut: map[string][]byte{
"nft list table inet orca-ingress": []byte("dnat to 127.0.0.1:8443 dnat to 127.0.0.1:8080 ora_rl"),
},
}
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "doctor"})
if err := rootCmd.Execute(); err != nil {
t.Fatalf("nft doctor: %v", err)
}
if !strings.Contains(buf.String(), "nft:table") {
t.Errorf("nft doctor alias did not run doctor nft checks:\n%s", buf.String())
}
}
func TestNftShowCmd_ExecError(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
origTransport := nftTransportOverride
origPeer := nftLeadPeerOverride
defer func() {
nftTransportOverride = origTransport
nftLeadPeerOverride = origPeer
}()
nftLeadPeerOverride = "lead"
nftTransportOverride = &mockNftTransport{
execErr: map[string]error{
"nft list table inet orca-ingress": errors.New("connection refused"),
},
}
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"nft", "show"})
if err := rootCmd.Execute(); err == nil {
t.Fatal("expected error from exec failure, got nil")
}
}
var _ context.Context = context.Background()