Merge pull request #28 from hashicorp/f-tls

Adding support for TLS
This commit is contained in:
Armon Dadgar 2014-04-07 15:18:38 -07:00
commit 19ce2e920d
24 changed files with 858 additions and 43 deletions

5
.gitignore vendored
View File

@ -23,7 +23,6 @@ _testmain.go
*.test
bin/
.vagrant/
website/npm-debug.log
*.old
*.attr

View File

@ -169,6 +169,13 @@ func (a *Agent) consulConfig() *consul.Config {
base.ProtocolVersion = uint8(a.config.Protocol)
}
// Copy the TLS configuration
base.VerifyIncoming = a.config.VerifyIncoming
base.VerifyOutgoing = a.config.VerifyOutgoing
base.CAFile = a.config.CAFile
base.CertFile = a.config.CertFile
base.KeyFile = a.config.KeyFile
// Setup the ServerUp callback
base.ServerUp = a.state.ConsulServerUp

View File

@ -103,6 +103,28 @@ type Config struct {
// EnableDebug is used to enable various debugging features
EnableDebug bool `mapstructure:"enable_debug"`
// 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 `mapstructure:"verify_incoming"`
// VerifyOutgoing is used to verify the authenticity of outgoing connections.
// This means that TLS requests are used. TLS connections must match a provided
// certificate authority. This is used to verify authenticity of server nodes.
VerifyOutgoing bool `mapstructure:"verify_outgoing"`
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
// or VerifyOutgoing to verify the TLS connection.
CAFile string `mapstructure:"ca_file"`
// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
// Must be provided to serve TLS connections.
CertFile string `mapstructure:"cert_file"`
// KeyFile is used to provide a TLS key that is used for serving TLS connections.
// Must be provided to serve TLS connections.
KeyFile string `mapstructure:"key_file"`
// Checks holds the provided check definitions
Checks []*CheckDefinition `mapstructure:"-"`
@ -335,6 +357,21 @@ func MergeConfig(a, b *Config) *Config {
if b.EnableDebug {
result.EnableDebug = true
}
if b.VerifyIncoming {
result.VerifyIncoming = true
}
if b.VerifyOutgoing {
result.VerifyOutgoing = true
}
if b.CAFile != "" {
result.CAFile = b.CAFile
}
if b.CertFile != "" {
result.CertFile = b.CertFile
}
if b.KeyFile != "" {
result.KeyFile = b.KeyFile
}
if b.Checks != nil {
result.Checks = append(result.Checks, b.Checks...)
}

View File

@ -193,6 +193,38 @@ func TestDecodeConfig(t *testing.T) {
if config.EnableDebug != true {
t.Fatalf("bad: %#v", config)
}
// TLS
input = `{"verify_incoming": true, "verify_outgoing": true}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
}
if config.VerifyIncoming != true {
t.Fatalf("bad: %#v", config)
}
if config.VerifyOutgoing != true {
t.Fatalf("bad: %#v", config)
}
// TLS keys
input = `{"ca_file": "my/ca/file", "cert_file": "my.cert", "key_file": "key.pem"}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
}
if config.CAFile != "my/ca/file" {
t.Fatalf("bad: %#v", config)
}
if config.CertFile != "my.cert" {
t.Fatalf("bad: %#v", config)
}
if config.KeyFile != "key.pem" {
t.Fatalf("bad: %#v", config)
}
}
func TestDecodeConfig_Service(t *testing.T) {
@ -318,6 +350,11 @@ func TestMergeConfig(t *testing.T) {
LeaveOnTerm: true,
SkipLeaveOnInt: true,
EnableDebug: true,
VerifyIncoming: true,
VerifyOutgoing: true,
CAFile: "test/ca.pem",
CertFile: "test/cert.pem",
KeyFile: "test/key.pem",
Checks: []*CheckDefinition{nil},
Services: []*ServiceDefinition{nil},
}

View File

@ -1,6 +1,7 @@
package consul
import (
"crypto/tls"
"fmt"
"github.com/hashicorp/consul/consul/structs"
"github.com/hashicorp/serf/serf"
@ -78,13 +79,22 @@ func NewClient(config *Config) (*Client, error) {
config.LogOutput = os.Stderr
}
// Create the tlsConfig
var tlsConfig *tls.Config
var err error
if config.VerifyOutgoing {
if tlsConfig, err = config.OutgoingTLSConfig(); err != nil {
return nil, err
}
}
// Create a logger
logger := log.New(config.LogOutput, "", log.LstdFlags)
// Create server
c := &Client{
config: config,
connPool: NewPool(clientRPCCache),
connPool: NewPool(clientRPCCache, tlsConfig),
eventCh: make(chan serf.Event, 256),
logger: logger,
shutdownCh: make(chan struct{}),
@ -94,7 +104,6 @@ func NewClient(config *Config) (*Client, error) {
go c.lanEventHandler()
// Initialize the lan Serf
var err error
c.serf, err = c.setupSerf(config.SerfLANConfig,
c.eventCh, serfLANSnapshot)
if err != nil {

View File

@ -9,14 +9,10 @@ import (
"time"
)
func testClient(t *testing.T) (string, *Client) {
return testClientDC(t, "dc1")
}
func testClientDC(t *testing.T, dc string) (string, *Client) {
func testClientConfig(t *testing.T) (string, *Config) {
dir := tmpDir(t)
config := DefaultConfig()
config.Datacenter = dc
config.Datacenter = "dc1"
config.DataDir = dir
// Adjust the ports
@ -32,6 +28,17 @@ func testClientDC(t *testing.T, dc string) (string, *Client) {
config.SerfLANConfig.MemberlistConfig.ProbeInterval = time.Second
config.SerfLANConfig.MemberlistConfig.GossipInterval = 100 * time.Millisecond
return dir, config
}
func testClient(t *testing.T) (string, *Client) {
return testClientDC(t, "dc1")
}
func testClientDC(t *testing.T, dc string) (string, *Client) {
dir, config := testClientConfig(t)
config.Datacenter = dc
client, err := NewClient(config)
if err != nil {
t.Fatalf("err: %v", err)
@ -119,3 +126,55 @@ func TestClient_RPC(t *testing.T) {
t.Fatalf("err: %v", err)
}
}
func TestClient_RPC_TLS(t *testing.T) {
dir1, conf1 := testServerConfig(t)
conf1.VerifyIncoming = true
conf1.VerifyOutgoing = true
configureTLS(conf1)
s1, err := NewServer(conf1)
if err != nil {
t.Fatalf("err: %v", err)
}
defer os.RemoveAll(dir1)
defer s1.Shutdown()
dir2, conf2 := testClientConfig(t)
conf2.VerifyOutgoing = true
configureTLS(conf2)
c1, err := NewClient(conf2)
if err != nil {
t.Fatalf("err: %v", err)
}
defer os.RemoveAll(dir2)
defer c1.Shutdown()
// Try an RPC
var out struct{}
if err := c1.RPC("Status.Ping", struct{}{}, &out); err != structs.ErrNoServers {
t.Fatalf("err: %v", err)
}
// Try to join
addr := fmt.Sprintf("127.0.0.1:%d",
s1.config.SerfLANConfig.MemberlistConfig.BindPort)
if _, err := c1.JoinLAN([]string{addr}); err != nil {
t.Fatalf("err: %v", err)
}
// Check the members
if len(s1.LANMembers()) != 2 {
t.Fatalf("bad len")
}
if len(c1.LANMembers()) != 2 {
t.Fatalf("bad len")
}
time.Sleep(10 * time.Millisecond)
// RPC shoudl succeed
if err := c1.RPC("Status.Ping", struct{}{}, &out); err != nil {
t.Fatalf("err: %v", err)
}
}

View File

@ -1,11 +1,15 @@
package consul
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/hashicorp/memberlist"
"github.com/hashicorp/raft"
"github.com/hashicorp/serf/serf"
"io"
"io/ioutil"
"net"
"os"
"time"
@ -81,6 +85,29 @@ type Config struct {
// ProtocolVersionMin and ProtocolVersionMax.
ProtocolVersion uint8
// 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
// 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
// must match a provided certificate authority. This is used to verify authenticity of
// server nodes.
VerifyOutgoing bool
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
// or VerifyOutgoing to verify the TLS connection.
CAFile string
// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
// Must be provided to serve TLS connections.
CertFile string
// KeyFile is used to provide a TLS key that is used for serving TLS connections.
// Must be provided to serve TLS connections.
KeyFile string
// ServerUp callback can be used to trigger a notification that
// a Consul server is now up and known about.
ServerUp func()
@ -98,6 +125,113 @@ func (c *Config) CheckVersion() error {
return nil
}
// CACertificate is used to open and parse a CA file
func (c *Config) CACertificate() (*x509.Certificate, error) {
if c.CAFile == "" {
return nil, nil
}
// Read the file
data, err := ioutil.ReadFile(c.CAFile)
if err != nil {
return nil, fmt.Errorf("Failed to read CA file: %v", err)
}
// Decode from the PEM format
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("Failed to decode CA PEM!")
}
// Parse the certificate
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("Failed to parse CA file: %v", err)
}
return cert, nil
}
// KeyPair is used to open and parse a certificate and key file
func (c *Config) KeyPair() (*tls.Certificate, error) {
if c.CertFile == "" || c.KeyFile == "" {
return nil, nil
}
cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
if err != nil {
return nil, fmt.Errorf("Failed to load cert/key pair: %v", err)
}
return &cert, err
}
// OutgoingTLSConfig generates a TLS configuration for outgoing requests
func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
// Create the tlsConfig
tlsConfig := &tls.Config{
RootCAs: x509.NewCertPool(),
InsecureSkipVerify: !c.VerifyOutgoing,
}
// Parse the CA cert if any
ca, err := c.CACertificate()
if err != nil {
return nil, err
} else if ca != nil {
tlsConfig.RootCAs.AddCert(ca)
}
// Ensure we have a CA if VerifyOutgoing is set
if c.VerifyOutgoing && ca == nil {
return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!")
}
// Add cert/key
cert, err := c.KeyPair()
if err != nil {
return nil, err
} else if cert != nil {
tlsConfig.Certificates = []tls.Certificate{*cert}
}
return tlsConfig, nil
}
// IncomingTLSConfig generates a TLS configuration for incoming requests
func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
// Create the tlsConfig
tlsConfig := &tls.Config{
ClientCAs: x509.NewCertPool(),
ClientAuth: tls.NoClientCert,
}
// Parse the CA cert if any
ca, err := c.CACertificate()
if err != nil {
return nil, err
} else if ca != nil {
tlsConfig.ClientCAs.AddCert(ca)
}
// Add cert/key
cert, err := c.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 ca == nil {
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!")
}
}
return tlsConfig, nil
}
// DefaultConfig is used to return a sane default configuration
func DefaultConfig() *Config {
hostname, err := os.Hostname()

200
consul/config_test.go Normal file
View File

@ -0,0 +1,200 @@
package consul
import (
"crypto/tls"
"testing"
)
func TestConfig_CACertificate_None(t *testing.T) {
conf := &Config{}
cert, err := conf.CACertificate()
if err != nil {
t.Fatalf("err: %v", err)
}
if cert != nil {
t.Fatalf("bad: %v", cert)
}
}
func TestConfig_CACertificate_Valid(t *testing.T) {
conf := &Config{
CAFile: "../test/ca/root.cer",
}
cert, err := conf.CACertificate()
if err != nil {
t.Fatalf("err: %v", err)
}
if cert == nil {
t.Fatalf("expected cert")
}
}
func TestConfig_KeyPair_None(t *testing.T) {
conf := &Config{}
cert, err := conf.KeyPair()
if err != nil {
t.Fatalf("err: %v", err)
}
if cert != nil {
t.Fatalf("bad: %v", cert)
}
}
func TestConfig_KeyPair_Valid(t *testing.T) {
conf := &Config{
CertFile: "../test/key/ourdomain.cer",
KeyFile: "../test/key/ourdomain.key",
}
cert, err := conf.KeyPair()
if err != nil {
t.Fatalf("err: %v", err)
}
if cert == nil {
t.Fatalf("expected cert")
}
}
func TestConfig_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)
}
}
func TestConfig_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 config")
}
if len(tls.RootCAs.Subjects()) != 1 {
t.Fatalf("expect root cert")
}
if !tls.InsecureSkipVerify {
t.Fatalf("expect to skip verification")
}
}
func TestConfig_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.InsecureSkipVerify {
t.Fatalf("should not skip verification")
}
}
func TestConfig_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 not skip verification")
}
if len(tls.Certificates) != 1 {
t.Fatalf("expected client cert")
}
}
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")
}
}

