package model import "testing" func TestGatewayAccountSelectionAndMerge(t *testing.T) { p := Defaults() p.Name = "gateway" p.Type = "mysql" p.ListenPort = 3306 p.GatewayUser = "old" p.GatewayPassword = "old-secret" patch := map[string]any{"gatewayAccounts": []any{map[string]any{"id": "a", "username": "one", "password": "first"}, map[string]any{"id": "b", "username": "two", "password": "second", "disabled": true}}} p, err := Merge(p, patch) if err != nil { t.Fatal(err) } if err = p.ValidateGateway(); err != nil { t.Fatal(err) } if _, ok := p.GatewayAccount("old"); ok { t.Fatal("legacy account still authenticates") } if _, ok := p.GatewayAccount("two"); ok { t.Fatal("disabled account authenticates") } patch["gatewayAccounts"] = []any{map[string]any{"id": "b", "username": "two", "disabled": true}, map[string]any{"id": "a", "username": "renamed"}} p, err = Merge(p, patch) if err != nil { t.Fatal(err) } if p.GatewayAccounts[1].Password != "first" { t.Fatal("password was not retained by ID") } redacted, flags := p.Redacted() if redacted.GatewayAccounts[1].Password != "" || !flags["gatewayAccount:a:password"] || p.GatewayAccounts[1].Password != "first" { t.Fatal("redaction mutated snapshot or exposed password") } p.AccessMode = "readonly" a := GatewayAccount{AccessMode: "readwrite"} if a.Apply(p).AccessMode != "readwrite" { t.Fatal("explicit account policy was overridden by legacy gateway mode") } } func TestGatewayValidationAndRequiredTargetUser(t *testing.T) { anonymous := Defaults() anonymous.Name, anonymous.Type, anonymous.ListenPort = "anonymous Redis gateway", "redis", 6379 if err := anonymous.ValidateGateway(); err != nil { t.Fatalf("account-free Redis gateway rejected: %v", err) } anonymous.Type = "mongodb" if err := anonymous.ValidateGateway(); err != nil { t.Fatalf("account-free MongoDB gateway rejected: %v", err) } p := Defaults() p.Name = "gateway" p.Type = "redis" p.ListenPort = 6379 p.GatewayAccounts = []GatewayAccount{{ID: "client", Username: "user", Password: "pass"}} if err := p.ValidateGateway(); err != nil { t.Fatal(err) } p.TargetHost = "localhost" p.TargetPort = 6379 p.TargetPassword = "target-secret" if err := p.Validate(); err != nil { t.Fatalf("Redis password-only target rejected: %v", err) } p.TargetPassword = "" if err := p.Validate(); err == nil { t.Fatal("empty Redis username without a password accepted") } p.TargetUser = " " p.TargetPassword = "target-secret" if err := p.Validate(); err == nil { t.Fatal("blank target username accepted") } p.TargetUser = "validation" p.GatewayAccounts[0].Username = "" if err := p.ValidateGateway(); err != nil { t.Fatalf("Redis password-only gateway rejected: %v", err) } p.GatewayAccounts[0].Username = " " if err := p.ValidateGateway(); err == nil { t.Fatal("blank gateway username accepted") } } func TestPasswordOnlyAccountsAreRedisSpecific(t *testing.T) { protocols := []string{"mysql", "mongodb", "redis", "rabbitmq", "pgsql", "http", "https", "sqlserver", "ssh", "sftp", "tcp"} for _, protocol := range protocols { t.Run(protocol+"/target", func(t *testing.T) { target := TargetDefaults() target.Name, target.Type = "Password-only target", protocol target.TargetHost, target.TargetPort = "target.invalid", 12345 target.Accounts = []TargetAccount{{ID: "password-only", Remark: "Password only", Password: "target-secret", AuthMethod: "password"}} err := target.Validate() if (err == nil) != (protocol == "redis") { t.Fatalf("password-only target validation error = %v", err) } }) t.Run(protocol+"/gateway", func(t *testing.T) { gateway := Defaults() gateway.Name, gateway.Type, gateway.ListenPort = "Password-only gateway", protocol, 12345 gateway.GatewayAccounts = []GatewayAccount{{ID: "password-only", Password: "gateway-secret"}} if IsSSH(protocol) { gateway.AccessMode = "" } err := gateway.ValidateGateway() if (err == nil) != (protocol == "redis") { t.Fatalf("password-only gateway validation error = %v", err) } }) } } func TestRedisPasswordOnlyGatewayUsesDefaultUserIdentity(t *testing.T) { p := Defaults() p.Name, p.Type, p.ListenPort = "Password-only gateway", "redis", 6379 p.GatewayAccounts = []GatewayAccount{{ID: "password-only", Password: "gateway-secret"}} if err := p.ValidateGateway(); err != nil { t.Fatal(err) } account, ok := p.GatewayAccount("default") if !ok || account.ID != "password-only" { t.Fatalf("default Redis identity did not select the password-only account: %+v, %v", account, ok) } p.GatewayAccounts = append(p.GatewayAccounts, GatewayAccount{ID: "explicit-default", Username: "default", Password: "other-secret"}) if err := p.ValidateGateway(); err == nil { t.Fatal("ambiguous empty and default Redis gateway usernames accepted") } }