diff --git a/configs/vui.yaml b/configs/vui.yaml index 7939e09..375da1e 100644 --- a/configs/vui.yaml +++ b/configs/vui.yaml @@ -6,8 +6,9 @@ ui: theme: "default" show_hidden_secrets: false -vaults: +profiles: vault-token: + engine: "vault" address: "http://localhost:8200" auth_method: "token" namespace: "" @@ -15,6 +16,7 @@ vaults: token: "${VAULT_TOKEN}" vault-ldap: + engine: "vault" address: "http://localhost:8200" auth_method: "ldap" namespace: "" @@ -23,6 +25,7 @@ vaults: password: "${LDAP_PASSWORD}" vault-aws: + engine: "vault" address: "http://localhost:8200" auth_method: "aws" namespace: "" @@ -33,6 +36,7 @@ vaults: aws_session_token: "${AWS_SESSION_TOKEN}" vault-k8s: + engine: "vault" address: "http://localhost:8200" auth_method: "kubernetes" namespace: "" @@ -43,6 +47,7 @@ vaults: k8s_ttl: 3600 vault-oidc: + engine: "vault" address: "http://localhost:8200" auth_method: "oidc" namespace: "" @@ -50,6 +55,7 @@ vaults: oidc_role: "vui-oidc-role" vault-jwt: + engine: "vault" address: "http://localhost:8200" auth_method: "jwt" namespace: "" @@ -58,6 +64,7 @@ vaults: jwt: "${JWT}" vault-userpass: + engine: "vault" address: "http://localhost:8200" auth_method: "userpass" namespace: "" @@ -66,6 +73,7 @@ vaults: password: "${USERPASS_PASSWORD}" vault-cert: + engine: "vault" address: "https://localhost:8208" cert_path: "./sandbox/files/vault.crt" auth_method: "cert" @@ -76,6 +84,7 @@ vaults: cert_key_path: "./sandbox/vol/certs/client.key" vault-azure-todo: + engine: "vault" address: "http://localhost:8201" auth_method: "azure" namespace: "" diff --git a/internal/backend/profiles_interactor.go b/internal/backend/profiles_interactor.go index cb4fbad..609e636 100644 --- a/internal/backend/profiles_interactor.go +++ b/internal/backend/profiles_interactor.go @@ -118,12 +118,18 @@ func (i *profileInteractor) ReloadConfiguration() error { func (i *profileInteractor) initializeConnections() error { factory := engines.NewEnginesFactory(i.logger) - for name, profile := range i.config.Vaults { + for name, profile := range i.config.Profiles { p := profile - client, err := factory.SetupEngine("vault", &p) // TODO: add support for other engines + // Use the engine from the profile, default to "vault" for backward compatibility + engine := p.Engine + if engine == "" { + engine = "vault" + } + + client, err := factory.SetupEngine(engine, &p) if err != nil { - i.logger.Warnf("Failed to setup engine for profile '%s': %v", name, err) + i.logger.Warnf("Failed to setup engine '%s' for profile '%s': %v", engine, name, err) continue } diff --git a/internal/config/config.go b/internal/config/config.go index 8535afb..3d1d080 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,9 +11,9 @@ import ( // Config represents the application configuration type Config struct { - App AppConfig `mapstructure:"app"` - UI UIConfig `mapstructure:"ui"` - Vaults map[string]VaultProfile `mapstructure:"vaults"` + App AppConfig `mapstructure:"app"` + UI UIConfig `mapstructure:"ui"` + Profiles map[string]Profile `mapstructure:"profiles"` } // AppConfig contains general application settings @@ -28,8 +28,9 @@ type UIConfig struct { ShowHiddenSecrets bool `mapstructure:"show_hidden_secrets"` } -// VaultProfile represents a vault connection profile -type VaultProfile struct { +// Profile represents a connection profile +type Profile struct { + Engine string `mapstructure:"engine"` Address string `mapstructure:"address"` CertPath string `mapstructure:"cert_path,omitempty"` AuthMethod string `mapstructure:"auth_method"` @@ -138,11 +139,12 @@ func setDefaults() { viper.SetDefault("ui.theme", "default") viper.SetDefault("ui.show_hidden_secrets", false) - // Vaults defaults - viper.SetDefault("vaults.local.address", "http://localhost:8200") - viper.SetDefault("vaults.local.auth_method", "token") - viper.SetDefault("vaults.local.namespace", "") - viper.SetDefault("vaults.local.auth_config.token", "${VAULT_TOKEN}") + // Profiles defaults + viper.SetDefault("profiles.local.engine", "vault") + viper.SetDefault("profiles.local.address", "http://localhost:8200") + viper.SetDefault("profiles.local.auth_method", "token") + viper.SetDefault("profiles.local.namespace", "") + viper.SetDefault("profiles.local.auth_config.token", "${VAULT_TOKEN}") } // Save saves the configuration to a file @@ -157,7 +159,7 @@ func (c *Config) Save() error { // Convert config back to viper format for saving viper.Set("app", c.App) viper.Set("ui", c.UI) - viper.Set("vaults", c.Vaults) + viper.Set("profiles", c.Profiles) return viper.WriteConfigAs(configFile) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 8213735..ff26c98 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -17,8 +17,9 @@ func TestLoad(t *testing.T) { assert.Equal(t, "info", config.App.LogLevel) assert.Equal(t, false, config.UI.ShowHiddenSecrets) assert.Equal(t, "default", config.UI.Theme) - assert.Equal(t, "http://localhost:8200", config.Vaults["local"].Address) - assert.Equal(t, "token", config.Vaults["local"].AuthMethod) + assert.Equal(t, "http://localhost:8200", config.Profiles["local"].Address) + assert.Equal(t, "token", config.Profiles["local"].AuthMethod) + assert.Equal(t, "vault", config.Profiles["local"].Engine) } func TestConfigSave(t *testing.T) { @@ -26,8 +27,9 @@ func TestConfigSave(t *testing.T) { UI: UIConfig{ Theme: "light", }, - Vaults: map[string]VaultProfile{ + Profiles: map[string]Profile{ "default": { + Engine: "vault", Address: "https://test.vault.com", AuthMethod: "token", }, diff --git a/internal/engines/engines_factory.go b/internal/engines/engines_factory.go index 21f87d7..ec69757 100644 --- a/internal/engines/engines_factory.go +++ b/internal/engines/engines_factory.go @@ -18,7 +18,7 @@ func NewEnginesFactory(logger *logrus.Logger) *EnginesFactory { } } -func (f *EnginesFactory) SetupEngine(name string, profile *config.VaultProfile) (SecretEngine, error) { +func (f *EnginesFactory) SetupEngine(name string, profile *config.Profile) (SecretEngine, error) { switch name { case "vault": return vault.NewVaultClient(f.logger, profile) diff --git a/internal/engines/engines_factory_test.go b/internal/engines/engines_factory_test.go index fc4b81f..f374d77 100644 --- a/internal/engines/engines_factory_test.go +++ b/internal/engines/engines_factory_test.go @@ -14,7 +14,8 @@ func TestEnginesFactory_SetupEngine(t *testing.T) { assert.NotNil(t, factory) t.Run("vault", func(t *testing.T) { - engine, err := factory.SetupEngine("vault", &config.VaultProfile{ + engine, err := factory.SetupEngine("vault", &config.Profile{ + Engine: "vault", Address: "http://localhost:8200", AuthMethod: "token", }) @@ -23,7 +24,8 @@ func TestEnginesFactory_SetupEngine(t *testing.T) { }) t.Run("unknown", func(t *testing.T) { - engine, err := factory.SetupEngine("unknown", &config.VaultProfile{ + engine, err := factory.SetupEngine("unknown", &config.Profile{ + Engine: "unknown", Address: "http://localhost:8200", }) assert.Error(t, err) diff --git a/internal/engines/vault/auth/aws.go b/internal/engines/vault/auth/aws.go index 6f82e79..bab7bdd 100644 --- a/internal/engines/vault/auth/aws.go +++ b/internal/engines/vault/auth/aws.go @@ -11,7 +11,7 @@ import ( ) // authenticateWithAWS authenticates using AWS -func (am *AuthManager) authenticateWithAWS(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithAWS(client *api.Client, profile *config.Profile) error { accessKeyID := profile.AuthConfig.AWSAccessKeyID if accessKeyID == "" { return fmt.Errorf("aws_access_key_id is required for AWS authentication") diff --git a/internal/engines/vault/auth/azure.go b/internal/engines/vault/auth/azure.go index 75f5889..f60246c 100644 --- a/internal/engines/vault/auth/azure.go +++ b/internal/engines/vault/auth/azure.go @@ -10,7 +10,7 @@ import ( ) // authenticateWithAzure authenticates using Azure -func (am *AuthManager) authenticateWithAzure(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithAzure(client *api.Client, profile *config.Profile) error { role := profile.AuthConfig.AzureRole if role == "" { return fmt.Errorf("azure_role is required for Azure authentication") diff --git a/internal/engines/vault/auth/cert.go b/internal/engines/vault/auth/cert.go index 67313f9..f58d85d 100644 --- a/internal/engines/vault/auth/cert.go +++ b/internal/engines/vault/auth/cert.go @@ -8,7 +8,7 @@ import ( ) // authenticateWithCert authenticates using certificates -func (am *AuthManager) authenticateWithCert(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithCert(client *api.Client, profile *config.Profile) error { certName := profile.AuthConfig.CertName if certName == "" { return fmt.Errorf("cert_name is required for cert authentication") diff --git a/internal/engines/vault/auth/gcp.go b/internal/engines/vault/auth/gcp.go index d0d8f50..ef2cd76 100644 --- a/internal/engines/vault/auth/gcp.go +++ b/internal/engines/vault/auth/gcp.go @@ -9,7 +9,7 @@ import ( ) // authenticateWithGCP authenticates using GCP -func (am *AuthManager) authenticateWithGCP(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithGCP(client *api.Client, profile *config.Profile) error { role := profile.AuthConfig.GCPRole if role == "" { return fmt.Errorf("gcp_role is required for GCP authentication") @@ -45,7 +45,7 @@ func (am *AuthManager) authenticateWithGCP(client *api.Client, profile *config.V } // getGCPCredentials gets GCP credentials from various sources -func (am *AuthManager) getGCPCredentials(profile *config.VaultProfile) (string, error) { +func (am *AuthManager) getGCPCredentials(profile *config.Profile) (string, error) { // Check if credentials are provided directly if credentials := profile.AuthConfig.GCPCredentials; credentials != "" { return credentials, nil diff --git a/internal/engines/vault/auth/jwt.go b/internal/engines/vault/auth/jwt.go index c93ddc4..86ea019 100644 --- a/internal/engines/vault/auth/jwt.go +++ b/internal/engines/vault/auth/jwt.go @@ -8,7 +8,7 @@ import ( ) // authenticateWithJWT authenticates using JWT -func (am *AuthManager) authenticateWithJWT(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithJWT(client *api.Client, profile *config.Profile) error { role := profile.AuthConfig.JWTRole if role == "" { return fmt.Errorf("jwt_role is required for JWT authentication") diff --git a/internal/engines/vault/auth/kubernetes.go b/internal/engines/vault/auth/kubernetes.go index 195c05e..6c12e8f 100644 --- a/internal/engines/vault/auth/kubernetes.go +++ b/internal/engines/vault/auth/kubernetes.go @@ -17,7 +17,7 @@ import ( ) // authenticateWithKubernetes authenticates using Kubernetes -func (am *AuthManager) authenticateWithKubernetes(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithKubernetes(client *api.Client, profile *config.Profile) error { role := profile.AuthConfig.K8sRole if role == "" { return fmt.Errorf("k8s_role is required for Kubernetes authentication") @@ -58,7 +58,7 @@ func (am *AuthManager) authenticateWithKubernetes(client *api.Client, profile *c } // getKubernetesToken gets Kubernetes service account token -func (am *AuthManager) getKubernetesToken(profile *config.VaultProfile) (string, error) { +func (am *AuthManager) getKubernetesToken(profile *config.Profile) (string, error) { homeDir, _ := os.UserHomeDir() var ( diff --git a/internal/engines/vault/auth/ldap.go b/internal/engines/vault/auth/ldap.go index 256c8a6..bb4f732 100644 --- a/internal/engines/vault/auth/ldap.go +++ b/internal/engines/vault/auth/ldap.go @@ -8,7 +8,7 @@ import ( ) // authenticateWithLDAP authenticates using LDAP -func (am *AuthManager) authenticateWithLDAP(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithLDAP(client *api.Client, profile *config.Profile) error { username := profile.AuthConfig.Username if username == "" { return fmt.Errorf("username is required for LDAP authentication") diff --git a/internal/engines/vault/auth/manager.go b/internal/engines/vault/auth/manager.go index 9e5e082..c07d59d 100644 --- a/internal/engines/vault/auth/manager.go +++ b/internal/engines/vault/auth/manager.go @@ -37,7 +37,7 @@ func (am *AuthManager) VerifyAuthentication(client *api.Client) error { } // Authenticate authenticates a client using the specified profile -func (am *AuthManager) Authenticate(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) Authenticate(client *api.Client, profile *config.Profile) error { switch profile.AuthMethod { case "token": return am.authenticateWithToken(client, profile) diff --git a/internal/engines/vault/auth/oidc.go b/internal/engines/vault/auth/oidc.go index 445e156..e0181ee 100644 --- a/internal/engines/vault/auth/oidc.go +++ b/internal/engines/vault/auth/oidc.go @@ -15,7 +15,7 @@ import ( "github.com/rvolykh/vui/internal/config" ) -func (am *AuthManager) authenticateWithOIDC(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithOIDC(client *api.Client, profile *config.Profile) error { role := profile.AuthConfig.OIDCRole if role == "" { return fmt.Errorf("oidc_role is required for OIDC authentication") diff --git a/internal/engines/vault/auth/token.go b/internal/engines/vault/auth/token.go index 1ec8b5a..cc387bd 100644 --- a/internal/engines/vault/auth/token.go +++ b/internal/engines/vault/auth/token.go @@ -8,7 +8,7 @@ import ( ) // authenticateWithToken authenticates using a token -func (am *AuthManager) authenticateWithToken(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithToken(client *api.Client, profile *config.Profile) error { token := profile.AuthConfig.Token if token == "" { return fmt.Errorf("token is required for token authentication") diff --git a/internal/engines/vault/auth/userpass.go b/internal/engines/vault/auth/userpass.go index 4461e16..5167b1a 100644 --- a/internal/engines/vault/auth/userpass.go +++ b/internal/engines/vault/auth/userpass.go @@ -8,7 +8,7 @@ import ( ) // authenticateWithUserpass authenticates using userpass -func (am *AuthManager) authenticateWithUserpass(client *api.Client, profile *config.VaultProfile) error { +func (am *AuthManager) authenticateWithUserpass(client *api.Client, profile *config.Profile) error { username := profile.AuthConfig.Username if username == "" { return fmt.Errorf("username is required for userpass authentication") diff --git a/internal/engines/vault/vault_client.go b/internal/engines/vault/vault_client.go index e8ca5ff..ed13917 100644 --- a/internal/engines/vault/vault_client.go +++ b/internal/engines/vault/vault_client.go @@ -17,11 +17,11 @@ import ( type VaultClient struct { apiClient *api.Client - profile *config.VaultProfile + profile *config.Profile logger *logrus.Logger } -func NewVaultClient(logger *logrus.Logger, profile *config.VaultProfile) (*VaultClient, error) { +func NewVaultClient(logger *logrus.Logger, profile *config.Profile) (*VaultClient, error) { apiConfig := api.DefaultConfig() apiConfig.Address = profile.Address diff --git a/internal/engines/vault/vault_client_test.go b/internal/engines/vault/vault_client_test.go index eabcbc2..5793a57 100644 --- a/internal/engines/vault/vault_client_test.go +++ b/internal/engines/vault/vault_client_test.go @@ -14,7 +14,8 @@ func TestVaultClient(t *testing.T) { logger := logrus.New() logger.SetLevel(logrus.ErrorLevel) - profile := &config.VaultProfile{ + profile := &config.Profile{ + Engine: "vault", Address: "http://localhost:8200", } diff --git a/internal/ui/app.go b/internal/ui/app.go index 2ca6484..92c8e11 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -207,12 +207,12 @@ func (a *App) refresh() { a.layout.Refresh() } -// switchVault shows the vault profiles screen for switching vaults +// switchVault shows the profiles screen for switching profiles func (a *App) switchVault() { a.showVaultProfiles() } -// showVaultProfiles shows the vault profiles screen +// showVaultProfiles shows the profiles screen func (a *App) showVaultProfiles() { // Mark that we're on the profiles screen a.onProfilesScreen = true diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index ab00dfe..ac9e04a 100644 --- a/internal/ui/app_test.go +++ b/internal/ui/app_test.go @@ -13,8 +13,9 @@ func TestNewApp(t *testing.T) { // Create test configuration cfg := &config.Config{ App: config.AppConfig{}, - Vaults: map[string]config.VaultProfile{ + Profiles: map[string]config.Profile{ "test": { + Engine: "vault", Address: "http://localhost:8200", AuthMethod: "token", }, diff --git a/internal/ui/layout_test.go b/internal/ui/layout_test.go index 959255d..9e300ca 100644 --- a/internal/ui/layout_test.go +++ b/internal/ui/layout_test.go @@ -12,8 +12,9 @@ import ( func TestLayoutOfflineMode(t *testing.T) { // Create test configuration cfg := &config.Config{ - Vaults: map[string]config.VaultProfile{ + Profiles: map[string]config.Profile{ "default": { + Engine: "vault", Address: "http://localhost:8200", AuthMethod: "token", }, @@ -41,8 +42,9 @@ func TestLayoutOfflineMode(t *testing.T) { func TestLayoutStructure(t *testing.T) { // Test that the layout structure is correct cfg := &config.Config{ - Vaults: map[string]config.VaultProfile{ + Profiles: map[string]config.Profile{ "default": { + Engine: "vault", Address: "http://localhost:8200", AuthMethod: "token", }, diff --git a/internal/ui/panels/profiles_table.go b/internal/ui/panels/profiles_table.go index b7c439a..b24ddfc 100644 --- a/internal/ui/panels/profiles_table.go +++ b/internal/ui/panels/profiles_table.go @@ -166,10 +166,10 @@ func (vpp *ProfilesTable) loadProfiles() error { // Clear existing items vpp.table.Clear() - // Get available vaults + // Get available profiles profiles := vpp.interactor.Profiles().ListConnections() - // Sort vaults by name (ascending) + // Sort profiles by name (ascending) sort.Strings(profiles) vpp.vaultNames = profiles @@ -185,7 +185,7 @@ func (vpp *ProfilesTable) loadProfiles() error { } if len(profiles) == 0 { - // Show a message when no vaults are configured + // Show a message when no profiles are configured cell := tview.NewTableCell("No profiles configured"). SetAlign(tview.AlignCenter). SetExpansion(1) diff --git a/internal/ui/panels/secrets_title.go b/internal/ui/panels/secrets_title.go index caccb01..74d1b5d 100644 --- a/internal/ui/panels/secrets_title.go +++ b/internal/ui/panels/secrets_title.go @@ -73,7 +73,7 @@ func (hp *SecretsTitle) getVaultInfo() string { } // Get profile for address - if profile, ok := hp.config.Vaults[currentVault]; ok { + if profile, ok := hp.config.Profiles[currentVault]; ok { return fmt.Sprintf("[green]%s[white] (%s)", currentVault, profile.Address) } diff --git a/internal/ui/panels/secrets_title_test.go b/internal/ui/panels/secrets_title_test.go index f490098..6045598 100644 --- a/internal/ui/panels/secrets_title_test.go +++ b/internal/ui/panels/secrets_title_test.go @@ -102,7 +102,7 @@ func TestSecretsTitle_GetVaultInfo_NoActiveVault(t *testing.T) { func TestSecretsTitle_GetVaultInfo_WithVaultNoProfile(t *testing.T) { fixtures := WithFixtures(t) - fixtures.cfg.Vaults = map[string]config.VaultProfile{} + fixtures.cfg.Profiles = map[string]config.Profile{} st := NewSecretsTitle(fixtures.cfg, fixtures.interactor, fixtures.logger) // With uninitialized vault manager, GetActiveVault returns empty string @@ -113,8 +113,9 @@ func TestSecretsTitle_GetVaultInfo_WithVaultNoProfile(t *testing.T) { func TestSecretsTitle_GetVaultInfo_WithProfile(t *testing.T) { fixtures := WithFixtures(t) - fixtures.cfg.Vaults = map[string]config.VaultProfile{ + fixtures.cfg.Profiles = map[string]config.Profile{ "test-vault": { + Engine: "vault", Address: "https://vault.example.com", }, } @@ -367,7 +368,7 @@ func TestSecretsTitle_Initialize_WithNilVaultManager(t *testing.T) { func TestSecretsTitle_GetVaultInfo_EmptyVaultsMap(t *testing.T) { fixtures := WithFixtures(t) - fixtures.cfg.Vaults = map[string]config.VaultProfile{} + fixtures.cfg.Profiles = map[string]config.Profile{} st := NewSecretsTitle(fixtures.cfg, fixtures.interactor, fixtures.logger) info := st.getVaultInfo() @@ -400,8 +401,9 @@ func TestSecretsTitle_UpdateVaultInfo_BeforeInitialize(t *testing.T) { func TestSecretsTitle_GetVaultInfo_WithAddress(t *testing.T) { fixtures := WithFixtures(t) - fixtures.cfg.Vaults = map[string]config.VaultProfile{ + fixtures.cfg.Profiles = map[string]config.Profile{ "prod": { + Engine: "vault", Address: "https://vault.prod.example.com", }, }