Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introspection API Implementation for Router Struct #17789

Merged
merged 2 commits into from
Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/plugin_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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$`),
Expand Down
3 changes: 3 additions & 0 deletions changelog/17789.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
sys/internal/inspect: Creates an endpoint to look to inspect internal subsystems.
```
11 changes: 11 additions & 0 deletions vault/inspectable.go
Original file line number Diff line number Diff line change
@@ -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 changes: 78 additions & 0 deletions vault/inspectable_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
23 changes: 23 additions & 0 deletions vault/logical_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
"leases/lookup/*",
"storage/raft/snapshot-auto/config/*",
"leases",
"internal/inspect/*",
},

Unauthenticated: []string{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
17 changes: 17 additions & 0 deletions vault/logical_system_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
},
}
}

Expand Down
1 change: 1 addition & 0 deletions vault/logical_system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func TestSystemBackend_RootPaths(t *testing.T) {
"leases/lookup/*",
"storage/raft/snapshot-auto/config/*",
"leases",
"internal/inspect/*",
}

b := testSystemBackend(t)
Expand Down
10 changes: 10 additions & 0 deletions vault/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions vault/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down