Add new /sys/well-known interface to get information about registered labels (#25695)

* Add new /sys/well-known interface to get information about registered labels

 - Add two new interfaces LIST/GET /sys/well-known which will provide
   a list of keys which are registered labels within the /.well-known space on
   the local server, along with a detailed info map for each
 - Add GET /sys/well-known/<label> to get details on a specific registered label
 - Add docs and tests for the new api endpoints

* Add test doc and remove copied comment

* Rename returned fields to use snake case

* Remove extra newline added when resolving the merge conflict

* Apply suggestions from code review

Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>

---------

Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
This commit is contained in:
Steven Clark
2024-02-29 17:07:49 -05:00
committed by GitHub
parent 7943a9e094
commit ab75d03a6c
6 changed files with 341 additions and 5 deletions

View File

@@ -15,6 +15,7 @@ import (
"hash"
"math/rand"
"net/http"
"net/url"
"path"
"path/filepath"
"runtime"
@@ -224,6 +225,7 @@ func NewSystemBackend(core *Core, logger log.Logger, config *logical.BackendConf
b.Backend.Paths = append(b.Backend.Paths, b.loginMFAPaths()...)
b.Backend.Paths = append(b.Backend.Paths, b.experimentPaths()...)
b.Backend.Paths = append(b.Backend.Paths, b.introspectionPaths()...)
b.Backend.Paths = append(b.Backend.Paths, b.wellKnownPaths()...)
if core.rawEnabled {
b.Backend.Paths = append(b.Backend.Paths, b.rawPaths()...)
@@ -6001,6 +6003,62 @@ func (b *SystemBackend) handleReadExperiments(ctx context.Context, _ *logical.Re
}, nil
}
// handleWellKnownList handles /sys/well-known/ endpoint to provide the list of registered labels
// within the /.well-known path on this server
func (b *SystemBackend) handleWellKnownList() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
labels := b.Core.WellKnownRedirects.List()
respKeys := []string{}
respKeyInfo := map[string]interface{}{}
for label, wk := range labels {
respKeys = append(respKeys, label)
mountPath := ""
m := b.Core.router.MatchingMountByUUID(wk.mountUUID)
if m != nil {
mountPath, _ = url.JoinPath(m.namespace.Path, m.Path)
}
respKeyInfo[label] = map[string]interface{}{
"mount_path": mountPath,
"mount_uuid": wk.mountUUID,
"prefix": wk.prefix,
}
}
return logical.ListResponseWithInfo(respKeys, respKeyInfo), nil
}
}
// handleWellKnownRead handles the "/sys/well-known/<name>" endpoint to read a registered well-known label
func (b *SystemBackend) handleWellKnownRead() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
label := data.Get("label").(string)
wk, ok := b.Core.WellKnownRedirects.Get(label)
if !ok {
return nil, nil
}
mountPath := ""
m := b.Core.router.MatchingMountByUUID(wk.mountUUID)
if m != nil {
mountPath, _ = url.JoinPath(m.namespace.Path, m.Path)
}
return &logical.Response{
Data: map[string]interface{}{
"label": label,
"mount_path": mountPath,
"mount_uuid": wk.mountUUID,
"prefix": wk.prefix,
},
}, nil
}
}
func sanitizePath(path string) string {
if !strings.HasSuffix(path, "/") {
path += "/"
@@ -7005,4 +7063,31 @@ This path responds to the following HTTP methods.
returns the manual license reporting data.
`,
},
"well-known-list": {
`List the registered well-known labels to associated mounts.`,
`
This path responds to the following HTTP methods.
LIST /
List the names of the registered labels within the .well-known folder on this server.
GET /
List the names of the registered labels within the .well-known folder on this server.
GET /<label>
Retrieve the information regarding a specific registered label on this server.
`,
},
"well-known": {
`Read the associated mount info for a registered label within the well-known path prefix`,
`
Retrieve the information regarding a specific registered label on this server.
`,
},
"well-known-label": {
`The label representing a path-prefix within the /.well-known/ path`,
"",
},
}

View File

@@ -5200,3 +5200,98 @@ func (b *SystemBackend) eventPaths() []*framework.Path {
},
}
}
func (b *SystemBackend) wellKnownPaths() []*framework.Path {
return []*framework.Path{
{
Pattern: "well-known/?$",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: "well-known",
OperationVerb: "list",
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.handleWellKnownList(),
Responses: map[int][]framework.Response{
http.StatusOK: {{
Description: "OK",
Fields: map[string]*framework.FieldSchema{
"keys": {
Type: framework.TypeStringSlice,
Required: true,
},
},
}},
},
},
logical.ListOperation: &framework.PathOperation{
Callback: b.handleWellKnownList(),
Responses: map[int][]framework.Response{
http.StatusOK: {{
Description: "OK",
Fields: map[string]*framework.FieldSchema{
"keys": {
Type: framework.TypeStringSlice,
Required: true,
},
},
}},
},
},
},
HelpSynopsis: strings.TrimSpace(sysHelp["well-known-list"][0]),
HelpDescription: strings.TrimSpace(sysHelp["well-known-list"][1]),
},
{
Pattern: "well-known/(?P<label>.+)",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: "well-known",
OperationSuffix: "label",
},
Fields: map[string]*framework.FieldSchema{
"label": {
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["well-known-label"][0]),
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.handleWellKnownRead(),
Responses: map[int][]framework.Response{
http.StatusOK: {{
Description: "OK",
Fields: map[string]*framework.FieldSchema{
"label": {
Type: framework.TypeString,
Required: true,
},
"mount_uuid": {
Type: framework.TypeString,
Required: true,
},
"mount_path": {
Type: framework.TypeString,
Required: true,
},
"prefix": {
Type: framework.TypeString,
Required: true,
},
},
}},
},
Summary: "Retrieve the associated mount information for a registered well-known label.",
},
},
HelpSynopsis: strings.TrimSpace(sysHelp["well-known"][0]),
HelpDescription: strings.TrimSpace(sysHelp["well-known"][1]),
},
}
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/hashicorp/go-hclog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2"
"github.com/hashicorp/go-uuid"
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
"github.com/hashicorp/vault/helper/builtinplugins"
"github.com/hashicorp/vault/helper/experiments"
@@ -7023,3 +7024,52 @@ func TestGetSealStatus_RedactionSettings(t *testing.T) {
assert.Empty(t, resp.BuildDate)
assert.Empty(t, resp.ClusterName)
}
// TestWellKnownSysApi verifies the GET/LIST endpoints of /sys/well-known and /sys/well-known/<label>
func TestWellKnownSysApi(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
b := c.systemBackend
myUuid, err := uuid.GenerateUUID()
require.NoError(t, err, "failed generating uuid")
err = c.WellKnownRedirects.TryRegister(context.Background(), c, myUuid, "mylabel1", "test-path/foo1")
require.NoError(t, err, "failed registering well-known redirect 1")
err = c.WellKnownRedirects.TryRegister(context.Background(), c, myUuid, "mylabel2", "test-path/foo2")
require.NoError(t, err, "failed registering well-known redirect 2")
// Test LIST operation
req := logical.TestRequest(t, logical.ListOperation, "well-known")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
require.NoError(t, err, "failed list well-known request")
require.NotNil(t, resp, "response from list well-known request was nil")
require.Contains(t, resp.Data["keys"], "mylabel1")
require.Contains(t, resp.Data["keys"], "mylabel2")
require.Len(t, resp.Data["keys"], 2)
keyInfo := resp.Data["key_info"].(map[string]interface{})
keyInfoLabel1 := keyInfo["mylabel1"].(map[string]interface{})
require.Equal(t, myUuid, keyInfoLabel1["mount_uuid"])
require.Equal(t, "test-path/foo1", keyInfoLabel1["prefix"])
keyInfoLabel2 := keyInfo["mylabel2"].(map[string]interface{})
require.Equal(t, myUuid, keyInfoLabel2["mount_uuid"])
require.Equal(t, "test-path/foo2", keyInfoLabel2["prefix"])
// Test GET operation
req = logical.TestRequest(t, logical.ReadOperation, "well-known/mylabel1")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
require.NoError(t, err, "failed get well-known request")
require.NotNil(t, resp, "response from get well-known request was nil")
require.Equal(t, myUuid, resp.Data["mount_uuid"])
require.Equal(t, "test-path/foo1", resp.Data["prefix"])
// Test GET operation on unknown label
req = logical.TestRequest(t, logical.ReadOperation, "well-known/mylabel1-unknown")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
require.NoError(t, err, "failed get well-known request")
require.Nil(t, resp, "response from unknown should have been nil was %v", resp)
}

View File

@@ -15,14 +15,13 @@ import (
)
type wellKnownRedirect struct {
c *Core
mountUUID string
prefix string
isPrefixMatch bool
c *Core
mountUUID string
prefix string
}
type wellKnownRedirectRegistry struct {
lock sync.Mutex
lock sync.RWMutex
paths *radix.Tree
}
@@ -54,6 +53,9 @@ func (reg *wellKnownRedirectRegistry) TryRegister(ctx context.Context, core *Cor
// Find any relevant redirects for a given source path
func (reg *wellKnownRedirectRegistry) Find(path string) (*wellKnownRedirect, string) {
reg.lock.RLock()
defer reg.lock.RUnlock()
s, a, found := reg.paths.LongestPrefix(path)
if found {
remaining := strings.TrimPrefix(path, s)
@@ -107,6 +109,32 @@ func (reg *wellKnownRedirectRegistry) DeregisterSource(mountUuid, src string) bo
return found
}
// List returns a map keyed by the registered label and associated well known redirect
func (reg *wellKnownRedirectRegistry) List() map[string]*wellKnownRedirect {
reg.lock.RLock()
defer reg.lock.RUnlock()
labels := map[string]*wellKnownRedirect{}
reg.paths.Walk(func(s string, v interface{}) bool {
labels[s] = v.(*wellKnownRedirect)
return false
})
return labels
}
// Get returns a well known redirect for a specific registered label
func (reg *wellKnownRedirectRegistry) Get(label string) (*wellKnownRedirect, bool) {
reg.lock.RLock()
defer reg.lock.RUnlock()
if v, ok := reg.paths.Get(label); ok {
return v.(*wellKnownRedirect), true
}
return nil, false
}
// Construct the full destination of the redirect, including any remaining path past the src
func (a *wellKnownRedirect) Destination(remaining string) (string, error) {
var destPath string

View File

@@ -0,0 +1,74 @@
---
layout: api
page_title: /sys/well-known - HTTP API
description: The `/sys/well-known` endpoint is used to list and read registered .well-known labels in Vault.
---
# `/sys/well-known`
Use the `/sys/well-known` endpoint to list and read registered labels on the `.well-known/` path prefix for your Vault instance.
## List well-known
List all registered labels.
| Method | Path |
|:-------|:------------------|
| `GET` | `/sys/well-known` |
| `LIST` | `/sys/well-known` |
### Sample request
```shell-session
$ curl \
--header "X-Vault-Token: ..." \
http://127.0.0.1:8200/v1/sys/well-known
```
### Sample response
```json
{
"key_info": {
"est/cacerts": {
"mount_path": "ns1/pki_int/",
"mount_uuid": "fc9d3ee4-ae92-4e3e-c0e1-a1fdb3e3b8cf",
"prefix": "est/cacerts"
}
},
"keys": [
"est/cacerts"
]
}
```
## Read well-known
Retrieve information for the specified label.
| Method | Path |
| :----- |:-------------------------|
| `GET` | `/sys/well-known/:label` |
### Parameters
- `label` `(string: <required>)`  URL path parameter specifying the registered label to fetch.
### Sample request
```shell-session
$ curl \
--header "X-Vault-Token: ..." \
http://127.0.0.1:8200/v1/sys/well-known/est/cacerts
```
### Sample response
```json
{
"label": "est/cacerts",
"mount_path": "ns1/pki_int/",
"mount_uuid": "fc9d3ee4-ae92-4e3e-c0e1-a1fdb3e3b8cf",
"prefix": "est/cacerts"
}
```

View File

@@ -761,6 +761,10 @@
"title": "<code>/sys/version-history</code>",
"path": "system/version-history"
},
{
"title": "<code>/sys/well-known</code>",
"path": "system/well-known"
},
{
"title": "<code>/sys/wrapping/lookup</code>",
"path": "system/wrapping-lookup"