Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add separate option for verifying incoming HTTPS traffic #2974

Merged
merged 3 commits into from
Apr 28, 2017
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
132 changes: 95 additions & 37 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,53 +256,111 @@ func TestSetupTLSConfig(t *testing.T) {

func TestClientTLSOptions(t *testing.T) {
t.Parallel()
_, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
// Start a server that verifies incoming HTTPS connections
_, srvVerify := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.CAFile = "../test/client_certs/rootca.crt"
conf.CertFile = "../test/client_certs/server.crt"
conf.KeyFile = "../test/client_certs/server.key"
conf.VerifyIncoming = true
conf.VerifyIncomingHTTPS = true
})
defer s.Stop()
defer srvVerify.Stop()

// Set up a client without certs
clientWithoutCerts, err := NewClient(&Config{
Address: s.HTTPSAddr,
Scheme: "https",
TLSConfig: TLSConfig{
Address: "consul.test",
CAFile: "../test/client_certs/rootca.crt",
},
// Start a server without VerifyIncomingHTTPS
_, srvNoVerify := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.CAFile = "../test/client_certs/rootca.crt"
conf.CertFile = "../test/client_certs/server.crt"
conf.KeyFile = "../test/client_certs/server.key"
conf.VerifyIncomingHTTPS = false
})
if err != nil {
t.Fatal(err)
}
defer srvNoVerify.Stop()

// Client without a cert
t.Run("client without cert, validation", func(t *testing.T) {
client, err := NewClient(&Config{
Address: srvVerify.HTTPSAddr,
Scheme: "https",
TLSConfig: TLSConfig{
Address: "consul.test",
CAFile: "../test/client_certs/rootca.crt",
},
})
if err != nil {
t.Fatal(err)
}

// Should fail
_, err = clientWithoutCerts.Agent().Self()
if err == nil || !strings.Contains(err.Error(), "bad certificate") {
t.Fatal(err)
}
// Should fail
_, err = client.Agent().Self()
if err == nil || !strings.Contains(err.Error(), "bad certificate") {
t.Fatal(err)
}
})

// Client with a valid cert
t.Run("client with cert, validation", func(t *testing.T) {
client, err := NewClient(&Config{
Address: srvVerify.HTTPSAddr,
Scheme: "https",
TLSConfig: TLSConfig{
Address: "consul.test",
CAFile: "../test/client_certs/rootca.crt",
CertFile: "../test/client_certs/client.crt",
KeyFile: "../test/client_certs/client.key",
},
})
if err != nil {
t.Fatal(err)
}

// Should succeed
_, err = client.Agent().Self()
if err != nil {
t.Fatal(err)
}
})

