Skip to content

Commit 11206c7

Browse files
committed
Require TLS for server RPC when enabled
Fixes #2525 We used to be checking a RequireTLS field that was never set. Instead we can just check the TLSConfig.EnableRPC field and require TLS if it's enabled. Added a few unfortunately slow integration tests to assert the intended behavior of misconfigured RPC TLS. Also disable a lot of noisy test logging when -v isn't specified.
1 parent ee53b22 commit 11206c7

File tree

4 files changed

+142
-6
lines changed

4 files changed

+142
-6
lines changed

client/client_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/hashicorp/nomad/nomad"
2222
"github.com/hashicorp/nomad/nomad/mock"
2323
"github.com/hashicorp/nomad/nomad/structs"
24+
nconfig "github.com/hashicorp/nomad/nomad/structs/config"
2425
"github.com/hashicorp/nomad/testutil"
2526
"github.com/mitchellh/hashstructure"
2627

@@ -382,6 +383,98 @@ func TestClient_Drivers_WhitelistBlacklistCombination(t *testing.T) {
382383
}
383384
}
384385

386+
// TestClient_MixedTLS asserts that when a server is running with TLS enabled
387+
// it will reject any RPC connections from clients that lack TLS. See #2525
388+
func TestClient_MixedTLS(t *testing.T) {
389+
const (
390+
cafile = "../helper/tlsutil/testdata/ca.pem"
391+
foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
392+
fookey = "../helper/tlsutil/testdata/nomad-foo-key.pem"
393+
)
394+
s1, addr := testServer(t, func(c *nomad.Config) {
395+
c.TLSConfig = &nconfig.TLSConfig{
396+
EnableHTTP: true,
397+
EnableRPC: true,
398+
VerifyServerHostname: true,
399+
CAFile: cafile,
400+
CertFile: foocert,
401+
KeyFile: fookey,
402+
}
403+
})
404+
defer s1.Shutdown()
405+
testutil.WaitForLeader(t, s1.RPC)
406+
407+
c1 := testClient(t, func(c *config.Config) {
408+
c.Servers = []string{addr}
409+
})
410+
defer c1.Shutdown()
411+
412+
req := structs.NodeSpecificRequest{
413+
NodeID: c1.Node().ID,
414+
QueryOptions: structs.QueryOptions{Region: "global"},
415+
}
416+
var out structs.SingleNodeResponse
417+
deadline := time.Now().Add(1234 * time.Millisecond)
418+
for time.Now().Before(deadline) {
419+
err := c1.RPC("Node.GetNode", &req, &out)
420+
if err == nil {
421+
t.Fatalf("client RPC succeeded when it should have failed:\n%+v", out)
422+
}
423+
}
424+
}
425+
426+
// TestClient_BadTLS asserts that when a client and server are running with TLS
427+
// enabled -- but their certificates are signed by different CAs -- they're
428+
// unable to communicate.
429+
func TestClient_BadTLS(t *testing.T) {
430+
const (
431+
cafile = "../helper/tlsutil/testdata/ca.pem"
432+
foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
433+
fookey = "../helper/tlsutil/testdata/nomad-foo-key.pem"
434+
badca = "../helper/tlsutil/testdata/ca-bad.pem"
435+
badcert = "../helper/tlsutil/testdata/nomad-bad.pem"
436+
badkey = "../helper/tlsutil/testdata/nomad-bad-key.pem"
437+
)
438+
s1, addr := testServer(t, func(c *nomad.Config) {
439+
c.TLSConfig = &nconfig.TLSConfig{
440+
EnableHTTP: true,
441+
EnableRPC: true,
442+
VerifyServerHostname: true,
443+
CAFile: cafile,
444+
CertFile: foocert,
445+
KeyFile: fookey,
446+
}
447+
})
448+
defer s1.Shutdown()
449+
testutil.WaitForLeader(t, s1.RPC)
450+
451+
c1 := testClient(t, func(c *config.Config) {
452+
c.Servers = []string{addr}
453+
c.TLSConfig = &nconfig.TLSConfig{
454+
EnableHTTP: true,
455+
EnableRPC: true,
456+
VerifyServerHostname: true,
457+
CAFile: badca,
458+
CertFile: badcert,
459+
KeyFile: badkey,
460+
}
461+
})
462+
defer c1.Shutdown()
463+
464+
req := structs.NodeSpecificRequest{
465+
NodeID: c1.Node().ID,
466+
QueryOptions: structs.QueryOptions{Region: "global"},
467+
}
468+
var out structs.SingleNodeResponse
469+
deadline := time.Now().Add(1234 * time.Millisecond)
470+
for time.Now().Before(deadline) {
471+
err := c1.RPC("Node.GetNode", &req, &out)
472+
if err == nil {
473+
t.Fatalf("client RPC succeeded when it should have failed:\n%+v", out)
474+
}
475+
}
476+
}
477+
385478
func TestClient_Register(t *testing.T) {
386479
s1, _ := testServer(t, nil)
387480
defer s1.Shutdown()

nomad/config.go

-3
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ type Config struct {
9292
// RaftTimeout is applied to any network traffic for raft. Defaults to 10s.
9393
RaftTimeout time.Duration
9494

95-
// RequireTLS ensures that all RPC traffic is protected with TLS
96-
RequireTLS bool
97-
9895
// SerfConfig is the configuration for the serf cluster
9996
SerfConfig *serf.Config
10097

nomad/rpc.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ func (s *Server) handleConn(conn net.Conn, isTLS bool) {
9797
return
9898
}
9999

100-
// Enforce TLS if VerifyIncoming is set
101-
if s.config.RequireTLS && !isTLS && RPCType(buf[0]) != rpcTLS {
100+
// Enforce TLS if EnableRPC is set
101+
if s.config.TLSConfig.EnableRPC && !isTLS && RPCType(buf[0]) != rpcTLS {
102102
s.logger.Printf("[WARN] nomad.rpc: Non-TLS connection attempted with RequireTLS set")
103103
conn.Close()
104104
return

nomad/server_test.go

+47-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/hashicorp/nomad/command/agent/consul"
1313
"github.com/hashicorp/nomad/nomad/structs"
14+
"github.com/hashicorp/nomad/nomad/structs/config"
1415
"github.com/hashicorp/nomad/testutil"
1516
)
1617

@@ -62,6 +63,11 @@ func testServer(t *testing.T, cb func(*Config)) *Server {
6263
f := false
6364
config.VaultConfig.Enabled = &f
6465

66+
// Squelch output when -v isn't specified
67+
if !testing.Verbose() {
68+
config.LogOutput = ioutil.Discard
69+
}
70+
6571
// Invoke the callback if any
6672
if cb != nil {
6773
cb(config)
@@ -71,7 +77,7 @@ func testServer(t *testing.T, cb func(*Config)) *Server {
7177
config.RaftConfig.StartAsLeader = !config.DevDisableBootstrap
7278

7379
shutdownCh := make(chan struct{})
74-
logger := log.New(config.LogOutput, "", log.LstdFlags)
80+
logger := log.New(config.LogOutput, fmt.Sprintf("[%s] ", config.NodeName), log.LstdFlags)
7581
consulSyncer, err := consul.NewSyncer(config.ConsulConfig, shutdownCh, logger)
7682
if err != nil {
7783
t.Fatalf("err: %v", err)
@@ -107,6 +113,46 @@ func TestServer_RPC(t *testing.T) {
107113
}
108114
}
109115

116+
func TestServer_RPC_MixedTLS(t *testing.T) {
117+
const (
118+
cafile = "../helper/tlsutil/testdata/ca.pem"
119+
foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
120+
fookey = "../helper/tlsutil/testdata/nomad-foo-key.pem"
121+
)
122+
s1 := testServer(t, func(c *Config) {
123+
c.BootstrapExpect = 3
124+
c.DevDisableBootstrap = true
125+
c.TLSConfig = &config.TLSConfig{
126+
EnableHTTP: true,
127+
EnableRPC: true,
128+
VerifyServerHostname: true,
129+
CAFile: cafile,
130+
CertFile: foocert,
131+
KeyFile: fookey,
132+
}
133+
})
134+
defer s1.Shutdown()
135+
136+
cb := func(c *Config) {
137+
c.BootstrapExpect = 3
138+
c.DevDisableBootstrap = true
139+
}
140+
s2 := testServer(t, cb)
141+
defer s2.Shutdown()
142+
s3 := testServer(t, cb)
143+
defer s3.Shutdown()
144+
145+
testJoin(t, s1, s2, s3)
146+
testutil.WaitForLeader(t, s2.RPC)
147+
testutil.WaitForLeader(t, s3.RPC)
148+
149+
// s1 shouldn't be able to join
150+
leader := ""
151+
if err := s1.RPC("Status.Leader", &structs.GenericRequest{}, &leader); err == nil {
152+
t.Errorf("expected a connection error from TLS server but received none; found leader: %q", leader)
153+
}
154+
}
155+
110156
func TestServer_Regions(t *testing.T) {
111157
// Make the servers
112158
s1 := testServer(t, func(c *Config) {

0 commit comments

Comments
 (0)