hcs-1936: Prepare for adding license auto-retrieval to auto-config in enterprise

This commit is contained in:
Matt Keeler 2021-05-17 16:01:32 -04:00 committed by Matt Keeler
parent 82f5cb3f08
commit 58b934133d
18 changed files with 161 additions and 54 deletions

7
.changelog/10248.txt Normal file
View File

@ -0,0 +1,7 @@
```release-note:breaking-change
licensing: **(Enterprise Only)** Consul Enterprise has removed support for temporary licensing. All server agents must have a valid license at startup and client agents must have a license at startup or be able to retrieve one from the servers.
```
```release-note:breaking-change
licensing: **(Enterprise Only)** Consul Enterprise client agents now require a valid non-anonymous ACL token for retrieving their license from the servers. Additionally client agents rely on the value of the `start_join` and `retry_join` configurations for determining the servers to query for the license. Therefore one must be set to use license auto-retrieval.
```

View File

@ -457,9 +457,7 @@ func (a *Agent) Start(ctx context.Context) error {
return fmt.Errorf("Failed to load TLS configurations after applying auto-config settings: %w", err) return fmt.Errorf("Failed to load TLS configurations after applying auto-config settings: %w", err)
} }
// we cannot use the context passed into this method as that context will be cancelled after the if err := a.startLicenseManager(ctx); err != nil {
// agent finishes starting up which would cause the license manager to stop
if err := a.startLicenseManager(&lib.StopChannelContext{StopCh: a.shutdownCh}); err != nil {
return err return err
} }

View File

