Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More rep porting #2391

Merged
merged 2 commits into from
Feb 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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