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-force/{prefix}": regexp.MustCompile(`^/sys/revoke-force/.+$`),
"/sys/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/revoke-prefix/.+$`), "/sys/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
"/sys/rotate": regexp.MustCompile(`^/sys/rotate$`), "/sys/rotate": regexp.MustCompile(`^/sys/rotate$`),
"/sys/internal/inspect/router/{tag}": regexp.MustCompile(`^/sys/internal/inspect/router/.+$`),
// enterprise-only paths // enterprise-only paths
"/sys/replication/dr/primary/secondary-token": regexp.MustCompile(`^/sys/replication/dr/primary/secondary-token$`), "/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/*", "leases/lookup/*",
"storage/raft/snapshot-auto/config/*", "storage/raft/snapshot-auto/config/*",
"leases", "leases",
"internal/inspect/*",
}, },
Unauthenticated: []string{ Unauthenticated: []string{
@ -4274,6 +4275,20 @@ func (b *SystemBackend) pathInternalCountersEntities(ctx context.Context, req *l
return resp, nil 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) { func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
if req.ClientToken == "" { if req.ClientToken == "" {
// 204 -- no ACL // 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.",
"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": { "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.",
`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]), HelpSynopsis: strings.TrimSpace(sysHelp["internal-counters-entities"][0]),
HelpDescription: strings.TrimSpace(sysHelp["internal-counters-entities"][1]), 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/*", "leases/lookup/*",
"storage/raft/snapshot-auto/config/*", "storage/raft/snapshot-auto/config/*",
"leases", "leases",
"internal/inspect/*",
} }
b := testSystemBackend(t) 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) { func (c *Core) decodeMountTable(ctx context.Context, raw []byte) (*MountTable, error) {
// Decode into mount table // Decode into mount table
mountTable := new(MountTable) mountTable := new(MountTable)

View File

@ -94,6 +94,43 @@ func (r *Router) reset() {
r.mountAccessorCache = radix.New() 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 // ValidateMountByAccessor returns the mount type and ID for a given mount
// accessor // accessor
func (r *Router) ValidateMountByAccessor(accessor string) *ValidateMountResponse { func (r *Router) ValidateMountByAccessor(accessor string) *ValidateMountResponse {