From d9229a5fbafcc77ec5f54cab783ecf3bcf6dcedd Mon Sep 17 00:00:00 2001 From: Daniel Huckins Date: Fri, 24 Feb 2023 15:03:21 -0500 Subject: [PATCH] VAULT-12112: add openapi responses for /sys/internal endpoints (#18542) * added responses for sys/internal/ui/mounts Signed-off-by: Daniel Huckins * responses for internal paths Signed-off-by: Daniel Huckins * added changelog * add schema validation for internal/ui/mounts Signed-off-by: Daniel Huckins * add counters test Signed-off-by: Daniel Huckins * update test to use new method Signed-off-by: Daniel Huckins * use new method in TestSystemBackend_InternalUIMounts Signed-off-by: Daniel Huckins * :rage4: fixed test, diff between core.HandleRequest and backend.HandleRequest Signed-off-by: Daniel Huckins * test feature flags Signed-off-by: Daniel Huckins --------- Signed-off-by: Daniel Huckins --- changelog/18542.txt | 3 + vault/counters_test.go | 13 +- .../api/feature_flag_ext_test.go | 4 +- vault/logical_system_paths.go | 145 ++++++++++++++++++ vault/logical_system_test.go | 38 +++++ 5 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 changelog/18542.txt diff --git a/changelog/18542.txt b/changelog/18542.txt new file mode 100644 index 000000000..ff4674010 --- /dev/null +++ b/changelog/18542.txt @@ -0,0 +1,3 @@ +```release-note:improvement +openapi: add openapi response definitions to /sys/internal endpoints +``` diff --git a/vault/counters_test.go b/vault/counters_test.go index 30f3f1a24..85d92b10d 100644 --- a/vault/counters_test.go +++ b/vault/counters_test.go @@ -6,6 +6,7 @@ import ( "github.com/go-test/deep" "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/sdk/helper/testhelpers/schema" "github.com/hashicorp/vault/sdk/logical" ) @@ -23,14 +24,22 @@ func testCountActiveTokens(t *testing.T, c *Core, root string) int { t.Helper() rootCtx := namespace.RootContext(nil) - resp, err := c.HandleRequest(rootCtx, &logical.Request{ + req := &logical.Request{ ClientToken: root, Operation: logical.ReadOperation, Path: "sys/internal/counters/tokens", - }) + } + resp, err := c.HandleRequest(rootCtx, req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("bad: resp: %#v\n err: %v", resp, err) } + schema.ValidateResponse( + t, + // we remove the `sys/` prefix b/c Core removes it before routing to the 'sys' backend + schema.GetResponseSchema(t, c.systemBackend.Route("internal/counters/tokens"), req.Operation), + resp, + true, + ) activeTokens := resp.Data["counters"].(*ActiveTokens) return activeTokens.ServiceTokens.Total diff --git a/vault/external_tests/api/feature_flag_ext_test.go b/vault/external_tests/api/feature_flag_ext_test.go index 039ad68bd..6b042250a 100644 --- a/vault/external_tests/api/feature_flag_ext_test.go +++ b/vault/external_tests/api/feature_flag_ext_test.go @@ -9,13 +9,15 @@ import ( "github.com/hashicorp/go-cleanhttp" vaulthttp "github.com/hashicorp/vault/http" + "github.com/hashicorp/vault/sdk/helper/testhelpers/schema" "github.com/hashicorp/vault/vault" "golang.org/x/net/http2" ) func TestFeatureFlags(t *testing.T) { cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{ - HandlerFunc: vaulthttp.Handler, + HandlerFunc: vaulthttp.Handler, + RequestResponseCallback: schema.ResponseValidatingCallback(t), }) cluster.Start() defer cluster.Cleanup() diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index 670e80961..8da7eacf6 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -1612,6 +1612,17 @@ func (b *SystemBackend) internalPaths() []*framework.Path { logical.ReadOperation: &framework.PathOperation{ // callback is absent because this is an unauthenticated method Summary: "Lists enabled feature flags.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "feature_flags": { + Type: framework.TypeCommaStringSlice, + Required: true, + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-feature-flags"][0]), @@ -1623,6 +1634,23 @@ func (b *SystemBackend) internalPaths() []*framework.Path { logical.ReadOperation: &framework.PathOperation{ Callback: b.pathInternalUIMountsRead, Summary: "Lists all enabled and visible auth and secrets mounts.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "secret": { + Description: "secret mounts", + Type: framework.TypeMap, + Required: true, + }, + "auth": { + Description: "auth mounts", + Type: framework.TypeMap, + Required: true, + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-mounts"][0]), @@ -1640,6 +1668,65 @@ func (b *SystemBackend) internalPaths() []*framework.Path { logical.ReadOperation: &framework.PathOperation{ Callback: b.pathInternalUIMountRead, Summary: "Return information about the given mount.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "type": { + Type: framework.TypeString, + Required: true, + }, + "description": { + Type: framework.TypeString, + Required: true, + }, + "accessor": { + Type: framework.TypeString, + Required: true, + }, + "local": { + Type: framework.TypeBool, + Required: true, + }, + "seal_wrap": { + Type: framework.TypeBool, + Required: true, + }, + "external_entropy_access": { + Type: framework.TypeBool, + Required: true, + }, + "options": { + Type: framework.TypeMap, + Required: true, + }, + "uuid": { + Type: framework.TypeString, + Required: true, + }, + "plugin_version": { + Type: framework.TypeString, + Required: true, + }, + "running_plugin_version": { + Type: framework.TypeString, + Required: true, + }, + "running_sha256": { + Type: framework.TypeString, + Required: true, + }, + "path": { + Type: framework.TypeString, + Required: true, + }, + "config": { + Type: framework.TypeMap, + Required: true, + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-mounts"][0]), @@ -1651,6 +1738,17 @@ func (b *SystemBackend) internalPaths() []*framework.Path { logical.ReadOperation: &framework.PathOperation{ Callback: pathInternalUINamespacesRead(b), Summary: "Backwards compatibility is not guaranteed for this API", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeCommaStringSlice, + Description: "field is only returned if there are one or more namespaces", + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-namespaces"][0]), @@ -1662,6 +1760,29 @@ func (b *SystemBackend) internalPaths() []*framework.Path { logical.ReadOperation: &framework.PathOperation{ Callback: b.pathInternalUIResultantACL, Summary: "Backwards compatibility is not guaranteed for this API", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "empty response returned if no client token", + Fields: nil, + }}, + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "root": { + Type: framework.TypeBool, + Required: true, + }, + "exact_paths": { + Type: framework.TypeMap, + Required: false, + }, + "glob_paths": { + Type: framework.TypeMap, + Required: false, + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-resultant-acl"][0]), @@ -1673,6 +1794,8 @@ func (b *SystemBackend) internalPaths() []*framework.Path { logical.ReadOperation: &framework.PathOperation{ Callback: b.pathInternalCountersRequests, Summary: "Backwards compatibility is not guaranteed for this API", + // callback only returns errors + Responses: nil, }, }, HelpSynopsis: strings.TrimSpace(sysHelp["internal-counters-requests"][0]), @@ -1684,6 +1807,17 @@ func (b *SystemBackend) internalPaths() []*framework.Path { logical.ReadOperation: &framework.PathOperation{ Callback: b.pathInternalCountersTokens, Summary: "Backwards compatibility is not guaranteed for this API", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "counters": { + Type: framework.TypeMap, + Required: true, + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(sysHelp["internal-counters-tokens"][0]), @@ -1695,6 +1829,17 @@ func (b *SystemBackend) internalPaths() []*framework.Path { logical.ReadOperation: &framework.PathOperation{ Callback: b.pathInternalCountersEntities, Summary: "Backwards compatibility is not guaranteed for this API", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "counters": { + Type: framework.TypeMap, + Required: true, + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(sysHelp["internal-counters-entities"][0]), diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 9075be14f..4647f0ddf 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -3630,6 +3630,7 @@ func TestSystemBackend_ToolsRandom(t *testing.T) { func TestSystemBackend_InternalUIMounts(t *testing.T) { _, b, rootToken := testCoreSystemBackend(t) + systemBackend := b.(*SystemBackend) // Ensure no entries are in the endpoint as a starting point req := logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts") @@ -3637,6 +3638,12 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation), + resp, + true, + ) exp := map[string]interface{}{ "secret": map[string]interface{}{}, @@ -3652,6 +3659,12 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation), + resp, + true, + ) exp = map[string]interface{}{ "secret": map[string]interface{}{ @@ -3787,6 +3800,12 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation), + resp, + true, + ) exp = map[string]interface{}{ "secret": map[string]interface{}{ @@ -3811,6 +3830,7 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) { func TestSystemBackend_InternalUIMount(t *testing.T) { core, b, rootToken := testCoreSystemBackend(t) + systemBackend := b.(*SystemBackend) req := logical.TestRequest(t, logical.UpdateOperation, "policy/secret") req.ClientToken = rootToken @@ -3840,6 +3860,12 @@ func TestSystemBackend_InternalUIMount(t *testing.T) { if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("Bad %#v %#v", err, resp) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation), + resp, + true, + ) if resp.Data["type"] != "kv" { t.Fatalf("Bad Response: %#v", resp) } @@ -3859,6 +3885,12 @@ func TestSystemBackend_InternalUIMount(t *testing.T) { if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("Bad %#v %#v", err, resp) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation), + resp, + true, + ) if resp.Data["type"] != "kv" { t.Fatalf("Bad Response: %#v", resp) } @@ -3869,6 +3901,12 @@ func TestSystemBackend_InternalUIMount(t *testing.T) { if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("Bad %#v %#v", err, resp) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, systemBackend.Route(req.Path), req.Operation), + resp, + true, + ) if resp.Data["type"] != "system" { t.Fatalf("Bad Response: %#v", resp) }