vault/api/sudo_paths.go
Max Bowsher 25540ad222
Fix regexes for sys/raw/ and sys/leases/lookup/ to match prevailing conventions (#21760)
* Fix regexes for `sys/raw/` and `sys/leases/lookup/` to match prevailing conventions

There are several endpoints in Vault which take an arbitrary path as the
last parameter. Many of these are defined in terms of the
`framework.MatchAllRegex` helper. Some were not, and were defined using
custom regexes which gave rise to multiple OpenAPI endpoints - one with
the path parameter, and one without.

We need to fix these definitions, because they give rise to a very
unnatural result when used to generate a client API - for example, you
end up with `LeasesLookUp()` which is only capable of being used to list
the very top level of the hierarchical collection of leases, and
`LeasesLookUpWithPrefix(prefix)` which must be used for all deeper
levels.

This PR changes the regexes used for `sys/raw/` and `sys/leases/lookup/`
to be consistent with the approach used for other well-known similar
endpoints, such as `cubbyhole/`, `kv-v1/` and `kv-v2/metadata/`.

This PR does have a very small compatibility issue, which I think is
tolerable: prior to this change, `sys/raw` with no trailing slash was
considered a valid endpoint, and now it will no longer be.

One way to observe this is to try `vault path-help sys/raw` - before
this change, it would work, after, it will not. You would have to
instead use `vault path-help sys/raw/foobar` to see the help.

I also considered whether losing the ability to read/write/delete
`sys/raw` would be an issue. In each case, the precise HTTP result code
will change, but each of these were meaningless operations that make no
sense - you cannot read/write/delete a "file" at the "root directory" of
the underlying Vault storage.

In fact, during testing, I discovered that currently, `vault write
sys/raw x=y` when using Raft storage, will permanently break the Vault
instance - it causes a panic within the Raft FSM, which re-occurs
immediately on restarting the server! This PR also closes off that
footgun / DoS vector.

None of these issues apply to `sys/leases/lookup/`, as the existing
regex in that case was already not matching the path without the
trailing slash.

* changelog

* Realign hardcoded sudo paths with updated OpenAPI spec
2023-07-21 13:50:07 -04:00

86 lines
5.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"regexp"
)
// sudoPaths is a map containing the paths that require a token's policy
// to have the "sudo" capability. The keys are the paths as strings, in
// the same format as they are returned by the OpenAPI spec. The values
// are the regular expressions that can be used to test whether a given
// path matches that path or not (useful specifically for the paths that
// contain templated fields.)
var sudoPaths = map[string]*regexp.Regexp{
"/auth/token/accessors": regexp.MustCompile(`^/auth/token/accessors/?$`),
"/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$`),
"/sys/audit/{path}": regexp.MustCompile(`^/sys/audit/.+$`),
"/sys/auth/{path}": regexp.MustCompile(`^/sys/auth/.+$`),
"/sys/auth/{path}/tune": regexp.MustCompile(`^/sys/auth/.+/tune$`),
"/sys/config/auditing/request-headers": regexp.MustCompile(`^/sys/config/auditing/request-headers$`),
"/sys/config/auditing/request-headers/{header}": regexp.MustCompile(`^/sys/config/auditing/request-headers/.+$`),
"/sys/config/cors": regexp.MustCompile(`^/sys/config/cors$`),
"/sys/config/ui/headers": regexp.MustCompile(`^/sys/config/ui/headers/?$`),
"/sys/config/ui/headers/{header}": regexp.MustCompile(`^/sys/config/ui/headers/.+$`),
"/sys/internal/inspect/router/{tag}": regexp.MustCompile(`^/sys/internal/inspect/router/.+$`),
"/sys/leases": regexp.MustCompile(`^/sys/leases$`),
// This entry is a bit wrong... sys/leases/lookup does NOT require sudo. But sys/leases/lookup/ with a trailing
// slash DOES require sudo. But the part of the Vault CLI that uses this logic doesn't pass operation-appropriate
// trailing slashes, it always strips them off, so we end up giving the wrong answer for one of these.
"/sys/leases/lookup/{prefix}": regexp.MustCompile(`^/sys/leases/lookup(?:/.+)?$`),
"/sys/leases/revoke-force/{prefix}": regexp.MustCompile(`^/sys/leases/revoke-force/.+$`),
"/sys/leases/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/leases/revoke-prefix/.+$`),
"/sys/plugins/catalog/{name}": regexp.MustCompile(`^/sys/plugins/catalog/[^/]+$`),
"/sys/plugins/catalog/{type}": regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
"/sys/plugins/catalog/{type}/{name}": regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
"/sys/raw/{path}": regexp.MustCompile(`^/sys/raw(?:/.+)?$`),
"/sys/remount": regexp.MustCompile(`^/sys/remount$`),
"/sys/revoke-force/{prefix}": regexp.MustCompile(`^/sys/revoke-force/.+$`),
"/sys/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
"/sys/rotate": regexp.MustCompile(`^/sys/rotate$`),
"/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$`),
"/sys/replication/performance/primary/secondary-token": regexp.MustCompile(`^/sys/replication/performance/primary/secondary-token$`),
"/sys/replication/primary/secondary-token": regexp.MustCompile(`^/sys/replication/primary/secondary-token$`),
"/sys/replication/reindex": regexp.MustCompile(`^/sys/replication/reindex$`),
"/sys/storage/raft/snapshot-auto/config": regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/?$`),
"/sys/storage/raft/snapshot-auto/config/{name}": regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/[^/]+$`),
}
func SudoPaths() map[string]*regexp.Regexp {
return sudoPaths
}
// Determine whether the given path requires the sudo capability.
// Note that this uses hardcoded static path information, so will return incorrect results for paths in namespaces,
// or for secret engines mounted at non-default paths.
// Expects to receive a path with an initial slash, but no trailing slashes, as the Vault CLI (the only known and
// expected user of this function) sanitizes its paths that way.
func IsSudoPath(path string) bool {
// Return early if the path is any of the non-templated sudo paths.
if _, ok := sudoPaths[path]; ok {
return true
}
// Some sudo paths have templated fields in them.
// (e.g. /sys/revoke-prefix/{prefix})
// The values in the sudoPaths map are actually regular expressions,
// so we can check if our path matches against them.
for _, sudoPathRegexp := range sudoPaths {
match := sudoPathRegexp.MatchString(path)
if match {
return true
}
}
return false
}