Make mount view read only until after mount persist (#3910)

This commit is contained in:
Vishal Nayak 2018-02-09 14:04:25 -05:00 committed by Jeff Mitchell
parent bd3cdd8095
commit bf66dc2841
10 changed files with 142 additions and 11 deletions

View File

@ -14,6 +14,10 @@ import (
// cluster operation. // cluster operation.
var ErrReadOnly = errors.New("Cannot write to readonly storage") var ErrReadOnly = errors.New("Cannot write to readonly storage")
// ErrSetupReadOnly is returned when a write operation is attempted on a
// storage while the backend is still being setup.
var ErrSetupReadOnly = errors.New("Cannot write to storage during setup")
// Storage is the way that logical backends are able read/write data. // Storage is the way that logical backends are able read/write data.
type Storage interface { type Storage interface {
List(context.Context, string) ([]string, error) List(context.Context, string) ([]string, error)

View File

@ -90,6 +90,12 @@ func (c *Core) enableAudit(ctx context.Context, entry *MountEntry) error {
viewPath := auditBarrierPrefix + entry.UUID + "/" viewPath := auditBarrierPrefix + entry.UUID + "/"
view := NewBarrierView(c.barrier, viewPath) view := NewBarrierView(c.barrier, viewPath)
// Mark the view as read-only until the mounting is complete and
// ensure that it is reset after. This ensures that there will be no
// writes during the construction of the backend.
view.setReadOnlyErr(logical.ErrSetupReadOnly)
defer view.setReadOnlyErr(nil)
// Lookup the new backend // Lookup the new backend
backend, err := c.newAuditBackend(ctx, entry, view, entry.Options) backend, err := c.newAuditBackend(ctx, entry, view, entry.Options)
if err != nil { if err != nil {
@ -320,14 +326,21 @@ func (c *Core) setupAudits(ctx context.Context) error {
viewPath := auditBarrierPrefix + entry.UUID + "/" viewPath := auditBarrierPrefix + entry.UUID + "/"
view := NewBarrierView(c.barrier, viewPath) view := NewBarrierView(c.barrier, viewPath)
// Mark the view as read-only until the mounting is complete and
// ensure that it is reset after. This ensures that there will be no
// writes during the construction of the backend.
view.setReadOnlyErr(logical.ErrSetupReadOnly)
// Initialize the backend // Initialize the backend
backend, err := c.newAuditBackend(ctx, entry, view, entry.Options) backend, err := c.newAuditBackend(ctx, entry, view, entry.Options)
if err != nil { if err != nil {
c.logger.Error("core: failed to create audit entry", "path", entry.Path, "error", err) c.logger.Error("core: failed to create audit entry", "path", entry.Path, "error", err)
view.setReadOnlyErr(nil)
continue continue
} }
if backend == nil { if backend == nil {
c.logger.Error("core: created audit entry was nil", "path", entry.Path, "type", entry.Type) c.logger.Error("core: created audit entry was nil", "path", entry.Path, "type", entry.Type)
view.setReadOnlyErr(nil)
continue continue
} }
@ -335,6 +348,8 @@ func (c *Core) setupAudits(ctx context.Context) error {
broker.Register(entry.Path, backend, view) broker.Register(entry.Path, backend, view)
successCount += 1 successCount += 1
view.setReadOnlyErr(nil)
} }
if len(c.audit.Entries) > 0 && successCount == 0 { if len(c.audit.Entries) > 0 && successCount == 0 {

View File

@ -94,6 +94,30 @@ func (n *NoopAudit) Invalidate(ctx context.Context) {
n.salt = nil n.salt = nil
} }
func TestAudit_ReadOnlyViewDuringMount(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
err := config.SaltView.Put(ctx, &logical.StorageEntry{
Key: "bar",
Value: []byte("baz"),
})
if err == nil || !strings.Contains(err.Error(), logical.ErrSetupReadOnly.Error()) {
t.Fatalf("expected a read-only error")
}
return &NoopAudit{}, nil
}
me := &MountEntry{
Table: auditTableType,
Path: "foo",
Type: "noop",
}
err := c.enableAudit(context.Background(), me)
if err != nil {
t.Fatalf("err: %v", err)
}
}
func TestCore_EnableAudit(t *testing.T) { func TestCore_EnableAudit(t *testing.T) {
c, keys, _ := TestCoreUnsealed(t) c, keys, _ := TestCoreUnsealed(t)
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) { c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {

View File

@ -96,6 +96,13 @@ func (c *Core) enableCredential(ctx context.Context, entry *MountEntry) error {
} }
viewPath := credentialBarrierPrefix + entry.UUID + "/" viewPath := credentialBarrierPrefix + entry.UUID + "/"
view := NewBarrierView(c.barrier, viewPath) view := NewBarrierView(c.barrier, viewPath)
// Mark the view as read-only until the mounting is complete and
// ensure that it is reset after. This ensures that there will be no
// writes during the construction of the backend.
view.setReadOnlyErr(logical.ErrSetupReadOnly)
defer view.setReadOnlyErr(nil)
var err error var err error
var backend logical.Backend var backend logical.Backend
sysView := c.mountEntrySysView(entry) sysView := c.mountEntrySysView(entry)
@ -446,6 +453,12 @@ func (c *Core) setupCredentials(ctx context.Context) error {
// Create a barrier view using the UUID // Create a barrier view using the UUID
viewPath := credentialBarrierPrefix + entry.UUID + "/" viewPath := credentialBarrierPrefix + entry.UUID + "/"
view = NewBarrierView(c.barrier, viewPath) view = NewBarrierView(c.barrier, viewPath)
// Mark the view as read-only until the mounting is complete and
// ensure that it is reset after. This ensures that there will be no
// writes during the construction of the backend.
view.setReadOnlyErr(logical.ErrSetupReadOnly)
// Initialize the backend // Initialize the backend
sysView := c.mountEntrySysView(entry) sysView := c.mountEntrySysView(entry)
conf := make(map[string]string) conf := make(map[string]string)
@ -463,15 +476,18 @@ func (c *Core) setupCredentials(ctx context.Context) error {
c.logger.Warn("core: skipping plugin-based credential entry", "path", entry.Path) c.logger.Warn("core: skipping plugin-based credential entry", "path", entry.Path)
goto ROUTER_MOUNT goto ROUTER_MOUNT
} }
view.setReadOnlyErr(nil)
return errLoadAuthFailed return errLoadAuthFailed
} }
if backend == nil { if backend == nil {
view.setReadOnlyErr(nil)
return fmt.Errorf("nil backend returned from %q factory", entry.Type) return fmt.Errorf("nil backend returned from %q factory", entry.Type)
} }
// Check for the correct backend type // Check for the correct backend type
backendType = backend.Type() backendType = backend.Type()
if entry.Type == "plugin" && backendType != logical.TypeCredential { if entry.Type == "plugin" && backendType != logical.TypeCredential {
view.setReadOnlyErr(nil)
return fmt.Errorf("cannot mount '%s' of type '%s' as an auth backend", entry.Config.PluginName, backendType) return fmt.Errorf("cannot mount '%s' of type '%s' as an auth backend", entry.Config.PluginName, backendType)
} }
@ -480,6 +496,7 @@ func (c *Core) setupCredentials(ctx context.Context) error {
path := credentialRoutePrefix + entry.Path path := credentialRoutePrefix + entry.Path
err = c.router.Mount(backend, path, entry, view) err = c.router.Mount(backend, path, entry, view)
if err != nil { if err != nil {
view.setReadOnlyErr(nil)
c.logger.Error("core: failed to mount auth entry", "path", entry.Path, "error", err) c.logger.Error("core: failed to mount auth entry", "path", entry.Path, "error", err)
return errLoadAuthFailed return errLoadAuthFailed
} }
@ -497,6 +514,8 @@ func (c *Core) setupCredentials(ctx context.Context) error {
c.router.tokenStoreSaltFunc = c.tokenStore.Salt c.router.tokenStoreSaltFunc = c.tokenStore.Salt
c.tokenStore.cubbyholeBackend = c.router.MatchingBackend("cubbyhole/").(*CubbyholeBackend) c.tokenStore.cubbyholeBackend = c.router.MatchingBackend("cubbyhole/").(*CubbyholeBackend)
} }
view.setReadOnlyErr(nil)
} }
if persistNeeded { if persistNeeded {

View File

@ -10,6 +10,30 @@ import (
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
) )
func TestAuth_ReadOnlyViewDuringMount(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
c.credentialBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
err := config.StorageView.Put(ctx, &logical.StorageEntry{
Key: "bar",
Value: []byte("baz"),
})
if err == nil || !strings.Contains(err.Error(), logical.ErrSetupReadOnly.Error()) {
t.Fatalf("expected a read-only error")
}
return &NoopBackend{}, nil
}
me := &MountEntry{
Table: credentialTableType,
Path: "foo",
Type: "noop",
}
err := c.enableCredential(context.Background(), me)
if err != nil {
t.Fatalf("err: %v", err)
}
}
func TestCore_DefaultAuthTable(t *testing.T) { func TestCore_DefaultAuthTable(t *testing.T) {
c, keys, _ := TestCoreUnsealed(t) c, keys, _ := TestCoreUnsealed(t)
verifyDefaultAuthTable(t, c.auth) verifyDefaultAuthTable(t, c.auth)

View File

@ -18,7 +18,7 @@ import (
type BarrierView struct { type BarrierView struct {
barrier BarrierStorage barrier BarrierStorage
prefix string prefix string
readonly bool readOnlyErr error
} }
var ( var (
@ -34,6 +34,10 @@ func NewBarrierView(barrier BarrierStorage, prefix string) *BarrierView {
} }
} }
func (v *BarrierView) setReadOnlyErr(readOnlyErr error) {
v.readOnlyErr = readOnlyErr
}
// sanityCheck is used to perform a sanity check on a key // sanityCheck is used to perform a sanity check on a key
func (v *BarrierView) sanityCheck(key string) error { func (v *BarrierView) sanityCheck(key string) error {
if strings.Contains(key, "..") { if strings.Contains(key, "..") {
@ -81,8 +85,8 @@ func (v *BarrierView) Put(ctx context.Context, entry *logical.StorageEntry) erro
expandedKey := v.expandKey(entry.Key) expandedKey := v.expandKey(entry.Key)
if v.readonly { if v.readOnlyErr != nil {
return logical.ErrReadOnly return v.readOnlyErr
} }
nested := &Entry{ nested := &Entry{
@ -101,8 +105,8 @@ func (v *BarrierView) Delete(ctx context.Context, key string) error {
expandedKey := v.expandKey(key) expandedKey := v.expandKey(key)
if v.readonly { if v.readOnlyErr != nil {
return logical.ErrReadOnly return v.readOnlyErr
} }
return v.barrier.Delete(ctx, expandedKey) return v.barrier.Delete(ctx, expandedKey)
@ -111,7 +115,7 @@ func (v *BarrierView) Delete(ctx context.Context, key string) error {
// SubView constructs a nested sub-view using the given prefix // SubView constructs a nested sub-view using the given prefix
func (v *BarrierView) SubView(prefix string) *BarrierView { func (v *BarrierView) SubView(prefix string) *BarrierView {
sub := v.expandKey(prefix) sub := v.expandKey(prefix)
return &BarrierView{barrier: v.barrier, prefix: sub, readonly: v.readonly} return &BarrierView{barrier: v.barrier, prefix: sub, readOnlyErr: v.readOnlyErr}
} }
// expandKey is used to expand to the full key path with the prefix // expandKey is used to expand to the full key path with the prefix

View File

@ -294,7 +294,7 @@ func TestBarrierView_Readonly(t *testing.T) {
} }
// Enable read only mode // Enable read only mode
view.readonly = true view.readOnlyErr = logical.ErrReadOnly
// Put should fail in readonly mode // Put should fail in readonly mode
if err := view.Put(context.Background(), entry.Logical()); err != logical.ErrReadOnly { if err := view.Put(context.Background(), entry.Logical()); err != logical.ErrReadOnly {

View File

@ -247,6 +247,13 @@ func (c *Core) mountInternal(ctx context.Context, entry *MountEntry) error {
} }
viewPath := backendBarrierPrefix + entry.UUID + "/" viewPath := backendBarrierPrefix + entry.UUID + "/"
view := NewBarrierView(c.barrier, viewPath) view := NewBarrierView(c.barrier, viewPath)
// Mark the view as read-only until the mounting is complete and
// ensure that it is reset after. This ensures that there will be no
// writes during the construction of the backend.
view.setReadOnlyErr(logical.ErrSetupReadOnly)
defer view.setReadOnlyErr(nil)
var backend logical.Backend var backend logical.Backend
var err error var err error
sysView := c.mountEntrySysView(entry) sysView := c.mountEntrySysView(entry)
@ -735,6 +742,11 @@ func (c *Core) setupMounts(ctx context.Context) error {
// Create a barrier view using the UUID // Create a barrier view using the UUID
view = NewBarrierView(c.barrier, barrierPath) view = NewBarrierView(c.barrier, barrierPath)
// Mark the view as read-only until the mounting is complete and
// ensure that it is reset after. This ensures that there will be no
// writes during the construction of the backend.
view.setReadOnlyErr(logical.ErrSetupReadOnly)
var backend logical.Backend var backend logical.Backend
var err error var err error
sysView := c.mountEntrySysView(entry) sysView := c.mountEntrySysView(entry)
@ -754,15 +766,18 @@ func (c *Core) setupMounts(ctx context.Context) error {
c.logger.Warn("core: skipping plugin-based mount entry", "path", entry.Path) c.logger.Warn("core: skipping plugin-based mount entry", "path", entry.Path)
goto ROUTER_MOUNT goto ROUTER_MOUNT
} }
view.setReadOnlyErr(nil)
return errLoadMountsFailed return errLoadMountsFailed
} }
if backend == nil { if backend == nil {
view.setReadOnlyErr(nil)
return fmt.Errorf("created mount entry of type %q is nil", entry.Type) return fmt.Errorf("created mount entry of type %q is nil", entry.Type)
} }
// Check for the correct backend type // Check for the correct backend type
backendType = backend.Type() backendType = backend.Type()
if entry.Type == "plugin" && backendType != logical.TypeLogical { if entry.Type == "plugin" && backendType != logical.TypeLogical {
view.setReadOnlyErr(nil)
return fmt.Errorf("cannot mount '%s' of type '%s' as a logical backend", entry.Config.PluginName, backendType) return fmt.Errorf("cannot mount '%s' of type '%s' as a logical backend", entry.Config.PluginName, backendType)
} }
@ -772,6 +787,7 @@ func (c *Core) setupMounts(ctx context.Context) error {
// Mount the backend // Mount the backend
err = c.router.Mount(backend, entry.Path, entry, view) err = c.router.Mount(backend, entry.Path, entry, view)
if err != nil { if err != nil {
view.setReadOnlyErr(nil)
c.logger.Error("core: failed to mount entry", "path", entry.Path, "error", err) c.logger.Error("core: failed to mount entry", "path", entry.Path, "error", err)
return errLoadMountsFailed return errLoadMountsFailed
} }
@ -784,6 +800,8 @@ func (c *Core) setupMounts(ctx context.Context) error {
if entry.Tainted { if entry.Tainted {
c.router.Taint(entry.Path) c.router.Taint(entry.Path)
} }
view.setReadOnlyErr(nil)
} }
return nil return nil
} }

View File

@ -14,6 +14,30 @@ import (
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
) )
func TestMount_ReadOnlyViewDuringMount(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
c.logicalBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
err := config.StorageView.Put(ctx, &logical.StorageEntry{
Key: "bar",
Value: []byte("baz"),
})
if err == nil || !strings.Contains(err.Error(), logical.ErrSetupReadOnly.Error()) {
t.Fatalf("expected a read-only error")
}
return &NoopBackend{}, nil
}
me := &MountEntry{
Table: mountTableType,
Path: "foo",
Type: "noop",
}
err := c.mount(context.Background(), me)
if err != nil {
t.Fatalf("err: %v", err)
}
}
func TestCore_DefaultMountTable(t *testing.T) { func TestCore_DefaultMountTable(t *testing.T) {
c, keys, _ := TestCoreUnsealed(t) c, keys, _ := TestCoreUnsealed(t)
verifyDefaultTable(t, c.mounts) verifyDefaultTable(t, c.mounts)

View File

@ -343,9 +343,8 @@ func testTokenStore(t testing.T, c *Core) *TokenStore {
// mounted, so that logical token functions can be used // mounted, so that logical token functions can be used
func TestCoreWithTokenStore(t testing.T) (*Core, *TokenStore, [][]byte, string) { func TestCoreWithTokenStore(t testing.T) (*Core, *TokenStore, [][]byte, string) {
c, keys, root := TestCoreUnsealed(t) c, keys, root := TestCoreUnsealed(t)
ts := testTokenStore(t, c)
return c, ts, keys, root return c, c.tokenStore, keys, root
} }
// TestCoreWithBackendTokenStore returns a core that has a token store // TestCoreWithBackendTokenStore returns a core that has a token store