diff --git a/internal/provider/azure/azure.go b/internal/provider/azure/azure.go index 522e406..69b8144 100644 --- a/internal/provider/azure/azure.go +++ b/internal/provider/azure/azure.go @@ -299,7 +299,7 @@ func (p *Provider) Objects(ctx context.Context, c provider.Container, t provider if s.ID == nil || deref(s.Managed) { continue } - out = append(out, secretObject(c, s)) + out = append(out, secretObject(c, s, p.tenant(c.Source.ID))) } } case provider.TypeKey: @@ -392,7 +392,7 @@ func (p *Provider) Versions(ctx context.Context, o provider.Object) ([]provider. } for _, s := range page.Value { if s.ID != nil { - out = append(out, secretObject(o.Container, s)) + out = append(out, secretObject(o.Container, s, p.tenant(o.Container.Source.ID))) } } } @@ -425,8 +425,13 @@ func (p *Provider) Versions(ctx context.Context, o provider.Object) ([]provider. default: return nil, provider.ErrUnsupported } + tenant := p.tenant(o.Container.Source.ID) for i := range out { out[i].Reference = versionedReference(out[i]) + 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, 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) }) return out, nil diff --git a/internal/provider/azure/azure_test.go b/internal/provider/azure/azure_test.go index a2417f7..5fd92ad 100644 --- a/internal/provider/azure/azure_test.go +++ b/internal/provider/azure/azure_test.go @@ -30,6 +30,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" "git.kebcloud.no/keb/lazysecrets/internal/provider" ) @@ -334,6 +335,21 @@ func TestPortalURLs(t *testing.T) { } } +func TestSecretPages(t *testing.T) { + c := provider.Container{Detail: "https://kv-app.vault.azure.net/"} + base := "https://portal.azure.com/#@contoso.onmicrosoft.com/asset/Microsoft_Azure_KeyVault/Secret/https://kv-app.vault.azure.net/secrets/db" + for id, want := range map[string]string{ + "https://kv-app.vault.azure.net/secrets/db": base, // listed: the secret's page + "https://kv-app.vault.azure.net/secrets/db/06140fa2b9a64869a96ef040f6d4dcab": base + "/06140fa2b9a64869a96ef040f6d4dcab", // a version's + } { + sid := azsecrets.ID(id) + o := secretObject(c, &azsecrets.SecretProperties{ID: &sid}, "contoso.onmicrosoft.com") + if o.URL != want { + t.Errorf("%s: %q, want %q", id, o.URL, want) + } + } +} + func TestVersionedReference(t *testing.T) { sec := provider.Object{Type: provider.TypeSecret, Version: "v1", Reference: "@Microsoft.KeyVault(SecretUri=https://kv.vault.azure.net/secrets/s)"} diff --git a/internal/provider/azure/convert.go b/internal/provider/azure/convert.go index 32f658c..d85b8d4 100644 --- a/internal/provider/azure/convert.go +++ b/internal/provider/azure/convert.go @@ -53,8 +53,8 @@ func PortalResourceURL(tenant, resourceID, blade string) string { return portal + tenantPrefix(tenant) + "resource" + resourceID + "/" + blade } -// portalAssetURL links to a Key Vault object's page; objectID must include -// the version. +// portalAssetURL links to a Key Vault object's page: a version's with +// one in objectID; a secret's own (its versions) without. func portalAssetURL(tenant, kind, objectID string) string { return portal + tenantPrefix(tenant) + "asset/Microsoft_Azure_KeyVault/" + kind + "/" + objectID } @@ -88,7 +88,10 @@ func objectURL(c provider.Container, collection, name string) string { return strings.TrimSuffix(c.Detail, "/") + "/" + collection + "/" + name } -func secretObject(c provider.Container, p *azsecrets.SecretProperties) provider.Object { +// secretObject is a secret, or a version of one, from a listing. Its URL +// is its page in the portal: the secret's (with its versions), or with +// a version, that version's. +func secretObject(c provider.Container, p *azsecrets.SecretProperties, tenant string) provider.Object { name := p.ID.Name() url := objectURL(c, "secrets", name) o := provider.Object{ @@ -100,7 +103,7 @@ func secretObject(c provider.Container, p *azsecrets.SecretProperties) provider. ContentType: deref(p.ContentType), Tags: tags(p.Tags), Reference: "@Microsoft.KeyVault(SecretUri=" + url + ")", - URL: ObjectListURL(c, provider.TypeSecret), + URL: portalAssetURL(tenant, "Secret", strings.TrimSuffix(url+"/"+p.ID.Version(), "/")), } if a := p.Attributes; a != nil { o.Enabled, o.Updated, o.Expires = a.Enabled, deref(a.Updated), deref(a.Expires)