Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ _testmain.go
*.test
bin/
.vagrant/


website/npm-debug.log

*.old
*.attr
7 changes: 7 additions & 0 deletions command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
37 changes: 37 additions & 0 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`

Expand Down Expand Up @@ -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...)
}
Expand Down
37 changes: 37 additions & 0 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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},
}
Expand Down
13 changes: 11 additions & 2 deletions consul/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package consul

import (
"crypto/tls"
"fmt"
"github.com/hashicorp/consul/consul/structs"
"github.com/hashicorp/serf/serf"
Expand Down Expand Up @@ -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{}),
Expand All @@ -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 {
Expand Down
71 changes: 65 additions & 6 deletions consul/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
}
Loading