mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
Fix sudo paths missing from OpenAPI and docs (#21772)
* Fix sudo paths missing from OpenAPI and docs Various sudo (a.k.a. root-protected) paths are implemented in non-standard ways, and as a result: * are not declared as x-vault-sudo in the OpenAPI spec * and as a result of that, are not included in the hardcoded patterns powering the Vault CLI `-output-policy` flag * and in some cases are missing from the table of all sudo paths in the docs too Fix these problems by: * Adding `seal` and `step-down` to the list of root paths for the system backend. They don't need to be there for enforcement, as those two special endpoints bypass the standard request handling code, but they do need to be there for the OpenAPI generator to be able to know they require sudo. The way in which those two endpoints do things differently can be observed in the code search results for `RootPrivsRequired`: https://github.com/search?q=repo%3Ahashicorp%2Fvault%20RootPrivsRequired&type=code * Fix the implementation of `auth/token/revoke-orphan` to implement endpoint sudo requirements in the standard way. Currently, it has an **incorrect** path declared in the special paths metadata, and then compensates with custom code throwing an error within the request handler function itself. * changelog * As discussed in PR, delete test which is just testing equality of a constant * Restore sudo check as requested, and add comment * Update vault/token_store.go Co-authored-by: Anton Averchenkov <84287187+averche@users.noreply.github.com> --------- Co-authored-by: Anton Averchenkov <84287187+averche@users.noreply.github.com>
This commit is contained in:
@@ -14,8 +14,8 @@ import (
|
|||||||
// path matches that path or not (useful specifically for the paths that
|
// path matches that path or not (useful specifically for the paths that
|
||||||
// contain templated fields.)
|
// contain templated fields.)
|
||||||
var sudoPaths = map[string]*regexp.Regexp{
|
var sudoPaths = map[string]*regexp.Regexp{
|
||||||
"/auth/token/accessors": regexp.MustCompile(`^/auth/token/accessors/?$`),
|
"/auth/token/accessors": regexp.MustCompile(`^/auth/token/accessors/?$`),
|
||||||
// TODO /auth/token/revoke-orphan requires sudo but isn't represented as such in the OpenAPI spec
|
"/auth/token/revoke-orphan": regexp.MustCompile(`^/auth/token/revoke-orphan$`),
|
||||||
"/pki/root": regexp.MustCompile(`^/pki/root$`),
|
"/pki/root": regexp.MustCompile(`^/pki/root$`),
|
||||||
"/pki/root/sign-self-issued": regexp.MustCompile(`^/pki/root/sign-self-issued$`),
|
"/pki/root/sign-self-issued": regexp.MustCompile(`^/pki/root/sign-self-issued$`),
|
||||||
"/sys/audit": regexp.MustCompile(`^/sys/audit$`),
|
"/sys/audit": regexp.MustCompile(`^/sys/audit$`),
|
||||||
@@ -45,8 +45,8 @@ var sudoPaths = map[string]*regexp.Regexp{
|
|||||||
"/sys/revoke-force/{prefix}": regexp.MustCompile(`^/sys/revoke-force/.+$`),
|
"/sys/revoke-force/{prefix}": regexp.MustCompile(`^/sys/revoke-force/.+$`),
|
||||||
"/sys/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
|
"/sys/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
|
||||||
"/sys/rotate": regexp.MustCompile(`^/sys/rotate$`),
|
"/sys/rotate": regexp.MustCompile(`^/sys/rotate$`),
|
||||||
// TODO /sys/seal requires sudo but isn't represented as such in the OpenAPI spec
|
"/sys/seal": regexp.MustCompile(`^/sys/seal$`),
|
||||||
// TODO /sys/step-down requires sudo but isn't represented as such in the OpenAPI spec
|
"/sys/step-down": regexp.MustCompile(`^/sys/step-down$`),
|
||||||
|
|
||||||
// enterprise-only paths
|
// enterprise-only paths
|
||||||
"/sys/replication/dr/primary/secondary-token": regexp.MustCompile(`^/sys/replication/dr/primary/secondary-token$`),
|
"/sys/replication/dr/primary/secondary-token": regexp.MustCompile(`^/sys/replication/dr/primary/secondary-token$`),
|
||||||
|
|||||||
3
changelog/21772.txt
Normal file
3
changelog/21772.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
core: Fix OpenAPI representation and `-output-policy` recognition of some non-standard sudo paths
|
||||||
|
```
|
||||||
@@ -120,6 +120,11 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
|
|||||||
"storage/raft/snapshot-auto/config/*",
|
"storage/raft/snapshot-auto/config/*",
|
||||||
"leases",
|
"leases",
|
||||||
"internal/inspect/*",
|
"internal/inspect/*",
|
||||||
|
// sys/seal and sys/step-down actually have their sudo requirement enforced through hardcoding
|
||||||
|
// PolicyCheckOpts.RootPrivsRequired in dedicated calls to Core.performPolicyChecks, but we still need
|
||||||
|
// to declare them here so that the generated OpenAPI spec gets their sudo status correct.
|
||||||
|
"seal",
|
||||||
|
"step-down",
|
||||||
},
|
},
|
||||||
|
|
||||||
Unauthenticated: []string{
|
Unauthenticated: []string{
|
||||||
|
|||||||
@@ -41,42 +41,6 @@ import (
|
|||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSystemBackend_RootPaths(t *testing.T) {
|
|
||||||
expected := []string{
|
|
||||||
"auth/*",
|
|
||||||
"remount",
|
|
||||||
"audit",
|
|
||||||
"audit/*",
|
|
||||||
"raw",
|
|
||||||
"raw/*",
|
|
||||||
"replication/primary/secondary-token",
|
|
||||||
"replication/performance/primary/secondary-token",
|
|
||||||
"replication/dr/primary/secondary-token",
|
|
||||||
"replication/reindex",
|
|
||||||
"replication/dr/reindex",
|
|
||||||
"replication/performance/reindex",
|
|
||||||
"rotate",
|
|
||||||
"config/cors",
|
|
||||||
"config/auditing/*",
|
|
||||||
"config/ui/headers/*",
|
|
||||||
"plugins/catalog/*",
|
|
||||||
"revoke-prefix/*",
|
|
||||||
"revoke-force/*",
|
|
||||||
"leases/revoke-prefix/*",
|
|
||||||
"leases/revoke-force/*",
|
|
||||||
"leases/lookup/*",
|
|
||||||
"storage/raft/snapshot-auto/config/*",
|
|
||||||
"leases",
|
|
||||||
"internal/inspect/*",
|
|
||||||
}
|
|
||||||
|
|
||||||
b := testSystemBackend(t)
|
|
||||||
actual := b.SpecialPaths().Root
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
|
||||||
t.Fatalf("bad: mismatch\nexpected:\n%#v\ngot:\n%#v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSystemConfigCORS(t *testing.T) {
|
func TestSystemConfigCORS(t *testing.T) {
|
||||||
b := testSystemBackend(t)
|
b := testSystemBackend(t)
|
||||||
paths := b.(*SystemBackend).configPaths()
|
paths := b.(*SystemBackend).configPaths()
|
||||||
|
|||||||
@@ -783,8 +783,8 @@ func NewTokenStore(ctx context.Context, logger log.Logger, core *Core, config *l
|
|||||||
|
|
||||||
PathsSpecial: &logical.Paths{
|
PathsSpecial: &logical.Paths{
|
||||||
Root: []string{
|
Root: []string{
|
||||||
"revoke-orphan/*",
|
"revoke-orphan",
|
||||||
"accessors*",
|
"accessors/",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Most token store items are local since tokens are local, but a
|
// Most token store items are local since tokens are local, but a
|
||||||
@@ -3285,8 +3285,8 @@ func (ts *TokenStore) revokeCommon(ctx context.Context, req *logical.Request, da
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleRevokeOrphan handles the auth/token/revoke-orphan/id path for revocation of tokens
|
// handleRevokeOrphan handles the auth/token/revoke-orphan path for revocation of tokens
|
||||||
// in a way that leaves child tokens orphaned. Normally, using sys/revoke/leaseID will revoke
|
// in a way that leaves child tokens orphaned. Normally, using sys/leases/revoke/{lease_id} will revoke
|
||||||
// the token and all children.
|
// the token and all children.
|
||||||
func (ts *TokenStore) handleRevokeOrphan(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
func (ts *TokenStore) handleRevokeOrphan(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
// Parse the id
|
// Parse the id
|
||||||
@@ -3295,6 +3295,8 @@ func (ts *TokenStore) handleRevokeOrphan(ctx context.Context, req *logical.Reque
|
|||||||
return logical.ErrorResponse("missing token ID"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("missing token ID"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO #21772 makes the sudo check below redundant, by correcting the TokenStore's PathsSpecial.Root to match this endpoint
|
||||||
|
|
||||||
// Check if the client token has sudo/root privileges for the requested path
|
// Check if the client token has sudo/root privileges for the requested path
|
||||||
isSudo := ts.System().(extendedSystemView).SudoPrivilege(ctx, req.MountPoint+req.Path, req.ClientToken)
|
isSudo := ts.System().(extendedSystemView).SudoPrivilege(ctx, req.MountPoint+req.Path, req.ClientToken)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package vault
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -2325,12 +2326,12 @@ func TestTokenStore_HandleRequest_RevokeOrphan(t *testing.T) {
|
|||||||
testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "foo"})
|
testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "foo"})
|
||||||
testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "50s", []string{"foo"})
|
testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "50s", []string{"foo"})
|
||||||
|
|
||||||
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan")
|
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/revoke-orphan")
|
||||||
req.Data = map[string]interface{}{
|
req.Data = map[string]interface{}{
|
||||||
"token": "child",
|
"token": "child",
|
||||||
}
|
}
|
||||||
req.ClientToken = root
|
req.ClientToken = root
|
||||||
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
||||||
if err != nil || (resp != nil && resp.IsError()) {
|
if err != nil || (resp != nil && resp.IsError()) {
|
||||||
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
||||||
}
|
}
|
||||||
@@ -2384,14 +2385,14 @@ func TestTokenStore_HandleRequest_RevokeOrphan_NonRoot(t *testing.T) {
|
|||||||
t.Fatalf("bad: %v", out)
|
t.Fatalf("bad: %v", out)
|
||||||
}
|
}
|
||||||
|
|
||||||
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan")
|
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/revoke-orphan")
|
||||||
req.Data = map[string]interface{}{
|
req.Data = map[string]interface{}{
|
||||||
"token": "child",
|
"token": "child",
|
||||||
}
|
}
|
||||||
req.ClientToken = "child"
|
req.ClientToken = "child"
|
||||||
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
||||||
if err != logical.ErrInvalidRequest {
|
if !errors.Is(err, logical.ErrPermissionDenied) {
|
||||||
t.Fatalf("did not get error when non-root revoking itself with orphan flag; resp is %#v", resp)
|
t.Fatalf("did not get expected error when non-root revoking itself with orphan flag; resp is %#v; err is %#v", resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|||||||
@@ -799,44 +799,45 @@ authenticated user.
|
|||||||
## Root protected API endpoints
|
## Root protected API endpoints
|
||||||
|
|
||||||
~> **Note:** Vault treats the HTTP POST and PUT verbs as equivalent, so for each mention
|
~> **Note:** Vault treats the HTTP POST and PUT verbs as equivalent, so for each mention
|
||||||
of POST in the table above, PUT may also be used. Vault uses the non-standard LIST HTTP
|
of POST in the table below, PUT may also be used. Vault uses the non-standard LIST HTTP
|
||||||
verb, but also allows list requests to be made using the GET verb along with `?list=true`
|
verb, but also allows list requests to be made using the GET verb along with `?list=true`
|
||||||
as a query parameter, so for each mention of LIST in the table above, GET with `?list=true`
|
as a query parameter, so for each mention of LIST in the table above, GET with `?list=true`
|
||||||
may also be used.
|
may also be used.
|
||||||
|
|
||||||
The following paths requires a root token or `sudo` capability in the policy:
|
The following paths requires a root token or `sudo` capability in the policy:
|
||||||
|
|
||||||
| Path | HTTP verb | Description |
|
| Path | HTTP verb | Description |
|
||||||
| -------------------------------------------------------------------------------------------------------------------------------------------------------| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|--------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------|---------------------------------------------------------------------------------------------------------------------|
|
||||||
| [auth/token/accessors](/vault/api-docs/auth/token#list-accessors) | LIST | List token accessors for all current Vault service tokens |
|
| [auth/token/accessors](/vault/api-docs/auth/token#list-accessors) | LIST | List token accessors for all current Vault service tokens |
|
||||||
| [auth/token/create](/vault/api-docs/auth/token#create-token) | POST | Create a periodic or an orphan token (`period` or `no_parent`) option |
|
| [auth/token/create](/vault/api-docs/auth/token#create-token) | POST | Create a periodic or an orphan token (`period` or `no_parent`) option |
|
||||||
| [pki/root](/vault/api-docs/secret/pki#delete-all-issuers-and-keys) | DELETE | Delete the current CA key ([pki secrets engine](/vault/docs/secrets/pki)) |
|
| [auth/token/revoke-orphan](/vault/api-docs/auth/token#revoke-token-and-orphan-children) | POST | Revoke a token but not its child tokens, which will be orphaned |
|
||||||
| [pki/root/sign-self-issued](/vault/api-docs/secret/pki#sign-self-issued) | POST | Use the configured CA certificate to sign a self-issued certificate ([pki secrets engine](/vault/docs/secrets/pki)) |
|
| [pki/root](/vault/api-docs/secret/pki#delete-all-issuers-and-keys) | DELETE | Delete the current CA key ([pki secrets engine](/vault/docs/secrets/pki)) |
|
||||||
| [sys/audit](/vault/api-docs/system/audit) | GET | List enabled audit devices |
|
| [pki/root/sign-self-issued](/vault/api-docs/secret/pki#sign-self-issued) | POST | Use the configured CA certificate to sign a self-issued certificate ([pki secrets engine](/vault/docs/secrets/pki)) |
|
||||||
| [sys/audit/:path](/vault/api-docs/system/audit) | POST, DELETE | Enable or remove an audit device |
|
| [sys/audit](/vault/api-docs/system/audit) | GET | List enabled audit devices |
|
||||||
| [sys/auth/:path](/vault/api-docs/system/auth) | GET, POST, DELETE | Manage the auth methods (enable, read, and delete) |
|
| [sys/audit/:path](/vault/api-docs/system/audit) | POST, DELETE | Enable or remove an audit device |
|
||||||
| [sys/auth/:path/tune](/vault/api-docs/system/auth#tune-auth-method) | GET, POST | Manage the auth methods (enable, read, delete, and tune) |
|
| [sys/auth/:path](/vault/api-docs/system/auth) | GET, POST, DELETE | Manage the auth methods (enable, read, and delete) |
|
||||||
| [sys/config/auditing/request-headers](/vault/api-docs/system/config-auditing) | GET | List the request headers that are configured to be audited |
|
| [sys/auth/:path/tune](/vault/api-docs/system/auth#tune-auth-method) | GET, POST | Manage the auth methods (enable, read, delete, and tune) |
|
||||||
| [sys/config/auditing/request-headers/:name](/vault/api-docs/system/config-auditing) | GET, POST, DELETE | Manage the auditing headers (create, update, read and delete) |
|
| [sys/config/auditing/request-headers](/vault/api-docs/system/config-auditing) | GET | List the request headers that are configured to be audited |
|
||||||
| [sys/config/cors](/vault/api-docs/system/config-cors) | GET, POST, DELETE | Configure CORS setting |
|
| [sys/config/auditing/request-headers/:name](/vault/api-docs/system/config-auditing) | GET, POST, DELETE | Manage the auditing headers (create, update, read and delete) |
|
||||||
| [sys/config/ui/headers](/vault/api-docs/system/config-ui) | GET, LIST | Configure the UI settings |
|
| [sys/config/cors](/vault/api-docs/system/config-cors) | GET, POST, DELETE | Configure CORS setting |
|
||||||
| [sys/config/ui/headers/:name](/vault/api-docs/system/config-ui#name) | POST, DELETE | Configure custom HTTP headers to be served with the UI |
|
| [sys/config/ui/headers](/vault/api-docs/system/config-ui) | GET, LIST | Configure the UI settings |
|
||||||
| [sys/internal/inspect/router/:tag](/vault/api-docs/system/inspect/router) | GET | Inspect the internal components of Vault's router. `tag` must be one of root, uuid, accessor, or storage |
|
| [sys/config/ui/headers/:name](/vault/api-docs/system/config-ui#name) | POST, DELETE | Configure custom HTTP headers to be served with the UI |
|
||||||
| [sys/leases/lookup/:prefix](/vault/api-docs/system/leases#list-leases) | LIST | List lease IDs |
|
| [sys/internal/inspect/router/:tag](/vault/api-docs/system/inspect/router) | GET | Inspect the internal components of Vault's router. `tag` must be one of root, uuid, accessor, or storage |
|
||||||
| [sys/leases/revoke-force/:prefix](/vault/api-docs/system/leases#revoke-force) | POST | Revoke all secrets or tokens ignoring backend errors |
|
| [sys/leases/lookup/:prefix](/vault/api-docs/system/leases#list-leases) | LIST | List lease IDs |
|
||||||
| [sys/leases/revoke-prefix/:prefix](/vault/api-docs/system/leases#revoke-prefix) | POST | Revoke all secrets generated under a given prefix |
|
| [sys/leases/revoke-force/:prefix](/vault/api-docs/system/leases#revoke-force) | POST | Revoke all secrets or tokens ignoring backend errors |
|
||||||
| [sys/plugins/catalog/:type/:name](/vault/api-docs/system/plugins-catalog#register-plugin) | GET, POST, DELETE | Register a new plugin, or read/remove an existing plugin |
|
| [sys/leases/revoke-prefix/:prefix](/vault/api-docs/system/leases#revoke-prefix) | POST | Revoke all secrets generated under a given prefix |
|
||||||
| [sys/raw:path](/vault/api-docs/system/raw) | GET, POST, DELETE | Used to access the raw underlying store in Vault |
|
| [sys/plugins/catalog/:type/:name](/vault/api-docs/system/plugins-catalog#register-plugin) | GET, POST, DELETE | Register a new plugin, or read/remove an existing plugin |
|
||||||
| [sys/raw:prefix](/vault/api-docs/system/raw#list-raw) | GET, LIST | Returns a list keys for a given path prefix |
|
| [sys/raw:path](/vault/api-docs/system/raw) | GET, POST, DELETE | Used to access the raw underlying store in Vault |
|
||||||
| [sys/remount](/vault/api-docs/system/remount) | POST | Moves an already-mounted backend to a new mount point |
|
| [sys/raw:prefix](/vault/api-docs/system/raw#list-raw) | GET, LIST | Returns a list keys for a given path prefix |
|
||||||
| [sys/replication/reindex](/vault/api-docs/system/replication#reindex-replication) | POST | Reindex the local data storage |
|
| [sys/remount](/vault/api-docs/system/remount) | POST | Moves an already-mounted backend to a new mount point |
|
||||||
| [sys/replication/performance/primary/secondary-token](/vault/api-docs/system/replication/replication-performance#generate-performance-secondary-token) | POST | Generate a performance secondary activation token |
|
| [sys/replication/reindex](/vault/api-docs/system/replication#reindex-replication) | POST | Reindex the local data storage |
|
||||||
| [sys/replication/dr/primary/secondary-token](/vault/api-docs/system/replication/replication-dr#generate-dr-secondary-token) | POST | Generate a DR secondary activation token |
|
| [sys/replication/performance/primary/secondary-token](/vault/api-docs/system/replication/replication-performance#generate-performance-secondary-token) | POST | Generate a performance secondary activation token |
|
||||||
| [sys/rotate](/vault/api-docs/system/rotate) | POST | Trigger a rotation of the backend encryption key |
|
| [sys/replication/dr/primary/secondary-token](/vault/api-docs/system/replication/replication-dr#generate-dr-secondary-token) | POST | Generate a DR secondary activation token |
|
||||||
| [sys/seal](/vault/api-docs/system/seal) | POST | Seals the Vault |
|
| [sys/rotate](/vault/api-docs/system/rotate) | POST | Trigger a rotation of the backend encryption key |
|
||||||
| [sys/step-down](/vault/api-docs/system/step-down) | POST | Forces a node to give up active status |
|
| [sys/seal](/vault/api-docs/system/seal) | POST | Seals the Vault |
|
||||||
| [sys/storage/raft/snapshot-auto/config](/vault/api-docs/system/storage/raftautosnapshots#list-automated-snapshots-configs) | LIST | Lists named configurations |
|
| [sys/step-down](/vault/api-docs/system/step-down) | POST | Forces a node to give up active status |
|
||||||
| [sys/storage/raft/snapshot-auto/config/:name](/vault/api-docs/system/storage/raftautosnapshots) | GET, POST, DELETE | Creates or updates a named configuration |
|
| [sys/storage/raft/snapshot-auto/config](/vault/api-docs/system/storage/raftautosnapshots#list-automated-snapshots-configs) | LIST | Lists named configurations |
|
||||||
|
| [sys/storage/raft/snapshot-auto/config/:name](/vault/api-docs/system/storage/raftautosnapshots) | GET, POST, DELETE | Creates or updates a named configuration |
|
||||||
|
|
||||||
### Tokens
|
### Tokens
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user