6182 lines
176 KiB
Go
6182 lines
176 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package vault
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"path"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-test/deep"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/hashicorp/errwrap"
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
"github.com/hashicorp/go-sockaddr"
|
|
"github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/helper/benchhelpers"
|
|
"github.com/hashicorp/vault/helper/identity"
|
|
"github.com/hashicorp/vault/helper/metricsutil"
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
"github.com/hashicorp/vault/sdk/helper/locksutil"
|
|
"github.com/hashicorp/vault/sdk/helper/tokenutil"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
func TestTokenStore_CreateOrphanResponse(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "auth/token/create-orphan",
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"policies": "default",
|
|
},
|
|
})
|
|
if err != nil && (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: err: %v, resp: %#v", err, resp)
|
|
}
|
|
if !resp.Auth.Orphan {
|
|
t.Fatalf("failed to set orphan as true in the response")
|
|
}
|
|
}
|
|
|
|
// TestTokenStore_CubbyholeDeletion tests that a token's cubbyhole
|
|
// can be used and that the cubbyhole is removed after the token is revoked.
|
|
func TestTokenStore_CubbyholeDeletion(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
testTokenStore_CubbyholeDeletion(t, c, root)
|
|
}
|
|
|
|
// TestTokenStore_CubbyholeDeletionSSCTokensDisabled tests that a legacy token's
|
|
// cubbyhole can be used, and that the cubbyhole is removed after the token is revoked.
|
|
func TestTokenStore_CubbyholeDeletionSSCTokensDisabled(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.disableSSCTokens = true
|
|
testTokenStore_CubbyholeDeletion(t, c, root)
|
|
}
|
|
|
|
func testTokenStore_CubbyholeDeletion(t *testing.T, c *Core, root string) {
|
|
ts := c.tokenStore
|
|
|
|
for i := 0; i < 10; i++ {
|
|
// Create a token
|
|
tokenReq := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "create",
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"ttl": "600s",
|
|
},
|
|
}
|
|
// Supplying token ID forces SHA1 hashing to be used
|
|
if i%2 == 0 {
|
|
tokenReq.Data = map[string]interface{}{
|
|
"id": "testroot",
|
|
"ttl": "600s",
|
|
}
|
|
}
|
|
resp := testMakeTokenViaRequest(t, ts, tokenReq)
|
|
token := resp.Auth.ClientToken
|
|
|
|
// Write data in the token's cubbyhole
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
ClientToken: token,
|
|
Operation: logical.UpdateOperation,
|
|
Path: "cubbyhole/sample/data",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
|
|
// Revoke the token
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
ClientToken: token,
|
|
Path: "revoke-self",
|
|
Operation: logical.UpdateOperation,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
}
|
|
|
|
// List the cubbyhole keys
|
|
cubbyholeKeys, err := ts.cubbyholeBackend.storageView.List(namespace.RootContext(nil), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// There should be no entries
|
|
if len(cubbyholeKeys) != 0 {
|
|
t.Fatalf("bad: len(cubbyholeKeys); expected: 0, actual: %d", len(cubbyholeKeys))
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_CubbyholeTidy(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
testTokenStore_CubbyholeTidy(t, c, root, namespace.RootContext(nil))
|
|
}
|
|
|
|
func testTokenStore_CubbyholeTidy(t *testing.T, c *Core, root string, nsCtx context.Context) {
|
|
ts := c.tokenStore
|
|
|
|
backend := c.router.MatchingBackend(nsCtx, mountPathCubbyhole)
|
|
view := c.router.MatchingStorageByAPIPath(nsCtx, mountPathCubbyhole)
|
|
|
|
for i := 1; i <= 20; i++ {
|
|
// Create 20 tokens
|
|
tokenReq := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "create",
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"ttl": "600s",
|
|
},
|
|
}
|
|
|
|
resp := testMakeTokenViaRequestContext(t, nsCtx, ts, tokenReq)
|
|
token := resp.Auth.ClientToken
|
|
|
|
// Supplying token ID forces SHA1 hashing to be used
|
|
if i%3 == 0 {
|
|
tokenReq.Data = map[string]interface{}{
|
|
"id": "testroot",
|
|
"ttl": "600s",
|
|
}
|
|
}
|
|
|
|
// Create 4 junk cubbyhole entries
|
|
if i%5 == 0 {
|
|
invalidToken, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
resp, err := backend.HandleRequest(nsCtx, &logical.Request{
|
|
ClientToken: invalidToken,
|
|
Operation: logical.UpdateOperation,
|
|
Path: "cubbyhole/sample/data",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
Storage: view,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
}
|
|
|
|
// Write into cubbyholes of 10 tokens
|
|
if i%2 == 0 {
|
|
continue
|
|
}
|
|
resp, err := c.HandleRequest(nsCtx, &logical.Request{
|
|
ClientToken: token,
|
|
Operation: logical.UpdateOperation,
|
|
Path: "cubbyhole/sample/data",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
}
|
|
|
|
// List all the cubbyhole storage keys
|
|
cubbyholeKeys, err := view.List(nsCtx, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(cubbyholeKeys) != 14 {
|
|
t.Fatalf("bad: len(cubbyholeKeys); expected: 14, actual: %d", len(cubbyholeKeys))
|
|
}
|
|
|
|
// Tidy cubbyhole storage
|
|
resp, err := ts.HandleRequest(nsCtx, &logical.Request{
|
|
Path: "tidy",
|
|
Operation: logical.UpdateOperation,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
|
|
// Wait for tidy operation to complete
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// List all the cubbyhole storage keys
|
|
cubbyholeKeys, err = view.List(nsCtx, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The junk entries must have been cleaned up
|
|
if len(cubbyholeKeys) != 10 {
|
|
t.Fatalf("bad: len(cubbyholeKeys); expected: 10, actual: %d", len(cubbyholeKeys))
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_Salting(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
saltedID, err := ts.SaltID(namespace.RootContext(nil), "foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.HasPrefix(saltedID, "h") {
|
|
t.Fatalf("expected sha1 hash; got sha2-256 hmac")
|
|
}
|
|
|
|
saltedID, err = ts.SaltID(namespace.RootContext(nil), "hvs.foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(saltedID, "h") {
|
|
t.Fatalf("expected sha2-256 hmac; got sha1 hash")
|
|
}
|
|
|
|
nsCtx := namespace.ContextWithNamespace(context.Background(), &namespace.Namespace{ID: "testid", Path: "ns1"})
|
|
saltedID, err = ts.SaltID(nsCtx, "foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(saltedID, "h") {
|
|
t.Fatalf("expected sha2-256 hmac; got sha1 hash")
|
|
}
|
|
|
|
saltedID, err = ts.SaltID(nsCtx, "hvs.foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(saltedID, "h") {
|
|
t.Fatalf("expected sha2-256 hmac; got sha1 hash")
|
|
}
|
|
}
|
|
|
|
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(namespace.RootContext(nil), entry.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
le := &logical.StorageEntry{
|
|
Key: saltedID,
|
|
Value: enc,
|
|
}
|
|
|
|
if err := ts.idView(namespace.RootNamespace).Put(namespace.RootContext(nil), 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,
|
|
}
|
|
// Same as entry from TokenEntryOld, but used for RegisterAuth
|
|
registryEntry := &logical.TokenEntry{
|
|
DisplayName: entry.DisplayName,
|
|
Path: entry.Path,
|
|
Policies: entry.Policies,
|
|
CreationTime: entry.CreationTime,
|
|
ExplicitMaxTTL: entry.ExplicitMaxTTL,
|
|
NumUses: entry.NumUses,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
|
|
if err := ts.expiration.RegisterAuth(namespace.RootContext(nil), registryEntry, auth, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), 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,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
if err := ts.create(namespace.RootContext(nil), 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(namespace.RootContext(nil), ent, auth, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, err = ts.Lookup(namespace.RootContext(nil), 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,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
if err := ts.create(namespace.RootContext(nil), 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(namespace.RootContext(nil), ent, auth, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, err = ts.Lookup(namespace.RootContext(nil), 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,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
if err := ts.create(namespace.RootContext(nil), 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(namespace.RootContext(nil), ent, auth, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, err = ts.Lookup(namespace.RootContext(nil), 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,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
if err := ts.create(namespace.RootContext(nil), 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(namespace.RootContext(nil), ent, auth, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, err = ts.Lookup(namespace.RootContext(nil), 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 testMakeServiceTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string) {
|
|
t.Helper()
|
|
testMakeTokenViaBackend(t, ts, root, client, ttl, policy, false)
|
|
}
|
|
|
|
func testMakeTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string, batch bool) {
|
|
t.Helper()
|
|
req := logical.TestRequest(benchhelpers.TBtoT(t), logical.UpdateOperation, "create")
|
|
req.ClientToken = root
|
|
if batch {
|
|
req.Data["type"] = "batch"
|
|
} else {
|
|
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 {
|
|
t.Helper()
|
|
return testMakeTokenViaRequestContext(t, namespace.RootContext(nil), ts, req)
|
|
}
|
|
|
|
func testMakeTokenViaRequestContext(t testing.TB, ctx context.Context, ts *TokenStore, req *logical.Request) *logical.Response {
|
|
t.Helper()
|
|
resp, err := ts.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatalf("got nil token from create call")
|
|
}
|
|
// Let the caller handle the error
|
|
if resp.IsError() {
|
|
return resp
|
|
}
|
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
te := &logical.TokenEntry{
|
|
Path: resp.Auth.CreationPath,
|
|
NamespaceID: ns.ID,
|
|
}
|
|
|
|
if resp.Auth.TokenType != logical.TokenTypeBatch {
|
|
if err := ts.expiration.RegisterAuth(ctx, te, resp.Auth, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
te, err = ts.Lookup(ctx, resp.Auth.ClientToken)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if te == nil {
|
|
t.Fatal("token entry was nil")
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
func testMakeTokenDirectly(t testing.TB, ts *TokenStore, te *logical.TokenEntry) {
|
|
if te.NamespaceID == "" {
|
|
te.NamespaceID = namespace.RootNamespaceID
|
|
}
|
|
if te.CreationTime == 0 {
|
|
te.CreationTime = time.Now().Unix()
|
|
}
|
|
if err := ts.create(namespace.RootContext(nil), te); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if te.Type == logical.TokenTypeDefault {
|
|
te.Type = logical.TokenTypeService
|
|
}
|
|
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,
|
|
TokenType: te.Type,
|
|
}
|
|
err := ts.expiration.RegisterAuth(namespace.RootContext(nil), te, auth, "")
|
|
switch err {
|
|
case nil:
|
|
if te.Type == logical.TokenTypeBatch {
|
|
t.Fatal("expected error from trying to register auth with batch token")
|
|
}
|
|
default:
|
|
if te.Type != logical.TokenTypeBatch {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testMakeServiceTokenViaCore(t testing.TB, c *Core, root, client, ttl string, policy []string) {
|
|
testMakeTokenViaCore(t, c, root, client, ttl, "", policy, false, nil)
|
|
}
|
|
|
|
func testMakeTokenViaCore(t testing.TB, c *Core, root, client, ttl, period string, policy []string, batch bool, outAuth *logical.Auth) {
|
|
req := logical.TestRequest(benchhelpers.TBtoT(t), logical.UpdateOperation, "auth/token/create")
|
|
req.ClientToken = root
|
|
if batch {
|
|
req.Data["type"] = "batch"
|
|
} else {
|
|
req.Data["id"] = client
|
|
}
|
|
req.Data["policies"] = policy
|
|
req.Data["ttl"] = ttl
|
|
|
|
if len(period) != 0 {
|
|
req.Data["period"] = period
|
|
}
|
|
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
if !batch {
|
|
if resp.Auth.ClientToken != client {
|
|
t.Fatalf("bad: %#v", *resp)
|
|
}
|
|
}
|
|
if outAuth != nil && resp != nil && resp.Auth != nil {
|
|
*outAuth = *resp.Auth
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_AccessorIndex(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
ent := &logical.TokenEntry{
|
|
Path: "test",
|
|
Policies: []string{"dev", "ops"},
|
|
TTL: time.Hour,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent)
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), 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(namespace.RootContext(nil), out.Accessor, false, 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)
|
|
}
|
|
|
|
// Make sure a batch token doesn't get an accessor
|
|
ent.Type = logical.TokenTypeBatch
|
|
testMakeTokenDirectly(t, ts, ent)
|
|
|
|
out, err = ts.Lookup(namespace.RootContext(nil), 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)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_LookupAccessor(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "60s", []string{"foo"})
|
|
out, err := ts.Lookup(namespace.RootContext(nil), "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(namespace.RootContext(nil), 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 {
|
|
testMakeServiceTokenViaBackend(t, ts, root, key, "60s", []string{"foo"})
|
|
}
|
|
|
|
// Revoke root to make the number of accessors match
|
|
internalRoot, _ := c.DecodeSSCToken(root)
|
|
salted, err := ts.SaltID(namespace.RootContext(nil), internalRoot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ts.revokeInternal(namespace.RootContext(nil), salted, false)
|
|
|
|
req := logical.TestRequest(t, logical.ListOperation, "accessors/")
|
|
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), accessor, false, 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")
|
|
}
|
|
saltID, err := ts.SaltID(namespace.RootContext(nil), accessor)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
le := &logical.StorageEntry{Key: saltID, Value: []byte(aEntry.TokenID)}
|
|
if err := ts.accessorView(namespace.RootNamespace).Put(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), accessor, false, 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_Renew_Revoke_Accessor(t *testing.T) {
|
|
exp := mockExpiration(t)
|
|
ts := exp.tokenStore
|
|
|
|
rootToken, err := ts.rootToken(namespace.RootContext(nil))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root := rootToken.ID
|
|
|
|
testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"})
|
|
|
|
auth := &logical.Auth{
|
|
ClientToken: "tokenid",
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: time.Hour,
|
|
Renewable: true,
|
|
},
|
|
}
|
|
|
|
te, err := ts.Lookup(namespace.RootContext(nil), "tokenid")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if te == nil {
|
|
t.Fatal("token entry was nil")
|
|
}
|
|
|
|
err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), "tokenid")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if out == nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
lookupAccessor := func() int64 {
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "lookup-accessor")
|
|
req.Data = map[string]interface{}{
|
|
"accessor": out.Accessor,
|
|
}
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if resp.Data == nil {
|
|
t.Fatal("response should contain data")
|
|
}
|
|
if resp.Data["accessor"].(string) == "" {
|
|
t.Fatal("accessor should not be empty")
|
|
}
|
|
ttl := resp.Data["ttl"].(int64)
|
|
if ttl == 0 {
|
|
t.Fatal("ttl was zero")
|
|
}
|
|
return ttl
|
|
}
|
|
|
|
firstTTL := lookupAccessor()
|
|
|
|
// Sleep so we can verify renew behavior
|
|
time.Sleep(10 * time.Second)
|
|
|
|
secondTTL := lookupAccessor()
|
|
if secondTTL >= firstTTL {
|
|
t.Fatalf("second TTL %d was greater than first %d", secondTTL, firstTTL)
|
|
}
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "renew-accessor")
|
|
req.Data = map[string]interface{}{
|
|
"accessor": out.Accessor,
|
|
}
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if resp.Auth == nil {
|
|
t.Fatal("resp auth is nil")
|
|
}
|
|
if resp.Auth.ClientToken != "" {
|
|
t.Fatal("client token found in response")
|
|
}
|
|
|
|
thirdTTL := lookupAccessor()
|
|
if thirdTTL <= secondTTL {
|
|
t.Fatalf("third TTL %d was not greater than second %d", thirdTTL, secondTTL)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "revoke-accessor")
|
|
req.Data = map[string]interface{}{
|
|
"accessor": out.Accessor,
|
|
}
|
|
|
|
_, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Revoking the token using the accessor should be idempotent since
|
|
// auth/token/revoke is
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if len(resp.Warnings) != 1 {
|
|
t.Fatalf("Was expecting 1 warning, got %d", len(resp.Warnings))
|
|
}
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
out, err = ts.Lookup(namespace.RootContext(nil), "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
|
|
testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"})
|
|
out, err = ts.Lookup(namespace.RootContext(nil), "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(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
out, err = ts.Lookup(namespace.RootContext(nil), "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(namespace.RootContext(nil))
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if te.ID == "" {
|
|
t.Fatalf("missing ID")
|
|
}
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), te.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
deepEqualTokenEntries(t, out, te)
|
|
}
|
|
|
|
func TestTokenStore_NoRootBatch(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
|
|
req.ClientToken = root
|
|
req.Data["type"] = "batch"
|
|
req.Data["policies"] = "root"
|
|
req.Data["ttl"] = "5m"
|
|
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected response")
|
|
}
|
|
if !resp.IsError() {
|
|
t.Fatalf("expected error, got %#v", *resp)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_CreateLookup(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
ent := &logical.TokenEntry{
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Path: "test",
|
|
Policies: []string{"dev", "ops"},
|
|
TTL: time.Hour,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent)
|
|
if ent.ID == "" {
|
|
t.Fatalf("missing ID")
|
|
}
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), ent.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
deepEqualTokenEntries(t, out, ent)
|
|
|
|
// New store should share the salt
|
|
ts2, err := NewTokenStore(namespace.RootContext(nil), 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(namespace.RootContext(nil), ent.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
deepEqualTokenEntries(t, out, ent)
|
|
}
|
|
|
|
func TestTokenStore_CreateLookup_ProvidedID(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
ent := &logical.TokenEntry{
|
|
ID: "foobarbaz",
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
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(namespace.RootContext(nil), ent); err == nil {
|
|
t.Fatal("expected error creating token with the same ID")
|
|
}
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), ent.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
deepEqualTokenEntries(t, out, ent)
|
|
|
|
// New store should share the salt
|
|
ts2, err := NewTokenStore(namespace.RootContext(nil), 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(namespace.RootContext(nil), ent.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
deepEqualTokenEntries(t, out, ent)
|
|
}
|
|
|
|
func TestTokenStore_CreateLookup_ExpirationInRestoreMode(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
ent := &logical.TokenEntry{
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Path: "test",
|
|
Policies: []string{"dev", "ops"},
|
|
}
|
|
if err := ts.create(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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),
|
|
namespace: namespace.RootNamespace,
|
|
}
|
|
if err := ts.expiration.persistEntry(namespace.RootContext(nil), le); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), ent.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
deepEqualTokenEntries(t, out, ent)
|
|
|
|
// Set to expired lease time
|
|
le.ExpireTime = time.Now().Add(-1 * time.Hour)
|
|
if err := ts.expiration.persistEntry(namespace.RootContext(nil), le); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
err = c.stopExpiration()
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), root)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Root is an unlimited use token, should be a no-op
|
|
te, err := ts.UseToken(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent)
|
|
|
|
// Use the token
|
|
te, err = ts.UseToken(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), te.ID)
|
|
|
|
// Lookup the token
|
|
ent2, err = ts.Lookup(namespace.RootContext(nil), 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"}, NamespaceID: namespace.RootNamespaceID}
|
|
if err := ts.create(namespace.RootContext(nil), ent); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
err := ts.revokeOrphan(namespace.RootContext(nil), "")
|
|
if err.Error() != "cannot revoke blank token" {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
err = ts.revokeOrphan(namespace.RootContext(nil), ent.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), 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", namespace: namespace.RootNamespace}, view)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ent := &logical.TokenEntry{Path: "test", Policies: []string{"dev", "ops"}, NamespaceID: namespace.RootNamespaceID}
|
|
if err := ts.create(namespace.RootContext(nil), ent); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Register a lease
|
|
req := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "noop/foo",
|
|
ClientToken: ent.ID,
|
|
}
|
|
req.SetTokenEntry(ent)
|
|
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(namespace.RootContext(nil), req, resp, "")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Revoke the token
|
|
err = ts.revokeOrphan(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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{
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Path: "test",
|
|
Policies: []string{"dev", "ops"},
|
|
TTL: time.Hour,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent)
|
|
|
|
ent2 := &logical.TokenEntry{
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Parent: ent.ID,
|
|
TTL: time.Hour,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent2)
|
|
|
|
err := ts.revokeOrphan(namespace.RootContext(nil), ent.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), ent2.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Unset the expected token parent's ID
|
|
ent2.Parent = ""
|
|
|
|
deepEqualTokenEntries(t, out, ent2)
|
|
}
|
|
|
|
// 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, false)
|
|
testTokenStore_RevokeTree_NonRecursive(t, 2, false)
|
|
testTokenStore_RevokeTree_NonRecursive(t, 10, false)
|
|
|
|
// corrupted trees with cycles
|
|
testTokenStore_RevokeTree_NonRecursive(t, 1, true)
|
|
testTokenStore_RevokeTree_NonRecursive(t, 10, true)
|
|
}
|
|
|
|
// 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, injectCycles bool) {
|
|
c, _, _ := TestCoreUnsealed(benchhelpers.TBtoT(t))
|
|
ts := c.tokenStore
|
|
root, children := buildTokenTree(t, ts, depth)
|
|
|
|
var cyclePaths []string
|
|
if injectCycles {
|
|
// Make the root the parent of itself
|
|
saltedRoot, _ := ts.SaltID(namespace.RootContext(nil), root.ID)
|
|
key := fmt.Sprintf("%s/%s", saltedRoot, saltedRoot)
|
|
cyclePaths = append(cyclePaths, key)
|
|
le := &logical.StorageEntry{Key: key}
|
|
|
|
if err := ts.parentView(namespace.RootNamespace).Put(namespace.RootContext(nil), le); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Make a deep child the parent of a shallow child
|
|
shallow, _ := ts.SaltID(namespace.RootContext(nil), children[0].ID)
|
|
deep, _ := ts.SaltID(namespace.RootContext(nil), children[len(children)-1].ID)
|
|
key = fmt.Sprintf("%s/%s", deep, shallow)
|
|
cyclePaths = append(cyclePaths, key)
|
|
le = &logical.StorageEntry{Key: key}
|
|
|
|
if err := ts.parentView(namespace.RootNamespace).Put(namespace.RootContext(nil), le); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
err := ts.revokeTree(namespace.RootContext(nil), &leaseEntry{})
|
|
if err.Error() != "cannot tree-revoke blank token" {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
saltCtx := namespace.RootContext(nil)
|
|
saltedID, err := c.tokenStore.SaltID(saltCtx, root.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tokenLeaseID := path.Join(root.Path, saltedID)
|
|
|
|
tokenLease, err := ts.expiration.loadEntry(namespace.RootContext(nil), tokenLeaseID)
|
|
if err != nil || tokenLease == nil {
|
|
t.Fatalf("err: %v, tokenLease: %#v", err, tokenLease)
|
|
}
|
|
|
|
// Nuke tree non recursively.
|
|
err = ts.revokeTree(namespace.RootContext(nil), tokenLease)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Append the root to ensure it was successfully
|
|
// deleted.
|
|
children = append(children, root)
|
|
for _, entry := range children {
|
|
out, err := ts.Lookup(namespace.RootContext(nil), entry.ID)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if out != nil {
|
|
t.Fatalf("bad: %#v", out)
|
|
}
|
|
}
|
|
|
|
for _, path := range cyclePaths {
|
|
entry, err := ts.parentView(namespace.RootNamespace).Get(namespace.RootContext(nil), path)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if entry != nil {
|
|
t.Fatalf("expected reference to be deleted: %v", entry)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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, false)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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{
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
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,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
testMakeTokenDirectly(t, ts, left)
|
|
|
|
right := &logical.TokenEntry{
|
|
Parent: node.ID,
|
|
TTL: time.Hour,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
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,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent1)
|
|
|
|
ent2 := &logical.TokenEntry{
|
|
Parent: ent1.ID,
|
|
TTL: time.Hour,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent2)
|
|
|
|
ent3 := &logical.TokenEntry{
|
|
Parent: ent2.ID,
|
|
TTL: time.Hour,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent3)
|
|
|
|
ent4 := &logical.TokenEntry{
|
|
Parent: ent2.ID,
|
|
TTL: time.Hour,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent4)
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-self")
|
|
req.ClientToken = ent1.ID
|
|
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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)
|
|
}
|
|
|
|
// Batch tokens too
|
|
req.Data["type"] = "batch"
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
expected := &logical.TokenEntry{
|
|
ID: resp.Auth.ClientToken,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Accessor: resp.Auth.Accessor,
|
|
Parent: root,
|
|
Policies: []string{"root"},
|
|
Path: "auth/token/create",
|
|
DisplayName: "token-foo-bar-baz",
|
|
TTL: 0,
|
|
Type: logical.TokenTypeService,
|
|
}
|
|
out, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
expected.CreationTime = out.CreationTime
|
|
expected.CubbyholeID = out.CubbyholeID
|
|
if !reflect.DeepEqual(out, expected) {
|
|
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, out)
|
|
}
|
|
}
|
|
|
|
func deepEqualTokenEntries(t *testing.T, a *logical.TokenEntry, b *logical.TokenEntry) {
|
|
if diff := cmp.Diff(a, b, cmpopts.IgnoreFields(logical.TokenEntry{}, "ExternalID")); diff != "" {
|
|
t.Fatalf("bad diff in token entries: %s", diff)
|
|
}
|
|
}
|
|
|
|
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"
|
|
|
|
// Make sure batch tokens can't do limited use counts
|
|
req.Data["type"] = "batch"
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("error handling request: %v", err)
|
|
}
|
|
if resp == nil || !resp.IsError() {
|
|
t.Fatalf("expected response error: resp: %#v ", resp)
|
|
}
|
|
|
|
delete(req.Data, "type")
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
expected := &logical.TokenEntry{
|
|
ID: resp.Auth.ClientToken,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Accessor: resp.Auth.Accessor,
|
|
Parent: root,
|
|
Policies: []string{"root"},
|
|
Path: "auth/token/create",
|
|
DisplayName: "token",
|
|
NumUses: 1,
|
|
TTL: 0,
|
|
Type: logical.TokenTypeService,
|
|
}
|
|
out, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
expected.CreationTime = out.CreationTime
|
|
expected.CubbyholeID = out.CubbyholeID
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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
|
|
|
|
// Make sure batch tokens won't automatically assign root
|
|
req.Data["type"] = "batch"
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("error handling request: %v", err)
|
|
}
|
|
if resp == nil || !resp.IsError() {
|
|
t.Fatalf("expected response error: resp: %#v", resp)
|
|
}
|
|
|
|
delete(req.Data, "type")
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
expected := &logical.TokenEntry{
|
|
ID: resp.Auth.ClientToken,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Accessor: resp.Auth.Accessor,
|
|
Parent: root,
|
|
Policies: []string{"root"},
|
|
Path: "auth/token/create",
|
|
DisplayName: "token",
|
|
TTL: 0,
|
|
Type: logical.TokenTypeService,
|
|
}
|
|
out, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
expected.CreationTime = out.CreationTime
|
|
expected.CubbyholeID = out.CubbyholeID
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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)
|
|
}
|
|
|
|
// Retry with batch; batch should not actually accept a custom ID
|
|
req.Data["type"] = "batch"
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
out, _ := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
|
|
if out.ID == "foobar" {
|
|
t.Fatalf("bad: %#v", out)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_CreateToken_NonRootID(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
testMakeServiceTokenViaBackend(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(namespace.RootContext(nil), 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)
|
|
}
|
|
|
|
// Retry with batch
|
|
req.Data["type"] = "batch"
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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
|
|
testMakeServiceTokenViaBackend(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(namespace.RootContext(nil), 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)
|
|
}
|
|
|
|
ent := &logical.TokenEntry{
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Path: "test",
|
|
Policies: []string{"foo", "bar"},
|
|
TTL: time.Hour,
|
|
}
|
|
testMakeTokenDirectly(t, ts, ent)
|
|
req.ClientToken = ent.ID
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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
|
|
testMakeServiceTokenViaBackend(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(namespace.RootContext(nil), 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(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "test1"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
testMakeServiceTokenViaBackend(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(namespace.RootContext(nil), 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",
|
|
"policies": "root",
|
|
}
|
|
resp := testMakeTokenViaRequest(t, ts, req)
|
|
|
|
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(namespace.RootContext(nil), req)
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
expectedErr := "expiring root tokens cannot create non-expiring root tokens"
|
|
if resp.Data["error"].(string) != expectedErr {
|
|
t.Fatalf("got err=%v, expected=%v", resp.Data["error"], expectedErr)
|
|
}
|
|
}
|
|
|
|
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(namespace.RootContext(nil), 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
|
|
testMakeServiceTokenViaBackend(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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), resp.Auth.ClientToken)
|
|
if !reflect.DeepEqual(out.Meta, meta) {
|
|
t.Fatalf("bad: expected:%#v\nactual:%#v", meta, out.Meta)
|
|
}
|
|
|
|
// Test with batch tokens
|
|
req.Data["type"] = "batch"
|
|
resp = testMakeTokenViaRequest(t, ts, req)
|
|
if resp.Auth.ClientToken == "" {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
out, _ = ts.Lookup(namespace.RootContext(nil), 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)
|
|
}
|
|
|
|
// Test batch tokens
|
|
req.Data["type"] = "batch"
|
|
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_Metric(t *testing.T) {
|
|
c, _, root, sink := TestCoreUnsealedWithMetrics(t)
|
|
ts := c.tokenStore
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
|
req.ClientToken = root
|
|
req.Data["ttl"] = "3h"
|
|
req.Data["policies"] = []string{"foo"}
|
|
req.MountPoint = "test/mount"
|
|
|
|
resp := testMakeTokenViaRequest(t, ts, req)
|
|
if resp.Auth.ClientToken == "" {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
expectSingleCount(t, sink, "token.creation")
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_Revoke(t *testing.T) {
|
|
exp := mockExpiration(t)
|
|
ts := exp.tokenStore
|
|
|
|
rootToken, err := ts.rootToken(namespace.RootContext(nil))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root := rootToken.ID
|
|
|
|
testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "foo"})
|
|
|
|
te, err := ts.Lookup(namespace.RootContext(nil), "child")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if te == nil {
|
|
t.Fatal("token entry was nil")
|
|
}
|
|
|
|
auth := &logical.Auth{
|
|
ClientToken: "child",
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: time.Hour,
|
|
Renewable: true,
|
|
},
|
|
}
|
|
err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "50s", []string{"foo"})
|
|
|
|
te, err = ts.Lookup(namespace.RootContext(nil), "sub-child")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if te == nil {
|
|
t.Fatal("token entry was nil")
|
|
}
|
|
|
|
auth = &logical.Auth{
|
|
ClientToken: "sub-child",
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: time.Hour,
|
|
Renewable: true,
|
|
},
|
|
}
|
|
err = exp.RegisterAuth(namespace.RootContext(nil), te, 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), "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(namespace.RootContext(nil), "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
|
|
testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "foo"})
|
|
testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "50s", []string{"foo"})
|
|
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "revoke")
|
|
req.Data = map[string]interface{}{
|
|
"token": "child",
|
|
}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), "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(namespace.RootContext(nil), "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
|
|
testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"root", "foo"})
|
|
testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "50s", []string{"foo"})
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan")
|
|
req.Data = map[string]interface{}{
|
|
"token": "child",
|
|
}
|
|
req.ClientToken = root
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), "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(namespace.RootContext(nil), "child")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
children, err := ts.idView(namespace.RootNamespace).List(namespace.RootContext(nil), 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(namespace.RootContext(nil), "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
|
|
testMakeServiceTokenViaBackend(t, ts, root, "child", "60s", []string{"foo"})
|
|
|
|
out, err := ts.Lookup(namespace.RootContext(nil), "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(namespace.RootContext(nil), 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(namespace.RootContext(nil), "child")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if out == nil {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_Lookup(t *testing.T) {
|
|
t.Run("service_token", func(t *testing.T) {
|
|
testTokenStoreHandleRequestLookup(t, false, false)
|
|
})
|
|
|
|
t.Run("service_token_periodic", func(t *testing.T) {
|
|
testTokenStoreHandleRequestLookup(t, false, true)
|
|
})
|
|
|
|
t.Run("batch_token", func(t *testing.T) {
|
|
testTokenStoreHandleRequestLookup(t, true, false)
|
|
})
|
|
}
|
|
|
|
func testTokenStoreHandleRequestLookup(t *testing.T, batch, periodic bool) {
|
|
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(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
if resp == nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
internalRoot, _ := c.DecodeSSCToken(root)
|
|
|
|
exp := map[string]interface{}{
|
|
"id": internalRoot,
|
|
"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": "",
|
|
"type": "service",
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
period := ""
|
|
if periodic {
|
|
period = "3600s"
|
|
}
|
|
|
|
outAuth := new(logical.Auth)
|
|
testMakeTokenViaCore(t, c, root, "client", "3600s", period, []string{"foo"}, batch, outAuth)
|
|
|
|
tokenType := "service"
|
|
expID := "client"
|
|
if batch {
|
|
tokenType = "batch"
|
|
expID = outAuth.ClientToken
|
|
}
|
|
|
|
// Test via POST
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "lookup")
|
|
req.Data = map[string]interface{}{
|
|
"token": expID,
|
|
}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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": expID,
|
|
"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": !batch,
|
|
"entity_id": "",
|
|
"type": tokenType,
|
|
}
|
|
if periodic {
|
|
exp["period"] = int64(3600)
|
|
}
|
|
|
|
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 diff := deep.Equal(resp.Data, exp); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
// Test last_renewal_time functionality
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "renew")
|
|
req.Data = map[string]interface{}{
|
|
"token": expID,
|
|
}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
if batch && !resp.IsError() || !batch && resp.IsError() {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
req.Path = "lookup"
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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 !batch {
|
|
if resp.Data["last_renewal_time"].(int64) == 0 {
|
|
t.Fatalf("last_renewal_time was zero")
|
|
}
|
|
} else if _, ok := resp.Data["last_renewal_time"]; ok {
|
|
t.Fatal("expected zero last renewal time")
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_LookupSelf(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
testMakeServiceTokenViaCore(t, c, root, "client", "3600s", []string{"foo"})
|
|
|
|
req := logical.TestRequest(t, logical.ReadOperation, "lookup-self")
|
|
req.ClientToken = "client"
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), 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": "",
|
|
"type": "service",
|
|
}
|
|
|
|
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(namespace.RootContext(nil))
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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_CreateToken_ExistingEntityAlias(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
i := core.identityStore
|
|
ctx := namespace.RootContext(nil)
|
|
testPolicyName := "testpolicy"
|
|
entityAliasName := "testentityalias"
|
|
testRoleName := "test"
|
|
|
|
// Create manually an entity
|
|
resp, err := i.HandleRequest(ctx, &logical.Request{
|
|
Path: "entity",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"name": "testentity",
|
|
"policies": []string{testPolicyName},
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
entityID := resp.Data["id"].(string)
|
|
|
|
// Find mount accessor
|
|
resp, err = core.systemBackend.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
Path: "auth",
|
|
Operation: logical.ReadOperation,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string)
|
|
|
|
// Create manually an entity alias
|
|
resp, err = i.HandleRequest(ctx, &logical.Request{
|
|
Path: "entity-alias",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"name": entityAliasName,
|
|
"canonical_id": entityID,
|
|
"mount_accessor": tokenMountAccessor,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error handling request: %v", err)
|
|
}
|
|
|
|
// Create token role
|
|
resp, err = core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/roles/" + testRoleName,
|
|
ClientToken: root,
|
|
Operation: logical.CreateOperation,
|
|
Data: map[string]interface{}{
|
|
"orphan": true,
|
|
"period": "72h",
|
|
"path_suffix": "happenin",
|
|
"bound_cidrs": []string{"0.0.0.0/0"},
|
|
"allowed_entity_aliases": []string{"test1", "test2", entityAliasName},
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
resp, err = core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/create/" + testRoleName,
|
|
Operation: logical.UpdateOperation,
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"entity_alias": entityAliasName,
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected a response")
|
|
}
|
|
if resp.Auth.EntityID != entityID {
|
|
t.Fatalf("expected %q got %q", entityID, resp.Auth.EntityID)
|
|
}
|
|
|
|
policyFound := false
|
|
for _, policy := range resp.Auth.IdentityPolicies {
|
|
if policy == testPolicyName {
|
|
policyFound = true
|
|
}
|
|
}
|
|
if !policyFound {
|
|
t.Fatalf("Policy %q not derived by entity but should be. Auth %#v", testPolicyName, resp.Auth)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_CreateToken_ExistingEntityAliasMixedCase(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
i := core.identityStore
|
|
ctx := namespace.RootContext(nil)
|
|
testPolicyName := "testpolicy"
|
|
entityAliasName := "tEStEntityAliaS"
|
|
entityAliasNameLower := "testentityalias"
|
|
testRoleName := "test"
|
|
|
|
// Create manually an entity
|
|
resp, err := i.HandleRequest(ctx, &logical.Request{
|
|
Path: "entity",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"name": "testentity",
|
|
"policies": []string{testPolicyName},
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
entityID := resp.Data["id"].(string)
|
|
|
|
// Find mount accessor
|
|
resp, err = core.systemBackend.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
Path: "auth",
|
|
Operation: logical.ReadOperation,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string)
|
|
|
|
// Create manually an entity alias
|
|
resp, err = i.HandleRequest(ctx, &logical.Request{
|
|
Path: "entity-alias",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"name": entityAliasName,
|
|
"canonical_id": entityID,
|
|
"mount_accessor": tokenMountAccessor,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error handling request: %v", err)
|
|
}
|
|
|
|
// Create token role
|
|
resp, err = core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/roles/" + testRoleName,
|
|
ClientToken: root,
|
|
Operation: logical.CreateOperation,
|
|
Data: map[string]interface{}{
|
|
"orphan": true,
|
|
"period": "72h",
|
|
"path_suffix": "happenin",
|
|
"bound_cidrs": []string{"0.0.0.0/0"},
|
|
"allowed_entity_aliases": []string{"test1", "test2", entityAliasName},
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
respMixedCase, err := core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/create/" + testRoleName,
|
|
Operation: logical.UpdateOperation,
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"entity_alias": entityAliasName,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error handling request: %v", err)
|
|
}
|
|
if respMixedCase.Auth.EntityID != entityID {
|
|
t.Fatalf("expected %q got %q", entityID, respMixedCase.Auth.EntityID)
|
|
}
|
|
|
|
// lowercase entity alias should match a mixed case alias
|
|
respLowerCase, err := core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/create/" + testRoleName,
|
|
Operation: logical.UpdateOperation,
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"entity_alias": entityAliasNameLower,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error handling request: %v", err)
|
|
}
|
|
|
|
// A token created with the mixed case alias should return the same entity
|
|
// id as the normal case response.
|
|
if respLowerCase.Auth.EntityID != entityID {
|
|
t.Fatalf("expected %q got %q", entityID, respLowerCase.Auth.EntityID)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_CreateToken_NonExistingEntityAlias(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
i := core.identityStore
|
|
ctx := namespace.RootContext(nil)
|
|
entityAliasName := "testentityalias"
|
|
testRoleName := "test"
|
|
|
|
// Create token role
|
|
resp, err := core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/roles/" + testRoleName,
|
|
ClientToken: root,
|
|
Operation: logical.CreateOperation,
|
|
Data: map[string]interface{}{
|
|
"period": "72h",
|
|
"path_suffix": "happenin",
|
|
"bound_cidrs": []string{"0.0.0.0/0"},
|
|
"allowed_entity_aliases": []string{"test1", "test2", entityAliasName},
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
// Create token with non existing entity alias
|
|
resp, err = core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/create/" + testRoleName,
|
|
Operation: logical.UpdateOperation,
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"entity_alias": entityAliasName,
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected a response")
|
|
}
|
|
|
|
// Read the new entity
|
|
resp, err = i.HandleRequest(ctx, &logical.Request{
|
|
Path: "entity/id/" + resp.Auth.EntityID,
|
|
Operation: logical.ReadOperation,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
|
|
// Get the attached alias information
|
|
aliases := resp.Data["aliases"].([]interface{})
|
|
if len(aliases) != 1 {
|
|
t.Fatalf("expected only one alias but got %d; Aliases: %#v", len(aliases), aliases)
|
|
}
|
|
alias := &identity.Alias{}
|
|
if err := mapstructure.Decode(aliases[0], alias); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Validate
|
|
if alias.Name != entityAliasName {
|
|
t.Fatalf("alias name should be %q but is %q", entityAliasName, alias.Name)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_CreateToken_GlobPatternWildcardEntityAlias(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
i := core.identityStore
|
|
ctx := namespace.RootContext(nil)
|
|
testRoleName := "test"
|
|
|
|
tests := []struct {
|
|
name string
|
|
globPattern string
|
|
aliasName string
|
|
}{
|
|
{
|
|
name: "prefix-asterisk",
|
|
globPattern: "*-web",
|
|
aliasName: "department-web",
|
|
},
|
|
{
|
|
name: "suffix-asterisk",
|
|
globPattern: "web-*",
|
|
aliasName: "web-department",
|
|
},
|
|
{
|
|
name: "middle-asterisk",
|
|
globPattern: "web-*-web",
|
|
aliasName: "web-department-web",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// Create token role
|
|
resp, err := core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/roles/" + testRoleName,
|
|
ClientToken: root,
|
|
Operation: logical.CreateOperation,
|
|
Data: map[string]interface{}{
|
|
"period": "72h",
|
|
"path_suffix": "happening",
|
|
"bound_cidrs": []string{"0.0.0.0/0"},
|
|
"allowed_entity_aliases": []string{"test1", "test2", test.globPattern},
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
// Create token with non existing entity alias
|
|
resp, err = core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/create/" + testRoleName,
|
|
Operation: logical.UpdateOperation,
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"entity_alias": test.aliasName,
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected a response")
|
|
}
|
|
|
|
// Read the new entity
|
|
resp, err = i.HandleRequest(ctx, &logical.Request{
|
|
Path: "entity/id/" + resp.Auth.EntityID,
|
|
Operation: logical.ReadOperation,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
|
|
// Get the attached alias information
|
|
aliases := resp.Data["aliases"].([]interface{})
|
|
if len(aliases) != 1 {
|
|
t.Fatalf("expected only one alias but got %d; Aliases: %#v", len(aliases), aliases)
|
|
}
|
|
alias := &identity.Alias{}
|
|
if err := mapstructure.Decode(aliases[0], alias); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Validate
|
|
if alias.Name != test.aliasName {
|
|
t.Fatalf("alias name should be %q but is %q", test.aliasName, alias.Name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_CreateToken_NotAllowedEntityAlias(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
i := core.identityStore
|
|
ctx := namespace.RootContext(nil)
|
|
testPolicyName := "testpolicy"
|
|
entityAliasName := "testentityalias"
|
|
testRoleName := "test"
|
|
|
|
// Create manually an entity
|
|
resp, err := i.HandleRequest(ctx, &logical.Request{
|
|
Path: "entity",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"name": "testentity",
|
|
"policies": []string{testPolicyName},
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
entityID := resp.Data["id"].(string)
|
|
|
|
// Find mount accessor
|
|
resp, err = core.systemBackend.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth",
|
|
Operation: logical.ReadOperation,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string)
|
|
|
|
// Create manually an entity alias
|
|
resp, err = i.HandleRequest(ctx, &logical.Request{
|
|
Path: "entity-alias",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"name": entityAliasName,
|
|
"canonical_id": entityID,
|
|
"mount_accessor": tokenMountAccessor,
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
// Create token role
|
|
resp, err = core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/roles/" + testRoleName,
|
|
ClientToken: root,
|
|
Operation: logical.CreateOperation,
|
|
Data: map[string]interface{}{
|
|
"period": "72h",
|
|
"allowed_entity_aliases": []string{"test1", "test2", "testentityaliasn"},
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
resp, _ = core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/create/" + testRoleName,
|
|
Operation: logical.UpdateOperation,
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"entity_alias": entityAliasName,
|
|
},
|
|
})
|
|
if resp == nil || resp.Data == nil {
|
|
t.Fatal("expected a response")
|
|
}
|
|
if resp.Data["error"] != "invalid 'entity_alias' value" {
|
|
t.Fatalf("wrong error returned. Err: %s", resp.Data["error"])
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_CreateToken_NoRoleEntityAlias(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
ctx := namespace.RootContext(nil)
|
|
entityAliasName := "testentityalias"
|
|
|
|
resp, _ := core.HandleRequest(ctx, &logical.Request{
|
|
Path: "auth/token/create",
|
|
Operation: logical.UpdateOperation,
|
|
ClientToken: root,
|
|
Data: map[string]interface{}{
|
|
"entity_alias": entityAliasName,
|
|
},
|
|
})
|
|
if resp == nil || resp.Data == nil {
|
|
t.Fatal("expected a response")
|
|
}
|
|
if resp.Data["error"] != "'entity_alias' is only allowed in combination with token role" {
|
|
t.Fatalf("wrong error returned. Err: %s", resp.Data["error"])
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_HandleRequest_RenewSelf(t *testing.T) {
|
|
exp := mockExpiration(t)
|
|
ts := exp.tokenStore
|
|
|
|
// Create new token
|
|
root, err := ts.rootToken(namespace.RootContext(nil))
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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",
|
|
"bound_cidrs": []string{"0.0.0.0/0"},
|
|
"explicit_max_ttl": "2h",
|
|
"token_num_uses": 123,
|
|
}
|
|
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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,
|
|
"token_period": int64(259200),
|
|
"period": int64(259200),
|
|
"allowed_policies": []string{"test1", "test2"},
|
|
"disallowed_policies": []string{},
|
|
"allowed_policies_glob": []string{},
|
|
"disallowed_policies_glob": []string{},
|
|
"path_suffix": "happenin",
|
|
"explicit_max_ttl": int64(7200),
|
|
"token_explicit_max_ttl": int64(7200),
|
|
"renewable": true,
|
|
"token_type": "default-service",
|
|
"token_num_uses": 123,
|
|
"allowed_entity_aliases": []string(nil),
|
|
"token_no_default_policy": false,
|
|
}
|
|
|
|
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
|
|
t.Fatal("unexpected bound cidrs")
|
|
}
|
|
delete(resp.Data, "bound_cidrs")
|
|
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
|
|
t.Fatal("unexpected bound cidrs")
|
|
}
|
|
delete(resp.Data, "token_bound_cidrs")
|
|
|
|
if diff := deep.Equal(expected, resp.Data); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
// 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,
|
|
"explicit_max_ttl": "80h",
|
|
"token_num_uses": 0,
|
|
"token_no_default_policy": true,
|
|
}
|
|
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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),
|
|
"token_period": int64(284400),
|
|
"allowed_policies": []string{"test3"},
|
|
"disallowed_policies": []string{},
|
|
"allowed_policies_glob": []string{},
|
|
"disallowed_policies_glob": []string{},
|
|
"path_suffix": "happenin",
|
|
"token_explicit_max_ttl": int64(288000),
|
|
"explicit_max_ttl": int64(288000),
|
|
"renewable": false,
|
|
"token_type": "default-service",
|
|
"allowed_entity_aliases": []string(nil),
|
|
"token_no_default_policy": true,
|
|
}
|
|
|
|
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
|
|
t.Fatal("unexpected bound cidrs")
|
|
}
|
|
delete(resp.Data, "bound_cidrs")
|
|
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
|
|
t.Fatal("unexpected bound cidrs")
|
|
}
|
|
delete(resp.Data, "token_bound_cidrs")
|
|
|
|
if diff := deep.Equal(expected, resp.Data); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
// 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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),
|
|
"token_explicit_max_ttl": int64(5),
|
|
"allowed_policies": []string{"test3"},
|
|
"disallowed_policies": []string{},
|
|
"allowed_policies_glob": []string{},
|
|
"disallowed_policies_glob": []string{},
|
|
"path_suffix": "happenin",
|
|
"period": int64(0),
|
|
"token_period": int64(0),
|
|
"renewable": false,
|
|
"token_type": "default-service",
|
|
"allowed_entity_aliases": []string(nil),
|
|
"token_no_default_policy": true,
|
|
}
|
|
|
|
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
|
|
t.Fatal("unexpected bound cidrs")
|
|
}
|
|
delete(resp.Data, "bound_cidrs")
|
|
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
|
|
t.Fatal("unexpected bound cidrs")
|
|
}
|
|
delete(resp.Data, "token_bound_cidrs")
|
|
|
|
if diff := deep.Equal(expected, resp.Data); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
// Update path_suffix and bound_cidrs with empty values
|
|
req.Operation = logical.CreateOperation
|
|
req.Data = map[string]interface{}{
|
|
"path_suffix": "",
|
|
"bound_cidrs": []string{},
|
|
"token_no_default_policy": false,
|
|
}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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,
|
|
"token_explicit_max_ttl": int64(5),
|
|
"explicit_max_ttl": int64(5),
|
|
"allowed_policies": []string{"test3"},
|
|
"disallowed_policies": []string{},
|
|
"allowed_policies_glob": []string{},
|
|
"disallowed_policies_glob": []string{},
|
|
"path_suffix": "",
|
|
"period": int64(0),
|
|
"token_period": int64(0),
|
|
"renewable": false,
|
|
"token_type": "default-service",
|
|
"allowed_entity_aliases": []string(nil),
|
|
"token_no_default_policy": false,
|
|
}
|
|
|
|
if diff := deep.Equal(expected, resp.Data); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
req.Operation = logical.ListOperation
|
|
req.Path = "auth/token/roles"
|
|
req.Data = map[string]interface{}{}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), 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 %q", keys[0])
|
|
}
|
|
|
|
req.Operation = logical.DeleteOperation
|
|
req.Path = "auth/token/roles/test"
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), roleReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%v resp:%v", err, resp)
|
|
}
|
|
|
|
roleReq.Operation = logical.ReadOperation
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "test1"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "test2"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "test3"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%v resp:%v", err, resp)
|
|
}
|
|
|
|
// policy containing a glob character in the non-glob disallowed_policies field
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "roles/testglobdisabled")
|
|
req.ClientToken = root
|
|
req.Data = map[string]interface{}{
|
|
"disallowed_policies": "test*",
|
|
}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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
|
|
|
|
// Test that the parent token's policies are rejected by disallowed_policies
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
|
|
req.ClientToken = parentToken
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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)
|
|
|
|
// Check to be sure 'test*' without globbing matches 'test*'
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "create/testglobdisabled")
|
|
req.Data["policies"] = []string{"test*"}
|
|
req.ClientToken = parentToken
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || resp != nil && !resp.IsError() {
|
|
t.Fatalf("expected an error response, got %#v", resp)
|
|
}
|
|
|
|
// Check to be sure 'test*' without globbing doesn't match 'test1' or 'test'
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "create/testglobdisabled")
|
|
req.Data["policies"] = []string{"test1", "test"}
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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)
|
|
}
|
|
|
|
// test not glob matching when using allowed_policies instead of allowed_policies_glob
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "roles/testnoglob")
|
|
req.ClientToken = root
|
|
req.Data = map[string]interface{}{
|
|
"allowed_policies": "test*",
|
|
}
|
|
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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/testnoglob"
|
|
req.Data["policies"] = []string{"test"}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
|
|
req.Data["policies"] = []string{"testfoo"}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
|
|
req.Data["policies"] = []string{"test*"}
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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_RoleDisallowedPoliciesGlob(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 4 different policies
|
|
policy, _ := ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "test1"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "test2"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "test3"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "test3b"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), 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_glob": "test1",
|
|
}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%v resp:%v", err, resp)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "roles/testnot23")
|
|
req.ClientToken = root
|
|
req.Data = map[string]interface{}{
|
|
"disallowed_policies_glob": "test2,test3*",
|
|
}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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", "test3b"}
|
|
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
|
|
|
|
// Test that the parent token's policies are rejected by disallowed_policies
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
|
|
req.ClientToken = parentToken
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || resp != nil && !resp.IsError() {
|
|
t.Fatalf("expected an error response, got %#v", resp)
|
|
}
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
|
|
req.ClientToken = parentToken
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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/test1")
|
|
req.Data["policies"] = []string{"foo", "bar"}
|
|
req.ClientToken = parentToken
|
|
testMakeTokenViaRequest(t, ts, req)
|
|
|
|
// Check to be sure 'test3*' matches 'test3'
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
|
|
req.Data["policies"] = []string{"test3"}
|
|
req.ClientToken = parentToken
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || resp != nil && !resp.IsError() {
|
|
t.Fatalf("expected an error response, got %#v", resp)
|
|
}
|
|
|
|
// Check to be sure 'test3*' matches 'test3b'
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
|
|
req.Data["policies"] = []string{"test3b"}
|
|
req.ClientToken = parentToken
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || resp != nil && !resp.IsError() {
|
|
t.Fatalf("expected an error response, got %#v", resp)
|
|
}
|
|
|
|
// Check that non-blacklisted policies still work
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
|
|
req.Data["policies"] = []string{"test1"}
|
|
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_glob": "default",
|
|
}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), req)
|
|
if err == nil || resp != nil && !resp.IsError() {
|
|
t.Fatal("expected an error response")
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_RoleAllowedPoliciesGlob(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
// test literal matching works in allowed_policies_glob
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
|
|
req.ClientToken = root
|
|
req.Data = map[string]interface{}{
|
|
"allowed_policies_glob": "test1,test2",
|
|
}
|
|
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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)
|
|
}
|
|
|
|
// test glob matching in allowed_policies_glob
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "roles/test")
|
|
req.ClientToken = root
|
|
req.Data = map[string]interface{}{
|
|
"allowed_policies_glob": "test*",
|
|
}
|
|
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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"
|
|
req.Data["policies"] = []string{"footest"}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
|
|
req.Data["policies"] = []string{"testfoo", "test2", "test"}
|
|
resp = testMakeTokenViaRequest(t, ts, req)
|
|
if resp.Auth.ClientToken == "" {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
}
|
|
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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.CreateOperation, "roles/test")
|
|
req.ClientToken = root
|
|
req.Data = map[string]interface{}{
|
|
"path_suffix": "happenin",
|
|
}
|
|
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), 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"
|
|
req.Operation = logical.UpdateOperation
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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": 2,
|
|
}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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": 2,
|
|
}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), req)
|
|
if resp != nil && err == nil {
|
|
t.Fatalf("expected error, response is %#v", *resp)
|
|
}
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_RoleTokenFields(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
// c, _, root := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
rootContext := namespace.RootContext(context.Background())
|
|
|
|
boundCIDRs, err := parseutil.ParseAddrs([]string{"127.0.0.1/32"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// First test the upgrade case. Create a role with values and ensure they
|
|
// are reflected properly on read.
|
|
{
|
|
roleEntry := &tsRoleEntry{
|
|
Name: "test",
|
|
TokenParams: tokenutil.TokenParams{
|
|
TokenType: logical.TokenTypeBatch,
|
|
},
|
|
Period: time.Second,
|
|
ExplicitMaxTTL: time.Hour,
|
|
}
|
|
roleEntry.BoundCIDRs = boundCIDRs
|
|
ns := namespace.RootNamespace
|
|
jsonEntry, err := logical.StorageEntryJSON("test", roleEntry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ts.rolesView(ns).Put(rootContext, jsonEntry); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Read it back
|
|
roleEntry, err = ts.tokenStoreRole(rootContext, "test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expRoleEntry := &tsRoleEntry{
|
|
Name: "test",
|
|
TokenParams: tokenutil.TokenParams{
|
|
TokenPeriod: time.Second,
|
|
TokenExplicitMaxTTL: time.Hour,
|
|
TokenBoundCIDRs: boundCIDRs,
|
|
TokenType: logical.TokenTypeBatch,
|
|
},
|
|
Period: time.Second,
|
|
ExplicitMaxTTL: time.Hour,
|
|
BoundCIDRs: boundCIDRs,
|
|
}
|
|
if diff := deep.Equal(expRoleEntry, roleEntry); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|
|
|
|
// Now, read that back through the API and verify we see what we expect
|
|
{
|
|
req := logical.TestRequest(t, logical.ReadOperation, "roles/test")
|
|
resp, err := ts.HandleRequest(rootContext, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"name": "test",
|
|
"orphan": false,
|
|
"period": int64(1),
|
|
"token_period": int64(1),
|
|
"allowed_policies": []string(nil),
|
|
"disallowed_policies": []string(nil),
|
|
"allowed_policies_glob": []string(nil),
|
|
"disallowed_policies_glob": []string(nil),
|
|
"path_suffix": "",
|
|
"token_explicit_max_ttl": int64(3600),
|
|
"explicit_max_ttl": int64(3600),
|
|
"renewable": false,
|
|
"token_type": "batch",
|
|
"allowed_entity_aliases": []string(nil),
|
|
"token_no_default_policy": false,
|
|
}
|
|
|
|
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
|
|
t.Fatalf("unexpected bound cidrs: %s", resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
|
|
}
|
|
delete(resp.Data, "bound_cidrs")
|
|
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
|
|
t.Fatalf("unexpected token bound cidrs: %s", resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
|
|
}
|
|
delete(resp.Data, "token_bound_cidrs")
|
|
|
|
if diff := deep.Equal(expected, resp.Data); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|
|
|
|
// Put values in just the old locations, but through the API
|
|
{
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
|
|
req.Data = map[string]interface{}{
|
|
"explicit_max_ttl": 7200,
|
|
"token_type": "default-service",
|
|
"period": 5,
|
|
"bound_cidrs": boundCIDRs[0].String(),
|
|
}
|
|
|
|
resp, err := ts.HandleRequest(rootContext, 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.ReadOperation, "roles/test")
|
|
resp, err = ts.HandleRequest(rootContext, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"name": "test",
|
|
"orphan": false,
|
|
"period": int64(5),
|
|
"token_period": int64(5),
|
|
"allowed_policies": []string(nil),
|
|
"disallowed_policies": []string(nil),
|
|
"allowed_policies_glob": []string(nil),
|
|
"disallowed_policies_glob": []string(nil),
|
|
"path_suffix": "",
|
|
"token_explicit_max_ttl": int64(7200),
|
|
"explicit_max_ttl": int64(7200),
|
|
"renewable": false,
|
|
"token_type": "default-service",
|
|
"allowed_entity_aliases": []string(nil),
|
|
"token_no_default_policy": false,
|
|
}
|
|
|
|
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
|
|
t.Fatalf("unexpected bound cidrs: %s", resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
|
|
}
|
|
delete(resp.Data, "bound_cidrs")
|
|
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
|
|
t.Fatalf("unexpected token bound cidrs: %s", resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
|
|
}
|
|
delete(resp.Data, "token_bound_cidrs")
|
|
|
|
if diff := deep.Equal(expected, resp.Data); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|
|
// Same thing for just the new locations
|
|
{
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
|
|
req.Data = map[string]interface{}{
|
|
"token_explicit_max_ttl": 5200,
|
|
"token_type": "default-service",
|
|
"token_period": 7,
|
|
"token_bound_cidrs": boundCIDRs[0].String(),
|
|
}
|
|
|
|
resp, err := ts.HandleRequest(rootContext, 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.ReadOperation, "roles/test")
|
|
resp, err = ts.HandleRequest(rootContext, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"name": "test",
|
|
"orphan": false,
|
|
"period": int64(0),
|
|
"token_period": int64(7),
|
|
"allowed_policies": []string(nil),
|
|
"disallowed_policies": []string(nil),
|
|
"allowed_policies_glob": []string(nil),
|
|
"disallowed_policies_glob": []string(nil),
|
|
"path_suffix": "",
|
|
"token_explicit_max_ttl": int64(5200),
|
|
"explicit_max_ttl": int64(0),
|
|
"renewable": false,
|
|
"token_type": "default-service",
|
|
"allowed_entity_aliases": []string(nil),
|
|
"token_no_default_policy": false,
|
|
}
|
|
|
|
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
|
|
t.Fatalf("unexpected token bound cidrs: %s", resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
|
|
}
|
|
delete(resp.Data, "token_bound_cidrs")
|
|
|
|
if diff := deep.Equal(expected, resp.Data); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|
|
// Put values in both locations
|
|
{
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
|
|
req.Data = map[string]interface{}{
|
|
"token_explicit_max_ttl": 7200,
|
|
"explicit_max_ttl": 5200,
|
|
"token_type": "service",
|
|
"token_period": 5,
|
|
"period": 1,
|
|
"token_bound_cidrs": boundCIDRs[0].String(),
|
|
"bound_cidrs": boundCIDRs[0].String(),
|
|
}
|
|
|
|
resp, err := ts.HandleRequest(rootContext, req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
if resp == nil {
|
|
t.Fatalf("expected a non-nil response")
|
|
}
|
|
if len(resp.Warnings) != 3 {
|
|
t.Fatalf("expected 3 warnings, got %#v", resp.Warnings)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.ReadOperation, "roles/test")
|
|
resp, err = ts.HandleRequest(rootContext, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"name": "test",
|
|
"orphan": false,
|
|
"period": int64(0),
|
|
"token_period": int64(5),
|
|
"allowed_policies": []string(nil),
|
|
"disallowed_policies": []string(nil),
|
|
"allowed_policies_glob": []string(nil),
|
|
"disallowed_policies_glob": []string(nil),
|
|
"path_suffix": "",
|
|
"token_explicit_max_ttl": int64(7200),
|
|
"explicit_max_ttl": int64(0),
|
|
"renewable": false,
|
|
"token_type": "service",
|
|
"allowed_entity_aliases": []string(nil),
|
|
"token_no_default_policy": false,
|
|
}
|
|
|
|
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
|
|
t.Fatalf("unexpected token bound cidrs: %s", resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String())
|
|
}
|
|
delete(resp.Data, "token_bound_cidrs")
|
|
|
|
if diff := deep.Equal(expected, resp.Data); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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_NumUses_ErrorCheckHelper(t *testing.T, resp *logical.Response, err error) {
|
|
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)
|
|
}
|
|
}
|
|
|
|
func testTokenStore_NumUses_SelfLookupHelper(t *testing.T, core *Core, clientToken string, expectedNumUses int) {
|
|
req := logical.TestRequest(t, logical.ReadOperation, "auth/token/lookup-self")
|
|
req.ClientToken = clientToken
|
|
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
// Just used the token, this should decrement the num_uses counter
|
|
expectedNumUses = expectedNumUses - 1
|
|
actualNumUses := resp.Data["num_uses"].(int)
|
|
|
|
if actualNumUses != expectedNumUses {
|
|
t.Fatalf("num_uses mismatch (expected %d, got %d)", expectedNumUses, actualNumUses)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_NumUses(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
roleNumUses := 10
|
|
lesserNumUses := 5
|
|
greaterNumUses := 15
|
|
|
|
// Create a test role with limited token_num_uses
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test-limited-uses")
|
|
req.ClientToken = root
|
|
req.Data = map[string]interface{}{
|
|
"token_num_uses": roleNumUses,
|
|
}
|
|
resp, err := core.HandleRequest(namespace.RootContext(nil), 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")
|
|
}
|
|
|
|
// Create a test role with unlimited token_num_uses
|
|
req.Path = "auth/token/roles/test-unlimited-uses"
|
|
req.Data = map[string]interface{}{}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), 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")
|
|
}
|
|
|
|
// Generate some tokens from the test roles
|
|
req.Path = "auth/token/create/test-limited-uses"
|
|
|
|
// first token, num_uses is expected to come from the limited uses role
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
|
|
noOverrideToken := resp.Auth.ClientToken
|
|
|
|
// second token, override num_uses with a lesser value, this should become the value
|
|
// applied to the token
|
|
req.Data = map[string]interface{}{
|
|
"num_uses": lesserNumUses,
|
|
}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
|
|
lesserOverrideToken := resp.Auth.ClientToken
|
|
|
|
// third token, override num_uses with a greater value, the value
|
|
// applied to the token should come from the limited uses role
|
|
req.Data = map[string]interface{}{
|
|
"num_uses": greaterNumUses,
|
|
}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
|
|
greaterOverrideToken := resp.Auth.ClientToken
|
|
|
|
// fourth token, override num_uses with a zero value, a num_uses value of zero
|
|
// has an internal meaning of unlimited so num_uses == 1 is actually less than
|
|
// num_uses == 0. In this case, the lesser value of the limited-uses role should be applied.
|
|
req.Data = map[string]interface{}{
|
|
"num_uses": 0,
|
|
}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
|
|
zeroOverrideToken := resp.Auth.ClientToken
|
|
|
|
// fifth token, override num_uses with a value from a role that has unlimited num_uses. num_uses
|
|
// should be the specified num_uses parameter at the create endpoint
|
|
req.Path = "auth/token/create/test-unlimited-uses"
|
|
req.Data = map[string]interface{}{
|
|
"num_uses": lesserNumUses,
|
|
}
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
testTokenStore_NumUses_ErrorCheckHelper(t, resp, err)
|
|
unlimitedRoleOverrideToken := resp.Auth.ClientToken
|
|
|
|
testTokenStore_NumUses_SelfLookupHelper(t, core, noOverrideToken, roleNumUses)
|
|
testTokenStore_NumUses_SelfLookupHelper(t, core, lesserOverrideToken, lesserNumUses)
|
|
testTokenStore_NumUses_SelfLookupHelper(t, core, greaterOverrideToken, roleNumUses)
|
|
testTokenStore_NumUses_SelfLookupHelper(t, core, zeroOverrideToken, roleNumUses)
|
|
testTokenStore_NumUses_SelfLookupHelper(t, core, unlimitedRoleOverrideToken, lesserNumUses)
|
|
}
|
|
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootNamespace, tokenCreationPolicy)
|
|
policy.Name = "policy1"
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), roleReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v, resp: %v", err, resp)
|
|
}
|
|
|
|
delete(tokenReq.Data, "policies")
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil))
|
|
|
|
tokenReq := &logical.Request{
|
|
Path: "create",
|
|
ClientToken: root.ID,
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"num_uses": 1,
|
|
},
|
|
}
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), tokenReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%v resp:%v", err, resp)
|
|
}
|
|
|
|
tut := resp.Auth.ClientToken
|
|
saltTut, err := ts.SaltID(namespace.RootContext(nil), tut)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
te, err := ts.lookupInternal(namespace.RootContext(nil), saltTut, true, 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(namespace.RootContext(nil), 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.lookupInternal(namespace.RootContext(nil), saltTut, true, 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(namespace.RootContext(nil), 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.lookupInternal(namespace.RootContext(nil), saltTut, true, 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, *logical.TokenEntry) error {
|
|
return fmt.Errorf("keep it frosty")
|
|
}
|
|
|
|
err = ts.revokeInternal(namespace.RootContext(nil), saltTut, false)
|
|
if err == nil {
|
|
t.Fatalf("expected err")
|
|
}
|
|
|
|
// Since revocation failed we should still be able to get a token
|
|
te, err = ts.lookupInternal(namespace.RootContext(nil), saltTut, true, 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, *logical.TokenEntry) error {
|
|
time.Sleep(1 * time.Second)
|
|
return fmt.Errorf("keep it frosty")
|
|
}
|
|
cubbyFuncLock.Unlock()
|
|
|
|
errCh := make(chan error)
|
|
defer close(errCh)
|
|
go func() {
|
|
cubbyFuncLock.RLock()
|
|
err := ts.revokeInternal(namespace.RootContext(nil), saltTut, false)
|
|
cubbyFuncLock.RUnlock()
|
|
errCh <- err
|
|
}()
|
|
|
|
// Give time for the function to start and grab locks
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
te, err = ts.lookupInternal(namespace.RootContext(nil), saltTut, true, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if te == nil {
|
|
t.Fatal("nil token entry")
|
|
}
|
|
|
|
err = <-errCh
|
|
if err == nil {
|
|
t.Fatal("expected error on ts.revokeInternal() in anonymous goroutine")
|
|
}
|
|
|
|
// Put back to normal
|
|
cubbyFuncLock.Lock()
|
|
defer cubbyFuncLock.Unlock()
|
|
ts.cubbyholeDestroyer = origDestroyCubbyhole
|
|
|
|
err = ts.revokeInternal(namespace.RootContext(nil), saltTut, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
te, err = ts.lookupInternal(namespace.RootContext(nil), saltTut, true, 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), tut)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
te, err := ts.lookupInternal(namespace.RootContext(nil), saltedTut, true, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to lookup token: %v", err)
|
|
}
|
|
|
|
// Destroy the token index
|
|
if ts.idView(namespace.RootNamespace).Delete(namespace.RootContext(nil), saltedTut); err != nil {
|
|
t.Fatalf("failed to delete token entry: %v", err)
|
|
}
|
|
|
|
// Destroy the cubby space
|
|
err = ts.cubbyholeDestroyer(namespace.RootContext(nil), ts, te)
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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 runs async so give it time
|
|
time.Sleep(10 * time.Second)
|
|
|
|
// Tidy should have removed all the dangling accessor entries
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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(namespace.RootContext(nil), tut)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
te, err := ts.lookupInternal(namespace.RootContext(nil), saltedTut, true, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to lookup token: %v", err)
|
|
}
|
|
|
|
// Destroy the token index
|
|
if ts.idView(namespace.RootNamespace).Delete(namespace.RootContext(nil), saltedTut); err != nil {
|
|
t.Fatalf("failed to delete token entry: %v", err)
|
|
}
|
|
|
|
// Destroy the cubby space
|
|
err = ts.cubbyholeDestroyer(namespace.RootContext(nil), ts, te)
|
|
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(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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 runs async so give it time
|
|
time.Sleep(10 * time.Second)
|
|
|
|
// Tidy should have removed all the dangling accessor entries
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), 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(namespace.RootContext(nil), 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", namespace: namespace.RootNamespace}, view)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create new token
|
|
root, err := ts.rootToken(namespace.RootContext(nil))
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Create a new token
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "create")
|
|
req.ClientToken = root.ID
|
|
req.Data["policies"] = []string{"default"}
|
|
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err: %v\nresp: %#v", err, resp)
|
|
}
|
|
|
|
auth := &logical.Auth{
|
|
ClientToken: resp.Auth.ClientToken,
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: time.Hour,
|
|
Renewable: true,
|
|
},
|
|
}
|
|
|
|
te := &logical.TokenEntry{
|
|
Path: resp.Auth.CreationPath,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
}
|
|
|
|
err = exp.RegisterAuth(namespace.RootContext(nil), te, auth, "")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify token entry through lookup
|
|
testTokenEntry, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if testTokenEntry == nil {
|
|
t.Fatal("token entry was nil")
|
|
}
|
|
|
|
tut := resp.Auth.ClientToken
|
|
|
|
req = &logical.Request{
|
|
Path: "prod/aws/foo",
|
|
ClientToken: tut,
|
|
}
|
|
req.SetTokenEntry(testTokenEntry)
|
|
|
|
resp = &logical.Response{
|
|
Secret: &logical.Secret{
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: time.Hour,
|
|
},
|
|
},
|
|
}
|
|
|
|
leases := []string{}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
leaseID, err := exp.Register(namespace.RootContext(nil), req, resp, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
leases = append(leases, leaseID)
|
|
}
|
|
|
|
sort.Strings(leases)
|
|
|
|
te, err = ts.lookupInternal(namespace.RootContext(nil), tut, false, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to lookup token: %v", err)
|
|
}
|
|
if te == nil {
|
|
t.Fatal("got nil token entry")
|
|
}
|
|
|
|
storedLeases, err := exp.lookupLeasesByToken(namespace.RootContext(nil), te)
|
|
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(namespace.RootContext(nil), tut)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
te, err = ts.lookupInternal(namespace.RootContext(nil), saltedTut, true, 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
|
|
if ts.idView(namespace.RootNamespace).Delete(namespace.RootContext(nil), saltedTut); err != nil {
|
|
t.Fatalf("failed to delete token entry: %v", err)
|
|
}
|
|
te, err = ts.lookupInternal(namespace.RootContext(nil), saltedTut, true, 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(namespace.RootContext(nil), testTokenEntry)
|
|
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(namespace.RootContext(nil), &logical.Request{}, nil)
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Verify leases are gone
|
|
storedLeases, err = exp.lookupLeasesByToken(namespace.RootContext(nil), testTokenEntry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(storedLeases) > 0 {
|
|
t.Fatal("found leases")
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_Batch_CannotCreateChildren(t *testing.T) {
|
|
var resp *logical.Response
|
|
|
|
core, _, root := TestCoreUnsealed(t)
|
|
ts := core.tokenStore
|
|
|
|
req := &logical.Request{
|
|
Path: "create",
|
|
ClientToken: root,
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"policies": []string{"policy1"},
|
|
"type": "batch",
|
|
},
|
|
}
|
|
resp = testMakeTokenViaRequest(t, ts, req)
|
|
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
|
|
t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies)
|
|
}
|
|
|
|
req.ClientToken = resp.Auth.ClientToken
|
|
resp = testMakeTokenViaRequest(t, ts, req)
|
|
if !resp.IsError() {
|
|
t.Fatalf("bad: expected error, got %#v", *resp)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_Batch_CannotRevoke(t *testing.T) {
|
|
var resp *logical.Response
|
|
var err error
|
|
|
|
core, _, root := TestCoreUnsealed(t)
|
|
ts := core.tokenStore
|
|
|
|
req := &logical.Request{
|
|
Path: "create",
|
|
ClientToken: root,
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"policies": []string{"policy1"},
|
|
"type": "batch",
|
|
},
|
|
}
|
|
resp = testMakeTokenViaRequest(t, ts, req)
|
|
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
|
|
t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies)
|
|
}
|
|
|
|
req.Path = "revoke"
|
|
req.Data["token"] = resp.Auth.ClientToken
|
|
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !resp.IsError() {
|
|
t.Fatalf("bad: expected error, got %#v", *resp)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_Batch_NoCubbyhole(t *testing.T) {
|
|
var resp *logical.Response
|
|
var err error
|
|
|
|
core, _, root := TestCoreUnsealed(t)
|
|
ts := core.tokenStore
|
|
|
|
req := &logical.Request{
|
|
Path: "create",
|
|
ClientToken: root,
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"policies": []string{"policy1"},
|
|
"type": "batch",
|
|
},
|
|
}
|
|
resp = testMakeTokenViaRequest(t, ts, req)
|
|
if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) {
|
|
t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies)
|
|
}
|
|
|
|
te, err := ts.Lookup(namespace.RootContext(nil), resp.Auth.ClientToken)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
req.Path = "cubbyhole/foo"
|
|
req.Operation = logical.CreateOperation
|
|
req.ClientToken = te.ID
|
|
req.SetTokenEntry(te)
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil && !errwrap.Contains(err, logical.ErrInvalidRequest.Error()) {
|
|
t.Fatal(err)
|
|
}
|
|
if !resp.IsError() {
|
|
t.Fatalf("bad: expected error, got %#v", *resp)
|
|
}
|
|
}
|
|
|
|
func TestTokenStore_TokenID(t *testing.T) {
|
|
t.Run("no custom ID provided", func(t *testing.T) {
|
|
c, _, initToken := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
// Ensure that a regular service token has a consts.ServiceTokenPrefix prefix
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
ClientToken: initToken,
|
|
Path: "create",
|
|
Operation: logical.UpdateOperation,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
if !strings.HasPrefix(resp.Auth.ClientToken, consts.ServiceTokenPrefix) {
|
|
t.Fatalf("token %q does not have a 'hvs.' prefix", resp.Auth.ClientToken)
|
|
}
|
|
})
|
|
|
|
t.Run("plain custom ID", func(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
ts := core.tokenStore
|
|
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
ClientToken: root,
|
|
Path: "create",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"id": "foobar",
|
|
},
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
|
}
|
|
|
|
// Ensure that using a custom token ID results in a warning
|
|
expectedWarning := "Supplying a custom ID for the token uses the weaker SHA1 hashing instead of the more secure SHA2-256 HMAC for token obfuscation. SHA1 hashed tokens on the wire leads to less secure lookups."
|
|
if resp.Warnings[0] != expectedWarning {
|
|
t.Fatalf("expected warning not present")
|
|
}
|
|
})
|
|
|
|
t.Run("service token prefix in custom ID", func(t *testing.T) {
|
|
c, _, initToken := TestCoreUnsealed(t)
|
|
ts := c.tokenStore
|
|
|
|
// Ensure that custom token ID having a consts.ServiceTokenPrefix prefix fails
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
ClientToken: initToken,
|
|
Path: "create",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"id": "hvs.foobar",
|
|
},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("expected an error")
|
|
}
|
|
if resp.Error().Error() != "custom token ID cannot have the 'hvs.' prefix" {
|
|
t.Fatalf("expected input error not present in error response")
|
|
}
|
|
})
|
|
|
|
t.Run("period in custom ID", func(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
ts := core.tokenStore
|
|
|
|
resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
ClientToken: root,
|
|
Path: "create",
|
|
Operation: logical.UpdateOperation,
|
|
Data: map[string]interface{}{
|
|
"id": "foobar.baz",
|
|
},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("expected an error")
|
|
}
|
|
if resp.Error().Error() != "custom token ID cannot have a '.' in the value" {
|
|
t.Fatalf("expected input error not present in error response")
|
|
}
|
|
})
|
|
}
|
|
|
|
func expectInGaugeCollection(t *testing.T, expectedLabels map[string]string, expectedValue float32, actual []metricsutil.GaugeLabelValues) {
|
|
t.Helper()
|
|
|
|
for _, glv := range actual {
|
|
actualLabels := make(map[string]string)
|
|
for _, l := range glv.Labels {
|
|
actualLabels[l.Name] = l.Value
|
|
}
|
|
if labelsMatch(actualLabels, expectedLabels) {
|
|
if expectedValue != glv.Value {
|
|
t.Errorf("expeced %v for %v, got %v", expectedValue, expectedLabels, glv.Value)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("didn't find labels %v", expectedLabels)
|
|
}
|
|
|
|
func TestTokenStore_Collectors(t *testing.T) {
|
|
ctx := namespace.RootContext(nil)
|
|
exp := mockExpiration(t)
|
|
ts := exp.tokenStore
|
|
|
|
// This helper is defined in expiration.go
|
|
sampleToken(t, exp, "auth/userpass/login", true, "default")
|
|
sampleToken(t, exp, "auth/userpass/login", true, "policy23457")
|
|
sampleToken(t, exp, "auth/token/create", false, "root")
|
|
sampleToken(t, exp, "auth/github/login", true, "root")
|
|
sampleToken(t, exp, "auth/github/login", false, "root")
|
|
|
|
waitForRestore(t, exp)
|
|
|
|
// By namespace:
|
|
values, err := ts.gaugeCollector(ctx)
|
|
if err != nil {
|
|
t.Fatalf("bad collector run: %v", err)
|
|
}
|
|
if len(values) != 1 {
|
|
t.Errorf("got %v values, expected 1", len(values))
|
|
}
|
|
expectInGaugeCollection(t,
|
|
map[string]string{"namespace": "root"},
|
|
5.0,
|
|
values)
|
|
|
|
values, err = ts.gaugeCollectorByPolicy(ctx)
|
|
if err != nil {
|
|
t.Fatalf("bad collector run: %v", err)
|
|
}
|
|
if len(values) != 3 {
|
|
t.Errorf("got %v values, expected 3", len(values))
|
|
}
|
|
expectInGaugeCollection(t,
|
|
map[string]string{"namespace": "root", "policy": "root"},
|
|
3.0,
|
|
values)
|
|
expectInGaugeCollection(t,
|
|
map[string]string{"namespace": "root", "policy": "default"},
|
|
1.0,
|
|
values)
|
|
expectInGaugeCollection(t,
|
|
map[string]string{"namespace": "root", "policy": "policy23457"},
|
|
1.0,
|
|
values)
|
|
|
|
values, err = ts.gaugeCollectorByTtl(ctx)
|
|
if err != nil {
|
|
t.Fatalf("bad collector run: %v", err)
|
|
}
|
|
if len(values) != 2 {
|
|
t.Errorf("got %v values, expected 2", len(values))
|
|
}
|
|
expectInGaugeCollection(t,
|
|
map[string]string{"namespace": "root", "creation_ttl": "1h"},
|
|
3.0,
|
|
values)
|
|
expectInGaugeCollection(t,
|
|
map[string]string{"namespace": "root", "creation_ttl": "+Inf"},
|
|
2.0,
|
|
values)
|
|
|
|
// Need to set up router for this to work, TODO
|
|
// ts.gaugeCollectorByMethod( ctx )
|
|
}
|