77e7379ab5
In order to implement this efficiently, I have introduced the concept of "singleton" backends -- currently, 'sys' and 'cubbyhole'. There isn't much reason to allow sys to be mounted at multiple places, and there isn't much reason you'd need multiple per-token storage areas. By restricting it to just one, I can store that particular mount instead of iterating through them in order to call the appropriate revoke function. Additionally, because revocation on the backend needs to be triggered by the token store, the token store's salt is kept in the router and client tokens going to the cubbyhole backend are double-salted by the router. This allows the token store to drive when revocation happens using its salted tokens.
257 lines
5.9 KiB
Go
257 lines
5.9 KiB
Go
package vault
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/helper/uuid"
|
|
"github.com/hashicorp/vault/logical"
|
|
)
|
|
|
|
func TestCubbyholeBackend_RootPaths(t *testing.T) {
|
|
b := testCubbyholeBackend()
|
|
root := b.SpecialPaths()
|
|
if root != nil {
|
|
t.Fatalf("unexpected: %v", root)
|
|
}
|
|
}
|
|
|
|
func TestCubbyholeBackend_Write(t *testing.T) {
|
|
b := testCubbyholeBackend()
|
|
req := logical.TestRequest(t, logical.WriteOperation, "foo")
|
|
clientToken := uuid.GenerateUUID()
|
|
req.ClientToken = clientToken
|
|
storage := req.Storage
|
|
req.Data["raw"] = "test"
|
|
|
|
resp, err := b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %v", resp)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.ReadOperation, "foo")
|
|
req.Storage = storage
|
|
req.ClientToken = clientToken
|
|
_, err = b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCubbyholeBackend_Read(t *testing.T) {
|
|
b := testCubbyholeBackend()
|
|
req := logical.TestRequest(t, logical.WriteOperation, "foo")
|
|
req.Data["raw"] = "test"
|
|
storage := req.Storage
|
|
clientToken := uuid.GenerateUUID()
|
|
req.ClientToken = clientToken
|
|
|
|
if _, err := b.HandleRequest(req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.ReadOperation, "foo")
|
|
req.Storage = storage
|
|
req.ClientToken = clientToken
|
|
|
|
resp, err := b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expected := &logical.Response{
|
|
Data: map[string]interface{}{
|
|
"raw": "test",
|
|
},
|
|
}
|
|
|
|
if !reflect.DeepEqual(resp, expected) {
|
|
t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
|
|
}
|
|
}
|
|
|
|
func TestCubbyholeBackend_Delete(t *testing.T) {
|
|
b := testCubbyholeBackend()
|
|
req := logical.TestRequest(t, logical.WriteOperation, "foo")
|
|
req.Data["raw"] = "test"
|
|
storage := req.Storage
|
|
clientToken := uuid.GenerateUUID()
|
|
req.ClientToken = clientToken
|
|
|
|
if _, err := b.HandleRequest(req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.DeleteOperation, "foo")
|
|
req.Storage = storage
|
|
req.ClientToken = clientToken
|
|
resp, err := b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %v", resp)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.ReadOperation, "foo")
|
|
req.Storage = storage
|
|
req.ClientToken = clientToken
|
|
resp, err = b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %v", resp)
|
|
}
|
|
}
|
|
|
|
func TestCubbyholeBackend_List(t *testing.T) {
|
|
b := testCubbyholeBackend()
|
|
req := logical.TestRequest(t, logical.WriteOperation, "foo")
|
|
clientToken := uuid.GenerateUUID()
|
|
req.Data["raw"] = "test"
|
|
req.ClientToken = clientToken
|
|
storage := req.Storage
|
|
|
|
if _, err := b.HandleRequest(req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.WriteOperation, "bar")
|
|
req.Data["raw"] = "baz"
|
|
req.ClientToken = clientToken
|
|
req.Storage = storage
|
|
|
|
if _, err := b.HandleRequest(req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.ListOperation, "")
|
|
req.Storage = storage
|
|
req.ClientToken = clientToken
|
|
resp, err := b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expKeys := []string{"foo", "bar"}
|
|
respKeys := resp.Data["keys"].([]string)
|
|
sort.Strings(expKeys)
|
|
sort.Strings(respKeys)
|
|
if !reflect.DeepEqual(respKeys, expKeys) {
|
|
t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expKeys, respKeys)
|
|
}
|
|
}
|
|
|
|
func TestCubbyholeIsolation(t *testing.T) {
|
|
b := testCubbyholeBackend()
|
|
|
|
clientTokenA := uuid.GenerateUUID()
|
|
clientTokenB := uuid.GenerateUUID()
|
|
var storageA logical.Storage
|
|
var storageB logical.Storage
|
|
|
|
// Populate and test A entries
|
|
req := logical.TestRequest(t, logical.WriteOperation, "foo")
|
|
req.ClientToken = clientTokenA
|
|
storageA = req.Storage
|
|
req.Data["raw"] = "test"
|
|
|
|
resp, err := b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %v", resp)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.ReadOperation, "foo")
|
|
req.Storage = storageA
|
|
req.ClientToken = clientTokenA
|
|
resp, err = b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expected := &logical.Response{
|
|
Data: map[string]interface{}{
|
|
"raw": "test",
|
|
},
|
|
}
|
|
|
|
if !reflect.DeepEqual(resp, expected) {
|
|
t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
|
|
}
|
|
|
|
// Populate and test B entries
|
|
req = logical.TestRequest(t, logical.WriteOperation, "bar")
|
|
req.ClientToken = clientTokenB
|
|
storageB = req.Storage
|
|
req.Data["raw"] = "baz"
|
|
|
|
resp, err = b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %v", resp)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.ReadOperation, "bar")
|
|
req.Storage = storageB
|
|
req.ClientToken = clientTokenB
|
|
resp, err = b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expected = &logical.Response{
|
|
Data: map[string]interface{}{
|
|
"raw": "baz",
|
|
},
|
|
}
|
|
|
|
if !reflect.DeepEqual(resp, expected) {
|
|
t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
|
|
}
|
|
|
|
// We shouldn't be able to read A from B and vice versa
|
|
req = logical.TestRequest(t, logical.ReadOperation, "foo")
|
|
req.Storage = storageB
|
|
req.ClientToken = clientTokenB
|
|
resp, err = b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("err: was able to read from other user's cubbyhole")
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.ReadOperation, "bar")
|
|
req.Storage = storageA
|
|
req.ClientToken = clientTokenA
|
|
resp, err = b.HandleRequest(req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("err: was able to read from other user's cubbyhole")
|
|
}
|
|
}
|
|
|
|
func testCubbyholeBackend() logical.Backend {
|
|
b, _ := CubbyholeBackendFactory(&logical.BackendConfig{
|
|
Logger: nil,
|
|
System: logical.StaticSystemView{
|
|
DefaultLeaseTTLVal: time.Hour * 24,
|
|
MaxLeaseTTLVal: time.Hour * 24 * 30,
|
|
},
|
|
})
|
|
return b
|
|
}
|