@ -92,6 +92,10 @@ func New(config Config) (*AutoConfig, error) {
} }
} }
if err := config.EnterpriseConfig.validateAndFinalize(); err != nil {
return nil, err
}
return &AutoConfig{ return &AutoConfig{
acConfig: config, acConfig: config,
logger: logger, logger: logger,
@ -126,13 +130,8 @@ func (ac *AutoConfig) ReadConfig() (*config.RuntimeConfig, error) {
// The context passed in can be used to cancel the retrieval of the initial configuration // The context passed in can be used to cancel the retrieval of the initial configuration
// like when receiving a signal during startup. // like when receiving a signal during startup.
func (ac *AutoConfig) InitialConfiguration(ctx context.Context) (*config.RuntimeConfig, error) { func (ac *AutoConfig) InitialConfiguration(ctx context.Context) (*config.RuntimeConfig, error) {
if ac.config == nil { if err := ac.maybeLoadConfig(); err != nil {
config, err := ac.ReadConfig() return nil, err
if err != nil {
return nil, err
}
ac.config = config
} }
switch { switch {
@ -180,6 +179,23 @@ func (ac *AutoConfig) InitialConfiguration(ctx context.Context) (*config.Runtime
} }
} }
// maybeLoadConfig will read the Consul configuration using the
// provided config loader if and only if the config field of
// the struct is nil. When it does this it will fill in that
// field. If the config field already is non-nil then this
// is a noop.
func (ac *AutoConfig) maybeLoadConfig() error {
if ac.config == nil {
config, err := ac.ReadConfig()
if err != nil {
return err
}
ac.config = config
}
return nil
}
// introToken is responsible for determining the correct intro token to use // introToken is responsible for determining the correct intro token to use
// when making the initial AutoConfig.InitialConfiguration RPC request. // when making the initial AutoConfig.InitialConfiguration RPC request.
func (ac *AutoConfig) introToken() (string, error) { func (ac *AutoConfig) introToken() (string, error) {

View File

@ -0,0 +1,11 @@
// +build !consulent
package autoconf
// AutoConfigEnterprise has no fields in OSS
type AutoConfigEnterprise struct{}
// newAutoConfigEnterprise initializes the enterprise AutoConfig struct
func newAutoConfigEnterprise(config Config) AutoConfigEnterprise {
return AutoConfigEnterprise{}
}

View File

@ -0,0 +1,11 @@
// +build !consulent
package autoconf
import (
"testing"
)
func newEnterpriseConfig(t *testing.T) EnterpriseConfig {
return EnterpriseConfig{}
}

View File

@ -136,11 +136,12 @@ func TestNew(t *testing.T) {
Loader: func(source config.Source) (result config.LoadResult, err error) { Loader: func(source config.Source) (result config.LoadResult, err error) {
return config.LoadResult{}, nil return config.LoadResult{}, nil
}, },
DirectRPC: newMockDirectRPC(t), DirectRPC: newMockDirectRPC(t),
Tokens: newMockTokenStore(t), Tokens: newMockTokenStore(t),
Cache: newMockCache(t), Cache: newMockCache(t),
TLSConfigurator: newMockTLSConfigurator(t), TLSConfigurator: newMockTLSConfigurator(t),
ServerProvider: newMockServerProvider(t), ServerProvider: newMockServerProvider(t),
EnterpriseConfig: newEnterpriseConfig(t),
} }
if tcase.modify != nil { if tcase.modify != nil {
@ -211,18 +212,15 @@ func setupRuntimeConfig(t *testing.T) *configLoader {
} }
func TestInitialConfiguration_disabled(t *testing.T) { func TestInitialConfiguration_disabled(t *testing.T) {
loader := setupRuntimeConfig(t) mcfg := newMockedConfig(t)
loader.addConfigHCL(` mcfg.loader.addConfigHCL(`
primary_datacenter = "primary" primary_datacenter = "primary"
auto_config = { auto_config = {
enabled = false enabled = false
} }
`) `)
conf := newMockedConfig(t).Config ac, err := New(mcfg.Config)
conf.Loader = loader.Load
ac, err := New(conf)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, ac) require.NotNil(t, ac)
@ -230,7 +228,7 @@ func TestInitialConfiguration_disabled(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, cfg) require.NotNil(t, cfg)
require.Equal(t, "primary", cfg.PrimaryDatacenter) require.Equal(t, "primary", cfg.PrimaryDatacenter)
require.NoFileExists(t, filepath.Join(*loader.opts.FlagValues.DataDir, autoConfigFileName)) require.NoFileExists(t, filepath.Join(*mcfg.loader.opts.FlagValues.DataDir, autoConfigFileName))
} }
func TestInitialConfiguration_cancelled(t *testing.T) { func TestInitialConfiguration_cancelled(t *testing.T) {

View File

@ -43,7 +43,7 @@ func (ac *AutoConfig) autoEncryptInitialCertsOnce(ctx context.Context, csr, key
} }
var resp structs.SignedResponse var resp structs.SignedResponse
servers, err := ac.autoEncryptHosts() servers, err := ac.joinHosts()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -69,7 +69,7 @@ func (ac *AutoConfig) autoEncryptInitialCertsOnce(ctx context.Context, csr, key
return nil, fmt.Errorf("No servers successfully responded to the auto-encrypt request") return nil, fmt.Errorf("No servers successfully responded to the auto-encrypt request")
} }
func (ac *AutoConfig) autoEncryptHosts() ([]string, error) { func (ac *AutoConfig) joinHosts() ([]string, error) {
// use servers known to gossip if there are any // use servers known to gossip if there are any
if ac.acConfig.ServerProvider != nil { if ac.acConfig.ServerProvider != nil {
if srv := ac.acConfig.ServerProvider.FindLANServer(); srv != nil { if srv := ac.acConfig.ServerProvider.FindLANServer(); srv != nil {

View File

@ -182,7 +182,7 @@ func TestAutoEncrypt_hosts(t *testing.T) {
}, },
} }
hosts, err := ac.autoEncryptHosts() hosts, err := ac.joinHosts()
if tcase.err != "" { if tcase.err != "" {
testutil.RequireErrorContains(t, err, tcase.err) testutil.RequireErrorContains(t, err, tcase.err)
} else { } else {

View File

@ -104,4 +104,7 @@ type Config struct {
// agent token as well as getting notifications when that token is updated. // agent token as well as getting notifications when that token is updated.
// This field is required. // This field is required.
Tokens TokenStore Tokens TokenStore
// EnterpriseConfig is the embedded specific enterprise configurations
EnterpriseConfig
} }

View File

@ -0,0 +1,11 @@
// +build !consulent
package autoconf
// EnterpriseConfig stub - only populated in Consul Enterprise
type EnterpriseConfig struct{}
// finalize is a noop for OSS
func (_ *EnterpriseConfig) validateAndFinalize() error {
return nil
}

View File

@ -0,0 +1,18 @@
// +build !consulent
package autoconf
import (
"testing"
)
// mockedEnterpriseConfig is pretty much just a stub in OSS
// It does contain an enterprise config for compatibility
// purposes but that in and of itself is just a stub.
type mockedEnterpriseConfig struct {
EnterpriseConfig
}
func newMockedEnterpriseConfig(t *testing.T) *mockedEnterpriseConfig {
return &mockedEnterpriseConfig{}
}

View File

@ -218,20 +218,25 @@ func (m *mockTokenStore) StopNotify(notifier token.Notifier) {
type mockedConfig struct { type mockedConfig struct {
Config Config
directRPC *mockDirectRPC loader *configLoader
serverProvider *mockServerProvider directRPC *mockDirectRPC
cache *mockCache serverProvider *mockServerProvider
tokens *mockTokenStore cache *mockCache
tlsCfg *mockTLSConfigurator tokens *mockTokenStore
tlsCfg *mockTLSConfigurator
enterpriseConfig *mockedEnterpriseConfig
} }
func newMockedConfig(t *testing.T) *mockedConfig { func newMockedConfig(t *testing.T) *mockedConfig {
loader := setupRuntimeConfig(t)
directRPC := newMockDirectRPC(t) directRPC := newMockDirectRPC(t)
serverProvider := newMockServerProvider(t) serverProvider := newMockServerProvider(t)
mcache := newMockCache(t) mcache := newMockCache(t)
tokens := newMockTokenStore(t) tokens := newMockTokenStore(t)
tlsCfg := newMockTLSConfigurator(t) tlsCfg := newMockTLSConfigurator(t)
entConfig := newMockedEnterpriseConfig(t)
// I am not sure it is well defined behavior but in testing it // I am not sure it is well defined behavior but in testing it
// out it does appear like Cleanup functions can fail tests // out it does appear like Cleanup functions can fail tests
// Adding in the mock expectations assertions here saves us // Adding in the mock expectations assertions here saves us
@ -248,18 +253,23 @@ func newMockedConfig(t *testing.T) *mockedConfig {
return &mockedConfig{ return &mockedConfig{
Config: Config{ Config: Config{
DirectRPC: directRPC, Loader: loader.Load,
ServerProvider: serverProvider, DirectRPC: directRPC,
Cache: mcache, ServerProvider: serverProvider,
Tokens: tokens, Cache: mcache,
TLSConfigurator: tlsCfg, Tokens: tokens,
Logger: testutil.Logger(t), TLSConfigurator: tlsCfg,
Logger: testutil.Logger(t),
EnterpriseConfig: entConfig.EnterpriseConfig,
}, },
loader: loader,
directRPC: directRPC, directRPC: directRPC,
serverProvider: serverProvider, serverProvider: serverProvider,
cache: mcache, cache: mcache,
tokens: tokens, tokens: tokens,
tlsCfg: tlsCfg, tlsCfg: tlsCfg,
enterpriseConfig: entConfig,
} }
} }

View File

@ -159,11 +159,6 @@ func NewClient(config *Config, deps Deps) (*Client, error) {
go c.monitorACLMode() go c.monitorACLMode()
} }
if err := c.startEnterprise(); err != nil {
c.Shutdown()
return nil, err
}
return c, nil return c, nil
} }

View File

@ -350,9 +350,6 @@ type Config struct {
// a Consul server is now up and known about. // a Consul server is now up and known about.
ServerUp func() ServerUp func()
// Shutdown callback is used to trigger a full Consul shutdown
Shutdown func()
// UserEventHandler callback can be used to handle incoming // UserEventHandler callback can be used to handle incoming
// user events. This function should not block. // user events. This function should not block.
UserEventHandler func(serf.UserEvent) UserEventHandler func(serf.UserEvent)

View File

@ -110,21 +110,30 @@ func NewBaseDeps(configLoader ConfigLoader, logOut io.Writer) (BaseDeps, error)
d.Router = router.NewRouter(d.Logger, cfg.Datacenter, fmt.Sprintf("%s.%s", cfg.NodeName, cfg.Datacenter), builder) d.Router = router.NewRouter(d.Logger, cfg.Datacenter, fmt.Sprintf("%s.%s", cfg.NodeName, cfg.Datacenter), builder)
acConf := autoconf.Config{ // this needs to happen prior to creating auto-config as some of the dependencies
DirectRPC: d.ConnPool, // must also be passed to auto-config
Logger: d.Logger, d, err = initEnterpriseBaseDeps(d, cfg)
Loader: configLoader, if err != nil {
ServerProvider: d.Router, return d, err
TLSConfigurator: d.TLSConfigurator,
Cache: d.Cache,
Tokens: d.Tokens,
} }
acConf := autoconf.Config{
DirectRPC: d.ConnPool,
Logger: d.Logger,
Loader: configLoader,
ServerProvider: d.Router,
TLSConfigurator: d.TLSConfigurator,
Cache: d.Cache,
Tokens: d.Tokens,
EnterpriseConfig: initEnterpriseAutoConfig(d.EnterpriseDeps),
}
d.AutoConfig, err = autoconf.New(acConf) d.AutoConfig, err = autoconf.New(acConf)
if err != nil { if err != nil {
return d, err return d, err
} }
return initEnterpriseBaseDeps(d, cfg) return d, nil
} }
// grpcLogInitOnce because the test suite will call NewBaseDeps in many tests and // grpcLogInitOnce because the test suite will call NewBaseDeps in many tests and

View File

@ -3,7 +3,9 @@
package agent package agent
import ( import (
autoconf "github.com/hashicorp/consul/agent/auto-config"
"github.com/hashicorp/consul/agent/config" "github.com/hashicorp/consul/agent/config"
"github.com/hashicorp/consul/agent/consul"
) )
// initEnterpriseBaseDeps is responsible for initializing the enterprise dependencies that // initEnterpriseBaseDeps is responsible for initializing the enterprise dependencies that
@ -11,3 +13,8 @@ import (
func initEnterpriseBaseDeps(d BaseDeps, _ *config.RuntimeConfig) (BaseDeps, error) { func initEnterpriseBaseDeps(d BaseDeps, _ *config.RuntimeConfig) (BaseDeps, error) {
return d, nil return d, nil
} }
// initEnterpriseAutoConfig is responsible for setting up auto-config for enterprise
func initEnterpriseAutoConfig(_ consul.EnterpriseDeps) autoconf.EnterpriseConfig {
return autoconf.EnterpriseConfig{}
}

View File

@ -23,6 +23,7 @@ func LoggerWithOutput(t TestingTB, output io.Writer) hclog.InterceptLogger {
} }
var sendTestLogsToStdout = os.Getenv("NOLOGBUFFER") == "1" var sendTestLogsToStdout = os.Getenv("NOLOGBUFFER") == "1"
var testLogOnlyFailed = os.Getenv("TEST_LOGGING_ONLY_FAILED") == "1"
// NewLogBuffer returns an io.Writer which buffers all writes. When the test // NewLogBuffer returns an io.Writer which buffers all writes. When the test
// ends, t.Failed is checked. If the test has failed or has been run in verbose // ends, t.Failed is checked. If the test has failed or has been run in verbose
@ -30,13 +31,18 @@ var sendTestLogsToStdout = os.Getenv("NOLOGBUFFER") == "1"
// //
// Set the env var NOLOGBUFFER=1 to disable buffering, resulting in all log // Set the env var NOLOGBUFFER=1 to disable buffering, resulting in all log
// output being written immediately to stdout. // output being written immediately to stdout.
//
// Typically log output is written either for failed tests or when go test
// is running with the verbose flag (-v) set. Setting TEST_LOGGING_ONLY_FAILED=1
// will prevent logs being output when the verbose flag is set if the test
// case is successful.
func NewLogBuffer(t TestingTB) io.Writer { func NewLogBuffer(t TestingTB) io.Writer {
if sendTestLogsToStdout { if sendTestLogsToStdout {
return os.Stdout return os.Stdout
} }
buf := &logBuffer{buf: new(bytes.Buffer)} buf := &logBuffer{buf: new(bytes.Buffer)}
t.Cleanup(func() { t.Cleanup(func() {
if t.Failed() || testing.Verbose() { if t.Failed() || (!testLogOnlyFailed && testing.Verbose()) {
buf.Lock() buf.Lock()
defer buf.Unlock() defer buf.Unlock()
buf.buf.WriteTo(os.Stdout) buf.buf.WriteTo(os.Stdout)

View File

@ -142,6 +142,15 @@ function start_consul {
'-p=9502:8502' '-p=9502:8502'
) )
fi fi
license="${CONSUL_LICENSE:-}"
# load the consul license so we can pass it into the consul
# containers as an env var in the case that this is a consul
# enterprise test
if test -z "$license" -a -n "${CONSUL_LICENSE_PATH:-}"
then
license=$(cat $CONSUL_LICENSE_PATH)
fi
# Run consul and expose some ports to the host to make debugging locally a # Run consul and expose some ports to the host to make debugging locally a
# bit easier. # bit easier.
@ -151,6 +160,7 @@ function start_consul {
$WORKDIR_SNIPPET \ $WORKDIR_SNIPPET \
--hostname "consul-${DC}" \ --hostname "consul-${DC}" \
--network-alias "consul-${DC}" \ --network-alias "consul-${DC}" \
-e "CONSUL_LICENSE=$license" \
${ports[@]} \ ${ports[@]} \
consul-dev \ consul-dev \
agent -dev -datacenter "${DC}" \ agent -dev -datacenter "${DC}" \