Introspection API Implementation for Router Struct (#17789)

* OSS Commit from ENT for Introspection API

* Add changelog
This commit is contained in:
divyaac 2022-11-04 09:39:09 -07:00 committed by GitHub
parent 419ba9159c
commit 2d3775a93b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 0 deletions

View File

@ -63,6 +63,7 @@ var sudoPaths = map[string]*regexp.Regexp{
"/sys/revoke-force/{prefix}": regexp.MustCompile(`^/sys/revoke-force/.+$`),
"/sys/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
"/sys/rotate": regexp.MustCompile(`^/sys/rotate$`),
"/sys/internal/inspect/router/{tag}": regexp.MustCompile(`^/sys/internal/inspect/router/.+$`),
// enterprise-only paths
"/sys/replication/dr/primary/secondary-token": regexp.MustCompile(`^/sys/replication/dr/primary/secondary-token$`),

3
changelog/17789.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
sys/internal/inspect: Creates an endpoint to look to inspect internal subsystems.
```

11
vault/inspectable.go Normal file
View File

@ -0,0 +1,11 @@
package vault
type Inspectable interface {
// Returns a record view of a particular subsystem
GetRecords(tag string) ([]map[string]interface{}, error)
}
type Deserializable interface {
// Converts a structure into a consummable map
Deserialize() map[string]interface{}
}

78
vault/inspectable_test.go Normal file
View File

@ -0,0 +1,78 @@
package vault
import (
"strings"
"testing"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/logical"
)
func TestInspectRouter(t *testing.T) {
// Verify that all the expected tables exist when we inspect the router
c, _, root := TestCoreUnsealed(t)
rootCtx := namespace.RootContext(nil)
subTrees := map[string][]string{
"routeEntry": {"root", "storage"},
"mountEntry": {"uuid", "accessor"},
}
subTreeFields := map[string][]string{
"routeEntry": {"tainted", "storage_prefix", "accessor", "mount_namespace", "mount_path", "mount_type", "uuid"},
"mountEntry": {"accessor", "mount_namespace", "mount_path", "mount_type", "uuid"},
}
for subTreeType, subTreeArray := range subTrees {
for _, tag := range subTreeArray {
resp, err := c.HandleRequest(rootCtx, &logical.Request{
ClientToken: root,
Operation: logical.ReadOperation,
Path: "sys/internal/inspect/router/" + tag,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %v", resp, err)
}
// Verify that data exists
data, ok := resp.Data[tag].([]map[string]interface{})
if !ok {
t.Fatalf("Router data is malformed")
}
for _, entry := range data {
for _, field := range subTreeFields[subTreeType] {
if _, ok := entry[field]; !ok {
t.Fatalf("Field %s not found in %s", field, tag)
}
}
}
}
}
}
func TestInvalidInspectRouterPath(t *testing.T) {
// Verify that attempting to inspect an invalid tree in the router fails
core, _, rootToken := testCoreSystemBackend(t)
rootCtx := namespace.RootContext(nil)
_, err := core.HandleRequest(rootCtx, &logical.Request{
ClientToken: rootToken,
Operation: logical.ReadOperation,
Path: "sys/internal/inspect/router/random",
})
if !strings.Contains(err.Error(), logical.ErrUnsupportedPath.Error()) {
t.Fatal("expected unsupported path error")
}
}
func TestInspectAPISudoProtect(t *testing.T) {
// Verify that the Inspect API path is sudo protected
core, _, rootToken := testCoreSystemBackend(t)
testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"secret"})
rootCtx := namespace.RootContext(nil)
_, err := core.HandleRequest(rootCtx, &logical.Request{
ClientToken: "tokenid",
Operation: logical.ReadOperation,
Path: "sys/internal/inspect/router/root",
})
if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) {
t.Fatal("expected permission denied error")
}
}

View File

@ -113,6 +113,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
"leases/lookup/*",
"storage/raft/snapshot-auto/config/*",
"leases",
"internal/inspect/*",
},
Unauthenticated: []string{
@ -4274,6 +4275,20 @@ func (b *SystemBackend) pathInternalCountersEntities(ctx context.Context, req *l
return resp, nil
}
func (b *SystemBackend) pathInternalInspectRouter(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
tag := d.Get("tag").(string)
inspectableRouter, err := b.Core.router.GetRecords(tag)
if err != nil {
return nil, err
}
resp := &logical.Response{
Data: map[string]interface{}{
tag: inspectableRouter,
},
}
return resp, nil
}
func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
if req.ClientToken == "" {
// 204 -- no ACL
@ -5686,6 +5701,14 @@ This path responds to the following HTTP methods.
"Count of active entities in this Vault cluster.",
"Count of active entities in this Vault cluster.",
},
"internal-inspect-router": {
"Information on the entries in each of the trees in the router. Inspectable trees are uuid, accessor, storage, and root.",
`
This path responds to the following HTTP methods.
GET /
Returns a list of entries in specified table
`,
},
"host-info": {
"Information about the host instance that this Vault server is running on.",
`Information about the host instance that this Vault server is running on.

View File

@ -1063,6 +1063,23 @@ func (b *SystemBackend) internalPaths() []*framework.Path {
HelpSynopsis: strings.TrimSpace(sysHelp["internal-counters-entities"][0]),
HelpDescription: strings.TrimSpace(sysHelp["internal-counters-entities"][1]),
},
{
Pattern: "internal/inspect/router/" + framework.GenericNameRegex("tag"),
Fields: map[string]*framework.FieldSchema{
"tag": {
Type: framework.TypeString,
Description: "Name of subtree being observed",
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathInternalInspectRouter,
Summary: "Expose the route entry and mount entry tables present in the router",
},
},
HelpSynopsis: strings.TrimSpace(sysHelp["internal-inspect-router"][0]),
HelpDescription: strings.TrimSpace(sysHelp["internal-inspect-router"][1]),
},
}
}

