2015-03-13 18:11:59 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
2015-07-10 22:18:02 +00:00
|
|
|
"bytes"
|
2018-01-08 18:31:38 +00:00
|
|
|
"context"
|
2017-07-31 15:28:06 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
2016-01-15 15:55:35 +00:00
|
|
|
"crypto/rand"
|
2015-11-19 01:26:03 +00:00
|
|
|
"crypto/sha256"
|
2016-08-15 13:42:42 +00:00
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2017-07-31 15:28:06 +00:00
|
|
|
"crypto/x509/pkix"
|
|
|
|
"encoding/base64"
|
2016-08-15 13:42:42 +00:00
|
|
|
"encoding/pem"
|
2015-07-10 15:56:14 +00:00
|
|
|
"fmt"
|
2017-04-07 22:50:03 +00:00
|
|
|
"io"
|
2017-07-31 15:28:06 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"math/big"
|
|
|
|
mathrand "math/rand"
|
2015-07-10 22:18:02 +00:00
|
|
|
"net"
|
2016-08-15 13:42:42 +00:00
|
|
|
"net/http"
|
2017-04-07 22:50:03 +00:00
|
|
|
"os"
|
2015-07-10 22:18:02 +00:00
|
|
|
"os/exec"
|
2017-04-07 22:50:03 +00:00
|
|
|
"path/filepath"
|
2017-05-24 00:36:20 +00:00
|
|
|
"sync"
|
2015-09-02 19:56:58 +00:00
|
|
|
"time"
|
2015-03-13 18:11:59 +00:00
|
|
|
|
2018-04-03 00:46:59 +00:00
|
|
|
log "github.com/hashicorp/go-hclog"
|
2017-01-17 20:43:10 +00:00
|
|
|
"github.com/mitchellh/copystructure"
|
2016-08-19 20:45:17 +00:00
|
|
|
|
2015-07-10 22:18:02 +00:00
|
|
|
"golang.org/x/crypto/ssh"
|
2017-02-24 15:45:29 +00:00
|
|
|
"golang.org/x/net/http2"
|
2015-07-10 22:18:02 +00:00
|
|
|
|
2017-02-24 15:45:29 +00:00
|
|
|
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
2016-01-09 02:21:02 +00:00
|
|
|
"github.com/hashicorp/go-uuid"
|
2017-02-24 15:45:29 +00:00
|
|
|
"github.com/hashicorp/vault/api"
|
2015-04-02 01:36:13 +00:00
|
|
|
"github.com/hashicorp/vault/audit"
|
2018-04-03 00:46:59 +00:00
|
|
|
"github.com/hashicorp/vault/helper/logging"
|
2017-07-31 15:28:06 +00:00
|
|
|
"github.com/hashicorp/vault/helper/reload"
|
2015-11-19 01:26:03 +00:00
|
|
|
"github.com/hashicorp/vault/helper/salt"
|
2015-04-01 03:24:51 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
2015-03-13 18:11:59 +00:00
|
|
|
"github.com/hashicorp/vault/physical"
|
2017-08-01 18:07:08 +00:00
|
|
|
"github.com/mitchellh/go-testing-interface"
|
2017-08-03 17:24:27 +00:00
|
|
|
|
|
|
|
physInmem "github.com/hashicorp/vault/physical/inmem"
|
2015-03-13 18:11:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// This file contains a number of methods that are useful for unit
|
|
|
|
// tests within other packages.
|
|
|
|
|
2015-07-10 22:18:02 +00:00
|
|
|
const (
|
|
|
|
testSharedPublicKey = `
|
2015-07-10 23:27:21 +00:00
|
|
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9i+hFxZHGo6KblVme4zrAcJstR6I0PTJozW286X4WyvPnkMYDQ5mnhEYC7UWCvjoTWbPEXPX7NjhRtwQTGD67bV+lrxgfyzK1JZbUXK4PwgKJvQD+XyyWYMzDgGSQY61KUSqCxymSm/9NZkPU3ElaQ9xQuTzPpztM4ROfb8f2Yv6/ZESZsTo0MTAkp8Pcy+WkioI/uJ1H7zqs0EA4OMY4aDJRu0UtP4rTVeYNEAuRXdX+eH4aW3KMvhzpFTjMbaJHJXlEeUm2SaX5TNQyTOvghCeQILfYIL/Ca2ij8iwCmulwdV6eQGfd4VDu40PvSnmfoaE38o6HaPnX0kUcnKiT
|
2015-07-10 22:18:02 +00:00
|
|
|
`
|
|
|
|
testSharedPrivateKey = `
|
|
|
|
-----BEGIN RSA PRIVATE KEY-----
|
|
|
|
MIIEogIBAAKCAQEAvYvoRcWRxqOim5VZnuM6wHCbLUeiND0yaM1tvOl+Fsrz55DG
|
|
|
|
A0OZp4RGAu1Fgr46E1mzxFz1+zY4UbcEExg+u21fpa8YH8sytSWW1FyuD8ICib0A
|
|
|
|
/l8slmDMw4BkkGOtSlEqgscpkpv/TWZD1NxJWkPcULk8z6c7TOETn2/H9mL+v2RE
|
|
|
|
mbE6NDEwJKfD3MvlpIqCP7idR+86rNBAODjGOGgyUbtFLT+K01XmDRALkV3V/nh+
|
|
|
|
GltyjL4c6RU4zG2iRyV5RHlJtkml+UzUMkzr4IQnkCC32CC/wmtoo/IsAprpcHVe
|
|
|
|
nkBn3eFQ7uND70p5n6GhN/KOh2j519JFHJyokwIDAQABAoIBAHX7VOvBC3kCN9/x
|
|
|
|
+aPdup84OE7Z7MvpX6w+WlUhXVugnmsAAVDczhKoUc/WktLLx2huCGhsmKvyVuH+
|
|
|
|
MioUiE+vx75gm3qGx5xbtmOfALVMRLopjCnJYf6EaFA0ZeQ+NwowNW7Lu0PHmAU8
|
|
|
|
Z3JiX8IwxTz14DU82buDyewO7v+cEr97AnERe3PUcSTDoUXNaoNxjNpEJkKREY6h
|
|
|
|
4hAY676RT/GsRcQ8tqe/rnCqPHNd7JGqL+207FK4tJw7daoBjQyijWuB7K5chSal
|
|
|
|
oPInylM6b13ASXuOAOT/2uSUBWmFVCZPDCmnZxy2SdnJGbsJAMl7Ma3MUlaGvVI+
|
|
|
|
Tfh1aQkCgYEA4JlNOabTb3z42wz6mz+Nz3JRwbawD+PJXOk5JsSnV7DtPtfgkK9y
|
|
|
|
6FTQdhnozGWShAvJvc+C4QAihs9AlHXoaBY5bEU7R/8UK/pSqwzam+MmxmhVDV7G
|
|
|
|
IMQPV0FteoXTaJSikhZ88mETTegI2mik+zleBpVxvfdhE5TR+lq8Br0CgYEA2AwJ
|
|
|
|
CUD5CYUSj09PluR0HHqamWOrJkKPFPwa+5eiTTCzfBBxImYZh7nXnWuoviXC0sg2
|
|
|
|
AuvCW+uZ48ygv/D8gcz3j1JfbErKZJuV+TotK9rRtNIF5Ub7qysP7UjyI7zCssVM
|
|
|
|
kuDd9LfRXaB/qGAHNkcDA8NxmHW3gpln4CFdSY8CgYANs4xwfercHEWaJ1qKagAe
|
|
|
|
rZyrMpffAEhicJ/Z65lB0jtG4CiE6w8ZeUMWUVJQVcnwYD+4YpZbX4S7sJ0B8Ydy
|
|
|
|
AhkSr86D/92dKTIt2STk6aCN7gNyQ1vW198PtaAWH1/cO2UHgHOy3ZUt5X/Uwxl9
|
|
|
|
cex4flln+1Viumts2GgsCQKBgCJH7psgSyPekK5auFdKEr5+Gc/jB8I/Z3K9+g4X
|
|
|
|
5nH3G1PBTCJYLw7hRzw8W/8oALzvddqKzEFHphiGXK94Lqjt/A4q1OdbCrhiE68D
|
|
|
|
My21P/dAKB1UYRSs9Y8CNyHCjuZM9jSMJ8vv6vG/SOJPsnVDWVAckAbQDvlTHC9t
|
|
|
|
O98zAoGAcbW6uFDkrv0XMCpB9Su3KaNXOR0wzag+WIFQRXCcoTvxVi9iYfUReQPi
|
|
|
|
oOyBJU/HMVvBfv4g+OVFLVgSwwm6owwsouZ0+D/LasbuHqYyqYqdyPJQYzWA2Y+F
|
|
|
|
+B6f4RoPdSXj24JHPg/ioRxjaj094UXJxua2yfkcecGNEuBQHSs=
|
|
|
|
-----END RSA PRIVATE KEY-----
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
2015-03-13 18:11:59 +00:00
|
|
|
// TestCore returns a pure in-memory, uninitialized core for testing.
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestCore(t testing.T) *Core {
|
2017-09-15 04:21:35 +00:00
|
|
|
return TestCoreWithSeal(t, nil, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestCoreRaw returns a pure in-memory, uninitialized core for testing. The raw
|
|
|
|
// storage endpoints are enabled with this core.
|
|
|
|
func TestCoreRaw(t testing.T) *Core {
|
|
|
|
return TestCoreWithSeal(t, nil, true)
|
2016-04-25 19:39:04 +00:00
|
|
|
}
|
|
|
|
|
2017-08-07 13:52:49 +00:00
|
|
|
// TestCoreNewSeal returns a pure in-memory, uninitialized core with
|
|
|
|
// the new seal configuration.
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestCoreNewSeal(t testing.T) *Core {
|
2017-10-23 17:42:04 +00:00
|
|
|
seal := NewTestSeal(t, nil)
|
|
|
|
return TestCoreWithSeal(t, seal, false)
|
2017-01-12 00:55:10 +00:00
|
|
|
}
|
|
|
|
|
2016-04-25 19:39:04 +00:00
|
|
|
// TestCoreWithSeal returns a pure in-memory, uninitialized core with the
|
|
|
|
// specified seal for testing.
|
2017-09-15 04:21:35 +00:00
|
|
|
func TestCoreWithSeal(t testing.T, testSeal Seal, enableRaw bool) *Core {
|
2018-04-03 00:46:59 +00:00
|
|
|
logger := logging.NewVaultLogger(log.Trace)
|
2017-08-03 17:24:27 +00:00
|
|
|
physicalBackend, err := physInmem.NewInmem(nil, logger)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-16 18:16:06 +00:00
|
|
|
|
|
|
|
conf := testCoreConfig(t, physicalBackend, logger)
|
|
|
|
|
2017-09-15 04:21:35 +00:00
|
|
|
if enableRaw {
|
|
|
|
conf.EnableRaw = true
|
|
|
|
}
|
|
|
|
|
2017-02-16 18:16:06 +00:00
|
|
|
if testSeal != nil {
|
|
|
|
conf.Seal = testSeal
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := NewCore(conf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2017-08-01 18:07:08 +00:00
|
|
|
func testCoreConfig(t testing.T, physicalBackend physical.Backend, logger log.Logger) *CoreConfig {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Helper()
|
2015-04-02 01:36:13 +00:00
|
|
|
noopAudits := map[string]audit.Factory{
|
2018-01-19 06:44:44 +00:00
|
|
|
"noop": func(_ context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
2015-11-19 01:26:03 +00:00
|
|
|
view := &logical.InmemStorage{}
|
2018-01-19 06:44:44 +00:00
|
|
|
view.Put(context.Background(), &logical.StorageEntry{
|
2015-11-19 01:26:03 +00:00
|
|
|
Key: "salt",
|
|
|
|
Value: []byte("foo"),
|
|
|
|
})
|
2017-05-24 00:36:20 +00:00
|
|
|
config.SaltConfig = &salt.Config{
|
2015-11-19 01:26:03 +00:00
|
|
|
HMAC: sha256.New,
|
|
|
|
HMACType: "hmac-sha256",
|
|
|
|
}
|
2017-05-24 00:36:20 +00:00
|
|
|
config.SaltView = view
|
2015-09-18 21:36:42 +00:00
|
|
|
return &noopAudit{
|
|
|
|
Config: config,
|
|
|
|
}, nil
|
2015-04-02 01:36:13 +00:00
|
|
|
},
|
|
|
|
}
|
2017-10-23 18:59:37 +00:00
|
|
|
|
2015-04-01 03:24:51 +00:00
|
|
|
noopBackends := make(map[string]logical.Factory)
|
2018-01-19 06:44:44 +00:00
|
|
|
noopBackends["noop"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
|
2015-09-04 20:58:12 +00:00
|
|
|
b := new(framework.Backend)
|
2018-01-19 06:44:44 +00:00
|
|
|
b.Setup(ctx, config)
|
2015-09-04 20:58:12 +00:00
|
|
|
return b, nil
|
2015-04-01 03:24:51 +00:00
|
|
|
}
|
2018-01-19 06:44:44 +00:00
|
|
|
noopBackends["http"] = func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
|
2015-05-27 21:19:12 +00:00
|
|
|
return new(rawHTTP), nil
|
|
|
|
}
|
2017-10-11 17:21:20 +00:00
|
|
|
|
|
|
|
credentialBackends := make(map[string]logical.Factory)
|
|
|
|
for backendName, backendFactory := range noopBackends {
|
|
|
|
credentialBackends[backendName] = backendFactory
|
|
|
|
}
|
|
|
|
for backendName, backendFactory := range testCredentialBackends {
|
|
|
|
credentialBackends[backendName] = backendFactory
|
|
|
|
}
|
|
|
|
|
2015-07-10 15:56:14 +00:00
|
|
|
logicalBackends := make(map[string]logical.Factory)
|
|
|
|
for backendName, backendFactory := range noopBackends {
|
|
|
|
logicalBackends[backendName] = backendFactory
|
|
|
|
}
|
2017-10-23 18:59:37 +00:00
|
|
|
|
2017-09-15 13:02:29 +00:00
|
|
|
logicalBackends["kv"] = LeasedPassthroughBackendFactory
|
2015-07-10 15:56:14 +00:00
|
|
|
for backendName, backendFactory := range testLogicalBackends {
|
|
|
|
logicalBackends[backendName] = backendFactory
|
|
|
|
}
|
2015-04-01 03:24:51 +00:00
|
|
|
|
2016-04-25 19:39:04 +00:00
|
|
|
conf := &CoreConfig{
|
2015-04-01 03:24:51 +00:00
|
|
|
Physical: physicalBackend,
|
2015-04-02 01:36:13 +00:00
|
|
|
AuditBackends: noopAudits,
|
2015-07-10 15:56:14 +00:00
|
|
|
LogicalBackends: logicalBackends,
|
2017-10-11 17:21:20 +00:00
|
|
|
CredentialBackends: credentialBackends,
|
2015-04-29 01:12:57 +00:00
|
|
|
DisableMlock: true,
|
2016-04-26 03:10:32 +00:00
|
|
|
Logger: logger,
|
2016-04-25 19:39:04 +00:00
|
|
|
}
|
|
|
|
|
2017-02-16 18:16:06 +00:00
|
|
|
return conf
|
2015-03-13 18:11:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestCoreInit initializes the core with a single key, and returns
|
2015-03-24 18:37:07 +00:00
|
|
|
// the key that must be used to unseal the core and a root token.
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestCoreInit(t testing.T, core *Core) ([][]byte, string) {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Helper()
|
|
|
|
secretShares, _, root := TestCoreInitClusterWrapperSetup(t, core, nil, nil)
|
|
|
|
return secretShares, root
|
2016-08-15 13:42:42 +00:00
|
|
|
}
|
|
|
|
|
2017-10-23 18:59:37 +00:00
|
|
|
func TestCoreInitClusterWrapperSetup(t testing.T, core *Core, clusterAddrs []*net.TCPAddr, handler http.Handler) ([][]byte, [][]byte, string) {
|
|
|
|
t.Helper()
|
2016-08-19 15:03:53 +00:00
|
|
|
core.SetClusterListenerAddrs(clusterAddrs)
|
2017-05-24 14:38:48 +00:00
|
|
|
core.SetClusterHandler(handler)
|
2017-10-23 18:59:37 +00:00
|
|
|
|
|
|
|
barrierConfig := &SealConfig{
|
|
|
|
SecretShares: 3,
|
|
|
|
SecretThreshold: 3,
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we support storing barrier keys, then set that to equal the min threshold to unseal
|
2018-01-19 08:44:06 +00:00
|
|
|
if core.seal.StoredKeysSupported() {
|
2017-10-23 18:59:37 +00:00
|
|
|
barrierConfig.StoredShares = barrierConfig.SecretThreshold
|
|
|
|
}
|
|
|
|
|
|
|
|
recoveryConfig := &SealConfig{
|
|
|
|
SecretShares: 3,
|
|
|
|
SecretThreshold: 3,
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
result, err := core.Initialize(context.Background(), &InitParams{
|
2017-10-23 18:59:37 +00:00
|
|
|
BarrierConfig: barrierConfig,
|
|
|
|
RecoveryConfig: recoveryConfig,
|
2016-09-13 22:42:24 +00:00
|
|
|
})
|
2015-03-13 18:11:59 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
2017-10-23 18:59:37 +00:00
|
|
|
return result.SecretShares, result.RecoveryShares, result.RootToken
|
2015-03-15 00:47:11 +00:00
|
|
|
}
|
|
|
|
|
2016-08-15 13:42:42 +00:00
|
|
|
func TestCoreUnseal(core *Core, key []byte) (bool, error) {
|
|
|
|
return core.Unseal(key)
|
|
|
|
}
|
|
|
|
|
2017-11-07 20:15:39 +00:00
|
|
|
func TestCoreUnsealWithRecoveryKeys(core *Core, key []byte) (bool, error) {
|
2018-01-19 06:44:44 +00:00
|
|
|
return core.UnsealWithRecoveryKeys(context.Background(), key)
|
2017-11-07 20:15:39 +00:00
|
|
|
}
|
|
|
|
|
2015-03-15 00:47:11 +00:00
|
|
|
// TestCoreUnsealed returns a pure in-memory core that is already
|
|
|
|
// initialized and unsealed.
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestCoreUnsealed(t testing.T) (*Core, [][]byte, string) {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Helper()
|
2015-03-15 00:47:11 +00:00
|
|
|
core := TestCore(t)
|
2017-09-15 04:21:35 +00:00
|
|
|
return testCoreUnsealed(t, core)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestCoreUnsealedRaw returns a pure in-memory core that is already
|
|
|
|
// initialized, unsealed, and with raw endpoints enabled.
|
|
|
|
func TestCoreUnsealedRaw(t testing.T) (*Core, [][]byte, string) {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Helper()
|
2017-09-15 04:21:35 +00:00
|
|
|
core := TestCoreRaw(t)
|
|
|
|
return testCoreUnsealed(t, core)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testCoreUnsealed(t testing.T, core *Core) (*Core, [][]byte, string) {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Helper()
|
2017-01-17 20:43:10 +00:00
|
|
|
keys, token := TestCoreInit(t, core)
|
|
|
|
for _, key := range keys {
|
|
|
|
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
|
|
|
|
t.Fatalf("unseal err: %s", err)
|
|
|
|
}
|
2015-03-15 00:47:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sealed, err := core.Sealed()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err checking seal status: %s", err)
|
|
|
|
}
|
|
|
|
if sealed {
|
|
|
|
t.Fatal("should not be sealed")
|
|
|
|
}
|
|
|
|
|
2017-01-17 20:43:10 +00:00
|
|
|
return core, keys, token
|
2015-03-13 18:11:59 +00:00
|
|
|
}
|
2015-03-15 01:25:36 +00:00
|
|
|
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestCoreUnsealedBackend(t testing.T, backend physical.Backend) (*Core, [][]byte, string) {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Helper()
|
2018-04-03 00:46:59 +00:00
|
|
|
logger := logging.NewVaultLogger(log.Trace)
|
2017-02-16 18:16:06 +00:00
|
|
|
conf := testCoreConfig(t, backend, logger)
|
2017-10-23 17:42:04 +00:00
|
|
|
conf.Seal = NewTestSeal(t, nil)
|
2017-02-16 18:16:06 +00:00
|
|
|
|
|
|
|
core, err := NewCore(conf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
2016-01-09 02:21:02 +00:00
|
|
|
|
2017-02-16 18:16:06 +00:00
|
|
|
keys, token := TestCoreInit(t, core)
|
|
|
|
for _, key := range keys {
|
|
|
|
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
|
|
|
|
t.Fatalf("unseal err: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-02-16 18:16:06 +00:00
|
|
|
sealed, err := core.Sealed()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err checking seal status: %s", err)
|
|
|
|
}
|
|
|
|
if sealed {
|
|
|
|
t.Fatal("should not be sealed")
|
|
|
|
}
|
|
|
|
|
|
|
|
return core, keys, token
|
|
|
|
}
|
|
|
|
|
2017-08-01 18:07:08 +00:00
|
|
|
func testTokenStore(t testing.T, c *Core) *TokenStore {
|
2016-01-09 02:21:02 +00:00
|
|
|
me := &MountEntry{
|
2016-05-26 17:38:51 +00:00
|
|
|
Table: credentialTableType,
|
2016-01-09 02:21:02 +00:00
|
|
|
Path: "token/",
|
|
|
|
Type: "token",
|
|
|
|
Description: "token based credentials",
|
|
|
|
}
|
|
|
|
|
|
|
|
meUUID, err := uuid.GenerateUUID()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
me.UUID = meUUID
|
|
|
|
|
|
|
|
view := NewBarrierView(c.barrier, credentialBarrierPrefix+me.UUID+"/")
|
2017-02-17 04:09:39 +00:00
|
|
|
sysView := c.mountEntrySysView(me)
|
2016-01-09 02:21:02 +00:00
|
|
|
|
2018-03-21 19:04:27 +00:00
|
|
|
tokenstore, _ := c.newCredentialBackend(context.Background(), me, sysView, view)
|
2016-01-09 02:21:02 +00:00
|
|
|
ts := tokenstore.(*TokenStore)
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
err = c.router.Unmount(context.Background(), "auth/token/")
|
2017-09-05 15:09:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = c.router.Mount(ts, "auth/token/", &MountEntry{Table: credentialTableType, UUID: "authtokenuuid", Path: "auth/token", Accessor: "authtokenaccessor"}, ts.view)
|
2017-07-01 00:54:05 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-01-09 02:21:02 +00:00
|
|
|
|
2017-09-05 15:09:00 +00:00
|
|
|
ts.SetExpirationManager(c.expiration)
|
2016-01-09 02:21:02 +00:00
|
|
|
|
2017-02-16 18:16:06 +00:00
|
|
|
return ts
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestCoreWithTokenStore returns an in-memory core that has a token store
|
|
|
|
// mounted, so that logical token functions can be used
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestCoreWithTokenStore(t testing.T) (*Core, *TokenStore, [][]byte, string) {
|
2017-02-16 18:16:06 +00:00
|
|
|
c, keys, root := TestCoreUnsealed(t)
|
|
|
|
|
2018-02-09 19:04:25 +00:00
|
|
|
return c, c.tokenStore, keys, root
|
2017-02-16 18:16:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestCoreWithBackendTokenStore returns a core that has a token store
|
|
|
|
// mounted and used the provided physical backend, so that logical token
|
|
|
|
// functions can be used
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestCoreWithBackendTokenStore(t testing.T, backend physical.Backend) (*Core, *TokenStore, [][]byte, string) {
|
2017-02-16 18:16:06 +00:00
|
|
|
c, keys, root := TestCoreUnsealedBackend(t, backend)
|
|
|
|
ts := testTokenStore(t, c)
|
|
|
|
|
2017-01-17 20:43:10 +00:00
|
|
|
return c, ts, keys, root
|
2016-01-09 02:21:02 +00:00
|
|
|
}
|
|
|
|
|
2015-03-15 01:25:36 +00:00
|
|
|
// TestKeyCopy is a silly little function to just copy the key so that
|
|
|
|
// it can be used with Unseal easily.
|
|
|
|
func TestKeyCopy(key []byte) []byte {
|
|
|
|
result := make([]byte, len(key))
|
|
|
|
copy(result, key)
|
|
|
|
return result
|
|
|
|
}
|
2015-04-02 01:36:13 +00:00
|
|
|
|
2017-03-23 22:54:15 +00:00
|
|
|
func TestDynamicSystemView(c *Core) *dynamicSystemView {
|
2017-04-07 22:50:03 +00:00
|
|
|
me := &MountEntry{
|
|
|
|
Config: MountConfig{
|
|
|
|
DefaultLeaseTTL: 24 * time.Hour,
|
|
|
|
MaxLeaseTTL: 2 * 24 * time.Hour,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return &dynamicSystemView{c, me}
|
|
|
|
}
|
|
|
|
|
2017-12-15 18:31:57 +00:00
|
|
|
// TestAddTestPlugin registers the testFunc as part of the plugin command to the
|
|
|
|
// plugin catalog.
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestAddTestPlugin(t testing.T, c *Core, name, testFunc string) {
|
2017-04-10 21:12:28 +00:00
|
|
|
file, err := os.Open(os.Args[0])
|
2017-04-07 22:50:03 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
hash := sha256.New()
|
|
|
|
|
|
|
|
_, err = io.Copy(hash, file)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sum := hash.Sum(nil)
|
2017-09-01 05:02:03 +00:00
|
|
|
|
|
|
|
// Determine plugin directory path
|
|
|
|
fullPath, err := filepath.EvalSymlinks(os.Args[0])
|
2017-04-07 22:50:03 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-09-01 05:02:03 +00:00
|
|
|
directoryPath := filepath.Dir(fullPath)
|
|
|
|
|
|
|
|
// Set core's plugin directory and plugin catalog directory
|
|
|
|
c.pluginDirectory = directoryPath
|
|
|
|
c.pluginCatalog.directory = directoryPath
|
2017-04-07 22:50:03 +00:00
|
|
|
|
2018-01-18 00:19:28 +00:00
|
|
|
command := fmt.Sprintf("%s", filepath.Base(os.Args[0]))
|
|
|
|
args := []string{fmt.Sprintf("--test.run=%s", testFunc)}
|
2018-01-19 06:44:44 +00:00
|
|
|
err = c.pluginCatalog.Set(context.Background(), name, command, args, sum)
|
2017-04-07 22:50:03 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-03-23 22:54:15 +00:00
|
|
|
}
|
|
|
|
|
2017-12-15 18:38:01 +00:00
|
|
|
// TestAddTestPluginTempDir registers the testFunc as part of the plugin command to the
|
|
|
|
// plugin catalog. It uses tmpDir as the plugin directory.
|
2017-12-15 18:31:57 +00:00
|
|
|
func TestAddTestPluginTempDir(t testing.T, c *Core, name, testFunc, tempDir string) {
|
|
|
|
file, err := os.Open(os.Args[0])
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
fi, err := file.Stat()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy over the file to the temp dir
|
|
|
|
dst := filepath.Join(tempDir, filepath.Base(os.Args[0]))
|
|
|
|
out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer out.Close()
|
|
|
|
|
|
|
|
if _, err = io.Copy(out, file); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = out.Sync()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-12-15 18:38:01 +00:00
|
|
|
// Determine plugin directory full path
|
2017-12-15 18:31:57 +00:00
|
|
|
fullPath, err := filepath.EvalSymlinks(tempDir)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
reader, err := os.Open(filepath.Join(fullPath, filepath.Base(os.Args[0])))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-12-15 18:38:01 +00:00
|
|
|
defer reader.Close()
|
2017-12-15 18:31:57 +00:00
|
|
|
|
|
|
|
// Find out the sha256
|
|
|
|
hash := sha256.New()
|
|
|
|
|
|
|
|
_, err = io.Copy(hash, reader)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sum := hash.Sum(nil)
|
|
|
|
|
|
|
|
// Set core's plugin directory and plugin catalog directory
|
|
|
|
c.pluginDirectory = fullPath
|
|
|
|
c.pluginCatalog.directory = fullPath
|
|
|
|
|
2018-01-18 00:19:28 +00:00
|
|
|
command := fmt.Sprintf("%s", filepath.Base(os.Args[0]))
|
|
|
|
args := []string{fmt.Sprintf("--test.run=%s", testFunc)}
|
2018-01-19 06:44:44 +00:00
|
|
|
err = c.pluginCatalog.Set(context.Background(), name, command, args, sum)
|
2017-12-15 18:31:57 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 15:56:14 +00:00
|
|
|
var testLogicalBackends = map[string]logical.Factory{}
|
2017-10-11 17:21:20 +00:00
|
|
|
var testCredentialBackends = map[string]logical.Factory{}
|
2015-07-10 15:56:14 +00:00
|
|
|
|
2017-12-15 18:38:01 +00:00
|
|
|
// StartSSHHostTestServer starts the test server which responds to SSH
|
|
|
|
// authentication. Used to test the SSH secret backend.
|
2015-08-18 23:48:50 +00:00
|
|
|
func StartSSHHostTestServer() (string, error) {
|
2015-07-10 22:18:02 +00:00
|
|
|
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testSharedPublicKey))
|
|
|
|
if err != nil {
|
2018-04-09 18:35:21 +00:00
|
|
|
return "", fmt.Errorf("error parsing public key")
|
2015-07-10 22:18:02 +00:00
|
|
|
}
|
|
|
|
serverConfig := &ssh.ServerConfig{
|
|
|
|
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
|
|
|
if bytes.Compare(pubKey.Marshal(), key.Marshal()) == 0 {
|
|
|
|
return &ssh.Permissions{}, nil
|
|
|
|
} else {
|
2018-04-09 18:35:21 +00:00
|
|
|
return nil, fmt.Errorf("key does not match")
|
2015-07-10 22:18:02 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
signer, err := ssh.ParsePrivateKey([]byte(testSharedPrivateKey))
|
|
|
|
if err != nil {
|
|
|
|
panic("Error parsing private key")
|
|
|
|
}
|
|
|
|
serverConfig.AddHostKey(signer)
|
|
|
|
|
|
|
|
soc, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
2018-04-09 18:35:21 +00:00
|
|
|
return "", fmt.Errorf("error listening to connection")
|
2015-07-10 22:18:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
conn, err := soc.Accept()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error accepting incoming connection: %s", err))
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
sshConn, chanReqs, _, err := ssh.NewServerConn(conn, serverConfig)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Handshaking error: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for chanReq := range chanReqs {
|
|
|
|
go func(chanReq ssh.NewChannel) {
|
|
|
|
if chanReq.ChannelType() != "session" {
|
|
|
|
chanReq.Reject(ssh.UnknownChannelType, "unknown channel type")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ch, requests, err := chanReq.Accept()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error accepting channel: %s", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
go func(ch ssh.Channel, in <-chan *ssh.Request) {
|
|
|
|
for req := range in {
|
|
|
|
executeServerCommand(ch, req)
|
|
|
|
}
|
|
|
|
}(ch, requests)
|
|
|
|
}(chanReq)
|
|
|
|
}
|
|
|
|
sshConn.Close()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return soc.Addr().String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This executes the commands requested to be run on the server.
|
|
|
|
// Used to test the SSH secret backend.
|
|
|
|
func executeServerCommand(ch ssh.Channel, req *ssh.Request) {
|
|
|
|
command := string(req.Payload[4:])
|
2016-05-15 19:22:21 +00:00
|
|
|
cmd := exec.Command("/bin/bash", []string{"-c", command}...)
|
2015-07-10 22:18:02 +00:00
|
|
|
req.Reply(true, nil)
|
|
|
|
|
|
|
|
cmd.Stdout = ch
|
|
|
|
cmd.Stderr = ch
|
|
|
|
cmd.Stdin = ch
|
|
|
|
|
|
|
|
err := cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error starting the command: '%s'", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
_, err := cmd.Process.Wait()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error while waiting for command to finish:'%s'", err))
|
|
|
|
}
|
|
|
|
ch.Close()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2017-10-11 17:21:20 +00:00
|
|
|
// This adds a credential backend for the test core. This needs to be
|
|
|
|
// invoked before the test core is created.
|
|
|
|
func AddTestCredentialBackend(name string, factory logical.Factory) error {
|
|
|
|
if name == "" {
|
|
|
|
return fmt.Errorf("missing backend name")
|
|
|
|
}
|
|
|
|
if factory == nil {
|
|
|
|
return fmt.Errorf("missing backend factory function")
|
|
|
|
}
|
|
|
|
testCredentialBackends[name] = factory
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-10 15:56:14 +00:00
|
|
|
// This adds a logical backend for the test core. This needs to be
|
|
|
|
// invoked before the test core is created.
|
|
|
|
func AddTestLogicalBackend(name string, factory logical.Factory) error {
|
|
|
|
if name == "" {
|
2018-04-09 18:35:21 +00:00
|
|
|
return fmt.Errorf("missing backend name")
|
2015-07-10 15:56:14 +00:00
|
|
|
}
|
|
|
|
if factory == nil {
|
2018-04-09 18:35:21 +00:00
|
|
|
return fmt.Errorf("missing backend factory function")
|
2015-07-10 15:56:14 +00:00
|
|
|
}
|
|
|
|
testLogicalBackends[name] = factory
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-18 21:36:42 +00:00
|
|
|
type noopAudit struct {
|
2017-05-24 00:36:20 +00:00
|
|
|
Config *audit.BackendConfig
|
|
|
|
salt *salt.Salt
|
|
|
|
saltMutex sync.RWMutex
|
2015-09-18 21:36:42 +00:00
|
|
|
}
|
2015-04-02 01:36:13 +00:00
|
|
|
|
2018-03-08 19:21:11 +00:00
|
|
|
func (n *noopAudit) GetHash(ctx context.Context, data string) (string, error) {
|
|
|
|
salt, err := n.Salt(ctx)
|
2017-05-24 00:36:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return salt.GetIdentifiedHMAC(data), nil
|
2015-11-19 01:26:03 +00:00
|
|
|
}
|
|
|
|
|
2018-03-02 17:18:39 +00:00
|
|
|
func (n *noopAudit) LogRequest(_ context.Context, _ *audit.LogInput) error {
|
2015-04-02 01:36:13 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-02 17:18:39 +00:00
|
|
|
func (n *noopAudit) LogResponse(_ context.Context, _ *audit.LogInput) error {
|
2015-04-02 01:36:13 +00:00
|
|
|
return nil
|
|
|
|
}
|
2015-05-27 21:19:12 +00:00
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (n *noopAudit) Reload(_ context.Context) error {
|
2016-09-30 19:04:50 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (n *noopAudit) Invalidate(_ context.Context) {
|
2017-05-24 00:36:20 +00:00
|
|
|
n.saltMutex.Lock()
|
|
|
|
defer n.saltMutex.Unlock()
|
|
|
|
n.salt = nil
|
|
|
|
}
|
|
|
|
|
2018-03-08 19:21:11 +00:00
|
|
|
func (n *noopAudit) Salt(ctx context.Context) (*salt.Salt, error) {
|
2017-05-24 00:36:20 +00:00
|
|
|
n.saltMutex.RLock()
|
|
|
|
if n.salt != nil {
|
|
|
|
defer n.saltMutex.RUnlock()
|
|
|
|
return n.salt, nil
|
|
|
|
}
|
|
|
|
n.saltMutex.RUnlock()
|
|
|
|
n.saltMutex.Lock()
|
|
|
|
defer n.saltMutex.Unlock()
|
|
|
|
if n.salt != nil {
|
|
|
|
return n.salt, nil
|
|
|
|
}
|
2018-03-08 19:21:11 +00:00
|
|
|
salt, err := salt.NewSalt(ctx, n.Config.SaltView, n.Config.SaltConfig)
|
2017-05-24 00:36:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
n.salt = salt
|
|
|
|
return salt, nil
|
|
|
|
}
|
|
|
|
|
2015-05-27 21:19:12 +00:00
|
|
|
type rawHTTP struct{}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (n *rawHTTP) HandleRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) {
|
2015-05-27 21:19:12 +00:00
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
logical.HTTPStatusCode: 200,
|
|
|
|
logical.HTTPContentType: "plain/text",
|
|
|
|
logical.HTTPRawBody: []byte("hello world"),
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (n *rawHTTP) HandleExistenceCheck(ctx context.Context, req *logical.Request) (bool, bool, error) {
|
2016-01-12 20:09:16 +00:00
|
|
|
return false, false, nil
|
2016-01-07 20:10:05 +00:00
|
|
|
}
|
|
|
|
|
2015-05-27 21:19:12 +00:00
|
|
|
func (n *rawHTTP) SpecialPaths() *logical.Paths {
|
|
|
|
return &logical.Paths{Unauthenticated: []string{"*"}}
|
|
|
|
}
|
2015-09-02 19:56:58 +00:00
|
|
|
|
|
|
|
func (n *rawHTTP) System() logical.SystemView {
|
|
|
|
return logical.StaticSystemView{
|
|
|
|
DefaultLeaseTTLVal: time.Hour * 24,
|
2016-09-28 22:32:49 +00:00
|
|
|
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
2015-09-02 19:56:58 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-10 14:11:37 +00:00
|
|
|
|
Backend plugin system (#2874)
* Add backend plugin changes
* Fix totp backend plugin tests
* Fix logical/plugin InvalidateKey test
* Fix plugin catalog CRUD test, fix NoopBackend
* Clean up commented code block
* Fix system backend mount test
* Set plugin_name to omitempty, fix handleMountTable config parsing
* Clean up comments, keep shim connections alive until cleanup
* Include pluginClient, disallow LookupPlugin call from within a plugin
* Add wrapper around backendPluginClient for proper cleanup
* Add logger shim tests
* Add logger, storage, and system shim tests
* Use pointer receivers for system view shim
* Use plugin name if no path is provided on mount
* Enable plugins for auth backends
* Add backend type attribute, move builtin/plugin/package
* Fix merge conflict
* Fix missing plugin name in mount config
* Add integration tests on enabling auth backend plugins
* Remove dependency cycle on mock-plugin
* Add passthrough backend plugin, use logical.BackendType to determine lease generation
* Remove vault package dependency on passthrough package
* Add basic impl test for passthrough plugin
* Incorporate feedback; set b.backend after shims creation on backendPluginServer
* Fix totp plugin test
* Add plugin backends docs
* Fix tests
* Fix builtin/plugin tests
* Remove flatten from PluginRunner fields
* Move mock plugin to logical/plugin, remove totp and passthrough plugins
* Move pluginMap into newPluginClient
* Do not create storage RPC connection on HandleRequest and HandleExistenceCheck
* Change shim logger's Fatal to no-op
* Change BackendType to uint32, match UX backend types
* Change framework.Backend Setup signature
* Add Setup func to logical.Backend interface
* Move OptionallyEnableMlock call into plugin.Serve, update docs and comments
* Remove commented var in plugin package
* RegisterLicense on logical.Backend interface (#3017)
* Add RegisterLicense to logical.Backend interface
* Update RegisterLicense to use callback func on framework.Backend
* Refactor framework.Backend.RegisterLicense
* plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs
* plugin: Revert BackendType to remove TypePassthrough and related references
* Fix typo in plugin backends docs
2017-07-20 17:28:40 +00:00
|
|
|
func (n *rawHTTP) Logger() log.Logger {
|
2018-04-03 00:46:59 +00:00
|
|
|
return logging.NewVaultLogger(log.Trace)
|
Backend plugin system (#2874)
* Add backend plugin changes
* Fix totp backend plugin tests
* Fix logical/plugin InvalidateKey test
* Fix plugin catalog CRUD test, fix NoopBackend
* Clean up commented code block
* Fix system backend mount test
* Set plugin_name to omitempty, fix handleMountTable config parsing
* Clean up comments, keep shim connections alive until cleanup
* Include pluginClient, disallow LookupPlugin call from within a plugin
* Add wrapper around backendPluginClient for proper cleanup
* Add logger shim tests
* Add logger, storage, and system shim tests
* Use pointer receivers for system view shim
* Use plugin name if no path is provided on mount
* Enable plugins for auth backends
* Add backend type attribute, move builtin/plugin/package
* Fix merge conflict
* Fix missing plugin name in mount config
* Add integration tests on enabling auth backend plugins
* Remove dependency cycle on mock-plugin
* Add passthrough backend plugin, use logical.BackendType to determine lease generation
* Remove vault package dependency on passthrough package
* Add basic impl test for passthrough plugin
* Incorporate feedback; set b.backend after shims creation on backendPluginServer
* Fix totp plugin test
* Add plugin backends docs
* Fix tests
* Fix builtin/plugin tests
* Remove flatten from PluginRunner fields
* Move mock plugin to logical/plugin, remove totp and passthrough plugins
* Move pluginMap into newPluginClient
* Do not create storage RPC connection on HandleRequest and HandleExistenceCheck
* Change shim logger's Fatal to no-op
* Change BackendType to uint32, match UX backend types
* Change framework.Backend Setup signature
* Add Setup func to logical.Backend interface
* Move OptionallyEnableMlock call into plugin.Serve, update docs and comments
* Remove commented var in plugin package
* RegisterLicense on logical.Backend interface (#3017)
* Add RegisterLicense to logical.Backend interface
* Update RegisterLicense to use callback func on framework.Backend
* Refactor framework.Backend.RegisterLicense
* plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs
* plugin: Revert BackendType to remove TypePassthrough and related references
* Fix typo in plugin backends docs
2017-07-20 17:28:40 +00:00
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (n *rawHTTP) Cleanup(ctx context.Context) {
|
2015-09-10 14:11:37 +00:00
|
|
|
// noop
|
|
|
|
}
|
2016-01-15 15:55:35 +00:00
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (n *rawHTTP) Initialize(ctx context.Context) error {
|
2017-01-13 19:51:10 +00:00
|
|
|
// noop
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (n *rawHTTP) InvalidateKey(context.Context, string) {
|
2017-01-07 23:18:22 +00:00
|
|
|
// noop
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (n *rawHTTP) Setup(ctx context.Context, config *logical.BackendConfig) error {
|
Backend plugin system (#2874)
* Add backend plugin changes
* Fix totp backend plugin tests
* Fix logical/plugin InvalidateKey test
* Fix plugin catalog CRUD test, fix NoopBackend
* Clean up commented code block
* Fix system backend mount test
* Set plugin_name to omitempty, fix handleMountTable config parsing
* Clean up comments, keep shim connections alive until cleanup
* Include pluginClient, disallow LookupPlugin call from within a plugin
* Add wrapper around backendPluginClient for proper cleanup
* Add logger shim tests
* Add logger, storage, and system shim tests
* Use pointer receivers for system view shim
* Use plugin name if no path is provided on mount
* Enable plugins for auth backends
* Add backend type attribute, move builtin/plugin/package
* Fix merge conflict
* Fix missing plugin name in mount config
* Add integration tests on enabling auth backend plugins
* Remove dependency cycle on mock-plugin
* Add passthrough backend plugin, use logical.BackendType to determine lease generation
* Remove vault package dependency on passthrough package
* Add basic impl test for passthrough plugin
* Incorporate feedback; set b.backend after shims creation on backendPluginServer
* Fix totp plugin test
* Add plugin backends docs
* Fix tests
* Fix builtin/plugin tests
* Remove flatten from PluginRunner fields
* Move mock plugin to logical/plugin, remove totp and passthrough plugins
* Move pluginMap into newPluginClient
* Do not create storage RPC connection on HandleRequest and HandleExistenceCheck
* Change shim logger's Fatal to no-op
* Change BackendType to uint32, match UX backend types
* Change framework.Backend Setup signature
* Add Setup func to logical.Backend interface
* Move OptionallyEnableMlock call into plugin.Serve, update docs and comments
* Remove commented var in plugin package
* RegisterLicense on logical.Backend interface (#3017)
* Add RegisterLicense to logical.Backend interface
* Update RegisterLicense to use callback func on framework.Backend
* Refactor framework.Backend.RegisterLicense
* plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs
* plugin: Revert BackendType to remove TypePassthrough and related references
* Fix typo in plugin backends docs
2017-07-20 17:28:40 +00:00
|
|
|
// noop
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *rawHTTP) Type() logical.BackendType {
|
|
|
|
return logical.TypeUnknown
|
|
|
|
}
|
|
|
|
|
2016-01-15 15:55:35 +00:00
|
|
|
func GenerateRandBytes(length int) ([]byte, error) {
|
|
|
|
if length < 0 {
|
|
|
|
return nil, fmt.Errorf("length must be >= 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := make([]byte, length)
|
|
|
|
if length == 0 {
|
|
|
|
return buf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
n, err := rand.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if n != length {
|
|
|
|
return nil, fmt.Errorf("unable to read %d bytes; only read %d", length, n)
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf, nil
|
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-08-01 18:07:08 +00:00
|
|
|
func TestWaitActive(t testing.T, core *Core) {
|
2017-09-01 05:02:03 +00:00
|
|
|
t.Helper()
|
2016-08-15 13:42:42 +00:00
|
|
|
start := time.Now()
|
|
|
|
var standby bool
|
|
|
|
var err error
|
|
|
|
for time.Now().Sub(start) < time.Second {
|
|
|
|
standby, err = core.Standby()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if !standby {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if standby {
|
|
|
|
t.Fatalf("should not be in standby mode")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-03 18:54:01 +00:00
|
|
|
type TestCluster struct {
|
2017-07-31 19:31:44 +00:00
|
|
|
BarrierKeys [][]byte
|
2017-10-23 18:59:37 +00:00
|
|
|
RecoveryKeys [][]byte
|
2017-07-31 19:31:44 +00:00
|
|
|
CACert *x509.Certificate
|
|
|
|
CACertBytes []byte
|
|
|
|
CACertPEM []byte
|
|
|
|
CACertPEMFile string
|
|
|
|
CAKey *ecdsa.PrivateKey
|
|
|
|
CAKeyPEM []byte
|
|
|
|
Cores []*TestClusterCore
|
|
|
|
ID string
|
|
|
|
RootToken string
|
|
|
|
RootCAs *x509.CertPool
|
|
|
|
TempDir string
|
2017-07-03 18:54:01 +00:00
|
|
|
}
|
|
|
|
|
2017-08-15 21:06:38 +00:00
|
|
|
func (c *TestCluster) Start() {
|
|
|
|
for _, core := range c.Cores {
|
2017-07-03 18:54:01 +00:00
|
|
|
if core.Server != nil {
|
|
|
|
for _, ln := range core.Listeners {
|
|
|
|
go core.Server.Serve(ln)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-27 20:34:06 +00:00
|
|
|
// UnsealCores uses the cluster barrier keys to unseal the test cluster cores
|
|
|
|
func (c *TestCluster) UnsealCores(t testing.T) {
|
|
|
|
numCores := len(c.Cores)
|
|
|
|
|
|
|
|
// Unseal first core
|
|
|
|
for _, key := range c.BarrierKeys {
|
|
|
|
if _, err := c.Cores[0].Unseal(TestKeyCopy(key)); err != nil {
|
|
|
|
t.Fatalf("unseal err: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify unsealed
|
|
|
|
sealed, err := c.Cores[0].Sealed()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err checking seal status: %s", err)
|
|
|
|
}
|
|
|
|
if sealed {
|
|
|
|
t.Fatal("should not be sealed")
|
|
|
|
}
|
|
|
|
|
|
|
|
TestWaitActive(t, c.Cores[0].Core)
|
|
|
|
|
|
|
|
// Unseal other cores
|
|
|
|
for i := 1; i < numCores; i++ {
|
|
|
|
for _, key := range c.BarrierKeys {
|
|
|
|
if _, err := c.Cores[i].Core.Unseal(TestKeyCopy(key)); err != nil {
|
|
|
|
t.Fatalf("unseal err: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Let them come fully up to standby
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
|
|
|
|
// Ensure cluster connection info is populated.
|
|
|
|
// Other cores should not come up as leaders.
|
|
|
|
for i := 1; i < numCores; i++ {
|
|
|
|
isLeader, _, _, err := c.Cores[i].Leader()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if isLeader {
|
|
|
|
t.Fatalf("core[%d] should not be leader", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
func (c *TestCluster) EnsureCoresSealed(t testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if err := c.ensureCoresSealed(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-15 21:06:38 +00:00
|
|
|
func (c *TestCluster) Cleanup() {
|
|
|
|
// Close listeners
|
|
|
|
for _, core := range c.Cores {
|
2017-07-03 18:54:01 +00:00
|
|
|
if core.Listeners != nil {
|
|
|
|
for _, ln := range core.Listeners {
|
|
|
|
ln.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-31 15:28:06 +00:00
|
|
|
|
2017-08-15 21:06:38 +00:00
|
|
|
// Seal the cores
|
2017-09-01 05:02:03 +00:00
|
|
|
c.ensureCoresSealed()
|
|
|
|
|
|
|
|
// Remove any temp dir that exists
|
|
|
|
if c.TempDir != "" {
|
|
|
|
os.RemoveAll(c.TempDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Give time to actually shut down/clean up before the next test
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *TestCluster) ensureCoresSealed() error {
|
2017-08-15 21:06:38 +00:00
|
|
|
for _, core := range c.Cores {
|
|
|
|
if err := core.Shutdown(); err != nil {
|
2017-09-01 05:02:03 +00:00
|
|
|
return err
|
2017-08-15 21:06:38 +00:00
|
|
|
}
|
|
|
|
timeout := time.Now().Add(60 * time.Second)
|
|
|
|
for {
|
|
|
|
if time.Now().After(timeout) {
|
2017-09-01 05:02:03 +00:00
|
|
|
return fmt.Errorf("timeout waiting for core to seal")
|
2017-08-15 21:06:38 +00:00
|
|
|
}
|
|
|
|
sealed, err := core.Sealed()
|
|
|
|
if err != nil {
|
2017-09-01 05:02:03 +00:00
|
|
|
return err
|
2017-08-15 21:06:38 +00:00
|
|
|
}
|
|
|
|
if sealed {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
2017-09-01 05:02:03 +00:00
|
|
|
return nil
|
2017-07-03 18:54:01 +00:00
|
|
|
}
|
|
|
|
|
2017-12-15 18:38:01 +00:00
|
|
|
// UnsealWithStoredKeys uses stored keys to unseal the test cluster cores
|
2017-11-30 14:43:07 +00:00
|
|
|
func (c *TestCluster) UnsealWithStoredKeys(t testing.T) error {
|
|
|
|
for _, core := range c.Cores {
|
2018-01-19 06:44:44 +00:00
|
|
|
if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
|
2017-11-30 14:43:07 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
timeout := time.Now().Add(60 * time.Second)
|
|
|
|
for {
|
|
|
|
if time.Now().After(timeout) {
|
|
|
|
return fmt.Errorf("timeout waiting for core to unseal")
|
|
|
|
}
|
|
|
|
sealed, err := core.Sealed()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !sealed {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-15 13:42:42 +00:00
|
|
|
type TestListener struct {
|
|
|
|
net.Listener
|
|
|
|
Address *net.TCPAddr
|
|
|
|
}
|
|
|
|
|
|
|
|
type TestClusterCore struct {
|
|
|
|
*Core
|
2017-10-23 18:59:37 +00:00
|
|
|
Client *api.Client
|
|
|
|
Handler http.Handler
|
|
|
|
Listeners []*TestListener
|
|
|
|
ReloadFuncs *map[string][]reload.ReloadFunc
|
|
|
|
ReloadFuncsLock *sync.RWMutex
|
|
|
|
Server *http.Server
|
|
|
|
ServerCert *x509.Certificate
|
|
|
|
ServerCertBytes []byte
|
|
|
|
ServerCertPEM []byte
|
|
|
|
ServerKey *ecdsa.PrivateKey
|
|
|
|
ServerKeyPEM []byte
|
|
|
|
TLSConfig *tls.Config
|
|
|
|
UnderlyingStorage physical.Backend
|
2016-08-15 13:42:42 +00:00
|
|
|
}
|
|
|
|
|
2017-07-31 15:28:06 +00:00
|
|
|
type TestClusterOptions struct {
|
|
|
|
KeepStandbysSealed bool
|
2017-09-04 20:29:43 +00:00
|
|
|
SkipInit bool
|
2017-07-31 15:28:06 +00:00
|
|
|
HandlerFunc func(*Core) http.Handler
|
|
|
|
BaseListenAddress string
|
2017-09-01 05:02:03 +00:00
|
|
|
NumCores int
|
2017-10-23 18:59:37 +00:00
|
|
|
SealFunc func() Seal
|
2018-04-03 00:46:59 +00:00
|
|
|
Logger log.Logger
|
2017-12-11 23:02:35 +00:00
|
|
|
TempDir string
|
2018-02-22 05:23:37 +00:00
|
|
|
CACert []byte
|
|
|
|
CAKey *ecdsa.PrivateKey
|
2017-07-31 15:28:06 +00:00
|
|
|
}
|
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
var DefaultNumCores = 3
|
|
|
|
|
|
|
|
type certInfo struct {
|
|
|
|
cert *x509.Certificate
|
|
|
|
certPEM []byte
|
|
|
|
certBytes []byte
|
|
|
|
key *ecdsa.PrivateKey
|
|
|
|
keyPEM []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTestCluster creates a new test cluster based on the provided core config
|
|
|
|
// and test cluster options.
|
2017-10-23 18:59:37 +00:00
|
|
|
//
|
|
|
|
// N.B. Even though a single base CoreConfig is provided, NewTestCluster will instantiate a
|
|
|
|
// core config for each core it creates. If separate seal per core is desired, opts.SealFunc
|
|
|
|
// can be provided to generate a seal for each one. Otherwise, the provided base.Seal will be
|
|
|
|
// shared among cores. NewCore's default behavior is to generate a new DefaultSeal if the
|
|
|
|
// provided Seal in coreConfig (i.e. base.Seal) is nil.
|
2017-08-01 18:07:08 +00:00
|
|
|
func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *TestCluster {
|
2018-02-22 05:23:37 +00:00
|
|
|
var err error
|
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
var numCores int
|
|
|
|
if opts == nil || opts.NumCores == 0 {
|
|
|
|
numCores = DefaultNumCores
|
|
|
|
} else {
|
|
|
|
numCores = opts.NumCores
|
|
|
|
}
|
|
|
|
|
2017-07-31 15:28:06 +00:00
|
|
|
certIPs := []net.IP{
|
|
|
|
net.IPv6loopback,
|
|
|
|
net.ParseIP("127.0.0.1"),
|
|
|
|
}
|
|
|
|
var baseAddr *net.TCPAddr
|
2017-07-31 16:13:29 +00:00
|
|
|
if opts != nil && opts.BaseListenAddress != "" {
|
2017-07-31 15:28:06 +00:00
|
|
|
baseAddr, err = net.ResolveTCPAddr("tcp", opts.BaseListenAddress)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("could not parse given base IP")
|
|
|
|
}
|
|
|
|
certIPs = append(certIPs, baseAddr.IP)
|
|
|
|
}
|
|
|
|
|
|
|
|
var testCluster TestCluster
|
2017-12-11 23:02:35 +00:00
|
|
|
if opts != nil && opts.TempDir != "" {
|
|
|
|
if _, err := os.Stat(opts.TempDir); os.IsNotExist(err) {
|
|
|
|
if err := os.MkdirAll(opts.TempDir, 0700); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
testCluster.TempDir = opts.TempDir
|
|
|
|
} else {
|
|
|
|
tempDir, err := ioutil.TempDir("", "vault-test-cluster-")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
testCluster.TempDir = tempDir
|
2017-07-31 15:28:06 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 05:23:37 +00:00
|
|
|
var caKey *ecdsa.PrivateKey
|
|
|
|
if opts != nil && opts.CAKey != nil {
|
|
|
|
caKey = opts.CAKey
|
|
|
|
} else {
|
|
|
|
caKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-07-31 15:28:06 +00:00
|
|
|
}
|
|
|
|
testCluster.CAKey = caKey
|
2018-02-22 05:23:37 +00:00
|
|
|
var caBytes []byte
|
|
|
|
if opts != nil && len(opts.CACert) > 0 {
|
|
|
|
caBytes = opts.CACert
|
|
|
|
} else {
|
|
|
|
caCertTemplate := &x509.Certificate{
|
|
|
|
Subject: pkix.Name{
|
|
|
|
CommonName: "localhost",
|
|
|
|
},
|
|
|
|
DNSNames: []string{"localhost"},
|
|
|
|
IPAddresses: certIPs,
|
|
|
|
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
|
|
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
|
|
BasicConstraintsValid: true,
|
|
|
|
IsCA: true,
|
|
|
|
}
|
|
|
|
caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, caKey.Public(), caKey)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
}
|
|
|
|
caCert, err := x509.ParseCertificate(caBytes)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-07-31 15:28:06 +00:00
|
|
|
testCluster.CACert = caCert
|
|
|
|
testCluster.CACertBytes = caBytes
|
|
|
|
testCluster.RootCAs = x509.NewCertPool()
|
|
|
|
testCluster.RootCAs.AddCert(caCert)
|
|
|
|
caCertPEMBlock := &pem.Block{
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
Bytes: caBytes,
|
|
|
|
}
|
|
|
|
testCluster.CACertPEM = pem.EncodeToMemory(caCertPEMBlock)
|
2017-07-31 19:31:44 +00:00
|
|
|
testCluster.CACertPEMFile = filepath.Join(testCluster.TempDir, "ca_cert.pem")
|
|
|
|
err = ioutil.WriteFile(testCluster.CACertPEMFile, testCluster.CACertPEM, 0755)
|
2017-07-31 15:28:06 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
marshaledCAKey, err := x509.MarshalECPrivateKey(caKey)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
caKeyPEMBlock := &pem.Block{
|
|
|
|
Type: "EC PRIVATE KEY",
|
|
|
|
Bytes: marshaledCAKey,
|
|
|
|
}
|
|
|
|
testCluster.CAKeyPEM = pem.EncodeToMemory(caKeyPEMBlock)
|
|
|
|
err = ioutil.WriteFile(filepath.Join(testCluster.TempDir, "ca_key.pem"), testCluster.CAKeyPEM, 0755)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
var certInfoSlice []*certInfo
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
//
|
|
|
|
// Certs generation
|
|
|
|
//
|
|
|
|
for i := 0; i < numCores; i++ {
|
|
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
certTemplate := &x509.Certificate{
|
|
|
|
Subject: pkix.Name{
|
|
|
|
CommonName: "localhost",
|
|
|
|
},
|
|
|
|
DNSNames: []string{"localhost"},
|
|
|
|
IPAddresses: certIPs,
|
|
|
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
|
|
|
x509.ExtKeyUsageServerAuth,
|
|
|
|
x509.ExtKeyUsageClientAuth,
|
|
|
|
},
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
|
|
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
|
|
}
|
|
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, caCert, key.Public(), caKey)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
certPEMBlock := &pem.Block{
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
Bytes: certBytes,
|
|
|
|
}
|
|
|
|
certPEM := pem.EncodeToMemory(certPEMBlock)
|
|
|
|
marshaledKey, err := x509.MarshalECPrivateKey(key)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
keyPEMBlock := &pem.Block{
|
|
|
|
Type: "EC PRIVATE KEY",
|
|
|
|
Bytes: marshaledKey,
|
|
|
|
}
|
|
|
|
keyPEM := pem.EncodeToMemory(keyPEMBlock)
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
certInfoSlice = append(certInfoSlice, &certInfo{
|
|
|
|
cert: cert,
|
|
|
|
certPEM: certPEM,
|
|
|
|
certBytes: certBytes,
|
|
|
|
key: key,
|
|
|
|
keyPEM: keyPEM,
|
|
|
|
})
|
2016-08-15 13:42:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Listener setup
|
|
|
|
//
|
2018-04-03 00:46:59 +00:00
|
|
|
logger := logging.NewVaultLogger(log.Trace)
|
2017-09-01 05:02:03 +00:00
|
|
|
ports := make([]int, numCores)
|
2017-07-31 15:28:06 +00:00
|
|
|
if baseAddr != nil {
|
2017-09-01 05:02:03 +00:00
|
|
|
for i := 0; i < numCores; i++ {
|
|
|
|
ports[i] = baseAddr.Port + i
|
|
|
|
}
|
2017-07-31 15:28:06 +00:00
|
|
|
} else {
|
|
|
|
baseAddr = &net.TCPAddr{
|
|
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
|
|
Port: 0,
|
|
|
|
}
|
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
listeners := [][]*TestListener{}
|
|
|
|
servers := []*http.Server{}
|
|
|
|
handlers := []http.Handler{}
|
|
|
|
tlsConfigs := []*tls.Config{}
|
|
|
|
certGetters := []*reload.CertificateGetter{}
|
|
|
|
for i := 0; i < numCores; i++ {
|
|
|
|
baseAddr.Port = ports[i]
|
|
|
|
ln, err := net.ListenTCP("tcp", baseAddr)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
certFile := filepath.Join(testCluster.TempDir, fmt.Sprintf("node%d_port_%d_cert.pem", i+1, ln.Addr().(*net.TCPAddr).Port))
|
|
|
|
keyFile := filepath.Join(testCluster.TempDir, fmt.Sprintf("node%d_port_%d_key.pem", i+1, ln.Addr().(*net.TCPAddr).Port))
|
|
|
|
err = ioutil.WriteFile(certFile, certInfoSlice[i].certPEM, 0755)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(keyFile, certInfoSlice[i].keyPEM, 0755)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
tlsCert, err := tls.X509KeyPair(certInfoSlice[i].certPEM, certInfoSlice[i].keyPEM)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-12-15 22:33:55 +00:00
|
|
|
certGetter := reload.NewCertificateGetter(certFile, keyFile, "")
|
2017-09-01 05:02:03 +00:00
|
|
|
certGetters = append(certGetters, certGetter)
|
|
|
|
tlsConfig := &tls.Config{
|
|
|
|
Certificates: []tls.Certificate{tlsCert},
|
|
|
|
RootCAs: testCluster.RootCAs,
|
|
|
|
ClientCAs: testCluster.RootCAs,
|
2017-11-09 20:55:23 +00:00
|
|
|
ClientAuth: tls.RequestClientCert,
|
2017-09-01 05:02:03 +00:00
|
|
|
NextProtos: []string{"h2", "http/1.1"},
|
|
|
|
GetCertificate: certGetter.GetCertificate,
|
|
|
|
}
|
|
|
|
tlsConfig.BuildNameToCertificate()
|
|
|
|
tlsConfigs = append(tlsConfigs, tlsConfig)
|
|
|
|
lns := []*TestListener{&TestListener{
|
|
|
|
Listener: tls.NewListener(ln, tlsConfig),
|
|
|
|
Address: ln.Addr().(*net.TCPAddr),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
listeners = append(listeners, lns)
|
|
|
|
var handler http.Handler = http.NewServeMux()
|
|
|
|
handlers = append(handlers, handler)
|
|
|
|
server := &http.Server{
|
|
|
|
Handler: handler,
|
|
|
|
}
|
|
|
|
servers = append(servers, server)
|
2017-02-27 17:49:35 +00:00
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-07-31 15:28:06 +00:00
|
|
|
// Create three cores with the same physical and different redirect/cluster
|
|
|
|
// addrs.
|
2016-11-08 15:31:35 +00:00
|
|
|
// N.B.: On OSX, instead of random ports, it assigns new ports to new
|
|
|
|
// listeners sequentially. Aside from being a bad idea in a security sense,
|
|
|
|
// it also broke tests that assumed it was OK to just use the port above
|
2017-07-31 15:28:06 +00:00
|
|
|
// the redirect addr. This has now been changed to 105 ports above, but if
|
2016-11-08 15:31:35 +00:00
|
|
|
// we ever do more than three nodes in a cluster it may need to be bumped.
|
2017-07-31 15:28:06 +00:00
|
|
|
// Note: it's 105 so that we don't conflict with a running Consul by
|
|
|
|
// default.
|
2016-08-15 13:42:42 +00:00
|
|
|
coreConfig := &CoreConfig{
|
|
|
|
LogicalBackends: make(map[string]logical.Factory),
|
|
|
|
CredentialBackends: make(map[string]logical.Factory),
|
|
|
|
AuditBackends: make(map[string]audit.Factory),
|
2017-09-01 05:02:03 +00:00
|
|
|
RedirectAddr: fmt.Sprintf("https://127.0.0.1:%d", listeners[0][0].Address.Port),
|
|
|
|
ClusterAddr: fmt.Sprintf("https://127.0.0.1:%d", listeners[0][0].Address.Port+105),
|
2016-08-15 13:42:42 +00:00
|
|
|
DisableMlock: true,
|
2017-07-31 15:28:06 +00:00
|
|
|
EnableUI: true,
|
2016-08-15 13:42:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if base != nil {
|
2017-07-31 15:28:06 +00:00
|
|
|
coreConfig.DisableCache = base.DisableCache
|
|
|
|
coreConfig.EnableUI = base.EnableUI
|
|
|
|
coreConfig.DefaultLeaseTTL = base.DefaultLeaseTTL
|
|
|
|
coreConfig.MaxLeaseTTL = base.MaxLeaseTTL
|
|
|
|
coreConfig.CacheSize = base.CacheSize
|
|
|
|
coreConfig.PluginDirectory = base.PluginDirectory
|
|
|
|
coreConfig.Seal = base.Seal
|
|
|
|
coreConfig.DevToken = base.DevToken
|
|
|
|
|
|
|
|
if !coreConfig.DisableMlock {
|
|
|
|
base.DisableMlock = false
|
|
|
|
}
|
|
|
|
|
2017-02-28 23:17:19 +00:00
|
|
|
if base.Physical != nil {
|
|
|
|
coreConfig.Physical = base.Physical
|
|
|
|
}
|
|
|
|
|
|
|
|
if base.HAPhysical != nil {
|
|
|
|
coreConfig.HAPhysical = base.HAPhysical
|
|
|
|
}
|
|
|
|
|
2016-08-15 13:42:42 +00:00
|
|
|
// Used to set something non-working to test fallback
|
|
|
|
switch base.ClusterAddr {
|
|
|
|
case "empty":
|
|
|
|
coreConfig.ClusterAddr = ""
|
|
|
|
case "":
|
|
|
|
default:
|
|
|
|
coreConfig.ClusterAddr = base.ClusterAddr
|
|
|
|
}
|
|
|
|
|
|
|
|
if base.LogicalBackends != nil {
|
|
|
|
for k, v := range base.LogicalBackends {
|
|
|
|
coreConfig.LogicalBackends[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if base.CredentialBackends != nil {
|
|
|
|
for k, v := range base.CredentialBackends {
|
|
|
|
coreConfig.CredentialBackends[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if base.AuditBackends != nil {
|
|
|
|
for k, v := range base.AuditBackends {
|
|
|
|
coreConfig.AuditBackends[k] = v
|
|
|
|
}
|
|
|
|
}
|
2016-12-02 11:31:06 +00:00
|
|
|
if base.Logger != nil {
|
|
|
|
coreConfig.Logger = base.Logger
|
|
|
|
}
|
2017-07-31 19:31:44 +00:00
|
|
|
|
2017-08-30 20:28:23 +00:00
|
|
|
coreConfig.ClusterCipherSuites = base.ClusterCipherSuites
|
|
|
|
|
2017-07-31 19:31:44 +00:00
|
|
|
coreConfig.DisableCache = base.DisableCache
|
|
|
|
|
|
|
|
coreConfig.DevToken = base.DevToken
|
2016-12-01 20:25:05 +00:00
|
|
|
}
|
|
|
|
|
2017-02-28 23:17:19 +00:00
|
|
|
if coreConfig.Physical == nil {
|
2017-08-03 17:24:27 +00:00
|
|
|
coreConfig.Physical, err = physInmem.NewInmem(nil, logger)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-28 23:17:19 +00:00
|
|
|
}
|
|
|
|
if coreConfig.HAPhysical == nil {
|
2017-08-03 17:24:27 +00:00
|
|
|
haPhys, err := physInmem.NewInmemHA(nil, logger)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
coreConfig.HAPhysical = haPhys.(physical.HABackend)
|
2017-02-28 23:17:19 +00:00
|
|
|
}
|
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
cores := []*Core{}
|
|
|
|
for i := 0; i < numCores; i++ {
|
|
|
|
coreConfig.RedirectAddr = fmt.Sprintf("https://127.0.0.1:%d", listeners[i][0].Address.Port)
|
|
|
|
if coreConfig.ClusterAddr != "" {
|
|
|
|
coreConfig.ClusterAddr = fmt.Sprintf("https://127.0.0.1:%d", listeners[i][0].Address.Port+105)
|
|
|
|
}
|
2017-10-23 18:59:37 +00:00
|
|
|
|
|
|
|
// if opts.SealFunc is provided, use that to generate a seal for the config instead
|
|
|
|
if opts != nil && opts.SealFunc != nil {
|
|
|
|
coreConfig.Seal = opts.SealFunc()
|
|
|
|
}
|
|
|
|
|
2018-04-03 00:46:59 +00:00
|
|
|
if opts != nil && opts.Logger != nil {
|
|
|
|
coreConfig.Logger = opts.Logger.Named(fmt.Sprintf("core%d", i))
|
2017-11-07 20:49:58 +00:00
|
|
|
}
|
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
c, err := NewCore(coreConfig)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
cores = append(cores, c)
|
|
|
|
if opts != nil && opts.HandlerFunc != nil {
|
|
|
|
handlers[i] = opts.HandlerFunc(c)
|
|
|
|
servers[i].Handler = handlers[i]
|
|
|
|
}
|
2017-07-31 15:28:06 +00:00
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Clustering setup
|
|
|
|
//
|
2016-08-19 15:03:53 +00:00
|
|
|
clusterAddrGen := func(lns []*TestListener) []*net.TCPAddr {
|
|
|
|
ret := make([]*net.TCPAddr, len(lns))
|
2016-08-15 13:42:42 +00:00
|
|
|
for i, ln := range lns {
|
2016-08-19 15:03:53 +00:00
|
|
|
ret[i] = &net.TCPAddr{
|
|
|
|
IP: ln.Address.IP,
|
2017-07-31 15:28:06 +00:00
|
|
|
Port: ln.Address.Port + 105,
|
2016-08-15 13:42:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
if numCores > 1 {
|
|
|
|
for i := 1; i < numCores; i++ {
|
|
|
|
cores[i].SetClusterListenerAddrs(clusterAddrGen(listeners[i]))
|
|
|
|
cores[i].SetClusterHandler(handlers[i])
|
|
|
|
}
|
|
|
|
}
|
2017-07-31 15:28:06 +00:00
|
|
|
|
2017-09-04 22:39:57 +00:00
|
|
|
if opts == nil || !opts.SkipInit {
|
2017-10-23 18:59:37 +00:00
|
|
|
bKeys, rKeys, root := TestCoreInitClusterWrapperSetup(t, cores[0], clusterAddrGen(listeners[0]), handlers[0])
|
|
|
|
barrierKeys, _ := copystructure.Copy(bKeys)
|
2017-09-04 20:28:21 +00:00
|
|
|
testCluster.BarrierKeys = barrierKeys.([][]byte)
|
2017-10-23 18:59:37 +00:00
|
|
|
recoveryKeys, _ := copystructure.Copy(rKeys)
|
|
|
|
testCluster.RecoveryKeys = recoveryKeys.([][]byte)
|
2017-09-04 20:28:21 +00:00
|
|
|
testCluster.RootToken = root
|
2017-07-31 15:28:06 +00:00
|
|
|
|
2017-09-04 20:28:21 +00:00
|
|
|
// Write root token and barrier keys
|
|
|
|
err = ioutil.WriteFile(filepath.Join(testCluster.TempDir, "root_token"), []byte(root), 0755)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
for i, key := range testCluster.BarrierKeys {
|
|
|
|
buf.Write([]byte(base64.StdEncoding.EncodeToString(key)))
|
|
|
|
if i < len(testCluster.BarrierKeys)-1 {
|
|
|
|
buf.WriteRune('\n')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(filepath.Join(testCluster.TempDir, "barrier_keys"), buf.Bytes(), 0755)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2017-07-31 15:28:06 +00:00
|
|
|
}
|
2017-10-23 18:59:37 +00:00
|
|
|
for i, key := range testCluster.RecoveryKeys {
|
|
|
|
buf.Write([]byte(base64.StdEncoding.EncodeToString(key)))
|
|
|
|
if i < len(testCluster.RecoveryKeys)-1 {
|
|
|
|
buf.WriteRune('\n')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(filepath.Join(testCluster.TempDir, "recovery_keys"), buf.Bytes(), 0755)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-07-31 15:28:06 +00:00
|
|
|
|
2017-09-04 20:28:21 +00:00
|
|
|
// Unseal first core
|
2017-10-23 18:59:37 +00:00
|
|
|
for _, key := range bKeys {
|
2017-09-04 20:28:21 +00:00
|
|
|
if _, err := cores[0].Unseal(TestKeyCopy(key)); err != nil {
|
|
|
|
t.Fatalf("unseal err: %s", err)
|
|
|
|
}
|
2017-01-17 20:43:10 +00:00
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
|
2017-10-23 18:59:37 +00:00
|
|
|
// If stored keys is supported, the above will no no-op, so trigger auto-unseal
|
|
|
|
// using stored keys to try to unseal
|
2018-01-19 06:44:44 +00:00
|
|
|
if err := cores[0].UnsealWithStoredKeys(ctx); err != nil {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-09-04 20:28:21 +00:00
|
|
|
// Verify unsealed
|
|
|
|
sealed, err := cores[0].Sealed()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err checking seal status: %s", err)
|
|
|
|
}
|
|
|
|
if sealed {
|
|
|
|
t.Fatal("should not be sealed")
|
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-09-04 20:28:21 +00:00
|
|
|
TestWaitActive(t, cores[0])
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-09-04 20:28:21 +00:00
|
|
|
// Unseal other cores unless otherwise specified
|
|
|
|
if (opts == nil || !opts.KeepStandbysSealed) && numCores > 1 {
|
|
|
|
for i := 1; i < numCores; i++ {
|
2017-10-23 18:59:37 +00:00
|
|
|
for _, key := range bKeys {
|
2017-09-04 20:28:21 +00:00
|
|
|
if _, err := cores[i].Unseal(TestKeyCopy(key)); err != nil {
|
|
|
|
t.Fatalf("unseal err: %s", err)
|
|
|
|
}
|
2017-09-01 05:02:03 +00:00
|
|
|
}
|
2017-10-23 18:59:37 +00:00
|
|
|
|
|
|
|
// If stored keys is supported, the above will no no-op, so trigger auto-unseal
|
|
|
|
// using stored keys
|
2018-01-19 06:44:44 +00:00
|
|
|
if err := cores[i].UnsealWithStoredKeys(ctx); err != nil {
|
2017-10-23 18:59:37 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-01-17 20:43:10 +00:00
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-09-04 20:28:21 +00:00
|
|
|
// Let them come fully up to standby
|
|
|
|
time.Sleep(2 * time.Second)
|
2016-08-15 13:42:42 +00:00
|
|
|
|
2017-09-04 20:28:21 +00:00
|
|
|
// Ensure cluster connection info is populated.
|
|
|
|
// Other cores should not come up as leaders.
|
|
|
|
for i := 1; i < numCores; i++ {
|
|
|
|
isLeader, _, _, err := cores[i].Leader()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if isLeader {
|
|
|
|
t.Fatalf("core[%d] should not be leader", i)
|
|
|
|
}
|
2017-09-01 05:02:03 +00:00
|
|
|
}
|
2016-08-15 13:42:42 +00:00
|
|
|
}
|
|
|
|
|
2017-09-04 20:28:21 +00:00
|
|
|
//
|
|
|
|
// Set test cluster core(s) and test cluster
|
|
|
|
//
|
2018-01-19 06:44:44 +00:00
|
|
|
cluster, err := cores[0].Cluster(context.Background())
|
2017-09-04 20:28:21 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
testCluster.ID = cluster.ID
|
2017-02-28 23:17:19 +00:00
|
|
|
}
|
|
|
|
|
2017-07-31 15:28:06 +00:00
|
|
|
getAPIClient := func(port int, tlsConfig *tls.Config) *api.Client {
|
2017-02-24 15:45:29 +00:00
|
|
|
transport := cleanhttp.DefaultPooledTransport()
|
2017-11-02 14:30:04 +00:00
|
|
|
transport.TLSClientConfig = tlsConfig.Clone()
|
|
|
|
if err := http2.ConfigureTransport(transport); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-24 15:45:29 +00:00
|
|
|
client := &http.Client{
|
|
|
|
Transport: transport,
|
|
|
|
CheckRedirect: func(*http.Request, []*http.Request) error {
|
|
|
|
// This can of course be overridden per-test by using its own client
|
2017-11-08 15:15:56 +00:00
|
|
|
return fmt.Errorf("redirects not allowed in these tests")
|
2017-02-24 15:45:29 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
config := api.DefaultConfig()
|
2017-11-10 22:44:25 +00:00
|
|
|
if config.Error != nil {
|
|
|
|
t.Fatal(config.Error)
|
|
|
|
}
|
2017-02-24 15:45:29 +00:00
|
|
|
config.Address = fmt.Sprintf("https://127.0.0.1:%d", port)
|
|
|
|
config.HttpClient = client
|
|
|
|
apiClient, err := api.NewClient(config)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-09-04 22:39:57 +00:00
|
|
|
if opts == nil || !opts.SkipInit {
|
2017-09-04 20:28:21 +00:00
|
|
|
apiClient.SetToken(testCluster.RootToken)
|
|
|
|
}
|
2017-02-24 15:45:29 +00:00
|
|
|
return apiClient
|
|
|
|
}
|
|
|
|
|
2017-01-17 20:43:10 +00:00
|
|
|
var ret []*TestClusterCore
|
2017-09-01 05:02:03 +00:00
|
|
|
for i := 0; i < numCores; i++ {
|
|
|
|
tcc := &TestClusterCore{
|
|
|
|
Core: cores[i],
|
|
|
|
ServerKey: certInfoSlice[i].key,
|
|
|
|
ServerKeyPEM: certInfoSlice[i].keyPEM,
|
|
|
|
ServerCert: certInfoSlice[i].cert,
|
|
|
|
ServerCertBytes: certInfoSlice[i].certBytes,
|
|
|
|
ServerCertPEM: certInfoSlice[i].certPEM,
|
|
|
|
Listeners: listeners[i],
|
|
|
|
Handler: handlers[i],
|
|
|
|
Server: servers[i],
|
|
|
|
TLSConfig: tlsConfigs[i],
|
|
|
|
Client: getAPIClient(listeners[i][0].Address.Port, tlsConfigs[i]),
|
|
|
|
}
|
|
|
|
tcc.ReloadFuncs = &cores[i].reloadFuncs
|
|
|
|
tcc.ReloadFuncsLock = &cores[i].reloadFuncsLock
|
|
|
|
tcc.ReloadFuncsLock.Lock()
|
|
|
|
(*tcc.ReloadFuncs)["listener|tcp"] = []reload.ReloadFunc{certGetters[i].Reload}
|
|
|
|
tcc.ReloadFuncsLock.Unlock()
|
|
|
|
ret = append(ret, tcc)
|
|
|
|
}
|
2017-07-31 15:28:06 +00:00
|
|
|
|
|
|
|
testCluster.Cores = ret
|
|
|
|
return &testCluster
|
2016-08-15 13:42:42 +00:00
|
|
|
}
|