Files
lazysecrets/internal/provider/azure/details.go
T
kebandClaude Opus 5.5 5381cf5d43
CI / Checks (pull_request) Successful in 24s
PR title / Conventional title (pull_request) Successful in 1s
feat: vaults, scopes and files show their details: a vault's settings, who has a scope, a file's keys and git status
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 13:41:12 +02:00

107 lines
3.0 KiB
Go

package azure
import (
"context"
"fmt"
"slices"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault"
"git.kebcloud.no/keb/lazysecrets/internal/provider"
)
// Details are a vault's settings, from the Key Vault management API: where
// it is, how access to it works, and how it's protected. Pinned vaults,
// known only by their URL, have none.
func (p *Provider) Details(ctx context.Context, c provider.Container) ([]provider.Attr, error) {
id, err := arm.ParseResourceID(c.ID)
if err != nil {
return nil, provider.ErrUnsupported
}
client, err := armkeyvault.NewVaultsClient(id.SubscriptionID, p.cred, p.opts.arm)
if err != nil {
return nil, err
}
resp, err := client.Get(ctx, id.ResourceGroupName, id.Name, nil)
if err != nil {
return nil, cleanError(err)
}
return vaultAttrs(resp.Vault), nil
}
func vaultAttrs(v armkeyvault.Vault) []provider.Attr {
var out []provider.Attr
add := func(label, value string) {
if value != "" {
out = append(out, provider.Attr{Label: label, Value: value})
}
}
if id, err := arm.ParseResourceID(deref(v.ID)); err == nil {
add("group", id.ResourceGroupName)
}
add("location", deref(v.Location))
pr := v.Properties
if pr == nil {
return out
}
if pr.SKU != nil && pr.SKU.Name != nil {
add("SKU", string(*pr.SKU.Name))
}
// Who may read secrets is decided by Azure roles or by the vault's
// own access policies, never both.
if on(pr.EnableRbacAuthorization, false) {
add("access", "Azure roles (RBAC)")
} else {
add("access", fmt.Sprintf("access policies (%d)", len(pr.AccessPolicies)))
}
if on(pr.EnableSoftDelete, true) {
days := int32(90)
if pr.SoftDeleteRetentionInDays != nil {
days = *pr.SoftDeleteRetentionInDays
}
add("soft delete", fmt.Sprintf("on, %d days", days))
} else {
add("soft delete", "off")
}
add("purge prot.", map[bool]string{true: "on", false: "off"}[on(pr.EnablePurgeProtection, false)])
add("network", network(pr))
if n := len(pr.PrivateEndpointConnections); n > 0 {
add("private", fmt.Sprintf("%d endpoint%s", n, map[bool]string{true: "s"}[n > 1]))
}
keys := make([]string, 0, len(v.Tags))
for k := range v.Tags {
keys = append(keys, k)
}
slices.Sort(keys)
for i, k := range keys {
label := ""
if i == 0 {
label = "tags"
}
out = append(out, provider.Attr{Label: label, Value: k + ": " + deref(v.Tags[k])})
}
return out
}
// network is where the vault can be reached from.
func network(pr *armkeyvault.VaultProperties) string {
if strings.EqualFold(deref(pr.PublicNetworkAccess), "disabled") {
return "private endpoints only"
}
acl := pr.NetworkACLs
if acl == nil || acl.DefaultAction == nil || *acl.DefaultAction != armkeyvault.NetworkRuleActionDeny {
return "all networks"
}
return fmt.Sprintf("selected networks (%d IP ranges, %d subnets)", len(acl.IPRules), len(acl.VirtualNetworkRules))
}
// on is *b, or def when it isn't set.
func on(b *bool, def bool) bool {
if b == nil {
return def
}
return *b
}