Files
lazysecrets/internal/provider/provider.go
T
keb 52b6257dd5 feat: show who an Azure account signs in as, in Details on its row (#5)
On an Azure account's group row, the Details pane now describes the account:

| field | from |
|---|---|
| name | token `name` |
| user (and "service principal") | az's account list |
| tenant: name · ID | az's account list (the name is borrowed from another entry in the same tenant when az left it out) |
| object ID | token `oid`; `y` on the row copies it |
| sign-in: MFA or not, and how | token `amr` |
| directory roles | token `wids`: built-in roles by name, others as `role 1a2b3c4d`, the default member role left out |
| subs: count · default | az's account list |
| cloud | az's account list, when not AzureCloud |

Nothing is fetched for it: the claims are the ones from the token lazysecrets already gets for the account's own tenant, read without verification. The token itself is never stored or shown. Before the first token, only the az-list fields show. A failing account shows its sign-in problem there instead (from #4).

New optional `provider.Accounts` interface; `azure.Provider` implements it. Tests: the fields from claims and az's list, sign-in methods, the UI showing it on the group row only, and `y` copying the object ID. Checked against a real signed-in account (name, MFA · password, default subscription) and an expired one (az-list fields only, since there's no token).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Karl-Einar Bergstrøm <karl-einarb@outlook.com>
Reviewed-on: #5
2026-10-11 10:09:17 +02:00

201 lines
6.4 KiB
Go

// Package provider defines the read-only interface every secret backend
// implements, and the types the UI works with.
//
// All backends map onto the same hierarchy:
//
// Source Azure subscription | Databricks workspace profile
// Container Key Vault | secret scope
// Object secret, key or certificate | secret
package provider
import (
"context"
"errors"
"fmt"
"time"
)
// Kind identifies a backend.
type Kind string
const (
KindAzure Kind = "Azure"
KindDatabricks Kind = "Databricks"
// KindRepo is a git repository's encrypted secret files (SOPS).
KindRepo Kind = "Repo"
)
// AuthError reports that the user must (re)authenticate with a backend, for
// example because their az CLI login has expired.
type AuthError struct {
Kind Kind
// Account names the identity when a provider has several, e.g.
// "Azure · admin.alice"; empty otherwise.
Account string
// Login is the command that signs the user in, e.g. ["az", "login"].
Login []string
Err error
}
func (e *AuthError) Error() string {
return fmt.Sprintf("%s: not signed in or session expired: %v", e.Who(), e.Err)
}
// Who is the account, or the kind if there is just one.
func (e *AuthError) Who() string {
if e.Account != "" {
return e.Account
}
return string(e.Kind)
}
func (e *AuthError) Unwrap() error { return e.Err }
// ErrUnsupported is returned by optional operations a backend cannot perform,
// such as listing versions of a Databricks secret.
var ErrUnsupported = errors.New("not supported by this provider")
// Source is a top-level account: an Azure subscription or a Databricks
// workspace profile.
type Source struct {
Kind Kind
// Provider is the ID of the provider instance the source came from.
// The app sets it; it routes requests when several providers share a
// Kind, e.g. one per Azure account.
Provider string
// ProviderLabel is that provider's Label, also set by the app.
ProviderLabel string
ID string
Name string
Detail string // e.g. tenant or workspace host
URL string // web page for the source, if any
// Here marks the source lazysecrets was started in (the current git
// repo): listed first, starred or not, and labelled.
Here bool
}
// ObjectType is the kind of object stored in a container.
type ObjectType string
const (
TypeSecret ObjectType = "Secret"
TypeKey ObjectType = "Key"
TypeCertificate ObjectType = "Certificate"
)
// Container holds objects: a Key Vault or a Databricks secret scope.
type Container struct {
Source Source
ID string
Name string
Detail string // e.g. vault URL, or "Azure Key Vault-backed: kv-name"
// Types lists the object types the container holds; empty means
// secrets only.
Types []ObjectType
URL string // web page for the container, if any
}
// ObjectTypes returns the container's object types, defaulting to secrets.
func (c Container) ObjectTypes() []ObjectType {
if len(c.Types) == 0 {
return []ObjectType{TypeSecret}
}
return c.Types
}
// Attr is a type-specific metadata field, shown in provider order.
type Attr struct {
Label string
Value string
}
// Object describes a secret, key or certificate without its value.
type Object struct {
Container Container
Type ObjectType
ID string
Name string
Version string
Enabled *bool
Updated time.Time
Expires time.Time
ContentType string
Tags map[string]string
// Attrs holds type-specific metadata, e.g. key type and size, or a
// certificate's subject, issuer and thumbprint.
Attrs []Attr
// Reference is how the object is referenced from config, e.g.
// "@Microsoft.KeyVault(SecretUri=...)" or "{{secrets/scope/key}}".
Reference string
URL string // web page for the object (or its list), if any
}
// Provider is a read-only secret backend. Every method may block on the
// network and must honour ctx cancellation.
type Provider interface {
Kind() Kind
// ID identifies the instance, stable across runs, e.g. "Azure" or
// "Azure:user@example.com/tenant".
ID() string
// Label names the instance in the UI, e.g. "Azure" or "Azure · user".
Label() string
Sources(ctx context.Context) ([]Source, error)
Containers(ctx context.Context, src Source) ([]Container, error)
// Objects lists objects of type t, one of c.ObjectTypes(). It returns
// metadata only; values are fetched with Value.
Objects(ctx context.Context, c Container, t ObjectType) ([]Object, error)
// Describe returns o with details that listing doesn't include, such as
// a key's type or a certificate's subject. It never fetches secret
// values; it may return o unchanged.
Describe(ctx context.Context, o Object) (Object, error)
// Versions may return ErrUnsupported.
Versions(ctx context.Context, o Object) ([]Object, error)
// Value returns a secret's value, or the public material of a key (PEM
// public key) or certificate (PEM certificate). Private key material is
// never returned for keys.
Value(ctx context.Context, o Object) (SecretValue, error)
}
// Current is implemented by providers that know the source lazysecrets
// was started in (the current git repo) and can find it faster than
// Sources lists everything, so it shows first.
type Current interface {
Current(ctx context.Context) (Source, bool)
}
// Locator is implemented by providers that can find where a container lives,
// to open links. address is a vault host (kv.vault.azure.net) or a
// workspace host. It returns the ID of the source holding it.
type Locator interface {
Locate(ctx context.Context, address string) (sourceID string, ok bool, err error)
}
// Files is implemented by providers whose containers are files on disk
// (SOPS and .env files in repos), so they can be opened in an editor.
type Files interface {
// File is where c is, and for o (nil for the whole file) the line its
// key is on.
File(c Container, o *Object) (File, error)
}
// File is a container's file, to open.
type File struct {
Path string
Line int // 1-based; 0 for the top
Sops string // the sops command to edit it with; empty for a plain file
}
// Accounts is implemented by providers that can describe the identity
// they use, from what they have locally: for the Details pane on the
// provider's row. ok is false when there's nothing to say.
type Accounts interface {
Account() (Account, bool)
}
// Account describes a signed-in identity.
type Account struct {
Fields []Attr
// Copy is what y copies on the provider's row, named CopyLabel.
Copy, CopyLabel string
}