mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-31 18:48:08 +00:00
Recovery Mode (#7559)
* Initial work * rework * s/dr/recovery * Add sys/raw support to recovery mode (#7577) * Factor the raw paths out so they can be run with a SystemBackend. # Conflicts: # vault/logical_system.go * Add handleLogicalRecovery which is like handleLogical but is only sufficient for use with the sys-raw endpoint in recovery mode. No authentication is done yet. * Integrate with recovery-mode. We now handle unauthenticated sys/raw requests, albeit on path v1/raw instead v1/sys/raw. * Use sys/raw instead raw during recovery. * Don't bother persisting the recovery token. Authenticate sys/raw requests with it. * RecoveryMode: Support generate-root for autounseals (#7591) * Recovery: Abstract config creation and log settings * Recovery mode integration test. (#7600) * Recovery: Touch up (#7607) * Recovery: Touch up * revert the raw backend creation changes * Added recovery operation token prefix * Move RawBackend to its own file * Update API path and hit it using CLI flag on generate-root * Fix a panic triggered when handling a request that yields a nil response. (#7618) * Improve integ test to actually make changes while in recovery mode and verify they're still there after coming back in regular mode. * Refuse to allow a second recovery token to be generated. * Resize raft cluster to size 1 and start as leader (#7626) * RecoveryMode: Setup raft cluster post unseal (#7635) * Setup raft cluster post unseal in recovery mode * Remove marking as unsealed as its not needed * Address review comments * Accept only one seal config in recovery mode as there is no scope for migration
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"go.uber.org/atomic"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -99,6 +100,7 @@ type ServerCommand struct {
|
||||
flagConfigs []string
|
||||
flagLogLevel string
|
||||
flagLogFormat string
|
||||
flagRecovery bool
|
||||
flagDev bool
|
||||
flagDevRootTokenID string
|
||||
flagDevListenAddr string
|
||||
@@ -197,6 +199,13 @@ func (c *ServerCommand) Flags() *FlagSets {
|
||||
Usage: `Log format. Supported values are "standard" and "json".`,
|
||||
})
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
Name: "recovery",
|
||||
Target: &c.flagRecovery,
|
||||
Usage: "Enable recovery mode. In this mode, Vault is used to perform recovery actions." +
|
||||
"Using a recovery operation token, \"sys/raw\" API can be used to manipulate the storage.",
|
||||
})
|
||||
|
||||
f = set.NewFlagSet("Dev Options")
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
@@ -365,6 +374,384 @@ func (c *ServerCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *ServerCommand) parseConfig() (*server.Config, error) {
|
||||
// Load the configuration
|
||||
var config *server.Config
|
||||
for _, path := range c.flagConfigs {
|
||||
current, err := server.LoadConfig(path)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("error loading configuration from %s: {{err}}", path), err)
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
config = current
|
||||
} else {
|
||||
config = config.Merge(current)
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *ServerCommand) runRecoveryMode() int {
|
||||
config, err := c.parseConfig()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
// Ensure at least one config was found.
|
||||
if config == nil {
|
||||
c.UI.Output(wrapAtLength(
|
||||
"No configuration files found. Please provide configurations with the " +
|
||||
"-config flag. If you are supplying the path to a directory, please " +
|
||||
"ensure the directory contains files with the .hcl or .json " +
|
||||
"extension."))
|
||||
return 1
|
||||
}
|
||||
|
||||
level, logLevelString, logLevelWasNotSet, logFormat, err := c.processLogLevelAndFormat(config)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
c.logger = log.New(&log.LoggerOptions{
|
||||
Output: c.logWriter,
|
||||
Level: level,
|
||||
// Note that if logFormat is either unspecified or standard, then
|
||||
// the resulting logger's format will be standard.
|
||||
JSONFormat: logFormat == logging.JSONFormat,
|
||||
})
|
||||
|
||||
logLevelStr, err := c.adjustLogLevel(config, logLevelWasNotSet)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if logLevelStr != "" {
|
||||
logLevelString = logLevelStr
|
||||
}
|
||||
|
||||
// create GRPC logger
|
||||
namedGRPCLogFaker := c.logger.Named("grpclogfaker")
|
||||
grpclog.SetLogger(&grpclogFaker{
|
||||
logger: namedGRPCLogFaker,
|
||||
log: os.Getenv("VAULT_GRPC_LOGGING") != "",
|
||||
})
|
||||
|
||||
if config.Storage == nil {
|
||||
c.UI.Output("A storage backend must be specified")
|
||||
return 1
|
||||
}
|
||||
|
||||
if config.DefaultMaxRequestDuration != 0 {
|
||||
vault.DefaultMaxRequestDuration = config.DefaultMaxRequestDuration
|
||||
}
|
||||
|
||||
proxyCfg := httpproxy.FromEnvironment()
|
||||
c.logger.Info("proxy environment", "http_proxy", proxyCfg.HTTPProxy,
|
||||
"https_proxy", proxyCfg.HTTPSProxy, "no_proxy", proxyCfg.NoProxy)
|
||||
|
||||
// Initialize the storage backend
|
||||
factory, exists := c.PhysicalBackends[config.Storage.Type]
|
||||
if !exists {
|
||||
c.UI.Error(fmt.Sprintf("Unknown storage type %s", config.Storage.Type))
|
||||
return 1
|
||||
}
|
||||
if config.Storage.Type == "raft" {
|
||||
if envCA := os.Getenv("VAULT_CLUSTER_ADDR"); envCA != "" {
|
||||
config.ClusterAddr = envCA
|
||||
}
|
||||
|
||||
if len(config.ClusterAddr) == 0 {
|
||||
c.UI.Error("Cluster address must be set when using raft storage")
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
namedStorageLogger := c.logger.Named("storage." + config.Storage.Type)
|
||||
backend, err := factory(config.Storage.Config, namedStorageLogger)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error initializing storage of type %s: %s", config.Storage.Type, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
infoKeys := make([]string, 0, 10)
|
||||
info := make(map[string]string)
|
||||
info["log level"] = logLevelString
|
||||
infoKeys = append(infoKeys, "log level")
|
||||
|
||||
var barrierSeal vault.Seal
|
||||
var sealConfigError error
|
||||
|
||||
if len(config.Seals) == 0 {
|
||||
config.Seals = append(config.Seals, &server.Seal{Type: vaultseal.Shamir})
|
||||
}
|
||||
|
||||
if len(config.Seals) > 1 {
|
||||
c.UI.Error("Only one seal block is accepted in recovery mode")
|
||||
return 1
|
||||
}
|
||||
|
||||
configSeal := config.Seals[0]
|
||||
sealType := vaultseal.Shamir
|
||||
if !configSeal.Disabled && os.Getenv("VAULT_SEAL_TYPE") != "" {
|
||||
sealType = os.Getenv("VAULT_SEAL_TYPE")
|
||||
configSeal.Type = sealType
|
||||
} else {
|
||||
sealType = configSeal.Type
|
||||
}
|
||||
|
||||
var seal vault.Seal
|
||||
sealLogger := c.logger.Named(sealType)
|
||||
seal, sealConfigError = serverseal.ConfigureSeal(configSeal, &infoKeys, &info, sealLogger, vault.NewDefaultSeal(shamirseal.NewSeal(c.logger.Named("shamir"))))
|
||||
if sealConfigError != nil {
|
||||
if !errwrap.ContainsType(sealConfigError, new(logical.KeyNotFoundError)) {
|
||||
c.UI.Error(fmt.Sprintf(
|
||||
"Error parsing Seal configuration: %s", sealConfigError))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if seal == nil {
|
||||
c.UI.Error(fmt.Sprintf(
|
||||
"After configuring seal nil returned, seal type was %s", sealType))
|
||||
return 1
|
||||
}
|
||||
|
||||
barrierSeal = seal
|
||||
|
||||
// Ensure that the seal finalizer is called, even if using verify-only
|
||||
defer func() {
|
||||
err = seal.Finalize(context.Background())
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error finalizing seals: %v", err))
|
||||
}
|
||||
}()
|
||||
|
||||
coreConfig := &vault.CoreConfig{
|
||||
Physical: backend,
|
||||
StorageType: config.Storage.Type,
|
||||
Seal: barrierSeal,
|
||||
Logger: c.logger,
|
||||
DisableMlock: config.DisableMlock,
|
||||
RecoveryMode: c.flagRecovery,
|
||||
ClusterAddr: config.ClusterAddr,
|
||||
}
|
||||
|
||||
core, newCoreError := vault.NewCore(coreConfig)
|
||||
if newCoreError != nil {
|
||||
if vault.IsFatalError(newCoreError) {
|
||||
c.UI.Error(fmt.Sprintf("Error initializing core: %s", newCoreError))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if err := core.InitializeRecovery(context.Background()); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error initializing core in recovery mode: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Compile server information for output later
|
||||
infoKeys = append(infoKeys, "storage")
|
||||
info["storage"] = config.Storage.Type
|
||||
|
||||
if coreConfig.ClusterAddr != "" {
|
||||
info["cluster address"] = coreConfig.ClusterAddr
|
||||
infoKeys = append(infoKeys, "cluster address")
|
||||
}
|
||||
|
||||
// Initialize the listeners
|
||||
lns := make([]ServerListener, 0, len(config.Listeners))
|
||||
for _, lnConfig := range config.Listeners {
|
||||
ln, _, _, err := server.NewListener(lnConfig.Type, lnConfig.Config, c.logWriter, c.UI)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error initializing listener of type %s: %s", lnConfig.Type, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
lns = append(lns, ServerListener{
|
||||
Listener: ln,
|
||||
config: lnConfig.Config,
|
||||
})
|
||||
}
|
||||
|
||||
listenerCloseFunc := func() {
|
||||
for _, ln := range lns {
|
||||
ln.Listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
defer c.cleanupGuard.Do(listenerCloseFunc)
|
||||
|
||||
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, "recovery mode")
|
||||
info["recovery mode"] = "true"
|
||||
|
||||
// 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 _, ln := range lns {
|
||||
handler := vaulthttp.Handler(&vault.HandlerProperties{
|
||||
Core: core,
|
||||
MaxRequestSize: ln.maxRequestSize,
|
||||
MaxRequestDuration: ln.maxRequestDuration,
|
||||
DisablePrintableCheck: config.DisablePrintableCheck,
|
||||
RecoveryMode: c.flagRecovery,
|
||||
RecoveryToken: atomic.NewString(""),
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
IdleTimeout: 5 * time.Minute,
|
||||
ErrorLog: c.logger.StandardLogger(nil),
|
||||
}
|
||||
|
||||
go server.Serve(ln.Listener)
|
||||
}
|
||||
|
||||
if sealConfigError != nil {
|
||||
init, err := core.Initialized(context.Background())
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error checking if core is initialized: %v", err))
|
||||
return 1
|
||||
}
|
||||
if init {
|
||||
c.UI.Error("Vault is initialized but no Seal key could be loaded")
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if newCoreError != nil {
|
||||
c.UI.Warn(wrapAtLength(
|
||||
"WARNING! A non-fatal error occurred during initialization. Please " +
|
||||
"check the logs for more information."))
|
||||
c.UI.Warn("")
|
||||
}
|
||||
|
||||
if !c.flagCombineLogs {
|
||||
c.UI.Output("==> Vault server started! Log data will stream in below:\n")
|
||||
}
|
||||
|
||||
c.logGate.Flush()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.ShutdownCh:
|
||||
c.UI.Output("==> Vault shutdown triggered")
|
||||
|
||||
c.cleanupGuard.Do(listenerCloseFunc)
|
||||
|
||||
if err := core.Shutdown(); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error with core shutdown: %s", err))
|
||||
}
|
||||
|
||||
return 0
|
||||
|
||||
case <-c.SigUSR2Ch:
|
||||
buf := make([]byte, 32*1024*1024)
|
||||
n := runtime.Stack(buf[:], true)
|
||||
c.logger.Info("goroutine trace", "stack", string(buf[:n]))
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *ServerCommand) adjustLogLevel(config *server.Config, logLevelWasNotSet bool) (string, error) {
|
||||
var logLevelString string
|
||||
if config.LogLevel != "" && logLevelWasNotSet {
|
||||
configLogLevel := strings.ToLower(strings.TrimSpace(config.LogLevel))
|
||||
logLevelString = configLogLevel
|
||||
switch configLogLevel {
|
||||
case "trace":
|
||||
c.logger.SetLevel(log.Trace)
|
||||
case "debug":
|
||||
c.logger.SetLevel(log.Debug)
|
||||
case "notice", "info", "":
|
||||
c.logger.SetLevel(log.Info)
|
||||
case "warn", "warning":
|
||||
c.logger.SetLevel(log.Warn)
|
||||
case "err", "error":
|
||||
c.logger.SetLevel(log.Error)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown log level: %s", config.LogLevel)
|
||||
}
|
||||
}
|
||||
return logLevelString, nil
|
||||
}
|
||||
|
||||
func (c *ServerCommand) processLogLevelAndFormat(config *server.Config) (log.Level, string, bool, logging.LogFormat, error) {
|
||||
// Create a logger. We wrap it in a gated writer so that it doesn't
|
||||
// start logging too early.
|
||||
c.logGate = &gatedwriter.Writer{Writer: os.Stderr}
|
||||
c.logWriter = c.logGate
|
||||
if c.flagCombineLogs {
|
||||
c.logWriter = os.Stdout
|
||||
}
|
||||
var level log.Level
|
||||
var logLevelWasNotSet bool
|
||||
logFormat := logging.UnspecifiedFormat
|
||||
logLevelString := c.flagLogLevel
|
||||
c.flagLogLevel = strings.ToLower(strings.TrimSpace(c.flagLogLevel))
|
||||
switch c.flagLogLevel {
|
||||
case notSetValue, "":
|
||||
logLevelWasNotSet = true
|
||||
logLevelString = "info"
|
||||
level = log.Info
|
||||
case "trace":
|
||||
level = log.Trace
|
||||
case "debug":
|
||||
level = log.Debug
|
||||
case "notice", "info":
|
||||
level = log.Info
|
||||
case "warn", "warning":
|
||||
level = log.Warn
|
||||
case "err", "error":
|
||||
level = log.Error
|
||||
default:
|
||||
return level, logLevelString, logLevelWasNotSet, logFormat, fmt.Errorf("unknown log level: %s", c.flagLogLevel)
|
||||
}
|
||||
|
||||
if c.flagLogFormat != notSetValue {
|
||||
var err error
|
||||
logFormat, err = logging.ParseLogFormat(c.flagLogFormat)
|
||||
if err != nil {
|
||||
return level, logLevelString, logLevelWasNotSet, logFormat, err
|
||||
}
|
||||
}
|
||||
if logFormat == logging.UnspecifiedFormat {
|
||||
logFormat = logging.ParseEnvLogFormat()
|
||||
}
|
||||
if logFormat == logging.UnspecifiedFormat {
|
||||
var err error
|
||||
logFormat, err = logging.ParseLogFormat(config.LogFormat)
|
||||
if err != nil {
|
||||
return level, logLevelString, logLevelWasNotSet, logFormat, err
|
||||
}
|
||||
}
|
||||
|
||||
return level, logLevelString, logLevelWasNotSet, logFormat, nil
|
||||
}
|
||||
|
||||
func (c *ServerCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
@@ -373,6 +760,10 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
if c.flagRecovery {
|
||||
return c.runRecoveryMode()
|
||||
}
|
||||
|
||||
// Automatically enable dev mode if other dev flags are provided.
|
||||
if c.flagDevHA || c.flagDevTransactional || c.flagDevLeasedKV || c.flagDevThreeNode || c.flagDevFourCluster || c.flagDevAutoSeal || c.flagDevKVV1 {
|
||||
c.flagDev = true
|
||||
@@ -413,18 +804,16 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
config.Listeners[0].Config["address"] = c.flagDevListenAddr
|
||||
}
|
||||
}
|
||||
for _, path := range c.flagConfigs {
|
||||
current, err := server.LoadConfig(path)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error loading configuration from %s: %s", path, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
config = current
|
||||
} else {
|
||||
config = config.Merge(current)
|
||||
}
|
||||
parsedConfig, err := c.parseConfig()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if config == nil {
|
||||
config = parsedConfig
|
||||
} else {
|
||||
config = config.Merge(parsedConfig)
|
||||
}
|
||||
|
||||
// Ensure at least one config was found.
|
||||
@@ -437,58 +826,12 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Create a logger. We wrap it in a gated writer so that it doesn't
|
||||
// start logging too early.
|
||||
c.logGate = &gatedwriter.Writer{Writer: os.Stderr}
|
||||
c.logWriter = c.logGate
|
||||
if c.flagCombineLogs {
|
||||
c.logWriter = os.Stdout
|
||||
}
|
||||
var level log.Level
|
||||
var logLevelWasNotSet bool
|
||||
logLevelString := c.flagLogLevel
|
||||
c.flagLogLevel = strings.ToLower(strings.TrimSpace(c.flagLogLevel))
|
||||
switch c.flagLogLevel {
|
||||
case notSetValue, "":
|
||||
logLevelWasNotSet = true
|
||||
logLevelString = "info"
|
||||
level = log.Info
|
||||
case "trace":
|
||||
level = log.Trace
|
||||
case "debug":
|
||||
level = log.Debug
|
||||
case "notice", "info":
|
||||
level = log.Info
|
||||
case "warn", "warning":
|
||||
level = log.Warn
|
||||
case "err", "error":
|
||||
level = log.Error
|
||||
default:
|
||||
c.UI.Error(fmt.Sprintf("Unknown log level: %s", c.flagLogLevel))
|
||||
level, logLevelString, logLevelWasNotSet, logFormat, err := c.processLogLevelAndFormat(config)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
logFormat := logging.UnspecifiedFormat
|
||||
if c.flagLogFormat != notSetValue {
|
||||
var err error
|
||||
logFormat, err = logging.ParseLogFormat(c.flagLogFormat)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if logFormat == logging.UnspecifiedFormat {
|
||||
logFormat = logging.ParseEnvLogFormat()
|
||||
}
|
||||
if logFormat == logging.UnspecifiedFormat {
|
||||
var err error
|
||||
logFormat, err = logging.ParseLogFormat(config.LogFormat)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if c.flagDevThreeNode || c.flagDevFourCluster {
|
||||
c.logger = log.New(&log.LoggerOptions{
|
||||
Mutex: &sync.Mutex{},
|
||||
@@ -507,25 +850,13 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
|
||||
allLoggers := []log.Logger{c.logger}
|
||||
|
||||
// adjust log level based on config setting
|
||||
if config.LogLevel != "" && logLevelWasNotSet {
|
||||
configLogLevel := strings.ToLower(strings.TrimSpace(config.LogLevel))
|
||||
logLevelString = configLogLevel
|
||||
switch configLogLevel {
|
||||
case "trace":
|
||||
c.logger.SetLevel(log.Trace)
|
||||
case "debug":
|
||||
c.logger.SetLevel(log.Debug)
|
||||
case "notice", "info", "":
|
||||
c.logger.SetLevel(log.Info)
|
||||
case "warn", "warning":
|
||||
c.logger.SetLevel(log.Warn)
|
||||
case "err", "error":
|
||||
c.logger.SetLevel(log.Error)
|
||||
default:
|
||||
c.UI.Error(fmt.Sprintf("Unknown log level: %s", config.LogLevel))
|
||||
return 1
|
||||
}
|
||||
logLevelStr, err := c.adjustLogLevel(config, logLevelWasNotSet)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if logLevelStr != "" {
|
||||
logLevelString = logLevelStr
|
||||
}
|
||||
|
||||
// create GRPC logger
|
||||
@@ -580,7 +911,6 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
if config.Storage.Type == "raft" {
|
||||
if envCA := os.Getenv("VAULT_CLUSTER_ADDR"); envCA != "" {
|
||||
config.ClusterAddr = envCA
|
||||
@@ -1066,6 +1396,9 @@ CLUSTER_SYNTHESIS_COMPLETE:
|
||||
info["cgo"] = "enabled"
|
||||
}
|
||||
|
||||
infoKeys = append(infoKeys, "recovery mode")
|
||||
info["recovery mode"] = "false"
|
||||
|
||||
// Server configuration output
|
||||
padding := 24
|
||||
sort.Strings(infoKeys)
|
||||
@@ -1263,6 +1596,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
|
||||
MaxRequestDuration: ln.maxRequestDuration,
|
||||
DisablePrintableCheck: config.DisablePrintableCheck,
|
||||
UnauthenticatedMetricsAccess: ln.unauthenticatedMetricsAccess,
|
||||
RecoveryMode: c.flagRecovery,
|
||||
})
|
||||
|
||||
// We perform validation on the config earlier, we can just cast here
|
||||
|
||||
Reference in New Issue
Block a user