- 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.
99 lines
3.2 KiB
Go
99 lines
3.2 KiB
Go
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
|
|
}
|
|
}
|
|
}
|
|
}
|