Introspection API Implementation for Router Struct (#17789)
* OSS Commit from ENT for Introspection API * Add changelog
This commit is contained in:
parent
419ba9159c
commit
2d3775a93b
|
@ -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$`),
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
sys/internal/inspect: Creates an endpoint to look to inspect internal subsystems.
|
||||
```
|
|
@ -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{}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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]),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ func TestSystemBackend_RootPaths(t *testing.T) {
|
|||
"leases/lookup/*",
|
||||
"storage/raft/snapshot-auto/config/*",
|
||||
"leases",
|
||||
"internal/inspect/*",
|
||||
}
|
||||
|
||||
b := testSystemBackend(t)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue