3325 lines
84 KiB
Go
3325 lines
84 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package vault
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/command/server"
|
|
|
|
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
|
|
logicalDb "github.com/hashicorp/vault/builtin/logical/database"
|
|
|
|
"github.com/hashicorp/vault/builtin/plugin"
|
|
|
|
"github.com/hashicorp/vault/builtin/audit/syslog"
|
|
|
|
"github.com/hashicorp/vault/builtin/audit/file"
|
|
"github.com/hashicorp/vault/builtin/audit/socket"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/go-test/deep"
|
|
"github.com/hashicorp/errwrap"
|
|
log "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
|
"github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/audit"
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
|
"github.com/hashicorp/vault/internalshared/configutil"
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/hashicorp/vault/sdk/physical"
|
|
"github.com/hashicorp/vault/sdk/physical/inmem"
|
|
"github.com/hashicorp/vault/version"
|
|
"github.com/sasha-s/go-deadlock"
|
|
)
|
|
|
|
// invalidKey is used to test Unseal
|
|
var invalidKey = []byte("abcdefghijklmnopqrstuvwxyz")[:17]
|
|
|
|
// TestNewCore_configureAuditBackends ensures that we are able to configure the
|
|
// supplied audit backends when getting a NewCore.
|
|
func TestNewCore_configureAuditBackends(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := map[string]struct {
|
|
backends map[string]audit.Factory
|
|
}{
|
|
"none": {
|
|
backends: nil,
|
|
},
|
|
"file": {
|
|
backends: map[string]audit.Factory{
|
|
"file": file.Factory,
|
|
},
|
|
},
|
|
"socket": {
|
|
backends: map[string]audit.Factory{
|
|
"socket": socket.Factory,
|
|
},
|
|
},
|
|
"syslog": {
|
|
backends: map[string]audit.Factory{
|
|
"syslog": syslog.Factory,
|
|
},
|
|
},
|
|
"all": {
|
|
backends: map[string]audit.Factory{
|
|
"file": file.Factory,
|
|
"socket": socket.Factory,
|
|
"syslog": syslog.Factory,
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
name := name
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
core := &Core{}
|
|
require.Len(t, core.auditBackends, 0)
|
|
core.configureAuditBackends(tc.backends)
|
|
require.Len(t, core.auditBackends, len(tc.backends))
|
|
for k := range tc.backends {
|
|
require.Contains(t, core.auditBackends, k)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNewCore_configureCredentialsBackends ensures that we are able to configure the
|
|
// supplied credential backends, in addition to defaults, when getting a NewCore.
|
|
func TestNewCore_configureCredentialsBackends(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := map[string]struct {
|
|
backends map[string]logical.Factory
|
|
}{
|
|
"none": {
|
|
backends: nil,
|
|
},
|
|
"plugin": {
|
|
backends: map[string]logical.Factory{
|
|
"plugin": plugin.Factory,
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
name := name
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
core := &Core{}
|
|
require.Len(t, core.credentialBackends, 0)
|
|
core.configureCredentialsBackends(tc.backends, corehelpers.NewTestLogger(t))
|
|
require.GreaterOrEqual(t, len(core.credentialBackends), len(tc.backends)+1) // token + ent
|
|
for k := range tc.backends {
|
|
require.Contains(t, core.credentialBackends, k)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNewCore_configureLogicalBackends ensures that we are able to configure the
|
|
// supplied logical backends, in addition to defaults, when getting a NewCore.
|
|
func TestNewCore_configureLogicalBackends(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// configureLogicalBackends will add some default backends for us:
|
|
// cubbyhole
|
|
// identity
|
|
// kv
|
|
// system
|
|
// In addition Enterprise versions of Vault may add additional engines.
|
|
|
|
tests := map[string]struct {
|
|
backends map[string]logical.Factory
|
|
adminNamespacePath string
|
|
expectedNonEntBackends int
|
|
}{
|
|
"none": {
|
|
backends: nil,
|
|
expectedNonEntBackends: 0,
|
|
},
|
|
"database": {
|
|
backends: map[string]logical.Factory{
|
|
"database": logicalDb.Factory,
|
|
},
|
|
adminNamespacePath: "foo",
|
|
expectedNonEntBackends: 5, // database + defaults
|
|
},
|
|
"kv": {
|
|
backends: map[string]logical.Factory{
|
|
"kv": logicalKv.Factory,
|
|
},
|
|
adminNamespacePath: "foo",
|
|
expectedNonEntBackends: 4, // kv + defaults (kv is a default)
|
|
},
|
|
"plugin": {
|
|
backends: map[string]logical.Factory{
|
|
"plugin": plugin.Factory,
|
|
},
|
|
adminNamespacePath: "foo",
|
|
expectedNonEntBackends: 5, // plugin + defaults
|
|
},
|
|
"all": {
|
|
backends: map[string]logical.Factory{
|
|
"database": logicalDb.Factory,
|
|
"kv": logicalKv.Factory,
|
|
"plugin": plugin.Factory,
|
|
},
|
|
adminNamespacePath: "foo",
|
|
expectedNonEntBackends: 6, // database, plugin + defaults
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
name := name
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
core := &Core{}
|
|
require.Len(t, core.logicalBackends, 0)
|
|
core.configureLogicalBackends(tc.backends, corehelpers.NewTestLogger(t), tc.adminNamespacePath)
|
|
require.GreaterOrEqual(t, len(core.logicalBackends), tc.expectedNonEntBackends)
|
|
require.Contains(t, core.logicalBackends, mountTypeKV)
|
|
require.Contains(t, core.logicalBackends, mountTypeCubbyhole)
|
|
require.Contains(t, core.logicalBackends, mountTypeSystem)
|
|
require.Contains(t, core.logicalBackends, mountTypeIdentity)
|
|
for k := range tc.backends {
|
|
require.Contains(t, core.logicalBackends, k)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNewCore_configureLogRequestLevel ensures that we are able to configure the
|
|
// supplied logging level when getting a NewCore.
|
|
func TestNewCore_configureLogRequestLevel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := map[string]struct {
|
|
level string
|
|
expectedLevel log.Level
|
|
}{
|
|
"none": {
|
|
level: "",
|
|
expectedLevel: log.NoLevel,
|
|
},
|
|
"trace": {
|
|
level: "trace",
|
|
expectedLevel: log.Trace,
|
|
},
|
|
"debug": {
|
|
level: "debug",
|
|
expectedLevel: log.Debug,
|
|
},
|
|
"info": {
|
|
level: "info",
|
|
expectedLevel: log.Info,
|
|
},
|
|
"warn": {
|
|
level: "warn",
|
|
expectedLevel: log.Warn,
|
|
},
|
|
"error": {
|
|
level: "error",
|
|
expectedLevel: log.Error,
|
|
},
|
|
"bad": {
|
|
level: "foo",
|
|
expectedLevel: log.NoLevel,
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
name := name
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We need to supply a logger, as configureLogRequestLevel emits
|
|
// warnings to the logs in certain circumstances.
|
|
core := &Core{
|
|
logger: corehelpers.NewTestLogger(t),
|
|
}
|
|
core.configureLogRequestLevel(tc.level)
|
|
require.Equal(t, tc.expectedLevel, log.Level(core.logRequestsLevel.Load()))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNewCore_configureListeners tests that we are able to configure listeners
|
|
// on a NewCore via config.
|
|
func TestNewCore_configureListeners(t *testing.T) {
|
|
// We would usually expect CoreConfig to come from server.NewConfig().
|
|
// However, we want to fiddle to give us some granular control over the config.
|
|
tests := map[string]struct {
|
|
config *CoreConfig
|
|
expectedListeners []*ListenerCustomHeaders
|
|
}{
|
|
"nil-listeners": {
|
|
config: &CoreConfig{
|
|
RawConfig: &server.Config{
|
|
SharedConfig: &configutil.SharedConfig{},
|
|
},
|
|
},
|
|
expectedListeners: nil,
|
|
},
|
|
"listeners-empty": {
|
|
config: &CoreConfig{
|
|
RawConfig: &server.Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*configutil.Listener{},
|
|
},
|
|
},
|
|
},
|
|
expectedListeners: nil,
|
|
},
|
|
"listeners-some": {
|
|
config: &CoreConfig{
|
|
RawConfig: &server.Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*configutil.Listener{
|
|
{Address: "foo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectedListeners: []*ListenerCustomHeaders{
|
|
{Address: "foo"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
name := name
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We need to init some values ourselves, usually CreateCore does this for us.
|
|
logger := corehelpers.NewTestLogger(t)
|
|
backend, err := inmem.NewInmem(nil, logger)
|
|
require.NoError(t, err)
|
|
storage := &logical.InmemStorage{}
|
|
core := &Core{
|
|
clusterListener: new(atomic.Value),
|
|
customListenerHeader: new(atomic.Value),
|
|
uiConfig: NewUIConfig(false, backend, storage),
|
|
}
|
|
|
|
err = core.configureListeners(tc.config)
|
|
require.NoError(t, err)
|
|
switch tc.expectedListeners {
|
|
case nil:
|
|
require.Nil(t, core.customListenerHeader.Load())
|
|
default:
|
|
for i, v := range core.customListenerHeader.Load().([]*ListenerCustomHeaders) {
|
|
require.Equal(t, v.Address, tc.config.RawConfig.Listeners[i].Address)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewCore_badRedirectAddr(t *testing.T) {
|
|
logger = logging.NewVaultLogger(log.Trace)
|
|
|
|
inm, err := inmem.NewInmem(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
conf := &CoreConfig{
|
|
RedirectAddr: "127.0.0.1:8200",
|
|
Physical: inm,
|
|
DisableMlock: true,
|
|
}
|
|
_, err = NewCore(conf)
|
|
if err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestSealConfig_Invalid(t *testing.T) {
|
|
s := &SealConfig{
|
|
SecretShares: 2,
|
|
SecretThreshold: 1,
|
|
}
|
|
err := s.Validate()
|
|
if err == nil {
|
|
t.Fatalf("expected err")
|
|
}
|
|
}
|
|
|
|
// TestCore_HasVaultVersion checks that versionHistory is correct and initialized
|
|
// after a core has been unsealed.
|
|
func TestCore_HasVaultVersion(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
if c.versionHistory == nil {
|
|
t.Fatalf("Version timestamps for core were not initialized for a new core")
|
|
}
|
|
versionEntry, ok := c.versionHistory[version.Version]
|
|
if !ok {
|
|
t.Fatalf("%s upgrade time not found", version.Version)
|
|
}
|
|
|
|
upgradeTime := versionEntry.TimestampInstalled
|
|
|
|
if upgradeTime.After(time.Now()) || upgradeTime.Before(time.Now().Add(-1*time.Hour)) {
|
|
t.Fatalf("upgrade time isn't within reasonable bounds of new core initialization. " +
|
|
fmt.Sprintf("time is: %+v, upgrade time is %+v", time.Now(), upgradeTime))
|
|
}
|
|
}
|
|
|
|
func TestCore_Unseal_MultiShare(t *testing.T) {
|
|
c := TestCore(t)
|
|
|
|
_, err := TestCoreUnseal(c, invalidKey)
|
|
if err != ErrNotInit {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
sealConf := &SealConfig{
|
|
SecretShares: 5,
|
|
SecretThreshold: 3,
|
|
}
|
|
res, err := c.Initialize(namespace.RootContext(nil), &InitParams{
|
|
BarrierConfig: sealConf,
|
|
RecoveryConfig: nil,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if !c.Sealed() {
|
|
t.Fatalf("should be sealed")
|
|
}
|
|
|
|
if prog, _ := c.SecretProgress(); prog != 0 {
|
|
t.Fatalf("bad progress: %d", prog)
|
|
}
|
|
|
|
for i := 0; i < 5; i++ {
|
|
unseal, err := TestCoreUnseal(c, res.SecretShares[i])
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Ignore redundant
|
|
_, err = TestCoreUnseal(c, res.SecretShares[i])
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if i >= 2 {
|
|
if !unseal {
|
|
t.Fatalf("should be unsealed")
|
|
}
|
|
if prog, _ := c.SecretProgress(); prog != 0 {
|
|
t.Fatalf("bad progress: %d", prog)
|
|
}
|
|
} else {
|
|
if unseal {
|
|
t.Fatalf("should not be unsealed")
|
|
}
|
|
if prog, _ := c.SecretProgress(); prog != i+1 {
|
|
t.Fatalf("bad progress: %d", prog)
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.Sealed() {
|
|
t.Fatalf("should not be sealed")
|
|
}
|
|
|
|
err = c.Seal(res.RootToken)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Ignore redundant
|
|
err = c.Seal(res.RootToken)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if !c.Sealed() {
|
|
t.Fatalf("should be sealed")
|
|
}
|
|
}
|
|
|
|
// TestCore_UseSSCTokenToggleOn will check that the root SSC
|
|
// token can be used even when disableSSCTokens is toggled on
|
|
func TestCore_UseSSCTokenToggleOn(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.disableSSCTokens = true
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
ctx := namespace.RootContext(nil)
|
|
resp, err := c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Read the key
|
|
req.Operation = logical.ReadOperation
|
|
req.Data = nil
|
|
err = c.PopulateTokenEntry(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
resp, err = c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp == nil || resp.Secret == nil || resp.Data == nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
if resp.Secret.TTL != time.Hour {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
if resp.Secret.LeaseID == "" {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
if resp.Data["foo"] != "bar" {
|
|
t.Fatalf("bad: %#v", resp.Data)
|
|
}
|
|
}
|
|
|
|
// TestCore_UseNonSSCTokenToggleOff will check that the root
|
|
// non-SSC token can be used even when disableSSCTokens is toggled
|
|
// off.
|
|
func TestCore_UseNonSSCTokenToggleOff(t *testing.T) {
|
|
coreConfig := &CoreConfig{
|
|
DisableSSCTokens: true,
|
|
}
|
|
c, _, root := TestCoreUnsealedWithConfig(t, coreConfig)
|
|
if len(root) > TokenLength+OldTokenPrefixLength || !strings.HasPrefix(root, consts.LegacyServiceTokenPrefix) {
|
|
t.Fatalf("token is not an old token type: %s, %d", root, len(root))
|
|
}
|
|
c.disableSSCTokens = false
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
ctx := namespace.RootContext(nil)
|
|
resp, err := c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Read the key
|
|
req.Operation = logical.ReadOperation
|
|
req.Data = nil
|
|
err = c.PopulateTokenEntry(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
resp, err = c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp == nil || resp.Secret == nil || resp.Data == nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
if resp.Secret.TTL != time.Hour {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
if resp.Secret.LeaseID == "" {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
if resp.Data["foo"] != "bar" {
|
|
t.Fatalf("bad: %#v", resp.Data)
|
|
}
|
|
}
|
|
|
|
func TestCore_Unseal_Single(t *testing.T) {
|
|
c := TestCore(t)
|
|
|
|
_, err := TestCoreUnseal(c, invalidKey)
|
|
if err != ErrNotInit {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
sealConf := &SealConfig{
|
|
SecretShares: 1,
|
|
SecretThreshold: 1,
|
|
}
|
|
res, err := c.Initialize(namespace.RootContext(nil), &InitParams{
|
|
BarrierConfig: sealConf,
|
|
RecoveryConfig: nil,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if !c.Sealed() {
|
|
t.Fatalf("should be sealed")
|
|
}
|
|
|
|
if prog, _ := c.SecretProgress(); prog != 0 {
|
|
t.Fatalf("bad progress: %d", prog)
|
|
}
|
|
|
|
unseal, err := TestCoreUnseal(c, res.SecretShares[0])
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if !unseal {
|
|
t.Fatalf("should be unsealed")
|
|
}
|
|
if prog, _ := c.SecretProgress(); prog != 0 {
|
|
t.Fatalf("bad progress: %d", prog)
|
|
}
|
|
|
|
if c.Sealed() {
|
|
t.Fatalf("should not be sealed")
|
|
}
|
|
}
|
|
|
|
func TestCore_Route_Sealed(t *testing.T) {
|
|
c := TestCore(t)
|
|
sealConf := &SealConfig{
|
|
SecretShares: 1,
|
|
SecretThreshold: 1,
|
|
}
|
|
|
|
ctx := namespace.RootContext(nil)
|
|
|
|
// Should not route anything
|
|
req := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "sys/mounts",
|
|
}
|
|
_, err := c.HandleRequest(ctx, req)
|
|
if err != consts.ErrSealed {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
res, err := c.Initialize(ctx, &InitParams{
|
|
BarrierConfig: sealConf,
|
|
RecoveryConfig: nil,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
unseal, err := TestCoreUnseal(c, res.SecretShares[0])
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !unseal {
|
|
t.Fatalf("should be unsealed")
|
|
}
|
|
|
|
// Should not error after unseal
|
|
req.ClientToken = res.RootToken
|
|
_, err = c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
// Attempt to unseal after doing a first seal
|
|
func TestCore_SealUnseal(t *testing.T) {
|
|
c, keys, root := TestCoreUnsealed(t)
|
|
if err := c.Seal(root); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
for i, key := range keys {
|
|
unseal, err := TestCoreUnseal(c, key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if i+1 == len(keys) && !unseal {
|
|
t.Fatalf("err: should be unsealed")
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCore_RunLockedUserUpdatesForStaleEntry tests that stale locked user entries
|
|
// get deleted upon unseal
|
|
func TestCore_RunLockedUserUpdatesForStaleEntry(t *testing.T) {
|
|
core, keys, root := TestCoreUnsealed(t)
|
|
storageUserLockoutPath := fmt.Sprintf(coreLockedUsersPath + "ns1/mountAccessor1/aliasName1")
|
|
|
|
// cleanup
|
|
defer core.barrier.Delete(context.Background(), storageUserLockoutPath)
|
|
|
|
// create invalid entry in storage to test stale entries get deleted on unseal
|
|
// last failed login time for this path is 1970-01-01 00:00:00 +0000 UTC
|
|
// since user lockout configurations are not configured, lockout duration will
|
|
// be set to default (15m) internally
|
|
compressedBytes, err := jsonutil.EncodeJSONAndCompress(int(time.Unix(0, 0).Unix()), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create an entry
|
|
entry := &logical.StorageEntry{
|
|
Key: storageUserLockoutPath,
|
|
Value: compressedBytes,
|
|
}
|
|
|
|
// Write to the physical backend
|
|
err = core.barrier.Put(context.Background(), entry)
|
|
if err != nil {
|
|
t.Fatalf("failed to write invalid locked user entry, err: %v", err)
|
|
}
|
|
|
|
// seal and unseal vault
|
|
if err := core.Seal(root); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
for i, key := range keys {
|
|
unseal, err := TestCoreUnseal(core, key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if i+1 == len(keys) && !unseal {
|
|
t.Fatalf("err: should be unsealed")
|
|
}
|
|
}
|
|
|
|
// locked user entry must be deleted upon unseal as it is stale
|
|
lastFailedLoginRaw, err := core.barrier.Get(context.Background(), storageUserLockoutPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if lastFailedLoginRaw != nil {
|
|
t.Fatal("err: stale locked user entry exists")
|
|
}
|
|
}
|
|
|
|
// TestCore_RunLockedUserUpdatesForValidEntry tests that valid locked user entries
|
|
// do not get removed on unseal
|
|
// Also tests that the userFailedLoginInfo map gets updated with correct information
|
|
func TestCore_RunLockedUserUpdatesForValidEntry(t *testing.T) {
|
|
core, keys, root := TestCoreUnsealed(t)
|
|
storageUserLockoutPath := fmt.Sprintf(coreLockedUsersPath + "ns1/mountAccessor1/aliasName1")
|
|
|
|
// cleanup
|
|
defer core.barrier.Delete(context.Background(), storageUserLockoutPath)
|
|
|
|
// create valid storage entry for locked user
|
|
lastFailedLoginTime := int(time.Now().Unix())
|
|
|
|
compressedBytes, err := jsonutil.EncodeJSONAndCompress(lastFailedLoginTime, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create an entry
|
|
entry := &logical.StorageEntry{
|
|
Key: storageUserLockoutPath,
|
|
Value: compressedBytes,
|
|
}
|
|
|
|
// Write to the physical backend
|
|
err = core.barrier.Put(context.Background(), entry)
|
|
if err != nil {
|
|
t.Fatalf("failed to write invalid locked user entry, err: %v", err)
|
|
}
|
|
|
|
// seal and unseal vault
|
|
if err := core.Seal(root); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
for i, key := range keys {
|
|
unseal, err := TestCoreUnseal(core, key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if i+1 == len(keys) && !unseal {
|
|
t.Fatalf("err: should be unsealed")
|
|
}
|
|
}
|
|
|
|
// locked user entry must exist as it is still valid
|
|
existingEntry, err := core.barrier.Get(context.Background(), storageUserLockoutPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if existingEntry == nil {
|
|
t.Fatalf("err: entry must exist for locked user in storage")
|
|
}
|
|
|
|
// userFailedLoginInfo map should have the correct information for locked user
|
|
loginUserInfoKey := FailedLoginUser{
|
|
aliasName: "aliasName1",
|
|
mountAccessor: "mountAccessor1",
|
|
}
|
|
|
|
failedLoginInfoFromMap := core.LocalGetUserFailedLoginInfo(context.Background(), loginUserInfoKey)
|
|
if failedLoginInfoFromMap == nil {
|
|
t.Fatalf("err: entry must exist for locked user in userFailedLoginInfo map")
|
|
}
|
|
if failedLoginInfoFromMap.lastFailedLoginTime != lastFailedLoginTime {
|
|
t.Fatalf("err: incorrect failed login time information for locked user updated in userFailedLoginInfo map")
|
|
}
|
|
if int(failedLoginInfoFromMap.count) != configutil.UserLockoutThresholdDefault {
|
|
t.Fatalf("err: incorrect failed login count information for locked user updated in userFailedLoginInfo map")
|
|
}
|
|
}
|
|
|
|
// Attempt to shutdown after unseal
|
|
func TestCore_Shutdown(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
if err := c.Shutdown(); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !c.Sealed() {
|
|
t.Fatal("wasn't sealed")
|
|
}
|
|
}
|
|
|
|
// verify the channel returned by ShutdownDone is closed after Finalize
|
|
func TestCore_ShutdownDone(t *testing.T) {
|
|
c := TestCoreWithSealAndUINoCleanup(t, &CoreConfig{})
|
|
testCoreUnsealed(t, c)
|
|
doneCh := c.ShutdownDone()
|
|
go func() {
|
|
time.Sleep(100 * time.Millisecond)
|
|
err := c.Shutdown()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-doneCh:
|
|
if !c.Sealed() {
|
|
t.Fatalf("shutdown done called prematurely!")
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatalf("shutdown notification not received")
|
|
}
|
|
}
|
|
|
|
// Attempt to seal bad token
|
|
func TestCore_Seal_BadToken(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
if err := c.Seal("foo"); err == nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if c.Sealed() {
|
|
t.Fatal("was sealed")
|
|
}
|
|
}
|
|
|
|
func TestCore_PreOneTen_BatchTokens(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
|
|
// load up some versions and ensure that 1.9 is the most recent one by timestamp (even though this isn't realistic)
|
|
upgradeTimePlusEpsilon := time.Now().UTC()
|
|
|
|
versionEntries := []VaultVersion{
|
|
{Version: "1.10.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
|
|
{Version: "1.9.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
|
|
}
|
|
|
|
for _, entry := range versionEntries {
|
|
_, err := c.storeVersionEntry(context.Background(), &entry, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error())
|
|
}
|
|
}
|
|
|
|
err := c.loadVersionHistory(c.activeContext)
|
|
if err != nil {
|
|
t.Fatalf("failed to populate version history cache, err: %s", err.Error())
|
|
}
|
|
|
|
// double check that we're working with 1.9
|
|
v, _, err := c.FindNewestVersionTimestamp()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v != "1.9.2" {
|
|
t.Fatalf("expected 1.9.2, found: %s", v)
|
|
}
|
|
|
|
// generate a batch token
|
|
te := &logical.TokenEntry{
|
|
NumUses: 1,
|
|
Policies: []string{"root"},
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Type: logical.TokenTypeBatch,
|
|
}
|
|
err = c.tokenStore.create(namespace.RootContext(nil), te)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// verify it uses the legacy prefix
|
|
if !strings.HasPrefix(te.ID, consts.LegacyBatchTokenPrefix) {
|
|
t.Fatalf("expected 1.9 batch token IDs to start with b. but it didn't: %s", te.ID)
|
|
}
|
|
}
|
|
|
|
func TestCore_OneTenPlus_BatchTokens(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
|
|
// load up some versions and ensure that 1.10 is the most recent version
|
|
upgradeTimePlusEpsilon := time.Now().UTC()
|
|
|
|
versionEntries := []VaultVersion{
|
|
{Version: "1.9.2", TimestampInstalled: upgradeTimePlusEpsilon.Add(-4 * time.Hour)},
|
|
{Version: "1.10.1", TimestampInstalled: upgradeTimePlusEpsilon.Add(2 * time.Hour)},
|
|
}
|
|
|
|
for _, entry := range versionEntries {
|
|
_, err := c.storeVersionEntry(context.Background(), &entry, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to write version entry %#v, err: %s", entry, err.Error())
|
|
}
|
|
}
|
|
|
|
err := c.loadVersionHistory(c.activeContext)
|
|
if err != nil {
|
|
t.Fatalf("failed to populate version history cache, err: %s", err.Error())
|
|
}
|
|
|
|
// double check that we're working with 1.10
|
|
v, _, err := c.FindNewestVersionTimestamp()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v != "1.10.1" {
|
|
t.Fatalf("expected 1.10.1, found: %s", v)
|
|
}
|
|
|
|
// generate a batch token
|
|
te := &logical.TokenEntry{
|
|
NumUses: 1,
|
|
Policies: []string{"root"},
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
Type: logical.TokenTypeBatch,
|
|
}
|
|
err = c.tokenStore.create(namespace.RootContext(nil), te)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// verify it uses the legacy prefix
|
|
if !strings.HasPrefix(te.ID, consts.BatchTokenPrefix) {
|
|
t.Fatalf("expected 1.10 batch token IDs to start with hvb. but it didn't: %s", te.ID)
|
|
}
|
|
}
|
|
|
|
// GH-3497
|
|
func TestCore_Seal_SingleUse(t *testing.T) {
|
|
c, keys, _ := TestCoreUnsealed(t)
|
|
c.tokenStore.create(namespace.RootContext(nil), &logical.TokenEntry{
|
|
ID: "foo",
|
|
NumUses: 1,
|
|
Policies: []string{"root"},
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
})
|
|
if err := c.Seal("foo"); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !c.Sealed() {
|
|
t.Fatal("not sealed")
|
|
}
|
|
for i, key := range keys {
|
|
unseal, err := TestCoreUnseal(c, key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if i+1 == len(keys) && !unseal {
|
|
t.Fatalf("err: should be unsealed")
|
|
}
|
|
}
|
|
if err := c.Seal("foo"); err == nil {
|
|
t.Fatal("expected error from revoked token")
|
|
}
|
|
te, err := c.tokenStore.Lookup(namespace.RootContext(nil), "foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if te != nil {
|
|
t.Fatalf("expected nil token entry, got %#v", *te)
|
|
}
|
|
}
|
|
|
|
// Ensure we get a LeaseID
|
|
func TestCore_HandleRequest_Lease(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
ctx := namespace.RootContext(nil)
|
|
resp, err := c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Read the key
|
|
req.Operation = logical.ReadOperation
|
|
req.Data = nil
|
|
err = c.PopulateTokenEntry(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
resp, err = c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp == nil || resp.Secret == nil || resp.Data == nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
if resp.Secret.TTL != time.Hour {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
if resp.Secret.LeaseID == "" {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
if resp.Data["foo"] != "bar" {
|
|
t.Fatalf("bad: %#v", resp.Data)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_Lease_MaxLength(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1000h",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
ctx := namespace.RootContext(nil)
|
|
resp, err := c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Read the key
|
|
req.Operation = logical.ReadOperation
|
|
req.Data = nil
|
|
err = c.PopulateTokenEntry(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
resp, err = c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp == nil || resp.Secret == nil || resp.Data == nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
if resp.Secret.TTL != c.maxLeaseTTL {
|
|
t.Fatalf("bad: %#v, %d", resp.Secret, c.maxLeaseTTL)
|
|
}
|
|
if resp.Secret.LeaseID == "" {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
if resp.Data["foo"] != "bar" {
|
|
t.Fatalf("bad: %#v", resp.Data)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_Lease_DefaultLength(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "0h",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
ctx := namespace.RootContext(nil)
|
|
resp, err := c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Read the key
|
|
req.Operation = logical.ReadOperation
|
|
req.Data = nil
|
|
err = c.PopulateTokenEntry(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
resp, err = c.HandleRequest(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp == nil || resp.Secret == nil || resp.Data == nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
if resp.Secret.TTL != c.defaultLeaseTTL {
|
|
t.Fatalf("bad: %d, %d", resp.Secret.TTL/time.Second, c.defaultLeaseTTL/time.Second)
|
|
}
|
|
if resp.Secret.LeaseID == "" {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
if resp.Data["foo"] != "bar" {
|
|
t.Fatalf("bad: %#v", resp.Data)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_MissingToken(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp.Data["error"] != logical.ErrPermissionDenied.Error() {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_InvalidToken(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: "foobarbaz",
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp.Data["error"] != "permission denied" {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
}
|
|
|
|
// Check that standard permissions work
|
|
func TestCore_HandleRequest_NoSlash(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
req := &logical.Request{
|
|
Operation: logical.HelpOperation,
|
|
Path: "secret",
|
|
ClientToken: root,
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v, resp: %v", err, resp)
|
|
}
|
|
if _, ok := resp.Data["help"]; !ok {
|
|
t.Fatalf("resp: %v", resp)
|
|
}
|
|
}
|
|
|
|
// Test a root path is denied if non-root
|
|
func TestCore_HandleRequest_RootPath(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"})
|
|
|
|
req := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "sys/policy", // root protected!
|
|
ClientToken: "child",
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
|
|
t.Fatalf("err: %v, resp: %v", err, resp)
|
|
}
|
|
}
|
|
|
|
// Test a root path is allowed if non-root but with sudo
|
|
func TestCore_HandleRequest_RootPath_WithSudo(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
// Set the 'test' policy object to permit access to sys/policy
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "sys/policy/test", // root protected!
|
|
Data: map[string]interface{}{
|
|
"rules": `path "sys/policy" { policy = "sudo" }`,
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Child token (non-root) but with 'test' policy should have access
|
|
testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"})
|
|
req = &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "sys/policy", // root protected!
|
|
ClientToken: "child",
|
|
}
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
}
|
|
|
|
// Check that standard permissions work
|
|
func TestCore_HandleRequest_PermissionDenied(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"})
|
|
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: "child",
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
|
|
t.Fatalf("err: %v, resp: %v", err, resp)
|
|
}
|
|
}
|
|
|
|
// Check that standard permissions work
|
|
func TestCore_HandleRequest_PermissionAllowed(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"})
|
|
|
|
// Set the 'test' policy object to permit access to secret/
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "sys/policy/test",
|
|
Data: map[string]interface{}{
|
|
"rules": `path "secret/*" { policy = "write" }`,
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Write should work now
|
|
req = &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: "child",
|
|
}
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_NoClientToken(t *testing.T) {
|
|
noop := &NoopBackend{
|
|
Response: &logical.Response{},
|
|
}
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the logical backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
|
|
req.Data["type"] = "noop"
|
|
req.Data["description"] = "foo"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to request with connection data
|
|
req = &logical.Request{
|
|
Path: "foo/login",
|
|
}
|
|
req.ClientToken = root
|
|
if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
ct := noop.Requests[0].ClientToken
|
|
if ct == "" || ct == root {
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_ConnOnLogin(t *testing.T) {
|
|
noop := &NoopBackend{
|
|
Login: []string{"login"},
|
|
Response: &logical.Response{},
|
|
BackendType: logical.TypeCredential,
|
|
}
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the credential backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to request with connection data
|
|
req = &logical.Request{
|
|
Path: "auth/foo/login",
|
|
Connection: &logical.Connection{},
|
|
}
|
|
if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if noop.Requests[0].Connection == nil {
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
}
|
|
}
|
|
|
|
// Ensure we get a client token
|
|
func TestCore_HandleLogin_Token(t *testing.T) {
|
|
noop := &NoopBackend{
|
|
Login: []string{"login"},
|
|
Response: &logical.Response{
|
|
Auth: &logical.Auth{
|
|
Policies: []string{"foo", "bar"},
|
|
Metadata: map[string]string{
|
|
"user": "armon",
|
|
},
|
|
DisplayName: "armon",
|
|
},
|
|
},
|
|
BackendType: logical.TypeCredential,
|
|
}
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.credentialBackends["noop"] = func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the credential backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to login
|
|
lreq := &logical.Request{
|
|
Path: "auth/foo/login",
|
|
}
|
|
lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Ensure we got a client token back
|
|
clientToken := lresp.Auth.ClientToken
|
|
if clientToken == "" {
|
|
t.Fatalf("bad: %#v", lresp)
|
|
}
|
|
|
|
// Check the policy and metadata
|
|
innerToken, _ := c.DecodeSSCToken(clientToken)
|
|
te, err := c.tokenStore.Lookup(namespace.RootContext(nil), innerToken)
|
|
if err != nil || te == nil {
|
|
t.Fatalf("tok: %s, err: %v", clientToken, err)
|
|
}
|
|
|
|
expectedID, _ := c.DecodeSSCToken(clientToken)
|
|
expect := &logical.TokenEntry{
|
|
ID: expectedID,
|
|
Accessor: te.Accessor,
|
|
Parent: "",
|
|
Policies: []string{"bar", "default", "foo"},
|
|
Path: "auth/foo/login",
|
|
Meta: map[string]string{
|
|
"user": "armon",
|
|
},
|
|
DisplayName: "foo-armon",
|
|
TTL: time.Hour * 24,
|
|
CreationTime: te.CreationTime,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
CubbyholeID: te.CubbyholeID,
|
|
Type: logical.TokenTypeService,
|
|
}
|
|
|
|
if diff := deep.Equal(te, expect); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
// Check that we have a lease with default duration
|
|
if lresp.Auth.TTL != noop.System().DefaultLeaseTTL() {
|
|
t.Fatalf("bad: %#v, defaultLeaseTTL: %#v", lresp.Auth, c.defaultLeaseTTL)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_AuditTrail(t *testing.T) {
|
|
// Create a noop audit backend
|
|
noop := &corehelpers.NoopAudit{}
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
noop = &corehelpers.NoopAudit{
|
|
Config: config,
|
|
}
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the audit backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/audit/noop")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Make a request
|
|
req = &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
req.ClientToken = root
|
|
if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Check the audit trail on request and response
|
|
if len(noop.ReqAuth) != 1 {
|
|
t.Fatalf("bad: %#v", noop)
|
|
}
|
|
auth := noop.ReqAuth[0]
|
|
if auth.ClientToken != root {
|
|
t.Fatalf("bad client token: %#v", auth)
|
|
}
|
|
if len(auth.Policies) != 1 || auth.Policies[0] != "root" {
|
|
t.Fatalf("bad: %#v", auth)
|
|
}
|
|
if len(noop.Req) != 1 || !reflect.DeepEqual(noop.Req[0], req) {
|
|
t.Fatalf("Bad: %#v", noop.Req[0])
|
|
}
|
|
|
|
if len(noop.RespAuth) != 2 {
|
|
t.Fatalf("bad: %#v", noop)
|
|
}
|
|
if !reflect.DeepEqual(noop.RespAuth[1], auth) {
|
|
t.Fatalf("bad: %#v, vs %#v", auth, noop.RespAuth)
|
|
}
|
|
if len(noop.RespReq) != 2 || !reflect.DeepEqual(noop.RespReq[1], req) {
|
|
t.Fatalf("Bad: %#v", noop.RespReq[1])
|
|
}
|
|
if len(noop.Resp) != 2 || !reflect.DeepEqual(noop.Resp[1], resp) {
|
|
t.Fatalf("Bad: %#v", noop.Resp[1])
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_AuditTrail_noHMACKeys(t *testing.T) {
|
|
// Create a noop audit backend
|
|
var noop *corehelpers.NoopAudit
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
noop = &corehelpers.NoopAudit{
|
|
Config: config,
|
|
}
|
|
return noop, nil
|
|
}
|
|
|
|
// Specify some keys to not HMAC
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/secret/tune")
|
|
req.Data["audit_non_hmac_request_keys"] = "foo"
|
|
req.ClientToken = root
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/secret/tune")
|
|
req.Data["audit_non_hmac_response_keys"] = "baz"
|
|
req.ClientToken = root
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Enable the audit backend
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/audit/noop")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Make a request
|
|
req = &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
req.ClientToken = root
|
|
if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Check the audit trail on request and response
|
|
if len(noop.ReqAuth) != 1 {
|
|
t.Fatalf("bad: %#v", noop)
|
|
}
|
|
auth := noop.ReqAuth[0]
|
|
if auth.ClientToken != root {
|
|
t.Fatalf("bad client token: %#v", auth)
|
|
}
|
|
if len(auth.Policies) != 1 || auth.Policies[0] != "root" {
|
|
t.Fatalf("bad: %#v", auth)
|
|
}
|
|
if len(noop.Req) != 1 || !reflect.DeepEqual(noop.Req[0], req) {
|
|
t.Fatalf("Bad: %#v", noop.Req[0])
|
|
}
|
|
if len(noop.ReqNonHMACKeys) != 1 || noop.ReqNonHMACKeys[0] != "foo" {
|
|
t.Fatalf("Bad: %#v", noop.ReqNonHMACKeys)
|
|
}
|
|
if len(noop.RespAuth) != 2 {
|
|
t.Fatalf("bad: %#v", noop)
|
|
}
|
|
if !reflect.DeepEqual(noop.RespAuth[1], auth) {
|
|
t.Fatalf("bad: %#v", auth)
|
|
}
|
|
if len(noop.RespReq) != 2 || !reflect.DeepEqual(noop.RespReq[1], req) {
|
|
t.Fatalf("Bad: %#v", noop.RespReq[1])
|
|
}
|
|
if len(noop.Resp) != 2 || !reflect.DeepEqual(noop.Resp[1], resp) {
|
|
t.Fatalf("Bad: %#v", noop.Resp[1])
|
|
}
|
|
|
|
// Test for response keys
|
|
// Make a request
|
|
req = &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "secret/test",
|
|
ClientToken: root,
|
|
}
|
|
req.ClientToken = root
|
|
err = c.PopulateTokenEntry(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if len(noop.RespNonHMACKeys) != 1 || !strutil.EquivalentSlices(noop.RespNonHMACKeys[0], []string{"baz"}) {
|
|
t.Fatalf("Bad: %#v", noop.RespNonHMACKeys)
|
|
}
|
|
if len(noop.RespReqNonHMACKeys) != 1 || !strutil.EquivalentSlices(noop.RespReqNonHMACKeys[0], []string{"foo"}) {
|
|
t.Fatalf("Bad: %#v", noop.RespReqNonHMACKeys)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleLogin_AuditTrail(t *testing.T) {
|
|
// Create a badass credential backend that always logs in as armon
|
|
noop := &corehelpers.NoopAudit{}
|
|
noopBack := &NoopBackend{
|
|
Login: []string{"login"},
|
|
Response: &logical.Response{
|
|
Auth: &logical.Auth{
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: time.Hour,
|
|
},
|
|
Policies: []string{"foo", "bar"},
|
|
Metadata: map[string]string{
|
|
"user": "armon",
|
|
},
|
|
},
|
|
},
|
|
BackendType: logical.TypeCredential,
|
|
}
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noopBack, nil
|
|
}
|
|
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
noop = &corehelpers.NoopAudit{
|
|
Config: config,
|
|
}
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the credential backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Enable the audit backend
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/audit/noop")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to login
|
|
lreq := &logical.Request{
|
|
Path: "auth/foo/login",
|
|
}
|
|
lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Ensure we got a client token back
|
|
clientToken := lresp.Auth.ClientToken
|
|
if clientToken == "" {
|
|
t.Fatalf("bad: %#v", lresp)
|
|
}
|
|
|
|
// Check the audit trail on request and response
|
|
if len(noop.ReqAuth) != 1 {
|
|
t.Fatalf("bad: %#v", noop)
|
|
}
|
|
if len(noop.Req) != 1 || !reflect.DeepEqual(noop.Req[0], lreq) {
|
|
t.Fatalf("Bad: %#v %#v", noop.Req[0], lreq)
|
|
}
|
|
|
|
if len(noop.RespAuth) != 2 {
|
|
t.Fatalf("bad: %#v", noop)
|
|
}
|
|
auth := noop.RespAuth[1]
|
|
if auth.ClientToken != clientToken {
|
|
t.Fatalf("bad client token: %#v", auth)
|
|
}
|
|
if len(auth.Policies) != 3 || auth.Policies[0] != "bar" || auth.Policies[1] != "default" || auth.Policies[2] != "foo" {
|
|
t.Fatalf("bad: %#v", auth)
|
|
}
|
|
if len(noop.RespReq) != 2 || !reflect.DeepEqual(noop.RespReq[1], lreq) {
|
|
t.Fatalf("Bad: %#v", noop.RespReq[1])
|
|
}
|
|
if len(noop.Resp) != 2 || !reflect.DeepEqual(noop.Resp[1], lresp) {
|
|
t.Fatalf("Bad: %#v %#v", noop.Resp[1], lresp)
|
|
}
|
|
}
|
|
|
|
// Check that we register a lease for new tokens
|
|
func TestCore_HandleRequest_CreateToken_Lease(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
// Create a new credential
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
|
|
req.ClientToken = root
|
|
req.Data["policies"] = []string{"foo"}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Ensure we got a new client token back
|
|
if resp.IsError() {
|
|
t.Fatalf("err: %v %v", err, *resp)
|
|
}
|
|
clientToken := resp.Auth.ClientToken
|
|
if clientToken == "" {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Check the policy and metadata
|
|
te, err := c.tokenStore.Lookup(namespace.RootContext(nil), clientToken)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expectedID, _ := c.DecodeSSCToken(clientToken)
|
|
expectedRootID, _ := c.DecodeSSCToken(root)
|
|
|
|
expect := &logical.TokenEntry{
|
|
ID: expectedID,
|
|
Accessor: te.Accessor,
|
|
Parent: expectedRootID,
|
|
Policies: []string{"default", "foo"},
|
|
Path: "auth/token/create",
|
|
DisplayName: "token",
|
|
CreationTime: te.CreationTime,
|
|
TTL: time.Hour * 24 * 32,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
CubbyholeID: te.CubbyholeID,
|
|
Type: logical.TokenTypeService,
|
|
}
|
|
if diff := deep.Equal(te, expect); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
// Check that we have a lease with default duration
|
|
if resp.Auth.TTL != c.defaultLeaseTTL {
|
|
t.Fatalf("bad: %#v", resp.Auth)
|
|
}
|
|
}
|
|
|
|
// Check that we handle excluding the default policy
|
|
func TestCore_HandleRequest_CreateToken_NoDefaultPolicy(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
// Create a new credential
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
|
|
req.ClientToken = root
|
|
req.Data["policies"] = []string{"foo"}
|
|
req.Data["no_default_policy"] = true
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Ensure we got a new client token back
|
|
clientToken := resp.Auth.ClientToken
|
|
if clientToken == "" {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Check the policy and metadata
|
|
te, err := c.tokenStore.Lookup(namespace.RootContext(nil), clientToken)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expectedID, _ := c.DecodeSSCToken(clientToken)
|
|
expectedRootID, _ := c.DecodeSSCToken(root)
|
|
|
|
expect := &logical.TokenEntry{
|
|
ID: expectedID,
|
|
Accessor: te.Accessor,
|
|
Parent: expectedRootID,
|
|
Policies: []string{"foo"},
|
|
Path: "auth/token/create",
|
|
DisplayName: "token",
|
|
CreationTime: te.CreationTime,
|
|
TTL: time.Hour * 24 * 32,
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
CubbyholeID: te.CubbyholeID,
|
|
Type: logical.TokenTypeService,
|
|
}
|
|
if diff := deep.Equal(te, expect); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|
|
|
|
func TestCore_LimitedUseToken(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
// Create a new credential
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create")
|
|
req.ClientToken = root
|
|
req.Data["num_uses"] = "1"
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Put a secret
|
|
req = &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/foo",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
ClientToken: resp.Auth.ClientToken,
|
|
}
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Second operation should fail
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCore_Standby_Seal(t *testing.T) {
|
|
// Create the first core and initialize it
|
|
logger = logging.NewVaultLogger(log.Trace)
|
|
|
|
inm, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inmha, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
redirectOriginal := "http://127.0.0.1:8200"
|
|
core, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectOriginal,
|
|
DisableMlock: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core.Shutdown()
|
|
keys, root := TestCoreInit(t, core)
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Verify unsealed
|
|
if core.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Wait for core to become active
|
|
TestWaitActive(t, core)
|
|
|
|
// Check the leader is local
|
|
isLeader, advertise, _, err := core.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !isLeader {
|
|
t.Fatalf("should be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
// Create the second core and initialize it
|
|
redirectOriginal2 := "http://127.0.0.1:8500"
|
|
core2, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectOriginal2,
|
|
DisableMlock: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core2.Shutdown()
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Verify unsealed
|
|
if core2.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Core2 should be in standby
|
|
standby, err := core2.Standby()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !standby {
|
|
t.Fatalf("should be standby")
|
|
}
|
|
|
|
// Check the leader is not local
|
|
isLeader, advertise, _, err = core2.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if isLeader {
|
|
t.Fatalf("should not be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
// Seal the standby core with the correct token. Shouldn't go down
|
|
err = core2.Seal(root)
|
|
if err == nil {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
keyUUID, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Seal the standby core with an invalid token. Shouldn't go down
|
|
err = core2.Seal(keyUUID)
|
|
if err == nil {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
}
|
|
|
|
func TestCore_StepDown(t *testing.T) {
|
|
// Create the first core and initialize it
|
|
logger = logging.NewVaultLogger(log.Trace).Named(t.Name())
|
|
|
|
inm, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inmha, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
redirectOriginal := "http://127.0.0.1:8200"
|
|
core, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectOriginal,
|
|
DisableMlock: true,
|
|
Logger: logger.Named("core1"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core.Shutdown()
|
|
keys, root := TestCoreInit(t, core)
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Verify unsealed
|
|
if core.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Wait for core to become active
|
|
TestWaitActive(t, core)
|
|
|
|
// Check the leader is local
|
|
isLeader, advertise, _, err := core.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !isLeader {
|
|
t.Fatalf("should be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
// Create the second core and initialize it
|
|
redirectOriginal2 := "http://127.0.0.1:8500"
|
|
core2, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectOriginal2,
|
|
DisableMlock: true,
|
|
Logger: logger.Named("core2"),
|
|
})
|
|
defer core2.Shutdown()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Verify unsealed
|
|
if core2.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Core2 should be in standby
|
|
standby, err := core2.Standby()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !standby {
|
|
t.Fatalf("should be standby")
|
|
}
|
|
|
|
// Check the leader is not local
|
|
isLeader, advertise, _, err = core2.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if isLeader {
|
|
t.Fatalf("should not be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
req := &logical.Request{
|
|
ClientToken: root,
|
|
Path: "sys/step-down",
|
|
}
|
|
|
|
// Create an identifier for the request
|
|
req.ID, err = uuid.GenerateUUID()
|
|
if err != nil {
|
|
t.Fatalf("failed to generate identifier for the request: path: %s err: %v", req.Path, err)
|
|
}
|
|
|
|
// Step down core
|
|
err = core.StepDown(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal("error stepping down core 1")
|
|
}
|
|
|
|
// Give time to switch leaders
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Core1 should be in standby
|
|
standby, err = core.Standby()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !standby {
|
|
t.Fatalf("should be standby")
|
|
}
|
|
|
|
// Check the leader is core2
|
|
isLeader, advertise, _, err = core2.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !isLeader {
|
|
t.Fatalf("should be leader")
|
|
}
|
|
if advertise != redirectOriginal2 {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal2)
|
|
}
|
|
|
|
// Check the leader is not local
|
|
isLeader, advertise, _, err = core.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if isLeader {
|
|
t.Fatalf("should not be leader")
|
|
}
|
|
if advertise != redirectOriginal2 {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal2)
|
|
}
|
|
|
|
// Step down core2
|
|
err = core2.StepDown(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal("error stepping down core 1")
|
|
}
|
|
|
|
// Give time to switch leaders -- core 1 will still be waiting on its
|
|
// cooling off period so give it a full 10 seconds to recover
|
|
time.Sleep(10 * time.Second)
|
|
|
|
// Core2 should be in standby
|
|
standby, err = core2.Standby()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !standby {
|
|
t.Fatalf("should be standby")
|
|
}
|
|
|
|
// Check the leader is core1
|
|
isLeader, advertise, _, err = core.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !isLeader {
|
|
t.Fatalf("should be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
// Check the leader is not local
|
|
isLeader, advertise, _, err = core2.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if isLeader {
|
|
t.Fatalf("should not be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
}
|
|
|
|
func TestCore_CleanLeaderPrefix(t *testing.T) {
|
|
// Create the first core and initialize it
|
|
logger = logging.NewVaultLogger(log.Trace)
|
|
|
|
inm, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inmha, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
redirectOriginal := "http://127.0.0.1:8200"
|
|
core, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectOriginal,
|
|
DisableMlock: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core.Shutdown()
|
|
keys, root := TestCoreInit(t, core)
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Verify unsealed
|
|
if core.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Wait for core to become active
|
|
TestWaitActive(t, core)
|
|
|
|
// Ensure that the original clean function has stopped running
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Put several random entries
|
|
for i := 0; i < 5; i++ {
|
|
keyUUID, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
valueUUID, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
core.barrier.Put(namespace.RootContext(nil), &logical.StorageEntry{
|
|
Key: coreLeaderPrefix + keyUUID,
|
|
Value: []byte(valueUUID),
|
|
})
|
|
}
|
|
|
|
entries, err := core.barrier.List(namespace.RootContext(nil), coreLeaderPrefix)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if len(entries) != 6 {
|
|
t.Fatalf("wrong number of core leader prefix entries, got %d", len(entries))
|
|
}
|
|
|
|
// Check the leader is local
|
|
isLeader, advertise, _, err := core.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !isLeader {
|
|
t.Fatalf("should be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
// Create a second core, attached to same in-memory store
|
|
redirectOriginal2 := "http://127.0.0.1:8500"
|
|
core2, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectOriginal2,
|
|
DisableMlock: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core2.Shutdown()
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Verify unsealed
|
|
if core2.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Core2 should be in standby
|
|
standby, err := core2.Standby()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !standby {
|
|
t.Fatalf("should be standby")
|
|
}
|
|
|
|
// Check the leader is not local
|
|
isLeader, advertise, _, err = core2.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if isLeader {
|
|
t.Fatalf("should not be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
// Seal the first core, should step down
|
|
err = core.Seal(root)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Core should be in standby
|
|
standby, err = core.Standby()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !standby {
|
|
t.Fatalf("should be standby")
|
|
}
|
|
|
|
// Wait for core2 to become active
|
|
TestWaitActive(t, core2)
|
|
|
|
// Check the leader is local
|
|
isLeader, advertise, _, err = core2.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !isLeader {
|
|
t.Fatalf("should be leader")
|
|
}
|
|
if advertise != redirectOriginal2 {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal2)
|
|
}
|
|
|
|
// Give time for the entries to clear out; it is conservative at 1/second
|
|
time.Sleep(10 * leaderPrefixCleanDelay)
|
|
|
|
entries, err = core2.barrier.List(namespace.RootContext(nil), coreLeaderPrefix)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if len(entries) != 1 {
|
|
t.Fatalf("wrong number of core leader prefix entries, got %d", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestCore_Standby(t *testing.T) {
|
|
logger = logging.NewVaultLogger(log.Trace)
|
|
|
|
inmha, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
testCore_Standby_Common(t, inmha, inmha.(physical.HABackend))
|
|
}
|
|
|
|
func TestCore_Standby_SeparateHA(t *testing.T) {
|
|
logger = logging.NewVaultLogger(log.Trace)
|
|
|
|
inmha, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inmha2, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
testCore_Standby_Common(t, inmha, inmha2.(physical.HABackend))
|
|
}
|
|
|
|
func testCore_Standby_Common(t *testing.T, inm physical.Backend, inmha physical.HABackend) {
|
|
// Create the first core and initialize it
|
|
redirectOriginal := "http://127.0.0.1:8200"
|
|
core, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha,
|
|
RedirectAddr: redirectOriginal,
|
|
DisableMlock: true,
|
|
BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core.Shutdown()
|
|
keys, root := TestCoreInit(t, core)
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Verify unsealed
|
|
if core.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Wait for core to become active
|
|
TestWaitActive(t, core)
|
|
|
|
testCoreAddSecretMount(t, core, root)
|
|
|
|
// Put a secret
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/foo",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
_, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Check the leader is local
|
|
isLeader, advertise, _, err := core.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !isLeader {
|
|
t.Fatalf("should be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
// Create a second core, attached to same in-memory store
|
|
redirectOriginal2 := "http://127.0.0.1:8500"
|
|
core2, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha,
|
|
RedirectAddr: redirectOriginal2,
|
|
DisableMlock: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core2.Shutdown()
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Verify unsealed
|
|
if core2.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Core2 should be in standby
|
|
standby, err := core2.Standby()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !standby {
|
|
t.Fatalf("should be standby")
|
|
}
|
|
|
|
// Request should fail in standby mode
|
|
_, err = core2.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != consts.ErrStandby {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Check the leader is not local
|
|
isLeader, advertise, _, err = core2.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if isLeader {
|
|
t.Fatalf("should not be leader")
|
|
}
|
|
if advertise != redirectOriginal {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal)
|
|
}
|
|
|
|
// Seal the first core, should step down
|
|
err = core.Seal(root)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Core should be in standby
|
|
standby, err = core.Standby()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !standby {
|
|
t.Fatalf("should be standby")
|
|
}
|
|
|
|
// Wait for core2 to become active
|
|
TestWaitActive(t, core2)
|
|
|
|
// Read the secret
|
|
req = &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "secret/foo",
|
|
ClientToken: root,
|
|
}
|
|
resp, err := core2.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify the response
|
|
if resp.Data["foo"] != "bar" {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Check the leader is local
|
|
isLeader, advertise, _, err = core2.Leader()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !isLeader {
|
|
t.Fatalf("should be leader")
|
|
}
|
|
if advertise != redirectOriginal2 {
|
|
t.Fatalf("Bad advertise: %v, orig is %v", advertise, redirectOriginal2)
|
|
}
|
|
|
|
if inm.(*inmem.InmemHABackend) == inmha.(*inmem.InmemHABackend) {
|
|
lockSize := inm.(*inmem.InmemHABackend).LockMapSize()
|
|
if lockSize == 0 {
|
|
t.Fatalf("locks not used with only one HA backend")
|
|
}
|
|
} else {
|
|
lockSize := inmha.(*inmem.InmemHABackend).LockMapSize()
|
|
if lockSize == 0 {
|
|
t.Fatalf("locks not used with expected HA backend")
|
|
}
|
|
|
|
lockSize = inm.(*inmem.InmemHABackend).LockMapSize()
|
|
if lockSize != 0 {
|
|
t.Fatalf("locks used with unexpected HA backend")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure that InternalData is never returned
|
|
func TestCore_HandleRequest_Login_InternalData(t *testing.T) {
|
|
noop := &NoopBackend{
|
|
Login: []string{"login"},
|
|
Response: &logical.Response{
|
|
Auth: &logical.Auth{
|
|
Policies: []string{"foo", "bar"},
|
|
InternalData: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
BackendType: logical.TypeCredential,
|
|
}
|
|
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the credential backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to login
|
|
lreq := &logical.Request{
|
|
Path: "auth/foo/login",
|
|
}
|
|
lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Ensure we do not get the internal data
|
|
if lresp.Auth.InternalData != nil {
|
|
t.Fatalf("bad: %#v", lresp)
|
|
}
|
|
}
|
|
|
|
// Ensure that InternalData is never returned
|
|
func TestCore_HandleRequest_InternalData(t *testing.T) {
|
|
noop := &NoopBackend{
|
|
Response: &logical.Response{
|
|
Secret: &logical.Secret{
|
|
InternalData: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
}
|
|
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the credential backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to read
|
|
lreq := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "foo/test",
|
|
ClientToken: root,
|
|
}
|
|
lreq.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
|
|
lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Ensure we do not get the internal data
|
|
if lresp.Secret.InternalData != nil {
|
|
t.Fatalf("bad: %#v", lresp)
|
|
}
|
|
}
|
|
|
|
// Ensure login does not return a secret
|
|
func TestCore_HandleLogin_ReturnSecret(t *testing.T) {
|
|
// Create a badass credential backend that always logs in as armon
|
|
noopBack := &NoopBackend{
|
|
Login: []string{"login"},
|
|
Response: &logical.Response{
|
|
Secret: &logical.Secret{},
|
|
Auth: &logical.Auth{
|
|
Policies: []string{"foo", "bar"},
|
|
},
|
|
},
|
|
BackendType: logical.TypeCredential,
|
|
}
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noopBack, nil
|
|
}
|
|
|
|
// Enable the credential backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to login
|
|
lreq := &logical.Request{
|
|
Path: "auth/foo/login",
|
|
}
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err != ErrInternalError {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
// Renew should return the same lease back
|
|
func TestCore_RenewSameLease(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
// Create a leasable secret
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Read the key
|
|
req.Operation = logical.ReadOperation
|
|
req.Data = nil
|
|
err = c.PopulateTokenEntry(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
original := resp.Secret.LeaseID
|
|
|
|
// Renew the lease
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/renew/"+resp.Secret.LeaseID)
|
|
req.ClientToken = root
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify the lease did not change
|
|
if resp.Secret.LeaseID != original {
|
|
t.Fatalf("lease id changed: %s %s", original, resp.Secret.LeaseID)
|
|
}
|
|
|
|
// Renew the lease (alternate path)
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/leases/renew/"+resp.Secret.LeaseID)
|
|
req.ClientToken = root
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify the lease did not change
|
|
if resp.Secret.LeaseID != original {
|
|
t.Fatalf("lease id changed: %s %s", original, resp.Secret.LeaseID)
|
|
}
|
|
}
|
|
|
|
// Renew of a token should not create a new lease
|
|
func TestCore_RenewToken_SingleRegister(t *testing.T) {
|
|
c, _, root := TestCoreUnsealed(t)
|
|
|
|
// Create a new token
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "auth/token/create",
|
|
Data: map[string]interface{}{
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: root,
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
newClient := resp.Auth.ClientToken
|
|
|
|
// Renew the token
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/renew")
|
|
req.ClientToken = newClient
|
|
req.Data = map[string]interface{}{
|
|
"token": newClient,
|
|
}
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Revoke using the renew prefix
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/revoke-prefix/auth/token/renew/")
|
|
req.ClientToken = root
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify our token is still valid (e.g. we did not get invalidated by the revoke)
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/lookup")
|
|
req.Data = map[string]interface{}{
|
|
"token": newClient,
|
|
}
|
|
req.ClientToken = newClient
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify the token exists
|
|
if newClient != resp.Data["id"].(string) {
|
|
t.Fatalf("bad: return IDs: expected %v, got %v",
|
|
resp.Data["id"], newClient)
|
|
}
|
|
}
|
|
|
|
// Based on bug GH-203, attempt to disable a credential backend with leased secrets
|
|
func TestCore_EnableDisableCred_WithLease(t *testing.T) {
|
|
noopBack := &NoopBackend{
|
|
Login: []string{"login"},
|
|
Response: &logical.Response{
|
|
Auth: &logical.Auth{
|
|
Policies: []string{"root"},
|
|
},
|
|
},
|
|
BackendType: logical.TypeCredential,
|
|
}
|
|
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noopBack, nil
|
|
}
|
|
|
|
secretWritingPolicy := `
|
|
name = "admins"
|
|
path "secret/*" {
|
|
capabilities = ["update", "create", "read"]
|
|
}
|
|
`
|
|
|
|
ps := c.policyStore
|
|
policy, _ := ParseACLPolicy(namespace.RootNamespace, secretWritingPolicy)
|
|
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Enable the credential backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/auth/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to login -- should fail because we don't allow root to be returned
|
|
lreq := &logical.Request{
|
|
Path: "auth/foo/login",
|
|
}
|
|
lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err == nil || lresp == nil || !lresp.IsError() {
|
|
t.Fatalf("expected error trying to auth and receive root policy")
|
|
}
|
|
|
|
// Fix and try again
|
|
noopBack.Response.Auth.Policies = []string{"admins"}
|
|
lreq = &logical.Request{
|
|
Path: "auth/foo/login",
|
|
}
|
|
lresp, err = c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Create a leasable secret
|
|
req = &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "secret/test",
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"lease": "1h",
|
|
},
|
|
ClientToken: lresp.Auth.ClientToken,
|
|
}
|
|
resp, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
|
|
// Read the key
|
|
req.Operation = logical.ReadOperation
|
|
req.Data = nil
|
|
err = c.PopulateTokenEntry(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
|
|
t.Fatalf("bad: %#v", resp.Secret)
|
|
}
|
|
|
|
// Renew the lease
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/leases/renew")
|
|
req.Data = map[string]interface{}{
|
|
"lease_id": resp.Secret.LeaseID,
|
|
}
|
|
req.ClientToken = lresp.Auth.ClientToken
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Disable the credential backend
|
|
req = logical.TestRequest(t, logical.DeleteOperation, "sys/auth/foo")
|
|
req.ClientToken = root
|
|
resp, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v %#v", err, resp)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_MountPointType(t *testing.T) {
|
|
noop := &NoopBackend{
|
|
Response: &logical.Response{},
|
|
}
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the logical backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
|
|
req.Data["type"] = "noop"
|
|
req.Data["description"] = "foo"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to request
|
|
req = &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "foo/test",
|
|
Connection: &logical.Connection{},
|
|
}
|
|
req.ClientToken = root
|
|
if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify Path, MountPoint, and MountType
|
|
if noop.Requests[0].Path != "test" {
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
}
|
|
if noop.Requests[0].MountPoint != "foo/" {
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
}
|
|
if noop.Requests[0].MountType != "noop" {
|
|
t.Fatalf("bad: %#v", noop.Requests)
|
|
}
|
|
}
|
|
|
|
func TestCore_Standby_Rotate(t *testing.T) {
|
|
// Create the first core and initialize it
|
|
logger = logging.NewVaultLogger(log.Trace)
|
|
|
|
inm, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inmha, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
redirectOriginal := "http://127.0.0.1:8200"
|
|
core, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectOriginal,
|
|
DisableMlock: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core.Shutdown()
|
|
keys, root := TestCoreInit(t, core)
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Wait for core to become active
|
|
TestWaitActive(t, core)
|
|
|
|
// Create a second core, attached to same in-memory store
|
|
redirectOriginal2 := "http://127.0.0.1:8500"
|
|
core2, err := NewCore(&CoreConfig{
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectOriginal2,
|
|
DisableMlock: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
defer core2.Shutdown()
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
|
|
// Rotate the encryption key
|
|
req := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "sys/rotate",
|
|
ClientToken: root,
|
|
}
|
|
_, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Seal the first core, should step down
|
|
err = core.Seal(root)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Wait for core2 to become active
|
|
TestWaitActive(t, core2)
|
|
|
|
// Read the key status
|
|
req = &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "sys/key-status",
|
|
ClientToken: root,
|
|
}
|
|
resp, err := core2.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify the response
|
|
if resp.Data["term"] != 2 {
|
|
t.Fatalf("bad: %#v", resp)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_Headers(t *testing.T) {
|
|
noop := &NoopBackend{
|
|
Response: &logical.Response{
|
|
Data: map[string]interface{}{},
|
|
},
|
|
}
|
|
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Mount tune
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo/tune")
|
|
req.Data["passthrough_request_headers"] = []string{"Should-Passthrough", "should-passthrough-case-insensitive"}
|
|
req.ClientToken = root
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to read
|
|
lreq := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "foo/test",
|
|
ClientToken: root,
|
|
Headers: map[string][]string{
|
|
"Should-Passthrough": {"foo"},
|
|
"Should-Passthrough-Case-Insensitive": {"baz"},
|
|
"Should-Not-Passthrough": {"bar"},
|
|
consts.AuthHeaderName: {"nope"},
|
|
},
|
|
}
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Check the headers
|
|
headers := noop.Requests[0].Headers
|
|
|
|
// Test passthrough values
|
|
if val, ok := headers["Should-Passthrough"]; ok {
|
|
expected := []string{"foo"}
|
|
if !reflect.DeepEqual(val, expected) {
|
|
t.Fatalf("expected: %v, got: %v", expected, val)
|
|
}
|
|
} else {
|
|
t.Fatalf("expected 'Should-Passthrough' to be present in the headers map")
|
|
}
|
|
|
|
if val, ok := headers["Should-Passthrough-Case-Insensitive"]; ok {
|
|
expected := []string{"baz"}
|
|
if !reflect.DeepEqual(val, expected) {
|
|
t.Fatalf("expected: %v, got: %v", expected, val)
|
|
}
|
|
} else {
|
|
t.Fatal("expected 'Should-Passthrough-Case-Insensitive' to be present in the headers map")
|
|
}
|
|
|
|
if _, ok := headers["Should-Not-Passthrough"]; ok {
|
|
t.Fatal("did not expect 'Should-Not-Passthrough' to be in the headers map")
|
|
}
|
|
|
|
if _, ok := headers[consts.AuthHeaderName]; ok {
|
|
t.Fatalf("did not expect %q to be in the headers map", consts.AuthHeaderName)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_Headers_denyList(t *testing.T) {
|
|
noop := &NoopBackend{
|
|
Response: &logical.Response{
|
|
Data: map[string]interface{}{},
|
|
},
|
|
}
|
|
|
|
c, _, root := TestCoreUnsealed(t)
|
|
c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
|
return noop, nil
|
|
}
|
|
|
|
// Enable the backend
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo")
|
|
req.Data["type"] = "noop"
|
|
req.ClientToken = root
|
|
_, err := c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Mount tune
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo/tune")
|
|
req.Data["passthrough_request_headers"] = []string{"Authorization", consts.AuthHeaderName}
|
|
req.ClientToken = root
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Attempt to read
|
|
lreq := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "foo/test",
|
|
ClientToken: root,
|
|
Headers: map[string][]string{
|
|
consts.AuthHeaderName: {"foo"},
|
|
},
|
|
}
|
|
_, err = c.HandleRequest(namespace.RootContext(nil), lreq)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Check the headers
|
|
headers := noop.Requests[0].Headers
|
|
|
|
// Test passthrough values, they should not be present in the backend
|
|
if _, ok := headers[consts.AuthHeaderName]; ok {
|
|
t.Fatalf("did not expect %q to be in the headers map", consts.AuthHeaderName)
|
|
}
|
|
}
|
|
|
|
func TestCore_HandleRequest_TokenCreate_RegisterAuthFailure(t *testing.T) {
|
|
core, _, root := TestCoreUnsealed(t)
|
|
|
|
// Create a root token and use that for subsequent requests
|
|
req := logical.TestRequest(t, logical.CreateOperation, "auth/token/create")
|
|
req.Data = map[string]interface{}{
|
|
"policies": []string{"root"},
|
|
}
|
|
req.ClientToken = root
|
|
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil || resp.Auth == nil || resp.Auth.ClientToken == "" {
|
|
t.Fatalf("expected a response from token creation, got: %#v", resp)
|
|
}
|
|
tokenWithRootPolicy := resp.Auth.ClientToken
|
|
|
|
// Use new token to create yet a new token, this should succeed
|
|
req = logical.TestRequest(t, logical.CreateOperation, "auth/token/create")
|
|
req.ClientToken = tokenWithRootPolicy
|
|
_, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Try again but force failure on RegisterAuth to simulate a network failure
|
|
// when registering the lease (e.g. a storage failure). This should trigger
|
|
// an expiration manager cleanup on the newly created token
|
|
core.expiration.testRegisterAuthFailure.Store(true)
|
|
req = logical.TestRequest(t, logical.CreateOperation, "auth/token/create")
|
|
req.ClientToken = tokenWithRootPolicy
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err == nil {
|
|
t.Fatalf("expected error, got a response: %#v", resp)
|
|
}
|
|
core.expiration.testRegisterAuthFailure.Store(false)
|
|
|
|
// Do a lookup against the client token that we used for the failed request.
|
|
// It should still be present
|
|
req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/lookup")
|
|
req.Data = map[string]interface{}{
|
|
"token": tokenWithRootPolicy,
|
|
}
|
|
req.ClientToken = root
|
|
_, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Do a token creation request with the token to ensure that it's still
|
|
// valid, should succeed.
|
|
req = logical.TestRequest(t, logical.CreateOperation, "auth/token/create")
|
|
req.ClientToken = tokenWithRootPolicy
|
|
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// mockServiceRegistration helps test whether standalone ServiceRegistration works
|
|
type mockServiceRegistration struct {
|
|
notifyActiveCount int
|
|
notifySealedCount int
|
|
notifyPerfCount int
|
|
notifyInitCount int
|
|
runDiscoveryCount int
|
|
}
|
|
|
|
func (m *mockServiceRegistration) Run(shutdownCh <-chan struct{}, wait *sync.WaitGroup, redirectAddr string) error {
|
|
m.runDiscoveryCount++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockServiceRegistration) NotifyActiveStateChange(isActive bool) error {
|
|
m.notifyActiveCount++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockServiceRegistration) NotifySealedStateChange(isSealed bool) error {
|
|
m.notifySealedCount++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockServiceRegistration) NotifyPerformanceStandbyStateChange(isStandby bool) error {
|
|
m.notifyPerfCount++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockServiceRegistration) NotifyInitializedStateChange(isInitialized bool) error {
|
|
m.notifyInitCount++
|
|
return nil
|
|
}
|
|
|
|
// TestCore_ServiceRegistration tests whether standalone ServiceRegistration works
|
|
func TestCore_ServiceRegistration(t *testing.T) {
|
|
// Make a mock service discovery
|
|
sr := &mockServiceRegistration{}
|
|
|
|
// Create the core
|
|
logger = logging.NewVaultLogger(log.Trace)
|
|
inm, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inmha, err := inmem.NewInmemHA(nil, logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const redirectAddr = "http://127.0.0.1:8200"
|
|
core, err := NewCore(&CoreConfig{
|
|
ServiceRegistration: sr,
|
|
Physical: inm,
|
|
HAPhysical: inmha.(physical.HABackend),
|
|
RedirectAddr: redirectAddr,
|
|
DisableMlock: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer core.Shutdown()
|
|
|
|
// Vault should not yet be registered
|
|
if diff := deep.Equal(sr, &mockServiceRegistration{}); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
// Vault should be registered
|
|
if diff := deep.Equal(sr, &mockServiceRegistration{
|
|
runDiscoveryCount: 1,
|
|
}); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
|
|
// Initialize and unseal the core
|
|
keys, _ := TestCoreInit(t, core)
|
|
for _, key := range keys {
|
|
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
|
|
t.Fatalf("unseal err: %s", err)
|
|
}
|
|
}
|
|
if core.Sealed() {
|
|
t.Fatal("should not be sealed")
|
|
}
|
|
|
|
// Wait for core to become active
|
|
TestWaitActive(t, core)
|
|
|
|
// Vault should be registered, unsealed, and active
|
|
if diff := deep.Equal(sr, &mockServiceRegistration{
|
|
runDiscoveryCount: 1,
|
|
notifyActiveCount: 1,
|
|
notifySealedCount: 1,
|
|
notifyInitCount: 1,
|
|
}); diff != nil {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|
|
|
|
func TestDetectedDeadlock(t *testing.T) {
|
|
testCore, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{DetectDeadlocks: "statelock"})
|
|
InduceDeadlock(t, testCore, 1)
|
|
}
|
|
|
|
func TestDefaultDeadlock(t *testing.T) {
|
|
testCore, _, _ := TestCoreUnsealed(t)
|
|
InduceDeadlock(t, testCore, 0)
|
|
}
|
|
|
|
func RestoreDeadlockOpts() func() {
|
|
opts := deadlock.Opts
|
|
return func() {
|
|
deadlock.Opts = opts
|
|
}
|
|
}
|
|
|
|
func InduceDeadlock(t *testing.T, vaultcore *Core, expected uint32) {
|
|
defer RestoreDeadlockOpts()()
|
|
var deadlocks uint32
|
|
deadlock.Opts.OnPotentialDeadlock = func() {
|
|
atomic.AddUint32(&deadlocks, 1)
|
|
}
|
|
var mtx deadlock.Mutex
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
vaultcore.expiration.coreStateLock.Lock()
|
|
mtx.Lock()
|
|
mtx.Unlock()
|
|
vaultcore.expiration.coreStateLock.Unlock()
|
|
}()
|
|
wg.Wait()
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
mtx.Lock()
|
|
vaultcore.expiration.coreStateLock.RLock()
|
|
vaultcore.expiration.coreStateLock.RUnlock()
|
|
mtx.Unlock()
|
|
}()
|
|
wg.Wait()
|
|
if atomic.LoadUint32(&deadlocks) != expected {
|
|
t.Fatalf("expected 1 deadlock, detected %d", deadlocks)
|
|
}
|
|
}
|