continuous-integration/drone Build is failing
Migrate the workspace to React and Ant Design, improve account forms and responsive layouts, support outbound diagnostics and SSH fingerprint confirmation, and persist the management startup port. Preserve database migrations, remove S3 settings storage, and improve gateway audit handling.
135 lines
4.8 KiB
Go
135 lines
4.8 KiB
Go
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")
|
|
}
|
|
}
|