// Client without a cert
t.Run("client without cert, no validation", func(t *testing.T) {
client, err := NewClient(&Config{
Address: srvNoVerify.HTTPSAddr,
Scheme: "https",
TLSConfig: TLSConfig{
Address: "consul.test",
CAFile: "../test/client_certs/rootca.crt",
},
})
if err != nil {
t.Fatal(err)
}

// Set up a client with valid certs
clientWithCerts, err := NewClient(&Config{
Address: s.HTTPSAddr,
Scheme: "https",
TLSConfig: TLSConfig{
Address: "consul.test",
CAFile: "../test/client_certs/rootca.crt",
CertFile: "../test/client_certs/client.crt",
KeyFile: "../test/client_certs/client.key",
},
// Should succeed
_, err = client.Agent().Self()
if err != nil {
t.Fatal(err)
}
})
if err != nil {
t.Fatal(err)
}

// Should succeed
_, err = clientWithCerts.Agent().Self()
if err != nil {
t.Fatal(err)
}
// Client with a valid cert
t.Run("client with cert, no validation", func(t *testing.T) {
client, err := NewClient(&Config{
Address: srvNoVerify.HTTPSAddr,
Scheme: "https",
TLSConfig: TLSConfig{
Address: "consul.test",
CAFile: "../test/client_certs/rootca.crt",
CertFile: "../test/client_certs/client.crt",
KeyFile: "../test/client_certs/client.key",
},
})
if err != nil {
t.Fatal(err)
}

// Should succeed
_, err = client.Agent().Self()
if err != nil {
t.Fatal(err)
}
})
}

func TestSetQueryOptions(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ func (a *Agent) consulConfig() *consul.Config {
a.config.Version, a.config.VersionPrerelease, revision)

// Copy the TLS configuration
base.VerifyIncoming = a.config.VerifyIncoming
base.VerifyIncoming = a.config.VerifyIncoming || a.config.VerifyIncomingRPC
base.VerifyOutgoing = a.config.VerifyOutgoing
base.VerifyServerHostname = a.config.VerifyServerHostname
base.CAFile = a.config.CAFile
Expand Down
16 changes: 16 additions & 0 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,16 @@ type Config struct {
// must match a provided certificate authority. This can be used to force client auth.
VerifyIncoming bool `mapstructure:"verify_incoming"`

// VerifyIncomingRPC is used to verify the authenticity of incoming RPC 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.
VerifyIncomingRPC bool `mapstructure:"verify_incoming_rpc"`

// VerifyIncomingHTTPS is used to verify the authenticity of incoming HTTPS 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.
VerifyIncomingHTTPS bool `mapstructure:"verify_incoming_https"`

// 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.
Expand Down Expand Up @@ -1529,6 +1539,12 @@ func MergeConfig(a, b *Config) *Config {
if b.VerifyIncoming {
result.VerifyIncoming = true
}
if b.VerifyIncomingRPC {
result.VerifyIncomingRPC = true
}
if b.VerifyIncomingHTTPS {
result.VerifyIncomingHTTPS = true
}
if b.VerifyOutgoing {
result.VerifyOutgoing = true
}
Expand Down
11 changes: 10 additions & 1 deletion command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ func TestDecodeConfig(t *testing.T) {
}

// TLS
input = `{"verify_incoming": true, "verify_outgoing": true, "verify_server_hostname": true, "tls_min_version": "tls12",
input = `{"verify_incoming": true, "verify_incoming_rpc": true, "verify_incoming_https": true,
"verify_outgoing": true, "verify_server_hostname": true, "tls_min_version": "tls12",
"tls_cipher_suites": "TLS_RSA_WITH_AES_256_CBC_SHA", "tls_prefer_server_cipher_suites": true}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
Expand All @@ -366,6 +367,14 @@ func TestDecodeConfig(t *testing.T) {
t.Fatalf("bad: %#v", config)
}

if config.VerifyIncomingRPC != true {
t.Fatalf("bad: %#v", config)
}

if config.VerifyIncomingHTTPS != true {
t.Fatalf("bad: %#v", config)
}

if config.VerifyOutgoing != true {
t.Fatalf("bad: %#v", config)
}
Expand Down
2 changes: 1 addition & 1 deletion command/agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPS
}

tlsConf := &tlsutil.Config{
VerifyIncoming: config.VerifyIncoming,
VerifyIncoming: config.VerifyIncoming || config.VerifyIncomingHTTPS,
VerifyOutgoing: config.VerifyOutgoing,
CAFile: config.CAFile,
CAPath: config.CAPath,
Expand Down
54 changes: 28 additions & 26 deletions testutil/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,32 +56,34 @@ type TestAddressConfig struct {

// TestServerConfig is the main server configuration struct.
type TestServerConfig struct {
NodeName string `json:"node_name"`
NodeID string `json:"node_id"`
NodeMeta map[string]string `json:"node_meta,omitempty"`
Performance *TestPerformanceConfig `json:"performance,omitempty"`
Bootstrap bool `json:"bootstrap,omitempty"`
Server bool `json:"server,omitempty"`
DataDir string `json:"data_dir,omitempty"`
Datacenter string `json:"datacenter,omitempty"`
DisableCheckpoint bool `json:"disable_update_check"`
LogLevel string `json:"log_level,omitempty"`
Bind string `json:"bind_addr,omitempty"`
Addresses *TestAddressConfig `json:"addresses,omitempty"`
Ports *TestPortConfig `json:"ports,omitempty"`
RaftProtocol int `json:"raft_protocol,omitempty"`
ACLMasterToken string `json:"acl_master_token,omitempty"`
ACLDatacenter string `json:"acl_datacenter,omitempty"`
ACLDefaultPolicy string `json:"acl_default_policy,omitempty"`
ACLEnforceVersion8 bool `json:"acl_enforce_version_8"`
Encrypt string `json:"encrypt,omitempty"`
CAFile string `json:"ca_file,omitempty"`
CertFile string `json:"cert_file,omitempty"`
KeyFile string `json:"key_file,omitempty"`
VerifyIncoming bool `json:"verify_incoming,omitempty"`
VerifyOutgoing bool `json:"verify_outgoing,omitempty"`
Stdout, Stderr io.Writer `json:"-"`
Args []string `json:"-"`
NodeName string `json:"node_name"`
NodeID string `json:"node_id"`
NodeMeta map[string]string `json:"node_meta,omitempty"`
Performance *TestPerformanceConfig `json:"performance,omitempty"`
Bootstrap bool `json:"bootstrap,omitempty"`
Server bool `json:"server,omitempty"`
DataDir string `json:"data_dir,omitempty"`
Datacenter string `json:"datacenter,omitempty"`
DisableCheckpoint bool `json:"disable_update_check"`
LogLevel string `json:"log_level,omitempty"`
Bind string `json:"bind_addr,omitempty"`
Addresses *TestAddressConfig `json:"addresses,omitempty"`
Ports *TestPortConfig `json:"ports,omitempty"`
RaftProtocol int `json:"raft_protocol,omitempty"`
ACLMasterToken string `json:"acl_master_token,omitempty"`
ACLDatacenter string `json:"acl_datacenter,omitempty"`
ACLDefaultPolicy string `json:"acl_default_policy,omitempty"`
ACLEnforceVersion8 bool `json:"acl_enforce_version_8"`
Encrypt string `json:"encrypt,omitempty"`
CAFile string `json:"ca_file,omitempty"`
CertFile string `json:"cert_file,omitempty"`
KeyFile string `json:"key_file,omitempty"`
VerifyIncoming bool `json:"verify_incoming,omitempty"`
VerifyIncomingRPC bool `json:"verify_incoming_rpc,omitempty"`
VerifyIncomingHTTPS bool `json:"verify_incoming_https,omitempty"`
VerifyOutgoing bool `json:"verify_outgoing,omitempty"`
Stdout, Stderr io.Writer `json:"-"`
Args []string `json:"-"`
}

// ServerConfigCallback is a function interface which can be
Expand Down
28 changes: 20 additions & 8 deletions website/source/docs/agent/options.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -1052,18 +1052,30 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
* <a name="verify_incoming"></a><a href="#verify_incoming">`verify_incoming`</a> - 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`](#ca_file). By default, this is false, and
Consul will not enforce the use of TLS or verify a client's authenticity. This
applies to both server RPC and to the HTTPS API. To enable the HTTPS API, you
must define an HTTPS port via the [`ports`](#ports) configuration. By default, HTTPS
is disabled.
by a Certificate Authority from the [`ca_file`](#ca_file) or [`ca_path`](#ca_path).
This applies to both server RPC and to the HTTPS API. By default, this is false, and
Consul will not enforce the use of TLS or verify a client's authenticity.

* <a name="verify_incoming_rpc"></a><a href="#verify_incoming_rpc">`verify_incoming_rpc`</a> - If
set to true, Consul requires that all incoming RPC
connections make use of TLS and that the client provides a certificate signed
by a Certificate Authority from the [`ca_file`](#ca_file) or [`ca_path`](#ca_path). By default,
this is false, and Consul will not enforce the use of TLS or verify a client's authenticity.

* <a name="verify_incoming_https"></a><a href="#verify_incoming_https">`verify_incoming_https`</a> - If
set to true, Consul requires that all incoming HTTPS
connections make use of TLS and that the client provides a certificate signed
by a Certificate Authority from the [`ca_file`](#ca_file) or [`ca_path`](#ca_path). By default,
this is false, and Consul will not enforce the use of TLS or verify a client's authenticity. To
enable the HTTPS API, you must define an HTTPS port via the [`ports`](#ports) configuration. By
default, HTTPS is disabled.

* <a name="verify_outgoing"></a><a href="#verify_outgoing">`verify_outgoing`</a> - If set to
true, Consul requires that all outgoing connections
make use of TLS and that the server provides a certificate that is signed by
the Certificate Authority from the [`ca_file`](#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.
a Certificate Authority from the [`ca_file`](#ca_file) or [`ca_path`](#ca_path). 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.

* <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
Expand Down