diff options
| author | Kali Kaneko <kali@leap.se> | 2015-02-23 14:45:48 -0400 | 
|---|---|---|
| committer | Kali Kaneko <kali@leap.se> | 2015-02-23 14:45:48 -0400 | 
| commit | 1f4e0c32f49337e216eaaac83a8eeca9674370a4 (patch) | |
| tree | c20f0ff4a6e177667f3a2233f2ef6eb7e2ee9233 | |
| parent | 4bad7062364c72215da9fd2256451c7a3d0a356f (diff) | |
| parent | 04be045242b0fd0c669610f54db7a9fdeb3b77f3 (diff) | |
Merge remote-tracking branch 'leapcode/pr/826' into release/0.8.x
| -rw-r--r-- | changes/non-curve-zmq | 1 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/backend.py | 43 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/backend_proxy.py | 25 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/signaler.py | 25 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/signaler_qt.py | 39 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/utils.py | 54 | ||||
| -rw-r--r-- | src/leap/bitmask/backend_app.py | 4 | ||||
| -rw-r--r-- | src/leap/bitmask/config/flags.py | 3 | 
8 files changed, 142 insertions, 52 deletions
| diff --git a/changes/non-curve-zmq b/changes/non-curve-zmq new file mode 100644 index 00000000..58737723 --- /dev/null +++ b/changes/non-curve-zmq @@ -0,0 +1 @@ +- Gracefully fall back to ZMQ ipc sockets with restricted access if CurveZMQ is not available (Feature #6646) diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py index 75eff8a9..cff731ba 100644 --- a/src/leap/bitmask/backend/backend.py +++ b/src/leap/bitmask/backend/backend.py @@ -20,6 +20,7 @@  # TODO use txzmq bindings instead.  import json +import os  import threading  import time @@ -28,10 +29,14 @@ import psutil  from twisted.internet import defer, reactor, threads  import zmq -from zmq.auth.thread import ThreadAuthenticator +try: +    from zmq.auth.thread import ThreadAuthenticator +except ImportError: +    pass  from leap.bitmask.backend.api import API, PING_REQUEST  from leap.bitmask.backend.utils import get_backend_certificates +from leap.bitmask.config import flags  from leap.bitmask.backend.signaler import Signaler  import logging @@ -43,12 +48,15 @@ class Backend(object):      Backend server.      Receives signals from backend_proxy and emit signals if needed.      """ -    # XXX this should not be hardcoded. Make it configurable. -    PORT = '5556' -      # XXX we might want to make this configurable per-platform,      # and use the most performant socket type on each one. -    BIND_ADDR = "tcp://127.0.0.1:%s" % PORT +    if flags.ZMQ_HAS_CURVE: +        # XXX this should not be hardcoded. Make it configurable. +        PORT = '5556' +        BIND_ADDR = "tcp://127.0.0.1:%s" % PORT +    else: +        SOCKET_FILE = "/tmp/bitmask.socket.0" +        BIND_ADDR = "ipc://%s" % SOCKET_FILE      PING_INTERVAL = 2  # secs @@ -73,20 +81,23 @@ class Backend(object):          context = zmq.Context()          socket = context.socket(zmq.REP) -        # Start an authenticator for this context. -        auth = ThreadAuthenticator(context) -        auth.start() -        # XXX do not hardcode this here. -        auth.allow('127.0.0.1') +        if flags.ZMQ_HAS_CURVE: +            # Start an authenticator for this context. +            auth = ThreadAuthenticator(context) +            auth.start() +            # XXX do not hardcode this here. +            auth.allow('127.0.0.1') -        # Tell authenticator to use the certificate in a directory -        auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) -        public, secret = get_backend_certificates() -        socket.curve_publickey = public -        socket.curve_secretkey = secret -        socket.curve_server = True  # must come before bind +            # Tell authenticator to use the certificate in a directory +            auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) +            public, secret = get_backend_certificates() +            socket.curve_publickey = public +            socket.curve_secretkey = secret +            socket.curve_server = True  # must come before bind          socket.bind(self.BIND_ADDR) +        if not flags.ZMQ_HAS_CURVE: +            os.chmod(self.SOCKET_FILE, 0600)          self._zmq_socket = socket diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py index 3e79289f..b2f79a70 100644 --- a/src/leap/bitmask/backend/backend_proxy.py +++ b/src/leap/bitmask/backend/backend_proxy.py @@ -30,6 +30,7 @@ import zmq  from leap.bitmask.backend.api import API, STOP_REQUEST, PING_REQUEST  from leap.bitmask.backend.utils import generate_zmq_certificates_if_needed  from leap.bitmask.backend.utils import get_backend_certificates +from leap.bitmask.config import flags  import logging  logger = logging.getLogger(__name__) @@ -41,8 +42,11 @@ class BackendProxy(object):      to the backend.      """ -    PORT = '5556' -    SERVER = "tcp://localhost:%s" % PORT +    if flags.ZMQ_HAS_CURVE: +        PORT = '5556' +        SERVER = "tcp://localhost:%s" % PORT +    else: +        SERVER = "ipc:///tmp/bitmask.socket.0"      POLL_TIMEOUT = 4000  # ms      POLL_TRIES = 3 @@ -59,15 +63,16 @@ class BackendProxy(object):          logger.debug("Connecting to server...")          socket = context.socket(zmq.REQ) -        # public, secret = zmq.curve_keypair() -        client_keys = zmq.curve_keypair() -        socket.curve_publickey = client_keys[0] -        socket.curve_secretkey = client_keys[1] +        if flags.ZMQ_HAS_CURVE: +            # public, secret = zmq.curve_keypair() +            client_keys = zmq.curve_keypair() +            socket.curve_publickey = client_keys[0] +            socket.curve_secretkey = client_keys[1] -        # The client must know the server's public key to make a CURVE -        # connection. -        public, _ = get_backend_certificates() -        socket.curve_serverkey = public +            # The client must know the server's public key to make a CURVE +            # connection. +            public, _ = get_backend_certificates() +            socket.curve_serverkey = public          socket.setsockopt(zmq.RCVTIMEO, 1000)          socket.setsockopt(zmq.LINGER, 0)  # Terminate early diff --git a/src/leap/bitmask/backend/signaler.py b/src/leap/bitmask/backend/signaler.py index 43cba994..aec2f606 100644 --- a/src/leap/bitmask/backend/signaler.py +++ b/src/leap/bitmask/backend/signaler.py @@ -26,6 +26,7 @@ import zmq  from leap.bitmask.backend.api import SIGNALS  from leap.bitmask.backend.utils import get_frontend_certificates +from leap.bitmask.config import flags  import logging  logger = logging.getLogger(__name__) @@ -36,8 +37,11 @@ class Signaler(object):      Signaler client.      Receives signals from the backend and sends to the signaling server.      """ -    PORT = "5667" -    SERVER = "tcp://localhost:%s" % PORT +    if flags.ZMQ_HAS_CURVE: +        PORT = "5667" +        SERVER = "tcp://localhost:%s" % PORT +    else: +        SERVER = "ipc:///tmp/bitmask.socket.1"      POLL_TIMEOUT = 2000  # ms      POLL_TRIES = 500 @@ -49,15 +53,16 @@ class Signaler(object):          logger.debug("Connecting to signaling server...")          socket = context.socket(zmq.REQ) -        # public, secret = zmq.curve_keypair() -        client_keys = zmq.curve_keypair() -        socket.curve_publickey = client_keys[0] -        socket.curve_secretkey = client_keys[1] +        if flags.ZMQ_HAS_CURVE: +            # public, secret = zmq.curve_keypair() +            client_keys = zmq.curve_keypair() +            socket.curve_publickey = client_keys[0] +            socket.curve_secretkey = client_keys[1] -        # The client must know the server's public key to make a CURVE -        # connection. -        public, _ = get_frontend_certificates() -        socket.curve_serverkey = public +            # The client must know the server's public key to make a CURVE +            # connection. +            public, _ = get_frontend_certificates() +            socket.curve_serverkey = public          socket.setsockopt(zmq.RCVTIMEO, 1000)          socket.setsockopt(zmq.LINGER, 0)  # Terminate early diff --git a/src/leap/bitmask/backend/signaler_qt.py b/src/leap/bitmask/backend/signaler_qt.py index 433f18ed..b7f48d21 100644 --- a/src/leap/bitmask/backend/signaler_qt.py +++ b/src/leap/bitmask/backend/signaler_qt.py @@ -18,16 +18,21 @@  Signaling server.  Receives signals from the signaling client and emit Qt signals for the GUI.  """ +import os  import threading  import time  from PySide import QtCore  import zmq -from zmq.auth.thread import ThreadAuthenticator +try: +    from zmq.auth.thread import ThreadAuthenticator +except ImportError: +    pass  from leap.bitmask.backend.api import SIGNALS  from leap.bitmask.backend.utils import get_frontend_certificates +from leap.bitmask.config import flags  import logging  logger = logging.getLogger(__name__) @@ -38,8 +43,12 @@ class SignalerQt(QtCore.QObject):      Signaling server.      Receives signals from the signaling client and emit Qt signals for the GUI.      """ -    PORT = "5667" -    BIND_ADDR = "tcp://127.0.0.1:%s" % PORT +    if flags.ZMQ_HAS_CURVE: +        PORT = "5667" +        BIND_ADDR = "tcp://127.0.0.1:%s" % PORT +    else: +        SOCKET_FILE = "/tmp/bitmask.socket.1" +        BIND_ADDR = "ipc://%s" % SOCKET_FILE      def __init__(self):          QtCore.QObject.__init__(self) @@ -67,20 +76,24 @@ class SignalerQt(QtCore.QObject):          context = zmq.Context()          socket = context.socket(zmq.REP) -        # Start an authenticator for this context. -        auth = ThreadAuthenticator(context) -        auth.start() -        auth.allow('127.0.0.1') +        if flags.ZMQ_HAS_CURVE: +            # Start an authenticator for this context. +            auth = ThreadAuthenticator(context) +            auth.start() +            auth.allow('127.0.0.1') -        # Tell authenticator to use the certificate in a directory -        auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) -        public, secret = get_frontend_certificates() -        socket.curve_publickey = public -        socket.curve_secretkey = secret -        socket.curve_server = True  # must come before bind +            # Tell authenticator to use the certificate in a directory +            auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) +            public, secret = get_frontend_certificates() +            socket.curve_publickey = public +            socket.curve_secretkey = secret +            socket.curve_server = True  # must come before bind          socket.bind(self.BIND_ADDR) +        if not flags.ZMQ_HAS_CURVE: +            os.chmod(self.SOCKET_FILE, 0600) +          while self._do_work.is_set():              # Wait for next request from client              try: diff --git a/src/leap/bitmask/backend/utils.py b/src/leap/bitmask/backend/utils.py index 18e70743..b2674330 100644 --- a/src/leap/bitmask/backend/utils.py +++ b/src/leap/bitmask/backend/utils.py @@ -22,20 +22,63 @@ import os  import shutil  import stat -import zmq.auth +import zmq +try: +    import zmq.auth +except ImportError: +    pass + +from leap.bitmask.config import flags  from leap.bitmask.util import get_path_prefix  from leap.common.files import mkdir_p +from leap.common.check import leap_assert  logger = logging.getLogger(__name__)  KEYS_DIR = os.path.join(get_path_prefix(), 'leap', 'zmq_certificates') +def _zmq_has_curve(): +    """ +    Return whether the current ZMQ has support for auth and CurveZMQ security. + +    :rtype: bool + +     Version notes: +       `zmq.curve_keypair()` is new in version 14.0, new in version libzmq-4.0. +            Requires libzmq (>= 4.0) to have been linked with libsodium. +       `zmq.auth` module is new in version 14.1 +       `zmq.has()` is new in version 14.1, new in version libzmq-4.1. +    """ +    zmq_version = zmq.zmq_version_info() +    pyzmq_version = zmq.pyzmq_version_info() + +    if pyzmq_version >= (14, 1, 0) and zmq_version >= (4, 1): +        return zmq.has('curve') + +    if pyzmq_version < (14, 1, 0): +        return False + +    if zmq_version < (4, 0): +        # security is new in libzmq 4.0 +        return False + +    try: +        zmq.curve_keypair() +    except zmq.error.ZMQError: +        # security requires libzmq to be linked against libsodium +        return False + +    return True + +  def generate_zmq_certificates():      """      Generate client and server CURVE certificate files.      """ +    leap_assert(flags.ZMQ_HAS_CURVE, "CurveZMQ not supported!") +      # Create directory for certificates, remove old content if necessary      if os.path.exists(KEYS_DIR):          shutil.rmtree(KEYS_DIR) @@ -53,6 +96,8 @@ def get_frontend_certificates():      """      Return the frontend's public and secret certificates.      """ +    leap_assert(flags.ZMQ_HAS_CURVE, "CurveZMQ not supported!") +      frontend_secret_file = os.path.join(KEYS_DIR, "frontend.key_secret")      public, secret = zmq.auth.load_certificate(frontend_secret_file)      return public, secret @@ -62,6 +107,8 @@ def get_backend_certificates(base_dir='.'):      """      Return the backend's public and secret certificates.      """ +    leap_assert(flags.ZMQ_HAS_CURVE, "CurveZMQ not supported!") +      backend_secret_file = os.path.join(KEYS_DIR, "backend.key_secret")      public, secret = zmq.auth.load_certificate(backend_secret_file)      return public, secret @@ -84,5 +131,8 @@ def generate_zmq_certificates_if_needed():      Generate the needed ZMQ certificates for backend/frontend communication if      needed.      """ -    if not _certificates_exist(): +    if flags.ZMQ_HAS_CURVE and not _certificates_exist():          generate_zmq_certificates() + + +flags.ZMQ_HAS_CURVE = _zmq_has_curve() diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index 286b04f7..3e88a95a 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -23,6 +23,7 @@ import signal  from leap.bitmask.backend.leapbackend import LeapBackend  from leap.bitmask.backend.utils import generate_zmq_certificates +from leap.bitmask.config import flags  from leap.bitmask.logs.utils import create_logger  from leap.bitmask.util import dict_to_flags @@ -57,7 +58,8 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None):      """      # The backend is the one who always creates the certificates. Either if it      # is run separately or in a process in the same app as the frontend. -    generate_zmq_certificates() +    if flags.ZMQ_HAS_CURVE: +        generate_zmq_certificates()      # ignore SIGINT since app.py takes care of signaling SIGTERM to us.      signal.signal(signal.SIGINT, signal.SIG_IGN) diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index 6b70659d..cdde1971 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -55,3 +55,6 @@ OPENVPN_VERBOSITY = 1  # Skip the checks in the wizard, use for testing purposes only!  SKIP_WIZARD_CHECKS = False + +# This flag tells us whether the current pyzmq supports using CurveZMQ or not. +ZMQ_HAS_CURVE = None | 
