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) | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										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
	 Steven Clark
					Steven Clark