diff --git a/vault/auth.go b/vault/auth.go index a88a16612472..de2b5fae6d25 100644 --- a/vault/auth.go +++ b/vault/auth.go @@ -75,8 +75,8 @@ func (c *Core) enableCredential(entry *MountEntry) error { return fmt.Errorf("token credential backend cannot be instantiated") } - if match := c.router.MatchingMount(credentialRoutePrefix + entry.Path); match != "" { - return logical.CodedError(409, fmt.Sprintf("existing mount at %s", match)) + if conflict := c.router.MountConflict(credentialRoutePrefix + entry.Path); conflict != "" { + return logical.CodedError(409, fmt.Sprintf("existing mount at %s", conflict)) } // Generate a new UUID and view diff --git a/vault/mount.go b/vault/mount.go index de1c9461a5fe..e6e3cc777045 100644 --- a/vault/mount.go +++ b/vault/mount.go @@ -238,8 +238,8 @@ func (c *Core) mountInternal(entry *MountEntry) error { c.mountsLock.Lock() defer c.mountsLock.Unlock() - // Verify there is no conflicting mount - if match := c.router.MatchingMount(entry.Path); match != "" { + // Verify there are no conflicting mounts + if match := c.router.MountConflict(entry.Path); match != "" { return logical.CodedError(409, fmt.Sprintf("existing mount at %s", match)) } diff --git a/vault/router.go b/vault/router.go index 8bd839ebe4ff..05a31c6e4768 100644 --- a/vault/router.go +++ b/vault/router.go @@ -19,7 +19,6 @@ type Router struct { mountUUIDCache *radix.Tree mountAccessorCache *radix.Tree tokenStoreSaltFunc func() (*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 @@ -231,14 +230,46 @@ func (r *Router) MatchingMountByAccessor(mountAccessor string) *MountEntry { // MatchingMount returns the mount prefix that would be used for a path func (r *Router) MatchingMount(path string) string { r.l.RLock() + defer r.l.RUnlock() + var mount = r.matchingMountInternal(path) + return mount +} + +func (r *Router) matchingMountInternal(path string) string { mount, _, ok := r.root.LongestPrefix(path) - r.l.RUnlock() if !ok { return "" } return mount } +// matchingPrefixInternal returns a mount prefix that a path may be a part of +func (r *Router) matchingPrefixInternal(path string) string { + var existing string = "" + fn := func(existing_path string, _v interface{}) bool { + if strings.HasPrefix(existing_path, path) { + existing = existing_path + return true + } + return false + } + r.root.WalkPrefix(path, fn) + return existing +} + +// MountConflict determines if there are potential path conflicts +func (r *Router) MountConflict(path string) string { + r.l.RLock() + defer r.l.RUnlock() + if exact_match := r.matchingMountInternal(path); exact_match != "" { + return exact_match + } + if prefix_match := r.matchingPrefixInternal(path); prefix_match != "" { + return prefix_match + } + return "" +} + // MatchingStorageByAPIPath/StoragePath returns the storage used for // API/Storage paths respectively func (r *Router) MatchingStorageByAPIPath(path string) logical.Storage { diff --git a/vault/router_test.go b/vault/router_test.go index bab33d762f1a..35d108e4cd50 100644 --- a/vault/router_test.go +++ b/vault/router_test.go @@ -118,6 +118,11 @@ func TestRouter_Mount(t *testing.T) { t.Fatalf("err: %v", err) } + meUUID, err = uuid.GenerateUUID() + if err != nil { + t.Fatal(err) + } + if path := r.MatchingMount("prod/aws/foo"); path != "prod/aws/" { t.Fatalf("bad: %s", path) } @@ -162,6 +167,25 @@ func TestRouter_Mount(t *testing.T) { if len(n.Paths) != 1 || n.Paths[0] != "foo" { t.Fatalf("bad: %v", n.Paths) } + + subMountEntry := &MountEntry{ + Path: "prod/", + UUID: meUUID, + Accessor: "prodaccessor", + } + + if r.MountConflict("prod/aws/") == "" { + t.Fatalf("bad: prod/aws/") + } + + // No error is shown here because MountConflict is checked before Mount + err = r.Mount(n, "prod/", subMountEntry, view) + if err != nil { + t.Fatalf("err: %v", err) + } + if r.MountConflict("prod/test") == "" { + t.Fatalf("bad: prod/test/") + } } func TestRouter_MountCredential(t *testing.T) { diff --git a/website/source/docs/secrets/index.html.md b/website/source/docs/secrets/index.html.md index ea8bb1c672eb..a4bb48449946 100644 --- a/website/source/docs/secrets/index.html.md +++ b/website/source/docs/secrets/index.html.md @@ -59,6 +59,13 @@ Once a secret backend is mounted, you can interact with it directly at its mount point according to its own API. You can use the `vault path-help` system to determine the paths it responds to. +Note that mount points cannot conflict with each other in Vault. There are +two broad implications of this fact. The first is that you cannot have +a mount which is prefixed with an existing mount. The second is that you +cannot create a mount point that is named as a prefix of an existing mount. +As an example, the mounts `foo/bar` and `foo/baz` can peacefully coexist +with each other whereas `foo` and `foo/baz` cannot + ## Barrier View An important concept around secret backends is that they receive a