Vault 4632 auth remount oss (#14141)

* Update plugin-portal.mdx (#13229)

Add a Vault plugin to allow authentication via SSH certificates and public keys

* oss changes

Co-authored-by: Wim <wim@42.be>
This commit is contained in:
Pratyoy Mukhopadhyay 2022-02-18 08:04:21 -08:00 committed by GitHub
parent f0dc3a553f
commit 475b55b460
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 623 additions and 121 deletions

View File

@ -2,8 +2,10 @@ package http
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
"github.com/go-test/deep"
"github.com/hashicorp/vault/vault"
@ -433,3 +435,130 @@ func TestSysTuneAuth_showUIMount(t *testing.T) {
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
}
}
func TestSysRemountAuth(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
TestServerAuth(t, addr, token)
resp := testHttpPost(t, token, addr+"/v1/sys/auth/foo", map[string]interface{}{
"type": "noop",
"description": "foo",
})
testResponseStatus(t, resp, 204)
resp = testHttpPost(t, token, addr+"/v1/sys/remount", map[string]interface{}{
"from": "auth/foo",
"to": "auth/bar",
})
testResponseStatus(t, resp, 200)
// Poll until the remount succeeds
var remountResp map[string]interface{}
testResponseBody(t, resp, &remountResp)
vault.RetryUntil(t, 5*time.Second, func() error {
resp = testHttpGet(t, token, addr+"/v1/sys/remount/status/"+remountResp["migration_id"].(string))
testResponseStatus(t, resp, 200)
var remountStatusResp map[string]interface{}
testResponseBody(t, resp, &remountStatusResp)
status := remountStatusResp["data"].(map[string]interface{})["migration_info"].(map[string]interface{})["status"]
if status != "success" {
return fmt.Errorf("Expected migration status to be successful, got %q", status)
}
return nil
})
resp = testHttpGet(t, token, addr+"/v1/sys/auth")
var actual map[string]interface{}
expected := map[string]interface{}{
"lease_id": "",
"renewable": false,
"lease_duration": json.Number("0"),
"wrap_info": nil,
"warnings": nil,
"auth": nil,
"data": map[string]interface{}{
"bar/": map[string]interface{}{
"description": "foo",
"type": "noop",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"token_type": "default-service",
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]interface{}{},
},
"token/": map[string]interface{}{
"description": "token based credentials",
"type": "token",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"force_no_cache": false,
"token_type": "default-service",
},
"local": false,
"seal_wrap": false,
"options": interface{}(nil),
},
},
"bar/": map[string]interface{}{
"description": "foo",
"type": "noop",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"token_type": "default-service",
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]interface{}{},
},
"token/": map[string]interface{}{
"description": "token based credentials",
"type": "token",
"external_entropy_access": false,
"config": map[string]interface{}{
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
"token_type": "default-service",
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": interface{}(nil),
},
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
expected["request_id"] = actual["request_id"]
for k, v := range actual["data"].(map[string]interface{}) {
if v.(map[string]interface{})["accessor"] == "" {
t.Fatalf("no accessor from %s", k)
}
if v.(map[string]interface{})["uuid"] == "" {
t.Fatalf("no uuid from %s", k)
}
expected[k].(map[string]interface{})["accessor"] = v.(map[string]interface{})["accessor"]
expected[k].(map[string]interface{})["uuid"] = v.(map[string]interface{})["uuid"]
expected["data"].(map[string]interface{})[k].(map[string]interface{})["accessor"] = v.(map[string]interface{})["accessor"]
expected["data"].(map[string]interface{})[k].(map[string]interface{})["uuid"] = v.(map[string]interface{})["uuid"]
}
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}

View File