View File

@ -1,6 +1,7 @@
package consul
import (
"crypto/tls"
"fmt"
"github.com/inconshreveable/muxado"
"github.com/ugorji/go/codec"
@ -37,6 +38,9 @@ type ConnPool struct {
// Pool maps an address to a open connection
pool map[string]*Conn
// TLS settings
tlsConfig *tls.Config
// Used to indicate the pool is shutdown
shutdown bool
shutdownCh chan struct{}
@ -44,11 +48,13 @@ type ConnPool struct {
// NewPool is used to make a new connection pool
// Maintain at most one connection per host, for up to maxTime.
// Set maxTime to 0 to disable reaping.
func NewPool(maxTime time.Duration) *ConnPool {
// Set maxTime to 0 to disable reaping. If TLS settings are provided
// outgoing connections use TLS.
func NewPool(maxTime time.Duration, tlsConfig *tls.Config) *ConnPool {
pool := &ConnPool{
maxTime: maxTime,
pool: make(map[string]*Conn),
tlsConfig: tlsConfig,
shutdownCh: make(chan struct{}),
}
if maxTime > 0 {
@ -104,20 +110,34 @@ func (p *ConnPool) getPooled(addr net.Addr) *Conn {
// getNewConn is used to return a new connection
func (p *ConnPool) getNewConn(addr net.Addr) (*Conn, error) {
// Try to dial the conn
rawConn, err := net.DialTimeout("tcp", addr.String(), 10*time.Second)
conn, err := net.DialTimeout("tcp", addr.String(), 10*time.Second)
if err != nil {
return nil, err
}
// Cast to TCPConn
conn := rawConn.(*net.TCPConn)
if tcp, ok := conn.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
tcp.SetNoDelay(true)
}
// Enable keep alives
conn.SetKeepAlive(true)
conn.SetNoDelay(true)
// Check if TLS is enabled
if p.tlsConfig != nil {
// Switch the connection into TLS mode
if _, err := conn.Write([]byte{byte(rpcTLS)}); err != nil {
conn.Close()
return nil, err
}
// Wrap the connection in a TLS client
conn = tls.Client(conn, p.tlsConfig)
}
// Write the Consul multiplex byte to set the mode
conn.Write([]byte{byte(rpcMultiplex)})
if _, err := conn.Write([]byte{byte(rpcMultiplex)}); err != nil {
conn.Close()
return nil, err
}
// Create a multiplexed session
session := muxado.Client(conn)

View File

@ -1,6 +1,7 @@
package consul
import (
"crypto/tls"
"fmt"
"net"
"sync"
@ -16,6 +17,9 @@ type RaftLayer struct {
// connCh is used to accept connections
connCh chan net.Conn
// TLS configuration
tlsConfig *tls.Config
// Tracks if we are closed
closed bool
closeCh chan struct{}
@ -23,12 +27,14 @@ type RaftLayer struct {
}
// NewRaftLayer is used to initialize a new RaftLayer which can
// be used as a StreamLayer for Raft
func NewRaftLayer(addr net.Addr) *RaftLayer {
// be used as a StreamLayer for Raft. If a tlsConfig is provided,
// then the connection will use TLS.
func NewRaftLayer(addr net.Addr, tlsConfig *tls.Config) *RaftLayer {
layer := &RaftLayer{
addr: addr,
connCh: make(chan net.Conn),
closeCh: make(chan struct{}),
addr: addr,
connCh: make(chan net.Conn),
tlsConfig: tlsConfig,
closeCh: make(chan struct{}),
}
return layer
}
@ -79,6 +85,18 @@ func (l *RaftLayer) Dial(address string, timeout time.Duration) (net.Conn, error
return nil, err
}
// Check for tls mode
if l.tlsConfig != nil {
// Switch the connection into TLS mode
if _, err := conn.Write([]byte{byte(rpcTLS)}); err != nil {
conn.Close()
return nil, err
}
// Wrap the connection in a TLS client
conn = tls.Client(conn, l.tlsConfig)
}
// Write the Raft byte to set the mode
_, err = conn.Write([]byte{byte(rpcRaft)})
if err != nil {

View File

@ -1,6 +1,7 @@
package consul
import (
"crypto/tls"
"fmt"
"github.com/armon/go-metrics"
"github.com/hashicorp/consul/consul/structs"
@ -19,6 +20,7 @@ const (
rpcConsul RPCType = iota
rpcRaft
rpcMultiplex
rpcTLS
)
const (
@ -43,14 +45,14 @@ func (s *Server) listen() {
s.rpcClients[conn] = struct{}{}
s.rpcClientLock.Unlock()
go s.handleConn(conn)
go s.handleConn(conn, false)
metrics.IncrCounter([]string{"consul", "rpc", "accept_conn"}, 1)
}
}
// handleConn is used to determine if this is a Raft or
// Consul type RPC connection and invoke the correct handler
func (s *Server) handleConn(conn net.Conn) {
func (s *Server) handleConn(conn net.Conn, isTLS bool) {
// Read a single byte
buf := make([]byte, 1)
if _, err := conn.Read(buf); err != nil {
@ -59,6 +61,13 @@ func (s *Server) handleConn(conn net.Conn) {
return
}
// Enforce TLS if VerifyIncoming is set
if s.config.VerifyIncoming && !isTLS && RPCType(buf[0]) != rpcTLS {
s.logger.Printf("[WARN] consul.rpc: Non-TLS connection attempted with VerifyIncoming set")
conn.Close()
return
}
// Switch on the byte
switch RPCType(buf[0]) {
case rpcConsul:
@ -71,6 +80,15 @@ func (s *Server) handleConn(conn net.Conn) {
case rpcMultiplex:
s.handleMultiplex(conn)
case rpcTLS:
if s.rpcTLS == nil {
s.logger.Printf("[WARN] consul.rpc: TLS connection attempted, server not configured for TLS")
conn.Close()
return
}
conn = tls.Server(conn, s.rpcTLS)
s.handleConn(conn, true)
default:
s.logger.Printf("[ERR] consul.rpc: unrecognized RPC byte: %v", buf[0])
conn.Close()

View File

@ -1,6 +1,7 @@
package consul
import (
"crypto/tls"
"fmt"
"github.com/hashicorp/raft"
"github.com/hashicorp/serf/serf"
@ -82,6 +83,9 @@ type Server struct {
rpcListener net.Listener
rpcServer *rpc.Server
// rpcTLS is the TLS config for incoming TLS requests
rpcTLS *tls.Config
// serfLAN is the Serf cluster maintained inside the DC
// which contains all the DC nodes
serfLAN *serf.Serf
@ -122,13 +126,28 @@ func NewServer(config *Config) (*Server, error) {
config.LogOutput = os.Stderr
}
// Create the tlsConfig for outgoing connections
var tlsConfig *tls.Config
var err error
if config.VerifyOutgoing {
if tlsConfig, err = config.OutgoingTLSConfig(); err != nil {
return nil, err
}
}
// Get the incoming tls config
incomingTLS, err := config.IncomingTLSConfig()
if err != nil {
return nil, err
}
// Create a logger
logger := log.New(config.LogOutput, "", log.LstdFlags)
// Create server
s := &Server{
config: config,
connPool: NewPool(time.Minute),
connPool: NewPool(time.Minute, tlsConfig),
eventChLAN: make(chan serf.Event, 256),
eventChWAN: make(chan serf.Event, 256),
logger: logger,
@ -136,11 +155,12 @@ func NewServer(config *Config) (*Server, error) {
remoteConsuls: make(map[string][]net.Addr),
rpcClients: make(map[net.Conn]struct{}),
rpcServer: rpc.NewServer(),
rpcTLS: incomingTLS,
shutdownCh: make(chan struct{}),
}
// Initialize the RPC layer
if err := s.setupRPC(); err != nil {
if err := s.setupRPC(tlsConfig); err != nil {
s.Shutdown()
return nil, fmt.Errorf("Failed to start RPC layer: %v", err)
}
@ -156,7 +176,6 @@ func NewServer(config *Config) (*Server, error) {
go s.wanEventHandler()
// Initialize the lan Serf
var err error
s.serfLAN, err = s.setupSerf(config.SerfLANConfig,
s.eventChLAN, serfLANSnapshot)
if err != nil {
@ -271,7 +290,7 @@ func (s *Server) setupRaft() error {
}
// setupRPC is used to setup the RPC listener
func (s *Server) setupRPC() error {
func (s *Server) setupRPC(tlsConfig *tls.Config) error {
// Create endpoints
s.endpoints.Status = &Status{s}
s.endpoints.Raft = &Raft{s}
@ -310,7 +329,7 @@ func (s *Server) setupRPC() error {
return fmt.Errorf("RPC advertise address is not advertisable: %v", addr)
}
s.raftLayer = NewRaftLayer(advertise)
s.raftLayer = NewRaftLayer(advertise, tlsConfig)
go s.listen()
return nil
}

View File

@ -25,19 +25,17 @@ func tmpDir(t *testing.T) string {
return dir
}
func testServer(t *testing.T) (string, *Server) {
return testServerDC(t, "dc1")
func configureTLS(config *Config) {
config.CAFile = "../test/ca/root.cer"
config.CertFile = "../test/key/ourdomain.cer"
config.KeyFile = "../test/key/ourdomain.key"
}
func testServerDC(t *testing.T, dc string) (string, *Server) {
return testServerDCBootstrap(t, dc, true)
}
func testServerDCBootstrap(t *testing.T, dc string, bootstrap bool) (string, *Server) {
func testServerConfig(t *testing.T) (string, *Config) {
dir := tmpDir(t)
config := DefaultConfig()
config.Bootstrap = bootstrap
config.Datacenter = dc
config.Bootstrap = true
config.Datacenter = "dc1"
config.DataDir = dir
// Adjust the ports
@ -65,7 +63,21 @@ func testServerDCBootstrap(t *testing.T, dc string, bootstrap bool) (string, *Se
config.RaftConfig.ElectionTimeout = 40 * time.Millisecond
config.ReconcileInterval = 100 * time.Millisecond
return dir, config
}
func testServer(t *testing.T) (string, *Server) {
return testServerDC(t, "dc1")
}
func testServerDC(t *testing.T, dc string) (string, *Server) {
return testServerDCBootstrap(t, dc, true)
}
func testServerDCBootstrap(t *testing.T, dc string, bootstrap bool) (string, *Server) {
dir, config := testServerConfig(t)
config.Datacenter = dc
config.Bootstrap = bootstrap
server, err := NewServer(config)
if err != nil {
t.Fatalf("err: %v", err)
@ -219,3 +231,55 @@ func TestServer_RPC(t *testing.T) {
t.Fatalf("err: %v", err)
}
}
func TestServer_JoinLAN_TLS(t *testing.T) {
dir1, conf1 := testServerConfig(t)
conf1.VerifyIncoming = true
conf1.VerifyOutgoing = true
configureTLS(conf1)
s1, err := NewServer(conf1)
if err != nil {
t.Fatalf("err: %v", err)
}
defer os.RemoveAll(dir1)
defer s1.Shutdown()
dir2, conf2 := testServerConfig(t)
conf2.Bootstrap = false
conf2.VerifyIncoming = true
conf2.VerifyOutgoing = true
configureTLS(conf2)
s2, err := NewServer(conf2)
if err != nil {
t.Fatalf("err: %v", err)
}
defer os.RemoveAll(dir2)
defer s2.Shutdown()
// Try to join
addr := fmt.Sprintf("127.0.0.1:%d",
s1.config.SerfLANConfig.MemberlistConfig.BindPort)
if _, err := s2.JoinLAN([]string{addr}); err != nil {
t.Fatalf("err: %v", err)
}
// Check the members
if len(s1.LANMembers()) != 2 {
t.Fatalf("bad len")
}
if len(s2.LANMembers()) != 2 {
t.Fatalf("bad len")
}
// Wait a while
time.Sleep(100 * time.Millisecond)
// Verify Raft has established a peer
if s1.Stats()["raft"]["num_peers"] != "1" {
t.Fatalf("bad: %v", s1.Stats()["raft"])
}
if s2.Stats()["raft"]["num_peers"] != "1" {
t.Fatalf("bad: %v", s2.Stats()["raft"])
}
}

2
test/ca/certindex Normal file
View File

@ -0,0 +1,2 @@
V 150407190456Z 0A unknown /CN=testco.internal/ST=California/C=US/emailAddress=test@testco.com/O=TestCo/OU=Beta
V 150407194146Z 0B unknown /CN=testco.internal/ST=California/C=US/emailAddress=test@testco.com/O=TestCo/OU=Beta

34
test/ca/myca.conf Normal file
View File

@ -0,0 +1,34 @@
[ ca ]
default_ca = myca
[ crl_ext ]
# issuerAltName=issuer:copy #this would copy the issuer name to altname
authorityKeyIdentifier=keyid:always
[ myca ]
new_certs_dir = /tmp
unique_subject = no
certificate = root.cer
database = certindex
private_key = privkey.pem
serial = serialfile
default_days = 365
default_md = sha1
policy = myca_policy
x509_extensions = myca_extensions
[ myca_policy ]
commonName = supplied
stateOrProvinceName = supplied
countryName = supplied
emailAddress = optional
organizationName = supplied
organizationalUnitName = optional
[ myca_extensions ]
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
keyUsage = digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth,clientAuth
crlDistributionPoints = URI:http://path.to.crl/myca.crl

27
test/ca/privkey.pem Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxrs6JK4NpiOItxrpNR/1ppUUmH7p2BgLCBZ6eHdclle9J56i
68adt8J85zaqphCfz6VDP58DsFx+N50PZyjQaDsUd0HejRqfHRMtg2O+UQkv4Z66
+Vo+gc6uGuANi2xMtSYDVTAqqzF48OOPQDgYkzcGxcFZzTRFFZt2vPnyHj8cHcaF
o/NMNVh7C3yTXevRGNm9u2mrbxCEeiHzFC2WUnvgU2jQuC7Fhnl33Zd3B6d3mQH6
O23ncmwxTcPUJe6xZaIRrDuzwUcyhLj5Z3faag/fpFIIcHSiHRfoqHLGsGg+3swI
d/zVJSSDHr7pJUu7Cre+vZa63FqDaooqvnisrQIDAQABAoIBABreo6zj76p/8XM4
a0GokZE1ZPR9bGawUYWFbIevM9CMCmI5+7M/RoHbBQJKDOapJsJviNkoSdpllxcz
4CpFhXAiVNEPEeUoLU1EE4pJSSkxwcySppsiTYNFi5rMomgwe2qeuiKhgZNl/AEt
82dubjwxW3QPgXHSWGjkfTht3wOhrczA8xyEjc9Bsad2ooA9IQk+VXYlPZXyXjs1
WwLYHmcSfveauLliLXeVU2Ux5PPwyreKMhyAfSHVQCycxK008u8WPy8nkAlpxKMC
UwCN+JKl69WCCA3CxXgM83zz4pXvB4EyMr8aTiqmOID8RIIrPcjCmVJki6KbJ9WG
S2CQVG0CgYEA5kVACrnjLtov426ZNifF2zUXu9x//7D6GkbJxzZLwXP/BJFcEOdQ
Fnjcs3s7wYh/wdTnEcQVWSJSAqnRt98c9yAXVnG5z1M0DYpAsY8xrdhEitxOf2oB
2cbvi4+cvUuUxk1hgva18UCT23aLP+iY2+t/ydBXAZ9kq1zz5CcpEBMCgYEA3O/R
g1Y9O36XxBmSYnkoCF5yGrPunnKKNBJc/WA7pTkQFYHr64Y/h5EKubzHD/VEd1Li
nDuGYxVMewf+5pHUhqSdpZtTxv25hjOsqLf5o5wm18JThGifs2zEVCTJOPti5n2M
RHakxuq1I625/QHidLBTQYuEBS/vywhapfaSaD8CgYEAhd1OPK4R30PiQRIjqXL3
t9ampISsOKXWz33FgbUT1zOq1in23rDKQzYh/4ktlPXYZ4NwjUhzrKyiBoBYtc7T
1OpoBs34Wgmhohl0QIThOZIXTq6CR9oFl2fqDDUBxp3wsFN905e+77A+BIBmtVFv
w7GlSVp/qibSbDiOZF1LptcCgYB8sJBi+jnmqOSIVRJLpysTxhHJxkDmhahACRkY
Gsau0cylBsUaEJMsNIyEFOmXtQml+k5QdDu9EdkvGm0evbDfKGqce1RF2w5okiNg
uSwXzVoSrOartMxk2/7VqkkycpX3lWWjgf4vEWmXsEVmaDjhOF5UgKPKtao0wQs/
3S/1ywKBgAIGgOuvL/GBcGqLikHLC+cputMvBAuE/tJnFHPxFoobskocVsMKbDTy
NYF7uPlzSGGClZsjE6DQyyGf5E9/U+EdwDKZwHYGCkzVjplUBo0BT3EN0vcc9jB/
ML9Ta4ETPyf66BhSVcD+eeNipPFAul0Q7uZhErH1zr1evTy8XXyI
-----END RSA PRIVATE KEY-----

28
test/ca/root.cer Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEtzCCA5+gAwIBAgIJAIewRMI8OnvTMA0GCSqGSIb3DQEBBQUAMIGYMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHDAa
BgNVBAoTE0hhc2hpQ29ycCBUZXN0IENlcnQxDDAKBgNVBAsTA0RldjEWMBQGA1UE
AxMNdGVzdC5pbnRlcm5hbDEgMB4GCSqGSIb3DQEJARYRdGVzdEBpbnRlcm5hbC5j
b20wHhcNMTQwNDA3MTkwMTA4WhcNMjQwNDA0MTkwMTA4WjCBmDELMAkGA1UEBhMC
VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRwwGgYDVQQK
ExNIYXNoaUNvcnAgVGVzdCBDZXJ0MQwwCgYDVQQLEwNEZXYxFjAUBgNVBAMTDXRl
c3QuaW50ZXJuYWwxIDAeBgkqhkiG9w0BCQEWEXRlc3RAaW50ZXJuYWwuY29tMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxrs6JK4NpiOItxrpNR/1ppUU
mH7p2BgLCBZ6eHdclle9J56i68adt8J85zaqphCfz6VDP58DsFx+N50PZyjQaDsU
d0HejRqfHRMtg2O+UQkv4Z66+Vo+gc6uGuANi2xMtSYDVTAqqzF48OOPQDgYkzcG
xcFZzTRFFZt2vPnyHj8cHcaFo/NMNVh7C3yTXevRGNm9u2mrbxCEeiHzFC2WUnvg
U2jQuC7Fhnl33Zd3B6d3mQH6O23ncmwxTcPUJe6xZaIRrDuzwUcyhLj5Z3faag/f
pFIIcHSiHRfoqHLGsGg+3swId/zVJSSDHr7pJUu7Cre+vZa63FqDaooqvnisrQID
AQABo4IBADCB/TAdBgNVHQ4EFgQUo/nrOfqvbee2VklVKIFlyQEbuJUwgc0GA1Ud
IwSBxTCBwoAUo/nrOfqvbee2VklVKIFlyQEbuJWhgZ6kgZswgZgxCzAJBgNVBAYT
AlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEcMBoGA1UE
ChMTSGFzaGlDb3JwIFRlc3QgQ2VydDEMMAoGA1UECxMDRGV2MRYwFAYDVQQDEw10
ZXN0LmludGVybmFsMSAwHgYJKoZIhvcNAQkBFhF0ZXN0QGludGVybmFsLmNvbYIJ
AIewRMI8OnvTMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADa9fV9h
gjapBlkNmu64WX0Ufub5dsJrdHS8672P30S7ILB7Mk0W8sL65IezRsZnG898yHf9
2uzmz5OvNTM9K380g7xFlyobSVq+6yqmmSAlA/ptAcIIZT727P5jig/DB7fzJM3g
jctDlEGOmEe50GQXc25VKpcpjAsNQi5ER5gowQ0v3IXNZs+yU+LvxLHc0rUJ/XSp
lFCAMOqd5uRoMOejnT51G6krvLNzPaQ3N9jQfNVY4Q0zfs0M+6dRWvqfqB9Vyq8/
POLMld+HyAZEBk9zK3ZVIXx6XS4dkDnSNR91njLq7eouf6M7+7s/oMQZZRtAfQ6r
wlW975rYa1ZqEdA=
-----END CERTIFICATE-----

1
test/ca/serialfile Normal file
View File

@ -0,0 +1 @@
0C

22
test/key/ourdomain.cer Normal file
View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIBCzANBgkqhkiG9w0BAQUFADCBmDELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRwwGgYDVQQKExNI
YXNoaUNvcnAgVGVzdCBDZXJ0MQwwCgYDVQQLEwNEZXYxFjAUBgNVBAMTDXRlc3Qu
aW50ZXJuYWwxIDAeBgkqhkiG9w0BCQEWEXRlc3RAaW50ZXJuYWwuY29tMB4XDTE0
MDQwNzE5NDE0NloXDTE1MDQwNzE5NDE0NlowfDEYMBYGA1UEAxMPdGVzdGNvLmlu
dGVybmFsMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQswCQYDVQQGEwJVUzEeMBwGCSqG
SIb3DQEJARYPdGVzdEB0ZXN0Y28uY29tMQ8wDQYDVQQKEwZUZXN0Q28xDTALBgNV
BAsTBEJldGEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALXNUbqVUjsspMHh
46PB37kI6rKzZlSsyaBYWsS19tdrwS83nSXZJlaMAVPLR/TR+b1P81zxZD8m8ZmQ
2bK70No5usFkdlbowVLAIMySIQmZF1tTLXbiCKldiwEjkOWa1xKwJauoM0XKnWkF
mLGDxIAl84DZaLgmNj2t/q8d+laDAgMBAAGjgagwgaUwCQYDVR0TBAIwADAdBgNV
HQ4EFgQUDNy9EoPn+YNrIlvkWMxh5QKS5JwwHwYDVR0jBBgwFoAUo/nrOfqvbee2
VklVKIFlyQEbuJUwCwYDVR0PBAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
BgEFBQcDAjAsBgNVHR8EJTAjMCGgH6AdhhtodHRwOi8vcGF0aC50by5jcmwvbXlj
YS5jcmwwDQYJKoZIhvcNAQEFBQADggEBABCvJP2zMF60ooDYLaaNuPcmkokWIp/n
X00dZ6y1aI063y8OE1CSY8rGv3W/ONkS3cOQvVhdAVtAzqGnDK3VsFZzmWR+tuqR
KUhkzcC0X4nNq91iik1dTj2skl1Jkq6lJhrY1sR6JXOSn68Iv2KAuLVNn5tC5hzB
WOK7S2ffqfof3eV+g0cgFNCzaS75cn8YlXBqQGpn5odcVDX8c80Xj/Si18wWDPR9
/kq5xKRsaFkKJYOoswRwoq9kwukruMndxf7/Az/YEHdimZKMpxfK/qzI0KUw4XO0
lpEkMZaA3l+xYB2fNHwlwyz77RNCoySCnii61hmxLNDwUiokgdJcY9U=
-----END CERTIFICATE-----

12
test/key/ourdomain.csr Normal file
View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIB1TCCAT4CAQAwgZQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKEwZUZXN0Q28xDTALBgNV
BAsTBEJldGExGDAWBgNVBAMTD3Rlc3Rjby5pbnRlcm5hbDEeMBwGCSqGSIb3DQEJ
ARYPdGVzdEB0ZXN0Y28uY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1
zVG6lVI7LKTB4eOjwd+5COqys2ZUrMmgWFrEtfbXa8EvN50l2SZWjAFTy0f00fm9
T/Nc8WQ/JvGZkNmyu9DaObrBZHZW6MFSwCDMkiEJmRdbUy124gipXYsBI5DlmtcS
sCWrqDNFyp1pBZixg8SAJfOA2Wi4JjY9rf6vHfpWgwIDAQABoAAwDQYJKoZIhvcN
AQEFBQADgYEAG7SdzgaTcJ1sMJ+pH+42J9Fyp2SY+WGEP3dA7f1/Lwc1dFHeKLPL
X0Gv6DgNkxUwWOe/yncq+dkuUkDGx3M1FRvpKCKFAywp+j0NxIll7821/2Jvf5/f
BVHPmgUIzZEYz0d6vcCQl2RKd83wLWcR77JTrD2S1JqAEkrMw/xUa/s=
-----END CERTIFICATE REQUEST-----

15
test/key/ourdomain.key Normal file
View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQC1zVG6lVI7LKTB4eOjwd+5COqys2ZUrMmgWFrEtfbXa8EvN50l
2SZWjAFTy0f00fm9T/Nc8WQ/JvGZkNmyu9DaObrBZHZW6MFSwCDMkiEJmRdbUy12
4gipXYsBI5DlmtcSsCWrqDNFyp1pBZixg8SAJfOA2Wi4JjY9rf6vHfpWgwIDAQAB
AoGAQNOSxg6CwPj9UulCa90w8mD8l3YjEiR+zP9UdnpQJ6aTv0t8bMeOxSOtQXzm
DqVlAR1mMQkebuprEhA1oGcaZGx5hYIfhad8k5e18nkfogMG2J23fjyw/Zvcsbpi
AebEHGwrPnGplG2AZ2knRVpQ0EkKLUZ3sHGQjCuh/srw5QECQQDb5vKCz181/wnE
0CVIAg61ibwyWNR2s3i2Xhsp0UmhbTSknP2mQzdUqegMB5Y5mbruXy46EHuia5H7
LnFXfx6BAkEA06VF154NirTBU6MCPtj0ynUkTFSYu7oJG7U+rMA7Bx4itLjAy76g
wniz6HWHMqgQP1G8mhkHYb6fmkBQY1V7AwJANjP07t1ioJKeu856ggdPzNuIcfiH
VkLirEEB/QrDVXDvmuu/ce37g3jl46EzHDuSYhM/97v8XYqaTwmhkmmZAQJAbFVq
3KVwdRF06+TCn3zaQE+Z1uBulZjyVJZ/kFmNXWVVioAPX7sh+qliHZkbLRjNyDuE
eLRbDPNQKtrEyzPUFQJAXQ4AKXWs1LMzNFueMH+qZ3NjX+GdVBrvdri7D+yzLXPL
IwNWljwTONXQHwwRlEHIQJVw85qfdLfNngHKEW5X7w==
-----END RSA PRIVATE KEY-----

1
test/notes.txt Normal file
View File

@ -0,0 +1 @@
Instructions from https://langui.sh/2009/01/18/openssl-self-signed-ca/

View File

@ -8,11 +8,12 @@ sidebar_current: "docs-agent-encryption"
The Consul agent supports encrypting all of its network traffic. The exact
method of this encryption is described on the
[encryption internals page](/docs/internals/security.html).
[encryption internals page](/docs/internals/security.html). There are two
seperate systems, one for gossip traffic and one for RPC.
## Enabling Encryption
## Gossip Encryption
Enabling encryption only requires that you set an encryption key when
Enabling gossip encryption only requires that you set an encryption key when
starting the Consul agent. The key can be set using the `-encrypt` flag
on `consul agent` or by setting the `encrypt_key` in a configuration file.
It is advisable to put the key in a configuration file to avoid other users
@ -47,3 +48,30 @@ $ consul agent -data=/tmp/consul -encrypt=cg8StVXbQJ0gPvMd9o7yrg==
All nodes within a Consul cluster must share the same encryption key in
order to send and receive cluster information.
# RPC Encryption with TLS
Consul supports using TLS to verify the authenticity of servers and clients. For this
to work, Consul requires that all clients and servers have key pairs that are generated
by a single Certificate Authority. This can be a private CA, used only internally. The
CA then signs keys for each of the agents. [Here](https://langui.sh/2009/01/18/openssl-self-signed-ca/)
is a tutorial on generating both a CA and signing keys using OpenSSL.
There are a number of things to consider when setting up TLS for Consul. Either we can
use TLS just to verify the authenticity of the servers, or we can also verify the authenticity
of clients. The former can be used to prevent unauthorized access. This behavior is controlled
using either the `verify_incoming` and `verify_outgoing` [options](/docs/agent/options.html).
If `verify_outgoing` is set, then agents verify the authenticity of Consuls for outgoing
connections. This means server nodes must present a certificate signed by the `ca_file` that
the agent has. This option must be set on all agents, and there must be a `ca_file` provided
to check the certificate against. If this is set, then all server nodes must have an appropriate
key pair set using `cert_file` and `key_file`.
If `verify_incoming` is set, then the servers verify the authenticity of all incoming
connections. Servers will also disallow any non-TLS connections. If this is set, then all
clients must have a valid key pair set using `cert_file` and `key_file`. To force clients to
use TLs, `verify_outgoing` must also be set.
TLS is used to secure the RPC calls between agents, but gossip between nodes is done over UDP
and is secured using a symmetric key. See above for enabling gossip encryption.

View File

@ -205,3 +205,27 @@ definitions support being updated during a reload.
* `statsite_addr` - Equivalent to the `-statsite` command-line flag.
* `verify_incoming` - If set to True, Consul requires that all incoming
connections make use of TLS, and that the client provides a certificate signed
by the Certificate Authority from the `ca_file`. By default, this is false, and
Consul will not enforce the use of TLS or verify a client's authenticity. This
only applies to Consul servers, since a client never has an incoming connection.
* `verify_outgoing` - If set to True, Consul requires that all outgoing connections
make use of TLS, and that the server provide a certificate that is signed by
the Certificate Authority from the `ca_file`. By default, this is false, and Consul
will not make use of TLS for outgoing connections. This applies to clients and servers,
as both will make outgoing connections.
* `ca_file` - This provides a the file path to a PEM encoded certificate authority.
The certificate authority is used to check the authenticity of client and server
connections with the appropriate `verify_incoming` or `verify_outgoing` flags.
* `cert_file` - This provides a the file path to a PEM encoded certificate.
The certificate is provided to clients or servers to verify the agents authenticity.
Must be provided along with the `key_file`.
* `key_file` - This provides a the file path to a PEM encoded private key.
The key is used with the certificate to verify the agents authenticity.
Must be provided along with the `cert_file`.