diff --git a/changelog/18515.txt b/changelog/18515.txt new file mode 100644 index 000000000..86eb71b19 --- /dev/null +++ b/changelog/18515.txt @@ -0,0 +1,3 @@ +```release-note:improvement +openapi: Add openapi response definitions to vault/logical_system_paths.go defined endpoints. +``` diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index 26c3e5aa0..2a4245fd0 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -301,15 +301,30 @@ func (b *SystemBackend) configPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handleLoggersRead, - Summary: "Read the log level for all existing loggers.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + Summary: "Read the log level for all existing loggers.", }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleLoggersWrite, - Summary: "Modify the log level for all existing loggers.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Modify the log level for all existing loggers.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handleLoggersDelete, - Summary: "Revert the all loggers to use log level provided in config.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Revert the all loggers to use log level provided in config.", }, }, }, @@ -329,15 +344,30 @@ func (b *SystemBackend) configPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handleLoggersByNameRead, - Summary: "Read the log level for a single logger.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + Summary: "Read the log level for a single logger.", }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleLoggersByNameWrite, - Summary: "Modify the log level of a single logger.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Modify the log level of a single logger.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handleLoggersByNameDelete, - Summary: "Revert a single logger to use log level provided in config.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Revert a single logger to use log level provided in config.", }, }, }, @@ -841,15 +871,66 @@ func (b *SystemBackend) pluginsCatalogCRUDPath() *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handlePluginCatalogUpdate, - Summary: "Register a new plugin, or updates an existing one with the supplied name.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + Summary: "Register a new plugin, or updates an existing one with the supplied name.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handlePluginCatalogDelete, - Summary: "Remove the plugin with the given name.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + Summary: "Remove the plugin with the given name.", }, logical.ReadOperation: &framework.PathOperation{ Callback: b.handlePluginCatalogRead, - Summary: "Return the configuration data for the plugin with the given name.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "name": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["plugin-catalog_name"][0]), + Required: true, + }, + "sha256": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["plugin-catalog_sha-256"][0]), + Required: true, + }, + "command": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["plugin-catalog_command"][0]), + Required: true, + }, + "args": { + Type: framework.TypeStringSlice, + Description: strings.TrimSpace(sysHelp["plugin-catalog_args"][0]), + Required: true, + }, + "version": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]), + Required: true, + }, + "builtin": { + Type: framework.TypeBool, + Required: true, + }, + "deprecation_status": { + Type: framework.TypeString, + Required: false, + }, + }, + }}, + }, + Summary: "Return the configuration data for the plugin with the given name.", }, }, @@ -873,7 +954,19 @@ func (b *SystemBackend) pluginsCatalogListPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ListOperation: &framework.PathOperation{ Callback: b.handlePluginCatalogTypedList, - Summary: "List the plugins in the catalog.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeStringSlice, + Description: "List of plugin names in the catalog", + Required: true, + }, + }, + }}, + }, + Summary: "List the plugins in the catalog.", }, }, @@ -883,8 +976,21 @@ func (b *SystemBackend) pluginsCatalogListPaths() []*framework.Path { { Pattern: "plugins/catalog/?$", - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.handlePluginCatalogUntypedList, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handlePluginCatalogUntypedList, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "detailed": { + Type: framework.TypeMap, + Required: false, + }, + }, + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["plugin-catalog-list-all"][0]), @@ -914,7 +1020,27 @@ func (b *SystemBackend) pluginsReloadPath() *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ - Callback: b.handlePluginReloadUpdate, + Callback: b.handlePluginReloadUpdate, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "reload_id": { + Type: framework.TypeString, + Required: true, + }, + }, + }}, + http.StatusAccepted: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "reload_id": { + Type: framework.TypeString, + Required: true, + }, + }, + }}, + }, Summary: "Reload mounted plugin backends.", Description: "Either the plugin name (`plugin`) or the desired plugin backend mounts (`mounts`) must be provided, but not both. In the case that the plugin name is provided, all mounted paths that use that plugin backend will be reloaded. If (`scope`) is provided and is (`global`), the plugin(s) are reloaded globally.", }, @@ -1246,7 +1372,18 @@ func (b *SystemBackend) leasePaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ListOperation: &framework.PathOperation{ Callback: b.handleLeaseLookupList, - Summary: "Returns a list of lease ids.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeCommaStringSlice, + Description: "A list of lease ids", + Required: false, + }, + }, + }}, + }, }, }, @@ -1267,7 +1404,43 @@ func (b *SystemBackend) leasePaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleLeaseLookup, - Summary: "Retrieve lease metadata.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "id": { + Type: framework.TypeString, + Description: "Lease id", + Required: true, + }, + "issue_time": { + Type: framework.TypeTime, + Description: "Timestamp for the lease's issue time", + Required: true, + }, + "renewable": { + Type: framework.TypeBool, + Description: "True if the lease is able to be renewed", + Required: true, + }, + "expire_time": { + Type: framework.TypeTime, + Description: "Optional lease expiry time ", + Required: true, + }, + "last_renewal": { + Type: framework.TypeTime, + Description: "Optional Timestamp of the last time the lease was renewed", + Required: true, + }, + "ttl": { + Type: framework.TypeInt, + Description: "Time to Live set for the lease, returns 0 if unset", + Required: true, + }, + }, + }}, + }, }, }, @@ -1296,7 +1469,12 @@ func (b *SystemBackend) leasePaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleRenew, - Summary: "Renews a lease, requesting to extend the lease.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Renews a lease, requesting to extend the lease.", }, }, @@ -1326,7 +1504,12 @@ func (b *SystemBackend) leasePaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleRevoke, - Summary: "Revokes a lease immediately.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Revokes a lease immediately.", }, }, @@ -1346,7 +1529,12 @@ func (b *SystemBackend) leasePaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ - Callback: b.handleRevokeForce, + Callback: b.handleRevokeForce, + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, Summary: "Revokes all secrets or tokens generated under a given prefix immediately", Description: "Unlike `/sys/leases/revoke-prefix`, this path ignores backend errors encountered during revocation. This is potentially very dangerous and should only be used in specific emergency situations where errors in the backend or the connected backend service prevent normal revocation.\n\nBy ignoring these errors, Vault abdicates responsibility for ensuring that the issued credentials or secrets are properly revoked and/or cleaned up. Access to this endpoint should be tightly controlled.", }, @@ -1374,7 +1562,12 @@ func (b *SystemBackend) leasePaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleRevokePrefix, - Summary: "Revokes all secrets (via a lease ID prefix) or tokens (via the tokens' path property) generated under a given prefix immediately.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Revokes all secrets (via a lease ID prefix) or tokens (via the tokens' path property) generated under a given prefix immediately.", }, }, @@ -1385,8 +1578,16 @@ func (b *SystemBackend) leasePaths() []*framework.Path { { Pattern: "leases/tidy$", - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: b.handleTidyLeases, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.handleTidyLeases, + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["tidy_leases"][0]), @@ -1408,9 +1609,28 @@ func (b *SystemBackend) leasePaths() []*framework.Path { }, }, - Callbacks: map[logical.Operation]framework.OperationFunc{ - // currently only works for irrevocable leases with param: type=irrevocable - logical.ReadOperation: b.handleLeaseCount, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + // currently only works for irrevocable leases with param: type=irrevocable + Callback: b.handleLeaseCount, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "lease_count": { + Type: framework.TypeInt, + Description: "Number of matching leases", + Required: true, + }, + "counts": { + Type: framework.TypeInt, + Description: "Number of matching leases per mount", + Required: true, + }, + }, + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["count-leases"][0]), @@ -1437,9 +1657,28 @@ func (b *SystemBackend) leasePaths() []*framework.Path { }, }, - Callbacks: map[logical.Operation]framework.OperationFunc{ - // currently only works for irrevocable leases with param: type=irrevocable - logical.ReadOperation: b.handleLeaseList, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + // currently only works for irrevocable leases with param: type=irrevocable + Callback: b.handleLeaseList, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "lease_count": { + Type: framework.TypeInt, + Description: "Number of matching leases", + Required: true, + }, + "counts": { + Type: framework.TypeInt, + Description: "Number of matching leases per mount", + Required: true, + }, + }, + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["list-leases"][0]), @@ -1505,8 +1744,15 @@ func (b *SystemBackend) metricsPath() *framework.Path { Query: true, }, }, - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.handleMetrics, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handleMetrics, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["metrics"][0]), HelpDescription: strings.TrimSpace(sysHelp["metrics"][1]), @@ -1529,8 +1775,15 @@ func (b *SystemBackend) monitorPath() *framework.Path { Default: "standard", }, }, - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.handleMonitor, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handleMonitor, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["monitor"][0]), HelpDescription: strings.TrimSpace(sysHelp["monitor"][1]), @@ -1721,9 +1974,41 @@ func (b *SystemBackend) policyPaths() []*framework.Path { { Pattern: "policy/?$", - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.handlePoliciesList(PolicyTypeACL), - logical.ListOperation: b.handlePoliciesList(PolicyTypeACL), + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handlePoliciesList(PolicyTypeACL), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeStringSlice, + Required: true, + }, + "policies": { + Type: framework.TypeStringSlice, + }, + }, + }}, + }, + }, + logical.ListOperation: &framework.PathOperation{ + Callback: b.handlePoliciesList(PolicyTypeACL), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeStringSlice, + Required: true, + }, + "policies": { + Type: framework.TypeStringSlice, + }, + }, + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["policy-list"][0]), @@ -1752,15 +2037,46 @@ func (b *SystemBackend) policyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handlePoliciesRead(PolicyTypeACL), - Summary: "Retrieve the policy body for the named policy.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "name": { + Type: framework.TypeString, + Required: true, + }, + "rules": { + Type: framework.TypeString, + Required: true, + }, + "policy": { + Type: framework.TypeString, + Required: false, + }, + }, + }}, + }, + Summary: "Retrieve the policy body for the named policy.", }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.handlePoliciesSet(PolicyTypeACL), - Summary: "Add a new or update an existing policy.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + Summary: "Add a new or update an existing policy.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handlePoliciesDelete(PolicyTypeACL), - Summary: "Delete the policy with the given name.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + Summary: "Delete the policy with the given name.", }, }, @@ -1771,8 +2087,24 @@ func (b *SystemBackend) policyPaths() []*framework.Path { { Pattern: "policies/acl/?$", - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ListOperation: b.handlePoliciesList(PolicyTypeACL), + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ListOperation: &framework.PathOperation{ + Callback: b.handlePoliciesList(PolicyTypeACL), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeStringSlice, + Required: true, + }, + "policies": { + Type: framework.TypeStringSlice, + }, + }, + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["policy-list"][0]), @@ -1796,15 +2128,46 @@ func (b *SystemBackend) policyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handlePoliciesRead(PolicyTypeACL), - Summary: "Retrieve information about the named ACL policy.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "name": { + Type: framework.TypeString, + Required: false, + }, + "rules": { + Type: framework.TypeString, + Required: false, + }, + "policy": { + Type: framework.TypeString, + Required: false, + }, + }, + }}, + }, + Summary: "Retrieve information about the named ACL policy.", }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.handlePoliciesSet(PolicyTypeACL), - Summary: "Add a new or update an existing ACL policy.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + Summary: "Add a new or update an existing ACL policy.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handlePoliciesDelete(PolicyTypeACL), - Summary: "Delete the ACL policy with the given name.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + Summary: "Delete the ACL policy with the given name.", }, }, @@ -1818,7 +2181,18 @@ func (b *SystemBackend) policyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ListOperation: &framework.PathOperation{ Callback: b.handlePoliciesPasswordList, - Summary: "List the existing password policies.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "keys": { + Type: framework.TypeStringSlice, + Required: false, + }, + }, + }}, + }, + Summary: "List the existing password policies.", }, }, }, @@ -1836,7 +2210,18 @@ func (b *SystemBackend) policyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handlePoliciesPasswordGenerate, - Summary: "Generate a password from an existing password policy.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "password": { + Type: framework.TypeString, + Required: true, + }, + }, + }}, + }, + Summary: "Generate a password from an existing password policy.", }, }, @@ -1861,15 +2246,38 @@ func (b *SystemBackend) policyPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handlePoliciesPasswordSet, - Summary: "Add a new or update an existing password policy.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + Summary: "Add a new or update an existing password policy.", }, logical.ReadOperation: &framework.PathOperation{ Callback: b.handlePoliciesPasswordGet, - Summary: "Retrieve an existing password policy.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "policy": { + Type: framework.TypeString, + Required: true, + }, + }, + }}, + }, + Summary: "Retrieve an existing password policy.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handlePoliciesPasswordDelete, - Summary: "Delete a password policy.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + Summary: "Delete a password policy.", }, }, @@ -2019,9 +2427,105 @@ func (b *SystemBackend) mountPaths() []*framework.Path { }, }, - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.handleMountTuneRead, - logical.UpdateOperation: b.handleMountTuneWrite, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handleMountTuneRead, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "max_lease_ttl": { + Type: framework.TypeInt, + Description: strings.TrimSpace(sysHelp["tune_max_lease_ttl"][0]), + Required: true, + }, + "description": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["auth_desc"][0]), + Required: true, + }, + "default_lease_ttl": { + Type: framework.TypeInt, + Description: strings.TrimSpace(sysHelp["tune_default_lease_ttl"][0]), + Required: true, + }, + "force_no_cache": { + Type: framework.TypeBool, + Required: true, + }, + "token_type": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["token_type"][0]), + Required: false, + }, + "allowed_managed_keys": { + Type: framework.TypeCommaStringSlice, + Description: strings.TrimSpace(sysHelp["tune_allowed_managed_keys"][0]), + Required: false, + }, + "allowed_response_headers": { + Type: framework.TypeCommaStringSlice, + Description: strings.TrimSpace(sysHelp["allowed_response_headers"][0]), + Required: false, + }, + "options": { + Type: framework.TypeKVPairs, + Description: strings.TrimSpace(sysHelp["tune_mount_options"][0]), + Required: false, + }, + "plugin_version": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]), + Required: false, + }, + "external_entropy_access": { + Type: framework.TypeBool, + Required: false, + }, + "audit_non_hmac_request_keys": { + Type: framework.TypeCommaStringSlice, + Required: false, + }, + "audit_non_hmac_response_keys": { + Type: framework.TypeCommaStringSlice, + Required: false, + }, + "listing_visibility": { + Type: framework.TypeString, + Required: false, + }, + "passthrough_request_headers": { + Type: framework.TypeCommaStringSlice, + Required: false, + }, + "user_lockout_counter_reset_duration": { + Type: framework.TypeInt64, + Required: false, + }, + "user_lockout_threshold": { + Type: framework.TypeInt64, // TODO this is actuall a Uint64 do we need a new type? + Required: false, + }, + "user_lockout_duration": { + Type: framework.TypeInt64, + Required: false, + }, + "user_lockout_disable": { + Type: framework.TypeBool, + Required: false, + }, + }, + }}, + }, + }, + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.handleMountTuneWrite, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["mount_tune"][0]), @@ -2080,15 +2584,93 @@ func (b *SystemBackend) mountPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handleReadMount, - Summary: "Read the configuration of the secret engine at the given path.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "type": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["mount_type"][0]), + Required: true, + }, + "description": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["mount_desc"][0]), + Required: true, + }, + "accessor": { + Type: framework.TypeString, + Required: true, + }, + "local": { + Type: framework.TypeBool, + Default: false, + Description: strings.TrimSpace(sysHelp["mount_local"][0]), + Required: true, + }, + "seal_wrap": { + Type: framework.TypeBool, + Default: false, + Description: strings.TrimSpace(sysHelp["seal_wrap"][0]), + Required: true, + }, + "external_entropy_access": { + Type: framework.TypeBool, + Required: true, + }, + "options": { + Type: framework.TypeKVPairs, + Description: strings.TrimSpace(sysHelp["mount_options"][0]), + Required: true, + }, + "plugin_version": { + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]), + Required: true, + }, + "uuid": { + Type: framework.TypeString, + Required: true, + }, + "running_plugin_version": { + Type: framework.TypeString, + Required: true, + }, + "running_sha256": { + Type: framework.TypeString, + Required: true, + }, + "config": { + Type: framework.TypeMap, + Description: strings.TrimSpace(sysHelp["mount_config"][0]), + Required: true, + }, + "deprecation_status": { + Type: framework.TypeString, + Required: false, + }, + }, + }}, + }, + Summary: "Read the configuration of the secret engine at the given path.", }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.handleMount, - Summary: "Enable a new secrets engine at the given path.", + Responses: map[int][]framework.Response{ + http.StatusNoContent: {{ + Description: "OK", + }}, + }, + Summary: "Enable a new secrets engine at the given path.", }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.handleUnmount, - Summary: "Disable the mount point specified at the given path.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + Summary: "Disable the mount point specified at the given path.", }, }, HelpSynopsis: strings.TrimSpace(sysHelp["mount"][0]), @@ -2098,8 +2680,16 @@ func (b *SystemBackend) mountPaths() []*framework.Path { { Pattern: "mounts$", - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.handleMountTable, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handleMountTable, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{}, + }}, + }, + }, }, HelpSynopsis: strings.TrimSpace(sysHelp["mounts"][0]), diff --git a/vault/logical_system_pprof.go b/vault/logical_system_pprof.go index ce7fc4b27..8e3d87846 100644 --- a/vault/logical_system_pprof.go +++ b/vault/logical_system_pprof.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/http" "net/http/pprof" "strconv" @@ -19,7 +20,12 @@ func (b *SystemBackend) pprofPaths() []*framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.handlePprofIndex, - Summary: "Returns an HTML page listing the available profiles.", + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + Summary: "Returns an HTML page listing the available profiles.", Description: `Returns an HTML page listing the available profiles. This should be mainly accessed via browsers or applications that can render pages.`, @@ -31,7 +37,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofCmdline, + Callback: b.handlePprofCmdline, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns the running program's command line.", Description: "Returns the running program's command line, with arguments separated by NUL bytes.", }, @@ -42,7 +53,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofGoroutine, + Callback: b.handlePprofGoroutine, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns stack traces of all current goroutines.", Description: "Returns stack traces of all current goroutines.", }, @@ -53,7 +69,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofHeap, + Callback: b.handlePprofHeap, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns a sampling of memory allocations of live object.", Description: "Returns a sampling of memory allocations of live object.", }, @@ -64,7 +85,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofAllocs, + Callback: b.handlePprofAllocs, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns a sampling of all past memory allocations.", Description: "Returns a sampling of all past memory allocations.", }, @@ -75,7 +101,13 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofThreadcreate, + Callback: b.handlePprofThreadcreate, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, + Summary: "Returns stack traces that led to the creation of new OS threads", Description: "Returns stack traces that led to the creation of new OS threads", }, @@ -86,7 +118,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofBlock, + Callback: b.handlePprofBlock, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns stack traces that led to blocking on synchronization primitives", Description: "Returns stack traces that led to blocking on synchronization primitives", }, @@ -97,7 +134,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofMutex, + Callback: b.handlePprofMutex, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns stack traces of holders of contended mutexes", Description: "Returns stack traces of holders of contended mutexes", }, @@ -115,7 +157,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofProfile, + Callback: b.handlePprofProfile, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns a pprof-formatted cpu profile payload.", Description: "Returns a pprof-formatted cpu profile payload. Profiling lasts for duration specified in seconds GET parameter, or for 30 seconds if not specified.", }, @@ -126,7 +173,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofSymbol, + Callback: b.handlePprofSymbol, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns the program counters listed in the request.", Description: "Returns the program counters listed in the request.", }, @@ -145,7 +197,12 @@ render pages.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ - Callback: b.handlePprofTrace, + Callback: b.handlePprofTrace, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Returns the execution trace in binary form.", Description: "Returns the execution trace in binary form. Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified.", }, diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 033beefda..c7c0da774 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -248,6 +248,14 @@ func TestSystemBackend_mounts(t *testing.T) { if diff := deep.Equal(resp.Data, conf); len(diff) > 0 { t.Fatalf("bad, diff: %#v", diff) } + + // validate the response structure for mount named read + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) } } @@ -270,9 +278,14 @@ func TestSystemBackend_mount(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - if resp != nil { - t.Fatalf("bad: %v", resp) - } + + // validate the response structure for mount named update + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) req = logical.TestRequest(t, logical.ReadOperation, "mounts") resp, err = b.HandleRequest(namespace.RootContext(nil), req) @@ -433,9 +446,14 @@ func TestSystemBackend_unmount(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - if resp != nil { - t.Fatalf("bad: %v", resp) - } + + // validate the response structure for mount named delete + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) } var capabilitiesPolicy = ` @@ -1095,6 +1113,15 @@ func TestSystemBackend_leases(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + + // validate the response structure for Update + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + if resp.Data["renewable"] == nil || resp.Data["renewable"].(bool) { t.Fatal("kv leases are not renewable") } @@ -1144,9 +1171,14 @@ func TestSystemBackend_leases_list(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - if resp == nil || resp.Data == nil { - t.Fatalf("bad: %#v", resp) - } + + // validate the response body for list + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) var keys []string if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil { t.Fatalf("err: %v", err) @@ -1305,6 +1337,14 @@ func TestSystemBackend_renew(t *testing.T) { t.Fatalf("err: %v", err) } + // Validate lease renewal response structure + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req2.Path), req2.Operation), + resp, + true, + ) + // Should get error about non-renewability if resp2.Data["error"] != "lease is not renewable" { t.Fatalf("bad: %#v", resp) @@ -1563,6 +1603,15 @@ func TestSystemBackend_revoke_invalidID(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + + // validate the response structure for lease revoke + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + if resp != nil { t.Fatalf("bad: %v", resp) } @@ -1630,9 +1679,14 @@ func TestSystemBackend_revokePrefix(t *testing.T) { if err != nil { t.Fatalf("err: %v %#v", err, resp2) } - if resp2 != nil { - t.Fatalf("bad: %#v", resp) - } + + // validate the response structure for lease revoke-prefix + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req2.Path), req2.Operation), + resp, + true, + ) // Attempt renew req3 := logical.TestRequest(t, logical.UpdateOperation, "leases/renew/"+resp.Secret.LeaseID) @@ -2066,6 +2120,14 @@ func TestSystemBackend_policyList(t *testing.T) { t.Fatalf("err: %v", err) } + // validate the response structure for policy read + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + exp := map[string]interface{}{ "keys": []string{"default", "root"}, "policies": []string{"default", "root"}, @@ -2090,6 +2152,14 @@ func TestSystemBackend_policyCRUD(t *testing.T) { t.Fatalf("bad: %#v", resp) } + // validate the response structure for policy named Update + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + // Read the policy req = logical.TestRequest(t, logical.ReadOperation, "policy/foo") resp, err = b.HandleRequest(namespace.RootContext(nil), req) @@ -2097,6 +2167,14 @@ func TestSystemBackend_policyCRUD(t *testing.T) { t.Fatalf("err: %v", err) } + // validate the response structure for policy named read + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + exp := map[string]interface{}{ "name": "foo", "rules": rules, @@ -2145,6 +2223,14 @@ func TestSystemBackend_policyCRUD(t *testing.T) { t.Fatalf("bad: %#v", resp) } + // validate the response structure for policy named delete + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + // Read the policy (deleted) req = logical.TestRequest(t, logical.ReadOperation, "policy/foo") resp, err = b.HandleRequest(namespace.RootContext(nil), req) @@ -2189,7 +2275,6 @@ func TestSystemBackend_enableAudit(t *testing.T) { func TestSystemBackend_auditHash(t *testing.T) { c, b, _ := testCoreSystemBackend(t) - paths := b.(*SystemBackend).auditPaths() c.auditBackends["noop"] = corehelpers.NoopAuditFactory(nil) req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo") @@ -2202,9 +2287,10 @@ func TestSystemBackend_auditHash(t *testing.T) { if resp != nil { t.Fatalf("bad: %v", resp) } + schema.ValidateResponse( t, - schema.FindResponseSchema(t, paths, 2, req.Operation), + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), resp, true, ) @@ -2219,9 +2305,10 @@ func TestSystemBackend_auditHash(t *testing.T) { if resp == nil || resp.Data == nil { t.Fatalf("response or its data was nil") } + schema.ValidateResponse( t, - schema.FindResponseSchema(t, paths, 0, req.Operation), + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), resp, true, ) @@ -2985,6 +3072,13 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { t.Fatalf("err: %v", err) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + if len(resp.Data["keys"].([]string)) != len(c.builtinRegistry.Keys(consts.PluginTypeDatabase)) { t.Fatalf("Wrong number of plugins, got %d, expected %d", len(resp.Data["keys"].([]string)), len(builtinplugins.Registry.Keys(consts.PluginTypeDatabase))) } @@ -2995,6 +3089,13 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { t.Fatalf("err: %v", err) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + // Get deprecation status directly from the registry so we can compare it to the API response deprecationStatus, _ := c.builtinRegistry.DeprecationStatus("mysql-database-plugin", consts.PluginTypeDatabase) @@ -3033,6 +3134,13 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { t.Fatalf("err: %v", resp.Error()) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + delete(req.Data, "args") resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || resp.Error() != nil { @@ -3065,6 +3173,13 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { t.Fatalf("err: %v", err) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/test-plugin") resp, err = b.HandleRequest(namespace.RootContext(nil), req) if resp != nil || err != nil { @@ -3490,6 +3605,14 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) { t.Fatalf("resp.Error: %v, err:%v", resp.Error(), err) } + // validate the response structure for mount update + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts") resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil { @@ -4828,7 +4951,6 @@ func TestSystemBackend_Loggers(t *testing.T) { t.Parallel() core, b, _ := testCoreSystemBackend(t) - // Test core overrides logging level outside of config, // an initial delete will ensure that we an initial read // to get expected values is based off of config and not @@ -4843,6 +4965,13 @@ func TestSystemBackend_Loggers(t *testing.T) { t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + req = &logical.Request{ Path: "loggers", Operation: logical.ReadOperation, @@ -4853,6 +4982,13 @@ func TestSystemBackend_Loggers(t *testing.T) { t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + initialLoggers := resp.Data req = &logical.Request{ @@ -4866,6 +5002,13 @@ func TestSystemBackend_Loggers(t *testing.T) { resp, err = b.HandleRequest(namespace.RootContext(nil), req) respIsError := resp != nil && resp.IsError() + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + if err != nil || (!tc.expectError && respIsError) { t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) } @@ -4885,6 +5028,13 @@ func TestSystemBackend_Loggers(t *testing.T) { t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + for _, logger := range core.allLoggers { loggerName := logger.Name() levelRaw, ok := resp.Data[loggerName] @@ -4909,6 +5059,13 @@ func TestSystemBackend_Loggers(t *testing.T) { t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + req = &logical.Request{ Path: "loggers", Operation: logical.ReadOperation, @@ -4919,6 +5076,13 @@ func TestSystemBackend_Loggers(t *testing.T) { t.Fatalf("unexpected error, err: %v, resp: %#v", err, resp) } + schema.ValidateResponse( + t, + schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation), + resp, + true, + ) + for _, logger := range core.allLoggers { loggerName := logger.Name() levelRaw, currentOk := resp.Data[loggerName] diff --git a/vault/login_mfa.go b/vault/login_mfa.go index 75aa00beb..28f126423 100644 --- a/vault/login_mfa.go +++ b/vault/login_mfa.go @@ -83,7 +83,12 @@ func (b *SystemBackend) loginMFAPaths() []*framework.Path { }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ - Callback: b.Core.loginMFABackend.handleMFALoginValidate, + Callback: b.Core.loginMFABackend.handleMFALoginValidate, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + }}, + }, Summary: "Validates the login for the given MFA methods. Upon successful validation, it returns an auth response containing the client token", ForwardPerformanceStandby: true, },