- 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.
174 lines
4.4 KiB
Go
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 }
|