mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +00:00
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:
@@ -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`,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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]),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@ type wellKnownRedirect struct {
|
||||
c *Core
|
||||
mountUUID string
|
||||
prefix string
|
||||
isPrefixMatch bool
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
74
website/content/api-docs/system/well-known.mdx
Normal file
74
website/content/api-docs/system/well-known.mdx
Normal 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"
|
||||
}
|
||||
```
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user