open-vault/vault/token_store_test.go
Jeff Mitchell 8916f6b625
Some atomic cleanup (#4732)
Taking inspiration from
https://github.com/golang/go/issues/17604#issuecomment-256384471
suggests that taking the address of a stack variable for use in atomics
works (at least, the race detector doesn't complain) but is doing it
wrong.

The only other change is a change in Leader() detecting if HA is enabled
to fast-path out. This value never changes after NewCore, so we don't
need to grab the read lock to check it.
2018-06-09 15:35:22 -04:00

4076 lines
109 KiB
Go

package vault
import (
"context"
"encoding/json"
"fmt"
"path"
"reflect"
"sort"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/locksutil"
"github.com/hashicorp/vault/logical"
)
type TokenEntryOld struct {
ID string
Accessor string
Parent string
Policies []string
Path string
Meta map[string]string
DisplayName string
NumUses int
CreationTime int64
TTL time.Duration
ExplicitMaxTTL time.Duration
Role string
Period time.Duration
}
func TestTokenStore_TokenEntryUpgrade(t *testing.T) {
var err error
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
// Use a struct that does not have struct tags to store the items and
// check if the lookup code handles them properly while reading back
entry := &TokenEntryOld{
DisplayName: "test-display-name",
Path: "test",
Policies: []string{"dev", "ops"},
CreationTime: time.Now().Unix(),
ExplicitMaxTTL: 100,
NumUses: 10,
}
entry.ID, err = uuid.GenerateUUID()
if err != nil {
t.Fatal(err)
}
enc, err := json.Marshal(entry)
if err != nil {
t.Fatal(err)
}
saltedID, err := ts.SaltID(context.Background(), entry.ID)
if err != nil {
t.Fatal(err)
}
path := lookupPrefix + saltedID
le := &logical.StorageEntry{
Key: path,
Value: enc,
}
if err := ts.view.Put(context.Background(), le); err != nil {
t.Fatal(err)
}
// Register with exp manager so lookup works
auth := &logical.Auth{
DisplayName: entry.DisplayName,
CreationPath: entry.Path,
Policies: entry.Policies,
ExplicitMaxTTL: entry.ExplicitMaxTTL,
NumUses: entry.NumUses,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
},
ClientToken: entry.ID,
}
if err := ts.expiration.RegisterAuth(entry.Path, auth); err != nil {
t.Fatal(err)
}
out, err := ts.Lookup(context.Background(), entry.ID)
if err != nil {
t.Fatalf("err: %s", err)
}
if out.DisplayName != "test-display-name" {
t.Fatalf("bad: display_name: expected: test-display-name, actual: %s", out.DisplayName)
}
if out.CreationTime == 0 {
t.Fatal("bad: expected a non-zero creation time")
}
if out.ExplicitMaxTTL != 100 {
t.Fatalf("bad: explicit_max_ttl: expected: 100, actual: %d", out.ExplicitMaxTTL)
}
if out.NumUses != 10 {
t.Fatalf("bad: num_uses: expected: 10, actual: %d", out.NumUses)
}
// Test the default case to ensure there are no regressions
ent := &logical.TokenEntry{
DisplayName: "test-display-name",
Path: "test",
Policies: []string{"dev", "ops"},
CreationTime: time.Now().Unix(),
ExplicitMaxTTL: 100,
NumUses: 10,
}
if err := ts.create(context.Background(), ent); err != nil {
t.Fatalf("err: %s", err)
}
auth = &logical.Auth{
DisplayName: ent.DisplayName,
CreationPath: ent.Path,
Policies: ent.Policies,
ExplicitMaxTTL: ent.ExplicitMaxTTL,
NumUses: ent.NumUses,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
},
ClientToken: ent.ID,
}
if err := ts.expiration.RegisterAuth(ent.Path, auth); err != nil {
t.Fatal(err)
}
out, err = ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %s", err)
}
if out.DisplayName != "test-display-name" {
t.Fatalf("bad: display_name: expected: test-display-name, actual: %s", out.DisplayName)
}
if out.CreationTime == 0 {
t.Fatal("bad: expected a non-zero creation time")
}
if out.ExplicitMaxTTL != 100 {
t.Fatalf("bad: explicit_max_ttl: expected: 100, actual: %d", out.ExplicitMaxTTL)
}
if out.NumUses != 10 {
t.Fatalf("bad: num_uses: expected: 10, actual: %d", out.NumUses)
}
// Fill in the deprecated fields and read out from proper fields
ent = &logical.TokenEntry{
Path: "test",
Policies: []string{"dev", "ops"},
DisplayNameDeprecated: "test-display-name",
CreationTimeDeprecated: time.Now().Unix(),
ExplicitMaxTTLDeprecated: 100,
NumUsesDeprecated: 10,
}
if err := ts.create(context.Background(), ent); err != nil {
t.Fatalf("err: %s", err)
}
auth = &logical.Auth{
DisplayName: ent.DisplayName,
CreationPath: ent.Path,
Policies: ent.Policies,
ExplicitMaxTTL: ent.ExplicitMaxTTL,
NumUses: ent.NumUses,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
},
ClientToken: ent.ID,
}
if err := ts.expiration.RegisterAuth(ent.Path, auth); err != nil {
t.Fatal(err)
}
out, err = ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %s", err)
}
if out.DisplayName != "test-display-name" {
t.Fatalf("bad: display_name: expected: test-display-name, actual: %s", out.DisplayName)
}
if out.CreationTime == 0 {
t.Fatal("bad: expected a non-zero creation time")
}
if out.ExplicitMaxTTL != 100 {
t.Fatalf("bad: explicit_max_ttl: expected: 100, actual: %d", out.ExplicitMaxTTL)
}
if out.NumUses != 10 {
t.Fatalf("bad: num_uses: expected: 10, actual: %d", out.NumUses)
}
// Check if NumUses picks up a lower value
ent = &logical.TokenEntry{
Path: "test",
NumUses: 5,
NumUsesDeprecated: 10,
}
if err := ts.create(context.Background(), ent); err != nil {
t.Fatalf("err: %s", err)
}
auth = &logical.Auth{
DisplayName: ent.DisplayName,
CreationPath: ent.Path,
Policies: ent.Policies,
ExplicitMaxTTL: ent.ExplicitMaxTTL,
NumUses: ent.NumUses,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
},
ClientToken: ent.ID,
}
if err := ts.expiration.RegisterAuth(ent.Path, auth); err != nil {
t.Fatal(err)
}
out, err = ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %s", err)
}
if out.NumUses != 5 {
t.Fatalf("bad: num_uses: expected: 5, actual: %d", out.NumUses)
}
// Switch the values from deprecated and proper field and check if the
// lower value is still getting picked up
ent = &logical.TokenEntry{
Path: "test",
NumUses: 10,
NumUsesDeprecated: 5,
}
if err := ts.create(context.Background(), ent); err != nil {
t.Fatalf("err: %s", err)
}
auth = &logical.Auth{
DisplayName: ent.DisplayName,
CreationPath: ent.Path,
Policies: ent.Policies,
ExplicitMaxTTL: ent.ExplicitMaxTTL,
NumUses: ent.NumUses,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
},
ClientToken: ent.ID,
}
if err := ts.expiration.RegisterAuth(ent.Path, auth); err != nil {
t.Fatal(err)
}
out, err = ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %s", err)
}
if out.NumUses != 5 {
t.Fatalf("bad: num_uses: expected: 5, actual: %d", out.NumUses)
}
}
func getBackendConfig(c *Core) *logical.BackendConfig {
return &logical.BackendConfig{
Logger: c.logger,
System: logical.StaticSystemView{
DefaultLeaseTTLVal: time.Hour * 24,
MaxLeaseTTLVal: time.Hour * 24 * 32,
},
}
}
func testMakeTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string) {
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["id"] = client
req.Data["policies"] = policy
req.Data["ttl"] = ttl
resp := testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken != client {
t.Fatalf("bad: %#v", resp)
}
}
func testMakeTokenViaRequest(t testing.TB, ts *TokenStore, req *logical.Request) *logical.Response {
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("got nil token from create call")
}
if err := ts.expiration.RegisterAuth(resp.Auth.CreationPath, resp.Auth); err != nil {
t.Fatal(err)
}
return resp
}
func testMakeTokenDirectly(t testing.TB, ts *TokenStore, te *logical.TokenEntry) {
if err := ts.create(context.Background(), te); err != nil {
t.Fatal(err)
}
auth := &logical.Auth{
NumUses: te.NumUses,
DisplayName: te.DisplayName,
Policies: te.Policies,
Metadata: te.Meta,
LeaseOptions: logical.LeaseOptions{
TTL: te.TTL,
Renewable: te.TTL > 0,
},
ClientToken: te.ID,
Accessor: te.Accessor,
EntityID: te.EntityID,
Period: te.Period,
ExplicitMaxTTL: te.ExplicitMaxTTL,
CreationPath: te.Path,
}
if err := ts.expiration.RegisterAuth(te.Path, auth); err != nil {
t.Fatal(err)
}
}
func testMakeTokenViaCore(t testing.TB, c *Core, root, client, ttl string, policy []string) {
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
req.ClientToken = root
req.Data["id"] = client
req.Data["policies"] = policy
req.Data["ttl"] = ttl
resp, err := c.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp.Auth.ClientToken != client {
t.Fatalf("bad: %#v", *resp)
}
}
func TestTokenStore_AccessorIndex(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
ent := &logical.TokenEntry{
Path: "test",
Policies: []string{"dev", "ops"},
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent)
out, err := ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %s", err)
}
// Ensure that accessor is created
if out == nil || out.Accessor == "" {
t.Fatalf("bad: %#v", out)
}
aEntry, err := ts.lookupByAccessor(context.Background(), out.Accessor, false)
if err != nil {
t.Fatalf("err: %s", err)
}
// Verify that the value returned from the index matches the token ID
if aEntry.TokenID != ent.ID {
t.Fatalf("bad: got\n%s\nexpected\n%s\n", aEntry.TokenID, ent.ID)
}
}
func TestTokenStore_HandleRequest_LookupAccessor(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testMakeTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"})
out, err := ts.Lookup(context.Background(), "tokenid")
if err != nil {
t.Fatalf("err: %s", err)
}
if out == nil {
t.Fatalf("err: %s", err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "lookup-accessor")
req.Data = map[string]interface{}{
"accessor": out.Accessor,
}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp.Data == nil {
t.Fatalf("response should contain data")
}
if resp.Data["accessor"].(string) == "" {
t.Fatalf("accessor should not be empty")
}
// Verify that the lookup-accessor operation does not return the token ID
if resp.Data["id"].(string) != "" {
t.Fatalf("token ID should not be returned")
}
}
func TestTokenStore_HandleRequest_ListAccessors(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testKeys := []string{"token1", "token2", "token3", "token4"}
for _, key := range testKeys {
testMakeTokenViaBackend(t, ts, root, key, "", []string{"foo"})
}
// Revoke root to make the number of accessors match
salted, err := ts.SaltID(context.Background(), root)
if err != nil {
t.Fatal(err)
}
ts.revokeSalted(context.Background(), salted, false)
req := logical.TestRequest(t, logical.ListOperation, "accessors/")
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp.Data == nil {
t.Fatalf("response should contain data")
}
if resp.Data["keys"] == nil {
t.Fatalf("keys should not be empty")
}
keys := resp.Data["keys"].([]string)
if len(keys) != len(testKeys) {
t.Fatalf("wrong number of accessors found")
}
if len(resp.Warnings) != 0 {
t.Fatalf("got warnings:\n%#v", resp.Warnings)
}
// Test upgrade from old struct method of accessor storage (of token id)
for _, accessor := range keys {
aEntry, err := ts.lookupByAccessor(context.Background(), accessor, false)
if err != nil {
t.Fatal(err)
}
if aEntry.TokenID == "" || aEntry.AccessorID == "" {
t.Fatalf("error, accessor entry looked up is empty, but no error thrown")
}
salted, err := ts.SaltID(context.Background(), accessor)
if err != nil {
t.Fatal(err)
}
path := accessorPrefix + salted
le := &logical.StorageEntry{Key: path, Value: []byte(aEntry.TokenID)}
if err := ts.view.Put(context.Background(), le); err != nil {
t.Fatalf("failed to persist accessor index entry: %v", err)
}
}
// Do the lookup again, should get same result
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp.Data == nil {
t.Fatalf("response should contain data")
}
if resp.Data["keys"] == nil {
t.Fatalf("keys should not be empty")
}
keys2 := resp.Data["keys"].([]string)
if len(keys) != len(testKeys) {
t.Fatalf("wrong number of accessors found")
}
if len(resp.Warnings) != 0 {
t.Fatalf("got warnings:\n%#v", resp.Warnings)
}
for _, accessor := range keys2 {
aEntry, err := ts.lookupByAccessor(context.Background(), accessor, false)
if err != nil {
t.Fatal(err)
}
if aEntry.TokenID == "" || aEntry.AccessorID == "" {
t.Fatalf("error, accessor entry looked up is empty, but no error thrown")
}
}
}
func TestTokenStore_HandleRequest_RevokeAccessor(t *testing.T) {
exp := mockExpiration(t)
ts := exp.tokenStore
rootToken, err := ts.rootToken(context.Background())
root := rootToken.ID
testMakeTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"})
auth := &logical.Auth{
ClientToken: "tokenid",
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
Renewable: true,
},
}
err = exp.RegisterAuth("auth/token/create", auth)
if err != nil {
t.Fatalf("err: %v", err)
}
out, err := ts.Lookup(context.Background(), "tokenid")
if err != nil {
t.Fatalf("err: %s", err)
}
if out == nil {
t.Fatalf("err: %s", err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-accessor")
req.Data = map[string]interface{}{
"accessor": out.Accessor,
}
_, err = ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("err: %s", err)
}
time.Sleep(200 * time.Millisecond)
out, err = ts.Lookup(context.Background(), "tokenid")
if err != nil {
t.Fatalf("err: %s", err)
}
if out != nil {
t.Fatalf("bad:\ngot %#v\nexpected: nil\n", out)
}
// Now test without registering the token through the expiration manager
testMakeTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"})
out, err = ts.Lookup(context.Background(), "tokenid")
if err != nil {
t.Fatalf("err: %s", err)
}
if out == nil {
t.Fatalf("err: %s", err)
}
req = logical.TestRequest(t, logical.UpdateOperation, "revoke-accessor")
req.Data = map[string]interface{}{
"accessor": out.Accessor,
}
_, err = ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("err: %s", err)
}
time.Sleep(200 * time.Millisecond)
out, err = ts.Lookup(context.Background(), "tokenid")
if err != nil {
t.Fatalf("err: %s", err)
}
if out != nil {
t.Fatalf("bad:\ngot %#v\nexpected: nil\n", out)
}
}
func TestTokenStore_RootToken(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
te, err := ts.rootToken(context.Background())
if err != nil {
t.Fatalf("err: %v", err)
}
if te.ID == "" {
t.Fatalf("missing ID")
}
out, err := ts.Lookup(context.Background(), te.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(out, te) {
t.Fatalf("bad: expected:%#v\nactual:%#v", te, out)
}
}
func TestTokenStore_CreateLookup(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
ent := &logical.TokenEntry{
Path: "test",
Policies: []string{"dev", "ops"},
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent)
if ent.ID == "" {
t.Fatalf("missing ID")
}
out, err := ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(out, ent) {
t.Fatalf("bad: expected:%#v\nactual:%#v", ent, out)
}
// New store should share the salt
ts2, err := NewTokenStore(context.Background(), hclog.New(&hclog.LoggerOptions{}), c, getBackendConfig(c))
if err != nil {
t.Fatalf("err: %v", err)
}
ts2.SetExpirationManager(c.expiration)
// Should still match
out, err = ts2.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(out, ent) {
t.Fatalf("bad: expected:%#v\nactual:%#v", ent, out)
}
}
func TestTokenStore_CreateLookup_ProvidedID(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
ent := &logical.TokenEntry{
ID: "foobarbaz",
Path: "test",
Policies: []string{"dev", "ops"},
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent)
if ent.ID != "foobarbaz" {
t.Fatalf("bad: ent.ID: expected:\"foobarbaz\"\n actual:%s", ent.ID)
}
if err := ts.create(context.Background(), ent); err == nil {
t.Fatal("expected error creating token with the same ID")
}
out, err := ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(out, ent) {
t.Fatalf("bad: expected:%#v\nactual:%#v", ent, out)
}
// New store should share the salt
ts2, err := NewTokenStore(context.Background(), hclog.New(&hclog.LoggerOptions{}), c, getBackendConfig(c))
if err != nil {
t.Fatalf("err: %v", err)
}
ts2.SetExpirationManager(c.expiration)
// Should still match
out, err = ts2.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(out, ent) {
t.Fatalf("bad: expected:%#v\nactual:%#v", ent, out)
}
}
func TestTokenStore_CreateLookup_ExpirationInRestoreMode(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
ent := &logical.TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
if err := ts.create(context.Background(), ent); err != nil {
t.Fatalf("err: %v", err)
}
if ent.ID == "" {
t.Fatalf("missing ID")
}
// Replace the lease with a lease with an expire time in the past
saltedID, err := ts.SaltID(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
// Create a lease entry
leaseID := path.Join(ent.Path, saltedID)
le := &leaseEntry{
LeaseID: leaseID,
ClientToken: ent.ID,
Path: ent.Path,
IssueTime: time.Now(),
ExpireTime: time.Now().Add(1 * time.Hour),
}
if err := ts.expiration.persistEntry(le); err != nil {
t.Fatalf("err: %v", err)
}
out, err := ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(out, ent) {
t.Fatalf("bad: expected:%#v\nactual:%#v", ent, out)
}
// Set to expired lease time
le.ExpireTime = time.Now().Add(-1 * time.Hour)
if err := ts.expiration.persistEntry(le); err != nil {
t.Fatalf("err: %v", err)
}
err = ts.expiration.Stop()
if err != nil {
t.Fatal(err)
}
// Reset expiration manager to restore mode
ts.expiration.restoreModeLock.Lock()
atomic.StoreInt32(ts.expiration.restoreMode, 1)
ts.expiration.restoreLocks = locksutil.CreateLocks()
ts.expiration.restoreModeLock.Unlock()
// Test that the token lookup does not return the token entry due to the
// expired lease
out, err = ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("lease expired, no token expected: %#v", out)
}
}
func TestTokenStore_UseToken(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
// Lookup the root token
ent, err := ts.Lookup(context.Background(), root)
if err != nil {
t.Fatalf("err: %v", err)
}
// Root is an unlimited use token, should be a no-op
te, err := ts.UseToken(context.Background(), ent)
if err != nil {
t.Fatalf("err: %v", err)
}
if te == nil {
t.Fatalf("token entry after use was nil")
}
// Lookup the root token again
ent2, err := ts.Lookup(context.Background(), root)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(ent, ent2) {
t.Fatalf("bad: ent:%#v ent2:%#v", ent, ent2)
}
// Create a restricted token
ent = &logical.TokenEntry{
Path: "test",
Policies: []string{"dev", "ops"},
NumUses: 2,
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent)
// Use the token
te, err = ts.UseToken(context.Background(), ent)
if err != nil {
t.Fatalf("err: %v", err)
}
if te == nil {
t.Fatalf("token entry for use #1 was nil")
}
// Lookup the token
ent2, err = ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should be reduced
if ent2.NumUses != 1 {
t.Fatalf("bad: %#v", ent2)
}
// Use the token
te, err = ts.UseToken(context.Background(), ent)
if err != nil {
t.Fatalf("err: %v", err)
}
if te == nil {
t.Fatalf("token entry for use #2 was nil")
}
if te.NumUses != tokenRevocationPending {
t.Fatalf("token entry after use #2 did not have revoke flag")
}
ts.revokeOrphan(context.Background(), te.ID)
// Lookup the token
ent2, err = ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should be revoked
if ent2 != nil {
t.Fatalf("bad: %#v", ent2)
}
}
func TestTokenStore_Revoke(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
ent := &logical.TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
if err := ts.create(context.Background(), ent); err != nil {
t.Fatalf("err: %v", err)
}
err := ts.revokeOrphan(context.Background(), "")
if err.Error() != "cannot revoke blank token" {
t.Fatalf("err: %v", err)
}
err = ts.revokeOrphan(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
out, err := ts.Lookup(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %#v", out)
}
}
func TestTokenStore_Revoke_Leases(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
view := NewBarrierView(c.barrier, "noop/")
// Mount a noop backend
noop := &NoopBackend{}
err := ts.expiration.router.Mount(noop, "noop/", &MountEntry{UUID: "noopuuid", Accessor: "noopaccessor"}, view)
if err != nil {
t.Fatal(err)
}
ent := &logical.TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
if err := ts.create(context.Background(), ent); err != nil {
t.Fatalf("err: %v", err)
}
// Register a lease
req := &logical.Request{
Operation: logical.ReadOperation,
Path: "noop/foo",
ClientToken: ent.ID,
}
resp := &logical.Response{
Secret: &logical.Secret{
LeaseOptions: logical.LeaseOptions{
TTL: 20 * time.Millisecond,
},
},
Data: map[string]interface{}{
"access_key": "xyz",
"secret_key": "abcd",
},
}
leaseID, err := ts.expiration.Register(req, resp)
if err != nil {
t.Fatalf("err: %v", err)
}
// Revoke the token
err = ts.revokeOrphan(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
time.Sleep(200 * time.Millisecond)
// Verify the lease is gone
out, err := ts.expiration.loadEntry(leaseID)
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %#v", out)
}
}
func TestTokenStore_Revoke_Orphan(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
ent := &logical.TokenEntry{
Path: "test",
Policies: []string{"dev", "ops"},
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent)
ent2 := &logical.TokenEntry{
Parent: ent.ID,
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent2)
err := ts.revokeOrphan(context.Background(), ent.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
out, err := ts.Lookup(context.Background(), ent2.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
// Unset the expected token parent's ID
ent2.Parent = ""
if !reflect.DeepEqual(out, ent2) {
t.Fatalf("bad: expected:%#v\nactual:%#v", ent2, out)
}
}
// This was the original function name, and now it just calls
// the non recursive version for a variety of depths.
func TestTokenStore_RevokeTree(t *testing.T) {
testTokenStore_RevokeTree_NonRecursive(t, 1)
testTokenStore_RevokeTree_NonRecursive(t, 2)
testTokenStore_RevokeTree_NonRecursive(t, 10)
}
// Revokes a given Token Store tree non recursively.
// The second parameter refers to the depth of the tree.
func testTokenStore_RevokeTree_NonRecursive(t testing.TB, depth uint64) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
root, children := buildTokenTree(t, ts, depth)
err := ts.revokeTree(context.Background(), "")
if err.Error() != "cannot tree-revoke blank token" {
t.Fatalf("err: %v", err)
}
// Nuke tree non recursively.
err = ts.revokeTree(context.Background(), root.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
// Append the root to ensure it was successfully
// deleted.
children = append(children, root)
for _, entry := range children {
out, err := ts.Lookup(context.Background(), entry.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %#v", out)
}
}
}
// A benchmark function that tests testTokenStore_RevokeTree_NonRecursive
// for a variety of different depths.
func BenchmarkTokenStore_RevokeTree(b *testing.B) {
benchmarks := []uint64{0, 1, 2, 4, 8, 16, 20}
for _, depth := range benchmarks {
b.Run(fmt.Sprintf("Tree of Depth %d", depth), func(b *testing.B) {
for i := 0; i < b.N; i++ {
testTokenStore_RevokeTree_NonRecursive(b, depth)
}
})
}
}
// Builds a TokenTree of a specified depth, so that
// we may run revoke tests on it.
func buildTokenTree(t testing.TB, ts *TokenStore, depth uint64) (root *logical.TokenEntry, children []*logical.TokenEntry) {
root = &logical.TokenEntry{
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, root)
frontier := []*logical.TokenEntry{root}
current := uint64(0)
for current < depth {
next := make([]*logical.TokenEntry, 0, 2*len(frontier))
for _, node := range frontier {
left := &logical.TokenEntry{
Parent: node.ID,
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, left)
right := &logical.TokenEntry{
Parent: node.ID,
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, right)
children = append(children, left, right)
next = append(next, left, right)
}
frontier = next
current++
}
return root, children
}
func TestTokenStore_RevokeSelf(t *testing.T) {
exp := mockExpiration(t)
ts := exp.tokenStore
ent1 := &logical.TokenEntry{
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent1)
ent2 := &logical.TokenEntry{
Parent: ent1.ID,
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent2)
ent3 := &logical.TokenEntry{
Parent: ent2.ID,
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent3)
ent4 := &logical.TokenEntry{
Parent: ent2.ID,
TTL: time.Hour,
}
testMakeTokenDirectly(t, ts, ent4)
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-self")
req.ClientToken = ent1.ID
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
lookup := []string{ent1.ID, ent2.ID, ent3.ID, ent4.ID}
var out *logical.TokenEntry
for _, id := range lookup {
var found bool
for i := 0; i < 10; i++ {
out, err = ts.Lookup(context.Background(), id)
if err != nil {
t.Fatalf("err: %v", err)
}
if out == nil {
found = true
break
}
time.Sleep(1000 * time.Millisecond)
}
if !found {
t.Fatalf("bad: %#v", out)
}
}
}
func TestTokenStore_HandleRequest_NonAssignable(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["policies"] = []string{"default", "foo"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Data["policies"] = []string{"default", "foo", responseWrappingPolicyName}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got a nil response")
}
if !resp.IsError() {
t.Fatalf("expected error; response is %#v", *resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_DisplayName(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["display_name"] = "foo_bar.baz!"
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
expected := &logical.TokenEntry{
ID: resp.Auth.ClientToken,
Accessor: resp.Auth.Accessor,
Parent: root,
Policies: []string{"root"},
Path: "auth/token/create",
DisplayName: "token-foo-bar-baz",
TTL: 0,
}
out, err := ts.Lookup(context.Background(), resp.Auth.ClientToken)
if err != nil {
t.Fatalf("err: %v", err)
}
expected.CreationTime = out.CreationTime
if !reflect.DeepEqual(out, expected) {
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, out)
}
}
func TestTokenStore_HandleRequest_CreateToken_NumUses(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["num_uses"] = "1"
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
expected := &logical.TokenEntry{
ID: resp.Auth.ClientToken,
Accessor: resp.Auth.Accessor,
Parent: root,
Policies: []string{"root"},
Path: "auth/token/create",
DisplayName: "token",
NumUses: 1,
TTL: 0,
}
out, err := ts.Lookup(context.Background(), resp.Auth.ClientToken)
if err != nil {
t.Fatalf("err: %v", err)
}
expected.CreationTime = out.CreationTime
if !reflect.DeepEqual(out, expected) {
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, out)
}
}
func TestTokenStore_HandleRequest_CreateToken_NumUses_Invalid(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["num_uses"] = "-1"
resp, err := ts.HandleRequest(context.Background(), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v resp: %#v", err, resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_NumUses_Restricted(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["num_uses"] = "1"
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
// We should NOT be able to use the restricted token to create a new token
req.ClientToken = resp.Auth.ClientToken
_, err = ts.HandleRequest(context.Background(), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v resp: %#v", err, resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_NoPolicy(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
expected := &logical.TokenEntry{
ID: resp.Auth.ClientToken,
Accessor: resp.Auth.Accessor,
Parent: root,
Policies: []string{"root"},
Path: "auth/token/create",
DisplayName: "token",
TTL: 0,
}
out, err := ts.Lookup(context.Background(), resp.Auth.ClientToken)
if err != nil {
t.Fatalf("err: %v", err)
}
expected.CreationTime = out.CreationTime
if !reflect.DeepEqual(out, expected) {
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, out)
}
}
func TestTokenStore_HandleRequest_CreateToken_BadParent(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = "random"
resp, err := ts.HandleRequest(context.Background(), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v resp: %#v", err, resp)
}
if resp.Data["error"] != "parent token lookup failed: no parent found" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["policies"] = []string{"foo"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_RootID(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["id"] = "foobar"
req.Data["policies"] = []string{"foo"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp.Auth.ClientToken != "foobar" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_NonRootID(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testMakeTokenViaBackend(t, ts, root, "client", "", []string{"foo"})
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = "client"
req.Data["id"] = "foobar"
req.Data["policies"] = []string{"foo"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v resp: %#v", err, resp)
}
if resp.Data["error"] != "root or sudo privileges required to specify token id" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_NonRoot_Subset(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testMakeTokenViaBackend(t, ts, root, "client", "", []string{"foo", "bar"})
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = "client"
req.Data["policies"] = []string{"foo"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_NonRoot_InvalidSubset(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testMakeTokenViaBackend(t, ts, root, "client", "", []string{"foo", "bar"})
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = "client"
req.Data["policies"] = []string{"foo", "bar", "baz"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v resp: %#v", err, resp)
}
if resp.Data["error"] != "child policies must be subset of parent" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_NonRoot_RootChild(t *testing.T) {
core, _, root := TestCoreUnsealed(t)
ts := core.tokenStore
ps := core.policyStore
policy, _ := ParseACLPolicy(tokenCreationPolicy)
policy.Name = "test1"
if err := ps.SetPolicy(context.Background(), policy); err != nil {
t.Fatal(err)
}
testMakeTokenViaBackend(t, ts, root, "sudoClient", "", []string{"test1"})
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = "sudoClient"
req.MountPoint = "auth/token/"
req.Data["policies"] = []string{"root"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v; resp: %#v", err, resp)
}
if resp == nil || resp.Data == nil {
t.Fatalf("expected a response")
}
if resp.Data["error"].(string) != "root tokens may not be created without parent token being root" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_Root_RootChild_NoExpiry_Expiry(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data = map[string]interface{}{
"ttl": "5m",
}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("err: %v; resp: %#v", err, resp)
}
if resp == nil || resp.Auth == nil {
t.Fatalf("failed to create a root token using another root token")
}
if !reflect.DeepEqual(resp.Auth.Policies, []string{"root"}) {
t.Fatalf("bad: policies: expected: root; actual: %s", resp.Auth.Policies)
}
if resp.Auth.TTL.Seconds() != 300 {
t.Fatalf("bad: expected 300 second ttl, got %v", resp.Auth.TTL.Seconds())
}
req.ClientToken = resp.Auth.ClientToken
req.Data = map[string]interface{}{
"ttl": "0",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err == nil {
t.Fatalf("expected error")
}
}
func TestTokenStore_HandleRequest_CreateToken_Root_RootChild(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("err: %v; resp: %#v", err, resp)
}
if resp == nil || resp.Auth == nil {
t.Fatalf("failed to create a root token using another root token")
}
if !reflect.DeepEqual(resp.Auth.Policies, []string{"root"}) {
t.Fatalf("bad: policies: expected: root; actual: %s", resp.Auth.Policies)
}
}
func TestTokenStore_HandleRequest_CreateToken_NonRoot_NoParent(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testMakeTokenViaBackend(t, ts, root, "client", "", []string{"foo"})
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = "client"
req.Data["no_parent"] = true
req.Data["policies"] = []string{"foo"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v resp: %#v", err, resp)
}
if resp.Data["error"] != "root or sudo privileges required to create orphan token" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_Root_NoParent(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["no_parent"] = true
req.Data["policies"] = []string{"foo"}
resp := testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
out, _ := ts.Lookup(context.Background(), resp.Auth.ClientToken)
if out.Parent != "" {
t.Fatalf("bad: %#v", out)
}
}
func TestTokenStore_HandleRequest_CreateToken_PathBased_NoParent(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create-orphan")
req.ClientToken = root
req.Data["policies"] = []string{"foo"}
resp := testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
out, _ := ts.Lookup(context.Background(), resp.Auth.ClientToken)
if out.Parent != "" {
t.Fatalf("bad: %#v", out)
}
}
func TestTokenStore_HandleRequest_CreateToken_Metadata(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["policies"] = []string{"foo"}
meta := map[string]string{
"user": "armon",
"source": "github",
}
req.Data["meta"] = meta
resp := testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
out, _ := ts.Lookup(context.Background(), resp.Auth.ClientToken)
if !reflect.DeepEqual(out.Meta, meta) {
t.Fatalf("bad: expected:%#v\nactual:%#v", meta, out.Meta)
}
}
func TestTokenStore_HandleRequest_CreateToken_Lease(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["policies"] = []string{"foo"}
req.Data["lease"] = "1h"
resp := testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
if resp.Auth.TTL != time.Hour {
t.Fatalf("bad: %#v", resp)
}
if !resp.Auth.Renewable {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_CreateToken_TTL(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["policies"] = []string{"foo"}
req.Data["ttl"] = "1h"
resp := testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
if resp.Auth.TTL != time.Hour {
t.Fatalf("bad: %#v", resp)
}
if !resp.Auth.Renewable {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_HandleRequest_Revoke(t *testing.T) {
exp := mockExpiration(t)
ts := exp.tokenStore
rootToken, err := ts.rootToken(context.Background())
root := rootToken.ID
testMakeTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"})
auth := &logical.Auth{
ClientToken: "child",
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
Renewable: true,
},
}
err = exp.RegisterAuth("auth/token/create", auth)
if err != nil {
t.Fatalf("err: %v", err)
}
testMakeTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"})
auth = &logical.Auth{
ClientToken: "sub-child",
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
Renewable: true,
},
}
err = exp.RegisterAuth("auth/token/create", auth)
if err != nil {
t.Fatalf("err: %v", err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "revoke")
req.Data = map[string]interface{}{
"token": "child",
}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
time.Sleep(200 * time.Millisecond)
out, err := ts.Lookup(context.Background(), "child")
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %#v", out)
}
// Sub-child should not exist
out, err = ts.Lookup(context.Background(), "sub-child")
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %v", out)
}
// Now test without registering the tokens through the expiration manager
testMakeTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"})
testMakeTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"})
req = logical.TestRequest(t, logical.UpdateOperation, "revoke")
req.Data = map[string]interface{}{
"token": "child",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
time.Sleep(200 * time.Millisecond)
out, err = ts.Lookup(context.Background(), "child")
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %#v", out)
}
// Sub-child should not exist
out, err = ts.Lookup(context.Background(), "sub-child")
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %v", out)
}
}
func TestTokenStore_HandleRequest_RevokeOrphan(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testMakeTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"})
testMakeTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"})
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan")
req.Data = map[string]interface{}{
"token": "child",
}
req.ClientToken = root
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
time.Sleep(200 * time.Millisecond)
out, err := ts.Lookup(context.Background(), "child")
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %v", out)
}
// Check that the parent entry is properly cleaned up
saltedID, err := ts.SaltID(context.Background(), "child")
if err != nil {
t.Fatal(err)
}
children, err := ts.view.List(context.Background(), parentPrefix+saltedID+"/")
if err != nil {
t.Fatalf("err: %v", err)
}
if len(children) != 0 {
t.Fatalf("bad: %v", children)
}
// Sub-child should exist!
out, err = ts.Lookup(context.Background(), "sub-child")
if err != nil {
t.Fatalf("err: %v", err)
}
if out == nil {
t.Fatalf("bad: %v", out)
}
}
func TestTokenStore_HandleRequest_RevokeOrphan_NonRoot(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testMakeTokenViaBackend(t, ts, root, "child", "", []string{"foo"})
out, err := ts.Lookup(context.Background(), "child")
if err != nil {
t.Fatalf("err: %v", err)
}
if out == nil {
t.Fatalf("bad: %v", out)
}
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan")
req.Data = map[string]interface{}{
"token": "child",
}
req.ClientToken = "child"
resp, err := ts.HandleRequest(context.Background(), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("did not get error when non-root revoking itself with orphan flag; resp is %#v", resp)
}
time.Sleep(200 * time.Millisecond)
// Should still exist
out, err = ts.Lookup(context.Background(), "child")
if err != nil {
t.Fatalf("err: %v", err)
}
if out == nil {
t.Fatalf("bad: %v", out)
}
}
func TestTokenStore_HandleRequest_Lookup(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "lookup")
req.Data = map[string]interface{}{
"token": root,
}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("bad: %#v", resp)
}
exp := map[string]interface{}{
"id": root,
"accessor": resp.Data["accessor"].(string),
"policies": []string{"root"},
"path": "auth/token/root",
"meta": map[string]string(nil),
"display_name": "root",
"orphan": true,
"num_uses": 0,
"creation_ttl": int64(0),
"ttl": int64(0),
"explicit_max_ttl": int64(0),
"expire_time": nil,
"entity_id": "",
}
if resp.Data["creation_time"].(int64) == 0 {
t.Fatalf("creation time was zero")
}
delete(resp.Data, "creation_time")
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data)
}
testMakeTokenViaCore(t, c, root, "client", "3600s", []string{"foo"})
// Test via GET
req = logical.TestRequest(t, logical.UpdateOperation, "lookup")
req.Data = map[string]interface{}{
"token": "client",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("bad: %#v", resp)
}
exp = map[string]interface{}{
"id": "client",
"accessor": resp.Data["accessor"],
"policies": []string{"default", "foo"},
"path": "auth/token/create",
"meta": map[string]string(nil),
"display_name": "token",
"orphan": false,
"num_uses": 0,
"creation_ttl": int64(3600),
"ttl": int64(3600),
"explicit_max_ttl": int64(0),
"renewable": true,
"entity_id": "",
}
if resp.Data["creation_time"].(int64) == 0 {
t.Fatalf("creation time was zero")
}
delete(resp.Data, "creation_time")
if resp.Data["issue_time"].(time.Time).IsZero() {
t.Fatal("issue time is default time")
}
delete(resp.Data, "issue_time")
if resp.Data["expire_time"].(time.Time).IsZero() {
t.Fatal("expire time is default time")
}
delete(resp.Data, "expire_time")
// Depending on timing of the test this may have ticked down, so accept 3599
if resp.Data["ttl"].(int64) == 3599 {
resp.Data["ttl"] = int64(3600)
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data)
}
// Test via POST
req = logical.TestRequest(t, logical.UpdateOperation, "lookup")
req.Data = map[string]interface{}{
"token": "client",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("bad: %#v", resp)
}
exp = map[string]interface{}{
"id": "client",
"accessor": resp.Data["accessor"],
"policies": []string{"default", "foo"},
"path": "auth/token/create",
"meta": map[string]string(nil),
"display_name": "token",
"orphan": false,
"num_uses": 0,
"creation_ttl": int64(3600),
"ttl": int64(3600),
"explicit_max_ttl": int64(0),
"renewable": true,
"entity_id": "",
}
if resp.Data["creation_time"].(int64) == 0 {
t.Fatalf("creation time was zero")
}
delete(resp.Data, "creation_time")
if resp.Data["issue_time"].(time.Time).IsZero() {
t.Fatal("issue time is default time")
}
delete(resp.Data, "issue_time")
if resp.Data["expire_time"].(time.Time).IsZero() {
t.Fatal("expire time is default time")
}
delete(resp.Data, "expire_time")
// Depending on timing of the test this may have ticked down, so accept 3599
if resp.Data["ttl"].(int64) == 3599 {
resp.Data["ttl"] = int64(3600)
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data)
}
// Test last_renewal_time functionality
req = logical.TestRequest(t, logical.UpdateOperation, "renew")
req.Data = map[string]interface{}{
"token": "client",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("bad: %#v", resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "lookup")
req.Data = map[string]interface{}{
"token": "client",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("bad: %#v", resp)
}
if resp.Data["last_renewal_time"].(int64) == 0 {
t.Fatalf("last_renewal_time was zero")
}
}
func TestTokenStore_HandleRequest_LookupSelf(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
testMakeTokenViaCore(t, c, root, "client", "3600s", []string{"foo"})
req := logical.TestRequest(t, logical.ReadOperation, "lookup-self")
req.ClientToken = "client"
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("bad: %#v", resp)
}
exp := map[string]interface{}{
"id": "client",
"accessor": resp.Data["accessor"],
"policies": []string{"default", "foo"},
"path": "auth/token/create",
"meta": map[string]string(nil),
"display_name": "token",
"orphan": false,
"renewable": true,
"num_uses": 0,
"creation_ttl": int64(3600),
"ttl": int64(3600),
"explicit_max_ttl": int64(0),
"entity_id": "",
}
if resp.Data["creation_time"].(int64) == 0 {
t.Fatalf("creation time was zero")
}
delete(resp.Data, "creation_time")
if resp.Data["issue_time"].(time.Time).IsZero() {
t.Fatalf("creation time was zero")
}
delete(resp.Data, "issue_time")
if resp.Data["expire_time"].(time.Time).IsZero() {
t.Fatalf("expire time was zero")
}
delete(resp.Data, "expire_time")
// Depending on timing of the test this may have ticked down, so accept 3599
if resp.Data["ttl"].(int64) == 3599 {
resp.Data["ttl"] = int64(3600)
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data)
}
}
func TestTokenStore_HandleRequest_Renew(t *testing.T) {
exp := mockExpiration(t)
ts := exp.tokenStore
// Create new token
root, err := ts.rootToken(context.Background())
if err != nil {
t.Fatalf("err: %v", err)
}
// Create a new token
auth := &logical.Auth{
ClientToken: root.ID,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
Renewable: true,
},
}
err = exp.RegisterAuth("auth/token/root", auth)
if err != nil {
t.Fatalf("err: %v", err)
}
// Get the original expire time to compare
originalExpire := auth.ExpirationTime()
beforeRenew := time.Now()
req := logical.TestRequest(t, logical.UpdateOperation, "renew")
req.Data = map[string]interface{}{
"token": root.ID,
"increment": "3600s",
}
req.Data["increment"] = "3600s"
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
// Get the new expire time
newExpire := resp.Auth.ExpirationTime()
if newExpire.Before(originalExpire) {
t.Fatalf("should expire later: %s %s", newExpire, originalExpire)
}
if newExpire.Before(beforeRenew.Add(time.Hour)) {
t.Fatalf("should have at least an hour: %s %s", newExpire, beforeRenew)
}
}
func TestTokenStore_HandleRequest_RenewSelf(t *testing.T) {
exp := mockExpiration(t)
ts := exp.tokenStore
// Create new token
root, err := ts.rootToken(context.Background())
if err != nil {
t.Fatalf("err: %v", err)
}
// Create a new token
auth := &logical.Auth{
ClientToken: root.ID,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
Renewable: true,
},
}
err = exp.RegisterAuth("auth/token/root", auth)
if err != nil {
t.Fatalf("err: %v", err)
}
// Get the original expire time to compare
originalExpire := auth.ExpirationTime()
beforeRenew := time.Now()
req := logical.TestRequest(t, logical.UpdateOperation, "renew-self")
req.ClientToken = auth.ClientToken
req.Data["increment"] = "3600s"
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
// Get the new expire time
newExpire := resp.Auth.ExpirationTime()
if newExpire.Before(originalExpire) {
t.Fatalf("should expire later: %s %s", newExpire, originalExpire)
}
if newExpire.Before(beforeRenew.Add(time.Hour)) {
t.Fatalf("should have at least an hour: %s %s", newExpire, beforeRenew)
}
}
func TestTokenStore_RoleCRUD(t *testing.T) {
core, _, root := TestCoreUnsealed(t)
req := logical.TestRequest(t, logical.ReadOperation, "auth/token/roles/test")
req.ClientToken = root
resp, err := core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("should not see a role")
}
// First test creation
req.Operation = logical.CreateOperation
req.Data = map[string]interface{}{
"orphan": true,
"period": "72h",
"allowed_policies": "test1,test2",
"path_suffix": "happenin",
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Operation = logical.ReadOperation
req.Data = map[string]interface{}{}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("got a nil response")
}
expected := map[string]interface{}{
"name": "test",
"orphan": true,
"period": int64(259200),
"allowed_policies": []string{"test1", "test2"},
"disallowed_policies": []string{},
"path_suffix": "happenin",
"explicit_max_ttl": int64(0),
"renewable": true,
}
if !reflect.DeepEqual(expected, resp.Data) {
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, resp.Data)
}
// Now test updating; this should be set to an UpdateOperation
// automatically due to the existence check
req.Operation = logical.CreateOperation
req.Data = map[string]interface{}{
"period": "79h",
"allowed_policies": "test3",
"path_suffix": "happenin",
"renewable": false,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Operation = logical.ReadOperation
req.Data = map[string]interface{}{}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("got a nil response")
}
expected = map[string]interface{}{
"name": "test",
"orphan": true,
"period": int64(284400),
"allowed_policies": []string{"test3"},
"disallowed_policies": []string{},
"path_suffix": "happenin",
"explicit_max_ttl": int64(0),
"renewable": false,
}
if !reflect.DeepEqual(expected, resp.Data) {
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, resp.Data)
}
// Now set explicit max ttl and clear the period
req.Operation = logical.CreateOperation
req.Data = map[string]interface{}{
"explicit_max_ttl": "5",
"period": "0s",
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Data = map[string]interface{}{}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("got a nil response")
}
expected = map[string]interface{}{
"name": "test",
"orphan": true,
"explicit_max_ttl": int64(5),
"allowed_policies": []string{"test3"},
"disallowed_policies": []string{},
"path_suffix": "happenin",
"period": int64(0),
"renewable": false,
}
if !reflect.DeepEqual(expected, resp.Data) {
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, resp.Data)
}
req.Operation = logical.ListOperation
req.Path = "auth/token/roles"
req.Data = map[string]interface{}{}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("got a nil response")
}
keysInt, ok := resp.Data["keys"]
if !ok {
t.Fatalf("did not find keys in response")
}
keys, ok := keysInt.([]string)
if !ok {
t.Fatalf("could not convert keys interface to key list")
}
if len(keys) != 1 {
t.Fatalf("unexpected number of keys: %d", len(keys))
}
if keys[0] != "test" {
t.Fatalf("expected \"test\", got \"%s\"", keys[0])
}
req.Operation = logical.DeleteOperation
req.Path = "auth/token/roles/test"
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Operation = logical.ReadOperation
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
}
func TestTokenStore_RoleDisallowedPoliciesWithRoot(t *testing.T) {
var resp *logical.Response
var err error
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
// Don't set disallowed_policies. Verify that a read on the role does return a non-nil value.
roleReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/role1",
Data: map[string]interface{}{
"disallowed_policies": "root,testpolicy",
},
ClientToken: root,
}
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
roleReq.Operation = logical.ReadOperation
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
expected := []string{"root", "testpolicy"}
if !reflect.DeepEqual(resp.Data["disallowed_policies"], expected) {
t.Fatalf("bad: expected: %#v, actual: %#v", expected, resp.Data["disallowed_policies"])
}
}
func TestTokenStore_RoleDisallowedPolicies(t *testing.T) {
var req *logical.Request
var resp *logical.Response
var err error
core, _, root := TestCoreUnsealed(t)
ts := core.tokenStore
ps := core.policyStore
// Create 3 different policies
policy, _ := ParseACLPolicy(tokenCreationPolicy)
policy.Name = "test1"
if err := ps.SetPolicy(context.Background(), policy); err != nil {
t.Fatal(err)
}
policy, _ = ParseACLPolicy(tokenCreationPolicy)
policy.Name = "test2"
if err := ps.SetPolicy(context.Background(), policy); err != nil {
t.Fatal(err)
}
policy, _ = ParseACLPolicy(tokenCreationPolicy)
policy.Name = "test3"
if err := ps.SetPolicy(context.Background(), policy); err != nil {
t.Fatal(err)
}
// Create roles with different disallowed_policies configuration
req = logical.TestRequest(t, logical.UpdateOperation, "roles/test1")
req.ClientToken = root
req.Data = map[string]interface{}{
"disallowed_policies": "test1",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "roles/test23")
req.ClientToken = root
req.Data = map[string]interface{}{
"disallowed_policies": "test2,test3",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "roles/test123")
req.ClientToken = root
req.Data = map[string]interface{}{
"disallowed_policies": "test1,test2,test3",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
// Create a token that has all the policies defined above
req = logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["policies"] = []string{"test1", "test2", "test3"}
resp = testMakeTokenViaRequest(t, ts, req)
if resp == nil || resp.Auth == nil {
t.Fatal("got nil response")
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: ClientToken; resp:%#v", resp)
}
parentToken := resp.Auth.ClientToken
req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
req.ClientToken = parentToken
resp, err = ts.HandleRequest(context.Background(), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatalf("expected an error response, got %#v", resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "create/test23")
req.ClientToken = parentToken
resp, err = ts.HandleRequest(context.Background(), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatalf("expected an error response, got %#v", resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "create/test123")
req.ClientToken = parentToken
resp, err = ts.HandleRequest(context.Background(), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatalf("expected an error response, got %#v", resp)
}
// Disallowed should act as a blacklist so make sure we can still make
// something with other policies in the request
req = logical.TestRequest(t, logical.UpdateOperation, "create/test123")
req.Data["policies"] = []string{"foo", "bar"}
req.ClientToken = parentToken
testMakeTokenViaRequest(t, ts, req)
// Create a role to have 'default' policy disallowed
req = logical.TestRequest(t, logical.UpdateOperation, "roles/default")
req.ClientToken = root
req.Data = map[string]interface{}{
"disallowed_policies": "default",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "create/default")
req.ClientToken = parentToken
resp, err = ts.HandleRequest(context.Background(), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatal("expected an error response")
}
}
func TestTokenStore_RoleAllowedPolicies(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"allowed_policies": "test1,test2",
}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Data = map[string]interface{}{}
req.Path = "create/test"
req.Data["policies"] = []string{"foo"}
resp, err = ts.HandleRequest(context.Background(), req)
if err == nil {
t.Fatalf("expected error")
}
req.Data["policies"] = []string{"test2"}
resp = testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
// When allowed_policies is blank, should fall back to a subset of the parent policies
req = logical.TestRequest(t, logical.UpdateOperation, "roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"allowed_policies": "",
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req = logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["policies"] = []string{"test1", "test2", "test3"}
resp = testMakeTokenViaRequest(t, ts, req)
if resp == nil || resp.Auth == nil {
t.Fatal("got nil response")
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: ClientToken; resp:%#v", resp)
}
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "test1", "test2", "test3"}) {
t.Fatalf("bad: %#v", resp.Auth.Policies)
}
parentToken := resp.Auth.ClientToken
req.Data = map[string]interface{}{}
req.ClientToken = parentToken
req.Path = "create/test"
req.Data["policies"] = []string{"foo"}
resp, err = ts.HandleRequest(context.Background(), req)
if err == nil {
t.Fatalf("expected error")
}
req.Data["policies"] = []string{"test2"}
resp = testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
delete(req.Data, "policies")
resp = testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "test1", "test2", "test3"}) {
t.Fatalf("bad: %#v", resp.Auth.Policies)
}
}
func TestTokenStore_RoleOrphan(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"orphan": true,
}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Path = "create/test"
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
out, err := ts.Lookup(context.Background(), resp.Auth.ClientToken)
if err != nil {
t.Fatalf("err: %v", err)
}
if out.Parent != "" {
t.Fatalf("expected orphan token, but found a parent")
}
if !strings.HasPrefix(out.Path, "auth/token/create/test") {
t.Fatalf("expected role in path but did not find it")
}
}
func TestTokenStore_RolePathSuffix(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"path_suffix": "happenin",
}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Path = "create/test"
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
out, err := ts.Lookup(context.Background(), resp.Auth.ClientToken)
if err != nil {
t.Fatalf("err: %v", err)
}
if out.Path != "auth/token/create/test/happenin" {
t.Fatalf("expected role in path but did not find it")
}
}
func TestTokenStore_RolePeriod(t *testing.T) {
core, _, root := TestCoreUnsealed(t)
core.defaultLeaseTTL = 10 * time.Second
core.maxLeaseTTL = 10 * time.Second
// Note: these requests are sent to Core since Core handles registration
// with the expiration manager and we need the storage to be consistent
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"period": 5,
}
resp, err := core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
// This first set of logic is to verify that a normal non-root token will
// be given a TTL of 10 seconds, and that renewing will not cause the TTL to
// increase since that's the configured backend max. Then we verify that
// increment works.
{
req.Path = "auth/token/create"
req.Data = map[string]interface{}{
"policies": []string{"default"},
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
req.ClientToken = resp.Auth.ClientToken
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl := resp.Data["ttl"].(int64)
if ttl > 10 {
t.Fatalf("TTL too large")
}
// Let the TTL go down a bit to 8 seconds
time.Sleep(2 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 8 {
t.Fatalf("TTL too large: %d", ttl)
}
// Renewing should not have the increment increase since we've hit the
// max
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 1,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 8 {
t.Fatalf("TTL too large")
}
}
// Now we create a token against the role. We should be able to renew;
// increment should be ignored as well.
{
req.ClientToken = root
req.Operation = logical.UpdateOperation
req.Path = "auth/token/create/test"
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatal("response was nil")
}
if resp.Auth == nil {
t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
req.ClientToken = resp.Auth.ClientToken
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl := resp.Data["ttl"].(int64)
if ttl > 5 {
t.Fatalf("TTL too large (expected %d, got %d", 5, ttl)
}
// Let the TTL go down a bit to 3 seconds
time.Sleep(3 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 1,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 5 {
t.Fatalf("TTL too large (expected %d, got %d", 5, ttl)
}
}
}
func TestTokenStore_RoleExplicitMaxTTL(t *testing.T) {
core, _, root := TestCoreUnsealed(t)
core.defaultLeaseTTL = 5 * time.Second
core.maxLeaseTTL = 5 * time.Hour
// Note: these requests are sent to Core since Core handles registration
// with the expiration manager and we need the storage to be consistent
// Make sure we can't make it larger than the system/mount max; we should get a warning on role write and an error on token creation
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"explicit_max_ttl": "100h",
}
resp, err := core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatalf("expected a warning")
}
req.Operation = logical.UpdateOperation
req.Path = "auth/token/create/test"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("expected an error")
}
if len(resp.Warnings) == 0 {
t.Fatalf("expected a warning")
}
// Reset to a good explicit max
req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"explicit_max_ttl": "10s",
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
// This first set of logic is to verify that a normal non-root token will
// be given a TTL of 5 seconds, and that renewing will cause the TTL to
// increase
{
req.Path = "auth/token/create"
req.Data = map[string]interface{}{
"policies": []string{"default"},
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
req.ClientToken = resp.Auth.ClientToken
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl := resp.Data["ttl"].(int64)
if ttl > 5 {
t.Fatalf("TTL too large")
}
// Let the TTL go down a bit to 3 seconds
time.Sleep(2 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl < 4 {
t.Fatalf("TTL too small after renewal")
}
}
// Now we create a token against the role. After renew our max should still
// be the same.
{
req.ClientToken = root
req.Operation = logical.UpdateOperation
req.Path = "auth/token/create/test"
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatal("response was nil")
}
if resp.Auth == nil {
t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
req.ClientToken = resp.Auth.ClientToken
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl := resp.Data["ttl"].(int64)
if ttl > 10 {
t.Fatalf("TTL too big")
}
// explicit max ttl is stored in the role so not returned here
maxTTL := resp.Data["explicit_max_ttl"].(int64)
if maxTTL != 0 {
t.Fatalf("expected 0 for explicit max TTL, got %d", maxTTL)
}
// Let the TTL go down a bit to ~7 seconds (8 against explicit max)
time.Sleep(2 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 300,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 8 {
t.Fatalf("TTL too big: %d", ttl)
}
// Let the TTL go down a bit more to ~5 seconds (6 against explicit max)
time.Sleep(2 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 300,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 6 {
t.Fatalf("TTL too big")
}
// It should expire
time.Sleep(8 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 300,
}
resp, err = core.HandleRequest(req)
if err == nil {
t.Fatalf("expected error")
}
time.Sleep(2 * time.Second)
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if resp != nil && err == nil {
t.Fatalf("expected error, response is %#v", *resp)
}
if err == nil {
t.Fatalf("expected error")
}
}
}
func TestTokenStore_Periodic(t *testing.T) {
core, _, root := TestCoreUnsealed(t)
core.defaultLeaseTTL = 10 * time.Second
core.maxLeaseTTL = 10 * time.Second
// Note: these requests are sent to Core since Core handles registration
// with the expiration manager and we need the storage to be consistent
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"period": 5,
}
resp, err := core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
// First make one directly and verify on renew it uses the period.
{
req.ClientToken = root
req.Operation = logical.UpdateOperation
req.Path = "auth/token/create"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("response was nil")
}
if resp.Auth == nil {
t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
req.ClientToken = resp.Auth.ClientToken
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl := resp.Data["ttl"].(int64)
if ttl > 5 {
t.Fatalf("TTL too large (expected %d, got %d)", 5, ttl)
}
// Let the TTL go down a bit
time.Sleep(2 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 1,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 5 {
t.Fatalf("TTL too large (expected %d, got %d)", 5, ttl)
}
}
// Now we create a token against the role and also set the te value
// directly. We should use the smaller of the two and be able to renew;
// increment should be ignored as well.
{
req.ClientToken = root
req.Operation = logical.UpdateOperation
req.Path = "auth/token/create/test"
req.Data = map[string]interface{}{
"period": 5,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatal("response was nil")
}
if resp.Auth == nil {
t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
req.ClientToken = resp.Auth.ClientToken
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl := resp.Data["ttl"].(int64)
if ttl < 4 || ttl > 5 {
t.Fatalf("TTL bad (expected %d, got %d)", 4, ttl)
}
// Let the TTL go down a bit
time.Sleep(2 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 1,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 5 {
t.Fatalf("TTL bad (expected less than %d, got %d)", 5, ttl)
}
}
}
func TestTokenStore_Periodic_ExplicitMax(t *testing.T) {
core, _, root := TestCoreUnsealed(t)
core.defaultLeaseTTL = 10 * time.Second
core.maxLeaseTTL = 10 * time.Second
// Note: these requests are sent to Core since Core handles registration
// with the expiration manager and we need the storage to be consistent
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"period": 5,
}
resp, err := core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
// First make one directly and verify on renew it uses the period.
{
req.ClientToken = root
req.Operation = logical.UpdateOperation
req.Path = "auth/token/create"
req.Data = map[string]interface{}{
"period": 5,
"explicit_max_ttl": 4,
}
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("response was nil")
}
if resp.Auth == nil {
t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
req.ClientToken = resp.Auth.ClientToken
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl := resp.Data["ttl"].(int64)
if ttl < 3 || ttl > 4 {
t.Fatalf("TTL bad (expected %d, got %d)", 3, ttl)
}
// Let the TTL go down a bit
time.Sleep(2 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 76,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 2 {
t.Fatalf("TTL bad (expected less than %d, got %d)", 2, ttl)
}
}
// Now we create a token against the role and also set the te value
// directly. We should use the smaller of the two and be able to renew;
// increment should be ignored as well.
{
req.Path = "auth/token/roles/test"
req.ClientToken = root
req.Operation = logical.UpdateOperation
req.Data = map[string]interface{}{
"period": 5,
"explicit_max_ttl": 4,
}
resp, err := core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.ClientToken = root
req.Operation = logical.UpdateOperation
req.Path = "auth/token/create/test"
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp == nil {
t.Fatal("response was nil")
}
if resp.Auth == nil {
t.Fatalf(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
req.ClientToken = resp.Auth.ClientToken
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl := resp.Data["ttl"].(int64)
if ttl < 3 || ttl > 4 {
t.Fatalf("TTL bad (expected %d, got %d)", 3, ttl)
}
// Let the TTL go down a bit
time.Sleep(2 * time.Second)
req.Operation = logical.UpdateOperation
req.Path = "auth/token/renew-self"
req.Data = map[string]interface{}{
"increment": 1,
}
resp, err = core.HandleRequest(req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
req.Operation = logical.ReadOperation
req.Path = "auth/token/lookup-self"
resp, err = core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
ttl = resp.Data["ttl"].(int64)
if ttl > 2 {
t.Fatalf("TTL bad (expected less than %d, got %d)", 2, ttl)
}
}
}
func TestTokenStore_NoDefaultPolicy(t *testing.T) {
var resp *logical.Response
var err error
core, _, root := TestCoreUnsealed(t)
ts := core.tokenStore
ps := core.policyStore
policy, _ := ParseACLPolicy(tokenCreationPolicy)
policy.Name = "policy1"
if err := ps.SetPolicy(context.Background(), policy); err != nil {
t.Fatal(err)
}
// Root token creates a token with desired policy. The created token
// should also have 'default' attached to it.
tokenData := map[string]interface{}{
"policies": []string{"policy1"},
}
tokenReq := &logical.Request{
Path: "create",
ClientToken: root,
Operation: logical.UpdateOperation,
Data: tokenData,
}
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies)
}
newToken := resp.Auth.ClientToken
// Root token creates a token with desired policy, but also requests
// that the token to not have 'default' policy. The resulting token
// should not have 'default' policy on it.
tokenData["no_default_policy"] = true
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
}
// A non-root token which has 'default' policy attached requests for a
// child token. Child token should also have 'default' policy attached.
tokenReq.ClientToken = newToken
tokenReq.Data = nil
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
t.Fatalf("bad: policies: expected: [default policy1]; actual: %s", resp.Auth.Policies)
}
// A non-root token which has 'default' policy attached and period explicitly
// set to its zero value requests for a child token. Child token should be
// successfully created and have 'default' policy attached.
tokenReq.Data = map[string]interface{}{
"period": "0s",
}
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
t.Fatalf("bad: policies: expected: [default policy1]; actual: %s", resp.Auth.Policies)
}
// A non-root token which has 'default' policy attached, request for a
// child token to not have 'default' policy while not sending a list
tokenReq.Data = map[string]interface{}{
"no_default_policy": true,
}
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
}
// In this case "default" shouldn't exist because we are not inheriting
// parent policies
tokenReq.Data = map[string]interface{}{
"policies": []string{"policy1"},
"no_default_policy": true,
}
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
}
// This is a non-root token which does not have 'default' policy
// attached
newToken = resp.Auth.ClientToken
tokenReq.Data = nil
tokenReq.ClientToken = newToken
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
}
roleReq := &logical.Request{
ClientToken: root,
Path: "roles/role1",
Operation: logical.CreateOperation,
}
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
tokenReq.Path = "create/role1"
tokenReq.Data = map[string]interface{}{
"policies": []string{"policy1"},
}
resp, err = ts.HandleRequest(context.Background(), tokenReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
}
// If 'allowed_policies' in role does not have 'default' in it, the
// tokens generated using that role should still have the 'default' policy
// attached to them.
roleReq.Operation = logical.UpdateOperation
roleReq.Data = map[string]interface{}{
"allowed_policies": "policy1",
}
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
t.Fatalf("bad: policies: expected: [default policy1]; actual: %s", resp.Auth.Policies)
}
// If 'allowed_policies' in role does not have 'default' in it, the
// tokens generated using that role should not have 'default' policy
// attached to them if disallowed_policies contains "default"
roleReq.Operation = logical.UpdateOperation
roleReq.Data = map[string]interface{}{
"allowed_policies": "policy1",
"disallowed_policies": "default",
}
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
}
roleReq.Data = map[string]interface{}{
"allowed_policies": "",
"disallowed_policies": "default",
}
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
resp = testMakeTokenViaRequest(t, ts, tokenReq)
if !reflect.DeepEqual(resp.Auth.Policies, []string{"policy1"}) {
t.Fatalf("bad: policies: expected: [policy1]; actual: %s", resp.Auth.Policies)
}
// Ensure that if default is in both allowed and disallowed, disallowed wins
roleReq.Data = map[string]interface{}{
"allowed_policies": "default",
"disallowed_policies": "default",
}
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
delete(tokenReq.Data, "policies")
resp, err = ts.HandleRequest(context.Background(), tokenReq)
if err == nil || (resp != nil && !resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
}
func TestTokenStore_AllowedDisallowedPolicies(t *testing.T) {
var resp *logical.Response
var err error
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
roleReq := &logical.Request{
ClientToken: root,
Path: "roles/role1",
Operation: logical.CreateOperation,
Data: map[string]interface{}{
"allowed_policies": "allowed1,allowed2",
"disallowed_policies": "disallowed1,disallowed2",
},
}
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
tokenReq := &logical.Request{
Path: "create/role1",
ClientToken: root,
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"policies": []string{"allowed1"},
},
}
resp, err = ts.HandleRequest(context.Background(), tokenReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
expected := []string{"allowed1", "default"}
if !reflect.DeepEqual(resp.Auth.Policies, expected) {
t.Fatalf("bad: expected:%#v actual:%#v", expected, resp.Auth.Policies)
}
// Try again with automatic default adding turned off
tokenReq = &logical.Request{
Path: "create/role1",
ClientToken: root,
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"policies": []string{"allowed1"},
"no_default_policy": true,
},
}
resp, err = ts.HandleRequest(context.Background(), tokenReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
expected = []string{"allowed1"}
if !reflect.DeepEqual(resp.Auth.Policies, expected) {
t.Fatalf("bad: expected:%#v actual:%#v", expected, resp.Auth.Policies)
}
tokenReq.Data = map[string]interface{}{
"policies": []string{"disallowed1"},
}
resp, err = ts.HandleRequest(context.Background(), tokenReq)
if err == nil {
t.Fatalf("expected an error")
}
roleReq.Operation = logical.UpdateOperation
roleReq.Data = map[string]interface{}{
"allowed_policies": "allowed1,common",
"disallowed_policies": "disallowed1,common",
}
resp, err = ts.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v, resp: %v", err, resp)
}
tokenReq.Data = map[string]interface{}{
"policies": []string{"allowed1", "common"},
}
resp, err = ts.HandleRequest(context.Background(), tokenReq)
if err == nil {
t.Fatalf("expected an error")
}
}
// Issue 2189
func TestTokenStore_RevokeUseCountToken(t *testing.T) {
var resp *logical.Response
var err error
cubbyFuncLock := &sync.RWMutex{}
cubbyFuncLock.Lock()
exp := mockExpiration(t)
ts := exp.tokenStore
root, _ := exp.tokenStore.rootToken(context.Background())
tokenReq := &logical.Request{
Path: "create",
ClientToken: root.ID,
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"num_uses": 1,
},
}
resp, err = ts.HandleRequest(context.Background(), tokenReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
tut := resp.Auth.ClientToken
saltTut, err := ts.SaltID(context.Background(), tut)
if err != nil {
t.Fatal(err)
}
te, err := ts.lookupSalted(context.Background(), saltTut, false)
if err != nil {
t.Fatal(err)
}
if te == nil {
t.Fatal("nil entry")
}
if te.NumUses != 1 {
t.Fatalf("bad: %d", te.NumUses)
}
te, err = ts.UseToken(context.Background(), te)
if err != nil {
t.Fatal(err)
}
if te == nil {
t.Fatal("nil entry")
}
if te.NumUses != tokenRevocationPending {
t.Fatalf("bad: %d", te.NumUses)
}
// Should return no entry because it's tainted
te, err = ts.lookupSalted(context.Background(), saltTut, false)
if err != nil {
t.Fatal(err)
}
if te != nil {
t.Fatalf("%#v", te)
}
// But it should show up in an API lookup call
req := &logical.Request{
Path: "lookup-self",
ClientToken: tut,
Operation: logical.UpdateOperation,
}
resp, err = ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatal(err)
}
if resp == nil || resp.Data == nil || resp.Data["num_uses"] == nil {
t.Fatal("nil resp or data")
}
if resp.Data["num_uses"].(int) != -1 {
t.Fatalf("bad: %v", resp.Data["num_uses"])
}
// Should return tainted entries
te, err = ts.lookupSalted(context.Background(), saltTut, true)
if err != nil {
t.Fatal(err)
}
if te == nil {
t.Fatal("nil entry")
}
if te.NumUses != tokenRevocationPending {
t.Fatalf("bad: %d", te.NumUses)
}
origDestroyCubbyhole := ts.cubbyholeDestroyer
ts.cubbyholeDestroyer = func(context.Context, *TokenStore, string) error {
return fmt.Errorf("keep it frosty")
}
err = ts.revokeSalted(context.Background(), saltTut, false)
if err == nil {
t.Fatalf("expected err")
}
// Since revocation failed we should still be able to get a token
te, err = ts.lookupSalted(context.Background(), saltTut, true)
if err != nil {
t.Fatal(err)
}
if te == nil {
t.Fatal("nil token entry")
}
// Check the race condition situation by making the process sleep
ts.cubbyholeDestroyer = func(context.Context, *TokenStore, string) error {
time.Sleep(1 * time.Second)
return fmt.Errorf("keep it frosty")
}
cubbyFuncLock.Unlock()
go func() {
cubbyFuncLock.RLock()
err := ts.revokeSalted(context.Background(), saltTut, false)
cubbyFuncLock.RUnlock()
if err == nil {
t.Fatalf("expected error")
}
}()
// Give time for the function to start and grab locks
time.Sleep(200 * time.Millisecond)
te, err = ts.lookupSalted(context.Background(), saltTut, true)
if err != nil {
t.Fatal(err)
}
if te == nil {
t.Fatal("nil token entry")
}
// Let things catch up
time.Sleep(2 * time.Second)
// Put back to normal
cubbyFuncLock.Lock()
defer cubbyFuncLock.Unlock()
ts.cubbyholeDestroyer = origDestroyCubbyhole
err = ts.revokeSalted(context.Background(), saltTut, false)
if err != nil {
t.Fatal(err)
}
te, err = ts.lookupSalted(context.Background(), saltTut, true)
if err != nil {
t.Fatal(err)
}
if te != nil {
t.Fatal("found entry")
}
}
// Create a token, delete the token entry while leaking accessors, invoke tidy
// and check if the dangling accessor entry is getting removed
func TestTokenStore_HandleTidyCase1(t *testing.T) {
var resp *logical.Response
var err error
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
// List the number of accessors. Since there is only root token
// present, the list operation should return only one key.
accessorListReq := &logical.Request{
Operation: logical.ListOperation,
Path: "accessors/",
ClientToken: root,
}
resp, err = ts.HandleRequest(context.Background(), accessorListReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
numberOfAccessors := len(resp.Data["keys"].([]string))
if numberOfAccessors != 1 {
t.Fatalf("bad: number of accessors. Expected: 1, Actual: %d", numberOfAccessors)
}
for i := 1; i <= 100; i++ {
// Create a regular token
tokenReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "create",
ClientToken: root,
Data: map[string]interface{}{
"policies": []string{"policy1"},
},
}
resp := testMakeTokenViaRequest(t, ts, tokenReq)
tut := resp.Auth.ClientToken
// Creation of another token should end up with incrementing
// the number of accessors
// the storage
resp, err = ts.HandleRequest(context.Background(), accessorListReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
numberOfAccessors = len(resp.Data["keys"].([]string))
if numberOfAccessors != i+1 {
t.Fatalf("bad: number of accessors. Expected: %d, Actual: %d", i+1, numberOfAccessors)
}
// Revoke the token while leaking other items associated with the
// token. Do this by doing what revokeSalted used to do before it was
// fixed, i.e., by deleting the storage entry for token and its
// cubbyhole and by not deleting its secondary index, its accessor and
// associated leases.
saltedTut, err := ts.SaltID(context.Background(), tut)
if err != nil {
t.Fatal(err)
}
_, err = ts.lookupSalted(context.Background(), saltedTut, true)
if err != nil {
t.Fatalf("failed to lookup token: %v", err)
}
// Destroy the token index
path := lookupPrefix + saltedTut
if ts.view.Delete(context.Background(), path); err != nil {
t.Fatalf("failed to delete token entry: %v", err)
}
// Destroy the cubby space
err = ts.destroyCubbyhole(context.Background(), saltedTut)
if err != nil {
t.Fatalf("failed to destroyCubbyhole: %v", err)
}
// Leaking of accessor should have resulted in no change to the number
// of accessors
resp, err = ts.HandleRequest(context.Background(), accessorListReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
numberOfAccessors = len(resp.Data["keys"].([]string))
if numberOfAccessors != i+1 {
t.Fatalf("bad: number of accessors. Expected: %d, Actual: %d", i+1, numberOfAccessors)
}
}
tidyReq := &logical.Request{
Path: "tidy",
Operation: logical.UpdateOperation,
ClientToken: root,
}
resp, err = ts.HandleRequest(context.Background(), tidyReq)
if err != nil {
t.Fatal(err)
}
if resp != nil && resp.IsError() {
t.Fatalf("resp: %#v", resp)
}
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
// Tidy should have removed all the dangling accessor entries
resp, err = ts.HandleRequest(context.Background(), accessorListReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
numberOfAccessors = len(resp.Data["keys"].([]string))
if numberOfAccessors != 1 {
t.Fatalf("bad: number of accessors. Expected: 1, Actual: %d", numberOfAccessors)
}
}
// Create a set of tokens along with a child token for each of them, delete the
// token entry while leaking accessors, invoke tidy and check if the dangling
// accessor entry is getting removed and check if child tokens are still present
// and turned into orphan tokens.
func TestTokenStore_HandleTidy_parentCleanup(t *testing.T) {
var resp *logical.Response
var err error
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
// List the number of accessors. Since there is only root token
// present, the list operation should return only one key.
accessorListReq := &logical.Request{
Operation: logical.ListOperation,
Path: "accessors/",
ClientToken: root,
}
resp, err = ts.HandleRequest(context.Background(), accessorListReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
numberOfAccessors := len(resp.Data["keys"].([]string))
if numberOfAccessors != 1 {
t.Fatalf("bad: number of accessors. Expected: 1, Actual: %d", numberOfAccessors)
}
for i := 1; i <= 100; i++ {
// Create a token
tokenReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "create",
ClientToken: root,
Data: map[string]interface{}{
"policies": []string{"policy1"},
},
}
resp := testMakeTokenViaRequest(t, ts, tokenReq)
tut := resp.Auth.ClientToken
// Create a child token
tokenReq = &logical.Request{
Operation: logical.UpdateOperation,
Path: "create",
ClientToken: tut,
Data: map[string]interface{}{
"policies": []string{"policy1"},
},
}
testMakeTokenViaRequest(t, ts, tokenReq)
// Creation of another token should end up with incrementing the number of
// accessors the storage
resp, err = ts.HandleRequest(context.Background(), accessorListReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
numberOfAccessors = len(resp.Data["keys"].([]string))
if numberOfAccessors != (i*2)+1 {
t.Fatalf("bad: number of accessors. Expected: %d, Actual: %d", i+1, numberOfAccessors)
}
// Revoke the token while leaking other items associated with the
// token. Do this by doing what revokeSalted used to do before it was
// fixed, i.e., by deleting the storage entry for token and its
// cubbyhole and by not deleting its secondary index, its accessor and
// associated leases.
saltedTut, err := ts.SaltID(context.Background(), tut)
if err != nil {
t.Fatal(err)
}
_, err = ts.lookupSalted(context.Background(), saltedTut, true)
if err != nil {
t.Fatalf("failed to lookup token: %v", err)
}
// Destroy the token index
path := lookupPrefix + saltedTut
if ts.view.Delete(context.Background(), path); err != nil {
t.Fatalf("failed to delete token entry: %v", err)
}
// Destroy the cubby space
err = ts.destroyCubbyhole(context.Background(), saltedTut)
if err != nil {
t.Fatalf("failed to destroyCubbyhole: %v", err)
}
// Leaking of accessor should have resulted in no change to the number
// of accessors
resp, err = ts.HandleRequest(context.Background(), accessorListReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
numberOfAccessors = len(resp.Data["keys"].([]string))
if numberOfAccessors != (i*2)+1 {
t.Fatalf("bad: number of accessors. Expected: %d, Actual: %d", (i*2)+1, numberOfAccessors)
}
}
tidyReq := &logical.Request{
Path: "tidy",
Operation: logical.UpdateOperation,
ClientToken: root,
}
resp, err = ts.HandleRequest(context.Background(), tidyReq)
if err != nil {
t.Fatal(err)
}
if resp != nil && resp.IsError() {
t.Fatalf("resp: %#v", resp)
}
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
// Tidy should have removed all the dangling accessor entries
resp, err = ts.HandleRequest(context.Background(), accessorListReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
// The number of accessors should be equal to number of valid child tokens
// (100) + the root token (1)
keys := resp.Data["keys"].([]string)
numberOfAccessors = len(keys)
if numberOfAccessors != 101 {
t.Fatalf("bad: number of accessors. Expected: 1, Actual: %d", numberOfAccessors)
}
req := logical.TestRequest(t, logical.UpdateOperation, "lookup-accessor")
for _, accessor := range keys {
req.Data = map[string]interface{}{
"accessor": accessor,
}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp.Data == nil {
t.Fatalf("response should contain data")
}
// These tokens should now be orphaned
if resp.Data["orphan"] != true {
t.Fatalf("token should be orphan")
}
}
}
func TestTokenStore_TidyLeaseRevocation(t *testing.T) {
exp := mockExpiration(t)
ts := exp.tokenStore
noop := &NoopBackend{}
_, barrier, _ := mockBarrier(t)
view := NewBarrierView(barrier, "logical/")
meUUID, err := uuid.GenerateUUID()
if err != nil {
t.Fatal(err)
}
err = exp.router.Mount(noop, "prod/aws/", &MountEntry{UUID: meUUID, Accessor: "awsaccessor"}, view)
if err != nil {
t.Fatal(err)
}
// Create new token
root, err := ts.rootToken(context.Background())
if err != nil {
t.Fatalf("err: %v", err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root.ID
req.Data["policies"] = []string{"default"}
resp, err := ts.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
// Create a new token
auth := &logical.Auth{
ClientToken: resp.Auth.ClientToken,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
Renewable: true,
},
}
err = exp.RegisterAuth("auth/token/create", auth)
if err != nil {
t.Fatalf("err: %v", err)
}
tut := resp.Auth.ClientToken
req = &logical.Request{
Path: "prod/aws/foo",
ClientToken: tut,
}
resp = &logical.Response{
Secret: &logical.Secret{
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
},
},
}
leases := []string{}
for i := 0; i < 10; i++ {
leaseID, err := exp.Register(req, resp)
if err != nil {
t.Fatal(err)
}
leases = append(leases, leaseID)
}
sort.Strings(leases)
storedLeases, err := exp.lookupLeasesByToken(tut)
if err != nil {
t.Fatal(err)
}
sort.Strings(storedLeases)
if !reflect.DeepEqual(leases, storedLeases) {
t.Fatalf("bad: %#v vs %#v", leases, storedLeases)
}
// Now, delete the token entry. The leases should still exist.
saltedTut, err := ts.SaltID(context.Background(), tut)
if err != nil {
t.Fatal(err)
}
te, err := ts.lookupSalted(context.Background(), saltedTut, true)
if err != nil {
t.Fatalf("failed to lookup token: %v", err)
}
if te == nil {
t.Fatal("got nil token entry")
}
// Destroy the token index
path := lookupPrefix + saltedTut
if ts.view.Delete(context.Background(), path); err != nil {
t.Fatalf("failed to delete token entry: %v", err)
}
te, err = ts.lookupSalted(context.Background(), saltedTut, true)
if err != nil {
t.Fatalf("failed to lookup token: %v", err)
}
if te != nil {
t.Fatal("got token entry")
}
// Verify leases still exist
storedLeases, err = exp.lookupLeasesByToken(tut)
if err != nil {
t.Fatal(err)
}
sort.Strings(storedLeases)
if !reflect.DeepEqual(leases, storedLeases) {
t.Fatalf("bad: %#v vs %#v", leases, storedLeases)
}
// Call tidy
ts.handleTidy(context.Background(), nil, nil)
time.Sleep(200 * time.Millisecond)
// Verify leases are gone
storedLeases, err = exp.lookupLeasesByToken(tut)
if err != nil {
t.Fatal(err)
}
if len(storedLeases) > 0 {
t.Fatal("found leases")
}
}