Hello world
Release / Build and publish (push) Successful in 20s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-12 13:03:13 +02:00
commit 5987787f38
130 files changed
+26897

No files matched your search

+408
View File
@@ -0,0 +1,408 @@
// Package fake is an in-memory provider used by tests and --demo mode.
package fake
import (
"cmp"
"context"
"errors"
"fmt"
"strings"
"time"
"git.kebcloud.no/keb/lazysecrets/internal/provider"
)
// Provider serves a fixed tree of sources, containers and objects,
// optionally sleeping before every call to simulate network latency.
type Provider struct {
kind provider.Kind
id, label string
delay time.Duration
sources []provider.Source
containers map[string][]provider.Container // by source ID
objects map[listKey][]provider.Object
errs map[listKey]error
values map[string]string // by object ID
versions map[string][]provider.Object // by object ID
details map[string][]provider.Attr // by container ID
}
type listKey struct {
container string
typ provider.ObjectType
}
var _ provider.Provider = (*Provider)(nil)
func newProvider(kind provider.Kind, delay time.Duration) *Provider {
return &Provider{
kind: kind,
delay: delay,
containers: map[string][]provider.Container{},
objects: map[listKey][]provider.Object{},
errs: map[listKey]error{},
values: map[string]string{},
versions: map[string][]provider.Object{},
}
}
func (p *Provider) Kind() provider.Kind { return p.kind }
func (p *Provider) ID() string {
if p.id != "" {
return p.id
}
return string(p.kind)
}
func (p *Provider) Label() string {
if p.label != "" {
return p.label
}
return string(p.kind)
}
// As renames the provider, to simulate several accounts of one kind.
func (p *Provider) As(id, label string) *Provider {
p.id, p.label = id, label
return p
}
func (p *Provider) Sources(ctx context.Context) ([]provider.Source, error) {
if err := p.wait(ctx); err != nil {
return nil, err
}
return p.sources, nil
}
func (p *Provider) Containers(ctx context.Context, src provider.Source) ([]provider.Container, error) {
if err := p.wait(ctx); err != nil {
return nil, err
}
return p.containers[src.ID], nil
}
func (p *Provider) Objects(ctx context.Context, c provider.Container, t provider.ObjectType) ([]provider.Object, error) {
if err := p.wait(ctx); err != nil {
return nil, err
}
k := listKey{p.containerID(c), t}
if err := p.errs[k]; err != nil {
return nil, err
}
return p.objects[k], nil
}
// Describe returns o unchanged; the fixtures already carry their details.
func (p *Provider) Describe(ctx context.Context, o provider.Object) (provider.Object, error) {
return o, ctx.Err()
}
func (p *Provider) Versions(ctx context.Context, o provider.Object) ([]provider.Object, error) {
if err := p.wait(ctx); err != nil {
return nil, err
}
if p.kind != provider.KindAzure { // only Key Vault keeps versions
return nil, provider.ErrUnsupported
}
vs := p.versions[o.ID]
if len(vs) == 0 { // every object has at least its current version
return []provider.Object{o}, nil
}
out := make([]provider.Object, len(vs))
for i, v := range vs {
v.Container, v.Type, v.ID, v.Name, v.Reference, v.URL = o.Container, o.Type, o.ID, o.Name, o.Reference, o.URL
out[i] = v
}
return out, nil
}
func (p *Provider) Value(ctx context.Context, o provider.Object) (provider.SecretValue, error) {
if err := p.wait(ctx); err != nil {
return provider.SecretValue{}, err
}
if o.ID == "" { // built from a link, like the real providers read it
o.ID = p.containerID(o.Container) + "/" + strings.ToLower(string(cmp.Or(o.Type, provider.TypeSecret))) + "/" + o.Name
}
v, ok := p.values[o.ID+"@"+o.Version]
if !ok {
v, ok = p.values[o.ID]
}
if !ok {
return provider.SecretValue{}, fmt.Errorf("%s %q not found", strings.ToLower(string(o.Type)), o.Name)
}
return provider.NewSecretValue([]byte(v)), nil
}
// Locate finds the source holding the vault (by host) or the workspace.
func (p *Provider) Locate(ctx context.Context, host string) (string, bool, error) {
if err := p.wait(ctx); err != nil {
return "", false, err
}
for _, src := range p.sources {
if p.kind == provider.KindDatabricks && src.Detail == host {
return src.ID, true, nil
}
for _, c := range p.containers[src.ID] {
if c.Detail == "https://"+host {
return src.ID, true, nil
}
}
}
return "", false, nil
}
func (p *Provider) wait(ctx context.Context) error {
if p.delay <= 0 {
return ctx.Err()
}
t := time.NewTimer(p.delay)
defer t.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-t.C:
return nil
}
}
const portal = "https://portal.azure.com/#resource"
// containerID finds a container by ID, or by vault URL like the real Azure
// provider, which only needs the URL (vaults opened from links).
func (p *Provider) containerID(c provider.Container) string {
for _, cs := range p.containers {
for _, x := range cs {
if x.ID == c.ID {
return x.ID
}
}
}
// Not one listed here (built from a link): by vault URL, or a scope
// by name in its workspace.
for _, cs := range p.containers {
for _, x := range cs {
if c.Detail != "" && strings.TrimSuffix(x.Detail, "/") == strings.TrimSuffix(c.Detail, "/") {
return x.ID
}
if c.Detail == "" && c.Name == x.Name && c.Source.ID != "" && c.Source.ID == x.Source.ID {
return x.ID
}
}
}
return c.ID
}
func (p *Provider) addSource(id, name, detail string) provider.Source {
s := provider.Source{Kind: p.kind, ID: id, Name: name, Detail: detail}
if p.kind == provider.KindAzure {
s.URL = portal + "/subscriptions/" + id + "/overview"
} else {
s.URL = "https://" + detail
}
p.sources = append(p.sources, s)
return s
}
func (p *Provider) addContainer(src provider.Source, name, detail string, types ...provider.ObjectType) provider.Container {
c := provider.Container{Source: src, ID: src.ID + "/" + name, Name: name, Detail: detail, Types: types}
vault := name
if p.kind == provider.KindDatabricks {
vault, _ = strings.CutPrefix(detail, "Azure Key Vault-backed: ")
}
if p.kind == provider.KindAzure || (p.kind == provider.KindDatabricks && vault != detail) {
c.URL = portal + "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.KeyVault/vaults/" + vault + "/overview"
}
p.containers[src.ID] = append(p.containers[src.ID], c)
return c
}
func (p *Provider) add(c provider.Container, t provider.ObjectType, o provider.Object, value string) provider.Object {
o.Container = c
o.Type = t
o.ID = c.ID + "/" + strings.ToLower(string(t)) + "/" + o.Name
if base, ok := strings.CutSuffix(c.URL, "/overview"); ok {
o.URL = base + map[provider.ObjectType]string{
provider.TypeSecret: "/secrets", provider.TypeKey: "/keys", provider.TypeCertificate: "/certificates",
}[t]
}
k := listKey{c.ID, t}
p.objects[k] = append(p.objects[k], o)
p.values[o.ID] = value
return o
}
func (p *Provider) fail(c provider.Container, err error, types ...provider.ObjectType) {
for _, t := range types {
p.errs[listKey{c.ID, t}] = err
}
}
var (
base = time.Date(2026, 9, 1, 9, 30, 0, 0, time.UTC)
yes = true
no = false
)
// ErrForbidden mimics a data-plane permission error from a backend.
var ErrForbidden = errors.New("403 Forbidden: caller lacks permission to list secrets")
// ErrForbiddenKeys mimics a vault where the caller can read secrets but not keys.
var ErrForbiddenKeys = errors.New("403 Forbidden: caller lacks permission to list keys")
const (
demoPublicKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAdemo\n-----END PUBLIC KEY-----"
demoCert = "-----BEGIN CERTIFICATE-----\nMIIBszCCAVmgAwIBAgIUDEMO\nDEMODEMODEMO\n-----END CERTIFICATE-----"
)
var allTypes = []provider.ObjectType{provider.TypeSecret, provider.TypeKey, provider.TypeCertificate}
// Azure returns a fake Azure provider with two subscriptions.
func Azure(delay time.Duration) *Provider {
p := newProvider(provider.KindAzure, delay)
secretRef := func(vault, name string) string {
return fmt.Sprintf("@Microsoft.KeyVault(SecretUri=https://%s.vault.azure.net/secrets/%s)", vault, name)
}
objectURL := func(vault, kind, name string) string {
return fmt.Sprintf("https://%s.vault.azure.net/%s/%s", vault, kind, name)
}
prod := p.addSource("sub-prod", "platform-prod", "tenant: example.onmicrosoft.com")
kv := p.addContainer(prod, "kv-app-prod", "https://kv-app-prod.vault.azure.net", allTypes...)
dbPassword := p.add(kv, provider.TypeSecret, provider.Object{
Name: "db-password", Version: "3f9c2a", Enabled: &yes, Updated: base,
Expires: base.AddDate(1, 0, 0), ContentType: "text/plain",
Tags: map[string]string{"owner": "data", "env": "prod"}, Reference: secretRef("kv-app-prod", "db-password"),
}, "correct-horse-battery-staple")
p.versions[dbPassword.ID] = []provider.Object{
{Version: "3f9c2a", Enabled: &yes, Updated: base, Expires: base.AddDate(1, 0, 0), ContentType: "text/plain"},
{Version: "91d3e0", Enabled: &yes, Updated: base.AddDate(0, -6, 0), Expires: base.AddDate(0, -1, 0)},
{Version: "0a77c4", Enabled: &no, Updated: base.AddDate(-1, 0, 0)},
}
p.values[dbPassword.ID+"@91d3e0"] = "previous-password"
p.values[dbPassword.ID+"@0a77c4"] = "original-password"
p.add(kv, provider.TypeSecret, provider.Object{
Name: "api-key", Version: "a17b04", Enabled: &yes, Updated: base.AddDate(0, -2, 0),
Reference: secretRef("kv-app-prod", "api-key"),
}, "sk_live_51HxDEMO0000000000000000")
p.add(kv, provider.TypeSecret, provider.Object{
Name: "legacy-token", Version: "0c11de", Enabled: &no, Updated: base.AddDate(-1, 0, 0),
Reference: secretRef("kv-app-prod", "legacy-token"),
}, "deprecated")
p.add(kv, provider.TypeKey, provider.Object{
Name: "signing-key", Version: "5e21b7", Enabled: &yes, Updated: base.AddDate(0, -3, 0),
Attrs: []provider.Attr{
{Label: "key type", Value: "RSA 2048"},
{Label: "ops", Value: "sign, verify"},
},
Reference: objectURL("kv-app-prod", "keys", "signing-key"),
}, demoPublicKey)
p.add(kv, provider.TypeKey, provider.Object{
Name: "data-encryption", Version: "c40f19", Enabled: &yes, Updated: base.AddDate(0, -5, 0),
Attrs: []provider.Attr{
{Label: "key type", Value: "EC P-256"},
{Label: "ops", Value: "wrapKey, unwrapKey"},
},
Reference: objectURL("kv-app-prod", "keys", "data-encryption"),
}, demoPublicKey)
p.add(kv, provider.TypeCertificate, provider.Object{
Name: "app-tls", Version: "77e0aa", Enabled: &yes, Updated: base.AddDate(0, -1, 0),
Expires: base.AddDate(0, 11, 0), ContentType: "application/x-pem-file",
Attrs: []provider.Attr{
{Label: "subject", Value: "CN=app.example.com"},
{Label: "issuer", Value: "CN=Example Issuing CA"},
{Label: "thumbprint", Value: "3B:1F:9A:0C:44:E2:7D:15"},
{Label: "SANs", Value: "app.example.com, www.app.example.com"},
{Label: "valid from", Value: "2026-08-01 09:30"},
},
Reference: objectURL("kv-app-prod", "certificates", "app-tls"),
}, demoCert)
p.add(kv, provider.TypeCertificate, provider.Object{
Name: "old-tls", Version: "19ab3c", Enabled: &yes, Updated: base.AddDate(-1, 0, 0),
Expires: base.AddDate(0, -6, 0), ContentType: "application/x-pkcs12",
Attrs: []provider.Attr{
{Label: "subject", Value: "CN=old.example.com"},
{Label: "issuer", Value: "CN=Example Issuing CA"},
{Label: "thumbprint", Value: "A0:77:12:DE:9B:03:C6:58"},
},
Reference: objectURL("kv-app-prod", "certificates", "old-tls"),
}, demoCert)
shared := p.addContainer(prod, "kv-shared-prod", "https://kv-shared-prod.vault.azure.net", allTypes...)
p.add(shared, provider.TypeSecret, provider.Object{
Name: "smtp-password", Enabled: &yes, Updated: base, Reference: secretRef("kv-shared-prod", "smtp-password"),
}, "smtp-demo-password")
p.fail(shared, ErrForbiddenKeys, provider.TypeKey)
locked := p.addContainer(prod, "kv-restricted", "https://kv-restricted.vault.azure.net", allTypes...)
p.fail(locked, ErrForbidden, allTypes...)
dev := p.addSource("sub-dev", "platform-dev", "tenant: example.onmicrosoft.com")
kvDev := p.addContainer(dev, "kv-app-dev", "https://kv-app-dev.vault.azure.net", allTypes...)
p.add(kvDev, provider.TypeSecret, provider.Object{
Name: "db-password", Enabled: &yes, Updated: base, Reference: secretRef("kv-app-dev", "db-password"),
}, "dev-password")
return p
}
// AzureOps returns a second fake Azure account. It shares platform-prod
// with Azure but sees a different vault there, and has a subscription of
// its own, like an admin account next to a personal one.
func AzureOps(delay time.Duration) *Provider {
p := newProvider(provider.KindAzure, delay)
secretRef := func(vault, name string) string {
return fmt.Sprintf("@Microsoft.KeyVault(SecretUri=https://%s.vault.azure.net/secrets/%s)", vault, name)
}
prod := p.addSource("sub-prod", "platform-prod", "tenant: example.onmicrosoft.com")
breakGlass := p.addContainer(prod, "kv-breakglass", "https://kv-breakglass.vault.azure.net", allTypes...)
p.add(breakGlass, provider.TypeSecret, provider.Object{
Name: "emergency-admin", Enabled: &yes, Updated: base.AddDate(0, -4, 0),
Tags: map[string]string{"owner": "ops"}, Reference: secretRef("kv-breakglass", "emergency-admin"),
}, "break-glass-demo-password")
shared := p.addContainer(prod, "kv-shared-prod", "https://kv-shared-prod.vault.azure.net", allTypes...)
p.add(shared, provider.TypeSecret, provider.Object{
Name: "smtp-password", Enabled: &yes, Updated: base, Reference: secretRef("kv-shared-prod", "smtp-password"),
}, "smtp-demo-password")
// Unlike the first account, ops may read keys here.
p.add(shared, provider.TypeKey, provider.Object{
Name: "backup-encryption", Version: "e9a102", Enabled: &yes, Updated: base.AddDate(0, -2, 0),
Attrs: []provider.Attr{{Label: "key type", Value: "RSA-HSM 3072"}, {Label: "ops", Value: "wrapKey, unwrapKey"}},
Reference: "https://kv-shared-prod.vault.azure.net/keys/backup-encryption",
}, demoPublicKey)
sandbox := p.addSource("sub-ops", "ops-sandbox", "tenant: example.onmicrosoft.com")
kv := p.addContainer(sandbox, "kv-ops-tools", "https://kv-ops-tools.vault.azure.net", allTypes...)
p.add(kv, provider.TypeSecret, provider.Object{
Name: "pagerduty-token", Enabled: &yes, Updated: base.AddDate(0, -1, 0), Reference: secretRef("kv-ops-tools", "pagerduty-token"),
}, "pd-demo-token")
return p
}
// Databricks returns a fake Databricks provider with two workspace profiles,
// including an Azure Key Vault-backed scope. Scopes hold secrets only.
func Databricks(delay time.Duration) *Provider {
p := newProvider(provider.KindDatabricks, delay)
secret := func(c provider.Container, key, value string) {
p.add(c, provider.TypeSecret, provider.Object{
Name: key, Updated: base, Reference: fmt.Sprintf("{{secrets/%s/%s}}", c.Name, key),
}, value)
}
prod := p.addSource("dbx-prod", "prod", "adb-1111111111111111.11.azuredatabricks.net")
app := p.addContainer(prod, "app", "Databricks-backed")
secret(app, "storage-key", "storage-demo-key")
secret(app, "sp-client-secret", "sp-demo-secret")
akv := p.addContainer(prod, "kv-app-prod", "Azure Key Vault-backed: kv-app-prod")
secret(akv, "db-password", "correct-horse-battery-staple")
dev := p.addSource("dbx-dev", "dev", "adb-2222222222222222.2.azuredatabricks.net")
devApp := p.addContainer(dev, "app", "Databricks-backed")
secret(devApp, "storage-key", "dev-storage-key")
return p
}
+136
View File
@@ -0,0 +1,136 @@
package fake
import (
"context"
"strings"
"git.kebcloud.no/keb/lazysecrets/internal/provider"
)
var _ provider.ContainerDetails = (*Provider)(nil)
// Details returns a file's details, like the repo provider's.
func (p *Provider) Details(ctx context.Context, c provider.Container) ([]provider.Attr, error) {
attrs, ok := p.details[c.ID]
if !ok {
return nil, provider.ErrUnsupported
}
return attrs, ctx.Err()
}
// Repos returns a fake repo provider: three git repos with SOPS and plain
// .env files, the first one the repo lazysecrets was started in.
func Repos() *Provider {
p := newProvider(provider.KindRepo, 0)
p.id, p.label = "Repos", "Repos"
p.details = map[string][]provider.Attr{}
repo := func(name string, here bool) provider.Source {
s := p.addSource("/home/dev/git/"+name, name, "github.com/acme/"+name)
s.Here = here
p.sources[len(p.sources)-1] = s
return s
}
// file adds a file and its keys, alternating name and value. git and
// commit are what git says of it: "tracked", "ignored" or "untracked".
file := func(src provider.Source, path, method, git, commit string, kv ...string) {
sops := method != ""
detail := "env · plain text"
if sops {
detail = "sops · " + method
}
c := p.addContainer(src, path, detail, provider.TypeSecret)
attrs := []provider.Attr{{Label: "format", Value: "env"}, {Label: "encrypted", Value: "no: plain text in the file"}}
if sops {
attrs = []provider.Attr{{Label: "format", Value: "sops " + format(path)}, {Label: "encryption", Value: method}}
}
for i := 0; i+1 < len(kv); i += 2 {
o := provider.Object{Name: kv[i], Attrs: attrs}
if sops {
o.Updated = base
}
p.add(c, provider.TypeSecret, o, kv[i+1])
}
d := []provider.Attr{{Label: "encryption", Value: "none: plain text"}}
if sops {
d = []provider.Attr{{Label: "encryption", Value: detail}}
keys := map[string]string{
"age": "age age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p",
"azure_kv": "azure_kv https://kv-sops-prod.vault.azure.net/keys/sops/1f2e3d4c",
}
for i, m := range strings.Split(method, ", ") {
label := ""
if i == 0 {
label = "keys" // on the first only, like the repo provider
}
d = append(d, provider.Attr{Label: label, Value: keys[m]})
}
d = append(d, provider.Attr{Label: "encrypted", Value: base.Local().Format("2006-01-02 15:04")})
}
switch {
case git == "tracked" && !sops:
d = append(d, provider.Attr{Label: "git", Value: "tracked: its values are in the repo's history", Warn: true})
case git == "untracked":
d = append(d, provider.Attr{Label: "git", Value: "untracked and not ignored: easy to commit by mistake", Warn: !sops})
default:
d = append(d, provider.Attr{Label: "git", Value: git})
}
if commit != "" {
d = append(d, provider.Attr{Label: "commit", Value: commit})
}
p.details[c.ID] = d
}
api := repo("lazysecrets-demo", true)
file(api, "deploy/prod/secrets.yaml", "age, azure_kv", "tracked", "4f9c2ab · 3 days ago",
"database.password", "pr0d-Pg-7f3k9QwZ",
"database.url", "postgres://app:pr0d-Pg-7f3k9QwZ@pg-demo-prod:5432/app",
"stripe.secret_key", "sk_live_demo_51NxDemoKey0000",
"jwt.signing_key", "c2lnbmluZy1rZXktZm9yLXRoZS1kZW1v",
"gcp.service_account", `{"type":"service_account","project_id":"lazysecrets-demo","client_email":"app@lazysecrets-demo.iam.gserviceaccount.com","private_key_id":"demo1234"}`)
file(api, "deploy/staging/secrets.yaml", "age", "tracked", "a71e05d · 2 weeks ago",
"database.password", "stg-Pg-2b8d1L",
"database.url", "postgres://app:stg-Pg-2b8d1L@pg-demo-stg:5432/app",
"stripe.secret_key", "sk_test_demo_51NxDemoKey0000")
file(api, "config/oauth.enc.json", "age", "tracked", "e03b6f1 · 2 months ago",
"github.client_secret", "ghs_demo0000000000000000000000",
"google.client_secret", "GOCSPX-demo-000000000000")
file(api, ".env", "", "ignored", "",
"DATABASE_URL", "postgres://app:local@localhost:5432/app",
"STRIPE_SECRET_KEY", "sk_test_demo_local",
"LOG_LEVEL", "debug")
infra := repo("platform-infra", false)
file(infra, "terraform/prod/secrets.enc.yaml", "azure_kv", "tracked", "9d40c3e · yesterday",
"postgres.admin_password", "infra-Pg-admin-0Xq",
"grafana.admin_password", "grafana-demo-admin",
"acr.pull_token", "acr-demo-pull-token")
file(infra, "k8s/monitoring/alertmanager.enc.yaml", "age", "tracked", "1c58e7a · 5 days ago",
"slack.webhook_url", "https://hooks.slack.com/services/T000/B000/demo",
"pagerduty.routing_key", "pd-demo-routing-key")
file(infra, "scripts/.env", "", "tracked", "77b2d90 · 8 months ago",
"AZURE_CLIENT_SECRET", "azure-demo-client-secret",
"ARM_SUBSCRIPTION_ID", "00000000-0000-0000-0000-000000000000")
web := repo("web-frontend", false)
file(web, ".env.production", "age", "tracked", "b2e91f4 · 4 days ago",
"SENTRY_AUTH_TOKEN", "sntrys_demo_token",
"NEXT_PUBLIC_API_URL", "https://api.acme.example")
file(web, ".env.local", "", "untracked", "",
"NEXT_PUBLIC_API_URL", "http://localhost:8080",
"ANALYTICS_KEY", "demo-analytics-key")
return p
}
// format is a SOPS file's format, by its name.
func format(path string) string {
switch {
case strings.HasSuffix(path, ".json"):
return "json"
case strings.HasSuffix(path, ".yaml"):
return "yaml"
}
return "dotenv"
}