open-vault/vault/router.go

1065 lines
30 KiB
Go
Raw Permalink Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2015-03-06 01:23:56 +00:00
package vault
import (
"context"
2015-03-06 01:23:56 +00:00
"fmt"
2021-10-13 16:51:20 +00:00
"regexp"
2015-03-06 01:23:56 +00:00
"strings"
"sync"
"sync/atomic"
2015-04-08 23:43:17 +00:00
"time"
2015-03-06 01:23:56 +00:00
"github.com/armon/go-metrics"
"github.com/armon/go-radix"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-secure-stdlib/strutil"
2018-09-18 03:03:00 +00:00
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
2015-03-06 01:23:56 +00:00
)
var deniedPassthroughRequestHeaders = []string{
consts.AuthHeaderName,
}
2021-10-13 16:51:20 +00:00
// matches when '+' is next to a non-slash char
var wcAdjacentNonSlashRegEx = regexp.MustCompile(`\+[^/]|[^/]\+`).MatchString
2015-03-06 01:23:56 +00:00
// Router is used to do prefix based routing of a request to a logical backend
type Router struct {
l sync.RWMutex
root *radix.Tree
mountUUIDCache *radix.Tree
mountAccessorCache *radix.Tree
tokenStoreSaltFunc func(context.Context) (*salt.Salt, error)
// storagePrefix maps the prefix used for storage (ala the BarrierView)
// to the backend. This is used to map a key back into the backend that owns it.
// For example, logical/uuid1/foobar -> secrets/ (kv backend) + foobar
storagePrefix *radix.Tree
logger hclog.Logger
2015-03-06 01:23:56 +00:00
}
// NewRouter returns a new router
func NewRouter() *Router {
r := &Router{
root: radix.New(),
storagePrefix: radix.New(),
mountUUIDCache: radix.New(),
mountAccessorCache: radix.New(),
// this will get replaced in production with a real logger but it's useful to have a default in place for tests
logger: hclog.NewNullLogger(),
2015-03-06 01:23:56 +00:00
}
return r
}
// routeEntry is used to represent a mount point in the router
type routeEntry struct {
2017-10-23 18:59:37 +00:00
tainted bool
backend logical.Backend
mountEntry *MountEntry
storageView logical.Storage
storagePrefix string
rootPaths atomic.Value
loginPaths atomic.Value
l sync.RWMutex
2015-03-06 01:23:56 +00:00
}
2021-10-13 16:51:20 +00:00
type wildcardPath struct {
// this sits in the hot path of requests so we are micro-optimizing by
// storing pre-split slices of path segments
segments []string
isPrefix bool
}
// loginPathsEntry is used to hold the routeEntry loginPaths
type loginPathsEntry struct {
paths *radix.Tree
wildcardPaths []wildcardPath
}
type ValidateMountResponse struct {
MountType string `json:"mount_type" structs:"mount_type" mapstructure:"mount_type"`
MountAccessor string `json:"mount_accessor" structs:"mount_accessor" mapstructure:"mount_accessor"`
MountPath string `json:"mount_path" structs:"mount_path" mapstructure:"mount_path"`
MountLocal bool `json:"mount_local" structs:"mount_local" mapstructure:"mount_local"`
}
func (r *Router) reset() {
r.l.Lock()
defer r.l.Unlock()
r.root = radix.New()
r.storagePrefix = radix.New()
r.mountUUIDCache = radix.New()
r.mountAccessorCache = radix.New()
}
func (r *Router) GetRecords(tag string) ([]map[string]interface{}, error) {
r.l.RLock()
defer r.l.RUnlock()
var data []map[string]interface{}
var tree *radix.Tree
switch tag {
case "root":
tree = r.root
case "uuid":
tree = r.mountUUIDCache
case "accessor":
tree = r.mountAccessorCache
case "storage":
tree = r.storagePrefix
default:
return nil, logical.ErrUnsupportedPath
}
for _, v := range tree.ToMap() {
info := v.(Deserializable).Deserialize()
data = append(data, info)
}
return data, nil
}
func (entry *routeEntry) Deserialize() map[string]interface{} {
entry.l.RLock()
defer entry.l.RUnlock()
ret := map[string]interface{}{
"tainted": entry.tainted,
"storage_prefix": entry.storagePrefix,
}
for k, v := range entry.mountEntry.Deserialize() {
ret[k] = v
}
return ret
}
// ValidateMountByAccessor returns the mount type and ID for a given mount
// accessor
func (r *Router) ValidateMountByAccessor(accessor string) *ValidateMountResponse {
if accessor == "" {
return nil
}
mountEntry := r.MatchingMountByAccessor(accessor)
if mountEntry == nil {
return nil
}
mountPath := mountEntry.Path
if mountEntry.Table == credentialTableType {
mountPath = credentialRoutePrefix + mountPath
}
return &ValidateMountResponse{
MountAccessor: mountEntry.Accessor,
MountType: mountEntry.Type,
MountPath: mountPath,
MountLocal: mountEntry.Local,
}
}
2016-05-15 16:58:36 +00:00
// SaltID is used to apply a salt and hash to an ID to make sure its not reversible
func (re *routeEntry) SaltID(id string) string {
return salt.SaltID(re.mountEntry.UUID, id, salt.SHA1Hash)
}
// Mount is used to expose a logical backend at a given prefix, using a unique salt,
// and the barrier view for that path.
func (r *Router) Mount(backend logical.Backend, prefix string, mountEntry *MountEntry, storageView *BarrierView) error {
2015-03-06 01:23:56 +00:00
r.l.Lock()
defer r.l.Unlock()
2018-09-18 03:03:00 +00:00
// prepend namespace
prefix = mountEntry.Namespace().Path + prefix
2015-03-06 01:23:56 +00:00
// Check if this is a nested mount
if existing, _, ok := r.root.LongestPrefix(prefix); ok && existing != "" {
return fmt.Errorf("cannot mount under existing mount %q", existing)
2015-03-06 01:23:56 +00:00
}
2015-03-31 00:46:18 +00:00
// Build the paths
Lazy-load plugin mounts (#3255) * Lazy load plugins to avoid setup-unwrap cycle * Remove commented blocks * Refactor NewTestCluster, use single core cluster on basic plugin tests * Set c.pluginDirectory in TestAddTestPlugin for setupPluginCatalog to work properly * Add special path to mock plugin * Move ensureCoresSealed to vault/testing.go * Use same method for EnsureCoresSealed and Cleanup * Bump ensureCoresSealed timeout to 60s * Correctly handle nil opts on NewTestCluster * Add metadata flag to APIClientMeta, use meta-enabled plugin when mounting to bootstrap * Check metadata flag directly on the plugin process * Plumb isMetadataMode down to PluginRunner * Add NOOP shims when running in metadata mode * Remove unused flag from the APIMetadata object * Remove setupSecretPlugins and setupCredentialPlugins functions * Move when we setup rollback manager to after the plugins are initialized * Fix tests * Fix merge issue * start rollback manager after the credential setup * Add guards against running certain client and server functions while in metadata mode * Call initialize once a plugin is loaded on the fly * Add more tests, update basic secret/auth plugin tests to trigger lazy loading * Skip mount if plugin removed from catalog * Fixup * Remove commented line on LookupPlugin * Fail on mount operation if plugin is re-added to catalog and mount is on existing path * Check type and special paths on startBackend * Fix merge conflicts * Refactor PluginRunner run methods to use runCommon, fix TestSystemBackend_Plugin_auth
2017-09-01 05:02:03 +00:00
paths := new(logical.Paths)
if backend != nil {
specialPaths := backend.SpecialPaths()
if specialPaths != nil {
paths = specialPaths
}
2015-03-06 01:23:56 +00:00
}
// Create a mount entry
re := &routeEntry{
tainted: mountEntry.Tainted,
2017-10-23 18:59:37 +00:00
backend: backend,
mountEntry: mountEntry,
storagePrefix: storageView.Prefix(),
2018-03-20 14:42:57 +00:00
storageView: storageView,
2015-03-06 01:23:56 +00:00
}
re.rootPaths.Store(pathsToRadix(paths.Root))
2021-10-13 16:51:20 +00:00
loginPathsEntry, err := parseUnauthenticatedPaths(paths.Unauthenticated)
if err != nil {
return err
}
re.loginPaths.Store(loginPathsEntry)
switch {
case prefix == "":
return fmt.Errorf("missing prefix to be used for router entry; mount_path: %q, mount_type: %q", re.mountEntry.Path, re.mountEntry.Type)
2017-10-23 18:59:37 +00:00
case re.storagePrefix == "":
return fmt.Errorf("missing storage view prefix; mount_path: %q, mount_type: %q", re.mountEntry.Path, re.mountEntry.Type)
case re.mountEntry.UUID == "":
return fmt.Errorf("missing mount identifier; mount_path: %q, mount_type: %q", re.mountEntry.Path, re.mountEntry.Type)
case re.mountEntry.Accessor == "":
return fmt.Errorf("missing mount accessor; mount_path: %q, mount_type: %q", re.mountEntry.Path, re.mountEntry.Type)
}
r.root.Insert(prefix, re)
2017-10-23 18:59:37 +00:00
r.storagePrefix.Insert(re.storagePrefix, re)
2017-06-17 03:54:19 +00:00
r.mountUUIDCache.Insert(re.mountEntry.UUID, re.mountEntry)
r.mountAccessorCache.Insert(re.mountEntry.Accessor, re.mountEntry)
2015-03-06 01:23:56 +00:00
return nil
}
// Unmount is used to remove a logical backend from a given prefix
func (r *Router) Unmount(ctx context.Context, prefix string) error {
2018-09-18 03:03:00 +00:00
ns, err := namespace.FromContext(ctx)
if err != nil {
return err
}
prefix = ns.Path + prefix
2015-03-06 01:23:56 +00:00
r.l.Lock()
defer r.l.Unlock()
// Fast-path out if the backend doesn't exist
raw, ok := r.root.Get(prefix)
if !ok {
return nil
}
// Call backend's Cleanup routine
re := raw.(*routeEntry)
2017-10-23 18:59:37 +00:00
if re.backend != nil {
re.backend.Cleanup(ctx)
2017-10-23 18:59:37 +00:00
}
// Purge from the radix trees
2015-03-06 01:23:56 +00:00
r.root.Delete(prefix)
2017-10-23 18:59:37 +00:00
r.storagePrefix.Delete(re.storagePrefix)
2017-06-17 03:54:19 +00:00
r.mountUUIDCache.Delete(re.mountEntry.UUID)
r.mountAccessorCache.Delete(re.mountEntry.Accessor)
2015-03-06 01:23:56 +00:00
return nil
}
// Remount is used to change the mount location of a logical backend
2018-09-18 03:03:00 +00:00
func (r *Router) Remount(ctx context.Context, src, dst string) error {
ns, err := namespace.FromContext(ctx)
if err != nil {
return err
}
src = ns.Path + src
dst = ns.Path + dst
2015-03-06 01:23:56 +00:00
r.l.Lock()
defer r.l.Unlock()
// Check for existing mount
raw, ok := r.root.Get(src)
if !ok {
return fmt.Errorf("no mount at %q", src)
2015-03-06 01:23:56 +00:00
}
// Update the mount point
r.root.Delete(src)
r.root.Insert(dst, raw)
return nil
}
2015-04-02 18:12:13 +00:00
// Taint is used to mark a path as tainted. This means only RollbackOperation
2016-03-01 01:29:04 +00:00
// RevokeOperation requests are allowed to proceed
2018-09-18 03:03:00 +00:00
func (r *Router) Taint(ctx context.Context, path string) error {
ns, err := namespace.FromContext(ctx)
if err != nil {
return err
}
path = ns.Path + path
2015-04-02 18:12:13 +00:00
r.l.Lock()
defer r.l.Unlock()
_, raw, ok := r.root.LongestPrefix(path)
if ok {
raw.(*routeEntry).tainted = true
2015-04-02 18:12:13 +00:00
}
return nil
}
2015-04-02 19:01:53 +00:00
// Untaint is used to unmark a path as tainted.
2018-09-18 03:03:00 +00:00
func (r *Router) Untaint(ctx context.Context, path string) error {
ns, err := namespace.FromContext(ctx)
if err != nil {
return err
}
path = ns.Path + path
2015-04-02 19:01:53 +00:00
r.l.Lock()
defer r.l.Unlock()
_, raw, ok := r.root.LongestPrefix(path)
if ok {
raw.(*routeEntry).tainted = false
2015-04-02 19:01:53 +00:00
}
return nil
}
2017-06-17 03:54:19 +00:00
func (r *Router) MatchingMountByUUID(mountID string) *MountEntry {
if mountID == "" {
return nil
}
r.l.RLock()
_, raw, ok := r.mountUUIDCache.LongestPrefix(mountID)
if !ok {
r.l.RUnlock()
return nil
}
r.l.RUnlock()
return raw.(*MountEntry)
}
// MatchingMountByAccessor returns the MountEntry by accessor lookup
func (r *Router) MatchingMountByAccessor(mountAccessor string) *MountEntry {
if mountAccessor == "" {
return nil
}
r.l.RLock()
_, raw, ok := r.mountAccessorCache.LongestPrefix(mountAccessor)
if !ok {
r.l.RUnlock()
return nil
}
r.l.RUnlock()
return raw.(*MountEntry)
}
2015-04-02 18:03:59 +00:00
// MatchingMount returns the mount prefix that would be used for a path
2018-09-18 03:03:00 +00:00
func (r *Router) MatchingMount(ctx context.Context, path string) string {
r.l.RLock()
2018-09-18 03:03:00 +00:00
mount := r.matchingMountInternal(ctx, path)
r.l.RUnlock()
2017-11-06 20:29:09 +00:00
return mount
}
2018-09-18 03:03:00 +00:00
func (r *Router) matchingMountInternal(ctx context.Context, path string) string {
ns, err := namespace.FromContext(ctx)
if err != nil {
return ""
}
path = ns.Path + path
mount, _, ok := r.root.LongestPrefix(path)
if !ok {
return ""
}
return mount
}
2017-11-06 20:29:09 +00:00
// matchingPrefixInternal returns a mount prefix that a path may be a part of
2018-09-18 03:03:00 +00:00
func (r *Router) matchingPrefixInternal(ctx context.Context, path string) string {
ns, err := namespace.FromContext(ctx)
if err != nil {
return ""
}
path = ns.Path + path
var existing string
fn := func(existingPath string, v interface{}) bool {
if strings.HasPrefix(existingPath, path) {
existing = existingPath
2017-11-06 20:29:09 +00:00
return true
}
return false
}
r.root.WalkPrefix(path, fn)
return existing
}
// MountConflict determines if there are potential path conflicts
2018-09-18 03:03:00 +00:00
func (r *Router) MountConflict(ctx context.Context, path string) string {
2017-11-06 20:29:09 +00:00
r.l.RLock()
defer r.l.RUnlock()
2018-09-18 03:03:00 +00:00
if exactMatch := r.matchingMountInternal(ctx, path); exactMatch != "" {
return exactMatch
2017-11-06 20:29:09 +00:00
}
2018-09-18 03:03:00 +00:00
if prefixMatch := r.matchingPrefixInternal(ctx, path); prefixMatch != "" {
return prefixMatch
2017-11-06 20:29:09 +00:00
}
return ""
}
2017-10-23 18:59:37 +00:00
// MatchingStorageByAPIPath/StoragePath returns the storage used for
// API/Storage paths respectively
2018-09-18 03:03:00 +00:00
func (r *Router) MatchingStorageByAPIPath(ctx context.Context, path string) logical.Storage {
return r.matchingStorage(ctx, path, true)
2017-10-23 18:59:37 +00:00
}
2018-09-18 03:03:00 +00:00
func (r *Router) MatchingStorageByStoragePath(ctx context.Context, path string) logical.Storage {
return r.matchingStorage(ctx, path, false)
2017-10-23 18:59:37 +00:00
}
2018-09-18 03:03:00 +00:00
func (r *Router) matchingStorage(ctx context.Context, path string, apiPath bool) logical.Storage {
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil
}
path = ns.Path + path
2017-10-23 18:59:37 +00:00
var raw interface{}
var ok bool
2015-04-02 18:03:59 +00:00
r.l.RLock()
2017-10-23 18:59:37 +00:00
if apiPath {
_, raw, ok = r.root.LongestPrefix(path)
} else {
_, raw, ok = r.storagePrefix.LongestPrefix(path)
}
2015-04-02 18:03:59 +00:00
r.l.RUnlock()
if !ok {
return nil
}
return raw.(*routeEntry).storageView
}
// MatchingMountEntry returns the MountEntry used for a path
2018-09-18 03:03:00 +00:00
func (r *Router) MatchingMountEntry(ctx context.Context, path string) *MountEntry {
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil
}
path = ns.Path + path
r.l.RLock()
_, raw, ok := r.root.LongestPrefix(path)
r.l.RUnlock()
if !ok {
return nil
}
return raw.(*routeEntry).mountEntry
}
// MatchingBackend returns the backend used for a path
2018-09-18 03:03:00 +00:00
func (r *Router) MatchingBackend(ctx context.Context, path string) logical.Backend {
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil
}
path = ns.Path + path
r.l.RLock()
_, raw, ok := r.root.LongestPrefix(path)
r.l.RUnlock()
if !ok {
return nil
}
re := raw.(*routeEntry)
re.l.RLock()
defer re.l.RUnlock()
return re.backend
}
// MatchingSystemView returns the SystemView used for a path
2018-09-18 03:03:00 +00:00
func (r *Router) MatchingSystemView(ctx context.Context, path string) logical.SystemView {
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil
}
path = ns.Path + path
r.l.RLock()
_, raw, ok := r.root.LongestPrefix(path)
r.l.RUnlock()
if !ok || raw.(*routeEntry).backend == nil {
return nil
}
return raw.(*routeEntry).backend.System()
2015-04-02 18:03:59 +00:00
}
2020-06-26 21:13:16 +00:00
func (r *Router) MatchingMountByAPIPath(ctx context.Context, path string) string {
me, _, _ := r.matchingMountEntryByPath(ctx, path, true)
if me == nil {
return ""
}
return me.Path
}
2018-09-18 03:03:00 +00:00
// MatchingStoragePrefixByAPIPath the storage prefix for the given api path
func (r *Router) MatchingStoragePrefixByAPIPath(ctx context.Context, path string) (string, bool) {
ns, err := namespace.FromContext(ctx)
if err != nil {
return "", false
}
path = ns.Path + path
_, prefix, found := r.matchingMountEntryByPath(ctx, path, true)
return prefix, found
2017-10-23 18:59:37 +00:00
}
2018-09-18 03:03:00 +00:00
// MatchingAPIPrefixByStoragePath the api path information for the given storage path
func (r *Router) MatchingAPIPrefixByStoragePath(ctx context.Context, path string) (*namespace.Namespace, string, string, bool) {
me, prefix, found := r.matchingMountEntryByPath(ctx, path, false)
if !found {
return nil, "", "", found
}
mountPath := me.Path
// Add back the prefix for credential backends
if strings.HasPrefix(path, credentialBarrierPrefix) {
mountPath = credentialRoutePrefix + mountPath
}
return me.Namespace(), mountPath, prefix, found
2017-10-23 18:59:37 +00:00
}
2018-09-18 03:03:00 +00:00
func (r *Router) matchingMountEntryByPath(ctx context.Context, path string, apiPath bool) (*MountEntry, string, bool) {
2017-10-23 18:59:37 +00:00
var raw interface{}
var ok bool
r.l.RLock()
2017-10-23 18:59:37 +00:00
if apiPath {
_, raw, ok = r.root.LongestPrefix(path)
} else {
_, raw, ok = r.storagePrefix.LongestPrefix(path)
}
r.l.RUnlock()
if !ok {
2018-09-18 03:03:00 +00:00
return nil, "", false
}
// Extract the mount path and storage prefix
re := raw.(*routeEntry)
2017-10-23 18:59:37 +00:00
prefix := re.storagePrefix
2018-09-18 03:03:00 +00:00
return re.mountEntry, prefix, true
}
2015-03-06 01:23:56 +00:00
// Route is used to route a given request
func (r *Router) Route(ctx context.Context, req *logical.Request) (*logical.Response, error) {
resp, _, _, err := r.routeCommon(ctx, req, false)
return resp, err
}
2018-09-18 03:03:00 +00:00
// RouteExistenceCheck is used to route a given existence check request
2018-10-15 16:56:24 +00:00
func (r *Router) RouteExistenceCheck(ctx context.Context, req *logical.Request) (*logical.Response, bool, bool, error) {
resp, ok, exists, err := r.routeCommon(ctx, req, true)
return resp, ok, exists, err
}
func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenceCheck bool) (*logical.Response, bool, bool, error) {
2018-09-18 03:03:00 +00:00
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil, false, false, err
}
2015-03-06 01:23:56 +00:00
// Find the mount point
r.l.RLock()
adjustedPath := req.Path
2018-09-18 03:03:00 +00:00
mount, raw, ok := r.root.LongestPrefix(ns.Path + adjustedPath)
if !ok && !strings.HasSuffix(adjustedPath, "/") {
// Re-check for a backend by appending a slash. This lets "foo" mean
// "foo/" at the root level which is almost always what we want.
adjustedPath += "/"
2018-09-18 03:03:00 +00:00
mount, raw, ok = r.root.LongestPrefix(ns.Path + adjustedPath)
}
2015-03-06 01:23:56 +00:00
r.l.RUnlock()
if !ok {
return logical.ErrorResponse(fmt.Sprintf("no handler for route %q. route entry not found.", req.Path)), false, false, logical.ErrUnsupportedPath
2015-03-06 01:23:56 +00:00
}
req.Path = adjustedPath
if !existenceCheck {
defer metrics.MeasureSince([]string{
"route", string(req.Operation),
strings.ReplaceAll(mount, "/", "-"),
}, time.Now())
}
re := raw.(*routeEntry)
2015-03-06 01:23:56 +00:00
// Grab a read lock on the route entry, this protects against the backend
// being reloaded during a request. The exception is a renew request on the
// token store; such a request will have already been routed through the
// token store -> exp manager -> here so we need to not grab the lock again
// or we'll be recursively grabbing it.
if !(req.Operation == logical.RenewOperation && strings.HasPrefix(req.Path, "auth/token/")) {
re.l.RLock()
defer re.l.RUnlock()
}
2017-10-23 18:59:37 +00:00
// Filtered mounts will have a nil backend
if re.backend == nil {
return logical.ErrorResponse(fmt.Sprintf("no handler for route %q. route entry found, but backend is nil.", req.Path)), false, false, logical.ErrUnsupportedPath
2017-10-23 18:59:37 +00:00
}
2015-04-02 18:12:13 +00:00
// If the path is tainted, we reject any operation except for
// Rollback and Revoke
if re.tainted {
2015-04-02 18:12:13 +00:00
switch req.Operation {
case logical.RevokeOperation, logical.RollbackOperation:
default:
return logical.ErrorResponse(fmt.Sprintf("no handler for route %q. route entry is tainted.", req.Path)), false, false, logical.ErrUnsupportedPath
2015-04-02 18:12:13 +00:00
}
}
// Adjust the path to exclude the routing prefix
2016-09-29 04:01:28 +00:00
originalPath := req.Path
2018-09-18 03:03:00 +00:00
req.Path = strings.TrimPrefix(ns.Path+req.Path, mount)
req.MountPoint = mount
Create unified aws auth backend (#2441) * Rename builtin/credential/aws-ec2 to aws The aws-ec2 authentication backend is being expanded and will become the generic aws backend. This is a small rename commit to keep the commit history clean. * Expand aws-ec2 backend to more generic aws This adds the ability to authenticate arbitrary AWS IAM principals using AWS's sts:GetCallerIdentity method. The AWS-EC2 auth backend is being to just AWS with the expansion. * Add missing aws auth handler to CLI This was omitted from the previous commit * aws auth backend general variable name cleanup Also fixed a bug where allowed auth types weren't being checked upon login, and added tests for it. * Update docs for the aws auth backend * Refactor aws bind validation * Fix env var override in aws backend test Intent is to override the AWS environment variables with the TEST_* versions if they are set, but the reverse was happening. * Update docs on use of IAM authentication profile AWS now allows you to change the instance profile of a running instance, so the use case of "a long-lived instance that's not in an instance profile" no longer means you have to use the the EC2 auth method. You can now just change the instance profile on the fly. * Fix typo in aws auth cli help * Respond to PR feedback * More PR feedback * Respond to additional PR feedback * Address more feedback on aws auth PR * Make aws auth_type immutable per role * Address more aws auth PR feedback * Address more iam auth PR feedback * Rename aws-ec2.html.md to aws.html.md Per PR feedback, to go along with new backend name. * Add MountType to logical.Request * Make default aws auth_type dependent upon MountType When MountType is aws-ec2, default to ec2 auth_type for backwards compatibility with legacy roles. Otherwise, default to iam. * Pass MountPoint and MountType back up to the core Previously the request router reset the MountPoint and MountType back to the empty string before returning to the core. This ensures they get set back to the correct values.
2017-04-24 19:15:50 +00:00
req.MountType = re.mountEntry.Type
req.SetMountRunningSha256(re.mountEntry.RunningSha256)
req.SetMountRunningVersion(re.mountEntry.RunningVersion)
req.SetMountIsExternalPlugin(re.mountEntry.IsExternalPlugin())
req.SetMountClass(re.mountEntry.MountClass())
if req.Path == "/" {
req.Path = ""
}
// Attach the storage view for the request
req.Storage = re.storageView
originalEntityID := req.EntityID
2017-10-23 18:59:37 +00:00
// Hash the request token unless the request is being routed to the token
// or system backend.
clientToken := req.ClientToken
switch {
2016-09-29 04:01:28 +00:00
case strings.HasPrefix(originalPath, "auth/token/"):
case strings.HasPrefix(originalPath, mountPathSystem):
case strings.HasPrefix(originalPath, mountPathIdentity):
case strings.HasPrefix(originalPath, mountPathCubbyhole):
2018-09-18 03:03:00 +00:00
if req.Operation == logical.RollbackOperation {
// Backend doesn't support this and it can't properly look up a
// cubbyhole ID so just return here
return nil, false, false, nil
}
2018-10-15 16:56:24 +00:00
te := req.TokenEntry()
if te == nil {
2018-09-18 03:03:00 +00:00
return nil, false, false, fmt.Errorf("nil token entry")
}
2018-09-18 03:03:00 +00:00
2018-10-15 16:56:24 +00:00
if te.Type != logical.TokenTypeService {
return logical.ErrorResponse(`cubbyhole operations are only supported by "service" type tokens`), false, false, nil
}
switch {
case te.NamespaceID == namespace.RootNamespaceID && !strings.HasPrefix(req.ClientToken, consts.LegacyServiceTokenPrefix) &&
!strings.HasPrefix(req.ClientToken, consts.ServiceTokenPrefix):
2018-09-18 03:03:00 +00:00
// In order for the token store to revoke later, we need to have the same
// salted ID, so we double-salt what's going to the cubbyhole backend
salt, err := r.tokenStoreSaltFunc(ctx)
if err != nil {
return nil, false, false, err
}
req.ClientToken = re.SaltID(salt.SaltID(req.ClientToken))
default:
2018-10-15 16:56:24 +00:00
if te.CubbyholeID == "" {
2018-09-18 03:03:00 +00:00
return nil, false, false, fmt.Errorf("empty cubbyhole id")
}
2018-10-15 16:56:24 +00:00
req.ClientToken = te.CubbyholeID
2018-09-18 03:03:00 +00:00
}
default:
req.ClientToken = re.SaltID(req.ClientToken)
}
2015-03-06 01:23:56 +00:00
2016-02-18 16:22:04 +00:00
// Cache the pointer to the original connection object
originalConn := req.Connection
2016-07-24 01:46:28 +00:00
// Cache the identifier of the request
originalReqID := req.ID
// Cache the client token's number of uses in the request
originalClientTokenRemainingUses := req.ClientTokenRemainingUses
req.ClientTokenRemainingUses = 0
2019-03-25 18:18:43 +00:00
originalMFACreds := req.MFACreds
2018-09-18 03:03:00 +00:00
req.MFACreds = nil
2019-03-25 18:18:43 +00:00
originalControlGroup := req.ControlGroup
req.ControlGroup = nil
// Cache the headers
headers := req.Headers
2019-02-05 21:02:15 +00:00
req.Headers = nil
// Cache the saved request SSC token
inboundToken := req.InboundSSCToken
// Ensure that the inbound token we cache in the
// request during token creation isn't sent to backends
req.InboundSSCToken = ""
// Filter and add passthrough headers to the backend
var passthroughRequestHeaders []string
if rawVal, ok := re.mountEntry.synthesizedConfigCache.Load("passthrough_request_headers"); ok {
passthroughRequestHeaders = rawVal.([]string)
}
2019-02-05 21:02:15 +00:00
var allowedResponseHeaders []string
if rawVal, ok := re.mountEntry.synthesizedConfigCache.Load("allowed_response_headers"); ok {
allowedResponseHeaders = rawVal.([]string)
}
if len(passthroughRequestHeaders) > 0 {
req.Headers = filteredHeaders(headers, passthroughRequestHeaders, deniedPassthroughRequestHeaders)
}
2017-01-04 21:44:03 +00:00
// Cache the wrap info of the request
var wrapInfo *logical.RequestWrapInfo
if req.WrapInfo != nil {
wrapInfo = &logical.RequestWrapInfo{
TTL: req.WrapInfo.TTL,
Format: req.WrapInfo.Format,
SealWrap: req.WrapInfo.SealWrap,
2017-01-04 21:44:03 +00:00
}
}
2016-09-29 04:01:28 +00:00
2018-09-18 03:03:00 +00:00
originalPolicyOverride := req.PolicyOverride
2018-06-08 21:53:28 +00:00
reqTokenEntry := req.TokenEntry()
req.SetTokenEntry(nil)
2015-03-06 01:23:56 +00:00
// Reset the request before returning
defer func() {
2016-09-29 04:01:28 +00:00
req.Path = originalPath
Create unified aws auth backend (#2441) * Rename builtin/credential/aws-ec2 to aws The aws-ec2 authentication backend is being expanded and will become the generic aws backend. This is a small rename commit to keep the commit history clean. * Expand aws-ec2 backend to more generic aws This adds the ability to authenticate arbitrary AWS IAM principals using AWS's sts:GetCallerIdentity method. The AWS-EC2 auth backend is being to just AWS with the expansion. * Add missing aws auth handler to CLI This was omitted from the previous commit * aws auth backend general variable name cleanup Also fixed a bug where allowed auth types weren't being checked upon login, and added tests for it. * Update docs for the aws auth backend * Refactor aws bind validation * Fix env var override in aws backend test Intent is to override the AWS environment variables with the TEST_* versions if they are set, but the reverse was happening. * Update docs on use of IAM authentication profile AWS now allows you to change the instance profile of a running instance, so the use case of "a long-lived instance that's not in an instance profile" no longer means you have to use the the EC2 auth method. You can now just change the instance profile on the fly. * Fix typo in aws auth cli help * Respond to PR feedback * More PR feedback * Respond to additional PR feedback * Address more feedback on aws auth PR * Make aws auth_type immutable per role * Address more aws auth PR feedback * Address more iam auth PR feedback * Rename aws-ec2.html.md to aws.html.md Per PR feedback, to go along with new backend name. * Add MountType to logical.Request * Make default aws auth_type dependent upon MountType When MountType is aws-ec2, default to ec2 auth_type for backwards compatibility with legacy roles. Otherwise, default to iam. * Pass MountPoint and MountType back up to the core Previously the request router reset the MountPoint and MountType back to the empty string before returning to the core. This ensures they get set back to the correct values.
2017-04-24 19:15:50 +00:00
req.MountPoint = mount
req.MountType = re.mountEntry.Type
req.SetMountRunningSha256(re.mountEntry.RunningSha256)
req.SetMountRunningVersion(re.mountEntry.RunningVersion)
req.SetMountIsExternalPlugin(re.mountEntry.IsExternalPlugin())
req.SetMountClass(re.mountEntry.MountClass())
req.Connection = originalConn
2016-07-24 01:46:28 +00:00
req.ID = originalReqID
req.Storage = nil
2015-03-24 18:09:25 +00:00
req.ClientToken = clientToken
req.ClientTokenRemainingUses = originalClientTokenRemainingUses
2017-01-04 21:44:03 +00:00
req.WrapInfo = wrapInfo
req.Headers = headers
2018-09-18 03:03:00 +00:00
req.PolicyOverride = originalPolicyOverride
2017-03-01 17:39:42 +00:00
// This is only set in one place, after routing, so should never be set
// by a backend
req.SetLastRemoteWAL(0)
// This will be used for attaching the mount accessor for the identities
// returned by the authentication backends
req.MountAccessor = re.mountEntry.Accessor
req.EntityID = originalEntityID
2018-06-08 21:53:28 +00:00
2019-03-25 18:18:43 +00:00
req.MFACreds = originalMFACreds
2018-09-18 03:03:00 +00:00
req.InboundSSCToken = inboundToken
// Before resetting the tokenEntry, see if an ExternalID was added
if req.TokenEntry() != nil && req.TokenEntry().ExternalID != "" {
reqTokenEntry.ExternalID = req.TokenEntry().ExternalID
}
2018-06-08 21:53:28 +00:00
req.SetTokenEntry(reqTokenEntry)
2019-03-25 18:18:43 +00:00
req.ControlGroup = originalControlGroup
}()
2015-03-06 01:23:56 +00:00
// Invoke the backend
if existenceCheck {
ok, exists, err := re.backend.HandleExistenceCheck(ctx, req)
2016-01-12 20:09:16 +00:00
return nil, ok, exists, err
} else {
resp, err := re.backend.HandleRequest(ctx, req)
2019-02-05 21:02:15 +00:00
if resp != nil {
if len(allowedResponseHeaders) > 0 {
resp.Headers = filteredHeaders(resp.Headers, allowedResponseHeaders, nil)
} else {
resp.Headers = nil
}
2018-10-15 16:56:24 +00:00
2019-02-05 21:02:15 +00:00
if resp.Auth != nil {
// When a token gets renewed, the request hits this path and
// reaches token store. Token store delegates the renewal to the
// expiration manager. Expiration manager in-turn creates a
// different logical request and forwards the request to the auth
// backend that had initially authenticated the login request. The
// forwarding to auth backend will make this code path hit for the
// second time for the same renewal request. The accessors in the
// Alias structs should be of the auth backend and not of the token
// store. Therefore, avoiding the overwriting of accessors by
// having a check for path prefix having "renew". This gets applied
// for "renew" and "renew-self" requests.
if !strings.HasPrefix(req.Path, "renew") {
if resp.Auth.Alias != nil {
resp.Auth.Alias.MountAccessor = re.mountEntry.Accessor
2018-10-15 16:56:24 +00:00
}
2019-02-05 21:02:15 +00:00
for _, alias := range resp.Auth.GroupAliases {
alias.MountAccessor = re.mountEntry.Accessor
}
}
switch re.mountEntry.Type {
case mountTypeToken, mountTypeNSToken:
2019-02-05 21:02:15 +00:00
// Nothing; we respect what the token store is telling us and
// we don't allow tuning
default:
switch re.mountEntry.Config.TokenType {
case logical.TokenTypeService, logical.TokenTypeBatch:
resp.Auth.TokenType = re.mountEntry.Config.TokenType
case logical.TokenTypeDefault, logical.TokenTypeDefaultService:
switch resp.Auth.TokenType {
case logical.TokenTypeDefault, logical.TokenTypeDefaultService, logical.TokenTypeService:
2019-02-05 21:02:15 +00:00
resp.Auth.TokenType = logical.TokenTypeService
default:
resp.Auth.TokenType = logical.TokenTypeBatch
2019-02-05 21:02:15 +00:00
}
case logical.TokenTypeDefaultBatch:
switch resp.Auth.TokenType {
case logical.TokenTypeDefault, logical.TokenTypeDefaultBatch, logical.TokenTypeBatch:
2019-02-05 21:02:15 +00:00
resp.Auth.TokenType = logical.TokenTypeBatch
default:
resp.Auth.TokenType = logical.TokenTypeService
2019-02-05 21:02:15 +00:00
}
2018-10-15 16:56:24 +00:00
}
}
}
}
2018-09-18 03:03:00 +00:00
2016-01-12 20:09:16 +00:00
return resp, false, false, err
}
2015-03-06 01:23:56 +00:00
}
// RootPath checks if the given path requires root privileges
2018-09-18 03:03:00 +00:00
func (r *Router) RootPath(ctx context.Context, path string) bool {
ns, err := namespace.FromContext(ctx)
if err != nil {
return false
}
adjustedPath := ns.Path + path
2015-03-06 01:23:56 +00:00
r.l.RLock()
2018-09-18 03:03:00 +00:00
mount, raw, ok := r.root.LongestPrefix(adjustedPath)
2015-03-06 01:23:56 +00:00
r.l.RUnlock()
if !ok {
return false
}
re := raw.(*routeEntry)
2015-03-06 01:23:56 +00:00
// Trim to get remaining path
2018-09-18 03:03:00 +00:00
remain := strings.TrimPrefix(adjustedPath, mount)
2015-03-06 01:23:56 +00:00
// Check the rootPaths of this backend
rootPaths := re.rootPaths.Load().(*radix.Tree)
match, raw, ok := rootPaths.LongestPrefix(remain)
2015-03-06 01:23:56 +00:00
if !ok {
return false
}
prefixMatch := raw.(bool)
// Handle the prefix match case
if prefixMatch {
return strings.HasPrefix(remain, match)
}
// Handle the exact match case
return match == remain
}
// LoginPath checks if the given path is used for logins
2021-10-13 16:51:20 +00:00
// Matching Priority
// 1. prefix
// 2. exact
// 3. wildcard
2018-09-18 03:03:00 +00:00
func (r *Router) LoginPath(ctx context.Context, path string) bool {
ns, err := namespace.FromContext(ctx)
if err != nil {
return false
}
adjustedPath := ns.Path + path
r.l.RLock()
2018-09-18 03:03:00 +00:00
mount, raw, ok := r.root.LongestPrefix(adjustedPath)
r.l.RUnlock()
if !ok {
return false
}
re := raw.(*routeEntry)
// Trim to get remaining path
2018-09-18 03:03:00 +00:00
remain := strings.TrimPrefix(adjustedPath, mount)
// Check the loginPaths of this backend
2021-10-13 16:51:20 +00:00
pe := re.loginPaths.Load().(*loginPathsEntry)
match, raw, ok := pe.paths.LongestPrefix(remain)
if !ok && len(pe.wildcardPaths) == 0 {
// no match found
return false
}
2021-10-13 16:51:20 +00:00
if ok {
prefixMatch := raw.(bool)
if prefixMatch {
// Handle the prefix match case
return strings.HasPrefix(remain, match)
}
if match == remain {
// Handle the exact match case
return true
}
}
2021-10-13 16:51:20 +00:00
// check Login Paths containing wildcards
reqPathParts := strings.Split(remain, "/")
for _, w := range pe.wildcardPaths {
if pathMatchesWildcardPath(reqPathParts, w.segments, w.isPrefix) {
return true
}
}
return false
}
// pathMatchesWildcardPath returns true if the path made up of the path slice
// matches the given wildcard path slice
func pathMatchesWildcardPath(path, wcPath []string, isPrefix bool) bool {
if len(wcPath) == 0 {
return false
}
if len(path) < len(wcPath) {
// check if the path coming in is shorter; if so it can't match
return false
}
if !isPrefix && len(wcPath) != len(path) {
// If it's not a prefix we expect the same number of segments
return false
}
for i, wcPathPart := range wcPath {
switch {
case wcPathPart == "+":
case wcPathPart == path[i]:
case isPrefix && i == len(wcPath)-1 && strings.HasPrefix(path[i], wcPathPart):
default:
// we encountered segments that did not match
return false
}
}
return true
}
func wildcardError(path, msg string) error {
return fmt.Errorf("path %q: invalid use of wildcards %s", path, msg)
}
func isValidUnauthenticatedPath(path string) (bool, error) {
switch {
case strings.Count(path, "*") > 1:
return false, wildcardError(path, "(multiple '*' is forbidden)")
case strings.Contains(path, "+*"):
return false, wildcardError(path, "('+*' is forbidden)")
case strings.Contains(path, "*") && path[len(path)-1] != '*':
return false, wildcardError(path, "('*' is only allowed at the end of a path)")
case wcAdjacentNonSlashRegEx(path):
return false, wildcardError(path, "('+' is not allowed next to a non-slash)")
}
return true, nil
}
// parseUnauthenticatedPaths converts a list of special paths to a
// loginPathsEntry
func parseUnauthenticatedPaths(paths []string) (*loginPathsEntry, error) {
var tempPaths []string
tempWildcardPaths := make([]wildcardPath, 0)
for _, path := range paths {
if ok, err := isValidUnauthenticatedPath(path); !ok {
return nil, err
}
if strings.Contains(path, "+") {
// Paths with wildcards are not stored in the radix tree because
// the radix tree does not handle wildcards in the middle of strings.
isPrefix := false
if path[len(path)-1] == '*' {
isPrefix = true
path = path[0 : len(path)-1]
}
// We are micro-optimizing by storing pre-split slices of path segments
wcPath := wildcardPath{segments: strings.Split(path, "/"), isPrefix: isPrefix}
tempWildcardPaths = append(tempWildcardPaths, wcPath)
} else {
// accumulate paths that do not contain wildcards
// to be stored in the radix tree
tempPaths = append(tempPaths, path)
}
}
return &loginPathsEntry{
paths: pathsToRadix(tempPaths),
wildcardPaths: tempWildcardPaths,
}, nil
}
2019-02-18 18:05:04 +00:00
// pathsToRadix converts a list of special paths to a radix tree.
func pathsToRadix(paths []string) *radix.Tree {
tree := radix.New()
for _, path := range paths {
// Check if this is a prefix or exact match
prefixMatch := len(path) >= 1 && path[len(path)-1] == '*'
if prefixMatch {
path = path[:len(path)-1]
}
2015-03-31 00:46:18 +00:00
tree.Insert(path, prefixMatch)
}
2015-03-31 00:46:18 +00:00
return tree
}
2019-02-05 21:02:15 +00:00
// filteredHeaders returns a headers map[string][]string that
// contains the filtered values contained in candidateHeaders. Filtering of
// candidateHeaders from the origHeaders is done is a case-insensitive manner.
// Headers that match values from deniedHeaders will be ignored.
func filteredHeaders(origHeaders map[string][]string, candidateHeaders, deniedHeaders []string) map[string][]string {
// Short-circuit if there's nothing to filter
2019-02-05 21:02:15 +00:00
if len(candidateHeaders) == 0 {
return nil
}
2019-02-05 21:02:15 +00:00
retHeaders := make(map[string][]string, len(origHeaders))
// Filter candidateHeaders values through deniedHeaders first. Returns the
// lowercased complement set. We call even if no denied headers to get the
// values lowercased.
allowedCandidateHeaders := strutil.Difference(candidateHeaders, deniedHeaders, true)
// Create a map that uses lowercased header values as the key and the original
// header naming as the value for comparison down below.
2019-02-05 21:02:15 +00:00
lowerOrigHeaderKeys := make(map[string]string, len(origHeaders))
for key := range origHeaders {
2019-02-05 21:02:15 +00:00
lowerOrigHeaderKeys[strings.ToLower(key)] = key
}
// Case-insensitive compare of passthrough headers against originating
// headers. The returned headers will be the same casing as the originating
// header name.
2019-02-05 21:02:15 +00:00
for _, ch := range allowedCandidateHeaders {
if header, ok := lowerOrigHeaderKeys[ch]; ok {
retHeaders[header] = origHeaders[header]
}
}
return retHeaders
}