Centralise tls configuration part 1 (#5366)

In order to be able to reload the TLS configuration, we need one way to generate the different configurations.

This PR introduces a `tlsutil.Configurator` which holds a `tlsutil.Config`. Afterwards it is responsible for rendering every `tls.Config`. In this particular PR I moved `IncomingHTTPSConfig`, `IncomingTLSConfig`, and `OutgoingTLSWrapper` into `tlsutil.Configurator`.

This PR is a pure refactoring - not a single feature added. And not a single test added. I only slightly modified existing tests as necessary.
This commit is contained in:
Hans Hasselberg 2019-02-26 16:52:07 +01:00 committed by GitHub
parent 198d4f6aed
commit 75ababb54f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 397 additions and 434 deletions

View File

@ -40,6 +40,7 @@ import (
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/lib/file"
"github.com/hashicorp/consul/logger"
"github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/consul/types"
"github.com/hashicorp/consul/watch"
multierror "github.com/hashicorp/go-multierror"
@ -249,6 +250,8 @@ type Agent struct {
// grpcServer is the server instance used currently to serve xDS API for
// Envoy.
grpcServer *grpc.Server
tlsConfigurator *tlsutil.Configurator
}
func New(c *config.RuntimeConfig) (*Agent, error) {
@ -383,15 +386,17 @@ func (a *Agent) Start() error {
// waiting to discover a consul server
consulCfg.ServerUp = a.sync.SyncFull.Trigger
a.tlsConfigurator = tlsutil.NewConfigurator(c.ToTLSUtilConfig())
// Setup either the client or the server.
if c.ServerMode {
server, err := consul.NewServerLogger(consulCfg, a.logger, a.tokens)
server, err := consul.NewServerLogger(consulCfg, a.logger, a.tokens, a.tlsConfigurator)
if err != nil {
return fmt.Errorf("Failed to start Consul server: %v", err)
}
a.delegate = server
} else {
client, err := consul.NewClientLogger(consulCfg, a.logger)
client, err := consul.NewClientLogger(consulCfg, a.logger, a.tlsConfigurator)
if err != nil {
return fmt.Errorf("Failed to start Consul client: %v", err)
}
@ -649,7 +654,7 @@ func (a *Agent) listenHTTP() ([]*HTTPServer, error) {
var tlscfg *tls.Config
_, isTCP := l.(*tcpKeepAliveListener)
if isTCP && proto == "https" {
tlscfg, err = a.config.IncomingHTTPSConfig()
tlscfg, err = a.tlsConfigurator.IncomingHTTPSConfig()
if err != nil {
return err
}

View File

@ -1,7 +1,6 @@
package config
import (
"crypto/tls"
"fmt"
"net"
"reflect"
@ -1440,25 +1439,6 @@ type RuntimeConfig struct {
Watches []map[string]interface{}
}
// IncomingHTTPSConfig returns the TLS configuration for HTTPS
// connections to consul.
func (c *RuntimeConfig) IncomingHTTPSConfig() (*tls.Config, error) {
tc := &tlsutil.Config{
VerifyIncoming: c.VerifyIncoming || c.VerifyIncomingHTTPS,
VerifyOutgoing: c.VerifyOutgoing,
CAFile: c.CAFile,
CAPath: c.CAPath,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
NodeName: c.NodeName,
ServerName: c.ServerName,
TLSMinVersion: c.TLSMinVersion,
CipherSuites: c.TLSCipherSuites,
PreferServerCipherSuites: c.TLSPreferServerCipherSuites,
}
return tc.IncomingTLSConfig()
}
func (c *RuntimeConfig) apiAddresses(maxPerType int) (unixAddrs, httpAddrs, httpsAddrs []string) {
if len(c.HTTPSAddrs) > 0 {
for i, addr := range c.HTTPSAddrs {
@ -1597,6 +1577,25 @@ func (c *RuntimeConfig) Sanitized() map[string]interface{} {
return sanitize("rt", reflect.ValueOf(c)).Interface().(map[string]interface{})
}
func (c *RuntimeConfig) ToTLSUtilConfig() *tlsutil.Config {
return &tlsutil.Config{
VerifyIncoming: c.VerifyIncoming,
VerifyIncomingRPC: c.VerifyIncomingRPC,
VerifyIncomingHTTPS: c.VerifyIncomingHTTPS,
VerifyOutgoing: c.VerifyOutgoing,
CAFile: c.CAFile,
CAPath: c.CAPath,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
NodeName: c.NodeName,
ServerName: c.ServerName,
TLSMinVersion: c.TLSMinVersion,
CipherSuites: c.TLSCipherSuites,
PreferServerCipherSuites: c.TLSPreferServerCipherSuites,
EnableAgentTLSForChecks: c.EnableAgentTLSForChecks,
}
}
// isSecret determines whether a field name represents a field which
// may contain a secret.
func isSecret(name string) bool {

View File

@ -5426,6 +5426,40 @@ func TestRuntime_ClientAddressAnyV6(t *testing.T) {
require.Equal(t, "[::1]:5688", https)
}
func TestRuntime_ToTLSUtilConfig(t *testing.T) {
c := &RuntimeConfig{
VerifyIncoming: true,
VerifyIncomingRPC: true,
VerifyIncomingHTTPS: true,
VerifyOutgoing: true,
CAFile: "a",
CAPath: "b",
CertFile: "c",
KeyFile: "d",
NodeName: "e",
ServerName: "f",
TLSMinVersion: "tls12",
TLSCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
TLSPreferServerCipherSuites: true,
EnableAgentTLSForChecks: true,
}
r := c.ToTLSUtilConfig()
require.Equal(t, c.VerifyIncoming, r.VerifyIncoming)
require.Equal(t, c.VerifyIncomingRPC, r.VerifyIncomingRPC)
require.Equal(t, c.VerifyIncomingHTTPS, r.VerifyIncomingHTTPS)
require.Equal(t, c.VerifyOutgoing, r.VerifyOutgoing)
require.Equal(t, c.CAFile, r.CAFile)
require.Equal(t, c.CAPath, r.CAPath)
require.Equal(t, c.CertFile, r.CertFile)
require.Equal(t, c.KeyFile, r.KeyFile)
require.Equal(t, c.NodeName, r.NodeName)
require.Equal(t, c.ServerName, r.ServerName)
require.Equal(t, c.TLSMinVersion, r.TLSMinVersion)
require.Equal(t, c.TLSCipherSuites, r.CipherSuites)
require.Equal(t, c.TLSPreferServerCipherSuites, r.PreferServerCipherSuites)
require.Equal(t, c.EnableAgentTLSForChecks, r.EnableAgentTLSForChecks)
}
func splitIPPort(hostport string) (net.IP, int) {
h, p, err := net.SplitHostPort(hostport)
if err != nil {

View File

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/consul/agent/router"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/serf/serf"
"golang.org/x/time/rate"
)
@ -88,10 +89,10 @@ type Client struct {
// NewClient is used to construct a new Consul client from the
// configuration, potentially returning an error
func NewClient(config *Config) (*Client, error) {
return NewClientLogger(config, nil)
return NewClientLogger(config, nil, tlsutil.NewConfigurator(config.ToTLSUtilConfig()))
}
func NewClientLogger(config *Config, logger *log.Logger) (*Client, error) {
func NewClientLogger(config *Config, logger *log.Logger, tlsConfigurator *tlsutil.Configurator) (*Client, error) {
// Check the protocol version
if err := config.CheckProtocolVersion(); err != nil {
return nil, err
@ -113,7 +114,7 @@ func NewClientLogger(config *Config, logger *log.Logger) (*Client, error) {
}
// Create the tls Wrapper
tlsWrap, err := config.tlsConfig().OutgoingTLSWrapper()
tlsWrap, err := tlsConfigurator.OutgoingRPCWrapper()
if err != nil {
return nil, err
}

View File

@ -382,6 +382,22 @@ type Config struct {
ConnectReplicationToken string
}
func (c *Config) ToTLSUtilConfig() *tlsutil.Config {
return &tlsutil.Config{
VerifyIncoming: c.VerifyIncoming,
VerifyOutgoing: c.VerifyOutgoing,
CAFile: c.CAFile,
CAPath: c.CAPath,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
NodeName: c.NodeName,
ServerName: c.ServerName,
TLSMinVersion: c.TLSMinVersion,
CipherSuites: c.TLSCipherSuites,
PreferServerCipherSuites: c.TLSPreferServerCipherSuites,
}
}
// CheckProtocolVersion validates the protocol version.
func (c *Config) CheckProtocolVersion() error {
if c.ProtocolVersion < ProtocolVersionMin {
@ -500,23 +516,3 @@ func DefaultConfig() *Config {
return conf
}
// tlsConfig maps this config into a tlsutil config.
func (c *Config) tlsConfig() *tlsutil.Config {
tlsConf := &tlsutil.Config{
VerifyIncoming: c.VerifyIncoming,
VerifyOutgoing: c.VerifyOutgoing,
VerifyServerHostname: c.VerifyServerHostname,
UseTLS: c.UseTLS,
CAFile: c.CAFile,
CAPath: c.CAPath,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
NodeName: c.NodeName,
ServerName: c.ServerName,
Domain: c.Domain,
TLSMinVersion: c.TLSMinVersion,
PreferServerCipherSuites: c.TLSPreferServerCipherSuites,
}
return tlsConf
}

View File

@ -253,12 +253,12 @@ type Server struct {
}
func NewServer(config *Config) (*Server, error) {
return NewServerLogger(config, nil, new(token.Store))
return NewServerLogger(config, nil, new(token.Store), tlsutil.NewConfigurator(config.ToTLSUtilConfig()))
}
// NewServer is used to construct a new Consul server from the
// configuration, potentially returning an error
func NewServerLogger(config *Config, logger *log.Logger, tokens *token.Store) (*Server, error) {
func NewServerLogger(config *Config, logger *log.Logger, tokens *token.Store, tlsConfigurator *tlsutil.Configurator) (*Server, error) {
// Check the protocol version.
if err := config.CheckProtocolVersion(); err != nil {
return nil, err
@ -297,14 +297,13 @@ func NewServerLogger(config *Config, logger *log.Logger, tokens *token.Store) (*
}
// Create the TLS wrapper for outgoing connections.
tlsConf := config.tlsConfig()
tlsWrap, err := tlsConf.OutgoingTLSWrapper()
tlsWrap, err := tlsConfigurator.OutgoingRPCWrapper()
if err != nil {
return nil, err
}
// Get the incoming TLS config.
incomingTLS, err := tlsConf.IncomingTLSConfig()
incomingTLS, err := tlsConfigurator.IncomingRPCConfig()
if err != nil {
return nil, err
}

View File

@ -18,6 +18,7 @@ import (
"github.com/hashicorp/consul/testrpc"
"github.com/hashicorp/consul/testutil"
"github.com/hashicorp/consul/testutil/retry"
"github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/consul/types"
"github.com/hashicorp/go-uuid"
)
@ -176,7 +177,7 @@ func newServer(c *Config) (*Server, error) {
w = os.Stderr
}
logger := log.New(w, c.NodeName+" - ", log.LstdFlags|log.Lmicroseconds)
srv, err := NewServerLogger(c, logger, new(token.Store))
srv, err := NewServerLogger(c, logger, new(token.Store), tlsutil.NewConfigurator(c.ToTLSUtilConfig()))
if err != nil {
return nil, err
}

View File

@ -26,6 +26,7 @@ import (
"github.com/hashicorp/consul/lib/freeport"
"github.com/hashicorp/consul/logger"
"github.com/hashicorp/consul/testutil/retry"
"github.com/hashicorp/consul/tlsutil"
"github.com/stretchr/testify/require"
)
@ -148,6 +149,7 @@ func (a *TestAgent) Start(t *testing.T) *TestAgent {
agent.LogWriter = a.LogWriter
agent.logger = log.New(logOutput, a.Name+" - ", log.LstdFlags|log.Lmicroseconds)
agent.MemSink = metrics.NewInmemSink(1*time.Second, time.Minute)
agent.tlsConfigurator = tlsutil.NewConfigurator(a.Config.ToTLSUtilConfig())
// we need the err var in the next exit condition
if err := agent.Start(); err == nil {

View File

@ -33,7 +33,9 @@ type Config struct {
// VerifyIncoming is used to verify the authenticity of incoming connections.
// This means that TCP requests are forbidden, only allowing for TLS. TLS connections
// must match a provided certificate authority. This can be used to force client auth.
VerifyIncoming bool
VerifyIncoming bool
VerifyIncomingRPC bool
VerifyIncomingHTTPS bool
// VerifyOutgoing is used to verify the authenticity of outgoing connections.
// This means that TLS requests are used, and TCP requests are not made. TLS connections
@ -87,6 +89,8 @@ type Config struct {
// PreferServerCipherSuites specifies whether to prefer the server's ciphersuite
// over the client ciphersuites.
PreferServerCipherSuites bool
EnableAgentTLSForChecks bool
}
// AppendCA opens and parses the CA file and adds the certificates to
@ -125,89 +129,6 @@ func (c *Config) skipBuiltinVerify() bool {
return c.VerifyServerHostname == false && c.ServerName == ""
}
// OutgoingTLSConfig generates a TLS configuration for outgoing
// requests. It will return a nil config if this configuration should
// not use TLS for outgoing connections.
func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
if !c.UseTLS && !c.VerifyOutgoing {
return nil, nil
}
// Create the tlsConfig
tlsConfig := &tls.Config{
RootCAs: x509.NewCertPool(),
InsecureSkipVerify: c.skipBuiltinVerify(),
ServerName: c.ServerName,
}
if len(c.CipherSuites) != 0 {
tlsConfig.CipherSuites = c.CipherSuites
}
if c.PreferServerCipherSuites {
tlsConfig.PreferServerCipherSuites = true
}
// Ensure we have a CA if VerifyOutgoing is set
if c.VerifyOutgoing && c.CAFile == "" && c.CAPath == "" {
return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!")
}
// Parse the CA certs if any
rootConfig := &rootcerts.Config{
CAFile: c.CAFile,
CAPath: c.CAPath,
}
if err := rootcerts.ConfigureTLS(tlsConfig, rootConfig); err != nil {
return nil, err
}
// Add cert/key
cert, err := c.KeyPair()
if err != nil {
return nil, err
} else if cert != nil {
tlsConfig.Certificates = []tls.Certificate{*cert}
}
// Check if a minimum TLS version was set
if c.TLSMinVersion != "" {
tlsvers, ok := TLSLookup[c.TLSMinVersion]
if !ok {
return nil, fmt.Errorf("TLSMinVersion: value %s not supported, please specify one of [tls10,tls11,tls12]", c.TLSMinVersion)
}
tlsConfig.MinVersion = tlsvers
}
return tlsConfig, nil
}
// OutgoingTLSWrapper returns a a DCWrapper based on the OutgoingTLS
// configuration. If hostname verification is on, the wrapper
// will properly generate the dynamic server name for verification.
func (c *Config) OutgoingTLSWrapper() (DCWrapper, error) {
// Get the TLS config
tlsConfig, err := c.OutgoingTLSConfig()
if err != nil {
return nil, err
}
// Check if TLS is not enabled
if tlsConfig == nil {
return nil, nil
}
// Generate the wrapper based on hostname verification
wrapper := func(dc string, conn net.Conn) (net.Conn, error) {
if c.VerifyServerHostname {
// Strip the trailing '.' from the domain if any
domain := strings.TrimSuffix(c.Domain, ".")
tlsConfig = tlsConfig.Clone()
tlsConfig.ServerName = "server." + dc + "." + domain
}
return c.wrapTLSClient(conn, tlsConfig)
}
return wrapper, nil
}
// SpecificDC is used to invoke a static datacenter
// and turns a DCWrapper into a Wrapper type.
func SpecificDC(dc string, tlsWrap DCWrapper) Wrapper {
@ -277,71 +198,158 @@ func (c *Config) wrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn,
return tlsConn, err
}
// IncomingTLSConfig generates a TLS configuration for incoming requests
func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
// Create the tlsConfig
type Configurator struct {
base *Config
}
func NewConfigurator(config *Config) *Configurator {
return &Configurator{base: config}
}
func (c *Configurator) commonTLSConfig() (*tls.Config, error) {
if c.base == nil {
return nil, fmt.Errorf("No config")
}
tlsConfig := &tls.Config{
ServerName: c.ServerName,
ClientCAs: x509.NewCertPool(),
ClientAuth: tls.NoClientCert,
ServerName: c.base.ServerName,
}
if tlsConfig.ServerName == "" {
tlsConfig.ServerName = c.NodeName
tlsConfig.ServerName = c.base.NodeName
}
// Set the cipher suites
if len(c.CipherSuites) != 0 {
tlsConfig.CipherSuites = c.CipherSuites
if len(c.base.CipherSuites) != 0 {
tlsConfig.CipherSuites = c.base.CipherSuites
}
if c.PreferServerCipherSuites {
if c.base.PreferServerCipherSuites {
tlsConfig.PreferServerCipherSuites = true
}
// Parse the CA certs if any
if c.CAFile != "" {
pool, err := rootcerts.LoadCAFile(c.CAFile)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
} else if c.CAPath != "" {
pool, err := rootcerts.LoadCAPath(c.CAPath)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
// Add cert/key
cert, err := c.KeyPair()
cert, err := c.base.KeyPair()
if err != nil {
return nil, err
} else if cert != nil {
tlsConfig.Certificates = []tls.Certificate{*cert}
}
// Check if we require verification
if c.VerifyIncoming {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
if c.CAFile == "" && c.CAPath == "" {
return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!")
}
if cert == nil {
return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!")
}
}
// Check if a minimum TLS version was set
if c.TLSMinVersion != "" {
tlsvers, ok := TLSLookup[c.TLSMinVersion]
if c.base.TLSMinVersion != "" {
tlsvers, ok := TLSLookup[c.base.TLSMinVersion]
if !ok {
return nil, fmt.Errorf("TLSMinVersion: value %s not supported, please specify one of [tls10,tls11,tls12]", c.TLSMinVersion)
return nil, fmt.Errorf("TLSMinVersion: value %s not supported, please specify one of [tls10,tls11,tls12]", c.base.TLSMinVersion)
}
tlsConfig.MinVersion = tlsvers
}
return tlsConfig, nil
}
func (c *Configurator) outgoingTLSConfig() (*tls.Config, error) {
tlsConfig, err := c.commonTLSConfig()
if err != nil {
return nil, err
}
tlsConfig.RootCAs = x509.NewCertPool()
tlsConfig.InsecureSkipVerify = c.base.skipBuiltinVerify()
// Ensure we have a CA if VerifyOutgoing is set
if c.base.VerifyOutgoing && c.base.CAFile == "" && c.base.CAPath == "" {
return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!")
}
// Parse the CA certs if any
rootConfig := &rootcerts.Config{
CAFile: c.base.CAFile,
CAPath: c.base.CAPath,
}
if err := rootcerts.ConfigureTLS(tlsConfig, rootConfig); err != nil {
return nil, err
}
return tlsConfig, nil
}
func (c *Configurator) incomingTLSConfig(verify bool) (*tls.Config, error) {
tlsConfig, err := c.commonTLSConfig()
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = x509.NewCertPool()
tlsConfig.ClientAuth = tls.NoClientCert
// Parse the CA certs if any
if c.base.CAFile != "" {
pool, err := rootcerts.LoadCAFile(c.base.CAFile)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
} else if c.base.CAPath != "" {
pool, err := rootcerts.LoadCAPath(c.base.CAPath)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if verify {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
if c.base.CAFile == "" && c.base.CAPath == "" {
return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!")
}
if len(tlsConfig.Certificates) == 0 {
return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!")
}
}
return tlsConfig, nil
}
func (c *Configurator) IncomingRPCConfig() (*tls.Config, error) {
return c.incomingTLSConfig(c.base.VerifyIncoming || c.base.VerifyIncomingRPC)
}
func (c *Configurator) IncomingHTTPSConfig() (*tls.Config, error) {
return c.incomingTLSConfig(c.base.VerifyIncoming || c.base.VerifyIncomingHTTPS)
}
func (c *Configurator) OutgoingRPCConfig() (*tls.Config, error) {
useTLS := c.base.CAFile != "" || c.base.CAPath != "" || c.base.VerifyOutgoing
if !useTLS {
return nil, nil
}
return c.outgoingTLSConfig()
}
func (c *Configurator) OutgoingRPCWrapper() (DCWrapper, error) {
// Get the TLS config
tlsConfig, err := c.OutgoingRPCConfig()
if err != nil {
return nil, err
}
// Check if TLS is not enabled
if tlsConfig == nil {
return nil, nil
}
// Generate the wrapper based on hostname verification
wrapper := func(dc string, conn net.Conn) (net.Conn, error) {
if c.base.VerifyServerHostname {
// Strip the trailing '.' from the domain if any
domain := strings.TrimSuffix(c.base.Domain, ".")
tlsConfig = tlsConfig.Clone()
tlsConfig.ServerName = "server." + dc + "." + domain
}
return c.base.wrapTLSClient(conn, tlsConfig)
}
return wrapper, nil
}
// ParseCiphers parse ciphersuites from the comma-separated string into recognized slice
func ParseCiphers(cipherStr string) ([]uint16, error) {
suites := []uint16{}

View File

@ -40,20 +40,6 @@ func TestConfig_CACertificate_Valid(t *testing.T) {
}
}
func TestConfig_CAPath_Valid(t *testing.T) {
conf := &Config{
CAPath: "../test/ca_path",
}
tlsConf, err := conf.IncomingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(tlsConf.ClientCAs.Subjects()) != 2 {
t.Fatalf("expected certs")
}
}
func TestConfig_KeyPair_None(t *testing.T) {
conf := &Config{}
cert, err := conf.KeyPair()
@ -79,53 +65,38 @@ func TestConfig_KeyPair_Valid(t *testing.T) {
}
}
func TestConfig_OutgoingTLS_MissingCA(t *testing.T) {
func TestConfigurator_OutgoingTLS_MissingCA(t *testing.T) {
conf := &Config{
VerifyOutgoing: true,
}
tls, err := conf.OutgoingTLSConfig()
if err == nil {
t.Fatalf("expected err")
}
if tls != nil {
t.Fatalf("bad: %v", tls)
}
c := NewConfigurator(conf)
tlsConf, err := c.OutgoingRPCConfig()
require.Error(t, err)
require.Nil(t, tlsConf)
}
func TestConfig_OutgoingTLS_OnlyCA(t *testing.T) {
func TestConfigurator_OutgoingTLS_OnlyCA(t *testing.T) {
conf := &Config{
CAFile: "../test/ca/root.cer",
}
tls, err := conf.OutgoingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tls != nil {
t.Fatalf("expected no config")
}
c := NewConfigurator(conf)
tlsConf, err := c.OutgoingRPCConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
}
func TestConfig_OutgoingTLS_VerifyOutgoing(t *testing.T) {
func TestConfigurator_OutgoingTLS_VerifyOutgoing(t *testing.T) {
conf := &Config{
VerifyOutgoing: true,
CAFile: "../test/ca/root.cer",
}
tls, err := conf.OutgoingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tls == nil {
t.Fatalf("expected config")
}
if len(tls.RootCAs.Subjects()) != 1 {
t.Fatalf("expect root cert")
}
if tls.ServerName != "" {
t.Fatalf("expect no server name verification")
}
if !tls.InsecureSkipVerify {
t.Fatalf("should skip built-in verification")
}
c := NewConfigurator(conf)
tlsConf, err := c.OutgoingRPCConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
require.Equal(t, len(tlsConf.RootCAs.Subjects()), 1)
require.Empty(t, tlsConf.ServerName)
require.True(t, tlsConf.InsecureSkipVerify)
}
func TestConfig_SkipBuiltinVerify(t *testing.T) {
@ -145,77 +116,51 @@ func TestConfig_SkipBuiltinVerify(t *testing.T) {
}
}
func TestConfig_OutgoingTLS_ServerName(t *testing.T) {
func TestConfigurator_OutgoingTLS_ServerName(t *testing.T) {
conf := &Config{
VerifyOutgoing: true,
CAFile: "../test/ca/root.cer",
ServerName: "consul.example.com",
}
tls, err := conf.OutgoingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tls == nil {
t.Fatalf("expected config")
}
if len(tls.RootCAs.Subjects()) != 1 {
t.Fatalf("expect root cert")
}
if tls.ServerName != "consul.example.com" {
t.Fatalf("expect server name")
}
if tls.InsecureSkipVerify {
t.Fatalf("should not skip built-in verification")
}
c := NewConfigurator(conf)
tlsConf, err := c.OutgoingRPCConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
require.Equal(t, len(tlsConf.RootCAs.Subjects()), 1)
require.Equal(t, tlsConf.ServerName, "consul.example.com")
require.False(t, tlsConf.InsecureSkipVerify)
}
func TestConfig_OutgoingTLS_VerifyHostname(t *testing.T) {
func TestConfigurator_OutgoingTLS_VerifyHostname(t *testing.T) {
conf := &Config{
VerifyOutgoing: true,
VerifyServerHostname: true,
CAFile: "../test/ca/root.cer",
}
tls, err := conf.OutgoingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tls == nil {
t.Fatalf("expected config")
}
if len(tls.RootCAs.Subjects()) != 1 {
t.Fatalf("expect root cert")
}
if tls.InsecureSkipVerify {
t.Fatalf("should not skip built-in verification")
}
c := NewConfigurator(conf)
tlsConf, err := c.OutgoingRPCConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
require.Equal(t, len(tlsConf.RootCAs.Subjects()), 1)
require.False(t, tlsConf.InsecureSkipVerify)
}
func TestConfig_OutgoingTLS_WithKeyPair(t *testing.T) {
func TestConfigurator_OutgoingTLS_WithKeyPair(t *testing.T) {
conf := &Config{
VerifyOutgoing: true,
CAFile: "../test/ca/root.cer",
CertFile: "../test/key/ourdomain.cer",
KeyFile: "../test/key/ourdomain.key",
}
tls, err := conf.OutgoingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tls == nil {
t.Fatalf("expected config")
}
if len(tls.RootCAs.Subjects()) != 1 {
t.Fatalf("expect root cert")
}
if !tls.InsecureSkipVerify {
t.Fatalf("should skip verification")
}
if len(tls.Certificates) != 1 {
t.Fatalf("expected client cert")
}
c := NewConfigurator(conf)
tlsConf, err := c.OutgoingRPCConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
require.True(t, tlsConf.InsecureSkipVerify)
require.Equal(t, len(tlsConf.Certificates), 1)
}
func TestConfig_OutgoingTLS_TLSMinVersion(t *testing.T) {
func TestConfigurator_OutgoingTLS_TLSMinVersion(t *testing.T) {
tlsVersions := []string{"tls10", "tls11", "tls12"}
for _, version := range tlsVersions {
conf := &Config{
@ -223,23 +168,19 @@ func TestConfig_OutgoingTLS_TLSMinVersion(t *testing.T) {
CAFile: "../test/ca/root.cer",
TLSMinVersion: version,
}
tls, err := conf.OutgoingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tls == nil {
t.Fatalf("expected config")
}
if tls.MinVersion != TLSLookup[version] {
t.Fatalf("expected tls min version: %v, %v", tls.MinVersion, TLSLookup[version])
}
c := NewConfigurator(conf)
tlsConf, err := c.OutgoingRPCConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
require.Equal(t, tlsConf.MinVersion, TLSLookup[version])
}
}
func startTLSServer(config *Config) (net.Conn, chan error) {
errc := make(chan error, 1)
tlsConfigServer, err := config.IncomingTLSConfig()
c := NewConfigurator(config)
tlsConfigServer, err := c.IncomingRPCConfig()
if err != nil {
errc <- err
return nil, errc
@ -273,7 +214,7 @@ func startTLSServer(config *Config) (net.Conn, chan error) {
return clientConn, errc
}
func TestConfig_outgoingWrapper_OK(t *testing.T) {
func TestConfigurator_outgoingWrapper_OK(t *testing.T) {
config := &Config{
CAFile: "../test/hostname/CertAuth.crt",
CertFile: "../test/hostname/Alice.crt",
@ -288,27 +229,22 @@ func TestConfig_outgoingWrapper_OK(t *testing.T) {
t.Fatalf("startTLSServer err: %v", <-errc)
}
wrap, err := config.OutgoingTLSWrapper()
if err != nil {
t.Fatalf("OutgoingTLSWrapper err: %v", err)
}
c := NewConfigurator(config)
wrap, err := c.OutgoingRPCWrapper()
require.NoError(t, err)
tlsClient, err := wrap("dc1", client)
if err != nil {
t.Fatalf("wrapTLS err: %v", err)
}
require.NoError(t, err)
defer tlsClient.Close()
if err := tlsClient.(*tls.Conn).Handshake(); err != nil {
t.Fatalf("write err: %v", err)
}
err = tlsClient.(*tls.Conn).Handshake()
require.NoError(t, err)
err = <-errc
if err != nil {
t.Fatalf("server: %v", err)
}
require.NoError(t, err)
}
func TestConfig_outgoingWrapper_BadDC(t *testing.T) {
func TestConfigurator_outgoingWrapper_BadDC(t *testing.T) {
config := &Config{
CAFile: "../test/hostname/CertAuth.crt",
CertFile: "../test/hostname/Alice.crt",
@ -323,25 +259,22 @@ func TestConfig_outgoingWrapper_BadDC(t *testing.T) {
t.Fatalf("startTLSServer err: %v", <-errc)
}
wrap, err := config.OutgoingTLSWrapper()
if err != nil {
t.Fatalf("OutgoingTLSWrapper err: %v", err)
}
c := NewConfigurator(config)
wrap, err := c.OutgoingRPCWrapper()
require.NoError(t, err)
tlsClient, err := wrap("dc2", client)
if err != nil {
t.Fatalf("wrapTLS err: %v", err)
}
require.NoError(t, err)
err = tlsClient.(*tls.Conn).Handshake()
if _, ok := err.(x509.HostnameError); !ok {
t.Fatalf("should get hostname err: %v", err)
}
_, ok := err.(x509.HostnameError)
require.True(t, ok)
tlsClient.Close()
<-errc
}
func TestConfig_outgoingWrapper_BadCert(t *testing.T) {
func TestConfigurator_outgoingWrapper_BadCert(t *testing.T) {
config := &Config{
CAFile: "../test/ca/root.cer",
CertFile: "../test/key/ourdomain.cer",
@ -356,15 +289,13 @@ func TestConfig_outgoingWrapper_BadCert(t *testing.T) {
t.Fatalf("startTLSServer err: %v", <-errc)
}
wrap, err := config.OutgoingTLSWrapper()
if err != nil {
t.Fatalf("OutgoingTLSWrapper err: %v", err)
}
c := NewConfigurator(config)
wrap, err := c.OutgoingRPCWrapper()
require.NoError(t, err)
tlsClient, err := wrap("dc1", client)
if err != nil {
t.Fatalf("wrapTLS err: %v", err)
}
require.NoError(t, err)
err = tlsClient.(*tls.Conn).Handshake()
if _, ok := err.(x509.HostnameError); !ok {
t.Fatalf("should get hostname err: %v", err)
@ -374,7 +305,7 @@ func TestConfig_outgoingWrapper_BadCert(t *testing.T) {
<-errc
}
func TestConfig_wrapTLS_OK(t *testing.T) {
func TestConfigurator_wrapTLS_OK(t *testing.T) {
config := &Config{
CAFile: "../test/ca/root.cer",
CertFile: "../test/key/ourdomain.cer",
@ -387,24 +318,19 @@ func TestConfig_wrapTLS_OK(t *testing.T) {
t.Fatalf("startTLSServer err: %v", <-errc)
}
clientConfig, err := config.OutgoingTLSConfig()
if err != nil {
t.Fatalf("OutgoingTLSConfig err: %v", err)
}
c := NewConfigurator(config)
clientConfig, err := c.OutgoingRPCConfig()
require.NoError(t, err)
tlsClient, err := config.wrapTLSClient(client, clientConfig)
if err != nil {
t.Fatalf("wrapTLS err: %v", err)
} else {
tlsClient.Close()
}
require.NoError(t, err)
tlsClient.Close()
err = <-errc
if err != nil {
t.Fatalf("server: %v", err)
}
require.NoError(t, err)
}
func TestConfig_wrapTLS_BadCert(t *testing.T) {
func TestConfigurator_wrapTLS_BadCert(t *testing.T) {
serverConfig := &Config{
CertFile: "../test/key/ssl-cert-snakeoil.pem",
KeyFile: "../test/key/ssl-cert-snakeoil.key",
@ -420,114 +346,16 @@ func TestConfig_wrapTLS_BadCert(t *testing.T) {
VerifyOutgoing: true,
}
clientTLSConfig, err := clientConfig.OutgoingTLSConfig()
if err != nil {
t.Fatalf("OutgoingTLSConfig err: %v", err)
}
c := NewConfigurator(clientConfig)
clientTLSConfig, err := c.OutgoingRPCConfig()
require.NoError(t, err)
tlsClient, err := clientConfig.wrapTLSClient(client, clientTLSConfig)
if err == nil {
t.Fatalf("wrapTLS no err")
}
if tlsClient != nil {
t.Fatalf("returned a client")
}
require.Error(t, err)
require.Nil(t, tlsClient)
err = <-errc
if err != nil {
t.Fatalf("server: %v", err)
}
}
func TestConfig_IncomingTLS(t *testing.T) {
conf := &Config{
VerifyIncoming: true,
CAFile: "../test/ca/root.cer",
CertFile: "../test/key/ourdomain.cer",
KeyFile: "../test/key/ourdomain.key",
}
tlsC, err := conf.IncomingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tlsC == nil {
t.Fatalf("expected config")
}
if len(tlsC.ClientCAs.Subjects()) != 1 {
t.Fatalf("expect client cert")
}
if tlsC.ClientAuth != tls.RequireAndVerifyClientCert {
t.Fatalf("should not skip verification")
}
if len(tlsC.Certificates) != 1 {
t.Fatalf("expected client cert")
}
}
func TestConfig_IncomingTLS_MissingCA(t *testing.T) {
conf := &Config{
VerifyIncoming: true,
CertFile: "../test/key/ourdomain.cer",
KeyFile: "../test/key/ourdomain.key",
}
_, err := conf.IncomingTLSConfig()
if err == nil {
t.Fatalf("expected err")
}
}
func TestConfig_IncomingTLS_MissingKey(t *testing.T) {
conf := &Config{
VerifyIncoming: true,
CAFile: "../test/ca/root.cer",
}
_, err := conf.IncomingTLSConfig()
if err == nil {
t.Fatalf("expected err")
}
}
func TestConfig_IncomingTLS_NoVerify(t *testing.T) {
conf := &Config{}
tlsC, err := conf.IncomingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tlsC == nil {
t.Fatalf("expected config")
}
if len(tlsC.ClientCAs.Subjects()) != 0 {
t.Fatalf("do not expect client cert")
}
if tlsC.ClientAuth != tls.NoClientCert {
t.Fatalf("should skip verification")
}
if len(tlsC.Certificates) != 0 {
t.Fatalf("unexpected client cert")
}
}
func TestConfig_IncomingTLS_TLSMinVersion(t *testing.T) {
tlsVersions := []string{"tls10", "tls11", "tls12"}
for _, version := range tlsVersions {
conf := &Config{
VerifyIncoming: true,
CAFile: "../test/ca/root.cer",
CertFile: "../test/key/ourdomain.cer",
KeyFile: "../test/key/ourdomain.key",
TLSMinVersion: version,
}
tls, err := conf.IncomingTLSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if tls == nil {
t.Fatalf("expected config")
}
if tls.MinVersion != TLSLookup[version] {
t.Fatalf("expected tls min version: %v, %v", tls.MinVersion, TLSLookup[version])
}
}
require.NoError(t, err)
}
func TestConfig_ParseCiphers(t *testing.T) {
@ -592,3 +420,93 @@ func TestConfig_ParseCiphers(t *testing.T) {
t.Fatal("should fail on unsupported cipherX")
}
}
func TestConfigurator_IncomingHTTPSConfig_CA_PATH(t *testing.T) {
conf := &Config{CAPath: "../test/ca_path"}
c := NewConfigurator(conf)
tlsConf, err := c.IncomingHTTPSConfig()
require.NoError(t, err)
require.Equal(t, len(tlsConf.ClientCAs.Subjects()), 2)
}
func TestConfigurator_IncomingHTTPS(t *testing.T) {
conf := &Config{
VerifyIncoming: true,
CAFile: "../test/ca/root.cer",
CertFile: "../test/key/ourdomain.cer",
KeyFile: "../test/key/ourdomain.key",
}
c := NewConfigurator(conf)
tlsConf, err := c.IncomingHTTPSConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
require.Equal(t, len(tlsConf.ClientCAs.Subjects()), 1)
require.Equal(t, tlsConf.ClientAuth, tls.RequireAndVerifyClientCert)
require.Equal(t, len(tlsConf.Certificates), 1)
}
func TestConfigurator_IncomingHTTPS_MissingCA(t *testing.T) {
conf := &Config{
VerifyIncoming: true,
CertFile: "../test/key/ourdomain.cer",
KeyFile: "../test/key/ourdomain.key",
}
c := NewConfigurator(conf)
_, err := c.IncomingHTTPSConfig()
require.Error(t, err)
}
func TestConfigurator_IncomingHTTPS_MissingKey(t *testing.T) {
conf := &Config{
VerifyIncoming: true,
CAFile: "../test/ca/root.cer",
}
c := NewConfigurator(conf)
_, err := c.IncomingHTTPSConfig()
require.Error(t, err)
}
func TestConfigurator_IncomingHTTPS_NoVerify(t *testing.T) {
conf := &Config{}
c := NewConfigurator(conf)
tlsConf, err := c.IncomingHTTPSConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
require.Equal(t, len(tlsConf.ClientCAs.Subjects()), 0)
require.Equal(t, tlsConf.ClientAuth, tls.NoClientCert)
require.Equal(t, len(tlsConf.Certificates), 0)
}
func TestConfigurator_IncomingHTTPS_TLSMinVersion(t *testing.T) {
tlsVersions := []string{"tls10", "tls11", "tls12"}
for _, version := range tlsVersions {
conf := &Config{
VerifyIncoming: true,
CAFile: "../test/ca/root.cer",
CertFile: "../test/key/ourdomain.cer",
KeyFile: "../test/key/ourdomain.key",
TLSMinVersion: version,
}
c := NewConfigurator(conf)
tlsConf, err := c.IncomingHTTPSConfig()
require.NoError(t, err)
require.NotNil(t, tlsConf)
require.Equal(t, tlsConf.MinVersion, TLSLookup[version])
}
}
func TestConfigurator_IncomingHTTPSCAPath_Valid(t *testing.T) {
conf := &Config{
CAPath: "../test/ca_path",
}
c := NewConfigurator(conf)
tlsConf, err := c.IncomingHTTPSConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(tlsConf.ClientCAs.Subjects()) != 2 {
t.Fatalf("expected certs")
}
}