mirror of
https://github.com/status-im/consul.git
synced 2025-01-10 22:06:20 +00:00
Merge pull request #927 from hashicorp/f-tls
Add new `verify_server_hostname` to mitigate possibility of MITM
This commit is contained in:
commit
ebf961ef8b
@ -288,10 +288,12 @@ func (a *Agent) consulConfig() *consul.Config {
|
|||||||
// Copy the TLS configuration
|
// Copy the TLS configuration
|
||||||
base.VerifyIncoming = a.config.VerifyIncoming
|
base.VerifyIncoming = a.config.VerifyIncoming
|
||||||
base.VerifyOutgoing = a.config.VerifyOutgoing
|
base.VerifyOutgoing = a.config.VerifyOutgoing
|
||||||
|
base.VerifyServerHostname = a.config.VerifyServerHostname
|
||||||
base.CAFile = a.config.CAFile
|
base.CAFile = a.config.CAFile
|
||||||
base.CertFile = a.config.CertFile
|
base.CertFile = a.config.CertFile
|
||||||
base.KeyFile = a.config.KeyFile
|
base.KeyFile = a.config.KeyFile
|
||||||
base.ServerName = a.config.ServerName
|
base.ServerName = a.config.ServerName
|
||||||
|
base.Domain = a.config.Domain
|
||||||
|
|
||||||
// Setup the ServerUp callback
|
// Setup the ServerUp callback
|
||||||
base.ServerUp = a.state.ConsulServerUp
|
base.ServerUp = a.state.ConsulServerUp
|
||||||
|
@ -188,6 +188,14 @@ type Config struct {
|
|||||||
// certificate authority. This is used to verify authenticity of server nodes.
|
// certificate authority. This is used to verify authenticity of server nodes.
|
||||||
VerifyOutgoing bool `mapstructure:"verify_outgoing"`
|
VerifyOutgoing bool `mapstructure:"verify_outgoing"`
|
||||||
|
|
||||||
|
// VerifyServerHostname is used to enable hostname verification of servers. This
|
||||||
|
// ensures that the certificate presented is valid for server.<datacenter>.<domain>.
|
||||||
|
// This prevents a compromised client from being restarted as a server, and then
|
||||||
|
// intercepting request traffic as well as being added as a raft peer. This should be
|
||||||
|
// enabled by default with VerifyOutgoing, but for legacy reasons we cannot break
|
||||||
|
// existing clients.
|
||||||
|
VerifyServerHostname bool `mapstructure:"verify_server_hostname"`
|
||||||
|
|
||||||
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
|
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
|
||||||
// or VerifyOutgoing to verify the TLS connection.
|
// or VerifyOutgoing to verify the TLS connection.
|
||||||
CAFile string `mapstructure:"ca_file"`
|
CAFile string `mapstructure:"ca_file"`
|
||||||
@ -838,6 +846,9 @@ func MergeConfig(a, b *Config) *Config {
|
|||||||
if b.VerifyOutgoing {
|
if b.VerifyOutgoing {
|
||||||
result.VerifyOutgoing = true
|
result.VerifyOutgoing = true
|
||||||
}
|
}
|
||||||
|
if b.VerifyServerHostname {
|
||||||
|
result.VerifyServerHostname = true
|
||||||
|
}
|
||||||
if b.CAFile != "" {
|
if b.CAFile != "" {
|
||||||
result.CAFile = b.CAFile
|
result.CAFile = b.CAFile
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ func TestDecodeConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TLS
|
// TLS
|
||||||
input = `{"verify_incoming": true, "verify_outgoing": true}`
|
input = `{"verify_incoming": true, "verify_outgoing": true, "verify_server_hostname": true}`
|
||||||
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
|
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
@ -259,6 +259,10 @@ func TestDecodeConfig(t *testing.T) {
|
|||||||
t.Fatalf("bad: %#v", config)
|
t.Fatalf("bad: %#v", config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.VerifyServerHostname != true {
|
||||||
|
t.Fatalf("bad: %#v", config)
|
||||||
|
}
|
||||||
|
|
||||||
// TLS keys
|
// TLS keys
|
||||||
input = `{"ca_file": "my/ca/file", "cert_file": "my.cert", "key_file": "key.pem", "server_name": "example.com"}`
|
input = `{"ca_file": "my/ca/file", "cert_file": "my.cert", "key_file": "key.pem", "server_name": "example.com"}`
|
||||||
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
|
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package consul
|
package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@ -91,10 +90,9 @@ func NewClient(config *Config) (*Client, error) {
|
|||||||
config.LogOutput = os.Stderr
|
config.LogOutput = os.Stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the tlsConfig
|
// Create the tls Wrapper
|
||||||
var tlsConfig *tls.Config
|
tlsWrap, err := config.tlsConfig().OutgoingTLSWrapper()
|
||||||
var err error
|
if err != nil {
|
||||||
if tlsConfig, err = config.tlsConfig().OutgoingTLSConfig(); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +102,7 @@ func NewClient(config *Config) (*Client, error) {
|
|||||||
// Create server
|
// Create server
|
||||||
c := &Client{
|
c := &Client{
|
||||||
config: config,
|
config: config,
|
||||||
connPool: NewPool(config.LogOutput, clientRPCCache, clientMaxStreams, tlsConfig),
|
connPool: NewPool(config.LogOutput, clientRPCCache, clientMaxStreams, tlsWrap),
|
||||||
eventCh: make(chan serf.Event, 256),
|
eventCh: make(chan serf.Event, 256),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
shutdownCh: make(chan struct{}),
|
shutdownCh: make(chan struct{}),
|
||||||
@ -357,7 +355,7 @@ func (c *Client) RPC(method string, args interface{}, reply interface{}) error {
|
|||||||
|
|
||||||
// Forward to remote Consul
|
// Forward to remote Consul
|
||||||
TRY_RPC:
|
TRY_RPC:
|
||||||
if err := c.connPool.RPC(server.Addr, server.Version, method, args, reply); err != nil {
|
if err := c.connPool.RPC(c.config.Datacenter, server.Addr, server.Version, method, args, reply); err != nil {
|
||||||
c.lastServer = nil
|
c.lastServer = nil
|
||||||
c.lastRPCTime = time.Time{}
|
c.lastRPCTime = time.Time{}
|
||||||
return err
|
return err
|
||||||
|
@ -56,6 +56,9 @@ type Config struct {
|
|||||||
// Node name is the name we use to advertise. Defaults to hostname.
|
// Node name is the name we use to advertise. Defaults to hostname.
|
||||||
NodeName string
|
NodeName string
|
||||||
|
|
||||||
|
// Domain is the DNS domain for the records. Defaults to "consul."
|
||||||
|
Domain string
|
||||||
|
|
||||||
// RaftConfig is the configuration used for Raft in the local DC
|
// RaftConfig is the configuration used for Raft in the local DC
|
||||||
RaftConfig *raft.Config
|
RaftConfig *raft.Config
|
||||||
|
|
||||||
@ -100,6 +103,14 @@ type Config struct {
|
|||||||
// server nodes.
|
// server nodes.
|
||||||
VerifyOutgoing bool
|
VerifyOutgoing bool
|
||||||
|
|
||||||
|
// VerifyServerHostname is used to enable hostname verification of servers. This
|
||||||
|
// ensures that the certificate presented is valid for server.<datacenter>.<domain>.
|
||||||
|
// This prevents a compromised client from being restarted as a server, and then
|
||||||
|
// intercepting request traffic as well as being added as a raft peer. This should be
|
||||||
|
// enabled by default with VerifyOutgoing, but for legacy reasons we cannot break
|
||||||
|
// existing clients.
|
||||||
|
VerifyServerHostname bool
|
||||||
|
|
||||||
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
|
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
|
||||||
// or VerifyOutgoing to verify the TLS connection.
|
// or VerifyOutgoing to verify the TLS connection.
|
||||||
CAFile string
|
CAFile string
|
||||||
@ -267,13 +278,15 @@ func DefaultConfig() *Config {
|
|||||||
|
|
||||||
func (c *Config) tlsConfig() *tlsutil.Config {
|
func (c *Config) tlsConfig() *tlsutil.Config {
|
||||||
tlsConf := &tlsutil.Config{
|
tlsConf := &tlsutil.Config{
|
||||||
VerifyIncoming: c.VerifyIncoming,
|
VerifyIncoming: c.VerifyIncoming,
|
||||||
VerifyOutgoing: c.VerifyOutgoing,
|
VerifyOutgoing: c.VerifyOutgoing,
|
||||||
CAFile: c.CAFile,
|
VerifyServerHostname: c.VerifyServerHostname,
|
||||||
CertFile: c.CertFile,
|
CAFile: c.CAFile,
|
||||||
KeyFile: c.KeyFile,
|
CertFile: c.CertFile,
|
||||||
NodeName: c.NodeName,
|
KeyFile: c.KeyFile,
|
||||||
ServerName: c.ServerName}
|
NodeName: c.NodeName,
|
||||||
|
ServerName: c.ServerName,
|
||||||
|
Domain: c.Domain,
|
||||||
|
}
|
||||||
return tlsConf
|
return tlsConf
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package consul
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@ -135,8 +134,8 @@ type ConnPool struct {
|
|||||||
// Pool maps an address to a open connection
|
// Pool maps an address to a open connection
|
||||||
pool map[string]*Conn
|
pool map[string]*Conn
|
||||||
|
|
||||||
// TLS settings
|
// TLS wrapper
|
||||||
tlsConfig *tls.Config
|
tlsWrap tlsutil.DCWrapper
|
||||||
|
|
||||||
// Used to indicate the pool is shutdown
|
// Used to indicate the pool is shutdown
|
||||||
shutdown bool
|
shutdown bool
|
||||||
@ -148,13 +147,13 @@ type ConnPool struct {
|
|||||||
// Set maxTime to 0 to disable reaping. maxStreams is used to control
|
// Set maxTime to 0 to disable reaping. maxStreams is used to control
|
||||||
// the number of idle streams allowed.
|
// the number of idle streams allowed.
|
||||||
// If TLS settings are provided outgoing connections use TLS.
|
// If TLS settings are provided outgoing connections use TLS.
|
||||||
func NewPool(logOutput io.Writer, maxTime time.Duration, maxStreams int, tlsConfig *tls.Config) *ConnPool {
|
func NewPool(logOutput io.Writer, maxTime time.Duration, maxStreams int, tlsWrap tlsutil.DCWrapper) *ConnPool {
|
||||||
pool := &ConnPool{
|
pool := &ConnPool{
|
||||||
logOutput: logOutput,
|
logOutput: logOutput,
|
||||||
maxTime: maxTime,
|
maxTime: maxTime,
|
||||||
maxStreams: maxStreams,
|
maxStreams: maxStreams,
|
||||||
pool: make(map[string]*Conn),
|
pool: make(map[string]*Conn),
|
||||||
tlsConfig: tlsConfig,
|
tlsWrap: tlsWrap,
|
||||||
shutdownCh: make(chan struct{}),
|
shutdownCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
if maxTime > 0 {
|
if maxTime > 0 {
|
||||||
@ -183,14 +182,14 @@ func (p *ConnPool) Shutdown() error {
|
|||||||
|
|
||||||
// Acquire is used to get a connection that is
|
// Acquire is used to get a connection that is
|
||||||
// pooled or to return a new connection
|
// pooled or to return a new connection
|
||||||
func (p *ConnPool) acquire(addr net.Addr, version int) (*Conn, error) {
|
func (p *ConnPool) acquire(dc string, addr net.Addr, version int) (*Conn, error) {
|
||||||
// Check for a pooled ocnn
|
// Check for a pooled ocnn
|
||||||
if conn := p.getPooled(addr, version); conn != nil {
|
if conn := p.getPooled(addr, version); conn != nil {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new connection
|
// Create a new connection
|
||||||
return p.getNewConn(addr, version)
|
return p.getNewConn(dc, addr, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPooled is used to return a pooled connection
|
// getPooled is used to return a pooled connection
|
||||||
@ -206,7 +205,7 @@ func (p *ConnPool) getPooled(addr net.Addr, version int) *Conn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getNewConn is used to return a new connection
|
// getNewConn is used to return a new connection
|
||||||
func (p *ConnPool) getNewConn(addr net.Addr, version int) (*Conn, error) {
|
func (p *ConnPool) getNewConn(dc string, addr net.Addr, version int) (*Conn, error) {
|
||||||
// Try to dial the conn
|
// Try to dial the conn
|
||||||
conn, err := net.DialTimeout("tcp", addr.String(), 10*time.Second)
|
conn, err := net.DialTimeout("tcp", addr.String(), 10*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -220,7 +219,7 @@ func (p *ConnPool) getNewConn(addr net.Addr, version int) (*Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if TLS is enabled
|
// Check if TLS is enabled
|
||||||
if p.tlsConfig != nil {
|
if p.tlsWrap != nil {
|
||||||
// Switch the connection into TLS mode
|
// Switch the connection into TLS mode
|
||||||
if _, err := conn.Write([]byte{byte(rpcTLS)}); err != nil {
|
if _, err := conn.Write([]byte{byte(rpcTLS)}); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@ -228,7 +227,7 @@ func (p *ConnPool) getNewConn(addr net.Addr, version int) (*Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the connection in a TLS client
|
// Wrap the connection in a TLS client
|
||||||
tlsConn, err := tlsutil.WrapTLSClient(conn, p.tlsConfig)
|
tlsConn, err := p.tlsWrap(dc, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -314,11 +313,11 @@ func (p *ConnPool) releaseConn(conn *Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getClient is used to get a usable client for an address and protocol version
|
// getClient is used to get a usable client for an address and protocol version
|
||||||
func (p *ConnPool) getClient(addr net.Addr, version int) (*Conn, *StreamClient, error) {
|
func (p *ConnPool) getClient(dc string, addr net.Addr, version int) (*Conn, *StreamClient, error) {
|
||||||
retries := 0
|
retries := 0
|
||||||
START:
|
START:
|
||||||
// Try to get a conn first
|
// Try to get a conn first
|
||||||
conn, err := p.acquire(addr, version)
|
conn, err := p.acquire(dc, addr, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to get conn: %v", err)
|
return nil, nil, fmt.Errorf("failed to get conn: %v", err)
|
||||||
}
|
}
|
||||||
@ -340,9 +339,9 @@ START:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RPC is used to make an RPC call to a remote host
|
// RPC is used to make an RPC call to a remote host
|
||||||
func (p *ConnPool) RPC(addr net.Addr, version int, method string, args interface{}, reply interface{}) error {
|
func (p *ConnPool) RPC(dc string, addr net.Addr, version int, method string, args interface{}, reply interface{}) error {
|
||||||
// Get a usable client
|
// Get a usable client
|
||||||
conn, sc, err := p.getClient(addr, version)
|
conn, sc, err := p.getClient(dc, addr, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rpc error: %v", err)
|
return fmt.Errorf("rpc error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package consul
|
package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hashicorp/consul/tlsutil"
|
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RaftLayer implements the raft.StreamLayer interface,
|
// RaftLayer implements the raft.StreamLayer interface,
|
||||||
@ -18,8 +18,8 @@ type RaftLayer struct {
|
|||||||
// connCh is used to accept connections
|
// connCh is used to accept connections
|
||||||
connCh chan net.Conn
|
connCh chan net.Conn
|
||||||
|
|
||||||
// TLS configuration
|
// TLS wrapper
|
||||||
tlsConfig *tls.Config
|
tlsWrap tlsutil.Wrapper
|
||||||
|
|
||||||
// Tracks if we are closed
|
// Tracks if we are closed
|
||||||
closed bool
|
closed bool
|
||||||
@ -30,12 +30,12 @@ type RaftLayer struct {
|
|||||||
// NewRaftLayer is used to initialize a new RaftLayer which can
|
// NewRaftLayer is used to initialize a new RaftLayer which can
|
||||||
// be used as a StreamLayer for Raft. If a tlsConfig is provided,
|
// be used as a StreamLayer for Raft. If a tlsConfig is provided,
|
||||||
// then the connection will use TLS.
|
// then the connection will use TLS.
|
||||||
func NewRaftLayer(addr net.Addr, tlsConfig *tls.Config) *RaftLayer {
|
func NewRaftLayer(addr net.Addr, tlsWrap tlsutil.Wrapper) *RaftLayer {
|
||||||
layer := &RaftLayer{
|
layer := &RaftLayer{
|
||||||
addr: addr,
|
addr: addr,
|
||||||
connCh: make(chan net.Conn),
|
connCh: make(chan net.Conn),
|
||||||
tlsConfig: tlsConfig,
|
tlsWrap: tlsWrap,
|
||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
return layer
|
return layer
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ func (l *RaftLayer) Dial(address string, timeout time.Duration) (net.Conn, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for tls mode
|
// Check for tls mode
|
||||||
if l.tlsConfig != nil {
|
if l.tlsWrap != nil {
|
||||||
// Switch the connection into TLS mode
|
// Switch the connection into TLS mode
|
||||||
if _, err := conn.Write([]byte{byte(rpcTLS)}); err != nil {
|
if _, err := conn.Write([]byte{byte(rpcTLS)}); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@ -95,7 +95,7 @@ func (l *RaftLayer) Dial(address string, timeout time.Duration) (net.Conn, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the connection in a TLS client
|
// Wrap the connection in a TLS client
|
||||||
conn, err = tlsutil.WrapTLSClient(conn, l.tlsConfig)
|
conn, err = l.tlsWrap(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ func (s *Server) forwardLeader(method string, args interface{}, reply interface{
|
|||||||
if server == nil {
|
if server == nil {
|
||||||
return structs.ErrNoLeader
|
return structs.ErrNoLeader
|
||||||
}
|
}
|
||||||
return s.connPool.RPC(server.Addr, server.Version, method, args, reply)
|
return s.connPool.RPC(s.config.Datacenter, server.Addr, server.Version, method, args, reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// forwardDC is used to forward an RPC call to a remote DC, or fail if no servers
|
// forwardDC is used to forward an RPC call to a remote DC, or fail if no servers
|
||||||
@ -229,7 +229,7 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{
|
|||||||
|
|
||||||
// Forward to remote Consul
|
// Forward to remote Consul
|
||||||
metrics.IncrCounter([]string{"consul", "rpc", "cross-dc", dc}, 1)
|
metrics.IncrCounter([]string{"consul", "rpc", "cross-dc", dc}, 1)
|
||||||
return s.connPool.RPC(server.Addr, server.Version, method, args, reply)
|
return s.connPool.RPC(dc, server.Addr, server.Version, method, args, reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// globalRPC is used to forward an RPC request to one server in each datacenter.
|
// globalRPC is used to forward an RPC request to one server in each datacenter.
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
"github.com/hashicorp/golang-lru"
|
"github.com/hashicorp/golang-lru"
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
"github.com/hashicorp/raft-boltdb"
|
"github.com/hashicorp/raft-boltdb"
|
||||||
@ -182,9 +183,9 @@ func NewServer(config *Config) (*Server, error) {
|
|||||||
config.LogOutput = os.Stderr
|
config.LogOutput = os.Stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the tlsConfig for outgoing connections
|
// Create the tls wrapper for outgoing connections
|
||||||
tlsConf := config.tlsConfig()
|
tlsConf := config.tlsConfig()
|
||||||
tlsConfig, err := tlsConf.OutgoingTLSConfig()
|
tlsWrap, err := tlsConf.OutgoingTLSWrapper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -207,7 +208,7 @@ func NewServer(config *Config) (*Server, error) {
|
|||||||
// Create server
|
// Create server
|
||||||
s := &Server{
|
s := &Server{
|
||||||
config: config,
|
config: config,
|
||||||
connPool: NewPool(config.LogOutput, serverRPCCache, serverMaxStreams, tlsConfig),
|
connPool: NewPool(config.LogOutput, serverRPCCache, serverMaxStreams, tlsWrap),
|
||||||
eventChLAN: make(chan serf.Event, 256),
|
eventChLAN: make(chan serf.Event, 256),
|
||||||
eventChWAN: make(chan serf.Event, 256),
|
eventChWAN: make(chan serf.Event, 256),
|
||||||
localConsuls: make(map[string]*serverParts),
|
localConsuls: make(map[string]*serverParts),
|
||||||
@ -242,7 +243,7 @@ func NewServer(config *Config) (*Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the RPC layer
|
// Initialize the RPC layer
|
||||||
if err := s.setupRPC(tlsConfig); err != nil {
|
if err := s.setupRPC(tlsWrap); err != nil {
|
||||||
s.Shutdown()
|
s.Shutdown()
|
||||||
return nil, fmt.Errorf("Failed to start RPC layer: %v", err)
|
return nil, fmt.Errorf("Failed to start RPC layer: %v", err)
|
||||||
}
|
}
|
||||||
@ -410,7 +411,7 @@ func (s *Server) setupRaft() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setupRPC is used to setup the RPC listener
|
// setupRPC is used to setup the RPC listener
|
||||||
func (s *Server) setupRPC(tlsConfig *tls.Config) error {
|
func (s *Server) setupRPC(tlsWrap tlsutil.DCWrapper) error {
|
||||||
// Create endpoints
|
// Create endpoints
|
||||||
s.endpoints.Status = &Status{s}
|
s.endpoints.Status = &Status{s}
|
||||||
s.endpoints.Catalog = &Catalog{s}
|
s.endpoints.Catalog = &Catalog{s}
|
||||||
@ -453,7 +454,10 @@ func (s *Server) setupRPC(tlsConfig *tls.Config) error {
|
|||||||
return fmt.Errorf("RPC advertise address is not advertisable: %v", addr)
|
return fmt.Errorf("RPC advertise address is not advertisable: %v", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.raftLayer = NewRaftLayer(advertise, tlsConfig)
|
// Provide a DC specific wrapper. Raft replication is only
|
||||||
|
// ever done in the same datacenter, so we can provide it as a constant.
|
||||||
|
wrapper := tlsutil.SpecificDC(s.config.Datacenter, tlsWrap)
|
||||||
|
s.raftLayer = NewRaftLayer(advertise, wrapper)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
test/hostname/Alice.crt
Normal file
25
test/hostname/Alice.crt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEOzCCAiWgAwIBAgIRAOpEyvnjEG/Z15f0PrOT7iowCwYJKoZIhvcNAQELMBMx
|
||||||
|
ETAPBgNVBAMTCENlcnRBdXRoMB4XDTE1MDUxMTIyNTMxN1oXDTE3MDUxMTIyNTMx
|
||||||
|
OFowEDEOMAwGA1UEAxMFQWxpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
||||||
|
AoIBAQCyxmMV9V0Cdp2mAXxL6h64cWLQlKsumsQfhZNImea8jLYT7+yyLpeHIF4G
|
||||||
|
7JiushloTnERyTi1wbq9BlU3BVYdX6tqvPXFFwFUXyOkDaSGS3vMCZUYd9PZg0TI
|
||||||
|
pyQK0/6+jSU7x7jDGVUMhJyvmXB9CgKxG0S8WiR6uGB9oWrTeDnXAzN1T4wNE4M+
|
||||||
|
a3P1ToT2k2IDklZ1t5gg6u9EiOAzK7QfpKXrO2MsGyGHhm+tQqNP6LuZv0u2nGW3
|
||||||
|
up+i3beQOvLQV0aeiy7zfR3KkIUCvDnmiPnkm35o6wmqFOXTNIU6VoT/l4WtU85F
|
||||||
|
Ikdtk1gkDLO1iyKiMRbj/hlRqKGxAgMBAAGjgZAwgY0wDgYDVR0PAQH/BAQDAgC4
|
||||||
|
MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUgt2Os881
|
||||||
|
V/je/BOaLavjeorhbi4wHwYDVR0jBBgwFoAUB2s4Gdz7ornOiti84HF+W+nwAj8w
|
||||||
|
HAYDVR0RBBUwE4IRc2VydmVyLmRjMS5jb25zdWwwCwYJKoZIhvcNAQELA4ICAQCX
|
||||||
|
Thsbgo1Z5maIyvBJOKX5vQifaSF8kRtX9fZvipvzHCjYxvOHfaTvgtWyxHXCc3tK
|
||||||
|
DyBswsc2MeHiZ5g0KG113lwLrhcSwEsg5yo0eB7tOTQp1rmCiF6DQYs1XyOqD7P8
|
||||||
|
S6clMgJWgpM8Ltw5mYALqDpShv1ND3AOJqENj/0tvdP7Y7cilG+s76HFXRcwKTRw
|
||||||
|
4rVP+Wr+t4WdXeS8cGxboQqGc40L3HNd5cxsbIM1kucfdrPBljWmyM9aiO1Nipm2
|
||||||
|
8dyyir8AFnvoGQ6DPi58jVCCbqosL/GXtVk+IgJ+8eE5T8jvhxBovzxArSSVYIaj
|
||||||
|
ZxYi85ixfLr1DC5mg5CWWB8ZzmjaUwfyQAcL/F3Q11CkqHw1VDoDzvTBWbguBu6X
|
||||||
|
xXexlgOQx4/lr8X1pjbbAZktNTOYDt4dTuhrKPU35zW35wTnSBoPrQ3cpGlRcszE
|
||||||
|
IksZSHi41IQd0zUOGCNZYpPFq8mTwu5ECGHfNvWDH7zEuSkO54tS5Dukxqd8VIQl
|
||||||
|
h9GB2Uyel8tFm4s/Dx9+glKyvsXDJQz3JmFaB2wPyAPZ1KL4GFI5R0LjUVSFJapP
|
||||||
|
TO3Ia24naOu3qYXWQK6jGwaCbTT6tdhgNy8EI0aDmv2AgqOXycutMJXF5UqkDmwY
|
||||||
|
ZqpVdf/TrmBy42pk/C0vpqiy6E4N7WllxhiY2AekkA==
|
||||||
|
-----END CERTIFICATE-----
|
27
test/hostname/Alice.key
Normal file
27
test/hostname/Alice.key
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAssZjFfVdAnadpgF8S+oeuHFi0JSrLprEH4WTSJnmvIy2E+/s
|
||||||
|
si6XhyBeBuyYrrIZaE5xEck4tcG6vQZVNwVWHV+rarz1xRcBVF8jpA2khkt7zAmV
|
||||||
|
GHfT2YNEyKckCtP+vo0lO8e4wxlVDIScr5lwfQoCsRtEvFokerhgfaFq03g51wMz
|
||||||
|
dU+MDRODPmtz9U6E9pNiA5JWdbeYIOrvRIjgMyu0H6Sl6ztjLBshh4ZvrUKjT+i7
|
||||||
|
mb9Ltpxlt7qfot23kDry0FdGnosu830dypCFArw55oj55Jt+aOsJqhTl0zSFOlaE
|
||||||
|
/5eFrVPORSJHbZNYJAyztYsiojEW4/4ZUaihsQIDAQABAoIBAF3C9szZdwKHu38J
|
||||||
|
YGtgSuRpc235yx4SRbJSmECHlyBknEowl2+MSCSysR3okNtuxSyTl3HAm2GYTZw9
|
||||||
|
6guFXPji6EB/AldwDV5213Z/QT698Bu/GtdOYWm/EyA5qQmUzhKabGDCCwEoFBcQ
|
||||||
|
piziyMCLs4W3y4ENtfw3H0REmIZ3s0XQRzuDdFCEMbr0Ij6EhP3hSD6es4PWTeHY
|
||||||
|
LSwoXm0WAxyZudJLhWZaBRxvl+TDY7nVV1jRPQ+ojMJjXfyPo+c2hbbS6luj++qH
|
||||||
|
6qO7fEpr8EXhO8/0/bPUi0ozE1LVy1kXtEwfszesU9r5XeBq7yTCIa7TTJ35Niwf
|
||||||
|
T7Ar9NECgYEA0L77+B/3dtedhsMdyiHpcxV6A77OIHsezh8uJzE+nQEgqvJ88N1W
|
||||||
|
BbF7YByYmaP1/dBrPI7ON52AnDOyo2lM7fVOwr7Ch12tpoa5HFb2WndKt6KokXi/
|
||||||
|
Tk8+zoCCZICCv1mtfIaepRTmxAeyqaFthchAv1nc7ojS1BWeXMLa9usCgYEA2z6N
|
||||||
|
YD8wV44d/qIMaSDVJlusyp8pi9l0ddB591KOYHhJ5RRF1qEd0pshj9sW6pcGGJqf
|
||||||
|
XFHAkEr/ZIACJK65Y+AFcbgzhyqX8Vy9LLYzWtpFP4SpjH19pYDTHaXvWsIjBlNG
|
||||||
|
poxtGYCQ8Uedm8IhtrbUorElQVjPlmRGU14B2tMCgYAPQCTAd/VoZVBI7DBc+CVK
|
||||||
|
FyOW6nW8wcH6ZSTGED720YJFevnNzx3dxJ2y4+PyNZxfMr7i6bv/LC6dOtmuPp80
|
||||||
|
M1vRtoYXxaxOIkGb5G6TJWv8BpIyLpQrcHayN4lPNmRW/oJCOsOUY/aIE9fltLl/
|
||||||
|
sKWqVTJi6vQcMogjVskQiQKBgHcH7f+sLtXKTdSaLDzDW5X4vcZARXEs/YKdTiqN
|
||||||
|
wsjzZcMej5AoZyWZnc4Zd8ajeebPw+d+Zxqv7RqmOQOrbPGhhbMo+6jN4jJjVD27
|
||||||
|
KgSQbno+z0J8O0QovfXhyiKvNg7QFZKEuRLYb1jftd0DuAQYHTe7D2v8CLAw/tFy
|
||||||
|
P3WLAoGABcEQEDUWqxfFFCed4mYSoOHvD44YMIzeMMOHXRnGGWug0WkULxzUV1L4
|
||||||
|
fTFPCqo6xsn/F3i7xRFpIWXlOzjZKHvw16ZpeZBNcdPjyk7XifhafJYLuknRe1fZ
|
||||||
|
lzLjhmvizTpd9GQIUS+39aGwGE9JI3H0NAdNA4pvEdKlPhJnG5U=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
29
test/hostname/CertAuth.crt
Normal file
29
test/hostname/CertAuth.crt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
@ -6,9 +6,19 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DCWrapper is a function that is used to wrap a non-TLS connection
|
||||||
|
// and returns an appropriate TLS connection or error. This takes
|
||||||
|
// a datacenter as an argument.
|
||||||
|
type DCWrapper func(dc string, conn net.Conn) (net.Conn, error)
|
||||||
|
|
||||||
|
// Wrapper is a variant of DCWrapper, where the DC is provided as
|
||||||
|
// a constant value. This is usually done by currying DCWrapper.
|
||||||
|
type Wrapper func(conn net.Conn) (net.Conn, error)
|
||||||
|
|
||||||
// Config used to create tls.Config
|
// Config used to create tls.Config
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// VerifyIncoming is used to verify the authenticity of incoming connections.
|
// VerifyIncoming is used to verify the authenticity of incoming connections.
|
||||||
@ -22,6 +32,14 @@ type Config struct {
|
|||||||
// server nodes.
|
// server nodes.
|
||||||
VerifyOutgoing bool
|
VerifyOutgoing bool
|
||||||
|
|
||||||
|
// VerifyServerHostname is used to enable hostname verification of servers. This
|
||||||
|
// ensures that the certificate presented is valid for server.<datacenter>.<domain>.
|
||||||
|
// This prevents a compromised client from being restarted as a server, and then
|
||||||
|
// intercepting request traffic as well as being added as a raft peer. This should be
|
||||||
|
// enabled by default with VerifyOutgoing, but for legacy reasons we cannot break
|
||||||
|
// existing clients.
|
||||||
|
VerifyServerHostname bool
|
||||||
|
|
||||||
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
|
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
|
||||||
// or VerifyOutgoing to verify the TLS connection.
|
// or VerifyOutgoing to verify the TLS connection.
|
||||||
CAFile string
|
CAFile string
|
||||||
@ -40,6 +58,9 @@ type Config struct {
|
|||||||
// ServerName is used with the TLS certificate to ensure the name we
|
// ServerName is used with the TLS certificate to ensure the name we
|
||||||
// provide matches the certificate
|
// provide matches the certificate
|
||||||
ServerName string
|
ServerName string
|
||||||
|
|
||||||
|
// Domain is the Consul TLD being used. Defaults to "consul."
|
||||||
|
Domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendCA opens and parses the CA file and adds the certificates to
|
// AppendCA opens and parses the CA file and adds the certificates to
|
||||||
@ -78,6 +99,10 @@ func (c *Config) KeyPair() (*tls.Certificate, error) {
|
|||||||
// requests. It will return a nil config if this configuration should
|
// requests. It will return a nil config if this configuration should
|
||||||
// not use TLS for outgoing connections.
|
// not use TLS for outgoing connections.
|
||||||
func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
|
func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
|
||||||
|
// If VerifyServerHostname is true, that implies VerifyOutgoing
|
||||||
|
if c.VerifyServerHostname {
|
||||||
|
c.VerifyOutgoing = true
|
||||||
|
}
|
||||||
if !c.VerifyOutgoing {
|
if !c.VerifyOutgoing {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -90,6 +115,11 @@ func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
|
|||||||
tlsConfig.ServerName = c.ServerName
|
tlsConfig.ServerName = c.ServerName
|
||||||
tlsConfig.InsecureSkipVerify = false
|
tlsConfig.InsecureSkipVerify = false
|
||||||
}
|
}
|
||||||
|
if c.VerifyServerHostname {
|
||||||
|
// ServerName is filled in dynamically based on the target DC
|
||||||
|
tlsConfig.ServerName = "VerifyServerHostname"
|
||||||
|
tlsConfig.InsecureSkipVerify = false
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure we have a CA if VerifyOutgoing is set
|
// Ensure we have a CA if VerifyOutgoing is set
|
||||||
if c.VerifyOutgoing && c.CAFile == "" {
|
if c.VerifyOutgoing && c.CAFile == "" {
|
||||||
@ -113,6 +143,51 @@ func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
|
|||||||
return tlsConfig, nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip the trailing '.' from the domain if any
|
||||||
|
domain := strings.TrimSuffix(c.Domain, ".")
|
||||||
|
|
||||||
|
// Generate the wrapper based on hostname verification
|
||||||
|
if c.VerifyServerHostname {
|
||||||
|
wrapper := func(dc string, conn net.Conn) (net.Conn, error) {
|
||||||
|
conf := *tlsConfig
|
||||||
|
conf.ServerName = "server." + dc + "." + domain
|
||||||
|
return WrapTLSClient(conn, &conf)
|
||||||
|
}
|
||||||
|
return wrapper, nil
|
||||||
|
} else {
|
||||||
|
wrapper := func(dc string, c net.Conn) (net.Conn, error) {
|
||||||
|
return WrapTLSClient(c, 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 {
|
||||||
|
if tlsWrap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return func(conn net.Conn) (net.Conn, error) {
|
||||||
|
return tlsWrap(dc, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap a net.Conn into a client tls connection, performing any
|
// Wrap a net.Conn into a client tls connection, performing any
|
||||||
// additional verification as needed.
|
// additional verification as needed.
|
||||||
//
|
//
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig_AppendCA_None(t *testing.T) {
|
func TestConfig_AppendCA_None(t *testing.T) {
|
||||||
@ -133,6 +135,29 @@ func TestConfig_OutgoingTLS_ServerName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfig_OutgoingTLS_VerifyHostname(t *testing.T) {
|
||||||
|
conf := &Config{
|
||||||
|
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.ServerName != "VerifyServerHostname" {
|
||||||
|
t.Fatalf("expect server name")
|
||||||
|
}
|
||||||
|
if tls.InsecureSkipVerify {
|
||||||
|
t.Fatalf("should not skip built-in verification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfig_OutgoingTLS_WithKeyPair(t *testing.T) {
|
func TestConfig_OutgoingTLS_WithKeyPair(t *testing.T) {
|
||||||
conf := &Config{
|
conf := &Config{
|
||||||
VerifyOutgoing: true,
|
VerifyOutgoing: true,
|
||||||
@ -168,8 +193,16 @@ func startTLSServer(config *Config) (net.Conn, chan error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client, server := net.Pipe()
|
client, server := net.Pipe()
|
||||||
|
|
||||||
|
// Use yamux to buffer the reads, otherwise it's easy to deadlock
|
||||||
|
muxConf := yamux.DefaultConfig()
|
||||||
|
serverSession, _ := yamux.Server(server, muxConf)
|
||||||
|
clientSession, _ := yamux.Client(client, muxConf)
|
||||||
|
clientConn, _ := clientSession.Open()
|
||||||
|
serverConn, _ := serverSession.Accept()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
tlsServer := tls.Server(server, tlsConfigServer)
|
tlsServer := tls.Server(serverConn, tlsConfigServer)
|
||||||
if err := tlsServer.Handshake(); err != nil {
|
if err := tlsServer.Handshake(); err != nil {
|
||||||
errc <- err
|
errc <- err
|
||||||
}
|
}
|
||||||
@ -183,7 +216,107 @@ func startTLSServer(config *Config) (net.Conn, chan error) {
|
|||||||
io.Copy(ioutil.Discard, tlsServer)
|
io.Copy(ioutil.Discard, tlsServer)
|
||||||
tlsServer.Close()
|
tlsServer.Close()
|
||||||
}()
|
}()
|
||||||
return client, errc
|
return clientConn, errc
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_outgoingWrapper_OK(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
CAFile: "../test/hostname/CertAuth.crt",
|
||||||
|
CertFile: "../test/hostname/Alice.crt",
|
||||||
|
KeyFile: "../test/hostname/Alice.key",
|
||||||
|
VerifyServerHostname: true,
|
||||||
|
Domain: "consul",
|
||||||
|
}
|
||||||
|
|
||||||
|
client, errc := startTLSServer(config)
|
||||||
|
if client == nil {
|
||||||
|
t.Fatalf("startTLSServer err: %v", <-errc)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap, err := config.OutgoingTLSWrapper()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OutgoingTLSWrapper err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsClient, err := wrap("dc1", client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wrapTLS err: %v", err)
|
||||||
|
}
|
||||||
|
defer tlsClient.Close()
|
||||||
|
if err := tlsClient.(*tls.Conn).Handshake(); err != nil {
|
||||||
|
t.Fatalf("write err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = <-errc
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_outgoingWrapper_BadDC(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
CAFile: "../test/hostname/CertAuth.crt",
|
||||||
|
CertFile: "../test/hostname/Alice.crt",
|
||||||
|
KeyFile: "../test/hostname/Alice.key",
|
||||||
|
VerifyServerHostname: true,
|
||||||
|
Domain: "consul",
|
||||||
|
}
|
||||||
|
|
||||||
|
client, errc := startTLSServer(config)
|
||||||
|
if client == nil {
|
||||||
|
t.Fatalf("startTLSServer err: %v", <-errc)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap, err := config.OutgoingTLSWrapper()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OutgoingTLSWrapper err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsClient, err := wrap("dc2", client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wrapTLS err: %v", err)
|
||||||
|
}
|
||||||
|
defer tlsClient.Close()
|
||||||
|
err = tlsClient.(*tls.Conn).Handshake()
|
||||||
|
|
||||||
|
if _, ok := err.(x509.HostnameError); !ok {
|
||||||
|
t.Fatalf("should get hostname err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-errc
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_outgoingWrapper_BadCert(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
CAFile: "../test/ca/root.cer",
|
||||||
|
CertFile: "../test/key/ourdomain.cer",
|
||||||
|
KeyFile: "../test/key/ourdomain.key",
|
||||||
|
VerifyServerHostname: true,
|
||||||
|
Domain: "consul",
|
||||||
|
}
|
||||||
|
|
||||||
|
client, errc := startTLSServer(config)
|
||||||
|
if client == nil {
|
||||||
|
t.Fatalf("startTLSServer err: %v", <-errc)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap, err := config.OutgoingTLSWrapper()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OutgoingTLSWrapper err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsClient, err := wrap("dc1", client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wrapTLS err: %v", err)
|
||||||
|
}
|
||||||
|
defer tlsClient.Close()
|
||||||
|
err = tlsClient.(*tls.Conn).Handshake()
|
||||||
|
|
||||||
|
if _, ok := err.(x509.HostnameError); !ok {
|
||||||
|
t.Fatalf("should get hostname err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-errc
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig_wrapTLS_OK(t *testing.T) {
|
func TestConfig_wrapTLS_OK(t *testing.T) {
|
||||||
|
@ -64,7 +64,8 @@ using OpenSSL. Note: client certificates must have
|
|||||||
for client and server authentication.
|
for client and server authentication.
|
||||||
|
|
||||||
TLS can be used to verify the authenticity of the servers or verify the authenticity of clients.
|
TLS can be used to verify the authenticity of the servers or verify the authenticity of clients.
|
||||||
These modes are controlled by the [`verify_outgoing`](/docs/agent/options.html#verify_outgoing)
|
These modes are controlled by the [`verify_outgoing`](/docs/agent/options.html#verify_outgoing),
|
||||||
|
[`verify_server_hostname`](/docs/agent/options.html#verify_server_hostname),
|
||||||
and [`verify_incoming`](/docs/agent/options.html#verify_incoming) options, respectively.
|
and [`verify_incoming`](/docs/agent/options.html#verify_incoming) options, respectively.
|
||||||
|
|
||||||
If [`verify_outgoing`](/docs/agent/options.html#verify_outgoing) is set, agents verify the
|
If [`verify_outgoing`](/docs/agent/options.html#verify_outgoing) is set, agents verify the
|
||||||
@ -74,6 +75,14 @@ by the certificate authority present on all agents, set via the agent's
|
|||||||
appropriate key pair set using [`cert_file`](/docs/agent/options.html#cert_file) and
|
appropriate key pair set using [`cert_file`](/docs/agent/options.html#cert_file) and
|
||||||
[`key_file`](/docs/agent/options.html#key_file).
|
[`key_file`](/docs/agent/options.html#key_file).
|
||||||
|
|
||||||
|
If [`verify_server_hostname`](/docs/agent/options.html#verify_server_hostname) is set, then
|
||||||
|
outgoing connections perform hostname verification. All servers must have a certificate
|
||||||
|
valid for "server.\<datacenter\>.\<domain\>" or the client will reject the handshake. This is
|
||||||
|
a new configuration as of 0.5.1, and it is used to prevent a compromised client from being
|
||||||
|
able to restart in server mode and perform a MITM attack. New deployments should set this
|
||||||
|
to true, and generate the proper certificates, but this is defaulted to false to avoid breaking
|
||||||
|
existing deployments.
|
||||||
|
|
||||||
If [`verify_incoming`](/docs/agent/options.html#verify_incoming) is set, the servers verify the
|
If [`verify_incoming`](/docs/agent/options.html#verify_incoming) is set, the servers verify the
|
||||||
authenticity of all incoming connections. All clients must have a valid key pair set using
|
authenticity of all incoming connections. All clients must have a valid key pair set using
|
||||||
[`cert_file`](/docs/agent/options.html#cert_file) and
|
[`cert_file`](/docs/agent/options.html#cert_file) and
|
||||||
|
@ -584,6 +584,14 @@ definitions support being updated during a reload.
|
|||||||
will not make use of TLS for outgoing connections. This applies to clients and servers
|
will not make use of TLS for outgoing connections. This applies to clients and servers
|
||||||
as both will make outgoing connections.
|
as both will make outgoing connections.
|
||||||
|
|
||||||
|
* <a name="verify_server_hostname"></a><a href="#verify_server_hostname">`verify_server_hostname`</a> - If set to
|
||||||
|
true, Consul verifies for all outgoing connections that the TLS certificate presented by the servers
|
||||||
|
matches "server.<datacenter>.<domain>" hostname. This implies `verify_outgoing`.
|
||||||
|
By default, this is false, and Consul does not verify the hostname of the certificate, only
|
||||||
|
that it is signed by a trusted CA. This setting is important to prevent a compromised
|
||||||
|
client from being restarted as a server, and thus being able to perform a MITM attack
|
||||||
|
or to be added as a Raft peer. This is new in 0.5.1.
|
||||||
|
|
||||||
* <a name="watches"></a><a href="#watches">`watches`</a> - Watches is a list of watch
|
* <a name="watches"></a><a href="#watches">`watches`</a> - Watches is a list of watch
|
||||||
specifications which allow an external process to be automatically invoked when a
|
specifications which allow an external process to be automatically invoked when a
|
||||||
particular data view is updated. See the
|
particular data view is updated. See the
|
||||||
|
Loading…
x
Reference in New Issue
Block a user