mirror of
https://github.com/status-im/consul.git
synced 2025-02-03 09:24:25 +00:00
5fb9df1640
* Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
387 lines
11 KiB
Go
387 lines
11 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package cluster
|
|
|
|
import (
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/mod/semver"
|
|
|
|
"github.com/hashicorp/consul/test/integration/consul-container/libs/utils"
|
|
)
|
|
|
|
// TODO: switch from semver to go-version
|
|
|
|
const (
|
|
remoteCertDirectory = "/consul/config/certs"
|
|
|
|
ConsulCACertPEM = "consul-agent-ca.pem"
|
|
ConsulCACertKey = "consul-agent-ca-key.pem"
|
|
)
|
|
|
|
type LogStore string
|
|
|
|
const (
|
|
LogStore_WAL LogStore = "wal"
|
|
LogStore_BoltDB LogStore = "boltdb"
|
|
)
|
|
|
|
// BuildContext provides a reusable object meant to share common configuration settings
|
|
// between agent configuration builders.
|
|
type BuildContext struct {
|
|
datacenter string
|
|
consulImageName string
|
|
consulVersion string
|
|
|
|
injectGossipEncryption bool // setup the agents to use a gossip encryption key
|
|
encryptKey string
|
|
|
|
injectCerts bool // initializes the built-in CA and distributes client certificates to agents
|
|
injectAutoEncryption bool // initialize the built-in CA and set up agents to use auto-encrpt
|
|
allowHTTPAnyway bool
|
|
useAPIWithTLS bool
|
|
useGRPCWithTLS bool
|
|
|
|
certVolume string
|
|
caCert string
|
|
tlsCertIndex int // keeps track of the certificates issued for naming purposes
|
|
|
|
aclEnabled bool
|
|
logStore LogStore
|
|
}
|
|
|
|
func (c *BuildContext) DockerImage() string {
|
|
return utils.DockerImage(c.consulImageName, c.consulVersion)
|
|
}
|
|
|
|
// BuildOptions define the desired automated test setup overrides that are
|
|
// applied across agents in the cluster
|
|
type BuildOptions struct {
|
|
// Datacenter is the override datacenter for agents.
|
|
Datacenter string
|
|
|
|
// ConsulImageName is the default Consul image name for agents in the
|
|
// cluster when none is specified.
|
|
ConsulImageName string
|
|
|
|
// ConsulVersion is the default Consul version for agents in the cluster
|
|
// when none is specified.
|
|
ConsulVersion string
|
|
|
|
// InjectGossipEncryption provides a gossip encryption key for all agents.
|
|
InjectGossipEncryption bool
|
|
|
|
// InjectCerts provides a CA for all agents and (future) agent certs.
|
|
//
|
|
// It also disables the HTTP API unless AllowHTTPAnyway is enabled.
|
|
InjectCerts bool
|
|
|
|
// InjectAutoEncryption configures auto-encrypt for TLS and sets up certs.
|
|
// Overrides InjectCerts.
|
|
//
|
|
// It also disables the HTTP API unless AllowHTTPAnyway is enabled.
|
|
InjectAutoEncryption bool
|
|
|
|
// AllowHTTPAnyway ensures that the HTTP API is enabled even when
|
|
// InjectCerts or InjectAutoEncryption are enabled.
|
|
AllowHTTPAnyway bool
|
|
|
|
// UseAPIWithTLS ensures that any accesses for the JSON API use the https
|
|
// port. By default it will not.
|
|
UseAPIWithTLS bool
|
|
|
|
// UseGRPCWithTLS ensures that any accesses for external gRPC use the
|
|
// grpc_tls port. By default it will not.
|
|
UseGRPCWithTLS bool
|
|
|
|
// ACLEnabled configures acl in agent configuration
|
|
ACLEnabled bool
|
|
|
|
//StoreLog define which LogStore to use
|
|
LogStore LogStore
|
|
}
|
|
|
|
func NewBuildContext(t *testing.T, opts BuildOptions) *BuildContext {
|
|
ctx := &BuildContext{
|
|
datacenter: opts.Datacenter,
|
|
consulImageName: opts.ConsulImageName,
|
|
consulVersion: opts.ConsulVersion,
|
|
injectGossipEncryption: opts.InjectGossipEncryption,
|
|
injectCerts: opts.InjectCerts,
|
|
injectAutoEncryption: opts.InjectAutoEncryption,
|
|
allowHTTPAnyway: opts.AllowHTTPAnyway,
|
|
useAPIWithTLS: opts.UseAPIWithTLS,
|
|
useGRPCWithTLS: opts.UseGRPCWithTLS,
|
|
aclEnabled: opts.ACLEnabled,
|
|
logStore: opts.LogStore,
|
|
}
|
|
|
|
if ctx.consulImageName == "" {
|
|
ctx.consulImageName = utils.GetTargetImageName()
|
|
}
|
|
if ctx.consulVersion == "" {
|
|
ctx.consulVersion = utils.TargetVersion
|
|
}
|
|
|
|
if opts.InjectGossipEncryption {
|
|
serfKey, err := newSerfEncryptionKey()
|
|
require.NoError(t, err, "could not generate serf encryption key")
|
|
ctx.encryptKey = serfKey
|
|
}
|
|
|
|
if opts.InjectAutoEncryption {
|
|
if opts.UseAPIWithTLS {
|
|
// TODO: we should improve this
|
|
t.Fatalf("Cannot use TLS with the API in conjunction with Auto Encrypt because you would need to use the Connect CA Cert for verification")
|
|
}
|
|
if opts.UseGRPCWithTLS {
|
|
// TODO: we should improve this
|
|
t.Fatalf("Cannot use TLS with gRPC in conjunction with Auto Encrypt because you would need to use the Connect CA Cert for verification")
|
|
}
|
|
}
|
|
|
|
if opts.InjectAutoEncryption || opts.InjectCerts {
|
|
ctx.createTLSCAFiles(t)
|
|
} else {
|
|
if opts.UseAPIWithTLS {
|
|
t.Fatalf("UseAPIWithTLS requires one of InjectAutoEncryption or InjectCerts to be set")
|
|
}
|
|
if opts.UseGRPCWithTLS {
|
|
t.Fatalf("UseGRPCWithTLS requires one of InjectAutoEncryption or InjectCerts to be set")
|
|
}
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
type Builder struct {
|
|
context *BuildContext // this is non-nil
|
|
conf *ConfigBuilder
|
|
}
|
|
|
|
// NewConfigBuilder instantiates a builder object with sensible defaults for a single consul instance
|
|
// This includes the following:
|
|
// * default ports with no plaintext options
|
|
// * debug logging
|
|
// * single server with bootstrap
|
|
// * bind to all interfaces, advertise on 'eth0'
|
|
// * connect enabled
|
|
func NewConfigBuilder(ctx *BuildContext) *Builder {
|
|
if ctx == nil {
|
|
panic("BuildContext is a required argument")
|
|
}
|
|
b := &Builder{
|
|
conf: &ConfigBuilder{},
|
|
context: ctx,
|
|
}
|
|
|
|
b.conf.Set("advertise_addr", `{{ GetInterfaceIP "eth0" }}`)
|
|
b.conf.Set("bind_addr", "0.0.0.0")
|
|
b.conf.Set("data_dir", "/consul/data")
|
|
b.conf.Set("bootstrap", true)
|
|
b.conf.Set("client_addr", "0.0.0.0")
|
|
b.conf.Set("connect.enabled", true)
|
|
b.conf.Set("log_level", "debug")
|
|
b.conf.Set("server", true)
|
|
b.conf.Set("ui_config.enabled", true)
|
|
|
|
// These are the default ports, disabling plaintext transport
|
|
b.conf.Set("ports.dns", 8600)
|
|
//nolint:staticcheck
|
|
if ctx.certVolume == "" {
|
|
b.conf.Set("ports.http", 8500)
|
|
b.conf.Set("ports.https", -1)
|
|
} else {
|
|
b.conf.Set("ports.http", -1)
|
|
b.conf.Set("ports.https", 8501)
|
|
}
|
|
b.conf.Set("ports.grpc", 8502)
|
|
b.conf.Set("ports.serf_lan", 8301)
|
|
b.conf.Set("ports.serf_wan", 8302)
|
|
b.conf.Set("ports.server", 8300)
|
|
|
|
if ctx.allowHTTPAnyway {
|
|
b.conf.Set("ports.http", 8500)
|
|
}
|
|
|
|
if ctx.consulVersion == "local" || semver.Compare("v"+ctx.consulVersion, "v1.14.0") >= 0 {
|
|
// Enable GRPCTLS for version after v1.14.0
|
|
b.conf.Set("ports.grpc_tls", 8503)
|
|
}
|
|
|
|
if ctx.aclEnabled {
|
|
b.conf.Set("acl.enabled", true)
|
|
b.conf.Set("acl.default_policy", "deny")
|
|
b.conf.Set("acl.enable_token_persistence", true)
|
|
}
|
|
|
|
ls := string(ctx.logStore)
|
|
if ls != "" && (ctx.consulVersion == "local" ||
|
|
semver.Compare("v"+ctx.consulVersion, "v1.15.0") >= 0) {
|
|
// Enable logstore backend for version after v1.15.0
|
|
if ls != string(LogStore_WAL) && ls != string(LogStore_BoltDB) {
|
|
ls = string(LogStore_BoltDB)
|
|
}
|
|
b.conf.Set("raft_logstore.backend", ls)
|
|
} else {
|
|
b.conf.Unset("raft_logstore.backend")
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
// Advanced lets you directly manipulate specific config settings.
|
|
func (b *Builder) Advanced(fn func(*ConfigBuilder)) *Builder {
|
|
if fn != nil {
|
|
fn(b.conf)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) Bootstrap(servers int) *Builder {
|
|
if servers < 1 {
|
|
b.conf.Unset("bootstrap")
|
|
b.conf.Unset("bootstrap_expect")
|
|
} else if servers == 1 {
|
|
b.conf.Set("bootstrap", true)
|
|
b.conf.Unset("bootstrap_expect")
|
|
} else {
|
|
b.conf.Unset("bootstrap")
|
|
b.conf.Set("bootstrap_expect", servers)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) Client() *Builder {
|
|
b.conf.Unset("ports.server")
|
|
b.conf.Unset("server")
|
|
b.conf.Unset("bootstrap")
|
|
b.conf.Unset("bootstrap_expect")
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) Datacenter(name string) *Builder {
|
|
b.conf.Set("datacenter", name)
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) Peering(enable bool) *Builder {
|
|
b.conf.Set("peering.enabled", enable)
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) NodeID(nodeID string) *Builder {
|
|
b.conf.Set("node_id", nodeID)
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) Partition(name string) *Builder {
|
|
b.conf.Set("partition", name)
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) RetryJoin(names ...string) *Builder {
|
|
b.conf.Set("retry_join", names)
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) EnableACL() *Builder {
|
|
b.conf.Set("acl.enabled", true)
|
|
b.conf.Set("acl.default_policy", "deny")
|
|
b.conf.Set("acl.enable_token_persistence", true)
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) Telemetry(statSite string) *Builder {
|
|
b.conf.Set("telemetry.statsite_address", statSite)
|
|
return b
|
|
}
|
|
|
|
// ToAgentConfig renders the builders configuration into a string
|
|
// representation of the json config file for agents.
|
|
func (b *Builder) ToAgentConfig(t *testing.T) *Config {
|
|
b.injectContextOptions(t)
|
|
|
|
out, err := json.MarshalIndent(b.conf, "", " ")
|
|
require.NoError(t, err, "could not generate json config")
|
|
|
|
confCopy, err := b.conf.Clone()
|
|
require.NoError(t, err)
|
|
|
|
cmd := []string{"agent"}
|
|
if utils.Debug {
|
|
cmd = []string{"/root/go/bin/dlv", "exec", "/bin/consul", "--listen=:4000", "--headless=true", "", "--accept-multiclient", "--continue", "--api-version=2", "--", "agent", "--config-file=/consul/config/config.json"}
|
|
}
|
|
return &Config{
|
|
JSON: string(out),
|
|
ConfigBuilder: confCopy,
|
|
|
|
Cmd: cmd,
|
|
|
|
Image: b.context.consulImageName,
|
|
Version: b.context.consulVersion,
|
|
|
|
CertVolume: b.context.certVolume,
|
|
CACert: b.context.caCert,
|
|
|
|
UseAPIWithTLS: b.context.useAPIWithTLS,
|
|
UseGRPCWithTLS: b.context.useGRPCWithTLS,
|
|
|
|
ACLEnabled: b.context.aclEnabled,
|
|
}
|
|
}
|
|
|
|
func (b *Builder) injectContextOptions(t *testing.T) {
|
|
var dc string
|
|
if b.context.datacenter != "" {
|
|
b.conf.Set("datacenter", b.context.datacenter)
|
|
dc = b.context.datacenter
|
|
}
|
|
if val, _ := b.conf.GetString("datacenter"); val == "" {
|
|
dc = "dc1"
|
|
}
|
|
b.conf.Set("datacenter", dc)
|
|
|
|
server, _ := b.conf.GetBool("server")
|
|
|
|
if b.context.encryptKey != "" {
|
|
b.conf.Set("encrypt", b.context.encryptKey)
|
|
}
|
|
|
|
// For any TLS setup, we add the CA to agent conf
|
|
if b.context.certVolume != "" {
|
|
b.conf.Set("tls.defaults.ca_file", filepath.Join(remoteCertDirectory, ConsulCACertPEM))
|
|
b.conf.Set("tls.defaults.verify_outgoing", true) // Secure settings
|
|
b.conf.Set("tls.internal_rpc.verify_server_hostname", true)
|
|
}
|
|
|
|
// Also for any TLS setup, generate server key pairs from the CA
|
|
if b.context.certVolume != "" && server {
|
|
keyFileName, certFileName := b.context.createTLSCertFiles(t, dc)
|
|
b.context.tlsCertIndex++
|
|
|
|
b.conf.Set("tls.defaults.cert_file", filepath.Join(remoteCertDirectory, certFileName))
|
|
b.conf.Set("tls.defaults.key_file", filepath.Join(remoteCertDirectory, keyFileName))
|
|
b.conf.Set("tls.internal_rpc.verify_incoming", true) // Only applies to servers for auto-encrypt
|
|
}
|
|
|
|
// This assumes we've already gone through the CA/Cert setup in the previous conditional
|
|
if b.context.injectAutoEncryption {
|
|
if server {
|
|
b.conf.Set("auto_encrypt.allow_tls", true) // This setting is different between client and servers
|
|
b.conf.Set("tls.grpc.use_auto_cert", true) // This is required for peering to work over the non-GRPC_TLS port
|
|
// VerifyIncoming does not apply to client agents for auto-encrypt
|
|
} else {
|
|
b.conf.Set("auto_encrypt.tls", true) // This setting is different between client and servers
|
|
b.conf.Set("tls.grpc.use_auto_cert", true) // This is required for peering to work over the non-GRPC_TLS port
|
|
}
|
|
}
|
|
|
|
if b.context.injectCerts && !b.context.injectAutoEncryption {
|
|
panic("client certificate distribution not implemented")
|
|
}
|
|
}
|