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") }