View File

@ -63,6 +63,7 @@ func TestSystemBackend_RootPaths(t *testing.T) {
"leases/lookup/*",
"storage/raft/snapshot-auto/config/*",
"leases",
"internal/inspect/*",
}
b := testSystemBackend(t)

View File

@ -461,6 +461,16 @@ func (e *MountEntry) SyncCache() {
}
}
func (entry *MountEntry) Deserialize() map[string]interface{} {
return map[string]interface{}{
"mount_path": entry.Path,
"mount_namespace": entry.Namespace().Path,
"uuid": entry.UUID,
"accessor": entry.Accessor,
"mount_type": entry.Type,
}
}
func (c *Core) decodeMountTable(ctx context.Context, raw []byte) (*MountTable, error) {
// Decode into mount table
mountTable := new(MountTable)

View File

@ -94,6 +94,43 @@ func (r *Router) reset() {
r.mountAccessorCache = radix.New()
}
func (r *Router) GetRecords(tag string) ([]map[string]interface{}, error) {
r.l.RLock()
defer r.l.RUnlock()
var data []map[string]interface{}
var tree *radix.Tree
switch tag {
case "root":
tree = r.root
case "uuid":
tree = r.mountUUIDCache
case "accessor":
tree = r.mountAccessorCache
case "storage":
tree = r.storagePrefix
default:
return nil, logical.ErrUnsupportedPath
}
for _, v := range tree.ToMap() {
info := v.(Deserializable).Deserialize()
data = append(data, info)
}
return data, nil
}
func (entry *routeEntry) Deserialize() map[string]interface{} {
entry.l.RLock()
defer entry.l.RUnlock()
ret := map[string]interface{}{
"tainted": entry.tainted,
"storage_prefix": entry.storagePrefix,
}
for k, v := range entry.mountEntry.Deserialize() {
ret[k] = v
}
return ret
}
// ValidateMountByAccessor returns the mount type and ID for a given mount
// accessor
func (r *Router) ValidateMountByAccessor(accessor string) *ValidateMountResponse {