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:
@@ -0,0 +1,110 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
)
|
||||
|
||||
// RenewEvent is emitted by AutoRenewer as the watched token's lifecycle
|
||||
// changes. Err set + Terminal true means the watcher has given up and the
|
||||
// token will eventually expire without further notice.
|
||||
type RenewEvent struct {
|
||||
At time.Time
|
||||
TTL time.Duration
|
||||
Err error
|
||||
Terminal bool
|
||||
}
|
||||
|
||||
// AutoRenewer wraps api.LifetimeWatcher with the two special cases that
|
||||
// matter for a token we did not necessarily just mint ourselves: batch
|
||||
// tokens are not renewable, and root tokens have TTL == 0 (never expire).
|
||||
// NewAutoRenewer returns (nil, nil) for either case — callers should treat
|
||||
// a nil renewer as "nothing to do", not an error.
|
||||
type AutoRenewer struct {
|
||||
client *api.Client
|
||||
w *api.LifetimeWatcher
|
||||
events chan RenewEvent
|
||||
}
|
||||
|
||||
// NewAutoRenewer builds a renewer from a *Info (e.g. from Lookup) rather
|
||||
// than requiring the original login *api.Secret, since a token resolved
|
||||
// from ~/.vault-token has no such Secret — this synthesises an equivalent
|
||||
// one from the lookup data.
|
||||
func NewAutoRenewer(c *api.Client, info *Info, increment time.Duration) (*AutoRenewer, error) {
|
||||
if !info.Renewable || info.ExpireTime == nil {
|
||||
return nil, nil
|
||||
}
|
||||
sec := &api.Secret{
|
||||
Auth: &api.SecretAuth{
|
||||
ClientToken: c.Token(),
|
||||
LeaseDuration: int(info.TTL.Seconds()),
|
||||
Renewable: info.Renewable,
|
||||
},
|
||||
}
|
||||
w, err := c.NewLifetimeWatcher(&api.LifetimeWatcherInput{
|
||||
Secret: sec,
|
||||
Increment: int(increment.Seconds()),
|
||||
RenewBuffer: api.DefaultLifetimeWatcherRenewBuffer,
|
||||
RenewBehavior: api.RenewBehaviorIgnoreErrors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating lifetime watcher: %w", err)
|
||||
}
|
||||
return &AutoRenewer{client: c, w: w, events: make(chan RenewEvent, 8)}, nil
|
||||
}
|
||||
|
||||
// Start runs the watcher until ctx is cancelled or Stop is called.
|
||||
func (a *AutoRenewer) Start(ctx context.Context) {
|
||||
go a.w.Start()
|
||||
go func() {
|
||||
defer close(a.events)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
a.w.Stop()
|
||||
return
|
||||
case out, ok := <-a.w.RenewCh():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a.emit(RenewEvent{At: time.Now(), TTL: leaseDuration(out)})
|
||||
case err, ok := <-a.w.DoneCh():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a.emit(RenewEvent{At: time.Now(), Err: err, Terminal: true})
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *AutoRenewer) emit(e RenewEvent) {
|
||||
select {
|
||||
case a.events <- e:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Events returns the channel of renewal notifications; closed when the
|
||||
// watcher stops.
|
||||
func (a *AutoRenewer) Events() <-chan RenewEvent { return a.events }
|
||||
|
||||
// Stop releases the underlying watcher.
|
||||
func (a *AutoRenewer) Stop() { a.w.Stop() }
|
||||
|
||||
// leaseDuration reads the renewed TTL out of a RenewOutput. Token renewals
|
||||
// (our only use case) carry it on Secret.Auth, not the top-level
|
||||
// Secret.LeaseDuration field that non-auth lease renewals use.
|
||||
func leaseDuration(out *api.RenewOutput) time.Duration {
|
||||
if out == nil || out.Secret == nil {
|
||||
return 0
|
||||
}
|
||||
if out.Secret.Auth != nil {
|
||||
return time.Duration(out.Secret.Auth.LeaseDuration) * time.Second
|
||||
}
|
||||
return time.Duration(out.Secret.LeaseDuration) * time.Second
|
||||
}
|
||||
Reference in New Issue
Block a user