feat(vault-tui): implement KV client and service for managing secrets

- Added internal/vault/client.go for creating a Vault client with configuration settings.
- Introduced internal/vault/errors.go to classify Vault API errors for better UI handling.
- Created internal/vault/kv.go to manage KV secrets, including listing, reading, writing, and deleting operations.
- Implemented internal/vault/mounts.go to list and describe secret engine mounts.
- Developed internal/vault/service.go to provide a unified entry point for Vault operations.
- Added internal/vault/kv_test.go for comprehensive testing of KV operations.
- Introduced internal/ui/toast.go for transient notifications in the UI.
- Added renovate.json for dependency management and updates.
This commit is contained in:
2026-08-14 11:09:03 +02:00
commit ae30ba1240
85 changed files with 9413 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
package auth
import (
"context"
"fmt"
"net/url"
"time"
"github.com/hashicorp/vault/api"
)
func init() { register(ldapMethod{}) }
type ldapMethod struct{}
func (ldapMethod) Name() string { return "ldap" }
func (ldapMethod) DisplayName() string { return "LDAP" }
func (ldapMethod) DefaultMount() string { return "ldap" }
func (ldapMethod) Fields() []Field {
return []Field{
{Name: "username", Label: "Username", Kind: FieldText, Required: true,
EnvFallback: []string{"VAULT_USERNAME", "LOGNAME", "USER"}},
{Name: "password", Label: "Password", Kind: FieldSecret, Required: true,
EnvFallback: []string{"VAULT_LDAP_PASSWORD", "VAULT_PASSWORD"}},
}
}
func (ldapMethod) Login(ctx context.Context, c *api.Client, req Request) (*api.Secret, error) {
mount := mountOf(req, "ldap")
return loginWrite(ctx, c, mount, "login/"+url.PathEscape(req.Creds.Get("username")),
map[string]interface{}{"password": req.Creds.Get("password")})
}
func init() { register(oktaMethod{}) }
// oktaMethod mirrors ldapMethod's request shape but adds an optional
// best-effort poll of auth/<mount>/verify/<nonce> for Okta Verify
// number-matching, reported via Request.Events so the TUI can show "tap 42
// in Okta Verify". This endpoint is not documented in Vault's public API
// reference; treat any poll failure as non-fatal and fall back to waiting
// for the original login response.
type oktaMethod struct{}
func (oktaMethod) Name() string { return "okta" }
func (oktaMethod) DisplayName() string { return "Okta" }
func (oktaMethod) DefaultMount() string { return "okta" }
func (oktaMethod) Description() string {
return "Okta username/password, with optional TOTP and Okta Verify push."
}
func (oktaMethod) Fields() []Field {
return []Field{
{Name: "username", Label: "Username", Kind: FieldText, Required: true,
EnvFallback: []string{"VAULT_USERNAME", "LOGNAME", "USER"}},
{Name: "password", Label: "Password", Kind: FieldSecret, Required: true,
EnvFallback: []string{"VAULT_PASSWORD"}},
{Name: "totp", Label: "TOTP code (optional)", Kind: FieldText},
}
}
func (oktaMethod) Login(ctx context.Context, c *api.Client, req Request) (*api.Secret, error) {
mount := mountOf(req, "okta")
data := map[string]interface{}{"password": req.Creds.Get("password")}
if totp := req.Creds.Get("totp"); totp != "" {
data["totp"] = totp
}
nonce := randomNonce(12)
data["nonce"] = nonce
if req.Events != nil {
go pollOktaVerify(ctx, c, mount, nonce, req)
}
req.Emit(Event{Kind: EventStatus, Message: "contacting Okta…"})
return loginWrite(ctx, c, mount, "login/"+url.PathEscape(req.Creds.Get("username")), data)
}
func pollOktaVerify(ctx context.Context, c *api.Client, mount, nonce string, req Request) {
t := time.NewTicker(1 * time.Second)
defer t.Stop()
p := fmt.Sprintf("auth/%s/verify/%s", mount, nonce)
for {
select {
case <-ctx.Done():
return
case <-t.C:
sec, err := c.Logical().ReadWithContext(ctx, p)
if err != nil || sec == nil || sec.Data == nil {
continue // best-effort; the primary login request is the source of truth
}
if answer, ok := sec.Data["correct_answer"].(string); ok && answer != "" {
req.Emit(Event{Kind: EventStatus, Message: fmt.Sprintf("in Okta Verify, tap the number %q", answer)})
return
}
}
}
}