@ -44,6 +44,11 @@ var (
// credentialAliases maps old backend names to new backend names, allowing us
// to move/rename backends but maintain backwards compatibility
credentialAliases = map[string]string{"aws-ec2": "aws"}
// protectedAuths marks auth mounts that are protected and cannot be remounted
protectedAuths = []string{
"auth/token",
}
)
// enableCredential is used to enable a new credential backend
@ -274,7 +279,7 @@ func (c *Core) disableCredentialInternal(ctx context.Context, path string, updat
entry := c.router.MatchingMountEntry(ctx, path)
// Mark the entry as tainted
if err := c.taintCredEntry(ctx, path, updateStorage); err != nil {
if err := c.taintCredEntry(ctx, ns.ID, path, updateStorage); err != nil {
return err
}
@ -387,6 +392,103 @@ func (c *Core) removeCredEntry(ctx context.Context, path string, updateStorage b
return nil
}
func (c *Core) remountCredential(ctx context.Context, src, dst namespace.MountPathDetails, updateStorage bool) error {
ns, err := namespace.FromContext(ctx)
if err != nil {
return err
}
if !strings.HasPrefix(src.MountPath, credentialRoutePrefix) {
return fmt.Errorf("cannot remount non-auth mount %q", src.MountPath)
}
if !strings.HasPrefix(dst.MountPath, credentialRoutePrefix) {
return fmt.Errorf("cannot remount auth mount to non-auth mount %q", dst.MountPath)
}
for _, auth := range protectedAuths {
if strings.HasPrefix(src.MountPath, auth) {
return fmt.Errorf("cannot remount %q", src.MountPath)
}
}
for _, auth := range protectedAuths {
if strings.HasPrefix(dst.MountPath, auth) {
return fmt.Errorf("cannot remount to %q", dst.MountPath)
}
}
srcRelativePath := src.GetRelativePath(ns)
dstRelativePath := dst.GetRelativePath(ns)
// Verify exact match of the route
srcMatch := c.router.MatchingMountEntry(ctx, srcRelativePath)
if srcMatch == nil {
return fmt.Errorf("no matching mount at %q", src.Namespace.Path+src.MountPath)
}
if match := c.router.MountConflict(ctx, dstRelativePath); match != "" {
return fmt.Errorf("path in use at %q", match)
}
// Mark the entry as tainted
if err := c.taintCredEntry(ctx, src.Namespace.ID, src.MountPath, updateStorage); err != nil {
return err
}
// Taint the router path to prevent routing
if err := c.router.Taint(ctx, srcRelativePath); err != nil {
return err
}
if c.expiration != nil {
revokeCtx := namespace.ContextWithNamespace(ctx, src.Namespace)
// Revoke all the dynamic keys
if err := c.expiration.RevokePrefix(revokeCtx, src.MountPath, true); err != nil {
return err
}
}
c.authLock.Lock()
if match := c.router.MountConflict(ctx, dstRelativePath); match != "" {
c.authLock.Unlock()
return fmt.Errorf("path in use at %q", match)
}
srcMatch.Tainted = false
srcMatch.NamespaceID = dst.Namespace.ID
srcMatch.namespace = dst.Namespace
srcPath := srcMatch.Path
srcMatch.Path = strings.TrimPrefix(dst.MountPath, credentialRoutePrefix)
// Update the mount table
if err := c.persistAuth(ctx, c.auth, &srcMatch.Local); err != nil {
srcMatch.Path = srcPath
srcMatch.Tainted = true
c.authLock.Unlock()
if err == logical.ErrReadOnly && c.perfStandby {
return err
}
return fmt.Errorf("failed to update auth table with error %+v", err)
}
// Remount the backend, setting the existing route entry
// against the new path
if err := c.router.Remount(ctx, srcRelativePath, dstRelativePath); err != nil {
c.authLock.Unlock()
return err
}
c.authLock.Unlock()
// Un-taint the new path in the router
if err := c.router.Untaint(ctx, dstRelativePath); err != nil {
return err
}
return nil
}
// remountCredEntryForceInternal takes a copy of the mount entry for the path and fully
// unmounts and remounts the backend to pick up any changes, such as filtered
// paths. This should be only used internal.
@ -420,26 +522,21 @@ func (c *Core) remountCredEntryForceInternal(ctx context.Context, path string, u
}
// taintCredEntry is used to mark an entry in the auth table as tainted
func (c *Core) taintCredEntry(ctx context.Context, path string, updateStorage bool) error {
func (c *Core) taintCredEntry(ctx context.Context, nsID, path string, updateStorage bool) error {
c.authLock.Lock()
defer c.authLock.Unlock()
ns, err := namespace.FromContext(ctx)
if err != nil {
return err
}
// Taint the entry from the auth table
// We do this on the original since setting the taint operates
// on the entries which a shallow clone shares anyways
entry, err := c.auth.setTaint(ns.ID, strings.TrimPrefix(path, credentialRoutePrefix), true, mountStateUnmounting)
entry, err := c.auth.setTaint(nsID, strings.TrimPrefix(path, credentialRoutePrefix), true, mountStateUnmounting)
if err != nil {
return err
}
// Ensure there was a match
if entry == nil {
return fmt.Errorf("no matching backend")
return fmt.Errorf("no matching backend for path %q namespaceID %q", path, nsID)
}
if updateStorage {

View File

@ -580,3 +580,166 @@ func TestCore_CredentialInitialize(t *testing.T) {
}
}
}
func remountCredentialFromRoot(c *Core, src, dst string, updateStorage bool) error {
srcPathDetails := c.splitNamespaceAndMountFromPath("", src)
dstPathDetails := c.splitNamespaceAndMountFromPath("", dst)
return c.remountCredential(namespace.RootContext(nil), srcPathDetails, dstPathDetails, updateStorage)
}
func TestCore_RemountCredential(t *testing.T) {
c, keys, _ := TestCoreUnsealed(t)
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
return &NoopBackend{
BackendType: logical.TypeCredential,
}, nil
}
me := &MountEntry{
Table: credentialTableType,
Path: "foo",
Type: "noop",
}
err := c.enableCredential(namespace.RootContext(nil), me)
if err != nil {
t.Fatalf("err: %v", err)
}
match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
if match != "auth/foo/" {
t.Fatalf("missing mount, match: %q", match)
}
err = remountCredentialFromRoot(c, "auth/foo", "auth/bar", true)
if err != nil {
t.Fatalf("err: %v", err)
}
match = c.router.MatchingMount(namespace.RootContext(nil), "auth/bar/baz")
if match != "auth/bar/" {
t.Fatalf("auth method not at new location, match: %q", match)
}
c.sealInternal()
for i, key := range keys {
unseal, err := TestCoreUnseal(c, key)
if err != nil {
t.Fatalf("err: %v", err)
}
if i+1 == len(keys) && !unseal {
t.Fatalf("should be unsealed")
}
}
match = c.router.MatchingMount(namespace.RootContext(nil), "auth/bar/baz")
if match != "auth/bar/" {
t.Fatalf("auth method not at new location after unseal, match: %q", match)
}
}
func TestCore_RemountCredential_Cleanup(t *testing.T) {
noop := &NoopBackend{
Login: []string{"login"},
BackendType: logical.TypeCredential,
}
c, _, _ := TestCoreUnsealed(t)
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
return noop, nil
}
me := &MountEntry{
Table: credentialTableType,
Path: "foo",
Type: "noop",
}
err := c.enableCredential(namespace.RootContext(nil), me)
if err != nil {
t.Fatalf("err: %v", err)
}
// Store the view
view := c.router.MatchingStorageByAPIPath(namespace.RootContext(nil), "auth/foo/")
// Inject data
se := &logical.StorageEntry{
Key: "plstodelete",
Value: []byte("test"),
}
if err := view.Put(context.Background(), se); err != nil {
t.Fatalf("err: %v", err)
}
// Generate a new token auth
noop.Response = &logical.Response{
Auth: &logical.Auth{
Policies: []string{"foo"},
},
}
r := &logical.Request{
Operation: logical.ReadOperation,
Path: "auth/foo/login",
}
resp, err := c.HandleRequest(namespace.RootContext(nil), r)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
// Disable should cleanup
err = remountCredentialFromRoot(c, "auth/foo", "auth/bar", true)
if err != nil {
t.Fatalf("err: %v", err)
}
// Token should be revoked
te, err := c.tokenStore.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
if err != nil {
t.Fatalf("err: %v", err)
}
if te != nil {
t.Fatalf("bad: %#v", te)
}
// View should be empty
out, err := logical.CollectKeys(context.Background(), view)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(out) != 1 && out[0] != "plstokeep" {
t.Fatalf("bad: %#v", out)
}
}
func TestCore_RemountCredential_InvalidSource(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
err := remountCredentialFromRoot(c, "foo", "auth/bar", true)
if err.Error() != `cannot remount non-auth mount "foo/"` {
t.Fatalf("err: %v", err)
}
}
func TestCore_RemountCredential_InvalidDestination(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
err := remountCredentialFromRoot(c, "auth/foo", "bar", true)
if err.Error() != `cannot remount auth mount to non-auth mount "bar/"` {
t.Fatalf("err: %v", err)
}
}
func TestCore_RemountCredential_ProtectedSource(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
err := remountCredentialFromRoot(c, "auth/token", "auth/bar", true)
if err.Error() != `cannot remount "auth/token/"` {
t.Fatalf("err: %v", err)
}
}
func TestCore_RemountCredential_ProtectedDestination(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
err := remountCredentialFromRoot(c, "auth/foo", "auth/token", true)
if err.Error() != `cannot remount to "auth/token/"` {
t.Fatalf("err: %v", err)
}
}

View File

@ -1208,14 +1208,33 @@ func (b *SystemBackend) handleRemount(ctx context.Context, req *logical.Request,
return handleError(fmt.Errorf("invalid destination mount: %v", err))
}
// Prevent target and source mounts from being in a protected path
for _, p := range protectedMounts {
if strings.HasPrefix(fromPathDetails.MountPath, p) {
return handleError(fmt.Errorf("cannot remount %q", fromPathDetails.MountPath))
// Check that target is a valid auth mount, if source is an auth mount
if strings.HasPrefix(fromPathDetails.MountPath, credentialRoutePrefix) {
if !strings.HasPrefix(toPathDetails.MountPath, credentialRoutePrefix) {
return handleError(fmt.Errorf("cannot remount auth mount to non-auth mount %q", toPathDetails.MountPath))
}
// Prevent target and source auth mounts from being in a protected path
for _, auth := range protectedAuths {
if strings.HasPrefix(fromPathDetails.MountPath, auth) {
return handleError(fmt.Errorf("cannot remount %q", fromPathDetails.MountPath))
}
}
if strings.HasPrefix(toPathDetails.MountPath, p) {
return handleError(fmt.Errorf("cannot remount to destination %+v", toPathDetails.MountPath))
for _, auth := range protectedAuths {
if strings.HasPrefix(toPathDetails.MountPath, auth) {
return handleError(fmt.Errorf("cannot remount to destination %q", toPathDetails.MountPath))
}
}
} else {
// Prevent target and source non-auth mounts from being in a protected path
for _, p := range protectedMounts {
if strings.HasPrefix(fromPathDetails.MountPath, p) {
return handleError(fmt.Errorf("cannot remount %q", fromPathDetails.MountPath))
}
if strings.HasPrefix(toPathDetails.MountPath, p) {
return handleError(fmt.Errorf("cannot remount to destination %+v", toPathDetails.MountPath))
}
}
}
@ -1225,9 +1244,10 @@ func (b *SystemBackend) handleRemount(ctx context.Context, req *logical.Request,
return handleError(fmt.Errorf("no matching mount at %q", sanitizePath(fromPath)))
}
if match := b.Core.router.MatchingMount(ctx, toPath); match != "" {
return handleError(fmt.Errorf("existing mount at %q", match))
if match := b.Core.router.MountConflict(ctx, sanitizePath(toPath)); match != "" {
return handleError(fmt.Errorf("path already in use at %q", match))
}
// If we are a performance secondary cluster we should forward the request
// to the primary. We fail early here since the view in use isn't marked as
// readonly
@ -1246,12 +1266,7 @@ func (b *SystemBackend) handleRemount(ctx context.Context, req *logical.Request,
logger := b.Core.Logger().Named("mounts.migration").With("migration_id", migrationID, "namespace", ns.Path, "to_path", toPath, "from_path", fromPath)
var err error
if !strings.Contains(fromPath, "auth") {
err = b.moveSecretsEngine(ns, logger, migrationID, entry.ViewPath(), fromPathDetails, toPathDetails)
} else {
logger.Error("Remount is unsupported for the source mount", "err", err)
}
err := b.moveMount(ns, logger, migrationID, entry, fromPathDetails, toPathDetails)
if err != nil {
logger.Error("remount failed", "error", err)
if err := b.Core.setMigrationStatus(migrationID, MigrationFailureStatus); err != nil {
@ -1269,14 +1284,25 @@ func (b *SystemBackend) handleRemount(ctx context.Context, req *logical.Request,
return resp, nil
}
// moveSecretsEngine carries out a remount operation on the secrets engine, updating the migration status as required
// moveMount carries out a remount operation on the secrets engine or auth method, updating the migration status as required
// It is expected to be called asynchronously outside of a request context, hence it creates a context derived from the active one
// and intermittently checks to see if it is still open.
func (b *SystemBackend) moveSecretsEngine(ns *namespace.Namespace, logger log.Logger, migrationID, viewPath string, fromPathDetails, toPathDetails namespace.MountPathDetails) error {
func (b *SystemBackend) moveMount(ns *namespace.Namespace, logger log.Logger, migrationID string, entry *MountEntry, fromPathDetails, toPathDetails namespace.MountPathDetails) error {
logger.Info("Starting to update the mount table and revoke leases")
revokeCtx := namespace.ContextWithNamespace(b.Core.activeContext, ns)
var err error
// Attempt remount
if err := b.Core.remountSecretsEngine(revokeCtx, fromPathDetails, toPathDetails, !b.Core.perfStandby); err != nil {
switch entry.Table {
case credentialTableType:
err = b.Core.remountCredential(revokeCtx, fromPathDetails, toPathDetails, !b.Core.perfStandby)
case mountTableType:
err = b.Core.remountSecretsEngine(revokeCtx, fromPathDetails, toPathDetails, !b.Core.perfStandby)
default:
return fmt.Errorf("cannot remount mount of table %q", entry.Table)
}
if err != nil {
return err
}
@ -1286,7 +1312,7 @@ func (b *SystemBackend) moveSecretsEngine(ns *namespace.Namespace, logger log.Lo
logger.Info("Removing the source mount from filtered paths on secondaries")
// Remove from filtered mounts and restart evaluation process
if err := b.Core.removePathFromFilteredPaths(revokeCtx, fromPathDetails.GetFullPath(), viewPath); err != nil {
if err := b.Core.removePathFromFilteredPaths(revokeCtx, fromPathDetails.GetFullPath(), entry.ViewPath()); err != nil {
return err
}

View File

@ -19,6 +19,7 @@ import (
"github.com/go-test/deep"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/audit"
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
"github.com/hashicorp/vault/helper/builtinplugins"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/namespace"
@ -683,6 +684,175 @@ func TestSystemBackend_CapabilitiesAccessor_BC(t *testing.T) {
}
}
func TestSystemBackend_remount_auth(t *testing.T) {
err := AddTestCredentialBackend("userpass", credUserpass.Factory)
if err != nil {
t.Fatal(err)
}
c, b, _ := testCoreSystemBackend(t)
userpassMe := &MountEntry{
Table: credentialTableType,
Path: "userpass1/",
Type: "userpass",
Description: "userpass",
}
err = c.enableCredential(namespace.RootContext(nil), userpassMe)
if err != nil {
t.Fatal(err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "remount")
req.Data["from"] = "auth/userpass1"
req.Data["to"] = "auth/userpass2"
req.Data["config"] = structs.Map(MountConfig{})
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
RetryUntil(t, 5*time.Second, func() error {
req = logical.TestRequest(t, logical.ReadOperation, fmt.Sprintf("remount/status/%s", resp.Data["migration_id"]))
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
migrationInfo := resp.Data["migration_info"].(*MountMigrationInfo)
if migrationInfo.MigrationStatus != MigrationSuccessStatus.String() {
return fmt.Errorf("Expected migration status to be successful, got %q", migrationInfo.MigrationStatus)
}
return nil
})
}
func TestSystemBackend_remount_auth_invalid(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "remount")
req.Data["from"] = "auth/unknown"
req.Data["to"] = "auth/foo"
req.Data["config"] = structs.Map(MountConfig{})
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if !strings.Contains(resp.Data["error"].(string), "no matching mount at \"auth/unknown/\"") {
t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
}
req.Data["to"] = "foo"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if !strings.Contains(resp.Data["error"].(string), "cannot remount auth mount to non-auth mount \"foo/\"") {
t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
}
}
func TestSystemBackend_remount_auth_protected(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "remount")
req.Data["from"] = "auth/token"
req.Data["to"] = "auth/foo"
req.Data["config"] = structs.Map(MountConfig{})
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if !strings.Contains(resp.Data["error"].(string), "cannot remount \"auth/token/\"") {
t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
}
req.Data["from"] = "auth/foo"
req.Data["to"] = "auth/token"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if !strings.Contains(resp.Data["error"].(string), "cannot remount to destination \"auth/token/\"") {
t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
}
}
func TestSystemBackend_remount_auth_destinationInUse(t *testing.T) {
err := AddTestCredentialBackend("userpass", credUserpass.Factory)
if err != nil {
t.Fatal(err)
}
c, b, _ := testCoreSystemBackend(t)
userpassMe := &MountEntry{
Table: credentialTableType,
Path: "userpass1/",
Type: "userpass",
Description: "userpass",
}
err = c.enableCredential(namespace.RootContext(nil), userpassMe)
if err != nil {
t.Fatal(err)
}
userpassMe2 := &MountEntry{
Table: credentialTableType,
Path: "userpass2/",
Type: "userpass",
Description: "userpass",
}
err = c.enableCredential(namespace.RootContext(nil), userpassMe2)
if err != nil {
t.Fatal(err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "remount")
req.Data["from"] = "auth/userpass1"
req.Data["to"] = "auth/userpass2"
req.Data["config"] = structs.Map(MountConfig{})
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if !strings.Contains(resp.Data["error"].(string), "path already in use at \"auth/userpass2/\"") {
t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
}
req.Data["to"] = "auth/userpass2/mypass"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if !strings.Contains(resp.Data["error"].(string), "path already in use at \"auth/userpass2/\"") {
t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
}
userpassMe3 := &MountEntry{
Table: credentialTableType,
Path: "userpass3/mypass/",
Type: "userpass",
Description: "userpass",
}
err = c.enableCredential(namespace.RootContext(nil), userpassMe3)
if err != nil {
t.Fatal(err)
}
req.Data["to"] = "auth/userpass3/"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if !strings.Contains(resp.Data["error"].(string), "path already in use at \"auth/userpass3/mypass/\"") {
t.Fatalf("Found unexpected error %q", resp.Data["error"].(string))
}
}
func TestSystemBackend_remount(t *testing.T) {
b := testSystemBackend(t)

View File

@ -903,11 +903,11 @@ func (c *Core) remountSecretsEngine(ctx context.Context, src, dst namespace.Moun
// Verify exact match of the route
srcMatch := c.router.MatchingMountEntry(ctx, srcRelativePath)
if srcMatch == nil {
return fmt.Errorf("no matching mount at %+v", src)
return fmt.Errorf("no matching mount at %q", src.Namespace.Path+src.MountPath)
}
if match := c.router.MatchingMount(ctx, dstRelativePath); match != "" {
return fmt.Errorf("existing mount at %q", match)
if match := c.router.MountConflict(ctx, dstRelativePath); match != "" {
return fmt.Errorf("path in use at %q", match)
}
// Mark the entry as tainted
@ -937,9 +937,9 @@ func (c *Core) remountSecretsEngine(ctx context.Context, src, dst namespace.Moun
}
c.mountsLock.Lock()
if match := c.router.MatchingMount(ctx, dstRelativePath); match != "" {
if match := c.router.MountConflict(ctx, dstRelativePath); match != "" {
c.mountsLock.Unlock()
return fmt.Errorf("existing mount at %q", match)
return fmt.Errorf("path in use at %q", match)
}
srcMatch.Tainted = false
@ -957,8 +957,7 @@ func (c *Core) remountSecretsEngine(ctx context.Context, src, dst namespace.Moun
return err
}
c.logger.Error("failed to update mounts table", "error", err)
return logical.CodedError(500, "failed to update mounts table")
return fmt.Errorf("failed to update mount table with error %+v", err)
}
// Remount the backend
@ -973,7 +972,6 @@ func (c *Core) remountSecretsEngine(ctx context.Context, src, dst namespace.Moun
return err
}
c.logger.Info("successful remount", "old_path", src, "new_path", dst)
return nil
}

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"reflect"
"strings"
"sync"
"testing"
"time"
@ -445,63 +444,6 @@ func testCore_Unmount_Cleanup(t *testing.T, causeFailure bool) {
}
}
func TestCore_RemountConcurrent(t *testing.T) {
c2, _, _ := TestCoreUnsealed(t)
noop := &NoopBackend{}
c2.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
return noop, nil
}
// Mount the noop backend
mount1 := &MountEntry{
Table: mountTableType,
Path: "test1/",
Type: "noop",
}
if err := c2.mount(namespace.RootContext(nil), mount1); err != nil {
t.Fatalf("err: %v", err)
}
mount2 := &MountEntry{
Table: mountTableType,
Path: "test2/",
Type: "noop",
}
if err := c2.mount(namespace.RootContext(nil), mount2); err != nil {
t.Fatalf("err: %v", err)
}
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
err := c2.remountSecretsEngineCurrentNamespace(namespace.RootContext(nil), "test1", "foo", true)
if err != nil {
t.Logf("err: %v", err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
err := c2.remountSecretsEngineCurrentNamespace(namespace.RootContext(nil), "test2", "foo", true)
if err != nil {
t.Logf("err: %v", err)
}
}()
wg.Wait()
c2MountMap := map[string]interface{}{}
for _, v := range c2.mounts.Entries {
if _, ok := c2MountMap[v.Path]; ok {
t.Fatalf("duplicated mount path found at %s", v.Path)
}
c2MountMap[v.Path] = v
}
}
func TestCore_Remount(t *testing.T) {
c, keys, _ := TestCoreUnsealed(t)
err := c.remountSecretsEngineCurrentNamespace(namespace.RootContext(nil), "secret", "foo", true)
@ -514,21 +456,9 @@ func TestCore_Remount(t *testing.T) {
t.Fatalf("failed remount")
}
inmemSink := metrics.NewInmemSink(1000000*time.Hour, 2000000*time.Hour)
conf := &CoreConfig{
Physical: c.physical,
DisableMlock: true,
BuiltinRegistry: NewMockBuiltinRegistry(),
MetricSink: metricsutil.NewClusterMetricSink("test-cluster", inmemSink),
MetricsHelper: metricsutil.NewMetricsHelper(inmemSink, false),
}
c2, err := NewCore(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
defer c2.Shutdown()
c.sealInternal()
for i, key := range keys {
unseal, err := TestCoreUnseal(c2, key)
unseal, err := TestCoreUnseal(c, key)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -537,20 +467,9 @@ func TestCore_Remount(t *testing.T) {
}
}
// Verify matching mount tables
if c.mounts.Type != c2.mounts.Type {
t.Fatal("types don't match")
}
cMountMap := map[string]interface{}{}
for _, v := range c.mounts.Entries {
cMountMap[v.Path] = v
}
c2MountMap := map[string]interface{}{}
for _, v := range c2.mounts.Entries {
c2MountMap[v.Path] = v
}
if diff := deep.Equal(cMountMap, c2MountMap); diff != nil {
t.Fatal(diff)
match = c.router.MatchingMount(namespace.RootContext(nil), "foo/bar")
if match != "foo/" {
t.Fatalf("failed remount")
}
}