Compare commits
1
Commits
v0.3.0
...
05d507e7bd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05d507e7bd
|
No files matched your search
@@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault/v2"
|
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault/v2"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph"
|
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph"
|
||||||
@@ -99,13 +100,7 @@ func (p *Provider) Label() string { return cmp.Or(p.label, string(provider.KindA
|
|||||||
// Sources lists the subscriptions the signed-in identity can see, skipping
|
// Sources lists the subscriptions the signed-in identity can see, skipping
|
||||||
// disabled and deleted ones.
|
// disabled and deleted ones.
|
||||||
func (p *Provider) Sources(ctx context.Context) ([]provider.Source, error) {
|
func (p *Provider) Sources(ctx context.Context) ([]provider.Source, error) {
|
||||||
var out []provider.Source
|
out, err := all(ctx, p.subs.NewListPager(nil), func(page armsubscriptions.ClientListResponse) (srcs []provider.Source) {
|
||||||
pager := p.subs.NewListPager(nil)
|
|
||||||
for pager.More() {
|
|
||||||
page, err := pager.NextPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cleanError(err)
|
|
||||||
}
|
|
||||||
for _, s := range page.Value {
|
for _, s := range page.Value {
|
||||||
switch deref(s.State) {
|
switch deref(s.State) {
|
||||||
case armsubscriptions.SubscriptionStateDisabled, armsubscriptions.SubscriptionStateDeleted:
|
case armsubscriptions.SubscriptionStateDisabled, armsubscriptions.SubscriptionStateDeleted:
|
||||||
@@ -118,7 +113,7 @@ func (p *Provider) Sources(ctx context.Context) ([]provider.Source, error) {
|
|||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.tenants[id] = tenant
|
p.tenants[id] = tenant
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
out = append(out, provider.Source{
|
srcs = append(srcs, provider.Source{
|
||||||
Kind: provider.KindAzure,
|
Kind: provider.KindAzure,
|
||||||
ID: id,
|
ID: id,
|
||||||
Name: deref(s.DisplayName),
|
Name: deref(s.DisplayName),
|
||||||
@@ -126,6 +121,10 @@ func (p *Provider) Sources(ctx context.Context) ([]provider.Source, error) {
|
|||||||
URL: PortalResourceURL(tenant, "/subscriptions/"+id, "overview"),
|
URL: PortalResourceURL(tenant, "/subscriptions/"+id, "overview"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return srcs
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
slices.SortFunc(out, func(a, b provider.Source) int { return cmp.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) })
|
slices.SortFunc(out, func(a, b provider.Source) int { return cmp.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) })
|
||||||
if len(p.opts.Vaults) > 0 {
|
if len(p.opts.Vaults) > 0 {
|
||||||
@@ -230,22 +229,29 @@ func (p *Provider) armVaults(ctx context.Context, src provider.Source) ([]provid
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var out []provider.Container
|
return all(ctx, client.NewListBySubscriptionPager(nil), func(page armkeyvault.VaultsClientListBySubscriptionResponse) (vaults []provider.Container) {
|
||||||
pager := client.NewListBySubscriptionPager(nil)
|
for _, v := range page.Value {
|
||||||
|
if v.Properties == nil || v.Properties.VaultURI == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vaults = append(vaults, provider.Container{
|
||||||
|
Source: src, ID: deref(v.ID), Name: deref(v.Name),
|
||||||
|
Detail: *v.Properties.VaultURI, Types: objectTypes, Attrs: armInfo(v).attrs(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return vaults
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// all lists every page, items picking out what to keep of each.
|
||||||
|
func all[P, T any](ctx context.Context, pager *runtime.Pager[P], items func(P) []T) ([]T, error) {
|
||||||
|
var out []T
|
||||||
for pager.More() {
|
for pager.More() {
|
||||||
page, err := pager.NextPage(ctx)
|
page, err := pager.NextPage(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cleanError(err)
|
return nil, cleanError(err)
|
||||||
}
|
}
|
||||||
for _, v := range page.Value {
|
out = append(out, items(page)...)
|
||||||
if v.Properties == nil || v.Properties.VaultURI == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, provider.Container{
|
|
||||||
Source: src, ID: deref(v.ID), Name: deref(v.Name),
|
|
||||||
Detail: *v.Properties.VaultURI, Types: objectTypes, Attrs: armInfo(v).attrs(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
@@ -288,51 +294,40 @@ func (p *Provider) Objects(ctx context.Context, c provider.Container, t provider
|
|||||||
var out []provider.Object
|
var out []provider.Object
|
||||||
switch t {
|
switch t {
|
||||||
case provider.TypeSecret:
|
case provider.TypeSecret:
|
||||||
pager := vc.secrets.NewListSecretPropertiesPager(nil)
|
tenant := p.tenant(c.Source.ID)
|
||||||
for pager.More() {
|
out, err = all(ctx, vc.secrets.NewListSecretPropertiesPager(nil), func(page azsecrets.ListSecretPropertiesResponse) (objs []provider.Object) {
|
||||||
page, err := pager.NextPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cleanError(err)
|
|
||||||
}
|
|
||||||
for _, s := range page.Value {
|
for _, s := range page.Value {
|
||||||
// Certificate-backing secrets appear under Certificates.
|
// Certificate-backing secrets appear under Certificates.
|
||||||
if s.ID == nil || deref(s.Managed) {
|
if s.ID != nil && !deref(s.Managed) {
|
||||||
continue
|
objs = append(objs, secretObject(c, s, tenant))
|
||||||
}
|
|
||||||
out = append(out, secretObject(c, s, p.tenant(c.Source.ID)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return objs
|
||||||
|
})
|
||||||
case provider.TypeKey:
|
case provider.TypeKey:
|
||||||
pager := vc.keys.NewListKeyPropertiesPager(nil)
|
out, err = all(ctx, vc.keys.NewListKeyPropertiesPager(nil), func(page azkeys.ListKeyPropertiesResponse) (objs []provider.Object) {
|
||||||
for pager.More() {
|
|
||||||
page, err := pager.NextPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cleanError(err)
|
|
||||||
}
|
|
||||||
for _, k := range page.Value {
|
for _, k := range page.Value {
|
||||||
if k.KID == nil || deref(k.Managed) {
|
if k.KID != nil && !deref(k.Managed) {
|
||||||
continue
|
objs = append(objs, keyObject(c, k))
|
||||||
}
|
|
||||||
out = append(out, keyObject(c, k))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return objs
|
||||||
|
})
|
||||||
case provider.TypeCertificate:
|
case provider.TypeCertificate:
|
||||||
pager := vc.certs.NewListCertificatePropertiesPager(nil)
|
out, err = all(ctx, vc.certs.NewListCertificatePropertiesPager(nil), func(page azcertificates.ListCertificatePropertiesResponse) (objs []provider.Object) {
|
||||||
for pager.More() {
|
|
||||||
page, err := pager.NextPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cleanError(err)
|
|
||||||
}
|
|
||||||
for _, cp := range page.Value {
|
for _, cp := range page.Value {
|
||||||
if cp.ID == nil {
|
if cp.ID != nil {
|
||||||
continue
|
objs = append(objs, certObject(c, cp))
|
||||||
}
|
|
||||||
out = append(out, certObject(c, cp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return objs
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return nil, provider.ErrUnsupported
|
return nil, provider.ErrUnsupported
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
slices.SortFunc(out, func(a, b provider.Object) int { return cmp.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) })
|
slices.SortFunc(out, func(a, b provider.Object) int { return cmp.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) })
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
@@ -353,7 +348,7 @@ func (p *Provider) Describe(ctx context.Context, o provider.Object) (provider.Ob
|
|||||||
if resp.Key != nil {
|
if resp.Key != nil {
|
||||||
if resp.Key.KID != nil {
|
if resp.Key.KID != nil {
|
||||||
o.Version = resp.Key.KID.Version()
|
o.Version = resp.Key.KID.Version()
|
||||||
o.URL = portalAssetURL(p.tenant(o.Container.Source.ID), "Key", string(*resp.Key.KID))
|
o.URL = portalAssetURL(p.tenant(o.Container.Source.ID), o.Type, string(*resp.Key.KID))
|
||||||
}
|
}
|
||||||
o.Attrs = keyAttrs(resp.Key)
|
o.Attrs = keyAttrs(resp.Key)
|
||||||
}
|
}
|
||||||
@@ -364,7 +359,7 @@ func (p *Provider) Describe(ctx context.Context, o provider.Object) (provider.Ob
|
|||||||
}
|
}
|
||||||
if resp.ID != nil {
|
if resp.ID != nil {
|
||||||
o.Version = resp.ID.Version()
|
o.Version = resp.ID.Version()
|
||||||
o.URL = portalAssetURL(p.tenant(o.Container.Source.ID), "Certificate", string(*resp.ID))
|
o.URL = portalAssetURL(p.tenant(o.Container.Source.ID), o.Type, string(*resp.ID))
|
||||||
}
|
}
|
||||||
if pol := resp.Policy; pol != nil && pol.SecretProperties != nil {
|
if pol := resp.Policy; pol != nil && pol.SecretProperties != nil {
|
||||||
o.ContentType = deref(pol.SecretProperties.ContentType)
|
o.ContentType = deref(pol.SecretProperties.ContentType)
|
||||||
@@ -381,56 +376,46 @@ func (p *Provider) Versions(ctx context.Context, o provider.Object) ([]provider.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
tenant := p.tenant(o.Container.Source.ID)
|
||||||
var out []provider.Object
|
var out []provider.Object
|
||||||
switch o.Type {
|
switch o.Type {
|
||||||
case provider.TypeSecret:
|
case provider.TypeSecret:
|
||||||
pager := vc.secrets.NewListSecretPropertiesVersionsPager(o.Name, nil)
|
out, err = all(ctx, vc.secrets.NewListSecretPropertiesVersionsPager(o.Name, nil), func(page azsecrets.ListSecretPropertiesVersionsResponse) (objs []provider.Object) {
|
||||||
for pager.More() {
|
|
||||||
page, err := pager.NextPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cleanError(err)
|
|
||||||
}
|
|
||||||
for _, s := range page.Value {
|
for _, s := range page.Value {
|
||||||
if s.ID != nil {
|
if s.ID != nil {
|
||||||
out = append(out, secretObject(o.Container, s, p.tenant(o.Container.Source.ID)))
|
objs = append(objs, secretObject(o.Container, s, tenant))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return objs
|
||||||
|
})
|
||||||
case provider.TypeKey:
|
case provider.TypeKey:
|
||||||
pager := vc.keys.NewListKeyPropertiesVersionsPager(o.Name, nil)
|
out, err = all(ctx, vc.keys.NewListKeyPropertiesVersionsPager(o.Name, nil), func(page azkeys.ListKeyPropertiesVersionsResponse) (objs []provider.Object) {
|
||||||
for pager.More() {
|
|
||||||
page, err := pager.NextPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cleanError(err)
|
|
||||||
}
|
|
||||||
for _, k := range page.Value {
|
for _, k := range page.Value {
|
||||||
if k.KID != nil {
|
if k.KID != nil {
|
||||||
out = append(out, keyObject(o.Container, k))
|
objs = append(objs, keyObject(o.Container, k))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return objs
|
||||||
|
})
|
||||||
case provider.TypeCertificate:
|
case provider.TypeCertificate:
|
||||||
pager := vc.certs.NewListCertificatePropertiesVersionsPager(o.Name, nil)
|
out, err = all(ctx, vc.certs.NewListCertificatePropertiesVersionsPager(o.Name, nil), func(page azcertificates.ListCertificatePropertiesVersionsResponse) (objs []provider.Object) {
|
||||||
for pager.More() {
|
|
||||||
page, err := pager.NextPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cleanError(err)
|
|
||||||
}
|
|
||||||
for _, cp := range page.Value {
|
for _, cp := range page.Value {
|
||||||
if cp.ID != nil {
|
if cp.ID != nil {
|
||||||
out = append(out, certObject(o.Container, cp))
|
objs = append(objs, certObject(o.Container, cp))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return objs
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return nil, provider.ErrUnsupported
|
return nil, provider.ErrUnsupported
|
||||||
}
|
}
|
||||||
tenant := p.tenant(o.Container.Source.ID)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for i := range out {
|
for i := range out {
|
||||||
out[i].Reference = versionedReference(out[i])
|
out[i].Reference = versionedReference(out[i])
|
||||||
if o.Type != provider.TypeSecret { // secretObject's is the version's already
|
if o.Type != provider.TypeSecret { // secretObject's is the version's already
|
||||||
kind := map[provider.ObjectType]string{provider.TypeKey: "Key", provider.TypeCertificate: "Certificate"}[o.Type]
|
out[i].URL = portalAssetURL(tenant, o.Type, out[i].ID+"/"+out[i].Version) // the version's page
|
||||||
out[i].URL = portalAssetURL(tenant, kind, out[i].ID+"/"+out[i].Version) // the version's page
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slices.SortFunc(out, func(a, b provider.Object) int { return b.Updated.Compare(a.Updated) })
|
slices.SortFunc(out, func(a, b provider.Object) int { return b.Updated.Compare(a.Updated) })
|
||||||
@@ -452,47 +437,19 @@ func versionedReference(o provider.Object) string {
|
|||||||
// Value returns a secret's value, a key's public key or a certificate's
|
// Value returns a secret's value, a key's public key or a certificate's
|
||||||
// public certificate, as of o.Version (latest if empty).
|
// public certificate, as of o.Version (latest if empty).
|
||||||
func (p *Provider) Value(ctx context.Context, o provider.Object) (provider.SecretValue, error) {
|
func (p *Provider) Value(ctx context.Context, o provider.Object) (provider.SecretValue, error) {
|
||||||
vc, err := p.clients(o.Container.Detail)
|
|
||||||
if err != nil {
|
|
||||||
return provider.SecretValue{}, err
|
|
||||||
}
|
|
||||||
switch o.Type {
|
|
||||||
case provider.TypeSecret:
|
|
||||||
v, _, err := p.VersionedValue(ctx, o)
|
v, _, err := p.VersionedValue(ctx, o)
|
||||||
return v, err
|
return v, err
|
||||||
case provider.TypeKey:
|
|
||||||
resp, err := vc.keys.GetKey(ctx, o.Name, o.Version, nil)
|
|
||||||
if err != nil {
|
|
||||||
return provider.SecretValue{}, cleanError(err)
|
|
||||||
}
|
|
||||||
pemKey, err := publicKeyPEM(resp.Key)
|
|
||||||
if err != nil {
|
|
||||||
return provider.SecretValue{}, err
|
|
||||||
}
|
|
||||||
return provider.NewSecretValue([]byte(pemKey)), nil
|
|
||||||
case provider.TypeCertificate:
|
|
||||||
resp, err := vc.certs.GetCertificate(ctx, o.Name, o.Version, nil)
|
|
||||||
if err != nil {
|
|
||||||
return provider.SecretValue{}, cleanError(err)
|
|
||||||
}
|
|
||||||
if len(resp.CER) == 0 {
|
|
||||||
return provider.SecretValue{}, errors.New("certificate has no public certificate yet (pending issuance?)")
|
|
||||||
}
|
|
||||||
return provider.NewSecretValue([]byte(certPEM(resp.CER))), nil
|
|
||||||
}
|
|
||||||
return provider.SecretValue{}, provider.ErrUnsupported
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersionedValue is Value, with the version read.
|
// VersionedValue is Value, with the version read.
|
||||||
func (p *Provider) VersionedValue(ctx context.Context, o provider.Object) (provider.SecretValue, string, error) {
|
func (p *Provider) VersionedValue(ctx context.Context, o provider.Object) (provider.SecretValue, string, error) {
|
||||||
if o.Type != provider.TypeSecret {
|
|
||||||
v, err := p.Value(ctx, o)
|
|
||||||
return v, o.Version, err
|
|
||||||
}
|
|
||||||
vc, err := p.clients(o.Container.Detail)
|
vc, err := p.clients(o.Container.Detail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provider.SecretValue{}, "", err
|
return provider.SecretValue{}, "", err
|
||||||
}
|
}
|
||||||
|
var text string
|
||||||
|
switch o.Type {
|
||||||
|
case provider.TypeSecret:
|
||||||
resp, err := vc.secrets.GetSecret(ctx, o.Name, o.Version, nil)
|
resp, err := vc.secrets.GetSecret(ctx, o.Name, o.Version, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provider.SecretValue{}, "", cleanError(err)
|
return provider.SecretValue{}, "", cleanError(err)
|
||||||
@@ -502,6 +459,27 @@ func (p *Provider) VersionedValue(ctx context.Context, o provider.Object) (provi
|
|||||||
version = resp.ID.Version()
|
version = resp.ID.Version()
|
||||||
}
|
}
|
||||||
return provider.NewSecretValue([]byte(deref(resp.Value))), version, nil
|
return provider.NewSecretValue([]byte(deref(resp.Value))), version, nil
|
||||||
|
case provider.TypeKey:
|
||||||
|
resp, err := vc.keys.GetKey(ctx, o.Name, o.Version, nil)
|
||||||
|
if err != nil {
|
||||||
|
return provider.SecretValue{}, "", cleanError(err)
|
||||||
|
}
|
||||||
|
if text, err = publicKeyPEM(resp.Key); err != nil {
|
||||||
|
return provider.SecretValue{}, "", err
|
||||||
|
}
|
||||||
|
case provider.TypeCertificate:
|
||||||
|
resp, err := vc.certs.GetCertificate(ctx, o.Name, o.Version, nil)
|
||||||
|
if err != nil {
|
||||||
|
return provider.SecretValue{}, "", cleanError(err)
|
||||||
|
}
|
||||||
|
if len(resp.CER) == 0 {
|
||||||
|
return provider.SecretValue{}, "", errors.New("certificate has no public certificate yet (pending issuance?)")
|
||||||
|
}
|
||||||
|
text = certPEM(resp.CER)
|
||||||
|
default:
|
||||||
|
return provider.SecretValue{}, "", provider.ErrUnsupported
|
||||||
|
}
|
||||||
|
return provider.NewSecretValue([]byte(text)), o.Version, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ func PortalResourceURL(tenant, resourceID, blade string) string {
|
|||||||
|
|
||||||
// portalAssetURL links to a Key Vault object's page: a version's with
|
// portalAssetURL links to a Key Vault object's page: a version's with
|
||||||
// one in objectID; a secret's own (its versions) without.
|
// one in objectID; a secret's own (its versions) without.
|
||||||
func portalAssetURL(tenant, kind, objectID string) string {
|
func portalAssetURL(tenant string, t provider.ObjectType, objectID string) string {
|
||||||
return portal + tenantPrefix(tenant) + "asset/Microsoft_Azure_KeyVault/" + kind + "/" + objectID
|
return portal + tenantPrefix(tenant) + "asset/Microsoft_Azure_KeyVault/" + string(t) + "/" + objectID
|
||||||
}
|
}
|
||||||
|
|
||||||
func tenantPrefix(tenant string) string {
|
func tenantPrefix(tenant string) string {
|
||||||
@@ -73,19 +73,13 @@ func ObjectListURL(c provider.Container, t provider.ObjectType) string {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
switch t {
|
return base + "/" + t.Collection()
|
||||||
case provider.TypeKey:
|
|
||||||
return base + "/keys"
|
|
||||||
case provider.TypeCertificate:
|
|
||||||
return base + "/certificates"
|
|
||||||
}
|
|
||||||
return base + "/secrets"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectURL is the data-plane URL of an object, e.g.
|
// objectURL is the data-plane URL of an object, e.g.
|
||||||
// https://kv.vault.azure.net/secrets/name.
|
// https://kv.vault.azure.net/secrets/name.
|
||||||
func objectURL(c provider.Container, collection, name string) string {
|
func objectURL(c provider.Container, t provider.ObjectType, name string) string {
|
||||||
return strings.TrimSuffix(c.Detail, "/") + "/" + collection + "/" + name
|
return strings.TrimSuffix(c.Detail, "/") + "/" + t.Collection() + "/" + name
|
||||||
}
|
}
|
||||||
|
|
||||||
// secretObject is a secret, or a version of one, from a listing. Its URL
|
// secretObject is a secret, or a version of one, from a listing. Its URL
|
||||||
@@ -93,7 +87,7 @@ func objectURL(c provider.Container, collection, name string) string {
|
|||||||
// a version, that version's.
|
// a version, that version's.
|
||||||
func secretObject(c provider.Container, p *azsecrets.SecretProperties, tenant string) provider.Object {
|
func secretObject(c provider.Container, p *azsecrets.SecretProperties, tenant string) provider.Object {
|
||||||
name := p.ID.Name()
|
name := p.ID.Name()
|
||||||
url := objectURL(c, "secrets", name)
|
url := objectURL(c, provider.TypeSecret, name)
|
||||||
o := provider.Object{
|
o := provider.Object{
|
||||||
Container: c,
|
Container: c,
|
||||||
Type: provider.TypeSecret,
|
Type: provider.TypeSecret,
|
||||||
@@ -103,7 +97,7 @@ func secretObject(c provider.Container, p *azsecrets.SecretProperties, tenant st
|
|||||||
ContentType: deref(p.ContentType),
|
ContentType: deref(p.ContentType),
|
||||||
Tags: tags(p.Tags),
|
Tags: tags(p.Tags),
|
||||||
Reference: "@Microsoft.KeyVault(SecretUri=" + url + ")",
|
Reference: "@Microsoft.KeyVault(SecretUri=" + url + ")",
|
||||||
URL: portalAssetURL(tenant, "Secret", strings.TrimSuffix(url+"/"+p.ID.Version(), "/")),
|
URL: portalAssetURL(tenant, provider.TypeSecret, strings.TrimSuffix(url+"/"+p.ID.Version(), "/")),
|
||||||
}
|
}
|
||||||
if a := p.Attributes; a != nil {
|
if a := p.Attributes; a != nil {
|
||||||
o.Enabled, o.Updated, o.Expires = a.Enabled, deref(a.Updated), deref(a.Expires)
|
o.Enabled, o.Updated, o.Expires = a.Enabled, deref(a.Updated), deref(a.Expires)
|
||||||
@@ -113,7 +107,7 @@ func secretObject(c provider.Container, p *azsecrets.SecretProperties, tenant st
|
|||||||
|
|
||||||
func keyObject(c provider.Container, p *azkeys.KeyProperties) provider.Object {
|
func keyObject(c provider.Container, p *azkeys.KeyProperties) provider.Object {
|
||||||
name := p.KID.Name()
|
name := p.KID.Name()
|
||||||
url := objectURL(c, "keys", name)
|
url := objectURL(c, provider.TypeKey, name)
|
||||||
o := provider.Object{
|
o := provider.Object{
|
||||||
Container: c,
|
Container: c,
|
||||||
Type: provider.TypeKey,
|
Type: provider.TypeKey,
|
||||||
@@ -132,7 +126,7 @@ func keyObject(c provider.Container, p *azkeys.KeyProperties) provider.Object {
|
|||||||
|
|
||||||
func certObject(c provider.Container, p *azcertificates.CertificateProperties) provider.Object {
|
func certObject(c provider.Container, p *azcertificates.CertificateProperties) provider.Object {
|
||||||
name := p.ID.Name()
|
name := p.ID.Name()
|
||||||
url := objectURL(c, "certificates", name)
|
url := objectURL(c, provider.TypeCertificate, name)
|
||||||
o := provider.Object{
|
o := provider.Object{
|
||||||
Container: c,
|
Container: c,
|
||||||
Type: provider.TypeCertificate,
|
Type: provider.TypeCertificate,
|
||||||
|
|||||||
@@ -70,13 +70,10 @@ func graphInfo(m map[string]any) vaultInfo {
|
|||||||
|
|
||||||
// armInfo reads a vault's settings from the management API.
|
// armInfo reads a vault's settings from the management API.
|
||||||
func armInfo(v *armkeyvault.Vault) vaultInfo {
|
func armInfo(v *armkeyvault.Vault) vaultInfo {
|
||||||
info := vaultInfo{location: deref(v.Location), tags: map[string]string{}}
|
info := vaultInfo{location: deref(v.Location), tags: tags(v.Tags)}
|
||||||
if id, err := arm.ParseResourceID(deref(v.ID)); err == nil {
|
if id, err := arm.ParseResourceID(deref(v.ID)); err == nil {
|
||||||
info.group = id.ResourceGroupName
|
info.group = id.ResourceGroupName
|
||||||
}
|
}
|
||||||
for k, t := range v.Tags {
|
|
||||||
info.tags[k] = deref(t)
|
|
||||||
}
|
|
||||||
pr := v.Properties
|
pr := v.Properties
|
||||||
if pr == nil {
|
if pr == nil {
|
||||||
return info
|
return info
|
||||||
@@ -122,7 +119,11 @@ func (v vaultInfo) attrs() []provider.Attr {
|
|||||||
} else {
|
} else {
|
||||||
add("soft delete", "off")
|
add("soft delete", "off")
|
||||||
}
|
}
|
||||||
add("purge prot.", map[bool]string{true: "on", false: "off"}[on(v.purge, false)])
|
if on(v.purge, false) {
|
||||||
|
add("purge prot.", "on")
|
||||||
|
} else {
|
||||||
|
add("purge prot.", "off")
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case strings.EqualFold(v.publicAccess, "disabled"):
|
case strings.EqualFold(v.publicAccess, "disabled"):
|
||||||
add("network", "private endpoints only")
|
add("network", "private endpoints only")
|
||||||
@@ -131,8 +132,11 @@ func (v vaultInfo) attrs() []provider.Attr {
|
|||||||
default:
|
default:
|
||||||
add("network", "all networks")
|
add("network", "all networks")
|
||||||
}
|
}
|
||||||
if v.endpoints > 0 {
|
switch {
|
||||||
add("private", fmt.Sprintf("%d endpoint%s", v.endpoints, map[bool]string{true: "s"}[v.endpoints > 1]))
|
case v.endpoints == 1:
|
||||||
|
add("private", "1 endpoint")
|
||||||
|
case v.endpoints > 1:
|
||||||
|
add("private", fmt.Sprintf("%d endpoints", v.endpoints))
|
||||||
}
|
}
|
||||||
keys := make([]string, 0, len(v.tags))
|
keys := make([]string, 0, len(v.tags))
|
||||||
for k := range v.tags {
|
for k := range v.tags {
|
||||||
|
|||||||
@@ -203,17 +203,20 @@ func (p *Provider) Containers(ctx context.Context, src provider.Source) ([]provi
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// vaultURL is the backing vault of an AKV-backed scope, or "". It falls
|
// fallbackVault is the backing vault of an AKV-backed scope, to read it
|
||||||
// back to the scope's Detail, so a scope remembered from an earlier session
|
// from when Databricks fails, or "" when there's none to fall back to. It
|
||||||
// (a favourite) works before its workspace's scopes are listed.
|
// falls back to the scope's Detail, so a scope remembered from an earlier
|
||||||
func (p *Provider) vaultURL(c provider.Container) string {
|
// session (a favourite) works before its workspace's scopes are listed.
|
||||||
|
func (p *Provider) fallbackVault(ctx context.Context, c provider.Container) string {
|
||||||
|
if p.keyVault == nil || ctx.Err() != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
url := p.vaults[c.ID]
|
url := p.vaults[c.ID]
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
if url == "" {
|
if url == "" {
|
||||||
url, _ = strings.CutPrefix(c.Detail, akvDetail)
|
if detail, ok := strings.CutPrefix(c.Detail, akvDetail); ok {
|
||||||
if url == c.Detail {
|
url = detail
|
||||||
url = ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return url
|
return url
|
||||||
@@ -264,8 +267,8 @@ func (p *Provider) Objects(ctx context.Context, c provider.Container, t provider
|
|||||||
|
|
||||||
// vaultObjects lists an AKV-backed scope's secrets from the vault itself.
|
// vaultObjects lists an AKV-backed scope's secrets from the vault itself.
|
||||||
func (p *Provider) vaultObjects(ctx context.Context, c provider.Container) ([]provider.Object, error) {
|
func (p *Provider) vaultObjects(ctx context.Context, c provider.Container) ([]provider.Object, error) {
|
||||||
url := p.vaultURL(c)
|
url := p.fallbackVault(ctx, c)
|
||||||
if url == "" || p.keyVault == nil || ctx.Err() != nil {
|
if url == "" {
|
||||||
return nil, errNoFallback
|
return nil, errNoFallback
|
||||||
}
|
}
|
||||||
secrets, err := p.keyVault.Objects(ctx, provider.Container{Detail: url}, provider.TypeSecret)
|
secrets, err := p.keyVault.Objects(ctx, provider.Container{Detail: url}, provider.TypeSecret)
|
||||||
@@ -302,8 +305,8 @@ func (p *Provider) Value(ctx context.Context, o provider.Object) (provider.Secre
|
|||||||
}
|
}
|
||||||
dbxErr := classify(pr, err)
|
dbxErr := classify(pr, err)
|
||||||
|
|
||||||
url := p.vaultURL(o.Container)
|
url := p.fallbackVault(ctx, o.Container)
|
||||||
if url == "" || p.keyVault == nil || ctx.Err() != nil {
|
if url == "" {
|
||||||
return provider.SecretValue{}, dbxErr
|
return provider.SecretValue{}, dbxErr
|
||||||
}
|
}
|
||||||
v, kvErr := p.keyVault.Value(ctx, provider.Object{
|
v, kvErr := p.keyVault.Value(ctx, provider.Object{
|
||||||
|
|||||||
@@ -236,9 +236,7 @@ func (p *Provider) add(c provider.Container, t provider.ObjectType, o provider.O
|
|||||||
o.Type = t
|
o.Type = t
|
||||||
o.ID = c.ID + "/" + strings.ToLower(string(t)) + "/" + o.Name
|
o.ID = c.ID + "/" + strings.ToLower(string(t)) + "/" + o.Name
|
||||||
if base, ok := strings.CutSuffix(c.URL, "/overview"); ok {
|
if base, ok := strings.CutSuffix(c.URL, "/overview"); ok {
|
||||||
o.URL = base + map[provider.ObjectType]string{
|
o.URL = base + "/" + t.Collection()
|
||||||
provider.TypeSecret: "/secrets", provider.TypeKey: "/keys", provider.TypeCertificate: "/certificates",
|
|
||||||
}[t]
|
|
||||||
}
|
}
|
||||||
k := listKey{c.ID, t}
|
k := listKey{c.ID, t}
|
||||||
p.objects[k] = append(p.objects[k], o)
|
p.objects[k] = append(p.objects[k], o)
|
||||||
@@ -271,12 +269,13 @@ const (
|
|||||||
|
|
||||||
var allTypes = []provider.ObjectType{provider.TypeSecret, provider.TypeKey, provider.TypeCertificate}
|
var allTypes = []provider.ObjectType{provider.TypeSecret, provider.TypeKey, provider.TypeCertificate}
|
||||||
|
|
||||||
|
func secretRef(vault, name string) string {
|
||||||
|
return fmt.Sprintf("@Microsoft.KeyVault(SecretUri=https://%s.vault.azure.net/secrets/%s)", vault, name)
|
||||||
|
}
|
||||||
|
|
||||||
// Azure returns a fake Azure provider with two subscriptions.
|
// Azure returns a fake Azure provider with two subscriptions.
|
||||||
func Azure(delay time.Duration) *Provider {
|
func Azure(delay time.Duration) *Provider {
|
||||||
p := newProvider(provider.KindAzure, delay)
|
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 {
|
objectURL := func(vault, kind, name string) string {
|
||||||
return fmt.Sprintf("https://%s.vault.azure.net/%s/%s", vault, kind, name)
|
return fmt.Sprintf("https://%s.vault.azure.net/%s/%s", vault, kind, name)
|
||||||
}
|
}
|
||||||
@@ -388,9 +387,6 @@ func Azure(delay time.Duration) *Provider {
|
|||||||
// its own, like an admin account next to a personal one.
|
// its own, like an admin account next to a personal one.
|
||||||
func AzureOps(delay time.Duration) *Provider {
|
func AzureOps(delay time.Duration) *Provider {
|
||||||
p := newProvider(provider.KindAzure, delay)
|
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")
|
prod := p.addSource("sub-prod", "platform-prod", "tenant: example.onmicrosoft.com")
|
||||||
breakGlass := p.addContainer(prod, "kv-breakglass", "https://kv-breakglass.vault.azure.net", allTypes...)
|
breakGlass := p.addContainer(prod, "kv-breakglass", "https://kv-breakglass.vault.azure.net", allTypes...)
|
||||||
|
|||||||
@@ -84,6 +84,20 @@ const (
|
|||||||
TypeCertificate ObjectType = "Certificate"
|
TypeCertificate ObjectType = "Certificate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Collection is the type's path segment in Key Vault URLs and links, e.g.
|
||||||
|
// "secrets".
|
||||||
|
func (t ObjectType) Collection() string {
|
||||||
|
switch t {
|
||||||
|
case TypeSecret:
|
||||||
|
return "secrets"
|
||||||
|
case TypeKey:
|
||||||
|
return "keys"
|
||||||
|
case TypeCertificate:
|
||||||
|
return "certificates"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Container holds objects: a Key Vault or a Databricks secret scope.
|
// Container holds objects: a Key Vault or a Databricks secret scope.
|
||||||
type Container struct {
|
type Container struct {
|
||||||
Source Source
|
Source Source
|
||||||
|
|||||||
@@ -361,30 +361,23 @@ func (p *Provider) Objects(_ context.Context, c provider.Container, t provider.O
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
meta, isSops := readMeta(c.ID)
|
meta, isSops := parseMeta(c.ID, b)
|
||||||
keys, err := parseKeys(c.ID, b)
|
keys, err := parseKeys(c.ID, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", c.Name, err)
|
return nil, fmt.Errorf("%s: %w", c.Name, err)
|
||||||
}
|
}
|
||||||
if !isSops { // a plain env file
|
attrs := []provider.Attr{{Label: "format", Value: "env"}, {Label: "encrypted", Value: "no: plain text in the file"}}
|
||||||
out := make([]provider.Object, 0, len(keys))
|
if isSops {
|
||||||
for _, k := range keys {
|
attrs = []provider.Attr{{Label: "format", Value: "sops " + formatOf(c.ID)}, {Label: "encryption", Value: strings.Join(meta.methods, ", ")}}
|
||||||
out = append(out, provider.Object{
|
|
||||||
Container: c, Type: provider.TypeSecret, ID: c.ID + "\x00" + k.extract, Name: k.name,
|
|
||||||
Attrs: []provider.Attr{{Label: "format", Value: "env"}, {Label: "encrypted", Value: "no: plain text in the file"}},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
}
|
||||||
out := make([]provider.Object, 0, len(keys))
|
out := make([]provider.Object, 0, len(keys))
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
o := provider.Object{
|
o := provider.Object{
|
||||||
Container: c, Type: provider.TypeSecret,
|
Container: c, Type: provider.TypeSecret,
|
||||||
ID: c.ID + "\x00" + k.extract, Name: k.name, Updated: meta.modified,
|
ID: c.ID + "\x00" + k.extract, Name: k.name, Updated: meta.modified, Attrs: attrs,
|
||||||
Attrs: []provider.Attr{{Label: "format", Value: "sops " + formatOf(c.ID)}, {Label: "encryption", Value: strings.Join(meta.methods, ", ")}},
|
|
||||||
}
|
}
|
||||||
if !k.encrypted {
|
if isSops && !k.encrypted {
|
||||||
o.Attrs = append(o.Attrs, provider.Attr{Label: "encrypted", Value: "no (left in plain text by the file's sops rules)"})
|
o.Attrs = append(slices.Clip(attrs), provider.Attr{Label: "encrypted", Value: "no (left in plain text by the file's sops rules)"})
|
||||||
}
|
}
|
||||||
out = append(out, o)
|
out = append(out, o)
|
||||||
}
|
}
|
||||||
@@ -403,10 +396,7 @@ func (p *Provider) Versions(context.Context, provider.Object) ([]provider.Object
|
|||||||
func (p *Provider) Value(ctx context.Context, o provider.Object) (provider.SecretValue, error) {
|
func (p *Provider) Value(ctx context.Context, o provider.Object) (provider.SecretValue, error) {
|
||||||
file, extract, ok := strings.Cut(o.ID, "\x00")
|
file, extract, ok := strings.Cut(o.ID, "\x00")
|
||||||
if !ok { // made from a link: the repo's root, the file's path, the key
|
if !ok { // made from a link: the repo's root, the file's path, the key
|
||||||
file = o.Container.ID
|
file = filePath(o.Container)
|
||||||
if file == "" {
|
|
||||||
file = filepath.Join(o.Container.Source.ID, filepath.FromSlash(o.Container.Name))
|
|
||||||
}
|
|
||||||
extract = extractFor(file, o.Name)
|
extract = extractFor(file, o.Name)
|
||||||
}
|
}
|
||||||
if _, isSops := readMeta(file); !isSops {
|
if _, isSops := readMeta(file); !isSops {
|
||||||
@@ -434,12 +424,18 @@ func (p *Provider) Contents(_ context.Context, c provider.Container) ([]byte, er
|
|||||||
return os.ReadFile(f.Path)
|
return os.ReadFile(f.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filePath is the file c is. A container made from a link has no ID: the
|
||||||
|
// file is its path in the repo.
|
||||||
|
func filePath(c provider.Container) string {
|
||||||
|
if c.ID != "" {
|
||||||
|
return c.ID
|
||||||
|
}
|
||||||
|
return filepath.Join(c.Source.ID, filepath.FromSlash(c.Name))
|
||||||
|
}
|
||||||
|
|
||||||
// File is the file c is, and the line o's key is on.
|
// File is the file c is, and the line o's key is on.
|
||||||
func (p *Provider) File(c provider.Container, o *provider.Object) (provider.File, error) {
|
func (p *Provider) File(c provider.Container, o *provider.Object) (provider.File, error) {
|
||||||
path := c.ID
|
path := filePath(c)
|
||||||
if path == "" { // made from a link
|
|
||||||
path = filepath.Join(c.Source.ID, filepath.FromSlash(c.Name))
|
|
||||||
}
|
|
||||||
f := provider.File{Path: path}
|
f := provider.File{Path: path}
|
||||||
if _, isSops := readMeta(path); isSops {
|
if _, isSops := readMeta(path); isSops {
|
||||||
f.Sops = p.s.Sops
|
f.Sops = p.s.Sops
|
||||||
@@ -609,34 +605,39 @@ type lazysecretsFile struct {
|
|||||||
|
|
||||||
// ShareKey is the sharing.key of the repo's .lazysecrets.yaml, if any.
|
// ShareKey is the sharing.key of the repo's .lazysecrets.yaml, if any.
|
||||||
func ShareKey(root string) (string, error) {
|
func ShareKey(root string) (string, error) {
|
||||||
|
f, err := readLazysecretsFile(root)
|
||||||
|
return f.Sharing.Key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// readLazysecretsFile reads the repo's .lazysecrets.yaml; without one, it's
|
||||||
|
// empty.
|
||||||
|
func readLazysecretsFile(root string) (lazysecretsFile, error) {
|
||||||
|
var f lazysecretsFile
|
||||||
b, err := os.ReadFile(filepath.Join(root, ".lazysecrets.yaml"))
|
b, err := os.ReadFile(filepath.Join(root, ".lazysecrets.yaml"))
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return "", nil
|
return f, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return f, err
|
||||||
}
|
}
|
||||||
var f lazysecretsFile
|
|
||||||
if err := yaml.Unmarshal(b, &f); err != nil {
|
if err := yaml.Unmarshal(b, &f); err != nil {
|
||||||
return "", fmt.Errorf("%s/.lazysecrets.yaml: %w", root, err)
|
return f, fmt.Errorf("%s/.lazysecrets.yaml: %w", root, err)
|
||||||
}
|
}
|
||||||
return f.Sharing.Key, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// selector says which files to look at: user (the include/exclude of the
|
// selector says which files to look at: user (the include/exclude of the
|
||||||
// repo's .lazysecrets.yaml or the config) for every file, and sopsWhere
|
// repo's .lazysecrets.yaml or the config) for every file, and sopsWhere
|
||||||
// (.sops.yaml's creation_rules, when there's no include) for SOPS files.
|
// (.sops.yaml's creation_rules, when there's no include) for SOPS files.
|
||||||
func (p *Provider) selector(root string, files []string) (user, sopsWhere func(string) bool, err error) {
|
func (p *Provider) selector(root string, files []string) (user, sopsWhere func(string) bool, err error) {
|
||||||
include, exclude := p.s.Include, p.s.Exclude
|
f, err := readLazysecretsFile(root)
|
||||||
if b, err := os.ReadFile(filepath.Join(root, ".lazysecrets.yaml")); err == nil {
|
if err != nil {
|
||||||
var f lazysecretsFile
|
return nil, nil, err
|
||||||
if err := yaml.Unmarshal(b, &f); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("%s/.lazysecrets.yaml: %w", root, err)
|
|
||||||
}
|
}
|
||||||
|
include, exclude := p.s.Include, p.s.Exclude
|
||||||
if len(f.Include) > 0 || len(f.Exclude) > 0 {
|
if len(f.Include) > 0 || len(f.Exclude) > 0 {
|
||||||
include, exclude = f.Include, f.Exclude
|
include, exclude = f.Include, f.Exclude
|
||||||
}
|
}
|
||||||
}
|
|
||||||
inc, err := globs(include)
|
inc, err := globs(include)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -907,12 +908,21 @@ func readMeta(file string) (sopsMeta, bool) {
|
|||||||
return sopsMeta{}, false // not a format sops writes: skip reading
|
return sopsMeta{}, false // not a format sops writes: skip reading
|
||||||
}
|
}
|
||||||
b, err := os.ReadFile(file)
|
b, err := os.ReadFile(file)
|
||||||
if err != nil || !bytes.Contains(b, []byte("sops")) {
|
if err != nil {
|
||||||
|
return sopsMeta{}, false
|
||||||
|
}
|
||||||
|
return parseMeta(file, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMeta is readMeta, of the file's contents b.
|
||||||
|
func parseMeta(file string, b []byte) (sopsMeta, bool) {
|
||||||
|
format := formatOf(file)
|
||||||
|
if len(b) > maxFileSize || format == "" || !bytes.Contains(b, []byte("sops")) {
|
||||||
return sopsMeta{}, false // cheap: most files aren't sops, no parsing
|
return sopsMeta{}, false // cheap: most files aren't sops, no parsing
|
||||||
}
|
}
|
||||||
var m sopsMeta
|
var m sopsMeta
|
||||||
known := []string{"age", "azure_kv", "kms", "gcp_kms", "hc_vault", "pgp"}
|
known := []string{"age", "azure_kv", "kms", "gcp_kms", "hc_vault", "pgp"}
|
||||||
switch formatOf(file) {
|
switch format {
|
||||||
case "yaml", "json":
|
case "yaml", "json":
|
||||||
var doc struct {
|
var doc struct {
|
||||||
Sops map[string]any `yaml:"sops"`
|
Sops map[string]any `yaml:"sops"`
|
||||||
|
|||||||
+7
-13
@@ -92,15 +92,9 @@ func (r Ref) ContainerName() string {
|
|||||||
// Container.Detail.
|
// Container.Detail.
|
||||||
func (r Ref) VaultURL() string { return "https://" + r.Host + "/" }
|
func (r Ref) VaultURL() string { return "https://" + r.Host + "/" }
|
||||||
|
|
||||||
var collections = map[provider.ObjectType]string{
|
|
||||||
provider.TypeSecret: "secrets",
|
|
||||||
provider.TypeKey: "keys",
|
|
||||||
provider.TypeCertificate: "certificates",
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeOf(collection string) (provider.ObjectType, bool) {
|
func typeOf(collection string) (provider.ObjectType, bool) {
|
||||||
for t, c := range collections {
|
for _, t := range []provider.ObjectType{provider.TypeSecret, provider.TypeKey, provider.TypeCertificate} {
|
||||||
if strings.EqualFold(c, collection) {
|
if strings.EqualFold(t.Collection(), collection) {
|
||||||
return t, true
|
return t, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +112,7 @@ func (r Ref) IdentifierURL() string {
|
|||||||
if r.Type == "" {
|
if r.Type == "" {
|
||||||
return u + "/"
|
return u + "/"
|
||||||
}
|
}
|
||||||
for _, seg := range []string{collections[r.Type], r.Name, r.Version} {
|
for _, seg := range []string{r.Type.Collection(), r.Name, r.Version} {
|
||||||
if seg != "" {
|
if seg != "" {
|
||||||
u += "/" + url.PathEscape(seg)
|
u += "/" + url.PathEscape(seg)
|
||||||
}
|
}
|
||||||
@@ -140,7 +134,7 @@ func (r Ref) String() string {
|
|||||||
segs = append(segs, r.Scope, r.Name)
|
segs = append(segs, r.Scope, r.Name)
|
||||||
default:
|
default:
|
||||||
if r.Type != "" {
|
if r.Type != "" {
|
||||||
segs = append(segs, collections[r.Type])
|
segs = append(segs, r.Type.Collection())
|
||||||
}
|
}
|
||||||
segs = append(segs, r.Name, r.Version)
|
segs = append(segs, r.Name, r.Version)
|
||||||
}
|
}
|
||||||
@@ -273,7 +267,7 @@ func parseLink(s string) (Ref, error) {
|
|||||||
r.Host = strings.ToLower(segs[0])
|
r.Host = strings.ToLower(segs[0])
|
||||||
rest := segs[1:]
|
rest := segs[1:]
|
||||||
|
|
||||||
switch strings.ToLower(u.Host) {
|
switch host := strings.ToLower(u.Host); host {
|
||||||
case "azure":
|
case "azure":
|
||||||
r.Kind = provider.KindAzure
|
r.Kind = provider.KindAzure
|
||||||
if r.Host == "subscriptions" {
|
if r.Host == "subscriptions" {
|
||||||
@@ -300,11 +294,11 @@ func parseLink(s string) (Ref, error) {
|
|||||||
}
|
}
|
||||||
case "databricks", "repo":
|
case "databricks", "repo":
|
||||||
r.Kind = provider.KindDatabricks
|
r.Kind = provider.KindDatabricks
|
||||||
if strings.EqualFold(u.Host, "repo") {
|
if host == "repo" {
|
||||||
r.Kind = provider.KindRepo
|
r.Kind = provider.KindRepo
|
||||||
}
|
}
|
||||||
if len(rest) > 2 {
|
if len(rest) > 2 {
|
||||||
return Ref{}, fmt.Errorf("%s link has too many parts", strings.ToLower(u.Host))
|
return Ref{}, fmt.Errorf("%s link has too many parts", host)
|
||||||
}
|
}
|
||||||
if len(rest) > 0 {
|
if len(rest) > 0 {
|
||||||
r.Scope = rest[0]
|
r.Scope = rest[0]
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ func (r Rules) Check(key []byte) error {
|
|||||||
needs = append(needs, fmt.Sprintf("%d (minLength)", r.MinLength))
|
needs = append(needs, fmt.Sprintf("%d (minLength)", r.MinLength))
|
||||||
}
|
}
|
||||||
if kinds < r.MinClasses {
|
if kinds < r.MinClasses {
|
||||||
has = append(has, fmt.Sprintf("%d %s of character", kinds, map[bool]string{true: "kind", false: "kinds"}[kinds == 1]))
|
kind := "kinds"
|
||||||
|
if kinds == 1 {
|
||||||
|
kind = "kind"
|
||||||
|
}
|
||||||
|
has = append(has, fmt.Sprintf("%d %s of character", kinds, kind))
|
||||||
needs = append(needs, fmt.Sprintf("%d of lowercase, uppercase, digits and symbols (minClasses)", r.MinClasses))
|
needs = append(needs, fmt.Sprintf("%d of lowercase, uppercase, digits and symbols (minClasses)", r.MinClasses))
|
||||||
}
|
}
|
||||||
if len(has) == 0 {
|
if len(has) == 0 {
|
||||||
|
|||||||
Reference in new issue
Block a user