forked from apache/cassandra-dtest
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sslnodetonode_test.py
192 lines (148 loc) · 7.88 KB
/
sslnodetonode_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import os
import os.path
import shutil
import time
import pytest
import logging
from dtest import Tester
from tools import sslkeygen
since = pytest.mark.since
logger = logging.getLogger(__name__)
# as the error message logged will be different per netty ssl implementation (jdk vs openssl (libre vs boring vs ...)),
# the best we can do is just look for a SSLHandshakeException
_LOG_ERR_HANDSHAKE = "javax.net.ssl.SSLHandshakeException"
_LOG_ERR_GENERAL = "javax.net.ssl.SSLException"
@since('3.6')
class TestNodeToNodeSSLEncryption(Tester):
def test_ssl_enabled(self):
"""Should be able to start with valid ssl options"""
credNode1 = sslkeygen.generate_credentials("127.0.0.1")
credNode2 = sslkeygen.generate_credentials("127.0.0.2", credNode1.cakeystore, credNode1.cacert)
self.setup_nodes(credNode1, credNode2)
self.cluster.start()
self.cql_connection(self.node1)
def test_ssl_correct_hostname_with_validation(self):
"""Should be able to start with valid ssl options"""
credNode1 = sslkeygen.generate_credentials("127.0.0.1")
credNode2 = sslkeygen.generate_credentials("127.0.0.2", credNode1.cakeystore, credNode1.cacert)
self.setup_nodes(credNode1, credNode2, endpoint_verification=True)
self.fixture_dtest_setup.allow_log_errors = False
self.cluster.start()
time.sleep(2)
self.cql_connection(self.node1)
def test_ssl_wrong_hostname_no_validation(self):
"""Should be able to start with valid ssl options"""
credNode1 = sslkeygen.generate_credentials("127.0.0.80")
credNode2 = sslkeygen.generate_credentials("127.0.0.81", credNode1.cakeystore, credNode1.cacert)
self.setup_nodes(credNode1, credNode2, endpoint_verification=False)
self.cluster.start()
time.sleep(2)
self.cql_connection(self.node1)
def test_ssl_wrong_hostname_with_validation(self):
"""Should be able to start with valid ssl options"""
credNode1 = sslkeygen.generate_credentials("127.0.0.80")
credNode2 = sslkeygen.generate_credentials("127.0.0.81", credNode1.cakeystore, credNode1.cacert)
self.setup_nodes(credNode1, credNode2, endpoint_verification=True)
self.fixture_dtest_setup.allow_log_errors = True
self.cluster.start(no_wait=True)
found = self._grep_msg(self.node1, _LOG_ERR_HANDSHAKE, _LOG_ERR_GENERAL)
assert found
found = self._grep_msg(self.node2, _LOG_ERR_HANDSHAKE, _LOG_ERR_GENERAL)
assert found
self.cluster.stop()
def test_ssl_client_auth_required_fail(self):
"""peers need to perform mutual auth (cient auth required), but do not supply the local cert"""
credNode1 = sslkeygen.generate_credentials("127.0.0.1")
credNode2 = sslkeygen.generate_credentials("127.0.0.2")
self.setup_nodes(credNode1, credNode2, client_auth=True)
self.fixture_dtest_setup.allow_log_errors = True
self.cluster.start(no_wait=True)
time.sleep(2)
found = self._grep_msg(self.node1, _LOG_ERR_HANDSHAKE, _LOG_ERR_GENERAL)
assert found
found = self._grep_msg(self.node2, _LOG_ERR_HANDSHAKE, _LOG_ERR_GENERAL)
assert found
self.cluster.stop()
assert found
def test_ssl_client_auth_required_succeed(self):
"""peers need to perform mutual auth (cient auth required), but do not supply the loca cert"""
credNode1 = sslkeygen.generate_credentials("127.0.0.1")
credNode2 = sslkeygen.generate_credentials("127.0.0.2", credNode1.cakeystore, credNode1.cacert)
sslkeygen.import_cert(credNode1.basedir, 'ca127.0.0.2', credNode2.cacert, credNode1.cakeystore)
sslkeygen.import_cert(credNode2.basedir, 'ca127.0.0.1', credNode1.cacert, credNode2.cakeystore)
self.setup_nodes(credNode1, credNode2, client_auth=True)
self.cluster.start()
self.cql_connection(self.node1)
def test_ca_mismatch(self):
"""CA mismatch should cause nodes to fail to connect"""
credNode1 = sslkeygen.generate_credentials("127.0.0.1")
credNode2 = sslkeygen.generate_credentials("127.0.0.2") # mismatching CA!
self.setup_nodes(credNode1, credNode2)
self.fixture_dtest_setup.allow_log_errors = True
self.cluster.start(no_wait=True)
found = self._grep_msg(self.node1, _LOG_ERR_HANDSHAKE)
self.cluster.stop()
assert found
@since('4.0')
def test_optional_outbound_tls(self):
"""listen on TLS port, but optionally connect using TLS. this supports the upgrade case of starting with a non-encrypted cluster and then upgrading each node to use encryption.
@jira_ticket CASSANDRA-10404
"""
credNode1 = sslkeygen.generate_credentials("127.0.0.1")
credNode2 = sslkeygen.generate_credentials("127.0.0.2", credNode1.cakeystore, credNode1.cacert)
# first, start cluster without TLS (either listening or connecting)
# Optional should be true by default in 4.0 thanks to CASSANDRA-15262
self.setup_nodes(credNode1, credNode2, internode_encryption='none')
self.cluster.start()
self.cql_connection(self.node1)
# next connect with TLS for the outbound connections
self.bounce_node_with_updated_config(credNode1, self.node1, 'all', encryption_optional=True)
self.bounce_node_with_updated_config(credNode2, self.node2, 'all', encryption_optional=True)
# now shutdown the plaintext port
self.bounce_node_with_updated_config(credNode1, self.node1, 'all', encryption_optional=False)
self.bounce_node_with_updated_config(credNode2, self.node2, 'all', encryption_optional=False)
self.cluster.stop()
def bounce_node_with_updated_config(self, credentials, node, internode_encryption, encryption_optional):
node.stop()
self.copy_cred(credentials, node, internode_encryption, encryption_optional)
node.start(wait_for_binary_proto=True)
def _grep_msg(self, node, *kwargs):
tries = 30
while tries > 0:
try:
for err in kwargs:
m = node.grep_log(err)
if m:
return True
except IOError:
pass # log does not exists yet
time.sleep(1)
tries -= 1
return False
def setup_nodes(self, credentials1, credentials2, endpoint_verification=False, client_auth=False, internode_encryption='all', encryption_optional=None):
cluster = self.cluster
cluster = cluster.populate(2)
self.node1 = cluster.nodelist()[0]
self.copy_cred(credentials1, self.node1, internode_encryption, encryption_optional, endpoint_verification, client_auth)
self.node2 = cluster.nodelist()[1]
self.copy_cred(credentials2, self.node2, internode_encryption, encryption_optional, endpoint_verification, client_auth)
def copy_cred(self, credentials, node, internode_encryption, encryption_optional, endpoint_verification=False, client_auth=False):
dir = node.get_conf_dir()
kspath = os.path.join(dir, 'keystore.jks')
tspath = os.path.join(dir, 'truststore.jks')
shutil.copyfile(credentials.keystore, kspath)
shutil.copyfile(credentials.cakeystore, tspath)
server_enc_options = {
'internode_encryption': internode_encryption,
'keystore': kspath,
'keystore_password': 'cassandra',
'truststore': tspath,
'truststore_password': 'cassandra',
'require_endpoint_verification': endpoint_verification,
'require_client_auth': client_auth,
}
if self.cluster.version() >= '4.0' and encryption_optional is not None:
server_enc_options['optional'] = encryption_optional
node.set_configuration_options(values={
'server_encryption_options': server_enc_options
})