Files
vault-tui/internal/auth/method.go
T
f.weber ae30ba1240 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.
2026-08-14 11:09:03 +02:00

174 lines
4.4 KiB
Go

// Package auth defines the auth-method abstraction vault-tui uses for every
// login flow. A Method declares only which fields it needs (Fields); the
// TUI and headless CLI both render that declaration generically — a
// textinput form in the TUI, a TTY prompt or env/flag lookup on the CLI —
// so adding a new method never requires UI code (see prefill.go and
// registry.go).
package auth
import (
"context"
"time"
"github.com/hashicorp/vault/api"
)
// FieldKind tells a renderer how to present a field.
type FieldKind uint8
const (
FieldText FieldKind = iota
FieldSecret
FieldBool
FieldSelect
FieldPath
FieldInt
)
// Field declares one credential input a Method needs.
type Field struct {
Name string
Label string
Help string
Kind FieldKind
Required bool
Default string
Options []string
// EnvFallback lists env vars consulted during Prefill, in order, before
// falling back to config params. Mirrors Vault CLI behaviour (e.g.
// VAULT_AUTH_GITHUB_TOKEN-style env overrides).
EnvFallback []string
// ConfigKey is the key looked up in profile.auth.params. Empty => Name.
ConfigKey string
// Validate runs before Login and must not have side effects.
Validate func(value string) error
}
// Credentials is a filled-in form: field name -> value.
type Credentials map[string]string
func (c Credentials) Get(name string) string { return c[name] }
func (c Credentials) Has(name string) bool {
v, ok := c[name]
return ok && v != ""
}
// Redacted returns a copy safe for logs: every FieldSecret value becomes
// "***". This is the only sanctioned way to log a Credentials map.
func (c Credentials) Redacted(fields []Field) map[string]string {
secret := map[string]bool{}
for _, f := range fields {
if f.Kind == FieldSecret {
secret[f.Name] = true
}
}
out := make(map[string]string, len(c))
for k, v := range c {
if secret[k] {
out[k] = "***"
} else {
out[k] = v
}
}
return out
}
// EventKind classifies out-of-band progress from a long-running login (OIDC
// browser wait, Okta push polling).
type EventKind uint8
const (
EventStatus EventKind = iota
EventOpenURL
EventWarning
)
// Event is one piece of progress emitted on Request.Events.
type Event struct {
Kind EventKind
Message string
URL string
}
// Request is everything a Method needs to perform one login.
type Request struct {
Mount string
Namespace string
Creds Credentials
// Events, if non-nil, receives progress notifications. Login must send
// non-blockingly (see emit) so a stalled consumer can never deadlock it.
Events chan<- Event
Timeout time.Duration
}
// Emit sends a progress event to Request.Events, non-blocking. Safe to
// call even when Events is nil (a headless caller that doesn't want
// progress) or when nobody is currently draining the channel.
func (r Request) Emit(e Event) {
if r.Events == nil {
return
}
select {
case r.Events <- e:
default:
}
}
// Result is the normalised outcome of a login.
type Result struct {
Secret *api.Secret
Token string
Accessor string
Renewable bool
TTL time.Duration
Policies []string
Namespace string
}
func (r *Result) String() string {
if r == nil {
return "<nil result>"
}
return "<login result accessor=" + r.Accessor + ">"
}
func (r *Result) GoString() string { return r.String() }
// NewResult normalises an *api.Secret returned by a successful Login.
func NewResult(sec *api.Secret, namespace string) (*Result, error) {
if sec == nil || sec.Auth == nil {
return nil, errNoAuth
}
ttl, _ := sec.TokenTTL()
policies, _ := sec.TokenPolicies()
accessor, _ := sec.TokenAccessor()
return &Result{
Secret: sec,
Token: sec.Auth.ClientToken,
Accessor: accessor,
Renewable: sec.Auth.Renewable,
TTL: ttl,
Policies: policies,
Namespace: namespace,
}, nil
}
// Method is the single abstraction every auth method implements.
//
// Contract: Fields() is pure and cheap. Login must respect ctx cancellation
// and must never read os.Stdin — interactive prompting is the caller's job
// (TUI form or CLI prompt), driven by Fields()/Missing(). Login must not
// mutate client's token; token persistence is internal/token's job.
type Method interface {
Name() string
DisplayName() string
DefaultMount() string
Fields() []Field
Login(ctx context.Context, client *api.Client, req Request) (*api.Secret, error)
}
// Describable is optional; implemented by methods with extra help text.
type Describable interface{ Description() string }