2015-03-11 22:33:25 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
2015-04-02 18:47:44 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/vault/logical"
|
2015-03-11 22:33:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestCore_DefaultMountTable(t *testing.T) {
|
2015-03-29 23:18:08 +00:00
|
|
|
c, key, _ := TestCoreUnsealed(t)
|
2015-03-11 22:33:25 +00:00
|
|
|
verifyDefaultTable(t, c.mounts)
|
|
|
|
|
|
|
|
// Start a second core with same physical
|
2015-03-12 17:22:12 +00:00
|
|
|
conf := &CoreConfig{Physical: c.physical}
|
2015-03-11 22:33:25 +00:00
|
|
|
c2, err := NewCore(conf)
|
|
|
|
if err != nil {
|
2015-03-12 01:29:49 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
unseal, err := c2.Unseal(key)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if !unseal {
|
|
|
|
t.Fatalf("should be unsealed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify matching mount tables
|
|
|
|
if !reflect.DeepEqual(c.mounts, c2.mounts) {
|
|
|
|
t.Fatalf("mismatch: %v %v", c.mounts, c2.mounts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
vault: make Mount related core functions public
/cc @armon - So I know the conversation we had related to this about
auth, but I think we still need to export these and do auth only at the
external API layer. If you're writing to the internal API, then all bets
are off.
The reason is simply that if you have access to the code, you can
already work around it anyways (you can disable auth or w/e), so a
compromised Vault source/binary is already a failure, and that is the
only thing that our previous unexported methods were protecting against.
If you write an external tool to access a Vault, it still needs to be
unsealed so _that_ is the primary security mechanism from an API
perspective. Once it is unsealed then the core API has full access to
the Vault, and identity/auth is only done at the external API layer, not
at the internal API layer.
The benefits of this approach is that it lets us still treat the "sys"
mount specially but at least have sys adopt helper/backend and use that
machinery and it can still be the only backend which actually has a
reference to *vault.Core to do core things (a key difference). So, an
AWS backend still will never be able to muck with things it can't, but
we're explicitly giving Sys (via struct initialization in Go itself)
a reference to *vault.Core.
2015-03-15 00:26:59 +00:00
|
|
|
func TestCore_Mount(t *testing.T) {
|
2015-03-29 23:18:08 +00:00
|
|
|
c, key, _ := TestCoreUnsealed(t)
|
2015-03-12 01:29:49 +00:00
|
|
|
me := &MountEntry{
|
|
|
|
Path: "foo",
|
|
|
|
Type: "generic",
|
|
|
|
}
|
2015-03-15 01:31:31 +00:00
|
|
|
err := c.mount(me)
|
2015-03-12 01:29:49 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
match := c.router.MatchingMount("foo/bar")
|
|
|
|
if match != "foo/" {
|
|
|
|
t.Fatalf("missing mount")
|
|
|
|
}
|
|
|
|
|
2015-03-12 17:22:12 +00:00
|
|
|
conf := &CoreConfig{Physical: c.physical}
|
2015-03-12 01:29:49 +00:00
|
|
|
c2, err := NewCore(conf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
unseal, err := c2.Unseal(key)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if !unseal {
|
|
|
|
t.Fatalf("should be unsealed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify matching mount tables
|
|
|
|
if !reflect.DeepEqual(c.mounts, c2.mounts) {
|
|
|
|
t.Fatalf("mismatch: %v %v", c.mounts, c2.mounts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
vault: make Mount related core functions public
/cc @armon - So I know the conversation we had related to this about
auth, but I think we still need to export these and do auth only at the
external API layer. If you're writing to the internal API, then all bets
are off.
The reason is simply that if you have access to the code, you can
already work around it anyways (you can disable auth or w/e), so a
compromised Vault source/binary is already a failure, and that is the
only thing that our previous unexported methods were protecting against.
If you write an external tool to access a Vault, it still needs to be
unsealed so _that_ is the primary security mechanism from an API
perspective. Once it is unsealed then the core API has full access to
the Vault, and identity/auth is only done at the external API layer, not
at the internal API layer.
The benefits of this approach is that it lets us still treat the "sys"
mount specially but at least have sys adopt helper/backend and use that
machinery and it can still be the only backend which actually has a
reference to *vault.Core to do core things (a key difference). So, an
AWS backend still will never be able to muck with things it can't, but
we're explicitly giving Sys (via struct initialization in Go itself)
a reference to *vault.Core.
2015-03-15 00:26:59 +00:00
|
|
|
func TestCore_Unmount(t *testing.T) {
|
2015-03-29 23:18:08 +00:00
|
|
|
c, key, _ := TestCoreUnsealed(t)
|
2015-03-15 01:31:31 +00:00
|
|
|
err := c.unmount("secret")
|
2015-03-12 01:29:49 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
match := c.router.MatchingMount("secret/foo")
|
|
|
|
if match != "" {
|
|
|
|
t.Fatalf("backend present")
|
|
|
|
}
|
|
|
|
|
2015-03-12 17:22:12 +00:00
|
|
|
conf := &CoreConfig{Physical: c.physical}
|
2015-03-12 01:29:49 +00:00
|
|
|
c2, err := NewCore(conf)
|
|
|
|
if err != nil {
|
2015-03-11 22:33:25 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
unseal, err := c2.Unseal(key)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if !unseal {
|
|
|
|
t.Fatalf("should be unsealed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify matching mount tables
|
|
|
|
if !reflect.DeepEqual(c.mounts, c2.mounts) {
|
|
|
|
t.Fatalf("mismatch: %v %v", c.mounts, c2.mounts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-02 18:47:44 +00:00
|
|
|
func TestCore_Unmount_Cleanup(t *testing.T) {
|
|
|
|
noop := &NoopBackend{}
|
|
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
c.logicalBackends["noop"] = func(map[string]string) (logical.Backend, error) {
|
|
|
|
return noop, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mount the noop backend
|
|
|
|
me := &MountEntry{
|
|
|
|
Path: "test/",
|
|
|
|
Type: "noop",
|
|
|
|
}
|
|
|
|
if err := c.mount(me); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the view
|
|
|
|
view := c.router.MatchingView("test/")
|
|
|
|
|
|
|
|
// Inject data
|
|
|
|
se := &logical.StorageEntry{
|
|
|
|
Key: "plstodelete",
|
|
|
|
Value: []byte("test"),
|
|
|
|
}
|
|
|
|
if err := view.Put(se); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup response
|
|
|
|
resp := &logical.Response{
|
|
|
|
Secret: &logical.Secret{
|
2015-04-09 19:14:04 +00:00
|
|
|
LeaseOptions: logical.LeaseOptions{
|
|
|
|
Lease: time.Hour,
|
|
|
|
},
|
2015-04-02 18:47:44 +00:00
|
|
|
},
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
noop.Response = resp
|
|
|
|
|
|
|
|
// Generate leased secret
|
|
|
|
r := &logical.Request{
|
|
|
|
Operation: logical.ReadOperation,
|
|
|
|
Path: "test/foo",
|
|
|
|
ClientToken: root,
|
|
|
|
}
|
|
|
|
resp, err := c.HandleRequest(r)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-04-08 20:35:32 +00:00
|
|
|
if resp.Secret.LeaseID == "" {
|
2015-04-02 18:47:44 +00:00
|
|
|
t.Fatalf("bad: %#v", resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmount, this should cleanup
|
|
|
|
if err := c.unmount("test/"); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rollback should be invoked
|
|
|
|
if noop.Requests[1].Operation != logical.RollbackOperation {
|
|
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Revoke should be invoked
|
|
|
|
if noop.Requests[2].Operation != logical.RevokeOperation {
|
|
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
|
|
}
|
|
|
|
if noop.Requests[2].Path != "foo" {
|
|
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
|
|
}
|
|
|
|
|
|
|
|
// View should be empty
|
|
|
|
out, err := CollectKeys(view)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(out) != 0 {
|
|
|
|
t.Fatalf("bad: %#v", out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
vault: make Mount related core functions public
/cc @armon - So I know the conversation we had related to this about
auth, but I think we still need to export these and do auth only at the
external API layer. If you're writing to the internal API, then all bets
are off.
The reason is simply that if you have access to the code, you can
already work around it anyways (you can disable auth or w/e), so a
compromised Vault source/binary is already a failure, and that is the
only thing that our previous unexported methods were protecting against.
If you write an external tool to access a Vault, it still needs to be
unsealed so _that_ is the primary security mechanism from an API
perspective. Once it is unsealed then the core API has full access to
the Vault, and identity/auth is only done at the external API layer, not
at the internal API layer.
The benefits of this approach is that it lets us still treat the "sys"
mount specially but at least have sys adopt helper/backend and use that
machinery and it can still be the only backend which actually has a
reference to *vault.Core to do core things (a key difference). So, an
AWS backend still will never be able to muck with things it can't, but
we're explicitly giving Sys (via struct initialization in Go itself)
a reference to *vault.Core.
2015-03-15 00:26:59 +00:00
|
|
|
func TestCore_Remount(t *testing.T) {
|
2015-03-29 23:18:08 +00:00
|
|
|
c, key, _ := TestCoreUnsealed(t)
|
2015-03-15 01:31:31 +00:00
|
|
|
err := c.remount("secret", "foo")
|
2015-03-12 19:09:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
match := c.router.MatchingMount("foo/bar")
|
|
|
|
if match != "foo/" {
|
|
|
|
t.Fatalf("failed remount")
|
|
|
|
}
|
|
|
|
|
|
|
|
conf := &CoreConfig{Physical: c.physical}
|
|
|
|
c2, err := NewCore(conf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
unseal, err := c2.Unseal(key)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if !unseal {
|
|
|
|
t.Fatalf("should be unsealed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify matching mount tables
|
|
|
|
if !reflect.DeepEqual(c.mounts, c2.mounts) {
|
|
|
|
t.Fatalf("mismatch: %v %v", c.mounts, c2.mounts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-02 19:04:37 +00:00
|
|
|
func TestCore_Remount_Cleanup(t *testing.T) {
|
|
|
|
noop := &NoopBackend{}
|
|
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
c.logicalBackends["noop"] = func(map[string]string) (logical.Backend, error) {
|
|
|
|
return noop, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mount the noop backend
|
|
|
|
me := &MountEntry{
|
|
|
|
Path: "test/",
|
|
|
|
Type: "noop",
|
|
|
|
}
|
|
|
|
if err := c.mount(me); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the view
|
|
|
|
view := c.router.MatchingView("test/")
|
|
|
|
|
|
|
|
// Inject data
|
|
|
|
se := &logical.StorageEntry{
|
|
|
|
Key: "plstokeep",
|
|
|
|
Value: []byte("test"),
|
|
|
|
}
|
|
|
|
if err := view.Put(se); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup response
|
|
|
|
resp := &logical.Response{
|
|
|
|
Secret: &logical.Secret{
|
2015-04-09 19:14:04 +00:00
|
|
|
LeaseOptions: logical.LeaseOptions{
|
|
|
|
Lease: time.Hour,
|
|
|
|
},
|
2015-04-02 19:04:37 +00:00
|
|
|
},
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
noop.Response = resp
|
|
|
|
|
|
|
|
// Generate leased secret
|
|
|
|
r := &logical.Request{
|
|
|
|
Operation: logical.ReadOperation,
|
|
|
|
Path: "test/foo",
|
|
|
|
ClientToken: root,
|
|
|
|
}
|
|
|
|
resp, err := c.HandleRequest(r)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-04-08 20:35:32 +00:00
|
|
|
if resp.Secret.LeaseID == "" {
|
2015-04-02 19:04:37 +00:00
|
|
|
t.Fatalf("bad: %#v", resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remount, this should cleanup
|
|
|
|
if err := c.remount("test/", "new/"); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rollback should be invoked
|
|
|
|
if noop.Requests[1].Operation != logical.RollbackOperation {
|
|
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Revoke should be invoked
|
|
|
|
if noop.Requests[2].Operation != logical.RevokeOperation {
|
|
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
|
|
}
|
|
|
|
if noop.Requests[2].Path != "foo" {
|
|
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
|
|
}
|
|
|
|
|
|
|
|
// View should not be empty
|
|
|
|
out, err := CollectKeys(view)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(out) != 1 && out[0] != "plstokeep" {
|
|
|
|
t.Fatalf("bad: %#v", out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
vault: make Mount related core functions public
/cc @armon - So I know the conversation we had related to this about
auth, but I think we still need to export these and do auth only at the
external API layer. If you're writing to the internal API, then all bets
are off.
The reason is simply that if you have access to the code, you can
already work around it anyways (you can disable auth or w/e), so a
compromised Vault source/binary is already a failure, and that is the
only thing that our previous unexported methods were protecting against.
If you write an external tool to access a Vault, it still needs to be
unsealed so _that_ is the primary security mechanism from an API
perspective. Once it is unsealed then the core API has full access to
the Vault, and identity/auth is only done at the external API layer, not
at the internal API layer.
The benefits of this approach is that it lets us still treat the "sys"
mount specially but at least have sys adopt helper/backend and use that
machinery and it can still be the only backend which actually has a
reference to *vault.Core to do core things (a key difference). So, an
AWS backend still will never be able to muck with things it can't, but
we're explicitly giving Sys (via struct initialization in Go itself)
a reference to *vault.Core.
2015-03-15 00:26:59 +00:00
|
|
|
func TestCore_Remount_Protected(t *testing.T) {
|
2015-03-29 23:18:08 +00:00
|
|
|
c, _, _ := TestCoreUnsealed(t)
|
2015-03-15 01:31:31 +00:00
|
|
|
err := c.remount("sys", "foo")
|
2015-03-12 19:09:30 +00:00
|
|
|
if err.Error() != "cannot remount 'sys/'" {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-11 22:33:25 +00:00
|
|
|
func TestDefaultMountTable(t *testing.T) {
|
|
|
|
table := defaultMountTable()
|
|
|
|
verifyDefaultTable(t, table)
|
|
|
|
}
|
|
|
|
|
|
|
|
func verifyDefaultTable(t *testing.T, table *MountTable) {
|
|
|
|
if len(table.Entries) != 2 {
|
|
|
|
t.Fatalf("bad: %v", table.Entries)
|
|
|
|
}
|
|
|
|
for idx, entry := range table.Entries {
|
|
|
|
switch idx {
|
|
|
|
case 0:
|
|
|
|
if entry.Path != "secret/" {
|
|
|
|
t.Fatalf("bad: %v", entry)
|
|
|
|
}
|
|
|
|
if entry.Type != "generic" {
|
|
|
|
t.Fatalf("bad: %v", entry)
|
|
|
|
}
|
|
|
|
case 1:
|
|
|
|
if entry.Path != "sys/" {
|
|
|
|
t.Fatalf("bad: %v", entry)
|
|
|
|
}
|
|
|
|
if entry.Type != "system" {
|
|
|
|
t.Fatalf("bad: %v", entry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if entry.Description == "" {
|
|
|
|
t.Fatalf("bad: %v", entry)
|
|
|
|
}
|
|
|
|
if entry.UUID == "" {
|
|
|
|
t.Fatalf("bad: %v", entry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|