You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

180 lines
6.9 KiB

package mock
import (
"context"
"path/filepath"
"testing"
"time"
"tera-clone/services/tera-api/internal/domain"
"tera-clone/services/tera-api/internal/externalauth"
)
type dualExternalAuthenticator struct{}
func (dualExternalAuthenticator) Authenticate(_ context.Context, method string, server externalauth.Server, username, password string, _ time.Duration) (bool, error) {
return (method == "radius" || method == "tacacs") && server.Secret == method+"-test-secret" && username == "remote-user" && password == "remote-password", nil
}
func newExternalSecretTestStore(t *testing.T) (*Store, StoreOptions) {
t.Helper()
directory := t.TempDir()
options := StoreOptions{
ConfigPath: filepath.Join(directory, "config.json"), SecretPath: filepath.Join(directory, "secrets.json"),
ExternalAuthenticator: dualExternalAuthenticator{},
}
store, err := NewStoreWithOptions(fixedClock, options)
if err != nil {
t.Fatal(err)
}
enabled, fallback, method, role := true, true, "radius", "port_operator"
_, err = store.UpdateExternalAuth(context.Background(), domain.ExternalAuthUpdate{
Enabled: &enabled, PrimaryMethod: &method, LocalFallback: &fallback, DefaultRole: &role,
RadiusServers: []domain.ExternalAuthServer{{ServerID: "radius-primary", Host: "radius.local", Port: 1645, Secret: "radius-test-secret", Enabled: true}},
TACACSServers: []domain.ExternalAuthServer{{ServerID: "tacacs-primary", Host: "tacacs.local", Port: 49, Secret: "tacacs-test-secret", Enabled: true}},
})
if err != nil {
t.Fatal(err)
}
if _, err := store.SaveAndApplyConfig(context.Background()); err != nil {
t.Fatal(err)
}
return store, options
}
func assertExternalTestSecrets(t *testing.T, store *Store, radius, tacacs string) {
t.Helper()
for method, expected := range map[string]string{"radius": radius, "tacacs": tacacs} {
for _, phase := range []string{"running", "persistent"} {
key := externalAuthSecretKey(phase, method, method+"-primary")
value, exists := store.secrets.Get(key)
if expected == "" {
if exists {
t.Errorf("cleared key %s was resurrected", key)
}
} else if !exists || string(value) != expected {
t.Errorf("key %s was not preserved", key)
}
}
}
}
func TestExternalAuthPolicyOnlyUpdatesPreserveBothSecrets(t *testing.T) {
for _, workflow := range []string{"apply-save", "save-apply", "save-and-apply"} {
t.Run(workflow, func(t *testing.T) {
store, options := newExternalSecretTestStore(t)
for _, method := range []string{"tacacs", "radius", "tacacs", "radius"} {
if _, err := store.UpdateExternalAuth(context.Background(), domain.ExternalAuthUpdate{PrimaryMethod: &method}); err != nil {
t.Fatal(err)
}
role := "administrator"
if _, err := store.UpdateExternalAuth(context.Background(), domain.ExternalAuthUpdate{DefaultRole: &role}); err != nil {
t.Fatal(err)
}
var err error
switch workflow {
case "apply-save":
_, err = store.ApplyConfig(context.Background())
if err == nil {
_, err = store.SaveConfig(context.Background())
}
case "save-apply":
_, err = store.SaveConfig(context.Background())
if err == nil {
_, err = store.ApplyConfig(context.Background())
}
case "save-and-apply":
_, err = store.SaveAndApplyConfig(context.Background())
}
if err != nil {
t.Fatal(err)
}
assertExternalTestSecrets(t, store, "radius-test-secret", "tacacs-test-secret")
// Also verify persisted state after reopening the store.
store, err = NewStoreWithOptions(fixedClock, options)
if err != nil {
t.Fatal(err)
}
assertExternalTestSecrets(t, store, "radius-test-secret", "tacacs-test-secret")
session, err := store.Login(context.Background(), domain.AuthLoginRequest{Username: "remote-user", Password: "remote-password"})
if err != nil || session.Actor != "external-remote-user" || session.Role != role {
t.Fatalf("external %s login failed after policy update: %v", method, err)
}
}
})
}
}
func TestExternalAuthSingleProviderUpdatePreservesOtherSecret(t *testing.T) {
for _, method := range []string{"radius", "tacacs"} {
t.Run(method, func(t *testing.T) {
store, _ := newExternalSecretTestStore(t)
servers := []domain.ExternalAuthServer{{ServerID: method + "-primary", Host: method + ".local", Port: 49, Enabled: true}}
update := domain.ExternalAuthUpdate{}
if method == "radius" {
servers[0].Port = 1645
update.RadiusServers = servers
} else {
update.TACACSServers = servers
}
if _, err := store.UpdateExternalAuth(context.Background(), update); err != nil {
t.Fatal(err)
}
if _, err := store.SaveAndApplyConfig(context.Background()); err != nil {
t.Fatal(err)
}
assertExternalTestSecrets(t, store, "radius-test-secret", "tacacs-test-secret")
})
}
}
func TestExternalAuthExplicitClearSurvivesPolicyUpdate(t *testing.T) {
store, _ := newExternalSecretTestStore(t)
_, err := store.UpdateExternalAuth(context.Background(), domain.ExternalAuthUpdate{
TACACSServers: []domain.ExternalAuthServer{{ServerID: "tacacs-primary", Host: "tacacs.local", Port: 49, Enabled: false, ClearSecret: true}},
})
if err != nil {
t.Fatal(err)
}
role := "administrator"
if _, err := store.UpdateExternalAuth(context.Background(), domain.ExternalAuthUpdate{DefaultRole: &role}); err != nil {
t.Fatal(err)
}
if _, err := store.SaveAndApplyConfig(context.Background()); err != nil {
t.Fatal(err)
}
assertExternalTestSecrets(t, store, "radius-test-secret", "")
}
func TestExternalAuthMissingSourceCannotEraseStoredSecret(t *testing.T) {
store, _ := newExternalSecretTestStore(t)
// Simulate a pending transaction written by the old implementation, which
// omitted unchanged secrets from the pending namespace.
store.pendingExternalAuth = cloneExternalAuthPointer(store.runningExternalAuth)
store.pendingExternalAuth.PrimaryMethod = "tacacs"
if _, err := store.ApplyConfig(context.Background()); err == nil {
t.Fatal("applying missing source secrets should fail instead of deleting destination secrets")
}
assertExternalTestSecrets(t, store, "radius-test-secret", "tacacs-test-secret")
}
func TestExternalAuthReportsMissingProtectedSecretAndRejectsPolicyOnlyUpdate(t *testing.T) {
store, _ := newExternalSecretTestStore(t)
for _, phase := range []string{"running", "persistent"} {
if err := store.secrets.Delete(externalAuthSecretKey(phase, "radius", "radius-primary")); err != nil {
t.Fatal(err)
}
}
settings, err := store.ExternalAuth(context.Background())
if err != nil {
t.Fatal(err)
}
if settings.RadiusServers[0].SecretStored || !settings.TACACSServers[0].SecretStored {
t.Fatal("secret availability must reflect the protected store, not stale configuration markers")
}
role := "administrator"
if _, err := store.UpdateExternalAuth(context.Background(), domain.ExternalAuthUpdate{DefaultRole: &role}); err == nil {
t.Fatal("a policy-only update with a missing protected secret must fail")
}
assertExternalTestSecrets(t, store, "", "tacacs-test-secret")
}