Files
flvx/go-backend/internal/store/repo/repository_nftables_test.go
T

324 lines
7.0 KiB
Go

package repo
import (
"path/filepath"
"strings"
"testing"
"time"
)
func TestNftablesNodeModeSSHConfigAndBindingPersistence(t *testing.T) {
r, err := Open(":memory:")
if err != nil {
t.Fatalf("open repo: %v", err)
}
defer r.Close()
now := time.Now().UnixMilli()
if err := r.CreateNode(
"nft-node",
"secret",
"203.0.113.10",
nil,
nil,
"10000-20000",
nil,
nil,
nil,
nil,
nil,
0,
0,
0,
now,
1,
"[::]",
"[::]",
1,
0,
nil,
nil,
nil,
nil,
"nftables",
); err != nil {
t.Fatalf("CreateNode: %v", err)
}
nodes, err := r.ListNodes()
if err != nil {
t.Fatalf("ListNodes: %v", err)
}
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
nodeID := nodes[0]["id"].(int64)
if got := nodes[0]["forwardMode"]; got != "nftables" {
t.Fatalf("expected forwardMode nftables, got %#v", got)
}
cfg := NftSSHConfigInput{
Host: "203.0.113.10",
Port: 22,
Username: "root",
AuthType: "private_key",
PrivateKey: "encrypted-private-key",
SudoMode: "none",
}
if err := r.UpsertNodeSSHConfig(nodeID, cfg, now); err != nil {
t.Fatalf("UpsertNodeSSHConfig: %v", err)
}
loaded, err := r.GetNodeSSHConfig(nodeID)
if err != nil {
t.Fatalf("GetNodeSSHConfig: %v", err)
}
if loaded.Host != cfg.Host || loaded.Port != cfg.Port || loaded.Username != cfg.Username || loaded.AuthType != cfg.AuthType {
t.Fatalf("unexpected ssh config: %+v", loaded)
}
binding := NftRuleBindingInput{
ForwardID: 42,
NodeID: nodeID,
InPort: 24000,
Protocols: "tcp,udp",
TargetAddr: "198.51.100.20:443",
BindIP: "",
RuleHash: "hash-a",
Status: "applied",
LastError: "",
}
if err := r.UpsertNftRuleBinding(binding, now); err != nil {
t.Fatalf("UpsertNftRuleBinding: %v", err)
}
bindings, err := r.ListNftRuleBindingsByNode(nodeID)
if err != nil {
t.Fatalf("ListNftRuleBindingsByNode: %v", err)
}
if len(bindings) != 1 {
t.Fatalf("expected 1 binding, got %d", len(bindings))
}
if bindings[0].ForwardID != 42 || bindings[0].RuleHash != "hash-a" || bindings[0].Status != "applied" {
t.Fatalf("unexpected binding: %+v", bindings[0])
}
if err := r.MarkNftRuleBindingError(42, nodeID, "nft failed", now+1); err != nil {
t.Fatalf("MarkNftRuleBindingError: %v", err)
}
bindings, err = r.ListNftRuleBindingsByNode(nodeID)
if err != nil {
t.Fatalf("ListNftRuleBindingsByNode after error: %v", err)
}
if bindings[0].Status != "error" || !strings.Contains(bindings[0].LastError, "nft failed") {
t.Fatalf("expected error binding, got %+v", bindings[0])
}
if err := r.DeleteNftRuleBindingsByForward(42); err != nil {
t.Fatalf("DeleteNftRuleBindingsByForward: %v", err)
}
bindings, err = r.ListNftRuleBindingsByNode(nodeID)
if err != nil {
t.Fatalf("ListNftRuleBindingsByNode after delete: %v", err)
}
if len(bindings) != 0 {
t.Fatalf("expected no bindings after delete, got %+v", bindings)
}
}
func TestNftablesNodeSSHConfigSurvivesRepositoryReopen(t *testing.T) {
tests := []struct {
name string
cfg NftSSHConfigInput
}{
{
name: "password",
cfg: NftSSHConfigInput{
Host: "203.0.113.10",
Port: 2222,
Username: "root",
AuthType: "password",
Password: "ssh-password",
Passphrase: "key-passphrase",
SudoMode: "sudo",
},
},
{
name: "private-key",
cfg: NftSSHConfigInput{
Host: "203.0.113.11",
Port: 2223,
Username: "admin",
AuthType: "private_key",
PrivateKey: "PRIVATE-KEY-SHOULD-PERSIST",
Passphrase: "key-passphrase",
SudoMode: "sudo_su",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "nftables-ssh.sqlite")
r, err := Open(dbPath)
if err != nil {
t.Fatalf("open repo: %v", err)
}
now := time.Now().UnixMilli()
if err := r.CreateNode(
"nft-node",
"secret",
tt.cfg.Host,
nil,
nil,
"10000-20000",
nil,
nil,
nil,
nil,
nil,
0,
0,
0,
now,
1,
"[::]",
"[::]",
1,
0,
nil,
nil,
nil,
nil,
"nftables",
); err != nil {
t.Fatalf("CreateNode: %v", err)
}
nodes, err := r.ListNodes()
if err != nil {
t.Fatalf("ListNodes: %v", err)
}
nodeID := nodes[0]["id"].(int64)
if err := r.UpsertNodeSSHConfig(nodeID, tt.cfg, now); err != nil {
t.Fatalf("UpsertNodeSSHConfig: %v", err)
}
if err := r.Close(); err != nil {
t.Fatalf("close repo: %v", err)
}
reopened, err := Open(dbPath)
if err != nil {
t.Fatalf("reopen repo: %v", err)
}
defer reopened.Close()
loaded, err := reopened.GetNodeSSHConfig(nodeID)
if err != nil {
t.Fatalf("GetNodeSSHConfig after reopen: %v", err)
}
if loaded.Host != tt.cfg.Host || loaded.Port != tt.cfg.Port || loaded.Username != tt.cfg.Username || loaded.AuthType != tt.cfg.AuthType || loaded.SudoMode != tt.cfg.SudoMode {
t.Fatalf("unexpected ssh config after reopen: %+v", loaded)
}
if tt.cfg.Password != "" && (!loaded.Password.Valid || loaded.Password.String != tt.cfg.Password) {
t.Fatalf("expected password to persist after reopen, got %+v", loaded.Password)
}
if tt.cfg.PrivateKey != "" && (!loaded.PrivateKey.Valid || loaded.PrivateKey.String != tt.cfg.PrivateKey) {
t.Fatalf("expected private key to persist after reopen, got %+v", loaded.PrivateKey)
}
if tt.cfg.Passphrase != "" && (!loaded.Passphrase.Valid || loaded.Passphrase.String != tt.cfg.Passphrase) {
t.Fatalf("expected passphrase to persist after reopen, got %+v", loaded.Passphrase)
}
})
}
}
func TestUpdateNodeWithoutForwardModePreservesExistingMode(t *testing.T) {
r, err := Open(":memory:")
if err != nil {
t.Fatalf("open repo: %v", err)
}
defer r.Close()
now := time.Now().UnixMilli()
if err := r.CreateNode(
"nft-node",
"secret",
"203.0.113.11",
nil,
nil,
"10000-20000",
nil,
nil,
nil,
nil,
nil,
0,
0,
0,
now,
1,
"[::]",
"[::]",
1,
0,
nil,
nil,
nil,
nil,
"nftables",
); err != nil {
t.Fatalf("CreateNode: %v", err)
}
nodes, err := r.ListNodes()
if err != nil {
t.Fatalf("ListNodes: %v", err)
}
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
nodeID := nodes[0]["id"].(int64)
if err := r.UpdateNode(
nodeID,
"nft-node-updated",
"203.0.113.11",
nil,
nil,
"10000-20000",
nil,
nil,
nil,
nil,
nil,
"",
0,
0,
0,
"[::]",
"[::]",
now+1,
); err != nil {
t.Fatalf("UpdateNode: %v", err)
}
gotNode, err := r.GetNodeRecord(nodeID)
if err != nil {
t.Fatalf("GetNodeRecord: %v", err)
}
if gotNode == nil {
t.Fatal("expected node record, got nil")
}
if gotNode.ForwardMode != "nftables" {
t.Fatalf("expected mapped forward mode nftables, got %q", gotNode.ForwardMode)
}
nodes, err = r.ListNodes()
if err != nil {
t.Fatalf("ListNodes after update: %v", err)
}
if got := nodes[0]["forwardMode"]; got != "nftables" {
t.Fatalf("expected persisted forwardMode nftables after update, got %#v", got)
}
}