Add a -dev-three-node option for devs. (#3081)

This commit is contained in:
Jeff Mitchell
2017-07-31 11:28:06 -04:00
committed by GitHub
parent dd72c96dc8
commit c6615e1b51
18 changed files with 878 additions and 551 deletions

View File

@@ -3,17 +3,20 @@ package command
import (
"encoding/base64"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"syscall"
"testing"
"time"
"golang.org/x/net/http2"
@@ -35,6 +38,7 @@ import (
"github.com/hashicorp/vault/helper/logformat"
"github.com/hashicorp/vault/helper/mlock"
"github.com/hashicorp/vault/helper/parseutil"
"github.com/hashicorp/vault/helper/reload"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/meta"
@@ -56,16 +60,17 @@ type ServerCommand struct {
meta.Meta
logger log.Logger
logGate *gatedwriter.Writer
logger log.Logger
cleanupGuard sync.Once
reloadFuncsLock *sync.RWMutex
reloadFuncs *map[string][]vault.ReloadFunc
reloadFuncs *map[string][]reload.ReloadFunc
}
func (c *ServerCommand) Run(args []string) int {
var dev, verifyOnly, devHA, devTransactional, devLeasedGeneric bool
var dev, verifyOnly, devHA, devTransactional, devLeasedGeneric, devThreeNode bool
var configPath []string
var logLevel, devRootTokenID, devListenAddress string
flags := c.Meta.FlagSet("server", meta.FlagSetDefault)
@@ -77,6 +82,7 @@ func (c *ServerCommand) Run(args []string) int {
flags.BoolVar(&devHA, "dev-ha", false, "")
flags.BoolVar(&devTransactional, "dev-transactional", false, "")
flags.BoolVar(&devLeasedGeneric, "dev-leased-generic", false, "")
flags.BoolVar(&devThreeNode, "dev-three-node", false, "")
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.Var((*sliceflag.StringFlag)(&configPath), "config", "config")
if err := flags.Parse(args); err != nil {
@@ -85,7 +91,7 @@ func (c *ServerCommand) Run(args []string) int {
// Create a logger. We wrap it in a gated writer so that it doesn't
// start logging too early.
logGate := &gatedwriter.Writer{Writer: colorable.NewColorable(os.Stderr)}
c.logGate = &gatedwriter.Writer{Writer: colorable.NewColorable(os.Stderr)}
var level int
logLevel = strings.ToLower(strings.TrimSpace(logLevel))
switch logLevel {
@@ -112,9 +118,9 @@ func (c *ServerCommand) Run(args []string) int {
}
switch strings.ToLower(logFormat) {
case "vault", "vault_json", "vault-json", "vaultjson", "json", "":
c.logger = logformat.NewVaultLoggerWithWriter(logGate, level)
c.logger = logformat.NewVaultLoggerWithWriter(c.logGate, level)
default:
c.logger = log.NewLogger(logGate, "vault")
c.logger = log.NewLogger(c.logGate, "vault")
c.logger.SetLevel(level)
}
grpclog.SetLogger(&grpclogFaker{
@@ -129,7 +135,7 @@ func (c *ServerCommand) Run(args []string) int {
devListenAddress = os.Getenv("VAULT_DEV_LISTEN_ADDRESS")
}
if devHA || devTransactional || devLeasedGeneric {
if devHA || devTransactional || devLeasedGeneric || devThreeNode {
dev = true
}
@@ -250,6 +256,10 @@ func (c *ServerCommand) Run(args []string) int {
}
}
if devThreeNode {
return c.enableThreeNodeDevCluster(coreConfig, info, infoKeys, devListenAddress)
}
var disableClustering bool
// Initialize the separate HA storage backend, if it exists
@@ -422,7 +432,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
c.reloadFuncsLock.Lock()
lns := make([]net.Listener, 0, len(config.Listeners))
for i, lnConfig := range config.Listeners {
ln, props, reloadFunc, err := server.NewListener(lnConfig.Type, lnConfig.Config, logGate)
ln, props, reloadFunc, err := server.NewListener(lnConfig.Type, lnConfig.Config, c.logGate)
if err != nil {
c.Ui.Output(fmt.Sprintf(
"Error initializing listener of type %s: %s",
@@ -567,7 +577,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
// If we're in Dev mode, then initialize the core
if dev {
init, err := c.enableDev(core, devRootTokenID)
init, err := c.enableDev(core, coreConfig)
if err != nil {
c.Ui.Output(fmt.Sprintf(
"Error initializing Dev mode: %s", err))
@@ -618,7 +628,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
c.Ui.Output("==> Vault server started! Log data will stream in below:\n")
// Release the log gate.
logGate.Flush()
c.logGate.Flush()
// Wait for shutdown
shutdownTriggered := false
@@ -642,7 +652,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
case <-c.SighupCh:
c.Ui.Output("==> Vault reload triggered")
if err := c.Reload(configPath); err != nil {
if err := c.Reload(c.reloadFuncsLock, c.reloadFuncs, configPath); err != nil {
c.Ui.Output(fmt.Sprintf("Error(s) were encountered during reload: %s", err))
}
}
@@ -653,7 +663,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
return 0
}
func (c *ServerCommand) enableDev(core *vault.Core, rootTokenID string) (*vault.InitResult, error) {
func (c *ServerCommand) enableDev(core *vault.Core, coreConfig *vault.CoreConfig) (*vault.InitResult, error) {
// Initialize it with a basic single key
init, err := core.Initialize(&vault.InitParams{
BarrierConfig: &vault.SealConfig{
@@ -700,14 +710,14 @@ func (c *ServerCommand) enableDev(core *vault.Core, rootTokenID string) (*vault.
}
}
if rootTokenID != "" {
if coreConfig.DevToken != "" {
req := &logical.Request{
ID: "dev-gen-root",
Operation: logical.UpdateOperation,
ClientToken: init.RootToken,
Path: "auth/token/create",
Data: map[string]interface{}{
"id": rootTokenID,
"id": coreConfig.DevToken,
"policies": []string{"root"},
"no_parent": true,
"no_default_policy": true,
@@ -715,13 +725,13 @@ func (c *ServerCommand) enableDev(core *vault.Core, rootTokenID string) (*vault.
}
resp, err := core.HandleRequest(req)
if err != nil {
return nil, fmt.Errorf("failed to create root token with ID %s: %s", rootTokenID, err)
return nil, fmt.Errorf("failed to create root token with ID %s: %s", coreConfig.DevToken, err)
}
if resp == nil {
return nil, fmt.Errorf("nil response when creating root token with ID %s", rootTokenID)
return nil, fmt.Errorf("nil response when creating root token with ID %s", coreConfig.DevToken)
}
if resp.Auth == nil {
return nil, fmt.Errorf("nil auth when creating root token with ID %s", rootTokenID)
return nil, fmt.Errorf("nil auth when creating root token with ID %s", coreConfig.DevToken)
}
init.RootToken = resp.Auth.ClientToken
@@ -747,6 +757,168 @@ func (c *ServerCommand) enableDev(core *vault.Core, rootTokenID string) (*vault.
return init, nil
}
func (c *ServerCommand) enableThreeNodeDevCluster(base *vault.CoreConfig, info map[string]string, infoKeys []string, devListenAddress string) int {
testCluster := vault.NewTestCluster(&testing.T{}, base, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
BaseListenAddress: devListenAddress,
})
defer c.cleanupGuard.Do(testCluster.Cleanup)
info["cluster parameters path"] = testCluster.TempDir
info["log level"] = "trace"
infoKeys = append(infoKeys, "cluster parameters path", "log level")
for i, core := range testCluster.Cores {
info[fmt.Sprintf("node %d redirect address", i)] = fmt.Sprintf("https://%s", core.Listeners[0].Address.String())
infoKeys = append(infoKeys, fmt.Sprintf("node %d redirect address", i))
}
infoKeys = append(infoKeys, "version")
verInfo := version.GetVersion()
info["version"] = verInfo.FullVersionNumber(false)
if verInfo.Revision != "" {
info["version sha"] = strings.Trim(verInfo.Revision, "'")
infoKeys = append(infoKeys, "version sha")
}
infoKeys = append(infoKeys, "cgo")
info["cgo"] = "disabled"
if version.CgoEnabled {
info["cgo"] = "enabled"
}
// Server configuration output
padding := 24
sort.Strings(infoKeys)
c.Ui.Output("==> Vault server configuration:\n")
for _, k := range infoKeys {
c.Ui.Output(fmt.Sprintf(
"%s%s: %s",
strings.Repeat(" ", padding-len(k)),
strings.Title(k),
info[k]))
}
c.Ui.Output("")
for _, core := range testCluster.Cores {
core.Server.Handler = vaulthttp.Handler(core.Core)
core.SetClusterHandler(core.Server.Handler)
}
testCluster.Start()
if base.DevToken != "" {
req := &logical.Request{
ID: "dev-gen-root",
Operation: logical.UpdateOperation,
ClientToken: testCluster.RootToken,
Path: "auth/token/create",
Data: map[string]interface{}{
"id": base.DevToken,
"policies": []string{"root"},
"no_parent": true,
"no_default_policy": true,
},
}
resp, err := testCluster.Cores[0].HandleRequest(req)
if err != nil {
c.Ui.Output(fmt.Sprintf("failed to create root token with ID %s: %s", base.DevToken, err))
return 1
}
if resp == nil {
c.Ui.Output(fmt.Sprintf("nil response when creating root token with ID %s", base.DevToken))
return 1
}
if resp.Auth == nil {
c.Ui.Output(fmt.Sprintf("nil auth when creating root token with ID %s", base.DevToken))
return 1
}
testCluster.RootToken = resp.Auth.ClientToken
req.ID = "dev-revoke-init-root"
req.Path = "auth/token/revoke-self"
req.Data = nil
resp, err = testCluster.Cores[0].HandleRequest(req)
if err != nil {
c.Ui.Output(fmt.Sprintf("failed to revoke initial root token: %s", err))
return 1
}
}
// Set the token
tokenHelper, err := c.TokenHelper()
if err != nil {
c.Ui.Output(fmt.Sprintf("%v", err))
return 1
}
if err := tokenHelper.Store(testCluster.RootToken); err != nil {
c.Ui.Output(fmt.Sprintf("%v", err))
return 1
}
if err := ioutil.WriteFile(filepath.Join(testCluster.TempDir, "root_token"), []byte(testCluster.RootToken), 0755); err != nil {
c.Ui.Output(fmt.Sprintf("%v", err))
return 1
}
c.Ui.Output(fmt.Sprintf(
"==> Three node dev mode is enabled\n\n" +
"The unseal key and root token are reproduced below in case you\n" +
"want to seal/unseal the Vault or play with authentication.\n",
))
for i, key := range testCluster.BarrierKeys {
c.Ui.Output(fmt.Sprintf(
"Unseal Key %d: %s",
i, base64.StdEncoding.EncodeToString(key),
))
}
c.Ui.Output(fmt.Sprintf(
"\nRoot Token: %s\n", testCluster.RootToken,
))
// Output the header that the server has started
c.Ui.Output("==> Vault server started! Log data will stream in below:\n")
// Release the log gate.
c.logGate.Flush()
// Wait for shutdown
shutdownTriggered := false
for !shutdownTriggered {
select {
case <-c.ShutdownCh:
c.Ui.Output("==> Vault shutdown triggered")
// Stop the listners so that we don't process further client requests.
c.cleanupGuard.Do(testCluster.Cleanup)
// Shutdown will wait until after Vault is sealed, which means the
// request forwarding listeners will also be closed (and also
// waited for).
for _, core := range testCluster.Cores {
if err := core.Shutdown(); err != nil {
c.Ui.Output(fmt.Sprintf("Error with core shutdown: %s", err))
}
}
shutdownTriggered = true
case <-c.SighupCh:
c.Ui.Output("==> Vault reload triggered")
for _, core := range testCluster.Cores {
if err := c.Reload(core.ReloadFuncsLock, core.ReloadFuncs, nil); err != nil {
c.Ui.Output(fmt.Sprintf("Error(s) were encountered during reload: %s", err))
}
}
}
}
return 0
}
// detectRedirect is used to attempt redirect address detection
func (c *ServerCommand) detectRedirect(detect physical.RedirectDetect,
config *server.Config) (string, error) {
@@ -921,55 +1093,29 @@ func (c *ServerCommand) setupTelemetry(config *server.Config) error {
return nil
}
func (c *ServerCommand) Reload(configPath []string) error {
c.reloadFuncsLock.RLock()
defer c.reloadFuncsLock.RUnlock()
func (c *ServerCommand) Reload(lock *sync.RWMutex, reloadFuncs *map[string][]reload.ReloadFunc, configPath []string) error {
lock.RLock()
defer lock.RUnlock()
var reloadErrors *multierror.Error
// Read the new config
var config *server.Config
for _, path := range configPath {
current, err := server.LoadConfig(path, c.logger)
if err != nil {
reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("Error loading configuration from %s: %s", path, err))
goto audit
}
if config == nil {
config = current
} else {
config = config.Merge(current)
}
}
// Ensure at least one config was found.
if config == nil {
reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("No configuration files found"))
goto audit
}
// Call reload on the listeners. This will call each listener with each
// config block, but they verify the address.
for _, lnConfig := range config.Listeners {
for _, relFunc := range (*c.reloadFuncs)["listener|"+lnConfig.Type] {
if err := relFunc(lnConfig.Config); err != nil {
reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("Error encountered reloading configuration: %s", err))
goto audit
for k, relFuncs := range *reloadFuncs {
switch {
case strings.HasPrefix(k, "listener|"):
for _, relFunc := range relFuncs {
if relFunc != nil {
if err := relFunc(nil); err != nil {
reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("Error encountered reloading listener: %v", err))
}
}
}
}
}
audit:
// file audit reload funcs
for k, relFuncs := range *c.reloadFuncs {
if !strings.HasPrefix(k, "audit_file|") {
continue
}
for _, relFunc := range relFuncs {
if relFunc != nil {
if err := relFunc(nil); err != nil {
reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("Error encountered reloading file audit backend at path %s: %v", strings.TrimPrefix(k, "audit_file|"), err))
case strings.HasPrefix(k, "audit_file|"):
for _, relFunc := range relFuncs {
if relFunc != nil {
if err := relFunc(nil); err != nil {
reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("Error encountered reloading file audit backend at path %s: %v", strings.TrimPrefix(k, "audit_file|"), err))
}
}
}
}