mirror of https://github.com/status-im/consul.git
Merge pull request #8086 from hashicorp/feature/auto-config/client-config-inject
This commit is contained in:
commit
abce1f0eee
|
@ -187,11 +187,11 @@ func (a *Agent) vetCheckRegisterWithAuthorizer(authz acl.Authorizer, check *stru
|
||||||
// Vet the check itself.
|
// Vet the check itself.
|
||||||
if len(check.ServiceName) > 0 {
|
if len(check.ServiceName) > 0 {
|
||||||
if authz.ServiceWrite(check.ServiceName, &authzContext) != acl.Allow {
|
if authz.ServiceWrite(check.ServiceName, &authzContext) != acl.Allow {
|
||||||
return acl.ErrPermissionDenied
|
return acl.PermissionDenied("Missing service:write on %v", structs.ServiceIDString(check.ServiceName, &check.EnterpriseMeta))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if authz.NodeWrite(a.config.NodeName, &authzContext) != acl.Allow {
|
if authz.NodeWrite(a.config.NodeName, &authzContext) != acl.Allow {
|
||||||
return acl.ErrPermissionDenied
|
return acl.PermissionDenied("Missing node:write on %s", a.config.NodeName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,11 +199,11 @@ func (a *Agent) vetCheckRegisterWithAuthorizer(authz acl.Authorizer, check *stru
|
||||||
if existing := a.State.Check(check.CompoundCheckID()); existing != nil {
|
if existing := a.State.Check(check.CompoundCheckID()); existing != nil {
|
||||||
if len(existing.ServiceName) > 0 {
|
if len(existing.ServiceName) > 0 {
|
||||||
if authz.ServiceWrite(existing.ServiceName, &authzContext) != acl.Allow {
|
if authz.ServiceWrite(existing.ServiceName, &authzContext) != acl.Allow {
|
||||||
return acl.ErrPermissionDenied
|
return acl.PermissionDenied("Missing service:write on %s", structs.ServiceIDString(existing.ServiceName, &existing.EnterpriseMeta))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if authz.NodeWrite(a.config.NodeName, &authzContext) != acl.Allow {
|
if authz.NodeWrite(a.config.NodeName, &authzContext) != acl.Allow {
|
||||||
return acl.ErrPermissionDenied
|
return acl.PermissionDenied("Missing node:write on %s", a.config.NodeName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ type TestACLAgent struct {
|
||||||
// The key is that we are the delegate so we can control the ResolveToken responses
|
// The key is that we are the delegate so we can control the ResolveToken responses
|
||||||
func NewTestACLAgent(t *testing.T, name string, hcl string, resolveAuthz authzResolver, resolveIdent identResolver) *TestACLAgent {
|
func NewTestACLAgent(t *testing.T, name string, hcl string, resolveAuthz authzResolver, resolveIdent identResolver) *TestACLAgent {
|
||||||
a := &TestACLAgent{Name: name, HCL: hcl, resolveAuthzFn: resolveAuthz, resolveIdentFn: resolveIdent}
|
a := &TestACLAgent{Name: name, HCL: hcl, resolveAuthzFn: resolveAuthz, resolveIdentFn: resolveIdent}
|
||||||
hclDataDir := `data_dir = "acl-agent"`
|
dataDir := `data_dir = "acl-agent"`
|
||||||
|
|
||||||
logOutput := testutil.TestWriter(t)
|
logOutput := testutil.TestWriter(t)
|
||||||
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
|
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
|
||||||
|
@ -66,13 +66,20 @@ func NewTestACLAgent(t *testing.T, name string, hcl string, resolveAuthz authzRe
|
||||||
Output: logOutput,
|
Output: logOutput,
|
||||||
})
|
})
|
||||||
|
|
||||||
a.Config = TestConfig(logger,
|
opts := []AgentOption{
|
||||||
config.Source{Name: a.Name, Format: "hcl", Data: a.HCL},
|
WithLogger(logger),
|
||||||
config.Source{Name: a.Name + ".data_dir", Format: "hcl", Data: hclDataDir},
|
WithBuilderOpts(config.BuilderOpts{
|
||||||
)
|
HCL: []string{
|
||||||
|
TestConfigHCL(NodeID()),
|
||||||
|
a.HCL,
|
||||||
|
dataDir,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
agent, err := New(a.Config, logger)
|
agent, err := New(opts...)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
a.Config = agent.GetConfig()
|
||||||
a.Agent = agent
|
a.Agent = agent
|
||||||
|
|
||||||
agent.LogOutput = logOutput
|
agent.LogOutput = logOutput
|
||||||
|
@ -258,7 +265,7 @@ var (
|
||||||
nodeRWSecret: {
|
nodeRWSecret: {
|
||||||
token: structs.ACLToken{
|
token: structs.ACLToken{
|
||||||
AccessorID: "efb6b7d5-d343-47c1-b4cb-aa6b94d2f490",
|
AccessorID: "efb6b7d5-d343-47c1-b4cb-aa6b94d2f490",
|
||||||
SecretID: nodeROSecret,
|
SecretID: nodeRWSecret,
|
||||||
},
|
},
|
||||||
rules: `node_prefix "Node" { policy = "write" }`,
|
rules: `node_prefix "Node" { policy = "write" }`,
|
||||||
},
|
},
|
||||||
|
|
317
agent/agent.go
317
agent/agent.go
|
@ -21,18 +21,22 @@ import (
|
||||||
"github.com/hashicorp/go-connlimit"
|
"github.com/hashicorp/go-connlimit"
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-memdb"
|
"github.com/hashicorp/go-memdb"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/ae"
|
"github.com/hashicorp/consul/agent/ae"
|
||||||
|
autoconf "github.com/hashicorp/consul/agent/auto-config"
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
cachetype "github.com/hashicorp/consul/agent/cache-types"
|
cachetype "github.com/hashicorp/consul/agent/cache-types"
|
||||||
"github.com/hashicorp/consul/agent/checks"
|
"github.com/hashicorp/consul/agent/checks"
|
||||||
"github.com/hashicorp/consul/agent/config"
|
"github.com/hashicorp/consul/agent/config"
|
||||||
"github.com/hashicorp/consul/agent/consul"
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
"github.com/hashicorp/consul/agent/local"
|
"github.com/hashicorp/consul/agent/local"
|
||||||
|
"github.com/hashicorp/consul/agent/pool"
|
||||||
"github.com/hashicorp/consul/agent/proxycfg"
|
"github.com/hashicorp/consul/agent/proxycfg"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/agent/systemd"
|
"github.com/hashicorp/consul/agent/systemd"
|
||||||
|
@ -160,6 +164,8 @@ type notifier interface {
|
||||||
// mode, it runs a full Consul server. In client-only mode, it only forwards
|
// mode, it runs a full Consul server. In client-only mode, it only forwards
|
||||||
// requests to other Consul servers.
|
// requests to other Consul servers.
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
|
autoConf *autoconf.AutoConfig
|
||||||
|
|
||||||
// config is the agent configuration.
|
// config is the agent configuration.
|
||||||
config *config.RuntimeConfig
|
config *config.RuntimeConfig
|
||||||
|
|
||||||
|
@ -247,8 +253,6 @@ type Agent struct {
|
||||||
eventLock sync.RWMutex
|
eventLock sync.RWMutex
|
||||||
eventNotify NotifyGroup
|
eventNotify NotifyGroup
|
||||||
|
|
||||||
reloadCh chan chan error
|
|
||||||
|
|
||||||
shutdown bool
|
shutdown bool
|
||||||
shutdownCh chan struct{}
|
shutdownCh chan struct{}
|
||||||
shutdownLock sync.Mutex
|
shutdownLock sync.Mutex
|
||||||
|
@ -313,22 +317,103 @@ type Agent struct {
|
||||||
// IP.
|
// IP.
|
||||||
httpConnLimiter connlimit.Limiter
|
httpConnLimiter connlimit.Limiter
|
||||||
|
|
||||||
|
// Connection Pool
|
||||||
|
connPool *pool.ConnPool
|
||||||
|
|
||||||
// enterpriseAgent embeds fields that we only access in consul-enterprise builds
|
// enterpriseAgent embeds fields that we only access in consul-enterprise builds
|
||||||
enterpriseAgent
|
enterpriseAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
// New verifies the configuration given has a Datacenter and DataDir
|
type agentOptions struct {
|
||||||
// configured, and maps the remaining config fields to fields on the Agent.
|
logger hclog.InterceptLogger
|
||||||
func New(c *config.RuntimeConfig, logger hclog.InterceptLogger) (*Agent, error) {
|
builderOpts config.BuilderOpts
|
||||||
if c.Datacenter == "" {
|
ui cli.Ui
|
||||||
return nil, fmt.Errorf("Must configure a Datacenter")
|
config *config.RuntimeConfig
|
||||||
}
|
overrides []config.Source
|
||||||
if c.DataDir == "" && !c.DevMode {
|
writers []io.Writer
|
||||||
return nil, fmt.Errorf("Must configure a DataDir")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
type AgentOption func(opt *agentOptions)
|
||||||
|
|
||||||
|
// WithLogger is used to override any automatic logger creation
|
||||||
|
// and provide one already built instead. This is mostly useful
|
||||||
|
// for testing.
|
||||||
|
func WithLogger(logger hclog.InterceptLogger) AgentOption {
|
||||||
|
return func(opt *agentOptions) {
|
||||||
|
opt.logger = logger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBuilderOpts specifies the command line config.BuilderOpts to use that the agent
|
||||||
|
// is being started with
|
||||||
|
func WithBuilderOpts(builderOpts config.BuilderOpts) AgentOption {
|
||||||
|
return func(opt *agentOptions) {
|
||||||
|
opt.builderOpts = builderOpts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCLI provides a cli.Ui instance to use when emitting configuration
|
||||||
|
// warnings during the first configuration parsing.
|
||||||
|
func WithCLI(ui cli.Ui) AgentOption {
|
||||||
|
return func(opt *agentOptions) {
|
||||||
|
opt.ui = ui
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLogWriter will add an additional log output to the logger that gets
|
||||||
|
// configured after configuration parsing
|
||||||
|
func WithLogWriter(writer io.Writer) AgentOption {
|
||||||
|
return func(opt *agentOptions) {
|
||||||
|
opt.writers = append(opt.writers, writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverrides is used to provide a config source to append to the tail sources
|
||||||
|
// during config building. It is really only useful for testing to tune non-user
|
||||||
|
// configurable tunables to make various tests converge more quickly than they
|
||||||
|
// could otherwise.
|
||||||
|
func WithOverrides(overrides ...config.Source) AgentOption {
|
||||||
|
return func(opt *agentOptions) {
|
||||||
|
opt.overrides = overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfig provides an already parsed configuration to the Agent
|
||||||
|
// Deprecated: Should allow the agent to parse the configuration.
|
||||||
|
func WithConfig(config *config.RuntimeConfig) AgentOption {
|
||||||
|
return func(opt *agentOptions) {
|
||||||
|
opt.config = config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenAgentOptions(options []AgentOption) agentOptions {
|
||||||
|
var flat agentOptions
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(&flat)
|
||||||
|
}
|
||||||
|
return flat
|
||||||
|
}
|
||||||
|
|
||||||
|
// New process the desired options and creates a new Agent.
|
||||||
|
// This process will
|
||||||
|
// * parse the config given the config Flags
|
||||||
|
// * setup logging
|
||||||
|
// * using predefined logger given in an option
|
||||||
|
// OR
|
||||||
|
// * initialize a new logger from the configuration
|
||||||
|
// including setting up gRPC logging
|
||||||
|
// * initialize telemetry
|
||||||
|
// * create a TLS Configurator
|
||||||
|
// * build a shared connection pool
|
||||||
|
// * create the ServiceManager
|
||||||
|
// * setup the NodeID if one isn't provided in the configuration
|
||||||
|
// * create the AutoConfig object for future use in fully
|
||||||
|
// resolving the configuration
|
||||||
|
func New(options ...AgentOption) (*Agent, error) {
|
||||||
|
flat := flattenAgentOptions(options)
|
||||||
|
|
||||||
|
// Create most of the agent
|
||||||
a := Agent{
|
a := Agent{
|
||||||
config: c,
|
|
||||||
checkReapAfter: make(map[structs.CheckID]time.Duration),
|
checkReapAfter: make(map[structs.CheckID]time.Duration),
|
||||||
checkMonitors: make(map[structs.CheckID]*checks.CheckMonitor),
|
checkMonitors: make(map[structs.CheckID]*checks.CheckMonitor),
|
||||||
checkTTLs: make(map[structs.CheckID]*checks.CheckTTL),
|
checkTTLs: make(map[structs.CheckID]*checks.CheckTTL),
|
||||||
|
@ -340,14 +425,84 @@ func New(c *config.RuntimeConfig, logger hclog.InterceptLogger) (*Agent, error)
|
||||||
eventCh: make(chan serf.UserEvent, 1024),
|
eventCh: make(chan serf.UserEvent, 1024),
|
||||||
eventBuf: make([]*UserEvent, 256),
|
eventBuf: make([]*UserEvent, 256),
|
||||||
joinLANNotifier: &systemd.Notifier{},
|
joinLANNotifier: &systemd.Notifier{},
|
||||||
reloadCh: make(chan chan error),
|
|
||||||
retryJoinCh: make(chan error),
|
retryJoinCh: make(chan error),
|
||||||
shutdownCh: make(chan struct{}),
|
shutdownCh: make(chan struct{}),
|
||||||
InterruptStartCh: make(chan struct{}),
|
InterruptStartCh: make(chan struct{}),
|
||||||
endpoints: make(map[string]string),
|
endpoints: make(map[string]string),
|
||||||
tokens: new(token.Store),
|
tokens: new(token.Store),
|
||||||
logger: logger,
|
logger: flat.logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse the configuration and handle the error/warnings
|
||||||
|
config, warnings, err := autoconf.LoadConfig(flat.builderOpts, config.Source{}, flat.overrides...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, w := range warnings {
|
||||||
|
if a.logger != nil {
|
||||||
|
a.logger.Warn(w)
|
||||||
|
} else if flat.ui != nil {
|
||||||
|
flat.ui.Warn(w)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(os.Stderr, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the config in the agent, this is just the preliminary configuration as we haven't
|
||||||
|
// loaded any auto-config sources yet.
|
||||||
|
a.config = config
|
||||||
|
|
||||||
|
if flat.logger == nil {
|
||||||
|
logConf := &logging.Config{
|
||||||
|
LogLevel: config.LogLevel,
|
||||||
|
LogJSON: config.LogJSON,
|
||||||
|
Name: logging.Agent,
|
||||||
|
EnableSyslog: config.EnableSyslog,
|
||||||
|
SyslogFacility: config.SyslogFacility,
|
||||||
|
LogFilePath: config.LogFile,
|
||||||
|
LogRotateDuration: config.LogRotateDuration,
|
||||||
|
LogRotateBytes: config.LogRotateBytes,
|
||||||
|
LogRotateMaxFiles: config.LogRotateMaxFiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, logOutput, err := logging.Setup(logConf, flat.writers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.logger = logger
|
||||||
|
a.LogOutput = logOutput
|
||||||
|
|
||||||
|
grpclog.SetLoggerV2(logging.NewGRPCLogger(logConf, a.logger))
|
||||||
|
}
|
||||||
|
|
||||||
|
memSink, err := lib.InitTelemetry(config.Telemetry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to initialize telemetry: %w", err)
|
||||||
|
}
|
||||||
|
a.MemSink = memSink
|
||||||
|
|
||||||
|
// TODO (autoconf) figure out how to let this setting be pushed down via autoconf
|
||||||
|
// right now it gets defaulted if unset so this check actually doesn't do much
|
||||||
|
// for a normal running agent.
|
||||||
|
if a.config.Datacenter == "" {
|
||||||
|
return nil, fmt.Errorf("Must configure a Datacenter")
|
||||||
|
}
|
||||||
|
if a.config.DataDir == "" && !a.config.DevMode {
|
||||||
|
return nil, fmt.Errorf("Must configure a DataDir")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfigurator, err := tlsutil.NewConfigurator(a.config.ToTLSUtilConfig(), a.logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.tlsConfigurator = tlsConfigurator
|
||||||
|
|
||||||
|
err = a.initializeConnectionPool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to initialize the connection pool: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
a.serviceManager = NewServiceManager(&a)
|
a.serviceManager = NewServiceManager(&a)
|
||||||
|
|
||||||
if err := a.initializeACLs(); err != nil {
|
if err := a.initializeACLs(); err != nil {
|
||||||
|
@ -356,13 +511,76 @@ func New(c *config.RuntimeConfig, logger hclog.InterceptLogger) (*Agent, error)
|
||||||
|
|
||||||
// Retrieve or generate the node ID before setting up the rest of the
|
// Retrieve or generate the node ID before setting up the rest of the
|
||||||
// agent, which depends on it.
|
// agent, which depends on it.
|
||||||
if err := a.setupNodeID(c); err != nil {
|
if err := a.setupNodeID(a.config); err != nil {
|
||||||
return nil, fmt.Errorf("Failed to setup node ID: %v", err)
|
return nil, fmt.Errorf("Failed to setup node ID: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
acOpts := []autoconf.Option{
|
||||||
|
autoconf.WithDirectRPC(a.connPool),
|
||||||
|
autoconf.WithTLSConfigurator(a.tlsConfigurator),
|
||||||
|
autoconf.WithBuilderOpts(flat.builderOpts),
|
||||||
|
autoconf.WithLogger(a.logger),
|
||||||
|
autoconf.WithOverrides(flat.overrides...),
|
||||||
|
}
|
||||||
|
ac, err := autoconf.New(acOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.autoConf = ac
|
||||||
|
|
||||||
return &a, nil
|
return &a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLogger retrieves the agents logger
|
||||||
|
// TODO make export the logger field and get rid of this method
|
||||||
|
// This is here for now to simplify the work I am doing and make
|
||||||
|
// reviewing the final PR easier.
|
||||||
|
func (a *Agent) GetLogger() hclog.InterceptLogger {
|
||||||
|
return a.logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig retrieves the agents config
|
||||||
|
// TODO make export the config field and get rid of this method
|
||||||
|
// This is here for now to simplify the work I am doing and make
|
||||||
|
// reviewing the final PR easier.
|
||||||
|
func (a *Agent) GetConfig() *config.RuntimeConfig {
|
||||||
|
a.stateLock.Lock()
|
||||||
|
defer a.stateLock.Unlock()
|
||||||
|
return a.config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) initializeConnectionPool() error {
|
||||||
|
var rpcSrcAddr *net.TCPAddr
|
||||||
|
if !ipaddr.IsAny(a.config.RPCBindAddr) {
|
||||||
|
rpcSrcAddr = &net.TCPAddr{IP: a.config.RPCBindAddr.IP}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have a log output for the connection pool.
|
||||||
|
logOutput := a.LogOutput
|
||||||
|
if logOutput == nil {
|
||||||
|
logOutput = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
pool := &pool.ConnPool{
|
||||||
|
Server: a.config.ServerMode,
|
||||||
|
SrcAddr: rpcSrcAddr,
|
||||||
|
LogOutput: logOutput,
|
||||||
|
TLSConfigurator: a.tlsConfigurator,
|
||||||
|
Datacenter: a.config.Datacenter,
|
||||||
|
}
|
||||||
|
if a.config.ServerMode {
|
||||||
|
pool.MaxTime = 2 * time.Minute
|
||||||
|
pool.MaxStreams = 64
|
||||||
|
} else {
|
||||||
|
pool.MaxTime = 127 * time.Second
|
||||||
|
pool.MaxStreams = 32
|
||||||
|
}
|
||||||
|
|
||||||
|
a.connPool = pool
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// LocalConfig takes a config.RuntimeConfig and maps the fields to a local.Config
|
// LocalConfig takes a config.RuntimeConfig and maps the fields to a local.Config
|
||||||
func LocalConfig(cfg *config.RuntimeConfig) local.Config {
|
func LocalConfig(cfg *config.RuntimeConfig) local.Config {
|
||||||
lc := local.Config{
|
lc := local.Config{
|
||||||
|
@ -385,7 +603,23 @@ func (a *Agent) Start() error {
|
||||||
a.stateLock.Lock()
|
a.stateLock.Lock()
|
||||||
defer a.stateLock.Unlock()
|
defer a.stateLock.Unlock()
|
||||||
|
|
||||||
c := a.config
|
// This needs to be done early on as it will potentially alter the configuration
|
||||||
|
// and then how other bits are brought up
|
||||||
|
c, err := a.autoConf.InitialConfiguration(&lib.StopChannelContext{StopCh: a.shutdownCh})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy over the existing node id, this cannot be
|
||||||
|
// changed while running anyways but this prevents
|
||||||
|
// breaking some existing behavior. then overwrite
|
||||||
|
// the configuration
|
||||||
|
c.NodeID = a.config.NodeID
|
||||||
|
a.config = c
|
||||||
|
|
||||||
|
if err := a.tlsConfigurator.Update(a.config.ToTLSUtilConfig()); err != nil {
|
||||||
|
return fmt.Errorf("Failed to load TLS configurations after applying auto-config settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := a.CheckSecurity(c); err != nil {
|
if err := a.CheckSecurity(c); err != nil {
|
||||||
a.logger.Error("Security error while parsing configuration: %#v", err)
|
a.logger.Error("Security error while parsing configuration: %#v", err)
|
||||||
|
@ -438,21 +672,22 @@ func (a *Agent) Start() error {
|
||||||
return fmt.Errorf("failed to start Consul enterprise component: %v", err)
|
return fmt.Errorf("failed to start Consul enterprise component: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfigurator, err := tlsutil.NewConfigurator(c.ToTLSUtilConfig(), a.logger)
|
options := []consul.ConsulOption{
|
||||||
if err != nil {
|
consul.WithLogger(a.logger),
|
||||||
return err
|
consul.WithTokenStore(a.tokens),
|
||||||
|
consul.WithTLSConfigurator(a.tlsConfigurator),
|
||||||
|
consul.WithConnectionPool(a.connPool),
|
||||||
}
|
}
|
||||||
a.tlsConfigurator = tlsConfigurator
|
|
||||||
|
|
||||||
// Setup either the client or the server.
|
// Setup either the client or the server.
|
||||||
if c.ServerMode {
|
if c.ServerMode {
|
||||||
server, err := consul.NewServerLogger(consulCfg, a.logger, a.tokens, a.tlsConfigurator)
|
server, err := consul.NewServerWithOptions(consulCfg, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to start Consul server: %v", err)
|
return fmt.Errorf("Failed to start Consul server: %v", err)
|
||||||
}
|
}
|
||||||
a.delegate = server
|
a.delegate = server
|
||||||
} else {
|
} else {
|
||||||
client, err := consul.NewClientLogger(consulCfg, a.logger, a.tlsConfigurator)
|
client, err := consul.NewClientWithOptions(consulCfg, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to start Consul client: %v", err)
|
return fmt.Errorf("Failed to start Consul client: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1873,12 +2108,6 @@ func (a *Agent) ShutdownEndpoints() {
|
||||||
a.logger.Info("Endpoints down")
|
a.logger.Info("Endpoints down")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadCh is used to return a channel that can be
|
|
||||||
// used for triggering reloads and returning a response.
|
|
||||||
func (a *Agent) ReloadCh() chan chan error {
|
|
||||||
return a.reloadCh
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetryJoinCh is a channel that transports errors
|
// RetryJoinCh is a channel that transports errors
|
||||||
// from the retry join process.
|
// from the retry join process.
|
||||||
func (a *Agent) RetryJoinCh() <-chan error {
|
func (a *Agent) RetryJoinCh() <-chan error {
|
||||||
|
@ -4052,14 +4281,40 @@ func (a *Agent) loadLimits(conf *config.RuntimeConfig) {
|
||||||
a.config.RPCMaxBurst = conf.RPCMaxBurst
|
a.config.RPCMaxBurst = conf.RPCMaxBurst
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadConfig will atomically reload all configs from the given newCfg,
|
// ReloadConfig will atomically reload all configuration, including
|
||||||
// including all services, checks, tokens, metadata, dnsServer configs, etc.
|
// all services, checks, tokens, metadata, dnsServer configs, etc.
|
||||||
// It will also reload all ongoing watches.
|
// It will also reload all ongoing watches.
|
||||||
func (a *Agent) ReloadConfig(newCfg *config.RuntimeConfig) error {
|
func (a *Agent) ReloadConfig() error {
|
||||||
|
newCfg, err := a.autoConf.ReadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy over the existing node id, this cannot be
|
||||||
|
// changed while running anyways but this prevents
|
||||||
|
// breaking some existing behavior.
|
||||||
|
newCfg.NodeID = a.config.NodeID
|
||||||
|
|
||||||
|
return a.reloadConfigInternal(newCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadConfigInternal is mainly needed for some unit tests. Instead of parsing
|
||||||
|
// the configuration using CLI flags and on disk config, this just takes a
|
||||||
|
// runtime configuration and applies it.
|
||||||
|
func (a *Agent) reloadConfigInternal(newCfg *config.RuntimeConfig) error {
|
||||||
if err := a.CheckSecurity(newCfg); err != nil {
|
if err := a.CheckSecurity(newCfg); err != nil {
|
||||||
a.logger.Error("Security error while reloading configuration: %#v", err)
|
a.logger.Error("Security error while reloading configuration: %#v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change the log level and update it
|
||||||
|
if logging.ValidateLogLevel(newCfg.LogLevel) {
|
||||||
|
a.logger.SetLevel(logging.LevelFromString(newCfg.LogLevel))
|
||||||
|
} else {
|
||||||
|
a.logger.Warn("Invalid log level in new configuration", "level", newCfg.LogLevel)
|
||||||
|
newCfg.LogLevel = a.config.LogLevel
|
||||||
|
}
|
||||||
|
|
||||||
// Bulk update the services and checks
|
// Bulk update the services and checks
|
||||||
a.PauseSync()
|
a.PauseSync()
|
||||||
defer a.ResumeSync()
|
defer a.ResumeSync()
|
||||||
|
|
|
@ -154,21 +154,7 @@ func (s *HTTPServer) AgentReload(resp http.ResponseWriter, req *http.Request) (i
|
||||||
return nil, acl.ErrPermissionDenied
|
return nil, acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger the reload
|
return nil, s.agent.ReloadConfig()
|
||||||
errCh := make(chan error)
|
|
||||||
select {
|
|
||||||
case <-s.agent.shutdownCh:
|
|
||||||
return nil, fmt.Errorf("Agent was shutdown before reload could be completed")
|
|
||||||
case s.agent.reloadCh <- errCh:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the result of the reload, or for the agent to shutdown
|
|
||||||
select {
|
|
||||||
case <-s.agent.shutdownCh:
|
|
||||||
return nil, fmt.Errorf("Agent was shutdown before reload could be completed")
|
|
||||||
case err := <-errCh:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAgentService(s *structs.NodeService) api.AgentService {
|
func buildAgentService(s *structs.NodeService) api.AgentService {
|
||||||
|
|
|
@ -501,7 +501,7 @@ func TestAgent_Service(t *testing.T) {
|
||||||
updateFunc: func() {
|
updateFunc: func() {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
// Reload
|
// Reload
|
||||||
require.NoError(t, a.ReloadConfig(a.Config))
|
require.NoError(t, a.reloadConfigInternal(a.Config))
|
||||||
},
|
},
|
||||||
// Should eventually timeout since there is no actual change
|
// Should eventually timeout since there is no actual change
|
||||||
wantWait: 200 * time.Millisecond,
|
wantWait: 200 * time.Millisecond,
|
||||||
|
@ -519,7 +519,7 @@ func TestAgent_Service(t *testing.T) {
|
||||||
// Reload
|
// Reload
|
||||||
newConfig := *a.Config
|
newConfig := *a.Config
|
||||||
newConfig.Services = append(newConfig.Services, &updatedProxy)
|
newConfig.Services = append(newConfig.Services, &updatedProxy)
|
||||||
require.NoError(t, a.ReloadConfig(&newConfig))
|
require.NoError(t, a.reloadConfigInternal(&newConfig))
|
||||||
},
|
},
|
||||||
wantWait: 100 * time.Millisecond,
|
wantWait: 100 * time.Millisecond,
|
||||||
wantCode: 200,
|
wantCode: 200,
|
||||||
|
@ -1352,7 +1352,7 @@ func TestAgent_Reload(t *testing.T) {
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := a.ReloadConfig(cfg2); err != nil {
|
if err := a.reloadConfigInternal(cfg2); err != nil {
|
||||||
t.Fatalf("got error %v want nil", err)
|
t.Fatalf("got error %v want nil", err)
|
||||||
}
|
}
|
||||||
if a.State.Service(structs.NewServiceID("redis-reloaded", nil)) == nil {
|
if a.State.Service(structs.NewServiceID("redis-reloaded", nil)) == nil {
|
||||||
|
@ -1505,7 +1505,7 @@ func TestAgent_ReloadDoesNotTriggerWatch(t *testing.T) {
|
||||||
// We check that reload does not go to critical
|
// We check that reload does not go to critical
|
||||||
ensureNothingCritical(r, "red-is-dead")
|
ensureNothingCritical(r, "red-is-dead")
|
||||||
|
|
||||||
if err := a.ReloadConfig(cfg2); err != nil {
|
if err := a.reloadConfigInternal(cfg2); err != nil {
|
||||||
t.Fatalf("got error %v want nil", err)
|
t.Fatalf("got error %v want nil", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5194,7 +5194,11 @@ func TestAgentConnectCALeafCert_good(t *testing.T) {
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
a := NewTestAgent(t, "")
|
a := StartTestAgent(t, TestAgent{Overrides: `
|
||||||
|
connect {
|
||||||
|
test_ca_leaf_root_change_spread = "1ns"
|
||||||
|
}
|
||||||
|
`})
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||||
testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil)
|
testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil)
|
||||||
|
@ -5297,7 +5301,11 @@ func TestAgentConnectCALeafCert_goodNotLocal(t *testing.T) {
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
a := NewTestAgent(t, "")
|
a := StartTestAgent(t, TestAgent{Overrides: `
|
||||||
|
connect {
|
||||||
|
test_ca_leaf_root_change_spread = "1ns"
|
||||||
|
}
|
||||||
|
`})
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||||
testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil)
|
testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil)
|
||||||
|
@ -5421,6 +5429,10 @@ func TestAgentConnectCALeafCert_secondaryDC_good(t *testing.T) {
|
||||||
a1 := StartTestAgent(t, TestAgent{Name: "dc1", HCL: `
|
a1 := StartTestAgent(t, TestAgent{Name: "dc1", HCL: `
|
||||||
datacenter = "dc1"
|
datacenter = "dc1"
|
||||||
primary_datacenter = "dc1"
|
primary_datacenter = "dc1"
|
||||||
|
`, Overrides: `
|
||||||
|
connect {
|
||||||
|
test_ca_leaf_root_change_spread = "1ns"
|
||||||
|
}
|
||||||
`})
|
`})
|
||||||
defer a1.Shutdown()
|
defer a1.Shutdown()
|
||||||
testrpc.WaitForTestAgent(t, a1.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a1.RPC, "dc1")
|
||||||
|
@ -5428,6 +5440,10 @@ func TestAgentConnectCALeafCert_secondaryDC_good(t *testing.T) {
|
||||||
a2 := StartTestAgent(t, TestAgent{Name: "dc2", HCL: `
|
a2 := StartTestAgent(t, TestAgent{Name: "dc2", HCL: `
|
||||||
datacenter = "dc2"
|
datacenter = "dc2"
|
||||||
primary_datacenter = "dc1"
|
primary_datacenter = "dc1"
|
||||||
|
`, Overrides: `
|
||||||
|
connect {
|
||||||
|
test_ca_leaf_root_change_spread = "1ns"
|
||||||
|
}
|
||||||
`})
|
`})
|
||||||
defer a2.Shutdown()
|
defer a2.Shutdown()
|
||||||
testrpc.WaitForTestAgent(t, a2.RPC, "dc2")
|
testrpc.WaitForTestAgent(t, a2.RPC, "dc2")
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -26,6 +27,7 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/hashicorp/consul/internal/go-sso/oidcauth/oidcauthtest"
|
||||||
"github.com/hashicorp/consul/ipaddr"
|
"github.com/hashicorp/consul/ipaddr"
|
||||||
"github.com/hashicorp/consul/sdk/freeport"
|
"github.com/hashicorp/consul/sdk/freeport"
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
|
@ -36,6 +38,7 @@ import (
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getService(a *TestAgent, id string) *structs.NodeService {
|
func getService(a *TestAgent, id string) *structs.NodeService {
|
||||||
|
@ -338,7 +341,7 @@ func TestAgent_makeNodeID(t *testing.T) {
|
||||||
|
|
||||||
// Turn on host-based IDs and try again. We should get the same ID
|
// Turn on host-based IDs and try again. We should get the same ID
|
||||||
// each time (and a different one from the random one above).
|
// each time (and a different one from the random one above).
|
||||||
a.Config.DisableHostNodeID = false
|
a.GetConfig().DisableHostNodeID = false
|
||||||
id, err = a.makeNodeID()
|
id, err = a.makeNodeID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
|
@ -2830,10 +2833,10 @@ func TestAgent_Service_MaintenanceMode(t *testing.T) {
|
||||||
|
|
||||||
func TestAgent_Service_Reap(t *testing.T) {
|
func TestAgent_Service_Reap(t *testing.T) {
|
||||||
// t.Parallel() // timing test. no parallel
|
// t.Parallel() // timing test. no parallel
|
||||||
a := NewTestAgent(t, `
|
a := StartTestAgent(t, TestAgent{Overrides: `
|
||||||
check_reap_interval = "50ms"
|
check_reap_interval = "50ms"
|
||||||
check_deregister_interval_min = "0s"
|
check_deregister_interval_min = "0s"
|
||||||
`)
|
`})
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
@ -2885,10 +2888,10 @@ func TestAgent_Service_Reap(t *testing.T) {
|
||||||
|
|
||||||
func TestAgent_Service_NoReap(t *testing.T) {
|
func TestAgent_Service_NoReap(t *testing.T) {
|
||||||
// t.Parallel() // timing test. no parallel
|
// t.Parallel() // timing test. no parallel
|
||||||
a := NewTestAgent(t, `
|
a := StartTestAgent(t, TestAgent{Overrides: `
|
||||||
check_reap_interval = "50ms"
|
check_reap_interval = "50ms"
|
||||||
check_deregister_interval_min = "0s"
|
check_deregister_interval_min = "0s"
|
||||||
`)
|
`})
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
|
|
||||||
svc := &structs.NodeService{
|
svc := &structs.NodeService{
|
||||||
|
@ -3574,7 +3577,7 @@ func TestAgent_ReloadConfigOutgoingRPCConfig(t *testing.T) {
|
||||||
verify_server_hostname = true
|
verify_server_hostname = true
|
||||||
`
|
`
|
||||||
c := TestConfig(testutil.Logger(t), config.Source{Name: t.Name(), Format: "hcl", Data: hcl})
|
c := TestConfig(testutil.Logger(t), config.Source{Name: t.Name(), Format: "hcl", Data: hcl})
|
||||||
require.NoError(t, a.ReloadConfig(c))
|
require.NoError(t, a.reloadConfigInternal(c))
|
||||||
tlsConf = a.tlsConfigurator.OutgoingRPCConfig()
|
tlsConf = a.tlsConfigurator.OutgoingRPCConfig()
|
||||||
require.False(t, tlsConf.InsecureSkipVerify)
|
require.False(t, tlsConf.InsecureSkipVerify)
|
||||||
require.Len(t, tlsConf.RootCAs.Subjects(), 2)
|
require.Len(t, tlsConf.RootCAs.Subjects(), 2)
|
||||||
|
@ -3604,7 +3607,7 @@ func TestAgent_ReloadConfigAndKeepChecksStatus(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c := TestConfig(testutil.Logger(t), config.Source{Name: t.Name(), Format: "hcl", Data: hcl})
|
c := TestConfig(testutil.Logger(t), config.Source{Name: t.Name(), Format: "hcl", Data: hcl})
|
||||||
require.NoError(t, a.ReloadConfig(c))
|
require.NoError(t, a.reloadConfigInternal(c))
|
||||||
// After reload, should be passing directly (no critical state)
|
// After reload, should be passing directly (no critical state)
|
||||||
for id, check := range a.State.Checks(nil) {
|
for id, check := range a.State.Checks(nil) {
|
||||||
require.Equal(t, "passing", check.Status, "check %q is wrong", id)
|
require.Equal(t, "passing", check.Status, "check %q is wrong", id)
|
||||||
|
@ -3643,7 +3646,7 @@ func TestAgent_ReloadConfigIncomingRPCConfig(t *testing.T) {
|
||||||
verify_server_hostname = true
|
verify_server_hostname = true
|
||||||
`
|
`
|
||||||
c := TestConfig(testutil.Logger(t), config.Source{Name: t.Name(), Format: "hcl", Data: hcl})
|
c := TestConfig(testutil.Logger(t), config.Source{Name: t.Name(), Format: "hcl", Data: hcl})
|
||||||
require.NoError(t, a.ReloadConfig(c))
|
require.NoError(t, a.reloadConfigInternal(c))
|
||||||
tlsConf, err = tlsConf.GetConfigForClient(nil)
|
tlsConf, err = tlsConf.GetConfigForClient(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, tlsConf.InsecureSkipVerify)
|
require.False(t, tlsConf.InsecureSkipVerify)
|
||||||
|
@ -3672,7 +3675,7 @@ func TestAgent_ReloadConfigTLSConfigFailure(t *testing.T) {
|
||||||
verify_incoming = true
|
verify_incoming = true
|
||||||
`
|
`
|
||||||
c := TestConfig(testutil.Logger(t), config.Source{Name: t.Name(), Format: "hcl", Data: hcl})
|
c := TestConfig(testutil.Logger(t), config.Source{Name: t.Name(), Format: "hcl", Data: hcl})
|
||||||
require.Error(t, a.ReloadConfig(c))
|
require.Error(t, a.reloadConfigInternal(c))
|
||||||
tlsConf, err := tlsConf.GetConfigForClient(nil)
|
tlsConf, err := tlsConf.GetConfigForClient(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tls.NoClientCert, tlsConf.ClientAuth)
|
require.Equal(t, tls.NoClientCert, tlsConf.ClientAuth)
|
||||||
|
@ -4546,3 +4549,109 @@ func TestAgent_JoinWAN_viaMeshGateway(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAutoConfig_Integration(t *testing.T) {
|
||||||
|
// eventually this test should really live with integration tests
|
||||||
|
// the goal here is to have one test server and another test client
|
||||||
|
// spin up both agents and allow the server to authorize the auto config
|
||||||
|
// request and then see the client joined
|
||||||
|
|
||||||
|
cfgDir := testutil.TempDir(t, "auto-config")
|
||||||
|
|
||||||
|
// write some test TLS certificates out to the cfg dir
|
||||||
|
cert, key, cacert, err := testTLSCertificates("server.dc1.consul")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
certFile := filepath.Join(cfgDir, "cert.pem")
|
||||||
|
caFile := filepath.Join(cfgDir, "cacert.pem")
|
||||||
|
keyFile := filepath.Join(cfgDir, "key.pem")
|
||||||
|
|
||||||
|
require.NoError(t, ioutil.WriteFile(certFile, []byte(cert), 0600))
|
||||||
|
require.NoError(t, ioutil.WriteFile(caFile, []byte(cacert), 0600))
|
||||||
|
require.NoError(t, ioutil.WriteFile(keyFile, []byte(key), 0600))
|
||||||
|
|
||||||
|
// generate a gossip key
|
||||||
|
gossipKey := make([]byte, 32)
|
||||||
|
n, err := rand.Read(gossipKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 32, n)
|
||||||
|
gossipKeyEncoded := base64.StdEncoding.EncodeToString(gossipKey)
|
||||||
|
|
||||||
|
// generate the JWT signing keys
|
||||||
|
pub, priv, err := oidcauthtest.GenerateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hclConfig := TestACLConfigWithParams(nil) + `
|
||||||
|
encrypt = "` + gossipKeyEncoded + `"
|
||||||
|
encrypt_verify_incoming = true
|
||||||
|
encrypt_verify_outgoing = true
|
||||||
|
verify_incoming = true
|
||||||
|
verify_outgoing = true
|
||||||
|
verify_server_hostname = true
|
||||||
|
ca_file = "` + caFile + `"
|
||||||
|
cert_file = "` + certFile + `"
|
||||||
|
key_file = "` + keyFile + `"
|
||||||
|
connect { enabled = true }
|
||||||
|
auto_encrypt { allow_tls = true }
|
||||||
|
auto_config {
|
||||||
|
authorizer {
|
||||||
|
enabled = true
|
||||||
|
claim_mappings = {
|
||||||
|
consul_node_name = "node"
|
||||||
|
}
|
||||||
|
claim_assertions = [
|
||||||
|
"value.node == \"${node}\""
|
||||||
|
]
|
||||||
|
bound_issuer = "consul"
|
||||||
|
bound_audiences = [
|
||||||
|
"consul"
|
||||||
|
]
|
||||||
|
jwt_validation_pub_keys = ["` + strings.ReplaceAll(pub, "\n", "\\n") + `"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
srv := StartTestAgent(t, TestAgent{Name: "TestAgent-Server", HCL: hclConfig})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken))
|
||||||
|
|
||||||
|
// sign a JWT token
|
||||||
|
now := time.Now()
|
||||||
|
token, err := oidcauthtest.SignJWT(priv, jwt.Claims{
|
||||||
|
Subject: "consul",
|
||||||
|
Issuer: "consul",
|
||||||
|
Audience: jwt.Audience{"consul"},
|
||||||
|
NotBefore: jwt.NewNumericDate(now.Add(-1 * time.Second)),
|
||||||
|
Expiry: jwt.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, map[string]interface{}{
|
||||||
|
"consul_node_name": "test-client",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
client := StartTestAgent(t, TestAgent{Name: "test-client", HCL: TestACLConfigWithParams(nil) + `
|
||||||
|
bootstrap = false
|
||||||
|
server = false
|
||||||
|
ca_file = "` + caFile + `"
|
||||||
|
verify_outgoing = true
|
||||||
|
verify_server_hostname = true
|
||||||
|
node_name = "test-client"
|
||||||
|
ports {
|
||||||
|
server = ` + strconv.Itoa(srv.Config.RPCBindAddr.Port) + `
|
||||||
|
}
|
||||||
|
auto_config {
|
||||||
|
enabled = true
|
||||||
|
intro_token = "` + token + `"
|
||||||
|
server_addresses = ["` + srv.Config.RPCBindAddr.String() + `"]
|
||||||
|
}`})
|
||||||
|
|
||||||
|
defer client.Shutdown()
|
||||||
|
|
||||||
|
// when this is successful we managed to get the gossip key and serf addresses to bind to
|
||||||
|
// and then connect. Additionally we would have to have certificates or else the
|
||||||
|
// verify_incoming config on the server would not let it work.
|
||||||
|
testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken))
|
||||||
|
|
||||||
|
// spot check that we now have an ACL token
|
||||||
|
require.NotEmpty(t, client.tokens.AgentToken())
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,480 @@
|
||||||
|
package autoconf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/agentpb"
|
||||||
|
"github.com/hashicorp/consul/agent/config"
|
||||||
|
"github.com/hashicorp/consul/lib"
|
||||||
|
"github.com/hashicorp/consul/logging"
|
||||||
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
|
"github.com/hashicorp/go-discover"
|
||||||
|
discoverk8s "github.com/hashicorp/go-discover/provider/k8s"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// autoConfigFileName is the name of the file that the agent auto-config settings are
|
||||||
|
// stored in within the data directory
|
||||||
|
autoConfigFileName = "auto-config.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DirectRPC is the interface that needs to be satisifed for AutoConfig to be able to perform
|
||||||
|
// direct RPCs against individual servers. This should not use
|
||||||
|
type DirectRPC interface {
|
||||||
|
RPC(dc string, node string, addr net.Addr, method string, args interface{}, reply interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
logger hclog.Logger
|
||||||
|
directRPC DirectRPC
|
||||||
|
tlsConfigurator *tlsutil.Configurator
|
||||||
|
builderOpts config.BuilderOpts
|
||||||
|
waiter *lib.RetryWaiter
|
||||||
|
overrides []config.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option represents one point of configurability for the New function
|
||||||
|
// when creating a new AutoConfig object
|
||||||
|
type Option func(*options)
|
||||||
|
|
||||||
|
// WithLogger will cause the created AutoConfig type to use the provided logger
|
||||||
|
func WithLogger(logger hclog.Logger) Option {
|
||||||
|
return func(opt *options) {
|
||||||
|
opt.logger = logger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTLSConfigurator will cause the created AutoConfig type to use the provided configurator
|
||||||
|
func WithTLSConfigurator(tlsConfigurator *tlsutil.Configurator) Option {
|
||||||
|
return func(opt *options) {
|
||||||
|
opt.tlsConfigurator = tlsConfigurator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConnectionPool will cause the created AutoConfig type to use the provided connection pool
|
||||||
|
func WithDirectRPC(directRPC DirectRPC) Option {
|
||||||
|
return func(opt *options) {
|
||||||
|
opt.directRPC = directRPC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBuilderOpts will cause the created AutoConfig type to use the provided CLI builderOpts
|
||||||
|
func WithBuilderOpts(builderOpts config.BuilderOpts) Option {
|
||||||
|
return func(opt *options) {
|
||||||
|
opt.builderOpts = builderOpts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRetryWaiter will cause the created AutoConfig type to use the provided retry waiter
|
||||||
|
func WithRetryWaiter(waiter *lib.RetryWaiter) Option {
|
||||||
|
return func(opt *options) {
|
||||||
|
opt.waiter = waiter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverrides is used to provide a config source to append to the tail sources
|
||||||
|
// during config building. It is really only useful for testing to tune non-user
|
||||||
|
// configurable tunables to make various tests converge more quickly than they
|
||||||
|
// could otherwise.
|
||||||
|
func WithOverrides(overrides ...config.Source) Option {
|
||||||
|
return func(opt *options) {
|
||||||
|
opt.overrides = overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoConfig is all the state necessary for being able to parse a configuration
|
||||||
|
// as well as perform the necessary RPCs to perform Agent Auto Configuration.
|
||||||
|
//
|
||||||
|
// NOTE: This struct and methods on it are not currently thread/goroutine safe.
|
||||||
|
// However it doesn't spawn any of its own go routines yet and is used in a
|
||||||
|
// synchronous fashion. In the future if either of those two conditions change
|
||||||
|
// then we will need to add some locking here. I am deferring that for now
|
||||||
|
// to help ease the review of this already large PR.
|
||||||
|
type AutoConfig struct {
|
||||||
|
config *config.RuntimeConfig
|
||||||
|
builderOpts config.BuilderOpts
|
||||||
|
logger hclog.Logger
|
||||||
|
directRPC DirectRPC
|
||||||
|
tlsConfigurator *tlsutil.Configurator
|
||||||
|
autoConfigData string
|
||||||
|
waiter *lib.RetryWaiter
|
||||||
|
overrides []config.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenOptions(opts []Option) options {
|
||||||
|
var flat options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&flat)
|
||||||
|
}
|
||||||
|
return flat
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new AutoConfig object for providing automatic
|
||||||
|
// Consul configuration.
|
||||||
|
func New(options ...Option) (*AutoConfig, error) {
|
||||||
|
flat := flattenOptions(options)
|
||||||
|
|
||||||
|
if flat.directRPC == nil {
|
||||||
|
return nil, fmt.Errorf("must provide a direct RPC delegate")
|
||||||
|
}
|
||||||
|
|
||||||
|
if flat.tlsConfigurator == nil {
|
||||||
|
return nil, fmt.Errorf("must provide a TLS configurator")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := flat.logger
|
||||||
|
if logger == nil {
|
||||||
|
logger = hclog.NewNullLogger()
|
||||||
|
} else {
|
||||||
|
logger = logger.Named(logging.AutoConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
waiter := flat.waiter
|
||||||
|
if waiter == nil {
|
||||||
|
waiter = lib.NewRetryWaiter(1, 0, 10*time.Minute, lib.NewJitterRandomStagger(25))
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := &AutoConfig{
|
||||||
|
builderOpts: flat.builderOpts,
|
||||||
|
logger: logger,
|
||||||
|
directRPC: flat.directRPC,
|
||||||
|
tlsConfigurator: flat.tlsConfigurator,
|
||||||
|
waiter: waiter,
|
||||||
|
overrides: flat.overrides,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ac, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig will build the configuration including the extraHead source injected
|
||||||
|
// after all other defaults but before any user supplied configuration and the overrides
|
||||||
|
// source injected as the final source in the configuration parsing chain.
|
||||||
|
func LoadConfig(builderOpts config.BuilderOpts, extraHead config.Source, overrides ...config.Source) (*config.RuntimeConfig, []string, error) {
|
||||||
|
b, err := config.NewBuilder(builderOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if extraHead.Data != "" {
|
||||||
|
b.Head = append(b.Head, extraHead)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(overrides) != 0 {
|
||||||
|
b.Tail = append(b.Tail, overrides...)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := b.BuildAndValidate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, b.Warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConfig will parse the current configuration and inject any
|
||||||
|
// auto-config sources if present into the correct place in the parsing chain.
|
||||||
|
func (ac *AutoConfig) ReadConfig() (*config.RuntimeConfig, error) {
|
||||||
|
src := config.Source{
|
||||||
|
Name: autoConfigFileName,
|
||||||
|
Format: "json",
|
||||||
|
Data: ac.autoConfigData,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, warnings, err := LoadConfig(ac.builderOpts, src, ac.overrides...)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range warnings {
|
||||||
|
ac.logger.Warn(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.config = cfg
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// restorePersistedAutoConfig will attempt to load the persisted auto-config
|
||||||
|
// settings from the data directory. It returns true either when there was an
|
||||||
|
// unrecoverable error or when the configuration was successfully loaded from
|
||||||
|
// disk. Recoverable errors, such as "file not found" are suppressed and this
|
||||||
|
// method will return false for the first boolean.
|
||||||
|
func (ac *AutoConfig) restorePersistedAutoConfig() (bool, error) {
|
||||||
|
if ac.config.DataDir == "" {
|
||||||
|
// no data directory means we don't have anything to potentially load
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(ac.config.DataDir, autoConfigFileName)
|
||||||
|
ac.logger.Debug("attempting to restore any persisted configuration", "path", path)
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
ac.logger.Info("restored persisted configuration", "path", path)
|
||||||
|
ac.autoConfigData = string(content)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return true, fmt.Errorf("failed to load %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore non-existence errors as that is an indicator that we haven't
|
||||||
|
// performed the auto configuration before
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitialConfiguration will perform a one-time RPC request to the configured servers
|
||||||
|
// to retrieve various cluster wide configurations. See the agent/agentpb/auto_config.proto
|
||||||
|
// file for a complete reference of what configurations can be applied in this manner.
|
||||||
|
// The returned configuration will be the new configuration with any auto-config settings
|
||||||
|
// already applied. If AutoConfig is not enabled this method will just parse any
|
||||||
|
// local configuration and return the built runtime configuration.
|
||||||
|
//
|
||||||
|
// The context passed in can be used to cancel the retrieval of the initial configuration
|
||||||
|
// like when receiving a signal during startup.
|
||||||
|
func (ac *AutoConfig) InitialConfiguration(ctx context.Context) (*config.RuntimeConfig, error) {
|
||||||
|
if ac.config == nil {
|
||||||
|
config, err := ac.ReadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ac.config.AutoConfig.Enabled {
|
||||||
|
return ac.config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ready, err := ac.restorePersistedAutoConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ready {
|
||||||
|
if err := ac.getInitialConfiguration(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-read the configuration now that we have our initial auto-config
|
||||||
|
config, err := ac.ReadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.config = config
|
||||||
|
return ac.config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// introToken is responsible for determining the correct intro token to use
|
||||||
|
// when making the initial Cluster.AutoConfig RPC request.
|
||||||
|
func (ac *AutoConfig) introToken() (string, error) {
|
||||||
|
conf := ac.config.AutoConfig
|
||||||
|
// without an intro token or intro token file we cannot do anything
|
||||||
|
if conf.IntroToken == "" && conf.IntroTokenFile == "" {
|
||||||
|
return "", fmt.Errorf("neither intro_token or intro_token_file settings are not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
token := conf.IntroToken
|
||||||
|
if token == "" {
|
||||||
|
// load the intro token from the file
|
||||||
|
content, err := ioutil.ReadFile(conf.IntroTokenFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Failed to read intro token from file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token = string(content)
|
||||||
|
|
||||||
|
if token == "" {
|
||||||
|
return "", fmt.Errorf("intro_token_file did not contain any token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// autoConfigHosts is responsible for taking the list of server addresses and
|
||||||
|
// resolving any go-discover provider invocations. It will then return a list
|
||||||
|
// of hosts. These might be hostnames and is expected that DNS resolution may
|
||||||
|
// be performed after this function runs. Additionally these may contain ports
|
||||||
|
// so SplitHostPort could also be necessary.
|
||||||
|
func (ac *AutoConfig) autoConfigHosts() ([]string, error) {
|
||||||
|
servers := ac.config.AutoConfig.ServerAddresses
|
||||||
|
|
||||||
|
providers := make(map[string]discover.Provider)
|
||||||
|
for k, v := range discover.Providers {
|
||||||
|
providers[k] = v
|
||||||
|
}
|
||||||
|
providers["k8s"] = &discoverk8s.Provider{}
|
||||||
|
|
||||||
|
disco, err := discover.New(
|
||||||
|
discover.WithUserAgent(lib.UserAgent()),
|
||||||
|
discover.WithProviders(providers),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to create go-discover resolver: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrs []string
|
||||||
|
for _, addr := range servers {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(addr, "provider="):
|
||||||
|
resolved, err := disco.Addrs(addr, ac.logger.StandardLogger(&hclog.StandardLoggerOptions{InferLevels: true}))
|
||||||
|
if err != nil {
|
||||||
|
ac.logger.Error("failed to resolve go-discover auto-config servers", "configuration", addr, "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs = append(addrs, resolved...)
|
||||||
|
ac.logger.Debug("discovered auto-config servers", "servers", resolved)
|
||||||
|
default:
|
||||||
|
addrs = append(addrs, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no auto-config server addresses available for use")
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveHost will take a single host string and convert it to a list of TCPAddrs
|
||||||
|
// This will process any port in the input as well as looking up the hostname using
|
||||||
|
// normal DNS resolution.
|
||||||
|
func (ac *AutoConfig) resolveHost(hostPort string) []net.TCPAddr {
|
||||||
|
port := ac.config.ServerPort
|
||||||
|
host, portStr, err := net.SplitHostPort(hostPort)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "missing port in address") {
|
||||||
|
host = hostPort
|
||||||
|
} else {
|
||||||
|
ac.logger.Warn("error splitting host address into IP and port", "address", hostPort, "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
port, err = strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
ac.logger.Warn("Parsed port is not an integer", "port", portStr, "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve the host to a list of IPs
|
||||||
|
ips, err := net.LookupIP(host)
|
||||||
|
if err != nil {
|
||||||
|
ac.logger.Warn("IP resolution failed", "host", host, "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrs []net.TCPAddr
|
||||||
|
for _, ip := range ips {
|
||||||
|
addrs = append(addrs, net.TCPAddr{IP: ip, Port: port})
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// recordAutoConfigReply takes an AutoConfig RPC reply records it with the agent
|
||||||
|
// This will persist the configuration to disk (unless in dev mode running without
|
||||||
|
// a data dir) and will reload the configuration.
|
||||||
|
func (ac *AutoConfig) recordAutoConfigReply(reply *agentpb.AutoConfigResponse) error {
|
||||||
|
conf, err := json.Marshal(translateConfig(reply.Config))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to encode auto-config configuration as JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.autoConfigData = string(conf)
|
||||||
|
|
||||||
|
if ac.config.DataDir == "" {
|
||||||
|
ac.logger.Debug("not persisting auto-config settings because there is no data directory")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(ac.config.DataDir, autoConfigFileName)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(path, conf, 0660)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write auto-config configurations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.logger.Debug("auto-config settings were persisted to disk")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInitialConfigurationOnce will perform full server to TCPAddr resolution and
|
||||||
|
// loop through each host trying to make the Cluster.AutoConfig RPC call. When
|
||||||
|
// successful the bool return will be true and the err value will indicate whether we
|
||||||
|
// successfully recorded the auto config settings (persisted to disk and stored internally
|
||||||
|
// on the AutoConfig object)
|
||||||
|
func (ac *AutoConfig) getInitialConfigurationOnce(ctx context.Context) (bool, error) {
|
||||||
|
token, err := ac.introToken()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := agentpb.AutoConfigRequest{
|
||||||
|
Datacenter: ac.config.Datacenter,
|
||||||
|
Node: ac.config.NodeName,
|
||||||
|
Segment: ac.config.SegmentName,
|
||||||
|
JWT: token,
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply agentpb.AutoConfigResponse
|
||||||
|
|
||||||
|
servers, err := ac.autoConfigHosts()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range servers {
|
||||||
|
// try each IP to see if we can successfully make the request
|
||||||
|
for _, addr := range ac.resolveHost(s) {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return false, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.logger.Debug("Making Cluster.AutoConfig RPC", "addr", addr.String())
|
||||||
|
if err = ac.directRPC.RPC(ac.config.Datacenter, ac.config.NodeName, &addr, "Cluster.AutoConfig", &request, &reply); err != nil {
|
||||||
|
ac.logger.Error("AutoConfig RPC failed", "addr", addr.String(), "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, ac.recordAutoConfigReply(&reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInitialConfiguration implements a loop to retry calls to getInitialConfigurationOnce.
|
||||||
|
// It uses the RetryWaiter on the AutoConfig object to control how often to attempt
|
||||||
|
// the initial configuration process. It is also canceallable by cancelling the provided context.
|
||||||
|
func (ac *AutoConfig) getInitialConfiguration(ctx context.Context) error {
|
||||||
|
// this resets the failures so that we will perform immediate request
|
||||||
|
wait := ac.waiter.Success()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-wait:
|
||||||
|
if done, err := ac.getInitialConfigurationOnce(ctx); done {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wait = ac.waiter.Failed()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,397 @@
|
||||||
|
package autoconf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/agentpb"
|
||||||
|
pbconfig "github.com/hashicorp/consul/agent/agentpb/config"
|
||||||
|
"github.com/hashicorp/consul/agent/config"
|
||||||
|
"github.com/hashicorp/consul/lib"
|
||||||
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockDirectRPC struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDirectRPC) RPC(dc string, node string, addr net.Addr, method string, args interface{}, reply interface{}) error {
|
||||||
|
retValues := m.Called(dc, node, addr, method, args, reply)
|
||||||
|
switch ret := retValues.Get(0).(type) {
|
||||||
|
case error:
|
||||||
|
return ret
|
||||||
|
case func(interface{}):
|
||||||
|
ret(reply)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("This should not happen, update mock direct rpc expectations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
opts []Option
|
||||||
|
err string
|
||||||
|
validate func(t *testing.T, ac *AutoConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]testCase{
|
||||||
|
"no-direct-rpc": {
|
||||||
|
opts: []Option{
|
||||||
|
WithTLSConfigurator(&tlsutil.Configurator{}),
|
||||||
|
},
|
||||||
|
err: "must provide a direct RPC delegate",
|
||||||
|
},
|
||||||
|
"no-tls-configurator": {
|
||||||
|
opts: []Option{
|
||||||
|
WithDirectRPC(&mockDirectRPC{}),
|
||||||
|
},
|
||||||
|
err: "must provide a TLS configurator",
|
||||||
|
},
|
||||||
|
"ok": {
|
||||||
|
opts: []Option{
|
||||||
|
WithTLSConfigurator(&tlsutil.Configurator{}),
|
||||||
|
WithDirectRPC(&mockDirectRPC{}),
|
||||||
|
},
|
||||||
|
validate: func(t *testing.T, ac *AutoConfig) {
|
||||||
|
t.Helper()
|
||||||
|
require.NotNil(t, ac.logger)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tcase := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
ac, err := New(tcase.opts...)
|
||||||
|
if tcase.err != "" {
|
||||||
|
testutil.RequireErrorContains(t, err, tcase.err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ac)
|
||||||
|
if tcase.validate != nil {
|
||||||
|
tcase.validate(t, ac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadConfig(t *testing.T) {
|
||||||
|
// Basically just testing that injection of the extra
|
||||||
|
// source works.
|
||||||
|
devMode := true
|
||||||
|
builderOpts := config.BuilderOpts{
|
||||||
|
// putting this in dev mode so that the config validates
|
||||||
|
// without having to specify a data directory
|
||||||
|
DevMode: &devMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, warnings, err := LoadConfig(builderOpts, config.Source{
|
||||||
|
Name: "test",
|
||||||
|
Format: "hcl",
|
||||||
|
Data: `node_name = "hobbiton"`,
|
||||||
|
},
|
||||||
|
config.Source{
|
||||||
|
Name: "overrides",
|
||||||
|
Format: "json",
|
||||||
|
Data: `{"check_reap_interval": "1ms"}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, warnings)
|
||||||
|
require.NotNil(t, cfg)
|
||||||
|
require.Equal(t, "hobbiton", cfg.NodeName)
|
||||||
|
require.Equal(t, 1*time.Millisecond, cfg.CheckReapInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadConfig(t *testing.T) {
|
||||||
|
// just testing that some auto config source gets injected
|
||||||
|
devMode := true
|
||||||
|
ac := AutoConfig{
|
||||||
|
autoConfigData: `{"node_name": "hobbiton"}`,
|
||||||
|
builderOpts: config.BuilderOpts{
|
||||||
|
// putting this in dev mode so that the config validates
|
||||||
|
// without having to specify a data directory
|
||||||
|
DevMode: &devMode,
|
||||||
|
},
|
||||||
|
logger: testutil.Logger(t),
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := ac.ReadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cfg)
|
||||||
|
require.Equal(t, "hobbiton", cfg.NodeName)
|
||||||
|
require.Same(t, ac.config, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSetupAutoConf(t *testing.T) (string, string, config.BuilderOpts) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// create top level directory to hold both config and data
|
||||||
|
tld := testutil.TempDir(t, "auto-config")
|
||||||
|
t.Cleanup(func() { os.RemoveAll(tld) })
|
||||||
|
|
||||||
|
// create the data directory
|
||||||
|
dataDir := filepath.Join(tld, "data")
|
||||||
|
require.NoError(t, os.Mkdir(dataDir, 0700))
|
||||||
|
|
||||||
|
// create the config directory
|
||||||
|
configDir := filepath.Join(tld, "config")
|
||||||
|
require.NoError(t, os.Mkdir(configDir, 0700))
|
||||||
|
|
||||||
|
builderOpts := config.BuilderOpts{
|
||||||
|
HCL: []string{
|
||||||
|
`data_dir = "` + dataDir + `"`,
|
||||||
|
`datacenter = "dc1"`,
|
||||||
|
`node_name = "autoconf"`,
|
||||||
|
`bind_addr = "127.0.0.1"`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataDir, configDir, builderOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitialConfiguration_disabled(t *testing.T) {
|
||||||
|
dataDir, configDir, builderOpts := testSetupAutoConf(t)
|
||||||
|
|
||||||
|
cfgFile := filepath.Join(configDir, "test.json")
|
||||||
|
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
|
||||||
|
"primary_datacenter": "primary",
|
||||||
|
"auto_config": {"enabled": false}
|
||||||
|
}`), 0600))
|
||||||
|
|
||||||
|
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
|
||||||
|
|
||||||
|
directRPC := mockDirectRPC{}
|
||||||
|
ac, err := New(WithBuilderOpts(builderOpts), WithTLSConfigurator(&tlsutil.Configurator{}), WithDirectRPC(&directRPC))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ac)
|
||||||
|
|
||||||
|
cfg, err := ac.InitialConfiguration(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cfg)
|
||||||
|
require.Equal(t, "primary", cfg.PrimaryDatacenter)
|
||||||
|
require.NoFileExists(t, filepath.Join(dataDir, autoConfigFileName))
|
||||||
|
|
||||||
|
// ensure no RPC was made
|
||||||
|
directRPC.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitialConfiguration_cancelled(t *testing.T) {
|
||||||
|
_, configDir, builderOpts := testSetupAutoConf(t)
|
||||||
|
|
||||||
|
cfgFile := filepath.Join(configDir, "test.json")
|
||||||
|
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
|
||||||
|
"primary_datacenter": "primary",
|
||||||
|
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["127.0.0.1:8300"]}
|
||||||
|
}`), 0600))
|
||||||
|
|
||||||
|
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
|
||||||
|
|
||||||
|
directRPC := mockDirectRPC{}
|
||||||
|
|
||||||
|
expectedRequest := agentpb.AutoConfigRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "autoconf",
|
||||||
|
JWT: "blarg",
|
||||||
|
}
|
||||||
|
|
||||||
|
directRPC.On("RPC", "dc1", "autoconf", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8300}, "Cluster.AutoConfig", &expectedRequest, mock.Anything).Return(fmt.Errorf("injected error")).Times(0)
|
||||||
|
ac, err := New(WithBuilderOpts(builderOpts), WithTLSConfigurator(&tlsutil.Configurator{}), WithDirectRPC(&directRPC))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ac)
|
||||||
|
|
||||||
|
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond))
|
||||||
|
defer cancelFn()
|
||||||
|
|
||||||
|
cfg, err := ac.InitialConfiguration(ctx)
|
||||||
|
testutil.RequireErrorContains(t, err, context.DeadlineExceeded.Error())
|
||||||
|
require.Nil(t, cfg)
|
||||||
|
|
||||||
|
// ensure no RPC was made
|
||||||
|
directRPC.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitialConfiguration_restored(t *testing.T) {
|
||||||
|
dataDir, configDir, builderOpts := testSetupAutoConf(t)
|
||||||
|
|
||||||
|
cfgFile := filepath.Join(configDir, "test.json")
|
||||||
|
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
|
||||||
|
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["127.0.0.1:8300"]}
|
||||||
|
}`), 0600))
|
||||||
|
|
||||||
|
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
|
||||||
|
|
||||||
|
// persist an auto config response to the data dir where it is expected
|
||||||
|
persistedFile := filepath.Join(dataDir, autoConfigFileName)
|
||||||
|
response := &pbconfig.Config{
|
||||||
|
PrimaryDatacenter: "primary",
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(translateConfig(response))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, ioutil.WriteFile(persistedFile, data, 0600))
|
||||||
|
|
||||||
|
directRPC := mockDirectRPC{}
|
||||||
|
|
||||||
|
ac, err := New(WithBuilderOpts(builderOpts), WithTLSConfigurator(&tlsutil.Configurator{}), WithDirectRPC(&directRPC))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ac)
|
||||||
|
|
||||||
|
cfg, err := ac.InitialConfiguration(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cfg)
|
||||||
|
require.Equal(t, "primary", cfg.PrimaryDatacenter)
|
||||||
|
|
||||||
|
// ensure no RPC was made
|
||||||
|
directRPC.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitialConfiguration_success(t *testing.T) {
|
||||||
|
dataDir, configDir, builderOpts := testSetupAutoConf(t)
|
||||||
|
|
||||||
|
cfgFile := filepath.Join(configDir, "test.json")
|
||||||
|
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
|
||||||
|
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["127.0.0.1:8300"]}
|
||||||
|
}`), 0600))
|
||||||
|
|
||||||
|
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
|
||||||
|
|
||||||
|
persistedFile := filepath.Join(dataDir, autoConfigFileName)
|
||||||
|
directRPC := mockDirectRPC{}
|
||||||
|
|
||||||
|
populateResponse := func(val interface{}) {
|
||||||
|
resp, ok := val.(*agentpb.AutoConfigResponse)
|
||||||
|
require.True(t, ok)
|
||||||
|
resp.Config = &pbconfig.Config{
|
||||||
|
PrimaryDatacenter: "primary",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedRequest := agentpb.AutoConfigRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "autoconf",
|
||||||
|
JWT: "blarg",
|
||||||
|
}
|
||||||
|
|
||||||
|
directRPC.On(
|
||||||
|
"RPC",
|
||||||
|
"dc1",
|
||||||
|
"autoconf",
|
||||||
|
&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8300},
|
||||||
|
"Cluster.AutoConfig",
|
||||||
|
&expectedRequest,
|
||||||
|
&agentpb.AutoConfigResponse{}).Return(populateResponse)
|
||||||
|
|
||||||
|
ac, err := New(WithBuilderOpts(builderOpts), WithTLSConfigurator(&tlsutil.Configurator{}), WithDirectRPC(&directRPC))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ac)
|
||||||
|
|
||||||
|
cfg, err := ac.InitialConfiguration(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cfg)
|
||||||
|
require.Equal(t, "primary", cfg.PrimaryDatacenter)
|
||||||
|
|
||||||
|
// the file was written to.
|
||||||
|
require.FileExists(t, persistedFile)
|
||||||
|
|
||||||
|
// ensure no RPC was made
|
||||||
|
directRPC.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitialConfiguration_retries(t *testing.T) {
|
||||||
|
dataDir, configDir, builderOpts := testSetupAutoConf(t)
|
||||||
|
|
||||||
|
cfgFile := filepath.Join(configDir, "test.json")
|
||||||
|
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
|
||||||
|
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["198.18.0.1", "198.18.0.2:8398", "198.18.0.3:8399", "127.0.0.1:1234"]}
|
||||||
|
}`), 0600))
|
||||||
|
|
||||||
|
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
|
||||||
|
|
||||||
|
persistedFile := filepath.Join(dataDir, autoConfigFileName)
|
||||||
|
directRPC := mockDirectRPC{}
|
||||||
|
|
||||||
|
populateResponse := func(val interface{}) {
|
||||||
|
resp, ok := val.(*agentpb.AutoConfigResponse)
|
||||||
|
require.True(t, ok)
|
||||||
|
resp.Config = &pbconfig.Config{
|
||||||
|
PrimaryDatacenter: "primary",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedRequest := agentpb.AutoConfigRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "autoconf",
|
||||||
|
JWT: "blarg",
|
||||||
|
}
|
||||||
|
|
||||||
|
// basically the 198.18.0.* addresses should fail indefinitely. the first time through the
|
||||||
|
// outer loop we inject a failure for the DNS resolution of localhost to 127.0.0.1. Then
|
||||||
|
// the second time through the outer loop we allow the localhost one to work.
|
||||||
|
directRPC.On(
|
||||||
|
"RPC",
|
||||||
|
"dc1",
|
||||||
|
"autoconf",
|
||||||
|
&net.TCPAddr{IP: net.IPv4(198, 18, 0, 1), Port: 8300},
|
||||||
|
"Cluster.AutoConfig",
|
||||||
|
&expectedRequest,
|
||||||
|
&agentpb.AutoConfigResponse{}).Return(fmt.Errorf("injected failure")).Times(0)
|
||||||
|
directRPC.On(
|
||||||
|
"RPC",
|
||||||
|
"dc1",
|
||||||
|
"autoconf",
|
||||||
|
&net.TCPAddr{IP: net.IPv4(198, 18, 0, 2), Port: 8398},
|
||||||
|
"Cluster.AutoConfig",
|
||||||
|
&expectedRequest,
|
||||||
|
&agentpb.AutoConfigResponse{}).Return(fmt.Errorf("injected failure")).Times(0)
|
||||||
|
directRPC.On(
|
||||||
|
"RPC",
|
||||||
|
"dc1",
|
||||||
|
"autoconf",
|
||||||
|
&net.TCPAddr{IP: net.IPv4(198, 18, 0, 3), Port: 8399},
|
||||||
|
"Cluster.AutoConfig",
|
||||||
|
&expectedRequest,
|
||||||
|
&agentpb.AutoConfigResponse{}).Return(fmt.Errorf("injected failure")).Times(0)
|
||||||
|
directRPC.On(
|
||||||
|
"RPC",
|
||||||
|
"dc1",
|
||||||
|
"autoconf",
|
||||||
|
&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1234},
|
||||||
|
"Cluster.AutoConfig",
|
||||||
|
&expectedRequest,
|
||||||
|
&agentpb.AutoConfigResponse{}).Return(fmt.Errorf("injected failure")).Once()
|
||||||
|
directRPC.On(
|
||||||
|
"RPC",
|
||||||
|
"dc1",
|
||||||
|
"autoconf",
|
||||||
|
&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1234},
|
||||||
|
"Cluster.AutoConfig",
|
||||||
|
&expectedRequest,
|
||||||
|
&agentpb.AutoConfigResponse{}).Return(populateResponse)
|
||||||
|
|
||||||
|
waiter := lib.NewRetryWaiter(2, 0, 1*time.Millisecond, nil)
|
||||||
|
ac, err := New(WithBuilderOpts(builderOpts), WithTLSConfigurator(&tlsutil.Configurator{}), WithDirectRPC(&directRPC), WithRetryWaiter(waiter))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ac)
|
||||||
|
|
||||||
|
cfg, err := ac.InitialConfiguration(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cfg)
|
||||||
|
require.Equal(t, "primary", cfg.PrimaryDatacenter)
|
||||||
|
|
||||||
|
// the file was written to.
|
||||||
|
require.FileExists(t, persistedFile)
|
||||||
|
|
||||||
|
// ensure no RPC was made
|
||||||
|
directRPC.AssertExpectations(t)
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package autoconf
|
||||||
|
|
||||||
|
import (
|
||||||
|
pbconfig "github.com/hashicorp/consul/agent/agentpb/config"
|
||||||
|
"github.com/hashicorp/consul/agent/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// translateAgentConfig is meant to take in a agent/agentpb/config.Config type
|
||||||
|
// and craft the corresponding agent/config.Config type. The need for this function
|
||||||
|
// should eventually be removed with the protobuf and normal version converging.
|
||||||
|
// In the meantime, its not desirable to have the flatter Config struct in protobufs
|
||||||
|
// as in the long term we want a configuration with more nested groupings.
|
||||||
|
//
|
||||||
|
// Why is this function not in the agent/agentpb/config package? The answer, that
|
||||||
|
// package cannot import the agent/config package without running into import cycles.
|
||||||
|
func translateConfig(c *pbconfig.Config) *config.Config {
|
||||||
|
out := config.Config{
|
||||||
|
Datacenter: &c.Datacenter,
|
||||||
|
PrimaryDatacenter: &c.PrimaryDatacenter,
|
||||||
|
NodeName: &c.NodeName,
|
||||||
|
SegmentName: &c.SegmentName,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate Auto Encrypt settings
|
||||||
|
if a := c.AutoEncrypt; a != nil {
|
||||||
|
out.AutoEncrypt = config.AutoEncrypt{
|
||||||
|
TLS: &a.TLS,
|
||||||
|
DNSSAN: a.DNSSAN,
|
||||||
|
IPSAN: a.IPSAN,
|
||||||
|
AllowTLS: &a.AllowTLS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate all the ACL settings
|
||||||
|
if a := c.ACL; a != nil {
|
||||||
|
out.ACL = config.ACL{
|
||||||
|
Enabled: &a.Enabled,
|
||||||
|
PolicyTTL: &a.PolicyTTL,
|
||||||
|
RoleTTL: &a.RoleTTL,
|
||||||
|
TokenTTL: &a.TokenTTL,
|
||||||
|
DownPolicy: &a.DownPolicy,
|
||||||
|
DefaultPolicy: &a.DefaultPolicy,
|
||||||
|
EnableKeyListPolicy: &a.EnableKeyListPolicy,
|
||||||
|
DisabledTTL: &a.DisabledTTL,
|
||||||
|
EnableTokenPersistence: &a.EnableTokenPersistence,
|
||||||
|
MSPDisableBootstrap: &a.MSPDisableBootstrap,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t := c.ACL.Tokens; t != nil {
|
||||||
|
var tokens []config.ServiceProviderToken
|
||||||
|
|
||||||
|
// create the slice of msp tokens if any
|
||||||
|
for _, mspToken := range t.ManagedServiceProvider {
|
||||||
|
tokens = append(tokens, config.ServiceProviderToken{
|
||||||
|
AccessorID: &mspToken.AccessorID,
|
||||||
|
SecretID: &mspToken.SecretID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
out.ACL.Tokens = config.Tokens{
|
||||||
|
Master: &t.Master,
|
||||||
|
Replication: &t.Replication,
|
||||||
|
AgentMaster: &t.AgentMaster,
|
||||||
|
Default: &t.Default,
|
||||||
|
Agent: &t.Agent,
|
||||||
|
ManagedServiceProvider: tokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate the Gossip settings
|
||||||
|
if g := c.Gossip; g != nil {
|
||||||
|
out.RetryJoinLAN = g.RetryJoinLAN
|
||||||
|
|
||||||
|
// Translate the Gossip Encryption settings
|
||||||
|
if e := c.Gossip.Encryption; e != nil {
|
||||||
|
out.EncryptKey = &e.Key
|
||||||
|
out.EncryptVerifyIncoming = &e.VerifyIncoming
|
||||||
|
out.EncryptVerifyOutgoing = &e.VerifyOutgoing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate the Generic TLS settings
|
||||||
|
if t := c.TLS; t != nil {
|
||||||
|
out.VerifyOutgoing = &t.VerifyOutgoing
|
||||||
|
out.VerifyServerHostname = &t.VerifyServerHostname
|
||||||
|
out.TLSMinVersion = &t.MinVersion
|
||||||
|
out.TLSCipherSuites = &t.CipherSuites
|
||||||
|
out.TLSPreferServerCipherSuites = &t.PreferServerCipherSuites
|
||||||
|
}
|
||||||
|
|
||||||
|
return &out
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package autoconf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
pbconfig "github.com/hashicorp/consul/agent/agentpb/config"
|
||||||
|
"github.com/hashicorp/consul/agent/config"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stringPointer(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolPointer(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_translateConfig(t *testing.T) {
|
||||||
|
original := pbconfig.Config{
|
||||||
|
Datacenter: "abc",
|
||||||
|
PrimaryDatacenter: "def",
|
||||||
|
NodeName: "ghi",
|
||||||
|
SegmentName: "jkl",
|
||||||
|
ACL: &pbconfig.ACL{
|
||||||
|
Enabled: true,
|
||||||
|
PolicyTTL: "1s",
|
||||||
|
RoleTTL: "2s",
|
||||||
|
TokenTTL: "3s",
|
||||||
|
DownPolicy: "deny",
|
||||||
|
DefaultPolicy: "deny",
|
||||||
|
EnableKeyListPolicy: true,
|
||||||
|
DisabledTTL: "4s",
|
||||||
|
EnableTokenPersistence: true,
|
||||||
|
MSPDisableBootstrap: false,
|
||||||
|
Tokens: &pbconfig.ACLTokens{
|
||||||
|
Master: "99e7e490-6baf-43fc-9010-78b6aa9a6813",
|
||||||
|
Replication: "51308d40-465c-4ac6-a636-7c0747edec89",
|
||||||
|
AgentMaster: "e012e1ea-78a2-41cc-bc8b-231a44196f39",
|
||||||
|
Default: "8781a3f5-de46-4b45-83e1-c92f4cfd0332",
|
||||||
|
Agent: "ddb8f1b0-8a99-4032-b601-87926bce244e",
|
||||||
|
ManagedServiceProvider: []*pbconfig.ACLServiceProviderToken{
|
||||||
|
{
|
||||||
|
AccessorID: "23f37987-7b9e-4e5b-acae-dbc9bc137bae",
|
||||||
|
SecretID: "e28b820a-438e-4e2b-ad24-fe59e6a4914f",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AutoEncrypt: &pbconfig.AutoEncrypt{
|
||||||
|
TLS: true,
|
||||||
|
DNSSAN: []string{"dns"},
|
||||||
|
IPSAN: []string{"198.18.0.1"},
|
||||||
|
AllowTLS: false,
|
||||||
|
},
|
||||||
|
Gossip: &pbconfig.Gossip{
|
||||||
|
RetryJoinLAN: []string{"10.0.0.1"},
|
||||||
|
Encryption: &pbconfig.GossipEncryption{
|
||||||
|
Key: "blarg",
|
||||||
|
VerifyOutgoing: true,
|
||||||
|
VerifyIncoming: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &pbconfig.TLS{
|
||||||
|
VerifyOutgoing: true,
|
||||||
|
VerifyServerHostname: true,
|
||||||
|
CipherSuites: "stuff",
|
||||||
|
MinVersion: "tls13",
|
||||||
|
PreferServerCipherSuites: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &config.Config{
|
||||||
|
Datacenter: stringPointer("abc"),
|
||||||
|
PrimaryDatacenter: stringPointer("def"),
|
||||||
|
NodeName: stringPointer("ghi"),
|
||||||
|
SegmentName: stringPointer("jkl"),
|
||||||
|
RetryJoinLAN: []string{"10.0.0.1"},
|
||||||
|
EncryptKey: stringPointer("blarg"),
|
||||||
|
EncryptVerifyIncoming: boolPointer(true),
|
||||||
|
EncryptVerifyOutgoing: boolPointer(true),
|
||||||
|
VerifyOutgoing: boolPointer(true),
|
||||||
|
VerifyServerHostname: boolPointer(true),
|
||||||
|
TLSCipherSuites: stringPointer("stuff"),
|
||||||
|
TLSMinVersion: stringPointer("tls13"),
|
||||||
|
TLSPreferServerCipherSuites: boolPointer(true),
|
||||||
|
ACL: config.ACL{
|
||||||
|
Enabled: boolPointer(true),
|
||||||
|
PolicyTTL: stringPointer("1s"),
|
||||||
|
RoleTTL: stringPointer("2s"),
|
||||||
|
TokenTTL: stringPointer("3s"),
|
||||||
|
DownPolicy: stringPointer("deny"),
|
||||||
|
DefaultPolicy: stringPointer("deny"),
|
||||||
|
EnableKeyListPolicy: boolPointer(true),
|
||||||
|
DisabledTTL: stringPointer("4s"),
|
||||||
|
EnableTokenPersistence: boolPointer(true),
|
||||||
|
MSPDisableBootstrap: boolPointer(false),
|
||||||
|
Tokens: config.Tokens{
|
||||||
|
Master: stringPointer("99e7e490-6baf-43fc-9010-78b6aa9a6813"),
|
||||||
|
Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"),
|
||||||
|
AgentMaster: stringPointer("e012e1ea-78a2-41cc-bc8b-231a44196f39"),
|
||||||
|
Default: stringPointer("8781a3f5-de46-4b45-83e1-c92f4cfd0332"),
|
||||||
|
Agent: stringPointer("ddb8f1b0-8a99-4032-b601-87926bce244e"),
|
||||||
|
ManagedServiceProvider: []config.ServiceProviderToken{
|
||||||
|
{
|
||||||
|
AccessorID: stringPointer("23f37987-7b9e-4e5b-acae-dbc9bc137bae"),
|
||||||
|
SecretID: stringPointer("e28b820a-438e-4e2b-ad24-fe59e6a4914f"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AutoEncrypt: config.AutoEncrypt{
|
||||||
|
TLS: boolPointer(true),
|
||||||
|
DNSSAN: []string{"dns"},
|
||||||
|
IPSAN: []string{"198.18.0.1"},
|
||||||
|
AllowTLS: boolPointer(false),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := translateConfig(&original)
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
}
|
|
@ -906,6 +906,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
|
||||||
ConnectMeshGatewayWANFederationEnabled: connectMeshGatewayWANFederationEnabled,
|
ConnectMeshGatewayWANFederationEnabled: connectMeshGatewayWANFederationEnabled,
|
||||||
ConnectSidecarMinPort: sidecarMinPort,
|
ConnectSidecarMinPort: sidecarMinPort,
|
||||||
ConnectSidecarMaxPort: sidecarMaxPort,
|
ConnectSidecarMaxPort: sidecarMaxPort,
|
||||||
|
ConnectTestCALeafRootChangeSpread: b.durationVal("connect.test_ca_leaf_root_change_spread", c.Connect.TestCALeafRootChangeSpread),
|
||||||
ExposeMinPort: exposeMinPort,
|
ExposeMinPort: exposeMinPort,
|
||||||
ExposeMaxPort: exposeMaxPort,
|
ExposeMaxPort: exposeMaxPort,
|
||||||
DataDir: b.stringVal(c.DataDir),
|
DataDir: b.stringVal(c.DataDir),
|
||||||
|
|
|
@ -513,6 +513,11 @@ type Connect struct {
|
||||||
CAProvider *string `json:"ca_provider,omitempty" hcl:"ca_provider" mapstructure:"ca_provider"`
|
CAProvider *string `json:"ca_provider,omitempty" hcl:"ca_provider" mapstructure:"ca_provider"`
|
||||||
CAConfig map[string]interface{} `json:"ca_config,omitempty" hcl:"ca_config" mapstructure:"ca_config"`
|
CAConfig map[string]interface{} `json:"ca_config,omitempty" hcl:"ca_config" mapstructure:"ca_config"`
|
||||||
MeshGatewayWANFederationEnabled *bool `json:"enable_mesh_gateway_wan_federation" hcl:"enable_mesh_gateway_wan_federation" mapstructure:"enable_mesh_gateway_wan_federation"`
|
MeshGatewayWANFederationEnabled *bool `json:"enable_mesh_gateway_wan_federation" hcl:"enable_mesh_gateway_wan_federation" mapstructure:"enable_mesh_gateway_wan_federation"`
|
||||||
|
|
||||||
|
// TestCALeafRootChangeSpread controls how long after a CA roots change before new leaft certs will be generated.
|
||||||
|
// This is only tuned in tests, generally set to 1ns to make tests deterministic with when to expect updated leaf
|
||||||
|
// certs by. This configuration is not exposed to users (not documented, and agent/config/default.go will override it)
|
||||||
|
TestCALeafRootChangeSpread *string `json:"test_ca_leaf_root_change_spread,omitempty" hcl:"test_ca_leaf_root_change_spread" mapstructure:"test_ca_leaf_root_change_spread"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SOA is the configuration of SOA for DNS
|
// SOA is the configuration of SOA for DNS
|
||||||
|
|
|
@ -190,6 +190,12 @@ func NonUserSource() Source {
|
||||||
|
|
||||||
# SegmentNameLimit is the maximum segment name length.
|
# SegmentNameLimit is the maximum segment name length.
|
||||||
segment_name_limit = 64
|
segment_name_limit = 64
|
||||||
|
|
||||||
|
connect = {
|
||||||
|
# 0s causes the value to be ignored and operate without capping
|
||||||
|
# the max time before leaf certs can be generated after a roots change.
|
||||||
|
test_ca_leaf_root_change_spread = "0s"
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,13 @@ func NewClient(config *Config) (*Client, error) {
|
||||||
return NewClientLogger(config, nil, c)
|
return NewClientLogger(config, nil, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientLogger(config *Config, logger hclog.InterceptLogger, tlsConfigurator *tlsutil.Configurator) (*Client, error) {
|
func NewClientWithOptions(config *Config, options ...ConsulOption) (*Client, error) {
|
||||||
|
flat := flattenConsulOptions(options)
|
||||||
|
|
||||||
|
logger := flat.logger
|
||||||
|
tlsConfigurator := flat.tlsConfigurator
|
||||||
|
connPool := flat.connPool
|
||||||
|
|
||||||
// Check the protocol version
|
// Check the protocol version
|
||||||
if err := config.CheckProtocolVersion(); err != nil {
|
if err := config.CheckProtocolVersion(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -130,7 +136,8 @@ func NewClientLogger(config *Config, logger hclog.InterceptLogger, tlsConfigurat
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
connPool := &pool.ConnPool{
|
if connPool == nil {
|
||||||
|
connPool = &pool.ConnPool{
|
||||||
Server: false,
|
Server: false,
|
||||||
SrcAddr: config.RPCSrcAddr,
|
SrcAddr: config.RPCSrcAddr,
|
||||||
LogOutput: config.LogOutput,
|
LogOutput: config.LogOutput,
|
||||||
|
@ -139,6 +146,7 @@ func NewClientLogger(config *Config, logger hclog.InterceptLogger, tlsConfigurat
|
||||||
TLSConfigurator: tlsConfigurator,
|
TLSConfigurator: tlsConfigurator,
|
||||||
Datacenter: config.Datacenter,
|
Datacenter: config.Datacenter,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create client
|
// Create client
|
||||||
c := &Client{
|
c := &Client{
|
||||||
|
@ -202,6 +210,10 @@ func NewClientLogger(config *Config, logger hclog.InterceptLogger, tlsConfigurat
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewClientLogger(config *Config, logger hclog.InterceptLogger, tlsConfigurator *tlsutil.Configurator) (*Client, error) {
|
||||||
|
return NewClientWithOptions(config, WithLogger(logger), WithTLSConfigurator(tlsConfigurator))
|
||||||
|
}
|
||||||
|
|
||||||
// Shutdown is used to shutdown the client
|
// Shutdown is used to shutdown the client
|
||||||
func (c *Client) Shutdown() error {
|
func (c *Client) Shutdown() error {
|
||||||
c.logger.Info("shutting down client")
|
c.logger.Info("shutting down client")
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/consul/agent/pool"
|
||||||
|
"github.com/hashicorp/consul/agent/token"
|
||||||
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type consulOptions struct {
|
||||||
|
logger hclog.InterceptLogger
|
||||||
|
tlsConfigurator *tlsutil.Configurator
|
||||||
|
connPool *pool.ConnPool
|
||||||
|
tokens *token.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConsulOption func(*consulOptions)
|
||||||
|
|
||||||
|
func WithLogger(logger hclog.InterceptLogger) ConsulOption {
|
||||||
|
return func(opt *consulOptions) {
|
||||||
|
opt.logger = logger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTLSConfigurator(tlsConfigurator *tlsutil.Configurator) ConsulOption {
|
||||||
|
return func(opt *consulOptions) {
|
||||||
|
opt.tlsConfigurator = tlsConfigurator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithConnectionPool(connPool *pool.ConnPool) ConsulOption {
|
||||||
|
return func(opt *consulOptions) {
|
||||||
|
opt.connPool = connPool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTokenStore(tokens *token.Store) ConsulOption {
|
||||||
|
return func(opt *consulOptions) {
|
||||||
|
opt.tokens = tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenConsulOptions(options []ConsulOption) consulOptions {
|
||||||
|
var flat consulOptions
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(&flat)
|
||||||
|
}
|
||||||
|
return flat
|
||||||
|
}
|
|
@ -322,6 +322,22 @@ func NewServer(config *Config) (*Server, error) {
|
||||||
// NewServerLogger is used to construct a new Consul server from the
|
// NewServerLogger is used to construct a new Consul server from the
|
||||||
// configuration, potentially returning an error
|
// configuration, potentially returning an error
|
||||||
func NewServerLogger(config *Config, logger hclog.InterceptLogger, tokens *token.Store, tlsConfigurator *tlsutil.Configurator) (*Server, error) {
|
func NewServerLogger(config *Config, logger hclog.InterceptLogger, tokens *token.Store, tlsConfigurator *tlsutil.Configurator) (*Server, error) {
|
||||||
|
return NewServerWithOptions(config,
|
||||||
|
WithLogger(logger),
|
||||||
|
WithTokenStore(tokens),
|
||||||
|
WithTLSConfigurator(tlsConfigurator))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerWithOptions is used to construct a new Consul server from the configuration
|
||||||
|
// and extra options, potentially returning an error
|
||||||
|
func NewServerWithOptions(config *Config, options ...ConsulOption) (*Server, error) {
|
||||||
|
flat := flattenConsulOptions(options)
|
||||||
|
|
||||||
|
logger := flat.logger
|
||||||
|
tokens := flat.tokens
|
||||||
|
tlsConfigurator := flat.tlsConfigurator
|
||||||
|
connPool := flat.connPool
|
||||||
|
|
||||||
// Check the protocol version.
|
// Check the protocol version.
|
||||||
if err := config.CheckProtocolVersion(); err != nil {
|
if err := config.CheckProtocolVersion(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -376,7 +392,8 @@ func NewServerLogger(config *Config, logger hclog.InterceptLogger, tokens *token
|
||||||
// Create the shutdown channel - this is closed but never written to.
|
// Create the shutdown channel - this is closed but never written to.
|
||||||
shutdownCh := make(chan struct{})
|
shutdownCh := make(chan struct{})
|
||||||
|
|
||||||
connPool := &pool.ConnPool{
|
if connPool == nil {
|
||||||
|
connPool = &pool.ConnPool{
|
||||||
Server: true,
|
Server: true,
|
||||||
SrcAddr: config.RPCSrcAddr,
|
SrcAddr: config.RPCSrcAddr,
|
||||||
LogOutput: config.LogOutput,
|
LogOutput: config.LogOutput,
|
||||||
|
@ -385,6 +402,7 @@ func NewServerLogger(config *Config, logger hclog.InterceptLogger, tokens *token
|
||||||
TLSConfigurator: tlsConfigurator,
|
TLSConfigurator: tlsConfigurator,
|
||||||
Datacenter: config.Datacenter,
|
Datacenter: config.Datacenter,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serverLogger := logger.NamedIntercept(logging.ConsulServer)
|
serverLogger := logger.NamedIntercept(logging.ConsulServer)
|
||||||
loggers := newLoggerStore(serverLogger)
|
loggers := newLoggerStore(serverLogger)
|
||||||
|
|
|
@ -4051,7 +4051,7 @@ func TestDNS_ServiceLookup_OnlyPassing(t *testing.T) {
|
||||||
|
|
||||||
newCfg := *a.Config
|
newCfg := *a.Config
|
||||||
newCfg.DNSOnlyPassing = false
|
newCfg.DNSOnlyPassing = false
|
||||||
err := a.ReloadConfig(&newCfg)
|
err := a.reloadConfigInternal(&newCfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// only_passing is now false. we should now get two nodes
|
// only_passing is now false. we should now get two nodes
|
||||||
|
@ -6996,7 +6996,7 @@ func TestDNS_ConfigReload(t *testing.T) {
|
||||||
newCfg.DNSSOA.Expire = 30
|
newCfg.DNSSOA.Expire = 30
|
||||||
newCfg.DNSSOA.Minttl = 40
|
newCfg.DNSSOA.Minttl = 40
|
||||||
|
|
||||||
err := a.ReloadConfig(&newCfg)
|
err := a.reloadConfigInternal(&newCfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, s := range a.dnsServers {
|
for _, s := range a.dnsServers {
|
||||||
|
@ -7077,7 +7077,7 @@ func TestDNS_ReloadConfig_DuringQuery(t *testing.T) {
|
||||||
// reload the config halfway through, that should not affect the ongoing query
|
// reload the config halfway through, that should not affect the ongoing query
|
||||||
newCfg := *a.Config
|
newCfg := *a.Config
|
||||||
newCfg.DNSAllowStale = true
|
newCfg.DNSAllowStale = true
|
||||||
a.ReloadConfig(&newCfg)
|
a.reloadConfigInternal(&newCfg)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case in := <-res:
|
case in := <-res:
|
||||||
|
|
|
@ -1447,7 +1447,7 @@ func TestRPC_HTTPSMaxConnsPerClient(t *testing.T) {
|
||||||
// Reload config with higher limit
|
// Reload config with higher limit
|
||||||
newCfg := *a.config
|
newCfg := *a.config
|
||||||
newCfg.HTTPMaxConnsPerClient = 10
|
newCfg.HTTPMaxConnsPerClient = 10
|
||||||
require.NoError(t, a.ReloadConfig(&newCfg))
|
require.NoError(t, a.reloadConfigInternal(&newCfg))
|
||||||
|
|
||||||
// Now another conn should be allowed
|
// Now another conn should be allowed
|
||||||
conn4, err := net.DialTimeout("tcp", addr.String(), time.Second)
|
conn4, err := net.DialTimeout("tcp", addr.String(), time.Second)
|
||||||
|
|
|
@ -1914,7 +1914,7 @@ func TestAgent_AliasCheck(t *testing.T) {
|
||||||
|
|
||||||
func TestAgent_sendCoordinate(t *testing.T) {
|
func TestAgent_sendCoordinate(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
a := agent.NewTestAgent(t, `
|
a := agent.StartTestAgent(t, agent.TestAgent{Overrides: `
|
||||||
sync_coordinate_interval_min = "1ms"
|
sync_coordinate_interval_min = "1ms"
|
||||||
sync_coordinate_rate_target = 10.0
|
sync_coordinate_rate_target = 10.0
|
||||||
consul = {
|
consul = {
|
||||||
|
@ -1924,7 +1924,7 @@ func TestAgent_sendCoordinate(t *testing.T) {
|
||||||
update_max_batches = 1
|
update_max_batches = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`)
|
`})
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
|
|
@ -466,7 +466,11 @@ func (p *ConnPool) getNewConn(dc string, nodeName string, addr net.Addr) (*Conn,
|
||||||
conf.LogOutput = p.LogOutput
|
conf.LogOutput = p.LogOutput
|
||||||
|
|
||||||
// Create a multiplexed session
|
// Create a multiplexed session
|
||||||
session, _ := yamux.Client(conn, conf)
|
session, err := yamux.Client(conn, conf)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("Failed to create yamux client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap the connection
|
// Wrap the connection
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
|
@ -552,7 +556,11 @@ func (p *ConnPool) RPC(
|
||||||
return fmt.Errorf("pool: ConnPool.RPC requires a node name")
|
return fmt.Errorf("pool: ConnPool.RPC requires a node name")
|
||||||
}
|
}
|
||||||
|
|
||||||
if method == "AutoEncrypt.Sign" {
|
// TODO (autoconf) probably will want to have a way to invoke the
|
||||||
|
// secure or insecure variant depending on whether its an ongoing
|
||||||
|
// or first time config request. For now though this is fine until
|
||||||
|
// those ongoing requests are implemented.
|
||||||
|
if method == "AutoEncrypt.Sign" || method == "Cluster.AutoConfig" {
|
||||||
return p.rpcInsecure(dc, nodeName, addr, method, args, reply)
|
return p.rpcInsecure(dc, nodeName, addr, method, args, reply)
|
||||||
} else {
|
} else {
|
||||||
return p.rpc(dc, nodeName, addr, method, args, reply)
|
return p.rpc(dc, nodeName, addr, method, args, reply)
|
||||||
|
|
|
@ -2,6 +2,9 @@ package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -20,6 +23,7 @@ import (
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
uuid "github.com/hashicorp/go-uuid"
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/config"
|
"github.com/hashicorp/consul/agent/config"
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
"github.com/hashicorp/consul/agent/consul"
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
|
@ -27,6 +31,7 @@ import (
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/sdk/freeport"
|
"github.com/hashicorp/consul/sdk/freeport"
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -81,6 +86,10 @@ type TestAgent struct {
|
||||||
// It is valid after Start().
|
// It is valid after Start().
|
||||||
srv *HTTPServer
|
srv *HTTPServer
|
||||||
|
|
||||||
|
// overrides is an hcl config source to use to override otherwise
|
||||||
|
// non-user settable configurations
|
||||||
|
Overrides string
|
||||||
|
|
||||||
// Agent is the embedded consul agent.
|
// Agent is the embedded consul agent.
|
||||||
// It is valid after Start().
|
// It is valid after Start().
|
||||||
*Agent
|
*Agent
|
||||||
|
@ -100,6 +109,7 @@ func NewTestAgent(t *testing.T, hcl string) *TestAgent {
|
||||||
// The caller is responsible for calling Shutdown() to stop the agent and remove
|
// The caller is responsible for calling Shutdown() to stop the agent and remove
|
||||||
// temporary directories.
|
// temporary directories.
|
||||||
func StartTestAgent(t *testing.T, a TestAgent) *TestAgent {
|
func StartTestAgent(t *testing.T, a TestAgent) *TestAgent {
|
||||||
|
t.Helper()
|
||||||
retry.RunWith(retry.ThreeTimes(), t, func(r *retry.R) {
|
retry.RunWith(retry.ThreeTimes(), t, func(r *retry.R) {
|
||||||
if err := a.Start(t); err != nil {
|
if err := a.Start(t); err != nil {
|
||||||
r.Fatal(err)
|
r.Fatal(err)
|
||||||
|
@ -109,9 +119,31 @@ func StartTestAgent(t *testing.T, a TestAgent) *TestAgent {
|
||||||
return &a
|
return &a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigHCL(nodeID string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
bind_addr = "127.0.0.1"
|
||||||
|
advertise_addr = "127.0.0.1"
|
||||||
|
datacenter = "dc1"
|
||||||
|
bootstrap = true
|
||||||
|
server = true
|
||||||
|
node_id = "%[1]s"
|
||||||
|
node_name = "Node-%[1]s"
|
||||||
|
connect {
|
||||||
|
enabled = true
|
||||||
|
ca_config {
|
||||||
|
cluster_id = "%[2]s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
performance {
|
||||||
|
raft_multiplier = 1
|
||||||
|
}`, nodeID, connect.TestClusterID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Start starts a test agent. It returns an error if the agent could not be started.
|
// Start starts a test agent. It returns an error if the agent could not be started.
|
||||||
// If no error is returned, the caller must call Shutdown() when finished.
|
// If no error is returned, the caller must call Shutdown() when finished.
|
||||||
func (a *TestAgent) Start(t *testing.T) (err error) {
|
func (a *TestAgent) Start(t *testing.T) (err error) {
|
||||||
|
t.Helper()
|
||||||
if a.Agent != nil {
|
if a.Agent != nil {
|
||||||
return fmt.Errorf("TestAgent already started")
|
return fmt.Errorf("TestAgent already started")
|
||||||
}
|
}
|
||||||
|
@ -148,6 +180,7 @@ func (a *TestAgent) Start(t *testing.T) (err error) {
|
||||||
// parsing.
|
// parsing.
|
||||||
d = filepath.ToSlash(d)
|
d = filepath.ToSlash(d)
|
||||||
hclDataDir = `data_dir = "` + d + `"`
|
hclDataDir = `data_dir = "` + d + `"`
|
||||||
|
a.DataDir = d
|
||||||
}
|
}
|
||||||
|
|
||||||
logOutput := a.LogOutput
|
logOutput := a.LogOutput
|
||||||
|
@ -164,11 +197,27 @@ func (a *TestAgent) Start(t *testing.T) (err error) {
|
||||||
|
|
||||||
portsConfig, returnPortsFn := randomPortsSource(a.UseTLS)
|
portsConfig, returnPortsFn := randomPortsSource(a.UseTLS)
|
||||||
a.returnPortsFn = returnPortsFn
|
a.returnPortsFn = returnPortsFn
|
||||||
a.Config = TestConfig(logger,
|
|
||||||
|
nodeID := NodeID()
|
||||||
|
|
||||||
|
opts := []AgentOption{
|
||||||
|
WithLogger(logger),
|
||||||
|
WithBuilderOpts(config.BuilderOpts{
|
||||||
|
HCL: []string{
|
||||||
|
TestConfigHCL(nodeID),
|
||||||
portsConfig,
|
portsConfig,
|
||||||
config.Source{Name: name, Format: "hcl", Data: a.HCL},
|
a.HCL,
|
||||||
config.Source{Name: name + ".data_dir", Format: "hcl", Data: hclDataDir},
|
hclDataDir,
|
||||||
)
|
},
|
||||||
|
}),
|
||||||
|
WithOverrides(config.Source{
|
||||||
|
Name: "test-overrides",
|
||||||
|
Format: "hcl",
|
||||||
|
Data: a.Overrides},
|
||||||
|
config.DefaultConsulSource(),
|
||||||
|
config.DevConsulSource(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil && a.returnPortsFn != nil {
|
if err != nil && a.returnPortsFn != nil {
|
||||||
|
@ -180,7 +229,7 @@ func (a *TestAgent) Start(t *testing.T) (err error) {
|
||||||
// write the keyring
|
// write the keyring
|
||||||
if a.Key != "" {
|
if a.Key != "" {
|
||||||
writeKey := func(key, filename string) error {
|
writeKey := func(key, filename string) error {
|
||||||
path := filepath.Join(a.Config.DataDir, filename)
|
path := filepath.Join(a.DataDir, filename)
|
||||||
if err := initKeyring(path, key); err != nil {
|
if err := initKeyring(path, key); err != nil {
|
||||||
cleanupTmpDir()
|
cleanupTmpDir()
|
||||||
return fmt.Errorf("Error creating keyring %s: %s", path, err)
|
return fmt.Errorf("Error creating keyring %s: %s", path, err)
|
||||||
|
@ -197,13 +246,14 @@ func (a *TestAgent) Start(t *testing.T) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
agent, err := New(a.Config, logger)
|
agent, err := New(opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupTmpDir()
|
cleanupTmpDir()
|
||||||
return fmt.Errorf("Error creating agent: %s", err)
|
return fmt.Errorf("Error creating agent: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
agent.LogOutput = logOutput
|
a.Config = agent.GetConfig()
|
||||||
|
|
||||||
agent.MemSink = metrics.NewInmemSink(1*time.Second, time.Minute)
|
agent.MemSink = metrics.NewInmemSink(1*time.Second, time.Minute)
|
||||||
|
|
||||||
id := string(a.Config.NodeID)
|
id := string(a.Config.NodeID)
|
||||||
|
@ -224,6 +274,7 @@ func (a *TestAgent) Start(t *testing.T) (err error) {
|
||||||
if err := a.waitForUp(); err != nil {
|
if err := a.waitForUp(); err != nil {
|
||||||
cleanupTmpDir()
|
cleanupTmpDir()
|
||||||
a.Shutdown()
|
a.Shutdown()
|
||||||
|
t.Logf("Error while waiting for test agent to start: %v", err)
|
||||||
return errwrap.Wrapf(name+": {{err}}", err)
|
return errwrap.Wrapf(name+": {{err}}", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +322,11 @@ func (a *TestAgent) waitForUp() error {
|
||||||
req := httptest.NewRequest("GET", "/v1/agent/self", nil)
|
req := httptest.NewRequest("GET", "/v1/agent/self", nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
_, err := a.httpServers[0].AgentSelf(resp, req)
|
_, err := a.httpServers[0].AgentSelf(resp, req)
|
||||||
if err != nil || resp.Code != 200 {
|
if acl.IsErrPermissionDenied(err) || resp.Code == 403 {
|
||||||
|
// permission denied is enough to show that the client is
|
||||||
|
// connected to the servers as it would get a 503 if
|
||||||
|
// it couldn't connect to them.
|
||||||
|
} else if err != nil && resp.Code != 200 {
|
||||||
retErr = fmt.Errorf("failed OK response: %v", err)
|
retErr = fmt.Errorf("failed OK response: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -372,7 +427,7 @@ func (a *TestAgent) consulConfig() *consul.Config {
|
||||||
// chance of port conflicts for concurrently executed test binaries.
|
// chance of port conflicts for concurrently executed test binaries.
|
||||||
// Instead of relying on one set of ports to be sufficient we retry
|
// Instead of relying on one set of ports to be sufficient we retry
|
||||||
// starting the agent with different ports on port conflict.
|
// starting the agent with different ports on port conflict.
|
||||||
func randomPortsSource(tls bool) (src config.Source, returnPortsFn func()) {
|
func randomPortsSource(tls bool) (data string, returnPortsFn func()) {
|
||||||
ports := freeport.MustTake(7)
|
ports := freeport.MustTake(7)
|
||||||
|
|
||||||
var http, https int
|
var http, https int
|
||||||
|
@ -384,10 +439,7 @@ func randomPortsSource(tls bool) (src config.Source, returnPortsFn func()) {
|
||||||
https = -1
|
https = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.Source{
|
return `
|
||||||
Name: "ports",
|
|
||||||
Format: "hcl",
|
|
||||||
Data: `
|
|
||||||
ports = {
|
ports = {
|
||||||
dns = ` + strconv.Itoa(ports[0]) + `
|
dns = ` + strconv.Itoa(ports[0]) + `
|
||||||
http = ` + strconv.Itoa(http) + `
|
http = ` + strconv.Itoa(http) + `
|
||||||
|
@ -397,8 +449,7 @@ func randomPortsSource(tls bool) (src config.Source, returnPortsFn func()) {
|
||||||
server = ` + strconv.Itoa(ports[5]) + `
|
server = ` + strconv.Itoa(ports[5]) + `
|
||||||
grpc = ` + strconv.Itoa(ports[6]) + `
|
grpc = ` + strconv.Itoa(ports[6]) + `
|
||||||
}
|
}
|
||||||
`,
|
`, func() { freeport.Return(ports) }
|
||||||
}, func() { freeport.Return(ports) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NodeID() string {
|
func NodeID() string {
|
||||||
|
@ -518,34 +569,34 @@ func TestACLConfigNew() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var aclConfigTpl = template.Must(template.New("ACL Config").Parse(`
|
var aclConfigTpl = template.Must(template.New("ACL Config").Parse(`
|
||||||
{{if ne .PrimaryDatacenter ""}}
|
{{- if ne .PrimaryDatacenter "" -}}
|
||||||
primary_datacenter = "{{ .PrimaryDatacenter }}"
|
primary_datacenter = "{{ .PrimaryDatacenter }}"
|
||||||
{{end}}
|
{{end -}}
|
||||||
acl {
|
acl {
|
||||||
enabled = true
|
enabled = true
|
||||||
{{if ne .DefaultPolicy ""}}
|
{{- if ne .DefaultPolicy ""}}
|
||||||
default_policy = "{{ .DefaultPolicy }}"
|
default_policy = "{{ .DefaultPolicy }}"
|
||||||
{{end}}
|
{{- end}}
|
||||||
enable_token_replication = {{printf "%t" .EnableTokenReplication }}
|
enable_token_replication = {{printf "%t" .EnableTokenReplication }}
|
||||||
{{if .HasConfiguredTokens }}
|
{{- if .HasConfiguredTokens}}
|
||||||
tokens {
|
tokens {
|
||||||
{{if ne .MasterToken ""}}
|
{{- if ne .MasterToken ""}}
|
||||||
master = "{{ .MasterToken }}"
|
master = "{{ .MasterToken }}"
|
||||||
{{end}}
|
{{- end}}
|
||||||
{{if ne .AgentToken ""}}
|
{{- if ne .AgentToken ""}}
|
||||||
agent = "{{ .AgentToken }}"
|
agent = "{{ .AgentToken }}"
|
||||||
{{end}}
|
{{- end}}
|
||||||
{{if ne .AgentMasterToken "" }}
|
{{- if ne .AgentMasterToken "" }}
|
||||||
agent_master = "{{ .AgentMasterToken }}"
|
agent_master = "{{ .AgentMasterToken }}"
|
||||||
{{end}}
|
{{- end}}
|
||||||
{{if ne .DefaultToken "" }}
|
{{- if ne .DefaultToken "" }}
|
||||||
default = "{{ .DefaultToken }}"
|
default = "{{ .DefaultToken }}"
|
||||||
{{end}}
|
{{- end}}
|
||||||
{{if ne .ReplicationToken "" }}
|
{{- if ne .ReplicationToken "" }}
|
||||||
replication = "{{ .ReplicationToken }}"
|
replication = "{{ .ReplicationToken }}"
|
||||||
{{end}}
|
{{- end}}
|
||||||
}
|
}
|
||||||
{{end}}
|
{{- end}}
|
||||||
}
|
}
|
||||||
`))
|
`))
|
||||||
|
|
||||||
|
@ -564,3 +615,43 @@ func TestACLConfigWithParams(params *TestACLConfigParams) string {
|
||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testTLSCertificates Generates a TLS CA and server key/cert and returns them
|
||||||
|
// in PEM encoded form.
|
||||||
|
func testTLSCertificates(serverName string) (cert string, key string, cacert string, err error) {
|
||||||
|
// generate CA
|
||||||
|
serial, err := tlsutil.GenerateSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.New(rand.NewSource(99)))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
ca, err := tlsutil.GenerateCA(signer, serial, 365, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate leaf
|
||||||
|
serial, err = tlsutil.GenerateSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, privateKey, err := tlsutil.GenerateCert(
|
||||||
|
signer,
|
||||||
|
ca,
|
||||||
|
serial,
|
||||||
|
"Test Cert Name",
|
||||||
|
365,
|
||||||
|
[]string{serverName},
|
||||||
|
nil,
|
||||||
|
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, privateKey, ca, nil
|
||||||
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ func TestAuthMethodCreateCommand(t *testing.T) {
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||||
client := a.Client()
|
client := a.Client()
|
||||||
|
|
||||||
t.Run("type required", func(t *testing.T) {
|
t.Run("type required", func(t *testing.T) {
|
||||||
|
@ -201,7 +201,7 @@ func TestAuthMethodCreateCommand_JSON(t *testing.T) {
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||||
client := a.Client()
|
client := a.Client()
|
||||||
|
|
||||||
t.Run("type required", func(t *testing.T) {
|
t.Run("type required", func(t *testing.T) {
|
||||||
|
@ -369,7 +369,7 @@ func TestAuthMethodCreateCommand_k8s(t *testing.T) {
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||||
client := a.Client()
|
client := a.Client()
|
||||||
|
|
||||||
t.Run("k8s host required", func(t *testing.T) {
|
t.Run("k8s host required", func(t *testing.T) {
|
||||||
|
@ -512,7 +512,7 @@ func TestAuthMethodCreateCommand_config(t *testing.T) {
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||||
client := a.Client()
|
client := a.Client()
|
||||||
|
|
||||||
checkMethod := func(t *testing.T, methodName string) {
|
checkMethod := func(t *testing.T, methodName string) {
|
||||||
|
|
|
@ -40,7 +40,7 @@ func TestPolicyReadCommand(t *testing.T) {
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
ui := cli.NewMockUi()
|
||||||
cmd := New(ui)
|
cmd := New(ui)
|
||||||
|
@ -86,7 +86,7 @@ func TestPolicyReadCommand_JSON(t *testing.T) {
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
ui := cli.NewMockUi()
|
||||||
cmd := New(ui)
|
cmd := New(ui)
|
||||||
|
|
|
@ -18,9 +18,7 @@ import (
|
||||||
"github.com/hashicorp/consul/service_os"
|
"github.com/hashicorp/consul/service_os"
|
||||||
"github.com/hashicorp/go-checkpoint"
|
"github.com/hashicorp/go-checkpoint"
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
multierror "github.com/hashicorp/go-multierror"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"google.golang.org/grpc/grpclog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(ui cli.Ui, revision, version, versionPre, versionHuman string, shutdownCh <-chan struct{}) *cmd {
|
func New(ui cli.Ui, revision, version, versionPre, versionHuman string, shutdownCh <-chan struct{}) *cmd {
|
||||||
|
@ -80,25 +78,6 @@ func (c *cmd) Run(args []string) int {
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
// readConfig is responsible for setup of our configuration using
|
|
||||||
// the command line and any file configs
|
|
||||||
func (c *cmd) readConfig() *config.RuntimeConfig {
|
|
||||||
b, err := config.NewBuilder(c.flagArgs)
|
|
||||||
if err != nil {
|
|
||||||
c.UI.Error(err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cfg, err := b.BuildAndValidate()
|
|
||||||
if err != nil {
|
|
||||||
c.UI.Error(err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, w := range b.Warnings {
|
|
||||||
c.UI.Warn(w)
|
|
||||||
}
|
|
||||||
return &cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkpointResults is used to handler periodic results from our update checker
|
// checkpointResults is used to handler periodic results from our update checker
|
||||||
func (c *cmd) checkpointResults(results *checkpoint.CheckResponse, err error) {
|
func (c *cmd) checkpointResults(results *checkpoint.CheckResponse, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -185,29 +164,22 @@ func (c *cmd) run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
config := c.readConfig()
|
logGate := logging.GatedWriter{Writer: &cli.UiWriter{Ui: c.UI}}
|
||||||
if config == nil {
|
|
||||||
|
agentOptions := []agent.AgentOption{
|
||||||
|
agent.WithBuilderOpts(c.flagArgs),
|
||||||
|
agent.WithCLI(c.UI),
|
||||||
|
agent.WithLogWriter(&logGate),
|
||||||
|
}
|
||||||
|
|
||||||
|
agent, err := agent.New(agentOptions...)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the log outputs
|
config := agent.GetConfig()
|
||||||
logConfig := &logging.Config{
|
c.logger = agent.GetLogger()
|
||||||
LogLevel: config.LogLevel,
|
|
||||||
LogJSON: config.LogJSON,
|
|
||||||
Name: logging.Agent,
|
|
||||||
EnableSyslog: config.EnableSyslog,
|
|
||||||
SyslogFacility: config.SyslogFacility,
|
|
||||||
LogFilePath: config.LogFile,
|
|
||||||
LogRotateDuration: config.LogRotateDuration,
|
|
||||||
LogRotateBytes: config.LogRotateBytes,
|
|
||||||
LogRotateMaxFiles: config.LogRotateMaxFiles,
|
|
||||||
}
|
|
||||||
logger, logGate, logOutput, ok := logging.Setup(logConfig, c.UI)
|
|
||||||
if !ok {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
c.logger = logger
|
|
||||||
|
|
||||||
//Setup gate to check if we should output CLI information
|
//Setup gate to check if we should output CLI information
|
||||||
cli := GatedUi{
|
cli := GatedUi{
|
||||||
|
@ -215,26 +187,8 @@ func (c *cmd) run(args []string) int {
|
||||||
ui: c.UI,
|
ui: c.UI,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup gRPC logger to use the same output/filtering
|
|
||||||
grpclog.SetLoggerV2(logging.NewGRPCLogger(logConfig, c.logger))
|
|
||||||
|
|
||||||
memSink, err := lib.InitTelemetry(config.Telemetry)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Error(err.Error())
|
|
||||||
logGate.Flush()
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the agent
|
// Create the agent
|
||||||
cli.output("Starting Consul agent...")
|
cli.output("Starting Consul agent...")
|
||||||
agent, err := agent.New(config, c.logger)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Error("Error creating agent", "error", err)
|
|
||||||
logGate.Flush()
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
agent.LogOutput = logOutput
|
|
||||||
agent.MemSink = memSink
|
|
||||||
|
|
||||||
segment := config.SegmentName
|
segment := config.SegmentName
|
||||||
if config.ServerMode {
|
if config.ServerMode {
|
||||||
|
@ -327,13 +281,9 @@ func (c *cmd) run(args []string) int {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var sig os.Signal
|
var sig os.Signal
|
||||||
var reloadErrCh chan error
|
|
||||||
select {
|
select {
|
||||||
case s := <-signalCh:
|
case s := <-signalCh:
|
||||||
sig = s
|
sig = s
|
||||||
case ch := <-agent.ReloadCh():
|
|
||||||
sig = syscall.SIGHUP
|
|
||||||
reloadErrCh = ch
|
|
||||||
case <-service_os.Shutdown_Channel():
|
case <-service_os.Shutdown_Channel():
|
||||||
sig = os.Interrupt
|
sig = os.Interrupt
|
||||||
case <-c.shutdownCh:
|
case <-c.shutdownCh:
|
||||||
|
@ -353,18 +303,11 @@ func (c *cmd) run(args []string) int {
|
||||||
case syscall.SIGHUP:
|
case syscall.SIGHUP:
|
||||||
c.logger.Info("Caught", "signal", sig)
|
c.logger.Info("Caught", "signal", sig)
|
||||||
|
|
||||||
conf, err := c.handleReload(agent, config)
|
err := agent.ReloadConfig()
|
||||||
if conf != nil {
|
|
||||||
config = conf
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("Reload config failed", "error", err)
|
c.logger.Error("Reload config failed", "error", err)
|
||||||
}
|
}
|
||||||
// Send result back if reload was called via HTTP
|
config = agent.GetConfig()
|
||||||
if reloadErrCh != nil {
|
|
||||||
reloadErrCh <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
c.logger.Info("Caught", "signal", sig)
|
c.logger.Info("Caught", "signal", sig)
|
||||||
|
|
||||||
|
@ -400,37 +343,6 @@ func (c *cmd) run(args []string) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleReload is invoked when we should reload our configs, e.g. SIGHUP
|
|
||||||
func (c *cmd) handleReload(agent *agent.Agent, cfg *config.RuntimeConfig) (*config.RuntimeConfig, error) {
|
|
||||||
c.logger.Info("Reloading configuration...")
|
|
||||||
var errs error
|
|
||||||
newCfg := c.readConfig()
|
|
||||||
if newCfg == nil {
|
|
||||||
errs = multierror.Append(errs, fmt.Errorf("Failed to reload configs"))
|
|
||||||
return cfg, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change the log level
|
|
||||||
if logging.ValidateLogLevel(newCfg.LogLevel) {
|
|
||||||
c.logger.SetLevel(logging.LevelFromString(newCfg.LogLevel))
|
|
||||||
} else {
|
|
||||||
errs = multierror.Append(fmt.Errorf(
|
|
||||||
"Invalid log level: %s. Valid log levels are: %v",
|
|
||||||
newCfg.LogLevel, logging.AllowedLogLevels()))
|
|
||||||
|
|
||||||
// Keep the current log level
|
|
||||||
newCfg.LogLevel = cfg.LogLevel
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := agent.ReloadConfig(newCfg); err != nil {
|
|
||||||
errs = multierror.Append(fmt.Errorf(
|
|
||||||
"Failed to reload configs: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return newCfg, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GatedUi) output(s string) {
|
func (g *GatedUi) output(s string) {
|
||||||
if !g.JSONoutput {
|
if !g.JSONoutput {
|
||||||
g.ui.Output(s)
|
g.ui.Output(s)
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/hashicorp/consul/testrpc"
|
"github.com/hashicorp/consul/testrpc"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent"
|
"github.com/hashicorp/consul/agent"
|
||||||
"github.com/hashicorp/consul/agent/config"
|
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
@ -205,67 +204,3 @@ func TestBadDataDirPermissions(t *testing.T) {
|
||||||
t.Fatalf("expected permission denied error, got: %s", out)
|
t.Fatalf("expected permission denied error, got: %s", out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReloadLoggerFail(t *testing.T) {
|
|
||||||
a := agent.NewTestAgent(t, "")
|
|
||||||
defer a.Shutdown()
|
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
|
||||||
cmd := New(ui, "", "", "", "", nil)
|
|
||||||
|
|
||||||
bindAddr := a.Config.BindAddr.String()
|
|
||||||
cmd.flagArgs.Config.BindAddr = &bindAddr
|
|
||||||
cmd.flagArgs.Config.DataDir = &a.Config.DataDir
|
|
||||||
|
|
||||||
cmd.logger = testutil.Logger(t)
|
|
||||||
|
|
||||||
newLogLevel := "BLAH"
|
|
||||||
cmd.flagArgs.Config.LogLevel = &newLogLevel
|
|
||||||
|
|
||||||
oldCfg := config.RuntimeConfig{
|
|
||||||
LogLevel: "INFO",
|
|
||||||
}
|
|
||||||
cfg, err := cmd.handleReload(a.Agent, &oldCfg)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Should fail with bad log level")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(err.Error(), "Invalid log level") {
|
|
||||||
t.Fatalf("expected invalid log level error, got: %s", err)
|
|
||||||
}
|
|
||||||
if cfg.LogLevel != "INFO" {
|
|
||||||
t.Fatalf("expected log level to stay the same, got: %s", cfg.LogLevel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReloadLoggerSuccess(t *testing.T) {
|
|
||||||
a := agent.NewTestAgent(t, "")
|
|
||||||
defer a.Shutdown()
|
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
|
||||||
cmd := New(ui, "", "", "", "", nil)
|
|
||||||
|
|
||||||
bindAddr := a.Config.BindAddr.String()
|
|
||||||
cmd.flagArgs.Config.BindAddr = &bindAddr
|
|
||||||
cmd.flagArgs.Config.DataDir = &a.Config.DataDir
|
|
||||||
|
|
||||||
cmd.logger = testutil.Logger(t)
|
|
||||||
|
|
||||||
newLogLevel := "ERROR"
|
|
||||||
cmd.flagArgs.Config.LogLevel = &newLogLevel
|
|
||||||
|
|
||||||
oldCfg := config.RuntimeConfig{
|
|
||||||
LogLevel: "INFO",
|
|
||||||
}
|
|
||||||
cfg, err := cmd.handleReload(a.Agent, &oldCfg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.LogLevel != "ERROR" {
|
|
||||||
t.Fatalf("expected log level to change to 'ERROR', got: %s", cfg.LogLevel)
|
|
||||||
}
|
|
||||||
if cmd.logger.IsWarn() || !cmd.logger.IsError() {
|
|
||||||
t.Fatal("expected logger level to change to 'ERROR'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package proxy
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -136,8 +137,12 @@ func (c *cmd) Run(args []string) int {
|
||||||
Name: logging.Proxy,
|
Name: logging.Proxy,
|
||||||
LogJSON: c.logJSON,
|
LogJSON: c.logJSON,
|
||||||
}
|
}
|
||||||
logger, logGate, _, ok := logging.Setup(logConfig, c.UI)
|
|
||||||
if !ok {
|
logGate := logging.GatedWriter{Writer: &cli.UiWriter{Ui: c.UI}}
|
||||||
|
|
||||||
|
logger, _, err := logging.Setup(logConfig, []io.Writer{&logGate})
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
c.logger = logger
|
c.logger = logger
|
||||||
|
|
|
@ -21,11 +21,6 @@ func TestReloadCommand(t *testing.T) {
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
|
|
||||||
// Setup a dummy response to errCh to simulate a successful reload
|
// Setup a dummy response to errCh to simulate a successful reload
|
||||||
go func() {
|
|
||||||
errCh := <-a.ReloadCh()
|
|
||||||
errCh <- nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
ui := cli.NewMockUi()
|
||||||
c := New(ui)
|
c := New(ui)
|
||||||
args := []string{"-http-addr=" + a.HTTPAddr()}
|
args := []string{"-http-addr=" + a.HTTPAddr()}
|
||||||
|
|
|
@ -127,7 +127,11 @@ func TestService_Dial(t *testing.T) {
|
||||||
func TestService_ServerTLSConfig(t *testing.T) {
|
func TestService_ServerTLSConfig(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
a := agent.StartTestAgent(t, agent.TestAgent{Name: "007"})
|
a := agent.StartTestAgent(t, agent.TestAgent{Name: "007", Overrides: `
|
||||||
|
connect {
|
||||||
|
test_ca_leaf_root_change_spread = "1ns"
|
||||||
|
}
|
||||||
|
`})
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||||
client := a.Client()
|
client := a.Client()
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
gsyslog "github.com/hashicorp/go-syslog"
|
gsyslog "github.com/hashicorp/go-syslog"
|
||||||
"github.com/mitchellh/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is used to set up logging.
|
// Config is used to set up logging.
|
||||||
|
@ -51,6 +50,8 @@ var (
|
||||||
logRotateBytes int
|
logRotateBytes int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LogSetupErrorFn func(string)
|
||||||
|
|
||||||
// Setup is used to perform setup of several logging objects:
|
// Setup is used to perform setup of several logging objects:
|
||||||
//
|
//
|
||||||
// * A hclog.Logger is used to perform filtering by log level and write to io.Writer.
|
// * A hclog.Logger is used to perform filtering by log level and write to io.Writer.
|
||||||
|
@ -62,17 +63,11 @@ var (
|
||||||
// The provided ui object will get any log messages related to setting up
|
// The provided ui object will get any log messages related to setting up
|
||||||
// logging itself, and will also be hooked up to the gated logger. The final bool
|
// logging itself, and will also be hooked up to the gated logger. The final bool
|
||||||
// parameter indicates if logging was set up successfully.
|
// parameter indicates if logging was set up successfully.
|
||||||
func Setup(config *Config, ui cli.Ui) (hclog.InterceptLogger, *GatedWriter, io.Writer, bool) {
|
func Setup(config *Config, writers []io.Writer) (hclog.InterceptLogger, io.Writer, error) {
|
||||||
// The gated writer buffers logs at startup and holds until it's flushed.
|
|
||||||
logGate := &GatedWriter{
|
|
||||||
Writer: &cli.UiWriter{Ui: ui},
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ValidateLogLevel(config.LogLevel) {
|
if !ValidateLogLevel(config.LogLevel) {
|
||||||
ui.Error(fmt.Sprintf(
|
return nil, nil, fmt.Errorf("Invalid log level: %s. Valid log levels are: %v",
|
||||||
"Invalid log level: %s. Valid log levels are: %v",
|
config.LogLevel,
|
||||||
config.LogLevel, allowedLogLevels))
|
allowedLogLevels)
|
||||||
return nil, nil, nil, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up syslog if it's enabled.
|
// Set up syslog if it's enabled.
|
||||||
|
@ -87,20 +82,15 @@ func Setup(config *Config, ui cli.Ui) (hclog.InterceptLogger, *GatedWriter, io.W
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Error(fmt.Sprintf("Syslog setup error: %v", err))
|
|
||||||
if i == retries {
|
if i == retries {
|
||||||
timeout := time.Duration(retries) * delay
|
timeout := time.Duration(retries) * delay
|
||||||
ui.Error(fmt.Sprintf("Syslog setup did not succeed within timeout (%s).", timeout.String()))
|
return nil, nil, fmt.Errorf("Syslog setup did not succeed within timeout (%s).", timeout.String())
|
||||||
return nil, nil, nil, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Error(fmt.Sprintf("Retrying syslog setup in %s...", delay.String()))
|
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writers := []io.Writer{logGate}
|
|
||||||
|
|
||||||
var logOutput io.Writer
|
|
||||||
if syslog != nil {
|
if syslog != nil {
|
||||||
writers = append(writers, syslog)
|
writers = append(writers, syslog)
|
||||||
}
|
}
|
||||||
|
@ -131,13 +121,12 @@ func Setup(config *Config, ui cli.Ui) (hclog.InterceptLogger, *GatedWriter, io.W
|
||||||
MaxFiles: config.LogRotateMaxFiles,
|
MaxFiles: config.LogRotateMaxFiles,
|
||||||
}
|
}
|
||||||
if err := logFile.openNew(); err != nil {
|
if err := logFile.openNew(); err != nil {
|
||||||
ui.Error(fmt.Sprintf("Failed to setup logging: %v", err))
|
return nil, nil, fmt.Errorf("Failed to setup logging: %w", err)
|
||||||
return nil, nil, nil, false
|
|
||||||
}
|
}
|
||||||
writers = append(writers, logFile)
|
writers = append(writers, logFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
logOutput = io.MultiWriter(writers...)
|
logOutput := io.MultiWriter(writers...)
|
||||||
|
|
||||||
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
|
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
|
||||||
Level: LevelFromString(config.LogLevel),
|
Level: LevelFromString(config.LogLevel),
|
||||||
|
@ -146,5 +135,5 @@ func Setup(config *Config, ui cli.Ui) (hclog.InterceptLogger, *GatedWriter, io.W
|
||||||
JSONFormat: config.LogJSON,
|
JSONFormat: config.LogJSON,
|
||||||
})
|
})
|
||||||
|
|
||||||
return logger, logGate, logOutput, true
|
return logger, logOutput, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
"github.com/mitchellh/cli"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,24 +18,19 @@ func TestLogger_SetupBasic(t *testing.T) {
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
LogLevel: "INFO",
|
LogLevel: "INFO",
|
||||||
}
|
}
|
||||||
ui := cli.NewMockUi()
|
|
||||||
|
|
||||||
logger, gatedWriter, writer, ok := Setup(cfg, ui)
|
logger, writer, err := Setup(cfg, nil)
|
||||||
require.True(ok)
|
require.NoError(err)
|
||||||
require.NotNil(gatedWriter)
|
|
||||||
require.NotNil(writer)
|
require.NotNil(writer)
|
||||||
require.NotNil(logger)
|
require.NotNil(logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogger_SetupInvalidLogLevel(t *testing.T) {
|
func TestLogger_SetupInvalidLogLevel(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
require := require.New(t)
|
|
||||||
cfg := &Config{}
|
cfg := &Config{}
|
||||||
ui := cli.NewMockUi()
|
|
||||||
|
|
||||||
_, _, _, ok := Setup(cfg, ui)
|
_, _, err := Setup(cfg, nil)
|
||||||
require.False(ok)
|
testutil.RequireErrorContains(t, err, "Invalid log level")
|
||||||
require.Contains(ui.ErrorWriter.String(), "Invalid log level")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogger_SetupLoggerErrorLevel(t *testing.T) {
|
func TestLogger_SetupLoggerErrorLevel(t *testing.T) {
|
||||||
|
@ -63,20 +60,19 @@ func TestLogger_SetupLoggerErrorLevel(t *testing.T) {
|
||||||
|
|
||||||
c.before(&cfg)
|
c.before(&cfg)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
ui := cli.NewMockUi()
|
var buf bytes.Buffer
|
||||||
|
|
||||||
logger, gatedWriter, _, ok := Setup(&cfg, ui)
|
logger, _, err := Setup(&cfg, []io.Writer{&buf})
|
||||||
require.True(ok)
|
require.NoError(err)
|
||||||
require.NotNil(logger)
|
require.NotNil(logger)
|
||||||
require.NotNil(gatedWriter)
|
|
||||||
|
|
||||||
gatedWriter.Flush()
|
|
||||||
|
|
||||||
logger.Error("test error msg")
|
logger.Error("test error msg")
|
||||||
logger.Info("test info msg")
|
logger.Info("test info msg")
|
||||||
|
|
||||||
require.Contains(ui.OutputWriter.String(), "[ERROR] test error msg")
|
output := buf.String()
|
||||||
require.NotContains(ui.OutputWriter.String(), "[INFO] test info msg")
|
|
||||||
|
require.Contains(output, "[ERROR] test error msg")
|
||||||
|
require.NotContains(output, "[INFO] test info msg")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,20 +83,19 @@ func TestLogger_SetupLoggerDebugLevel(t *testing.T) {
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
LogLevel: "DEBUG",
|
LogLevel: "DEBUG",
|
||||||
}
|
}
|
||||||
ui := cli.NewMockUi()
|
var buf bytes.Buffer
|
||||||
|
|
||||||
logger, gatedWriter, _, ok := Setup(cfg, ui)
|
logger, _, err := Setup(cfg, []io.Writer{&buf})
|
||||||
require.True(ok)
|
require.NoError(err)
|
||||||
require.NotNil(logger)
|
require.NotNil(logger)
|
||||||
require.NotNil(gatedWriter)
|
|
||||||
|
|
||||||
gatedWriter.Flush()
|
|
||||||
|
|
||||||
logger.Info("test info msg")
|
logger.Info("test info msg")
|
||||||
logger.Debug("test debug msg")
|
logger.Debug("test debug msg")
|
||||||
|
|
||||||
require.Contains(ui.OutputWriter.String(), "[INFO] test info msg")
|
output := buf.String()
|
||||||
require.Contains(ui.OutputWriter.String(), "[DEBUG] test debug msg")
|
|
||||||
|
require.Contains(output, "[INFO] test info msg")
|
||||||
|
require.Contains(output, "[DEBUG] test debug msg")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogger_SetupLoggerWithName(t *testing.T) {
|
func TestLogger_SetupLoggerWithName(t *testing.T) {
|
||||||
|
@ -110,18 +105,15 @@ func TestLogger_SetupLoggerWithName(t *testing.T) {
|
||||||
LogLevel: "DEBUG",
|
LogLevel: "DEBUG",
|
||||||
Name: "test-system",
|
Name: "test-system",
|
||||||
}
|
}
|
||||||
ui := cli.NewMockUi()
|
var buf bytes.Buffer
|
||||||
|
|
||||||
logger, gatedWriter, _, ok := Setup(cfg, ui)
|
logger, _, err := Setup(cfg, []io.Writer{&buf})
|
||||||
require.True(ok)
|
require.NoError(err)
|
||||||
require.NotNil(logger)
|
require.NotNil(logger)
|
||||||
require.NotNil(gatedWriter)
|
|
||||||
|
|
||||||
gatedWriter.Flush()
|
|
||||||
|
|
||||||
logger.Warn("test warn msg")
|
logger.Warn("test warn msg")
|
||||||
|
|
||||||
require.Contains(ui.OutputWriter.String(), "[WARN] test-system: test warn msg")
|
require.Contains(buf.String(), "[WARN] test-system: test warn msg")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogger_SetupLoggerWithJSON(t *testing.T) {
|
func TestLogger_SetupLoggerWithJSON(t *testing.T) {
|
||||||
|
@ -132,19 +124,16 @@ func TestLogger_SetupLoggerWithJSON(t *testing.T) {
|
||||||
LogJSON: true,
|
LogJSON: true,
|
||||||
Name: "test-system",
|
Name: "test-system",
|
||||||
}
|
}
|
||||||
ui := cli.NewMockUi()
|
var buf bytes.Buffer
|
||||||
|
|
||||||
logger, gatedWriter, _, ok := Setup(cfg, ui)
|
logger, _, err := Setup(cfg, []io.Writer{&buf})
|
||||||
require.True(ok)
|
require.NoError(err)
|
||||||
require.NotNil(logger)
|
require.NotNil(logger)
|
||||||
require.NotNil(gatedWriter)
|
|
||||||
|
|
||||||
gatedWriter.Flush()
|
|
||||||
|
|
||||||
logger.Warn("test warn msg")
|
logger.Warn("test warn msg")
|
||||||
|
|
||||||
var jsonOutput map[string]string
|
var jsonOutput map[string]string
|
||||||
err := json.Unmarshal(ui.OutputWriter.Bytes(), &jsonOutput)
|
err = json.Unmarshal(buf.Bytes(), &jsonOutput)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Contains(jsonOutput, "@level")
|
require.Contains(jsonOutput, "@level")
|
||||||
require.Equal(jsonOutput["@level"], "warn")
|
require.Equal(jsonOutput["@level"], "warn")
|
||||||
|
@ -163,13 +152,11 @@ func TestLogger_SetupLoggerWithValidLogPath(t *testing.T) {
|
||||||
LogLevel: "INFO",
|
LogLevel: "INFO",
|
||||||
LogFilePath: tmpDir + "/",
|
LogFilePath: tmpDir + "/",
|
||||||
}
|
}
|
||||||
ui := cli.NewMockUi()
|
var buf bytes.Buffer
|
||||||
|
|
||||||
logger, gatedWriter, writer, ok := Setup(cfg, ui)
|
logger, _, err := Setup(cfg, []io.Writer{&buf})
|
||||||
require.True(ok)
|
require.NoError(err)
|
||||||
require.NotNil(logger)
|
require.NotNil(logger)
|
||||||
require.NotNil(gatedWriter)
|
|
||||||
require.NotNil(writer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogger_SetupLoggerWithInValidLogPath(t *testing.T) {
|
func TestLogger_SetupLoggerWithInValidLogPath(t *testing.T) {
|
||||||
|
@ -180,14 +167,12 @@ func TestLogger_SetupLoggerWithInValidLogPath(t *testing.T) {
|
||||||
LogLevel: "INFO",
|
LogLevel: "INFO",
|
||||||
LogFilePath: "nonexistentdir/",
|
LogFilePath: "nonexistentdir/",
|
||||||
}
|
}
|
||||||
ui := cli.NewMockUi()
|
var buf bytes.Buffer
|
||||||
|
|
||||||
logger, gatedWriter, writer, ok := Setup(cfg, ui)
|
logger, _, err := Setup(cfg, []io.Writer{&buf})
|
||||||
require.Contains(ui.ErrorWriter.String(), "no such file or directory")
|
require.Error(err)
|
||||||
require.False(ok)
|
require.True(errors.Is(err, os.ErrNotExist))
|
||||||
require.Nil(logger)
|
require.Nil(logger)
|
||||||
require.Nil(gatedWriter)
|
|
||||||
require.Nil(writer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogger_SetupLoggerWithInValidLogPathPermission(t *testing.T) {
|
func TestLogger_SetupLoggerWithInValidLogPathPermission(t *testing.T) {
|
||||||
|
@ -203,12 +188,10 @@ func TestLogger_SetupLoggerWithInValidLogPathPermission(t *testing.T) {
|
||||||
LogLevel: "INFO",
|
LogLevel: "INFO",
|
||||||
LogFilePath: tmpDir + "/",
|
LogFilePath: tmpDir + "/",
|
||||||
}
|
}
|
||||||
ui := cli.NewMockUi()
|
var buf bytes.Buffer
|
||||||
|
|
||||||
logger, gatedWriter, writer, ok := Setup(cfg, ui)
|
logger, _, err := Setup(cfg, []io.Writer{&buf})
|
||||||
require.Contains(ui.ErrorWriter.String(), "permission denied")
|
require.Error(err)
|
||||||
require.False(ok)
|
require.True(errors.Is(err, os.ErrPermission))
|
||||||
require.Nil(logger)
|
require.Nil(logger)
|
||||||
require.Nil(gatedWriter)
|
|
||||||
require.Nil(writer)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ const (
|
||||||
Agent string = "agent"
|
Agent string = "agent"
|
||||||
AntiEntropy string = "anti_entropy"
|
AntiEntropy string = "anti_entropy"
|
||||||
AutoEncrypt string = "auto_encrypt"
|
AutoEncrypt string = "auto_encrypt"
|
||||||
|
AutoConfig string = "auto_config"
|
||||||
Autopilot string = "autopilot"
|
Autopilot string = "autopilot"
|
||||||
AWS string = "aws"
|
AWS string = "aws"
|
||||||
Azure string = "azure"
|
Azure string = "azure"
|
||||||
|
|
Loading…
Reference in New Issue