8916f6b625
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.
4076 lines
109 KiB
Go
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")
|
|
}
|
|
}
|