From 0704127020183467c0787a278613b25cdd7e7053 Mon Sep 17 00:00:00 2001 From: Leland Ursu Date: Wed, 15 Feb 2023 15:02:21 -0500 Subject: [PATCH] added OpenAPI response objects for sys endpoints (#18633) * added response objects for sys 3 section * Update vault/logical_system_paths.go Co-authored-by: Daniel Huckins * Update vault/logical_raw.go Co-authored-by: Daniel Huckins * Update vault/logical_system_paths.go Co-authored-by: Daniel Huckins * Update vault/logical_system_quotas.go Co-authored-by: Daniel Huckins * Update vault/logical_system_quotas.go Co-authored-by: Daniel Huckins * Update vault/logical_system_quotas.go Co-authored-by: Daniel Huckins * add tests and update based on reviews * added changelog file * finally got make fmt to work... * fixed copy pasta test case * updated based on review * Update vault/logical_system_quotas.go Co-authored-by: Anton Averchenkov <84287187+averche@users.noreply.github.com> * Update vault/logical_system_test.go Co-authored-by: Daniel Huckins * Update vault/logical_system_test.go Co-authored-by: Daniel Huckins --------- Co-authored-by: lursu Co-authored-by: Daniel Huckins Co-authored-by: Anton Averchenkov <84287187+averche@users.noreply.github.com> --- changelog/18633.txt | 3 + vault/logical_raw.go | 48 +++++- vault/logical_system_paths.go | 267 ++++++++++++++++++++++++++++++++- vault/logical_system_quotas.go | 81 ++++++++++ vault/logical_system_test.go | 17 +++ 5 files changed, 404 insertions(+), 12 deletions(-) create mode 100644 changelog/18633.txt diff --git a/changelog/18633.txt b/changelog/18633.txt new file mode 100644 index 000000000..2048c46d9 --- /dev/null +++ b/changelog/18633.txt @@ -0,0 +1,3 @@ +```release-note:improvement +openapi: Add openapi response definitions to /sys defined endpoints. +``` \ No newline at end of file diff --git a/vault/logical_raw.go b/vault/logical_raw.go index 0d51db2f5..827a77c6b 100644 --- a/vault/logical_raw.go +++ b/vault/logical_raw.go @@ -5,6 +5,7 @@ import ( "context" "encoding/base64" "fmt" + "net/http" "strings" log "github.com/hashicorp/go-hclog" @@ -315,23 +316,60 @@ func rawPaths(prefix string, r *RawBackend) []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: r.handleRawRead, - Summary: "Read the value of the key at the given path.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "value": { + Type: framework.TypeString, + Required: true, + }, + }, + }}, + }, + Summary: "Read the value of the key at the given path.", }, logical.UpdateOperation: &framework.PathOperation{ Callback: r.handleRawWrite, - Summary: "Update the value of the key at the given path.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + Summary: "Update the value of the key at the given path.", }, logical.CreateOperation: &framework.PathOperation{ Callback: r.handleRawWrite, - Summary: "Create a key with value at the given path.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Create a key with value at the given path.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: r.handleRawDelete, - Summary: "Delete the key with given path.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Delete the key with given path.", }, logical.ListOperation: &framework.PathOperation{ Callback: r.handleRawList, - Summary: "Return a list keys for a given path prefix.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeStringSlice, + Required: true, + }, + }, + }}, + }, + Summary: "Return a list keys for a given path prefix.", }, }, diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index 2a4245fd0..94d6e0cf3 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -375,6 +375,47 @@ func (b *SystemBackend) configPaths() []*framework.Path { } func (b *SystemBackend) rekeyPaths() []*framework.Path { + respFields := map[string]*framework.FieldSchema{ + "nounce": { + Type: framework.TypeString, + Required: true, + }, + "started": { + Type: framework.TypeString, + Required: true, + }, + "t": { + Type: framework.TypeInt, + Required: true, + }, + "n": { + Type: framework.TypeInt, + Required: true, + }, + "progress": { + Type: framework.TypeInt, + Required: true, + }, + "required": { + Type: framework.TypeInt, + Required: true, + }, + "verification_required": { + Type: framework.TypeBool, + Required: true, + }, + "verification_nonce": { + Type: framework.TypeString, + Required: true, + }, + "backup": { + Type: framework.TypeBool, + }, + "pgp_fingerprints": { + Type: framework.TypeCommaStringSlice, + }, + } + return []*framework.Path{ { Pattern: "rekey/init", @@ -404,13 +445,30 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: respFields, + }}, + }, Summary: "Reads the configuration and progress of the current rekey attempt.", }, logical.UpdateOperation: &framework.PathOperation{ + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: respFields, + }}, + }, Summary: "Initializes a new rekey attempt.", Description: "Only a single rekey attempt can take place at a time, and changing the parameters of a rekey requires canceling and starting a new rekey, which will also provide a new nonce.", }, logical.DeleteOperation: &framework.PathOperation{ + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Cancels any in-progress rekey.", Description: "This clears the rekey settings as well as any progress made. This must be called to change the parameters of the rekey. Note: verification is still a part of a rekey. If rekeying is canceled during the verification flow, the current unseal keys remain valid.", }, @@ -424,11 +482,35 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handleRekeyRetrieveBarrier, - Summary: "Return the backup copy of PGP-encrypted unseal keys.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "nonce": { + Type: framework.TypeString, + Required: true, + }, + "keys": { + Type: framework.TypeMap, + Required: true, + }, + "keys_base64": { + Type: framework.TypeMap, + Required: true, + }, + }, + }}, + }, + Summary: "Return the backup copy of PGP-encrypted unseal keys.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handleRekeyDeleteBarrier, - Summary: "Delete the backup copy of PGP-encrypted unseal keys.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Delete the backup copy of PGP-encrypted unseal keys.", }, }, @@ -441,9 +523,37 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path { Fields: map[string]*framework.FieldSchema{}, - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.handleRekeyRetrieveRecovery, - logical.DeleteOperation: b.handleRekeyDeleteRecovery, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handleRekeyRetrieveRecovery, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "nonce": { + Type: framework.TypeString, + Required: true, + }, + "keys": { + Type: framework.TypeMap, + Required: true, + }, + "keys_base64": { + Type: framework.TypeMap, + Required: true, + }, + }, + }}, + }, + }, + logical.DeleteOperation: &framework.PathOperation{ + Callback: b.handleRekeyDeleteRecovery, + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["rekey_backup"][0]), @@ -465,6 +575,55 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "nounce": { + Type: framework.TypeString, + Required: true, + }, + "complete": { + Type: framework.TypeBool, + }, + "started": { + Type: framework.TypeString, + }, + "t": { + Type: framework.TypeInt, + }, + "n": { + Type: framework.TypeInt, + }, + "progress": { + Type: framework.TypeInt, + }, + "required": { + Type: framework.TypeInt, + }, + "keys": { + Type: framework.TypeCommaStringSlice, + }, + "keys_base64": { + Type: framework.TypeCommaStringSlice, + }, + "verification_required": { + Type: framework.TypeBool, + Required: true, + }, + "verification_nonce": { + Type: framework.TypeString, + Required: true, + }, + "backup": { + Type: framework.TypeBool, + }, + "pgp_fingerprints": { + Type: framework.TypeCommaStringSlice, + }, + }, + }}, + }, Summary: "Enter a single unseal key share to progress the rekey of the Vault.", }, }, @@ -485,13 +644,81 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "nounce": { + Type: framework.TypeString, + Required: true, + }, + "started": { + Type: framework.TypeString, + Required: true, + }, + "t": { + Type: framework.TypeInt, + Required: true, + }, + "n": { + Type: framework.TypeInt, + Required: true, + }, + "progress": { + Type: framework.TypeInt, + Required: true, + }, + }, + }}, + }, Summary: "Read the configuration and progress of the current rekey verification attempt.", }, logical.DeleteOperation: &framework.PathOperation{ + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "nounce": { + Type: framework.TypeString, + Required: true, + }, + "started": { + Type: framework.TypeString, + Required: true, + }, + "t": { + Type: framework.TypeInt, + Required: true, + }, + "n": { + Type: framework.TypeInt, + Required: true, + }, + "progress": { + Type: framework.TypeInt, + Required: true, + }, + }, + }}, + }, Summary: "Cancel any in-progress rekey verification operation.", Description: "This clears any progress made and resets the nonce. Unlike a `DELETE` against `sys/rekey/init`, this only resets the current verification operation, not the entire rekey atttempt.", }, logical.UpdateOperation: &framework.PathOperation{ + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "nounce": { + Type: framework.TypeString, + Required: true, + }, + "complete": { + Type: framework.TypeBool, + }, + }, + }}, + }, Summary: "Enter a single new key share to progress the rekey verification operation.", }, }, @@ -1706,7 +1933,18 @@ func (b *SystemBackend) remountPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleRemount, - Summary: "Initiate a mount migration", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "migration_id": { + Type: framework.TypeString, + Required: true, + }, + }, + }}, + }, + Summary: "Initiate a mount migration", }, }, HelpSynopsis: strings.TrimSpace(sysHelp["remount"][0]), @@ -1725,7 +1963,22 @@ func (b *SystemBackend) remountPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handleRemountStatusCheck, - Summary: "Check status of a mount migration", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "migration_id": { + Type: framework.TypeString, + Required: true, + }, + "migration_info": { + Type: framework.TypeMap, + Required: true, + }, + }, + }}, + }, + Summary: "Check status of a mount migration", }, }, HelpSynopsis: strings.TrimSpace(sysHelp["remount-status"][0]), diff --git a/vault/logical_system_quotas.go b/vault/logical_system_quotas.go index 9d8de5769..3795a026e 100644 --- a/vault/logical_system_quotas.go +++ b/vault/logical_system_quotas.go @@ -2,6 +2,7 @@ package vault import ( "context" + "net/http" "strings" "time" @@ -34,9 +35,33 @@ func (b *SystemBackend) quotasPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleQuotasConfigUpdate(), + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, }, logical.ReadOperation: &framework.PathOperation{ Callback: b.handleQuotasConfigRead(), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "enable_rate_limit_audit_logging": { + Type: framework.TypeBool, + Required: true, + }, + "enable_rate_limit_response_headers": { + Type: framework.TypeBool, + Required: true, + }, + "rate_limit_exempt_paths": { + Type: framework.TypeStringSlice, + Required: true, + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(quotasHelp["quotas-config"][0]), @@ -47,6 +72,17 @@ func (b *SystemBackend) quotasPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ListOperation: &framework.PathOperation{ Callback: b.handleRateLimitQuotasList(), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeStringSlice, + Required: true, + }, + }, + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(quotasHelp["rate-limit-list"][0]), @@ -92,12 +128,57 @@ from any further requests until after the 'block_interval' has elapsed.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleRateLimitQuotasUpdate(), + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: http.StatusText(http.StatusNoContent), + }}, + }, }, logical.ReadOperation: &framework.PathOperation{ Callback: b.handleRateLimitQuotasRead(), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "type": { + Type: framework.TypeString, + Required: true, + }, + "name": { + Type: framework.TypeString, + Required: true, + }, + "path": { + Type: framework.TypeString, + Required: true, + }, + "role": { + Type: framework.TypeString, + Required: true, + }, + "rate": { + Type: framework.TypeFloat, + Required: true, + }, + "interval": { + Type: framework.TypeInt, + Required: true, + }, + "block_interval": { + Type: framework.TypeInt, + Required: true, + }, + }, + }}, + }, }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handleRateLimitQuotasDelete(), + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, }, }, HelpSynopsis: strings.TrimSpace(quotasHelp["rate-limit"][0]), diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index c7c0da774..c872f0c09 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -758,12 +758,29 @@ func TestSystemBackend_remount_auth(t *testing.T) { req.Data["config"] = structs.Map(MountConfig{}) resp, err := b.HandleRequest(namespace.RootContext(nil), req) + // validate the response structure for remount named read + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + corehelpers.RetryUntil(t, 5*time.Second, func() error { req = logical.TestRequest(t, logical.ReadOperation, fmt.Sprintf("remount/status/%s", resp.Data["migration_id"])) resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil { t.Fatalf("err: %v", err) } + + // validate the response structure for remount status read + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + migrationInfo := resp.Data["migration_info"].(*MountMigrationInfo) if migrationInfo.MigrationStatus != MigrationSuccessStatus.String() { return fmt.Errorf("Expected migration status to be successful, got %q", migrationInfo.MigrationStatus)