mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +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:
@@ -15,7 +15,7 @@ import (
|
||||
// contain templated fields.)
|
||||
var sudoPaths = map[string]*regexp.Regexp{
|
||||
"/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/sign-self-issued": regexp.MustCompile(`^/pki/root/sign-self-issued$`),
|
||||
"/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-prefix/{prefix}": regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
|
||||
"/sys/rotate": regexp.MustCompile(`^/sys/rotate$`),
|
||||
// TODO /sys/seal requires sudo but isn't represented as such in the OpenAPI spec
|
||||
// TODO /sys/step-down requires sudo but isn't represented as such in the OpenAPI spec
|
||||
"/sys/seal": regexp.MustCompile(`^/sys/seal$`),
|
||||
"/sys/step-down": regexp.MustCompile(`^/sys/step-down$`),
|
||||
|
||||
// enterprise-only paths
|
||||
"/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/*",
|
||||
"leases",
|
||||
"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{
|
||||
|
||||
@@ -41,42 +41,6 @@ import (
|
||||
"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) {
|
||||
b := testSystemBackend(t)
|
||||
paths := b.(*SystemBackend).configPaths()
|
||||
|
||||
@@ -783,8 +783,8 @@ func NewTokenStore(ctx context.Context, logger log.Logger, core *Core, config *l
|
||||
|
||||
PathsSpecial: &logical.Paths{
|
||||
Root: []string{
|
||||
"revoke-orphan/*",
|
||||
"accessors*",
|
||||
"revoke-orphan",
|
||||
"accessors/",
|
||||
},
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// handleRevokeOrphan handles the auth/token/revoke-orphan/id path for revocation of tokens
|
||||
// in a way that leaves child tokens orphaned. Normally, using sys/revoke/leaseID will revoke
|
||||
// handleRevokeOrphan handles the auth/token/revoke-orphan path for revocation of tokens
|
||||
// in a way that leaves child tokens orphaned. Normally, using sys/leases/revoke/{lease_id} will revoke
|
||||
// the token and all children.
|
||||
func (ts *TokenStore) handleRevokeOrphan(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
isSudo := ts.System().(extendedSystemView).SudoPrivilege(ctx, req.MountPoint+req.Path, req.ClientToken)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package vault
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"reflect"
|
||||
@@ -2325,12 +2326,12 @@ func TestTokenStore_HandleRequest_RevokeOrphan(t *testing.T) {
|
||||
testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "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{}{
|
||||
"token": "child",
|
||||
}
|
||||
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()) {
|
||||
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)
|
||||
}
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan")
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/revoke-orphan")
|
||||
req.Data = map[string]interface{}{
|
||||
"token": "child",
|
||||
}
|
||||
req.ClientToken = "child"
|
||||
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
|
||||
if err != logical.ErrInvalidRequest {
|
||||
t.Fatalf("did not get error when non-root revoking itself with orphan flag; resp is %#v", resp)
|
||||
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
||||
if !errors.Is(err, logical.ErrPermissionDenied) {
|
||||
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)
|
||||
|
||||
@@ -799,7 +799,7 @@ authenticated user.
|
||||
## Root protected API endpoints
|
||||
|
||||
~> **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`
|
||||
as a query parameter, so for each mention of LIST in the table above, GET with `?list=true`
|
||||
may also be used.
|
||||
@@ -807,9 +807,10 @@ authenticated user.
|
||||
The following paths requires a root token or `sudo` capability in the policy:
|
||||
|
||||
| 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/create](/vault/api-docs/auth/token#create-token) | POST | Create a periodic or an orphan token (`period` or `no_parent`) option |
|
||||
| [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](/vault/api-docs/secret/pki#delete-all-issuers-and-keys) | DELETE | Delete the current CA key ([pki secrets engine](/vault/docs/secrets/pki)) |
|
||||
| [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](/vault/api-docs/system/audit) | GET | List enabled audit devices |
|
||||
|
||||
Reference in New Issue
Block a user