From 9bae5d42c3588c1cafaeb2a882cc350d0586d9b9 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Mon, 22 Jun 2020 15:13:17 -0700 Subject: [PATCH] test: add SSL test This adds a simple SSL test along with the framework for running the test Zookeeper in a mode where it listens on both SSL and non-SSL ports. --- kazoo/testing/__init__.py | 9 ++- kazoo/testing/common.py | 31 +++++++- kazoo/testing/harness.py | 37 +++++++++- kazoo/testing/sslfixtures.py | 138 +++++++++++++++++++++++++++++++++++ kazoo/tests/test_client.py | 10 ++- 5 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 kazoo/testing/sslfixtures.py diff --git a/kazoo/testing/__init__.py b/kazoo/testing/__init__.py index c1ae12cc..325d9179 100644 --- a/kazoo/testing/__init__.py +++ b/kazoo/testing/__init__.py @@ -1,4 +1,7 @@ -from kazoo.testing.harness import KazooTestCase, KazooTestHarness +from kazoo.testing.harness import ( + KazooTestCase, + SSLKazooTestCase, + KazooTestHarness +) - -__all__ = ('KazooTestHarness', 'KazooTestCase', ) +__all__ = ('KazooTestHarness', 'KazooTestCase', 'SSLKazooTestCase', ) diff --git a/kazoo/testing/common.py b/kazoo/testing/common.py index 9cf0ff00..52034e40 100644 --- a/kazoo/testing/common.py +++ b/kazoo/testing/common.py @@ -33,6 +33,8 @@ import tempfile import traceback +from . import sslfixtures + log = logging.getLogger(__name__) @@ -63,7 +65,8 @@ def to_java_compatible_path(path): ServerInfo = namedtuple( "ServerInfo", - "server_id client_port election_port leader_port admin_port peer_type") + "server_id client_port secure_client_port " + "election_port leader_port admin_port peer_type") class ManagedZooKeeper(object): @@ -105,6 +108,8 @@ def run(self): log_path = os.path.join(self.working_path, "log") log4j_path = os.path.join(self.working_path, "log4j.properties") data_path = os.path.join(self.working_path, "data") + truststore_path = os.path.join(self.working_path, "cacert.pem") + keystore_path = os.path.join(self.working_path, "keystore.pem") # various setup steps if not os.path.exists(self.working_path): @@ -114,18 +119,31 @@ def run(self): if not os.path.exists(data_path): os.mkdir(data_path) + with open(truststore_path, "w") as truststore: + truststore.write(sslfixtures.ca_cert) + with open(keystore_path, "w") as keystore: + keystore.write(sslfixtures.server_cert) + keystore.write(sslfixtures.server_key) + with open(config_path, "w") as config: config.write(""" tickTime=2000 dataDir=%s clientPort=%s +secureClientPort=%s maxClientCnxns=0 admin.serverPort=%s +serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider +ssl.keyStore.location=%s +ssl.trustStore.location=%s %s """ % (to_java_compatible_path(data_path), self.server_info.client_port, + self.server_info.secure_client_port, self.server_info.admin_port, + keystore_path, + truststore_path, "\n".join(self.configuration_entries))) # NOQA # setup a replicated setup if peers are specified @@ -232,6 +250,11 @@ def address(self): """Get the address of the ZooKeeper instance.""" return "%s:%s" % (self.host, self.client_port) + @property + def secure_address(self): + """Get the address of the SSL ZooKeeper instance.""" + return "%s:%s" % (self.host, self.secure_client_port) + @property def running(self): return self._running @@ -240,6 +263,10 @@ def running(self): def client_port(self): return self.server_info.client_port + @property + def secure_client_port(self): + return self.server_info.secure_client_port + def reset(self): """Stop the zookeeper instance, cleaning out its on disk-data.""" self.stop() @@ -291,7 +318,7 @@ def __init__(self, install_path=None, classpath=None, else: peer_type = 'participant' info = ServerInfo(server_id, port, port + 1, port + 2, port + 3, - peer_type) + port + 4, peer_type) peers.append(info) port += 10 diff --git a/kazoo/testing/harness.py b/kazoo/testing/harness.py index ce8748aa..a143b157 100644 --- a/kazoo/testing/harness.py +++ b/kazoo/testing/harness.py @@ -1,6 +1,7 @@ """Kazoo testing harnesses""" import logging import os +import tempfile import uuid import unittest @@ -12,6 +13,8 @@ KazooState ) from kazoo.testing.common import ZookeeperCluster +from kazoo.testing import sslfixtures +from kazoo.tests.util import TRAVIS_ZK_VERSION log = logging.getLogger(__name__) @@ -158,6 +161,10 @@ def cluster(self): def servers(self): return ",".join([s.address for s in self.cluster]) + @property + def secure_servers(self): + return ",".join([s.secure_address for s in self.cluster]) + def _get_nonchroot_client(self): c = KazooClient(self.servers) self._clients.append(c) @@ -193,7 +200,10 @@ def setup_zookeeper(self, **client_options): if do_start: self.cluster.start() namespace = "/kazootests" + uuid.uuid4().hex - self.hosts = self.servers + namespace + if client_options.get('use_ssl'): + self.hosts = self.secure_servers + namespace + else: + self.hosts = self.servers + namespace if 'timeout' not in client_options: client_options['timeout'] = self.DEFAULT_CLIENT_TIMEOUT self.client = self._get_client(**client_options) @@ -245,3 +255,28 @@ def setUp(self): def tearDown(self): self.teardown_zookeeper() + + +class SSLKazooTestCase(KazooTestCase): + def setUp(self): + ssl_path = tempfile.mkdtemp() + key_path = os.path.join(ssl_path, 'key.pem') + cert_path = os.path.join(ssl_path, 'cert.pem') + cacert_path = os.path.join(ssl_path, 'cacert.pem') + with open(key_path, 'w') as key_file: + key_file.write(sslfixtures.client_key) + with open(cert_path, 'w') as cert_file: + cert_file.write(sslfixtures.client_cert) + with open(cacert_path, 'w') as cacert_file: + cacert_file.write(sslfixtures.ca_cert) + self.setup_zookeeper( + use_ssl=True, + keyfile=key_path, + certfile=cert_path, + ca=cacert_path) + if TRAVIS_ZK_VERSION: + version = TRAVIS_ZK_VERSION + else: + version = self.client.server_version() + if not version or version < (3, 5): + pytest.skip("Must use Zookeeper 3.5 or above") diff --git a/kazoo/testing/sslfixtures.py b/kazoo/testing/sslfixtures.py new file mode 100644 index 00000000..fad95bfa --- /dev/null +++ b/kazoo/testing/sslfixtures.py @@ -0,0 +1,138 @@ +ca_cert = """ +-----BEGIN CERTIFICATE----- +MIIDkTCCAnmgAwIBAgIUG8baUt2UBIoO569cnAU8xPP6l2UwDQYJKoZIhvcNAQEL +BQAwWDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAoM +DENvbXBhbnkgTmFtZTEMMAoGA1UECwwDT3JnMQ8wDQYDVQQDDAZjYXJvb3QwHhcN +MjAwNjE2MjI0NjA4WhcNMzAwMzE2MjI0NjA4WjBYMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMQ29tcGFueSBOYW1lMQwwCgYDVQQL +DANPcmcxDzANBgNVBAMMBmNhcm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAOt8elMFE46KAP+YzLuFaRoYIeeWtTEi5WUZBtwRCDTcquW/tiQX7Kfj +bjcEEjbtuTY1386qIWOC9muQfbt/uFdAi6AN+o1Ta4/IC2olGR9p15ol/i5fQv98 +DuvKZrX4qLOYzlDVW31AYpJ2HrEPn1M2hiKIxmn3ahqHT6I18NvMnse0pvfIODpM +XG8hy/xHzNoOCbh07kXmCalAh4aWrvBK5k7FVNso08E6Nr/+a1e++wAQtv615J73 +rvnF8dpTvb04oKpVUZCHFKAsp6HmdXml6cjoh/9z3jGPJPEfKfAU0rTjZthFkVrr +iLFnRLeV0JcLC4kM4r6V6ULb7R2Xl88CAwEAAaNTMFEwHQYDVR0OBBYEFCsPRk0g +sH+rW1xUQ+cA8FoxhTduMB8GA1UdIwQYMBaAFCsPRk0gsH+rW1xUQ+cA8FoxhTdu +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFTVPYLceMgH/ALx +5mvx1yKxhLY/kADsLJ03+3Sn8gksZzIhA6yTB3TetL0dexxiC7a80pLjdScyIq+L +EHeVlA2GpevyK9VzFzGDfLRATLSBJXqdbfE3joFECivaQFvu0KUeI0qdmyzxvJQr +NaIeSQDUbdBm3Cpge7UdwKDtDmg8fXzZ21KEg/cW8XM/8D/6LW2ytInEvSIs0PlS +XVZ1xL1vES2xay/4eOwDtk0MQsrQ2W3Am6nhx3RVreio373CJ52KhcfxqoBGbGuz +UknntHVF+Cp+w1CpmeOZARSAl7yxNtOuwwYX2/eEX0ZTCxFzhXUd4I4qQv+RG99X +OVYdPKQ= +-----END CERTIFICATE----- +""" + +client_cert = """ +-----BEGIN CERTIFICATE----- +MIIDyzCCArOgAwIBAgIUG8baUt2UBIoO569cnAU8xPP6l2YwDQYJKoZIhvcNAQEL +BQAwWDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAoM +DENvbXBhbnkgTmFtZTEMMAoGA1UECwwDT3JnMQ8wDQYDVQQDDAZjYXJvb3QwHhcN +MjAwNjE2MjI0NjA4WhcNMzAwMzE2MjI0NjA4WjBqMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTEQMA4GA1UEBwwHT2FrbGFuZDEVMBMGA1UECgwMQ29t +cGFueSBOYW1lMQwwCgYDVQQLDANPcmcxDzANBgNVBAMMBmNsaWVudDCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3t8ny3O6RQJky5EQbm6vJ2AE7Seri +VdqRoJeCgRcS3mYfigTifCiwSMB3NbGeE2hlm/IPoTsIsy1Idr3WsVpZIweUS5at +h2UGnSTaSMNdmOzfsFLXqyg25PCZNeAtdhZ6+rYTzBT5bQafrap7bo0rtaHFTy4V +GiL2/w3omolDxTkJgfpWpwnoB2rxgkIcDdDPRIR05fCohuYrBCrJlTUXkCR0P7d7 +Pengt7UF1HGuT6Xm2l+THkk0Caa/gAndS+nZnBcTMcsREzxFtm6K27LKMy9HPvLZ +pQ5uhTEmchFKwc1htyZzLSLg8zZUji2c1X0ImKzRm6y8Em3re446mNcCAwEAAaN7 +MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQg +Q2VydGlmaWNhdGUwHQYDVR0OBBYEFEhocSf93pMDgrbda+T5HllPABYxMB8GA1Ud +IwQYMBaAFCsPRk0gsH+rW1xUQ+cA8FoxhTduMA0GCSqGSIb3DQEBCwUAA4IBAQAc +UXxgHeTz8xjnCvDTQdSFbaesFuUJGDKGyBuU0417741CO9mcljOOTtQycPzhg9rg +aUwgfvyxJu/bbLwBHgrCke3UKkYNhoqpSiHM89PXXGU5oIpW0dwHCgHI71YC7VFY +tD1vzjl5F8+Ya6Em91DqbXq+HDteBTJowiJl5ALCotR1qfsYO1pk0HCGm4+e2kmv +5B8V1woM8yplDscp+XdNhM4zf+gxXLMtDGfIspYi0SXqzKcSTxx7NgL5KH2Okwoq +I1WJF3cyINV7ZDCzWQhlg/tIbUxZVSmCErSGLRw4A7ay2xn8RuLvR9nq10Ltq9Fl +F7xcPmb81CA5YfCzZjWD +-----END CERTIFICATE----- +""" + +client_key = """ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrt7fJ8tzukUCZ +MuREG5urydgBO0nq4lXakaCXgoEXEt5mH4oE4nwosEjAdzWxnhNoZZvyD6E7CLMt +SHa91rFaWSMHlEuWrYdlBp0k2kjDXZjs37BS16soNuTwmTXgLXYWevq2E8wU+W0G +n62qe26NK7WhxU8uFRoi9v8N6JqJQ8U5CYH6VqcJ6Adq8YJCHA3Qz0SEdOXwqIbm +KwQqyZU1F5AkdD+3ez3p4Le1BdRxrk+l5tpfkx5JNAmmv4AJ3Uvp2ZwXEzHLERM8 +RbZuituyyjMvRz7y2aUOboUxJnIRSsHNYbcmcy0i4PM2VI4tnNV9CJis0ZusvBJt +63uOOpjXAgMBAAECggEAeVj+F0d6G1koRlrxdrPv4YllDRHB3awOscOhsg/2e8DI +y3BUT7lhtjtFXkWf+yHTYz4S4w9qh9ZREJoZ13PlpwxAtfV1KUj92kW/PTDIpM76 +58H3z8M639NTHDeZHA/kWGSV0Z/R46yUAE9NIaHScQ7Hb0eI+e7NppHXkhOjSYYj +ftctYppsNmsnTfjFw/hHnh8krxsdTMI1IMcZ7OEJ4i/tWaBeBnCSocbvYCjPoT8C +NDgjBHL2xP2ggVKuGBRp51kc7vbKeZs7SAzcE113QPznBruSpBz8HX0qBXatjHrI +AuAV5Ys/vOelYuzssc2jkPS/1F1WNeo9/7fuskF7KQKBgQDSQ9j9FyL1A3R4eWTj +Ta/gTv9QOktelywOvv7JQ+Tk+WZU9a2nBtz1xmbyRRBAmSXM3eE9dcILs/1a6Zcj +OqsNXcjZHMPz7DZqPnkbT35PmzKpwX5H9WSVKJzDWWXOv8RqvxcSkE60W6aZpyQ2 ++33wPYKZWrhxMbBybdaU3gpJCwKBgQDREXFjy4GTKltQ2eyD6N01Ybb/iXiTpjCj +AjVaLskZ+DCzSJGy/cfFh8bGhS3BeGpr7zozhrwFssGnpxzQJOj4cRt0u+0vX/x0 +3ru6vlRaWi02mBtJDfykVsZByAGJmmjycyRnea+AJUtq38j+OZYt3z9T/e8eqrDc +uKYWQJMG5QKBgA75/i6owziocldSbjkyg2B6v/y66jtmkSDLweIcD+WvTfanOSM4 +feyToSmIecvgHK0m8LAr/xJOU7FyCe3cc+qTz4aHYf2xyX5vzLiBRQHukAE/YvtF +I1Dsf536dCHxzs+7pK0R0klObeNr4Ex6RVIjT3YU3CZT37mG0U3xMtJZAoGBAJGp +goQSPPQo9bhuLTB2pPDVZOfDgwP0LTzVhBCSOkDiwCOZcxHvaSmCAQc9sPR0Dkmn +Qri1rfiWZACIGTYYEU1PmN6LNKTmToGq0cc5ZpIQ9CFudsw+d4CUiMs6K4AQ2f/a +I0oI6TLySQpsYqBlxPGu8nN1oA2QxWrzJ5ynXwjVAoGAf5FxSyS2aZnIvDexow4b +VryFDLEU9XxZ93zXxLiadEhUhOM8ftA5WRntk93p3VKTFjGs3ZL1Lag01lnf/dEL +tf1y0yDzZy5/znr0qXZwHbS2C8suwJbvDcgQyNd5x2WEifITfF/b2L8++UYPLDEf +MsSjWsBs+tTE+c7UdO9nArA= +-----END PRIVATE KEY----- +""" + +server_cert = """ +-----BEGIN CERTIFICATE----- +MIIDzjCCAragAwIBAgIUG8baUt2UBIoO569cnAU8xPP6l2owDQYJKoZIhvcNAQEL +BQAwWDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAoM +DENvbXBhbnkgTmFtZTEMMAoGA1UECwwDT3JnMQ8wDQYDVQQDDAZjYXJvb3QwHhcN +MjAwNjIyMjA0MDI3WhcNMzAwMzIyMjA0MDI3WjBtMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTEQMA4GA1UEBwwHT2FrbGFuZDEVMBMGA1UECgwMQ29t +cGFueSBOYW1lMQwwCgYDVQQLDANPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWBao+FUC9G1bE8jUgdoVypPmU8 +eWQdYQ9JGffWsQJbl0fInwyNlrzHf3cqoqIjp5Hiu3YWQbUklusIWG8xWMAVp1jt +1RnUscpZdcuKphtyAJ9EbUKgJdrr65ek7Mq6RXPYUJFJpgA9uosqxVfpKjUsrn8J +DMQDJvARAnIdoKbF9wqmJWAeAjjpFEUSG8KbW0EV8DjVIP8lEU7cP+uVrdgRVsP/ +JmVkBKS4rvNuraFtEPC0i0eq74r8zzZ9/nsYqavuEnTanE1mTeGM/wgKCbheOnDm +jpsFnNA4EVJdtzEUMP9JRjfiV5sJqDVLkBr3uh4rJPT/Qdgjl14Gxa0yfscCAwEA +AaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0 +ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFOL4f3K+liU5zFzL9A4XVkmLE5I1MB8G +A1UdIwQYMBaAFCsPRk0gsH+rW1xUQ+cA8FoxhTduMA0GCSqGSIb3DQEBCwUAA4IB +AQBJyX8dtQoDk7Z1sOx+cp+eWEUGgTq/z7rCKGJEVv/6GWV8DOU+NJ2n0ppHFEiA +VlPrGr4G2WSCcPVI7f4O3mCtF7FULaPnPWrJRlQhFgQhMmREzzPtdMvukbvDGG9Q +HCZqlaIh8o5DvwRhs/2vvRHdOb4LLy0BPkejM1WBcP9/8iBfMYF5YX3OSEaaSa2o +1pGiFUq1mBC943p0E33DspGEtN8jlskqmxUYKTXKCwXa6ujF9jtSFr0lgDd/1q4q +pBAgqcfkJA9su+4/FhVX2hk/9Mw07LfPlUxAaN7heLEM1FER5II1g6KLt4PWYpFI +utM34as3PKZXbemopWf2I9DD +-----END CERTIFICATE----- +""" + +server_key = """ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1gWqPhVAvRtWx +PI1IHaFcqT5lPHlkHWEPSRn31rECW5dHyJ8MjZa8x393KqKiI6eR4rt2FkG1JJbr +CFhvMVjAFadY7dUZ1LHKWXXLiqYbcgCfRG1CoCXa6+uXpOzKukVz2FCRSaYAPbqL +KsVX6So1LK5/CQzEAybwEQJyHaCmxfcKpiVgHgI46RRFEhvCm1tBFfA41SD/JRFO +3D/rla3YEVbD/yZlZASkuK7zbq2hbRDwtItHqu+K/M82ff57GKmr7hJ02pxNZk3h +jP8ICgm4Xjpw5o6bBZzQOBFSXbcxFDD/SUY34lebCag1S5Aa97oeKyT0/0HYI5de +BsWtMn7HAgMBAAECggEBALHRCOF0PJskbUPGy67EAIqz78HkXhSbx7Pe7QbmGOHz +cyDLRi5ZJDzrwU+wwEXSYl9E3lyQ72yUXRoDQgglMwpz7E/uKKC2xDs2K3Xt7k/i +/wMJWXU1SgDQsi0NUUJG/LXCTSQZH/0KfI/Mpis67bPMg2NHLW92mxYDw/6iUfov +PS/RuGPMRYTJXOorDqXpcLCEfEncui+W+3i1mUNjuPuC+5Z/eNZ27MZ6+fXKPu20 ++qYIWlHtKSvd91jiJPx5lE4gQxWe/o/Npu77R2HFRVU0wGwGmF93iZkYL2E+vKSC +7oDLtgHtPnzOMQJWrgGmhCm63dcpWsKjjqK1P9p/2uECgYEA7dhNmdtG2DUMUuKM +csN02kfqB2WXiSxMhDvt1UeiQ6IjUX9IwKXcvGeyj21CQxzppFnLJDQBORhKRL49 +JZRiWkOhk/kBOJ7cwyEqLZ44OqOw5jSZDq2yGSLrTm09cZ49SeRQzdfMEdfFNj/B +VQiChFNoMUPdV+eE7IQAesLN5ZECgYEAw1wxNMdXQKiIBiV/aK3HWKBxamWQWAPF +w1RTWGs5fzXgyVzEZztOD32S+3DNFy5GI5loYOZENJeE2K4I+o8WKPjCADd50qCR +tHSUeTg7VqYMDhvsIUgu/bgxnkxsN4qirIrCeNLHU+W00mqCso9fJD1BMn+mcxnB +ODjGBzq6ktcCgYBgoGy+YfBBLCQKlFFc2n1hpK+2O5a5us6HNlWkPUr10SwqEtz6 +ryejPzmyvT+bRmbBR12ZIquQh4a8sNDksIjYhPtiw1m0qA2mFJYvHEVZxWC71YiN +BxIA1kfkf8rjmtbpayFMzyvv38oWBQbFRgTIP55nzVtiOAOvlt0fAn9lkQKBgBtA +YwTPwdYdLExfMrSxeRtd1jRXUplUfWPQAYhV/MFIDMFKFYOo/CyhAb3dcF6Jb/NL +VQofVOhZMojfThVVnGu+t6E8G9xL1cdsc8GtHGnr48acEoZaAWQXK6S/WGEvD54u +BQ7BqFiFtIGdpfmukkEF4nBe6iMoNpHXKZickqYlAoGBALpCmyjQCkT1viIIXR3a +H55RR6pgCBqSguscSJFgR9PC7SPTQQI+qayv5LsMZ+vjBLgf7ykq4LS0PKIYLhtT +HAAfk0QZ6J/MD1rqT8kJ54qxHaq35hp1l4kFozx+PU3NiYrpylu20886TXj9P7qH +TKEjqmurkUcofeaRqrUmU+aK +-----END PRIVATE KEY----- +""" diff --git a/kazoo/tests/test_client.py b/kazoo/tests/test_client.py index 45ec0092..62f3d3b4 100644 --- a/kazoo/tests/test_client.py +++ b/kazoo/tests/test_client.py @@ -9,7 +9,7 @@ from mock import patch import pytest -from kazoo.testing import KazooTestCase +from kazoo.testing import KazooTestCase, SSLKazooTestCase from kazoo.exceptions import ( AuthFailedError, BadArgumentsError, @@ -1157,6 +1157,14 @@ def test_request_queuing_session_expired(self): client.stop() +class TestSSLClient(SSLKazooTestCase): + def test_create(self): + client = self.client + path = client.create("/1") + assert path == "/1" + assert client.exists("/1") + + dummy_dict = { 'aversion': 1, 'ctime': 0,