355 lines
9.9 KiB
Go
355 lines
9.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package command
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
kv "github.com/hashicorp/vault-plugin-secrets-kv"
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/hashicorp/vault/audit"
|
|
"github.com/hashicorp/vault/builtin/logical/pki"
|
|
"github.com/hashicorp/vault/builtin/logical/ssh"
|
|
"github.com/hashicorp/vault/builtin/logical/transit"
|
|
"github.com/hashicorp/vault/helper/benchhelpers"
|
|
"github.com/hashicorp/vault/helper/builtinplugins"
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/hashicorp/vault/sdk/physical/inmem"
|
|
"github.com/hashicorp/vault/vault"
|
|
"github.com/hashicorp/vault/vault/seal"
|
|
"github.com/mitchellh/cli"
|
|
|
|
auditFile "github.com/hashicorp/vault/builtin/audit/file"
|
|
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
|
|
vaulthttp "github.com/hashicorp/vault/http"
|
|
)
|
|
|
|
var (
|
|
defaultVaultLogger = log.NewNullLogger()
|
|
|
|
defaultVaultCredentialBackends = map[string]logical.Factory{
|
|
"userpass": credUserpass.Factory,
|
|
}
|
|
|
|
defaultVaultAuditBackends = map[string]audit.Factory{
|
|
"file": auditFile.Factory,
|
|
}
|
|
|
|
defaultVaultLogicalBackends = map[string]logical.Factory{
|
|
"generic-leased": vault.LeasedPassthroughBackendFactory,
|
|
"pki": pki.Factory,
|
|
"ssh": ssh.Factory,
|
|
"transit": transit.Factory,
|
|
"kv": kv.Factory,
|
|
}
|
|
)
|
|
|
|
// assertNoTabs asserts the CLI help has no tab characters.
|
|
func assertNoTabs(tb testing.TB, c cli.Command) {
|
|
tb.Helper()
|
|
|
|
if strings.ContainsRune(c.Help(), '\t') {
|
|
tb.Errorf("%#v help output contains tabs", c)
|
|
}
|
|
}
|
|
|
|
// testVaultServer creates a test vault cluster and returns a configured API
|
|
// client and closer function.
|
|
func testVaultServer(tb testing.TB) (*api.Client, func()) {
|
|
tb.Helper()
|
|
|
|
client, _, closer := testVaultServerUnseal(tb)
|
|
return client, closer
|
|
}
|
|
|
|
func testVaultServerWithSecrets(ctx context.Context, tb testing.TB) (*api.Client, func()) {
|
|
tb.Helper()
|
|
|
|
client, _, closer := testVaultServerUnseal(tb)
|
|
|
|
// enable kv-v1 backend
|
|
if err := client.Sys().Mount("kv-v1/", &api.MountInput{
|
|
Type: "kv-v1",
|
|
}); err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
// enable kv-v2 backend
|
|
if err := client.Sys().Mount("kv-v2/", &api.MountInput{
|
|
Type: "kv-v2",
|
|
}); err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
// populate dummy secrets
|
|
for _, path := range []string{
|
|
"foo",
|
|
"app-1/foo",
|
|
"app-1/bar",
|
|
"app-1/nested/baz",
|
|
} {
|
|
if err := client.KVv1("kv-v1").Put(ctx, path, map[string]interface{}{
|
|
"user": "test",
|
|
"password": "Hashi123",
|
|
}); err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
if _, err := client.KVv2("kv-v2").Put(ctx, path, map[string]interface{}{
|
|
"user": "test",
|
|
"password": "Hashi123",
|
|
}); err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
}
|
|
|
|
return client, closer
|
|
}
|
|
|
|
func testVaultServerWithKVVersion(tb testing.TB, kvVersion string) (*api.Client, func()) {
|
|
tb.Helper()
|
|
|
|
client, _, closer := testVaultServerUnsealWithKVVersionWithSeal(tb, kvVersion, nil)
|
|
return client, closer
|
|
}
|
|
|
|
func testVaultServerAllBackends(tb testing.TB) (*api.Client, func()) {
|
|
tb.Helper()
|
|
|
|
client, _, closer := testVaultServerCoreConfig(tb, &vault.CoreConfig{
|
|
DisableMlock: true,
|
|
DisableCache: true,
|
|
Logger: defaultVaultLogger,
|
|
CredentialBackends: credentialBackends,
|
|
AuditBackends: auditBackends,
|
|
LogicalBackends: logicalBackends,
|
|
BuiltinRegistry: builtinplugins.Registry,
|
|
})
|
|
return client, closer
|
|
}
|
|
|
|
// testVaultServerAutoUnseal creates a test vault cluster and sets it up with auto unseal
|
|
// the function returns a client, the recovery keys, and a closer function
|
|
func testVaultServerAutoUnseal(tb testing.TB) (*api.Client, []string, func()) {
|
|
testSeal, _ := seal.NewTestSeal(nil)
|
|
autoSeal, err := vault.NewAutoSeal(testSeal)
|
|
if err != nil {
|
|
tb.Fatal("unable to create autoseal", err)
|
|
}
|
|
return testVaultServerUnsealWithKVVersionWithSeal(tb, "1", autoSeal)
|
|
}
|
|
|
|
// testVaultServerUnseal creates a test vault cluster and returns a configured
|
|
// API client, list of unseal keys (as strings), and a closer function.
|
|
func testVaultServerUnseal(tb testing.TB) (*api.Client, []string, func()) {
|
|
return testVaultServerUnsealWithKVVersionWithSeal(tb, "1", nil)
|
|
}
|
|
|
|
func testVaultServerUnsealWithKVVersionWithSeal(tb testing.TB, kvVersion string, seal vault.Seal) (*api.Client, []string, func()) {
|
|
tb.Helper()
|
|
logger := log.NewInterceptLogger(&log.LoggerOptions{
|
|
Output: log.DefaultOutput,
|
|
Level: log.Debug,
|
|
JSONFormat: logging.ParseEnvLogFormat() == logging.JSONFormat,
|
|
})
|
|
|
|
return testVaultServerCoreConfigWithOpts(tb, &vault.CoreConfig{
|
|
DisableMlock: true,
|
|
DisableCache: true,
|
|
Logger: logger,
|
|
CredentialBackends: defaultVaultCredentialBackends,
|
|
AuditBackends: defaultVaultAuditBackends,
|
|
LogicalBackends: defaultVaultLogicalBackends,
|
|
BuiltinRegistry: builtinplugins.Registry,
|
|
Seal: seal,
|
|
}, &vault.TestClusterOptions{
|
|
HandlerFunc: vaulthttp.Handler,
|
|
NumCores: 1,
|
|
KVVersion: kvVersion,
|
|
})
|
|
}
|
|
|
|
// testVaultServerUnseal creates a test vault cluster and returns a configured
|
|
// API client, list of unseal keys (as strings), and a closer function
|
|
// configured with the given plugin directory.
|
|
func testVaultServerPluginDir(tb testing.TB, pluginDir string) (*api.Client, []string, func()) {
|
|
tb.Helper()
|
|
|
|
return testVaultServerCoreConfig(tb, &vault.CoreConfig{
|
|
DisableMlock: true,
|
|
DisableCache: true,
|
|
Logger: defaultVaultLogger,
|
|
CredentialBackends: defaultVaultCredentialBackends,
|
|
AuditBackends: defaultVaultAuditBackends,
|
|
LogicalBackends: defaultVaultLogicalBackends,
|
|
PluginDirectory: pluginDir,
|
|
BuiltinRegistry: builtinplugins.Registry,
|
|
})
|
|
}
|
|
|
|
func testVaultServerCoreConfig(tb testing.TB, coreConfig *vault.CoreConfig) (*api.Client, []string, func()) {
|
|
return testVaultServerCoreConfigWithOpts(tb, coreConfig, &vault.TestClusterOptions{
|
|
HandlerFunc: vaulthttp.Handler,
|
|
NumCores: 1, // Default is 3, but we don't need that many
|
|
})
|
|
}
|
|
|
|
// testVaultServerCoreConfig creates a new vault cluster with the given core
|
|
// configuration. This is a lower-level test helper. If the seal config supports recovery keys, then
|
|
// recovery keys are returned. Otherwise, unseal keys are returned
|
|
func testVaultServerCoreConfigWithOpts(tb testing.TB, coreConfig *vault.CoreConfig, opts *vault.TestClusterOptions) (*api.Client, []string, func()) {
|
|
tb.Helper()
|
|
|
|
cluster := vault.NewTestCluster(benchhelpers.TBtoT(tb), coreConfig, opts)
|
|
cluster.Start()
|
|
|
|
// Make it easy to get access to the active
|
|
core := cluster.Cores[0].Core
|
|
vault.TestWaitActive(benchhelpers.TBtoT(tb), core)
|
|
|
|
// Get the client already setup for us!
|
|
client := cluster.Cores[0].Client
|
|
client.SetToken(cluster.RootToken)
|
|
|
|
var keys [][]byte
|
|
if coreConfig.Seal != nil && coreConfig.Seal.RecoveryKeySupported() {
|
|
keys = cluster.RecoveryKeys
|
|
} else {
|
|
keys = cluster.BarrierKeys
|
|
}
|
|
|
|
return client, encodeKeys(keys), cluster.Cleanup
|
|
}
|
|
|
|
// Convert the unseal keys to base64 encoded, since these are how the user
|
|
// will get them.
|
|
func encodeKeys(rawKeys [][]byte) []string {
|
|
keys := make([]string, len(rawKeys))
|
|
for i := range rawKeys {
|
|
keys[i] = base64.StdEncoding.EncodeToString(rawKeys[i])
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// testVaultServerUninit creates an uninitialized server.
|
|
func testVaultServerUninit(tb testing.TB) (*api.Client, func()) {
|
|
tb.Helper()
|
|
|
|
inm, err := inmem.NewInmem(nil, defaultVaultLogger)
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
core, err := vault.NewCore(&vault.CoreConfig{
|
|
DisableMlock: true,
|
|
DisableCache: true,
|
|
Logger: defaultVaultLogger,
|
|
Physical: inm,
|
|
CredentialBackends: defaultVaultCredentialBackends,
|
|
AuditBackends: defaultVaultAuditBackends,
|
|
LogicalBackends: defaultVaultLogicalBackends,
|
|
BuiltinRegistry: builtinplugins.Registry,
|
|
})
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
ln, addr := vaulthttp.TestServer(tb, core)
|
|
|
|
client, err := api.NewClient(&api.Config{
|
|
Address: addr,
|
|
})
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
closer := func() {
|
|
core.Shutdown()
|
|
ln.Close()
|
|
}
|
|
|
|
return client, closer
|
|
}
|
|
|
|
// testVaultServerBad creates an http server that returns a 500 on each request
|
|
// to simulate failures.
|
|
func testVaultServerBad(tb testing.TB) (*api.Client, func()) {
|
|
tb.Helper()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
server := &http.Server{
|
|
Addr: "127.0.0.1:0",
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "500 internal server error", http.StatusInternalServerError)
|
|
}),
|
|
ReadTimeout: 1 * time.Second,
|
|
ReadHeaderTimeout: 1 * time.Second,
|
|
WriteTimeout: 1 * time.Second,
|
|
IdleTimeout: 1 * time.Second,
|
|
}
|
|
|
|
go func() {
|
|
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
tb.Fatal(err)
|
|
}
|
|
}()
|
|
|
|
client, err := api.NewClient(&api.Config{
|
|
Address: "http://" + listener.Addr().String(),
|
|
})
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
return client, func() {
|
|
ctx, done := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer done()
|
|
|
|
server.Shutdown(ctx)
|
|
}
|
|
}
|
|
|
|
// testTokenAndAccessor creates a new authentication token capable of being renewed with
|
|
// the default policy attached. It returns the token and it's accessor.
|
|
func testTokenAndAccessor(tb testing.TB, client *api.Client) (string, string) {
|
|
tb.Helper()
|
|
|
|
secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
|
|
Policies: []string{"default"},
|
|
TTL: "30m",
|
|
})
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" {
|
|
tb.Fatalf("missing auth data: %#v", secret)
|
|
}
|
|
return secret.Auth.ClientToken, secret.Auth.Accessor
|
|
}
|
|
|
|
func testClient(tb testing.TB, addr string, token string) *api.Client {
|
|
tb.Helper()
|
|
config := api.DefaultConfig()
|
|
config.Address = addr
|
|
client, err := api.NewClient(config)
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
client.SetToken(token)
|
|
|
|
return client
|
|
}
|