From 7d5c3dcd969161322deed6c43f8a6a3cb92c3369 Mon Sep 17 00:00:00 2001 From: Micah Anderson Date: Tue, 11 Nov 2014 11:53:55 -0500 Subject: upgrade to 14.4.1 --- examples/security/generate_certificates.py | 49 ++++++++++++ examples/security/grasslands.py | 29 ++++++++ examples/security/ioloop-ironhouse.py | 116 +++++++++++++++++++++++++++++ examples/security/ironhouse.py | 95 +++++++++++++++++++++++ examples/security/stonehouse.py | 95 +++++++++++++++++++++++ examples/security/strawhouse.py | 94 +++++++++++++++++++++++ examples/security/woodhouse.py | 90 ++++++++++++++++++++++ 7 files changed, 568 insertions(+) create mode 100644 examples/security/generate_certificates.py create mode 100644 examples/security/grasslands.py create mode 100644 examples/security/ioloop-ironhouse.py create mode 100644 examples/security/ironhouse.py create mode 100644 examples/security/stonehouse.py create mode 100644 examples/security/strawhouse.py create mode 100644 examples/security/woodhouse.py (limited to 'examples/security') diff --git a/examples/security/generate_certificates.py b/examples/security/generate_certificates.py new file mode 100644 index 0000000..80db258 --- /dev/null +++ b/examples/security/generate_certificates.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +""" +Generate client and server CURVE certificate files then move them into the +appropriate store directory, private_keys or public_keys. The certificates +generated by this script are used by the stonehouse and ironhouse examples. + +In practice this would be done by hand or some out-of-band process. + +Author: Chris Laws +""" + +import os +import shutil +import zmq.auth + +def generate_certificates(base_dir): + ''' Generate client and server CURVE certificate files''' + keys_dir = os.path.join(base_dir, 'certificates') + public_keys_dir = os.path.join(base_dir, 'public_keys') + secret_keys_dir = os.path.join(base_dir, 'private_keys') + + # Create directories for certificates, remove old content if necessary + for d in [keys_dir, public_keys_dir, secret_keys_dir]: + if os.path.exists(d): + shutil.rmtree(d) + os.mkdir(d) + + # create new keys in certificates dir + server_public_file, server_secret_file = zmq.auth.create_certificates(keys_dir, "server") + client_public_file, client_secret_file = zmq.auth.create_certificates(keys_dir, "client") + + # move public keys to appropriate directory + for key_file in os.listdir(keys_dir): + if key_file.endswith(".key"): + shutil.move(os.path.join(keys_dir, key_file), + os.path.join(public_keys_dir, '.')) + + # move secret keys to appropriate directory + for key_file in os.listdir(keys_dir): + if key_file.endswith(".key_secret"): + shutil.move(os.path.join(keys_dir, key_file), + os.path.join(secret_keys_dir, '.')) + +if __name__ == '__main__': + if zmq.zmq_version_info() < (4,0): + raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version())) + + generate_certificates(os.path.dirname(__file__)) diff --git a/examples/security/grasslands.py b/examples/security/grasslands.py new file mode 100644 index 0000000..cbd3ab9 --- /dev/null +++ b/examples/security/grasslands.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +''' +No protection at all. + +All connections are accepted, there is no authentication, and no privacy. + +This is how ZeroMQ always worked until we built security into the wire +protocol in early 2013. Internally, it uses a security mechanism called +"NULL". + +Author: Chris Laws +''' + +import zmq + + +ctx = zmq.Context().instance() + +server = ctx.socket(zmq.PUSH) +server.bind('tcp://*:9000') + +client = ctx.socket(zmq.PULL) +client.connect('tcp://127.0.0.1:9000') + +server.send(b"Hello") +msg = client.recv() +if msg == b"Hello": + print("Grasslands test OK") diff --git a/examples/security/ioloop-ironhouse.py b/examples/security/ioloop-ironhouse.py new file mode 100644 index 0000000..fbde306 --- /dev/null +++ b/examples/security/ioloop-ironhouse.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +''' +Ironhouse extends Stonehouse with client public key authentication. + +This is the strongest security model we have today, protecting against every +attack we know about, except end-point attacks (where an attacker plants +spyware on a machine to capture data before it's encrypted, or after it's +decrypted). + +This example demonstrates using the IOLoopAuthenticator. + +Author: Chris Laws +''' + +import logging +import os +import sys + +import zmq +import zmq.auth +from zmq.auth.ioloop import IOLoopAuthenticator +from zmq.eventloop import ioloop, zmqstream + +def echo(server, msg): + logging.debug("server recvd %s", msg) + reply = msg + [b'World'] + logging.debug("server sending %s", reply) + server.send_multipart(reply) + +def setup_server(server_secret_file, endpoint='tcp://127.0.0.1:9000'): + """setup a simple echo server with CURVE auth""" + server = zmq.Context.instance().socket(zmq.ROUTER) + + server_public, server_secret = zmq.auth.load_certificate(server_secret_file) + server.curve_secretkey = server_secret + server.curve_publickey = server_public + server.curve_server = True # must come before bind + server.bind(endpoint) + + server_stream = zmqstream.ZMQStream(server) + # simple echo + server_stream.on_recv_stream(echo) + return server_stream + +def client_msg_recvd(msg): + logging.debug("client recvd %s", msg) + logging.info("Ironhouse test OK") + # stop the loop when we get the reply + ioloop.IOLoop.instance().stop() + +def setup_client(client_secret_file, server_public_file, endpoint='tcp://127.0.0.1:9000'): + """setup a simple client with CURVE auth""" + + client = zmq.Context.instance().socket(zmq.DEALER) + + # We need two certificates, one for the client and one for + # the server. The client must know the server's public key + # to make a CURVE connection. + client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + client.curve_secretkey = client_secret + client.curve_publickey = client_public + + server_public, _ = zmq.auth.load_certificate(server_public_file) + # The client must know the server's public key to make a CURVE connection. + client.curve_serverkey = server_public + client.connect(endpoint) + + client_stream = zmqstream.ZMQStream(client) + client_stream.on_recv(client_msg_recvd) + return client_stream + + +def run(): + '''Run Ironhouse example''' + + # These direcotries are generated by the generate_certificates script + base_dir = os.path.dirname(__file__) + keys_dir = os.path.join(base_dir, 'certificates') + public_keys_dir = os.path.join(base_dir, 'public_keys') + secret_keys_dir = os.path.join(base_dir, 'private_keys') + + if not (os.path.exists(keys_dir) and + os.path.exists(public_keys_dir) and + os.path.exists(secret_keys_dir)): + logging.critical("Certificates are missing - run generate_certificates script first") + sys.exit(1) + + # Start an authenticator for this context. + auth = IOLoopAuthenticator() + auth.allow('127.0.0.1') + # Tell authenticator to use the certificate in a directory + auth.configure_curve(domain='*', location=public_keys_dir) + + server_secret_file = os.path.join(secret_keys_dir, "server.key_secret") + server = setup_server(server_secret_file) + server_public_file = os.path.join(public_keys_dir, "server.key") + client_secret_file = os.path.join(secret_keys_dir, "client.key_secret") + client = setup_client(client_secret_file, server_public_file) + client.send(b'Hello') + + auth.start() + ioloop.IOLoop.instance().start() + +if __name__ == '__main__': + if zmq.zmq_version_info() < (4,0): + raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version())) + + if '-v' in sys.argv: + level = logging.DEBUG + else: + level = logging.INFO + + logging.basicConfig(level=level, format="[%(levelname)s] %(message)s") + + run() diff --git a/examples/security/ironhouse.py b/examples/security/ironhouse.py new file mode 100644 index 0000000..dc56e41 --- /dev/null +++ b/examples/security/ironhouse.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +''' +Ironhouse extends Stonehouse with client public key authentication. + +This is the strongest security model we have today, protecting against every +attack we know about, except end-point attacks (where an attacker plants +spyware on a machine to capture data before it's encrypted, or after it's +decrypted). + +Author: Chris Laws +''' + +import logging +import os +import sys + +import zmq +import zmq.auth +from zmq.auth.thread import ThreadAuthenticator + + +def run(): + ''' Run Ironhouse example ''' + + # These direcotries are generated by the generate_certificates script + base_dir = os.path.dirname(__file__) + keys_dir = os.path.join(base_dir, 'certificates') + public_keys_dir = os.path.join(base_dir, 'public_keys') + secret_keys_dir = os.path.join(base_dir, 'private_keys') + + if not (os.path.exists(keys_dir) and + os.path.exists(public_keys_dir) and + os.path.exists(secret_keys_dir)): + logging.critical("Certificates are missing - run generate_certificates.py script first") + sys.exit(1) + + ctx = zmq.Context().instance() + + # Start an authenticator for this context. + auth = ThreadAuthenticator(ctx) + auth.start() + auth.allow('127.0.0.1') + # Tell authenticator to use the certificate in a directory + auth.configure_curve(domain='*', location=public_keys_dir) + + server = ctx.socket(zmq.PUSH) + + server_secret_file = os.path.join(secret_keys_dir, "server.key_secret") + server_public, server_secret = zmq.auth.load_certificate(server_secret_file) + server.curve_secretkey = server_secret + server.curve_publickey = server_public + server.curve_server = True # must come before bind + server.bind('tcp://*:9000') + + client = ctx.socket(zmq.PULL) + + # We need two certificates, one for the client and one for + # the server. The client must know the server's public key + # to make a CURVE connection. + client_secret_file = os.path.join(secret_keys_dir, "client.key_secret") + client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + client.curve_secretkey = client_secret + client.curve_publickey = client_public + + server_public_file = os.path.join(public_keys_dir, "server.key") + server_public, _ = zmq.auth.load_certificate(server_public_file) + # The client must know the server's public key to make a CURVE connection. + client.curve_serverkey = server_public + client.connect('tcp://127.0.0.1:9000') + + server.send(b"Hello") + + if client.poll(1000): + msg = client.recv() + if msg == b"Hello": + logging.info("Ironhouse test OK") + else: + logging.error("Ironhouse test FAIL") + + # stop auth thread + auth.stop() + +if __name__ == '__main__': + if zmq.zmq_version_info() < (4,0): + raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version())) + + if '-v' in sys.argv: + level = logging.DEBUG + else: + level = logging.INFO + + logging.basicConfig(level=level, format="[%(levelname)s] %(message)s") + + run() diff --git a/examples/security/stonehouse.py b/examples/security/stonehouse.py new file mode 100644 index 0000000..679dea8 --- /dev/null +++ b/examples/security/stonehouse.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +''' +Stonehouse uses the "CURVE" security mechanism. + +This gives us strong encryption on data, and (as far as we know) unbreakable +authentication. Stonehouse is the minimum you would use over public networks, +and assures clients that they are speaking to an authentic server, while +allowing any client to connect. + +Author: Chris Laws +''' + +import logging +import os +import sys +import time + +import zmq +import zmq.auth +from zmq.auth.thread import ThreadAuthenticator + + +def run(): + ''' Run Stonehouse example ''' + + # These directories are generated by the generate_certificates script + base_dir = os.path.dirname(__file__) + keys_dir = os.path.join(base_dir, 'certificates') + public_keys_dir = os.path.join(base_dir, 'public_keys') + secret_keys_dir = os.path.join(base_dir, 'private_keys') + + if not (os.path.exists(keys_dir) and + os.path.exists(public_keys_dir) and + os.path.exists(secret_keys_dir)): + logging.critical("Certificates are missing: run generate_certificates.py script first") + sys.exit(1) + + ctx = zmq.Context().instance() + + # Start an authenticator for this context. + auth = ThreadAuthenticator(ctx) + auth.start() + auth.allow('127.0.0.1') + # Tell the authenticator how to handle CURVE requests + auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) + + server = ctx.socket(zmq.PUSH) + server_secret_file = os.path.join(secret_keys_dir, "server.key_secret") + server_public, server_secret = zmq.auth.load_certificate(server_secret_file) + server.curve_secretkey = server_secret + server.curve_publickey = server_public + server.curve_server = True # must come before bind + server.bind('tcp://*:9000') + + client = ctx.socket(zmq.PULL) + # We need two certificates, one for the client and one for + # the server. The client must know the server's public key + # to make a CURVE connection. + client_secret_file = os.path.join(secret_keys_dir, "client.key_secret") + client_public, client_secret = zmq.auth.load_certificate(client_secret_file) + client.curve_secretkey = client_secret + client.curve_publickey = client_public + + # The client must know the server's public key to make a CURVE connection. + server_public_file = os.path.join(public_keys_dir, "server.key") + server_public, _ = zmq.auth.load_certificate(server_public_file) + client.curve_serverkey = server_public + + client.connect('tcp://127.0.0.1:9000') + + server.send(b"Hello") + + if client.poll(1000): + msg = client.recv() + if msg == b"Hello": + logging.info("Stonehouse test OK") + else: + logging.error("Stonehouse test FAIL") + + # stop auth thread + auth.stop() + +if __name__ == '__main__': + if zmq.zmq_version_info() < (4,0): + raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version())) + + if '-v' in sys.argv: + level = logging.DEBUG + else: + level = logging.INFO + + logging.basicConfig(level=level, format="[%(levelname)s] %(message)s") + + run() diff --git a/examples/security/strawhouse.py b/examples/security/strawhouse.py new file mode 100644 index 0000000..dc75bd7 --- /dev/null +++ b/examples/security/strawhouse.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +''' +Allow or deny clients based on IP address. + +Strawhouse, which is plain text with filtering on IP addresses. It still +uses the NULL mechanism, but we install an authentication hook that checks +the IP address against a whitelist or blacklist and allows or denies it +accordingly. + +Author: Chris Laws +''' + +import logging +import sys + +import zmq +import zmq.auth +from zmq.auth.thread import ThreadAuthenticator + + +def run(): + '''Run strawhouse client''' + + allow_test_pass = False + deny_test_pass = False + + ctx = zmq.Context().instance() + + # Start an authenticator for this context. + auth = ThreadAuthenticator(ctx) + auth.start() + + # Part 1 - demonstrate allowing clients based on IP address + auth.allow('127.0.0.1') + + server = ctx.socket(zmq.PUSH) + server.zap_domain = b'global' # must come before bind + server.bind('tcp://*:9000') + + client_allow = ctx.socket(zmq.PULL) + client_allow.connect('tcp://127.0.0.1:9000') + + server.send(b"Hello") + + msg = client_allow.recv() + if msg == b"Hello": + allow_test_pass = True + + client_allow.close() + + # Part 2 - demonstrate denying clients based on IP address + auth.stop() + + auth = ThreadAuthenticator(ctx) + auth.start() + + auth.deny('127.0.0.1') + + client_deny = ctx.socket(zmq.PULL) + client_deny.connect('tcp://127.0.0.1:9000') + + if server.poll(50, zmq.POLLOUT): + server.send(b"Hello") + + if client_deny.poll(50): + msg = client_deny.recv() + else: + deny_test_pass = True + else: + deny_test_pass = True + + client_deny.close() + + auth.stop() # stop auth thread + + if allow_test_pass and deny_test_pass: + logging.info("Strawhouse test OK") + else: + logging.error("Strawhouse test FAIL") + + +if __name__ == '__main__': + if zmq.zmq_version_info() < (4,0): + raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version())) + + if '-v' in sys.argv: + level = logging.DEBUG + else: + level = logging.INFO + + logging.basicConfig(level=level, format="[%(levelname)s] %(message)s") + + run() diff --git a/examples/security/woodhouse.py b/examples/security/woodhouse.py new file mode 100644 index 0000000..efedee4 --- /dev/null +++ b/examples/security/woodhouse.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +''' +Woodhouse extends Strawhouse with a name and password check. + +This uses the PLAIN mechanism which does plain-text username and password authentication). +It's not really secure, and anyone sniffing the network (trivial with WiFi) +can capture passwords and then login. + +Author: Chris Laws +''' + +import logging +import sys + +import zmq +import zmq.auth +from zmq.auth.thread import ThreadAuthenticator + +def run(): + '''Run woodhouse example''' + + valid_client_test_pass = False + invalid_client_test_pass = False + + ctx = zmq.Context().instance() + + # Start an authenticator for this context. + auth = ThreadAuthenticator(ctx) + auth.start() + auth.allow('127.0.0.1') + # Instruct authenticator to handle PLAIN requests + auth.configure_plain(domain='*', passwords={'admin': 'secret'}) + + server = ctx.socket(zmq.PUSH) + server.plain_server = True # must come before bind + server.bind('tcp://*:9000') + + client = ctx.socket(zmq.PULL) + client.plain_username = b'admin' + client.plain_password = b'secret' + client.connect('tcp://127.0.0.1:9000') + + server.send(b"Hello") + + if client.poll(): + msg = client.recv() + if msg == b"Hello": + valid_client_test_pass = True + + client.close() + + + # now use invalid credentials - expect no msg received + client2 = ctx.socket(zmq.PULL) + client2.plain_username = b'admin' + client2.plain_password = b'bogus' + client2.connect('tcp://127.0.0.1:9000') + + server.send(b"World") + + if client2.poll(50): + msg = client.recv() + if msg == "World": + invalid_client_test_pass = False + else: + # no message is expected + invalid_client_test_pass = True + + # stop auth thread + auth.stop() + + if valid_client_test_pass and invalid_client_test_pass: + logging.info("Woodhouse test OK") + else: + logging.error("Woodhouse test FAIL") + + +if __name__ == '__main__': + if zmq.zmq_version_info() < (4,0): + raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version())) + + if '-v' in sys.argv: + level = logging.DEBUG + else: + level = logging.INFO + + logging.basicConfig(level=level, format="[%(levelname)s] %(message)s") + + run() -- cgit v1.2.3