-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
So the different drivers can be test their new implementations of sni-proxy for using it, need to start the cluster from the commandline like this: ``` ❯ ccm start --sni-proxy sni_proxy listening on: 127.0.0.1:443 ``` using it from python code, would be a bit diffrent: ```python nodes_info = get_cluster_info(self.cluster.get_path(), address=self.cluster.nodelist()[0].address(), port=9142) docker_id, listen_address, listen_port = \ start_sni_proxy(self.cluster.get_path(), nodes_info=nodes_info) ``` Ref: scylladb/gocql#97
- Loading branch information
Showing
9 changed files
with
296 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
include *.md | ||
include ccmlib/resources/bin/*.sh | ||
include ccmlib/scylla_test_ssl/scylla-manager-agent.key | ||
include ccmlib/scylla_test_ssl/scylla-manager-agent.crt | ||
include ccmlib/scylla_test_ssl/scylla-manager-agent.crt | ||
include ccmlib/resources/docker/sniproxy/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
FROM alpine | ||
|
||
RUN apk --no-cache add sniproxy | ||
|
||
EXPOSE 80 443 | ||
ENTRYPOINT ["sniproxy", "-f"] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import os | ||
import string | ||
import subprocess | ||
import json | ||
import base64 | ||
from contextlib import contextmanager | ||
import tempfile | ||
from textwrap import dedent | ||
import distutils.dir_util | ||
|
||
import yaml | ||
|
||
from ccmlib.utils.ssl_utils import generate_ssl_stores | ||
|
||
|
||
@contextmanager | ||
def file_or_memory(path=None, data=None): | ||
# since we can't read keys/cert from memory yet | ||
# see https://github.com/python/cpython/pull/2449 which isn't accepted and PEP-543 that was withdrawn | ||
# so we use temporary file to load the key | ||
if data: | ||
with tempfile.NamedTemporaryFile(mode="wb") as f: | ||
d = base64.decodebytes(bytes(data, encoding='utf-8')) | ||
f.write(d) | ||
if not d.endswith(b"\n"): | ||
f.write(b"\n") | ||
|
||
f.flush() | ||
yield f.name | ||
|
||
if path: | ||
yield path | ||
|
||
|
||
def create_cloud_config(ssl_dir, host, port, username='cassandra', password='cassandra'): | ||
|
||
def encode_base64(filename): | ||
return base64.b64encode(open(os.path.join(ssl_dir, filename), 'rb').read()).decode() | ||
|
||
cadata = encode_base64('ccm_node.cer') | ||
certificate_data = encode_base64('ccm_node.cer') | ||
key_data = encode_base64('ccm_node.key') | ||
|
||
config = dict(datacenters={'eu-west-1': dict(certificateAuthorityData=cadata, | ||
server=f'{host}:{port}', | ||
nodeDomain='cluster-id.scylla.com')}, | ||
authInfos={'default': dict(clientCertificateData=certificate_data, | ||
clientKeyData=key_data, | ||
username=username, | ||
password=password, | ||
insecureSkipTlsVerify=False)}, | ||
contexts={'default': dict(datacenterName='eu-west-1', authInfoName='default')}, | ||
currentContext='default') | ||
|
||
with open(os.path.join(ssl_dir, 'config_data.yaml'), 'w') as config_file: | ||
config_file.write(yaml.safe_dump(config, sort_keys=False)) | ||
|
||
config = dict(datacenters={'eu-west-1': dict(certificateAuthorityPath=os.path.join(ssl_dir, 'ccm_node.cer'), | ||
server=f'{host}:{port}', | ||
nodeDomain='cluster-id.scylla.com')}, | ||
authInfos={'default': dict(clientCertificatePath=os.path.join(ssl_dir, 'ccm_node.cer'), | ||
clientKeyPath=os.path.join(ssl_dir, 'ccm_node.key'), | ||
username=username, | ||
password=password, | ||
insecureSkipTlsVerify=False)}, | ||
contexts={'default': dict(datacenterName='eu-west-1', authInfoName='default')}, | ||
currentContext='default') | ||
|
||
with open(os.path.join(ssl_dir, 'config_path.yaml'), 'w') as config_file: | ||
config_file.write(yaml.safe_dump(config, sort_keys=False)) | ||
|
||
return os.path.join(ssl_dir, 'config_data.yaml'), os.path.join(ssl_dir, 'config_path.yaml') | ||
|
||
|
||
def stop_sni_proxy(docker_id): | ||
subprocess.check_output(['/bin/bash', '-c', f'docker rm -f {docker_id}']) | ||
|
||
|
||
def configure_sni_proxy(conf_dir, nodes_info, listen_port=443): | ||
sniproxy_conf_tmpl = dedent(""" | ||
user sniproxy | ||
pidfile /var/run/sniproxy/sniproxy.pid | ||
error_log { | ||
filename /dev/stderr | ||
priority debug | ||
} | ||
listener $FIRST_ADDRESS $listen_port { | ||
proto tls | ||
access_log { | ||
filename /dev/stdout | ||
} | ||
} | ||
table { | ||
$TABLES | ||
} | ||
""") | ||
tables = "" | ||
mapping = {} | ||
address, port, host_id = list(nodes_info)[0] | ||
tables += f" any.cluster-id.scylla.com {address}:{port}\n" | ||
mapping['FIRST_ADDRESS'] = address | ||
mapping['listen_port'] = listen_port | ||
|
||
for address, port, host_id in nodes_info: | ||
tables += f" {host_id}.cluster-id.scylla.com {address}:{port}\n" | ||
|
||
tmpl = string.Template(sniproxy_conf_tmpl) | ||
sniproxy_conf_path = os.path.join(conf_dir, 'sniproxy.conf') | ||
|
||
with open(sniproxy_conf_path, 'w') as fp: | ||
fp.write(tmpl.substitute(TABLES=tables, **mapping)) | ||
|
||
return sniproxy_conf_path | ||
|
||
|
||
def start_sni_proxy(conf_dir, nodes_info, listen_port=443): | ||
address, _, _ = list(nodes_info)[0] | ||
sniproxy_conf_path = configure_sni_proxy(conf_dir, nodes_info, listen_port=443) | ||
sniproxy_dockerfile = os.path.join(os.path.dirname(__file__), '..', 'resources', 'docker', 'sniproxy') | ||
subprocess.check_output(['/bin/bash', '-c', f'docker build {sniproxy_dockerfile} -t sniproxy'], universal_newlines=True) | ||
docker_id = subprocess.check_output(['/bin/bash', '-c', f'docker run -d --network=host -v {sniproxy_conf_path}:/etc/sniproxy.conf:z -p 443 -it sniproxy'], universal_newlines=True) | ||
|
||
return docker_id.strip(), address, listen_port | ||
|
||
|
||
def get_cluster_info(cluster, port=9142): | ||
|
||
node1 = cluster.nodelist()[0] | ||
stdout, stderr = node1.run_cqlsh(cmds='select JSON host_id,broadcast_address from system.local ;', | ||
return_output=True) | ||
|
||
nodes_info = [] | ||
for line in stdout.splitlines()[3:-2]: | ||
host = json.loads(line) | ||
nodes_info.append((host['broadcast_address'], port, host['host_id'])) | ||
|
||
stdout, stderr = node1.run_cqlsh(cmds='select JSON peer,host_id from system.peers ;', | ||
return_output=True) | ||
|
||
for line in stdout.splitlines()[3:-2]: | ||
host = json.loads(line) | ||
nodes_info.append((host['peer'], port, host['host_id'])) | ||
|
||
return nodes_info | ||
|
||
|
||
def refresh_certs(cluster, nodes_info): | ||
with tempfile.TemporaryDirectory() as tmp_dir: | ||
dns_names = ['any.cluster-id.scylla.com'] + \ | ||
['{}.cluster-id.scylla.com'.format(host_id) for _, _, host_id in nodes_info] | ||
generate_ssl_stores(tmp_dir, dns_names=dns_names) | ||
distutils.dir_util.copy_tree(tmp_dir, cluster.get_path()) | ||
|
||
|
||
if __name__ == "__main__": | ||
from ccmlib.cmds.command import Cmd | ||
from ccmlib import common | ||
|
||
a = Cmd() | ||
a.path = common.get_default_path() | ||
a.cluster = a._load_current_cluster() | ||
nodes_info = get_cluster_info(a.cluster) | ||
conf_dir = a.cluster.get_path() | ||
docker_id, host, port = start_sni_proxy(conf_dir=conf_dir, nodes_info=nodes_info) | ||
print(create_cloud_config(conf_dir, host, port)) | ||
stop_sni_proxy(docker_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import os | ||
import subprocess | ||
import logging | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def generate_ssl_stores(base_dir, passphrase='cassandra', dns_names=None): | ||
""" | ||
Util for generating ssl stores using java keytool -- nondestructive method if stores already exist this method is | ||
a no-op. | ||
@param base_dir (str) directory where keystore.jks, truststore.jks and ccm_node.cer will be placed | ||
@param passphrase (Optional[str]) currently ccm expects a passphrase of 'cassandra' so it's the default but it can be | ||
overridden for failure testing | ||
@return None | ||
@throws CalledProcessError If the keytool fails during any step | ||
""" | ||
|
||
if os.path.exists(os.path.join(base_dir, 'keystore.jks')): | ||
print("keystores already exists - skipping generation of ssl keystores") | ||
return | ||
|
||
legacy = ['-legacy'] if '-legacy' in subprocess.run(['openssl', 'pkcs12', '--help'], | ||
universal_newlines=True, stderr=subprocess.PIPE).stderr else '' | ||
dns_names = dns_names or ['any.cluster-id.scylla.com'] | ||
ext = ",".join(["dns:{}".format(name) for name in dns_names]) | ||
print("generating keystore.jks in [{0}]".format(base_dir)) | ||
subprocess.check_call(['keytool', '-genkeypair', '-alias', 'ccm_node', '-keyalg', 'RSA', '-validity', '365', | ||
'-keystore', os.path.join(base_dir, 'keystore.jks'), '-storepass', passphrase, | ||
'-dname', 'cn=Cassandra Node,ou=CCMnode,o=DataStax,c=US', '-keypass', passphrase, | ||
'-ext', 'san={}'.format(ext)]) | ||
|
||
print("exporting cert from keystore.jks in [{0}]".format(base_dir)) | ||
subprocess.check_call(['keytool', '-export', '-rfc', '-alias', 'ccm_node', | ||
'-keystore', os.path.join(base_dir, 'keystore.jks'), | ||
'-file', os.path.join(base_dir, 'ccm_node.cer'), '-storepass', passphrase]) | ||
print("importing cert into truststore.jks in [{0}]".format(base_dir)) | ||
subprocess.check_call(['keytool', '-import', '-file', os.path.join(base_dir, 'ccm_node.cer'), | ||
'-alias', 'ccm_node', '-keystore', os.path.join(base_dir, 'truststore.jks'), | ||
'-storepass', passphrase, '-noprompt']) | ||
# Added for scylla: Generate pem format cert/key | ||
print("exporting cert to pks12 from keystore.jks in [{0}]".format(base_dir)) | ||
subprocess.check_call(['keytool', '-importkeystore', '-srckeystore', os.path.join(base_dir, 'keystore.jks'), | ||
'-srcstorepass', passphrase, '-srckeypass', passphrase, '-destkeystore', | ||
os.path.join(base_dir, 'ccm_node.p12'), '-deststoretype', 'PKCS12', | ||
'-srcalias', 'ccm_node', '-deststorepass', passphrase, '-destkeypass', passphrase]) | ||
print("Using openssl to split pks12 in [{0}] to pem format".format(base_dir)) | ||
subprocess.check_call(['openssl', 'pkcs12', '-in', os.path.join(base_dir, 'ccm_node.p12'), | ||
'-passin', 'pass:{0}'.format(passphrase), '-nokeys', | ||
'-out', os.path.join(base_dir, 'ccm_node.pem')] + legacy) | ||
# Key with password. We want without... | ||
subprocess.check_call(['openssl', 'pkcs12', '-in', os.path.join(base_dir, 'ccm_node.p12'), | ||
'-passin', 'pass:{0}'.format(passphrase), | ||
'-passout', 'pass:{0}'.format(passphrase), '-nocerts', | ||
'-out', os.path.join(base_dir, 'ccm_node.tmp')] + legacy) | ||
subprocess.check_call(['openssl', 'rsa', '-in', os.path.join(base_dir, 'ccm_node.tmp'), | ||
'-passin', 'pass:{0}'.format(passphrase), | ||
'-out', os.path.join(base_dir, 'ccm_node.key')]) | ||
|
||
|
||
if __name__ == "__main__": | ||
generate_ssl_stores('/home/fruch/ccm_ssl', dns_names=['any.cluster-id.scylla.com']) |