Skip to content

Commit

Permalink
More rep porting (#2391)
Browse files Browse the repository at this point in the history
* More rep porting

* Add a bit more porting
  • Loading branch information
jefferai authored Feb 17, 2017
1 parent 2901591 commit 5780c8e
Show file tree
Hide file tree
Showing 14 changed files with 418 additions and 103 deletions.
92 changes: 79 additions & 13 deletions vault/auth.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package vault

import (
"encoding/json"
"errors"
"fmt"
"strings"
Expand All @@ -17,6 +16,10 @@ const (
// can only be viewed or modified after an unseal.
coreAuthConfigPath = "core/auth"

// coreLocalAuthConfigPath is used to store credential configuration for
// local (non-replicated) mounts
coreLocalAuthConfigPath = "core/local-auth"

// credentialBarrierPrefix is the prefix to the UUID used in the
// barrier view for the credential backends.
credentialBarrierPrefix = "auth/"
Expand Down Expand Up @@ -71,19 +74,28 @@ func (c *Core) enableCredential(entry *MountEntry) error {
}

// Generate a new UUID and view
entryUUID, err := uuid.GenerateUUID()
if err != nil {
return err
if entry.UUID == "" {
entryUUID, err := uuid.GenerateUUID()
if err != nil {
return err
}
entry.UUID = entryUUID
}
entry.UUID = entryUUID
view := NewBarrierView(c.barrier, credentialBarrierPrefix+entry.UUID+"/")

viewPath := credentialBarrierPrefix + entry.UUID + "/"
view := NewBarrierView(c.barrier, viewPath)
sysView := c.mountEntrySysView(entry)

// Create the new backend
backend, err := c.newCredentialBackend(entry.Type, c.mountEntrySysView(entry), view, nil)
backend, err := c.newCredentialBackend(entry.Type, sysView, view, nil)
if err != nil {
return err
}

if err := backend.Initialize(); err != nil {
return err
}

// Update the auth table
newTable := c.auth.shallowClone()
newTable.Entries = append(newTable.Entries, entry)
Expand Down Expand Up @@ -121,7 +133,7 @@ func (c *Core) disableCredential(path string) (bool, error) {
fullPath := credentialRoutePrefix + path
view := c.router.MatchingStorageView(fullPath)
if view == nil {
return false, fmt.Errorf("no matching backend")
return false, fmt.Errorf("no matching backend %s", fullPath)
}

// Mark the entry as tainted
Expand Down Expand Up @@ -206,12 +218,19 @@ func (c *Core) taintCredEntry(path string) error {
// loadCredentials is invoked as part of postUnseal to load the auth table
func (c *Core) loadCredentials() error {
authTable := &MountTable{}
localAuthTable := &MountTable{}

// Load the existing mount table
raw, err := c.barrier.Get(coreAuthConfigPath)
if err != nil {
c.logger.Error("core: failed to read auth table", "error", err)
return errLoadAuthFailed
}
rawLocal, err := c.barrier.Get(coreLocalAuthConfigPath)
if err != nil {
c.logger.Error("core: failed to read local auth table", "error", err)
return errLoadAuthFailed
}

c.authLock.Lock()
defer c.authLock.Unlock()
Expand All @@ -223,6 +242,13 @@ func (c *Core) loadCredentials() error {
}
c.auth = authTable
}
if rawLocal != nil {
if err := jsonutil.DecodeJSON(rawLocal.Value, localAuthTable); err != nil {
c.logger.Error("core: failed to decode local auth table", "error", err)
return errLoadAuthFailed
}
c.auth.Entries = append(c.auth.Entries, localAuthTable.Entries...)
}

// Done if we have restored the auth table
if c.auth != nil {
Expand Down Expand Up @@ -272,24 +298,58 @@ func (c *Core) persistAuth(table *MountTable) error {
}
}

nonLocalAuth := &MountTable{
Type: credentialTableType,
}

localAuth := &MountTable{
Type: credentialTableType,
}

for _, entry := range table.Entries {
if entry.Local {
localAuth.Entries = append(localAuth.Entries, entry)
} else {
nonLocalAuth.Entries = append(nonLocalAuth.Entries, entry)
}
}

// Marshal the table
raw, err := json.Marshal(table)
compressedBytes, err := jsonutil.EncodeJSONAndCompress(nonLocalAuth, nil)
if err != nil {
c.logger.Error("core: failed to encode auth table", "error", err)
c.logger.Error("core: failed to encode and/or compress auth table", "error", err)
return err
}

// Create an entry
entry := &Entry{
Key: coreAuthConfigPath,
Value: raw,
Value: compressedBytes,
}

// Write to the physical backend
if err := c.barrier.Put(entry); err != nil {
c.logger.Error("core: failed to persist auth table", "error", err)
return err
}

// Repeat with local auth
compressedBytes, err = jsonutil.EncodeJSONAndCompress(localAuth, nil)
if err != nil {
c.logger.Error("core: failed to encode and/or compress local auth table", "error", err)
return err
}

entry = &Entry{
Key: coreLocalAuthConfigPath,
Value: compressedBytes,
}

if err := c.barrier.Put(entry); err != nil {
c.logger.Error("core: failed to persist local auth table", "error", err)
return err
}

return nil
}

Expand All @@ -312,15 +372,21 @@ func (c *Core) setupCredentials() error {
}

// Create a barrier view using the UUID
view = NewBarrierView(c.barrier, credentialBarrierPrefix+entry.UUID+"/")
viewPath := credentialBarrierPrefix + entry.UUID + "/"
view = NewBarrierView(c.barrier, viewPath)
sysView := c.mountEntrySysView(entry)

// Initialize the backend
backend, err = c.newCredentialBackend(entry.Type, c.mountEntrySysView(entry), view, nil)
backend, err = c.newCredentialBackend(entry.Type, sysView, view, nil)
if err != nil {
c.logger.Error("core: failed to create credential entry", "path", entry.Path, "error", err)
return errLoadAuthFailed
}

if err := backend.Initialize(); err != nil {
return err
}

// Mount the backend
path := credentialRoutePrefix + entry.Path
err = c.router.Mount(backend, path, entry, view)
Expand Down
86 changes: 85 additions & 1 deletion vault/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package vault

import (
"reflect"
"strings"
"testing"

"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
)

Expand Down Expand Up @@ -84,6 +86,88 @@ func TestCore_EnableCredential(t *testing.T) {
}
}

// Test that the local table actually gets populated as expected with local
// entries, and that upon reading the entries from both are recombined
// correctly
func TestCore_EnableCredential_Local(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
c.credentialBackends["noop"] = func(*logical.BackendConfig) (logical.Backend, error) {
return &NoopBackend{}, nil
}

c.auth = &MountTable{
Type: credentialTableType,
Entries: []*MountEntry{
&MountEntry{
Table: credentialTableType,
Path: "noop/",
Type: "noop",
UUID: "abcd",
},
&MountEntry{
Table: credentialTableType,
Path: "noop2/",
Type: "noop",
UUID: "bcde",
},
},
}

// Both should set up successfully
err := c.setupCredentials()
if err != nil {
t.Fatal(err)
}

rawLocal, err := c.barrier.Get(coreLocalAuthConfigPath)
if err != nil {
t.Fatal(err)
}
if rawLocal == nil {
t.Fatal("expected non-nil local credential")
}
localCredentialTable := &MountTable{}
if err := jsonutil.DecodeJSON(rawLocal.Value, localCredentialTable); err != nil {
t.Fatal(err)
}
if len(localCredentialTable.Entries) > 0 {
t.Fatalf("expected no entries in local credential table, got %#v", localCredentialTable)
}

c.auth.Entries[1].Local = true
if err := c.persistAuth(c.auth); err != nil {
t.Fatal(err)
}

rawLocal, err = c.barrier.Get(coreLocalAuthConfigPath)
if err != nil {
t.Fatal(err)
}
if rawLocal == nil {
t.Fatal("expected non-nil local credential")
}
localCredentialTable = &MountTable{}
if err := jsonutil.DecodeJSON(rawLocal.Value, localCredentialTable); err != nil {
t.Fatal(err)
}
if len(localCredentialTable.Entries) != 1 {
t.Fatalf("expected one entry in local credential table, got %#v", localCredentialTable)
}

oldCredential := c.auth
if err := c.loadCredentials(); err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(oldCredential, c.auth) {
t.Fatalf("expected\n%#v\ngot\n%#v\n", oldCredential, c.auth)
}

if len(c.auth.Entries) != 2 {
t.Fatalf("expected two credential entries, got %#v", localCredentialTable)
}
}

func TestCore_EnableCredential_twice_409(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
c.credentialBackends["noop"] = func(*logical.BackendConfig) (logical.Backend, error) {
Expand Down Expand Up @@ -132,7 +216,7 @@ func TestCore_DisableCredential(t *testing.T) {
}

existed, err := c.disableCredential("foo")
if existed || err.Error() != "no matching backend" {
if existed || (err != nil && !strings.HasPrefix(err.Error(), "no matching backend")) {
t.Fatalf("existed: %v; err: %v", existed, err)
}

Expand Down
19 changes: 19 additions & 0 deletions vault/barrier.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ type SecurityBarrier interface {
// VerifyMaster is used to check if the given key matches the master key
VerifyMaster(key []byte) error

// SetMasterKey is used to directly set a new master key. This is used in
// repliated scenarios due to the chicken and egg problem of reloading the
// keyring from disk before we have the master key to decrypt it.
SetMasterKey(key []byte) error

// ReloadKeyring is used to re-read the underlying keyring.
// This is used for HA deployments to ensure the latest keyring
// is present in the leader.
Expand Down Expand Up @@ -119,8 +124,14 @@ type SecurityBarrier interface {
// Rekey is used to change the master key used to protect the keyring
Rekey([]byte) error

// For replication we must send over the keyring, so this must be available
Keyring() (*Keyring, error)

// SecurityBarrier must provide the storage APIs
BarrierStorage

// SecurityBarrier must provide the encryption APIs
BarrierEncryptor
}

// BarrierStorage is the storage only interface required for a Barrier.
Expand All @@ -139,6 +150,14 @@ type BarrierStorage interface {
List(prefix string) ([]string, error)
}

// BarrierEncryptor is the in memory only interface that does not actually
// use the underlying barrier. It is used for lower level modules like the
// Write-Ahead-Log and Merkle index to allow them to use the barrier.
type BarrierEncryptor interface {
Encrypt(key string, plaintext []byte) ([]byte, error)
Decrypt(key string, ciphertext []byte) ([]byte, error)
}

// Entry is used to represent data stored by the security barrier
type Entry struct {
Key string
Expand Down
Loading

0 comments on commit 5780c8e

Please sign in to comment.