diff options
author | Tomás Touceda <chiiph@leap.se> | 2013-06-28 14:25:19 -0300 |
---|---|---|
committer | Tomás Touceda <chiiph@leap.se> | 2013-06-28 14:25:19 -0300 |
commit | 5b975799ce9b7a6e0a88be4bcb48bdfb90800bb3 (patch) | |
tree | bda674a79b1aeccb37b67609517bc4761db7ae07 /src/leap/eip | |
parent | 9cea9c8a34343f8792d65b96f93ae22bd8685878 (diff) | |
parent | c088a1544a5f7a51359d2802019c0740aab0cc5b (diff) |
Merge branch 'release-0.2.2'0.2.2
Diffstat (limited to 'src/leap/eip')
-rw-r--r-- | src/leap/eip/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/eip/checks.py | 542 | ||||
-rw-r--r-- | src/leap/eip/config.py | 398 | ||||
-rw-r--r-- | src/leap/eip/constants.py | 3 | ||||
-rw-r--r-- | src/leap/eip/eipconnection.py | 405 | ||||
-rw-r--r-- | src/leap/eip/exceptions.py | 175 | ||||
-rw-r--r-- | src/leap/eip/openvpnconnection.py | 410 | ||||
-rw-r--r-- | src/leap/eip/specs.py | 136 | ||||
-rw-r--r-- | src/leap/eip/tests/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/eip/tests/data.py | 51 | ||||
-rw-r--r-- | src/leap/eip/tests/test_checks.py | 372 | ||||
-rw-r--r-- | src/leap/eip/tests/test_config.py | 298 | ||||
-rw-r--r-- | src/leap/eip/tests/test_eipconnection.py | 216 | ||||
-rw-r--r-- | src/leap/eip/tests/test_openvpnconnection.py | 161 | ||||
-rw-r--r-- | src/leap/eip/udstelnet.py | 38 |
15 files changed, 0 insertions, 3205 deletions
diff --git a/src/leap/eip/__init__.py b/src/leap/eip/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/src/leap/eip/__init__.py +++ /dev/null diff --git a/src/leap/eip/checks.py b/src/leap/eip/checks.py deleted file mode 100644 index af824c57..00000000 --- a/src/leap/eip/checks.py +++ /dev/null @@ -1,542 +0,0 @@ -import logging -import time -import os -import sys - -import requests - -from leap import __branding as BRANDING -from leap import certs as leapcerts -from leap.base.auth import srpauth_protected, magick_srpauth -from leap.base import config as baseconfig -from leap.base import constants as baseconstants -from leap.base import providers -from leap.crypto import certs -from leap.eip import config as eipconfig -from leap.eip import constants as eipconstants -from leap.eip import exceptions as eipexceptions -from leap.eip import specs as eipspecs -from leap.util.certs import get_mac_cabundle -from leap.util.fileutil import mkdir_p -from leap.util.web import get_https_domain_and_port - -logger = logging.getLogger(name=__name__) - -""" -ProviderCertChecker -------------------- -Checks on certificates. To be moved to base. -docs TBD - -EIPConfigChecker ----------- -It is used from the eip conductor (a instance of EIPConnection that is -managed from the QtApp), running `run_all` method before trying to call -`connect` or any other of the state-changing methods. - -It checks that the needed files are provided or can be discovered over the -net. Much of these tests are not specific to EIP module, and can be splitted -into base.tests to be invoked by the base leap init routines. -However, I'm testing them alltogether for the sake of having the whole unit -reachable and testable as a whole. - -""" - - -def get_branding_ca_cert(domain): - # deprecated - ca_file = BRANDING.get('provider_ca_file') - if ca_file: - return leapcerts.where(ca_file) - - -class ProviderCertChecker(object): - """ - Several checks needed for getting - client certs and checking tls connection - with provider. - """ - def __init__(self, fetcher=requests, - domain=None): - - self.fetcher = fetcher - self.domain = domain - #XXX needs some kind of autoinit - #right now we set by hand - #by loading and reading provider config - self.apidomain = None - self.cacert = eipspecs.provider_ca_path(domain) - - def run_all( - self, checker=None, - skip_download=False, skip_verify=False): - - if not checker: - checker = self - - do_verify = not skip_verify - logger.debug('do_verify: %s', do_verify) - # checker.download_ca_cert() - - # For MVS+ - # checker.download_ca_signature() - # checker.get_ca_signatures() - # checker.is_there_trust_path() - - # For MVS - checker.is_there_provider_ca() - - checker.is_https_working(verify=do_verify, autocacert=False) - checker.check_new_cert_needed(verify=do_verify) - - def download_ca_cert(self, uri=None, verify=True): - req = self.fetcher.get(uri, verify=verify) - req.raise_for_status() - - # should check domain exists - capath = self._get_ca_cert_path(self.domain) - with open(capath, 'w') as f: - f.write(req.content) - - def check_ca_cert_fingerprint( - self, hash_type="SHA256", - fingerprint=None): - """ - compares the fingerprint in - the ca cert with a string - we are passed - returns True if they are equal, False if not. - @param hash_type: digest function - @type hash_type: str - @param fingerprint: the fingerprint to compare with. - @type fingerprint: str (with : separator) - @rtype bool - """ - ca_cert_path = self.ca_cert_path - ca_cert_fpr = certs.get_cert_fingerprint( - filepath=ca_cert_path) - return ca_cert_fpr == fingerprint - - def verify_api_https(self, uri): - assert uri.startswith('https://') - cacert = self.ca_cert_path - verify = cacert or True - - # DEBUG - logger.debug('uri -> %s' % uri) - logger.debug('cacertpath -> %s' % cacert) - - req = self.fetcher.get(uri, verify=verify) - req.raise_for_status() - return True - - def download_ca_signature(self): - # MVS+ - raise NotImplementedError - - def get_ca_signatures(self): - # MVS+ - raise NotImplementedError - - def is_there_trust_path(self): - # MVS+ - raise NotImplementedError - - def is_there_provider_ca(self): - if not self.cacert: - return False - cacert_exists = os.path.isfile(self.cacert) - if cacert_exists: - logger.debug('True') - return True - logger.debug('False!') - return False - - def is_https_working( - self, uri=None, verify=True, - autocacert=False): - if uri is None: - uri = self._get_root_uri() - # XXX raise InsecureURI or something better - try: - assert uri.startswith('https') - except AssertionError: - raise AssertionError( - "uri passed should start with https") - if autocacert and verify is True and self.cacert is not None: - logger.debug('verify cert: %s', self.cacert) - verify = self.cacert - if sys.platform == "darwin": - verify = get_mac_cabundle() - logger.debug('checking https connection') - logger.debug('uri: %s (verify:%s)', uri, verify) - - try: - self.fetcher.get(uri, verify=verify) - - except requests.exceptions.SSLError as exc: - raise eipexceptions.HttpsBadCertError - - except requests.exceptions.ConnectionError: - logger.error('ConnectionError') - raise eipexceptions.HttpsNotSupported - - else: - return True - - def check_new_cert_needed(self, skip_download=False, verify=True): - # XXX add autocacert - if not self.is_cert_valid(do_raise=False): - logger.debug('cert needed: true') - self.download_new_client_cert( - skip_download=skip_download, - verify=verify) - return True - logger.debug('cert needed: false') - return False - - def download_new_client_cert(self, uri=None, verify=True, - skip_download=False, - credentials=None): - logger.debug('download new client cert') - if skip_download: - return True - if uri is None: - uri = self._get_client_cert_uri() - # XXX raise InsecureURI or something better - #assert uri.startswith('https') - - if verify is True and self.cacert is not None: - verify = self.cacert - logger.debug('verify = %s', verify) - - fgetfn = self.fetcher.get - - if credentials: - user, passwd = credentials - logger.debug('apidomain = %s', self.apidomain) - - @srpauth_protected(user, passwd, - server="https://%s" % self.apidomain, - verify=verify) - def getfn(*args, **kwargs): - return fgetfn(*args, **kwargs) - - else: - # XXX FIXME fix decorated args - @magick_srpauth(verify) - def getfn(*args, **kwargs): - return fgetfn(*args, **kwargs) - try: - - req = getfn(uri, verify=verify) - req.raise_for_status() - - except requests.exceptions.SSLError: - logger.warning('SSLError while fetching cert. ' - 'Look below for stack trace.') - # XXX raise better exception - return self.fail("SSLError") - except Exception as exc: - return self.fail(exc.message) - - try: - logger.debug('validating cert...') - pemfile_content = req.content - valid = self.is_valid_pemfile(pemfile_content) - if not valid: - logger.warning('invalid cert') - return False - cert_path = self._get_client_cert_path() - self.write_cert(pemfile_content, to=cert_path) - except: - logger.warning('Error while validating cert') - raise - return True - - def is_cert_valid(self, cert_path=None, do_raise=True): - exists = lambda: self.is_certificate_exists() - valid_pemfile = lambda: self.is_valid_pemfile() - not_expired = lambda: self.is_cert_not_expired() - - valid = exists() and valid_pemfile() and not_expired() - if not valid: - if do_raise: - raise Exception('missing valid cert') - else: - return False - return True - - def is_certificate_exists(self, certfile=None): - if certfile is None: - certfile = self._get_client_cert_path() - return os.path.isfile(certfile) - - def is_cert_not_expired(self, certfile=None, now=time.gmtime): - if certfile is None: - certfile = self._get_client_cert_path() - from_, to_ = certs.get_time_boundaries(certfile) - - return from_ < now() < to_ - - def is_valid_pemfile(self, cert_s=None): - """ - checks that the passed string - is a valid pem certificate - @param cert_s: string containing pem content - @type cert_s: string - @rtype: bool - """ - if cert_s is None: - certfile = self._get_client_cert_path() - with open(certfile) as cf: - cert_s = cf.read() - try: - valid = certs.can_load_cert_and_pkey(cert_s) - except certs.BadCertError: - logger.warning("Not valid pemfile") - valid = False - return valid - - @property - def ca_cert_path(self): - return self._get_ca_cert_path(self.domain) - - def _get_root_uri(self): - return u"https://%s/" % self.domain - - def _get_client_cert_uri(self): - return "https://%s/1/cert" % self.apidomain - - def _get_client_cert_path(self): - return eipspecs.client_cert_path(domain=self.domain) - - def _get_ca_cert_path(self, domain): - # XXX this folder path will be broken for win - # and this should be moved to eipspecs.ca_path - - # XXX use baseconfig.get_provider_path(folder=Foo) - # !!! - - capath = baseconfig.get_config_file( - 'cacert.pem', - folder='providers/%s/keys/ca' % domain) - folder, fname = os.path.split(capath) - if not os.path.isdir(folder): - mkdir_p(folder) - return capath - - def write_cert(self, pemfile_content, to=None): - folder, filename = os.path.split(to) - if not os.path.isdir(folder): - mkdir_p(folder) - with open(to, 'w') as cert_f: - cert_f.write(pemfile_content) - - def set_api_domain(self, domain): - self.apidomain = domain - - -class EIPConfigChecker(object): - """ - Several checks needed - to ensure a EIPConnection - can be sucessfully established. - use run_all to run all checks. - """ - - def __init__(self, fetcher=requests, domain=None): - # we do not want to accept too many - # argument on init. - # we want tests - # to be explicitely run. - - self.fetcher = fetcher - - # if not domain, get from config - self.domain = domain - self.apidomain = None - self.cacert = eipspecs.provider_ca_path(domain) - - self.defaultprovider = providers.LeapProviderDefinition(domain=domain) - self.defaultprovider.load() - self.eipconfig = eipconfig.EIPConfig(domain=domain) - self.set_api_domain() - self.eipserviceconfig = eipconfig.EIPServiceConfig(domain=domain) - self.eipserviceconfig.load() - - def run_all(self, checker=None, skip_download=False): - """ - runs all checks in a row. - will raise if some error encountered. - catching those exceptions is not - our responsibility at this moment - """ - if not checker: - checker = self - - # let's call all tests - # needed for a sane eip session. - - # TODO: get rid of check_default. - # check_complete should - # be enough. but here to make early tests easier. - checker.check_default_eipconfig() - - checker.check_is_there_default_provider() - checker.fetch_definition(skip_download=skip_download) - checker.fetch_eip_service_config(skip_download=skip_download) - checker.check_complete_eip_config() - #checker.ping_gateway() - - # public checks - - def check_default_eipconfig(self): - """ - checks if default eipconfig exists, - and dumps a default file if not - """ - # XXX ONLY a transient check - # because some old function still checks - # for eip config at the beginning. - - # it *really* does not make sense to - # dump it right now, we can get an in-memory - # config object and dump it to disk in a - # later moment - logger.debug('checking default eip config') - if not self._is_there_default_eipconfig(): - self._dump_default_eipconfig() - - def check_is_there_default_provider(self, config=None): - """ - raises EIPMissingDefaultProvider if no - default provider found on eip config. - This is catched by ui and runs FirstRunWizard (MVS+) - """ - if config is None: - config = self.eipconfig.config - logger.debug('checking default provider') - provider = config.get('provider', None) - if provider is None: - raise eipexceptions.EIPMissingDefaultProvider - # XXX raise also if malformed ProviderDefinition? - return True - - def fetch_definition(self, skip_download=False, - force_download=False, - config=None, uri=None, - domain=None): - """ - fetches a definition file from server - """ - # TODO: - # - Implement diff - # - overwrite only if different. - # (attend to serial field different, for instance) - - logger.debug('fetching definition') - - if skip_download: - logger.debug('(fetching def skipped)') - return True - if config is None: - config = self.defaultprovider.config - if uri is None: - if not domain: - domain = config.get('provider', None) - uri = self._get_provider_definition_uri(domain=domain) - - if sys.platform == "darwin": - verify = get_mac_cabundle() - else: - verify = True - - self.defaultprovider.load( - from_uri=uri, - fetcher=self.fetcher, - verify=verify) - self.defaultprovider.save() - - def fetch_eip_service_config(self, skip_download=False, - force_download=False, - config=None, uri=None, # domain=None, - autocacert=True, verify=True): - if skip_download: - return True - if config is None: - self.eipserviceconfig.load() - config = self.eipserviceconfig.config - if uri is None: - #XXX - #if not domain: - #domain = self.domain or config.get('provider', None) - uri = self._get_eip_service_uri( - domain=self.apidomain) - - if autocacert and self.cacert is not None: - verify = self.cacert - - self.eipserviceconfig.load( - from_uri=uri, - fetcher=self.fetcher, - force_download=force_download, - verify=verify) - self.eipserviceconfig.save() - - def check_complete_eip_config(self, config=None): - # TODO check for gateway - if config is None: - config = self.eipconfig.config - try: - assert 'provider' in config - assert config['provider'] is not None - # XXX assert there is gateway !! - except AssertionError: - raise eipexceptions.EIPConfigurationError - - # XXX TODO: - # We should WRITE eip config if missing or - # incomplete at this point - #self.eipconfig.save() - - # - # private helpers - # - - def _is_there_default_eipconfig(self): - return self.eipconfig.exists() - - def _dump_default_eipconfig(self): - self.eipconfig.save(force=True) - - def _get_provider_definition_uri(self, domain=None, path=None): - if domain is None: - domain = self.domain or baseconstants.DEFAULT_PROVIDER - if path is None: - path = baseconstants.DEFINITION_EXPECTED_PATH - uri = u"https://%s/%s" % (domain, path) - logger.debug('getting provider definition from %s' % uri) - return uri - - def _get_eip_service_uri(self, domain=None, path=None): - if domain is None: - domain = self.domain or baseconstants.DEFAULT_PROVIDER - if path is None: - path = eipconstants.EIP_SERVICE_EXPECTED_PATH - uri = "https://%s/%s" % (domain, path) - logger.debug('getting eip service file from %s', uri) - return uri - - def set_api_domain(self): - """sets api domain from defaultprovider config object""" - api = self.defaultprovider.config.get('api_uri', None) - # the caller is responsible for having loaded the config - # object at this point - if api: - api_dom = get_https_domain_and_port(api) - self.apidomain = "%s:%s" % api_dom - - def get_api_domain(self): - """gets api domain""" - return self.apidomain diff --git a/src/leap/eip/config.py b/src/leap/eip/config.py deleted file mode 100644 index 917871da..00000000 --- a/src/leap/eip/config.py +++ /dev/null @@ -1,398 +0,0 @@ -import logging -import os -import platform -import re -import tempfile - -from leap import __branding as BRANDING -from leap import certs -from leap.util.misc import null_check -from leap.util.fileutil import (which, mkdir_p, check_and_fix_urw_only) - -from leap.base import config as baseconfig -from leap.baseapp.permcheck import (is_pkexec_in_system, - is_auth_agent_running) -from leap.eip import exceptions as eip_exceptions -from leap.eip import specs as eipspecs - -logger = logging.getLogger(name=__name__) -provider_ca_file = BRANDING.get('provider_ca_file', None) - -_platform = platform.system() - - -class EIPConfig(baseconfig.JSONLeapConfig): - spec = eipspecs.eipconfig_spec - - def _get_slug(self): - eipjsonpath = baseconfig.get_config_file( - 'eip.json') - return eipjsonpath - - def _set_slug(self, *args, **kwargs): - raise AttributeError("you cannot set slug") - - slug = property(_get_slug, _set_slug) - - -class EIPServiceConfig(baseconfig.JSONLeapConfig): - spec = eipspecs.eipservice_config_spec - - def _get_slug(self): - domain = getattr(self, 'domain', None) - if domain: - path = baseconfig.get_provider_path(domain) - else: - path = baseconfig.get_default_provider_path() - return baseconfig.get_config_file( - 'eip-service.json', folder=path) - - def _set_slug(self): - raise AttributeError("you cannot set slug") - - slug = property(_get_slug, _set_slug) - - -def get_socket_path(): - socket_path = os.path.join( - tempfile.mkdtemp(prefix="leap-tmp"), - 'openvpn.socket') - #logger.debug('socket path: %s', socket_path) - return socket_path - - -def get_eip_gateway(eipconfig=None, eipserviceconfig=None): - """ - return the first host in eip service config - that matches the name defined in the eip.json config - file. - """ - # XXX eventually we should move to a more clever - # gateway selection. maybe we could return - # all gateways that match our cluster. - - null_check(eipconfig, "eipconfig") - null_check(eipserviceconfig, "eipserviceconfig") - PLACEHOLDER = "testprovider.example.org" - - conf = eipconfig.config - eipsconf = eipserviceconfig.config - - primary_gateway = conf.get('primary_gateway', None) - if not primary_gateway: - return PLACEHOLDER - - gateways = eipsconf.get('gateways', None) - if not gateways: - logger.error('missing gateways in eip service config') - return PLACEHOLDER - - if len(gateways) > 0: - for gw in gateways: - clustername = gw.get('cluster', None) - if not clustername: - logger.error('no cluster name') - return - - if clustername == primary_gateway: - # XXX at some moment, we must - # make this a more generic function, - # and return ports, protocols... - ipaddress = gw.get('ip_address', None) - if not ipaddress: - logger.error('no ip_address') - return - return ipaddress - logger.error('could not find primary gateway in provider' - 'gateway list') - - -def get_cipher_options(eipserviceconfig=None): - """ - gathers optional cipher options from eip-service config. - :param eipserviceconfig: EIPServiceConfig instance - """ - null_check(eipserviceconfig, 'eipserviceconfig') - eipsconf = eipserviceconfig.get_config() - - ALLOWED_KEYS = ("auth", "cipher", "tls-cipher") - CIPHERS_REGEX = re.compile("[A-Z0-9\-]+") - opts = [] - if 'openvpn_configuration' in eipsconf: - config = eipserviceconfig.config.get( - "openvpn_configuration", {}) - for key, value in config.items(): - if key in ALLOWED_KEYS and value is not None: - sanitized_val = CIPHERS_REGEX.findall(value) - if len(sanitized_val) != 0: - _val = sanitized_val[0] - opts.append('--%s' % key) - opts.append('%s' % _val) - return opts - -LINUX_UP_DOWN_SCRIPT = "/etc/leap/resolv-update" -OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-down-root.so" - - -def has_updown_scripts(): - """ - checks the existence of the up/down scripts - """ - # XXX should check permissions too - is_file = os.path.isfile(LINUX_UP_DOWN_SCRIPT) - if not is_file: - logger.warning( - "Could not find up/down scripts at %s! " - "Risk of DNS Leaks!!!") - return is_file - - -def build_ovpn_options(daemon=False, socket_path=None, **kwargs): - """ - build a list of options - to be passed in the - openvpn invocation - @rtype: list - @rparam: options - """ - # XXX review which of the - # options we don't need. - - # TODO pass also the config file, - # since we will need to take some - # things from there if present. - - provider = kwargs.pop('provider', None) - eipconfig = EIPConfig(domain=provider) - eipconfig.load() - eipserviceconfig = EIPServiceConfig(domain=provider) - eipserviceconfig.load() - - # get user/group name - # also from config. - user = baseconfig.get_username() - group = baseconfig.get_groupname() - - opts = [] - - opts.append('--client') - - opts.append('--dev') - # XXX same in win? - opts.append('tun') - opts.append('--persist-tun') - opts.append('--persist-key') - - verbosity = kwargs.get('ovpn_verbosity', None) - if verbosity and 1 <= verbosity <= 6: - opts.append('--verb') - opts.append("%s" % verbosity) - - # remote ############################## - # (server, port, protocol) - - opts.append('--remote') - - gw = get_eip_gateway(eipconfig=eipconfig, - eipserviceconfig=eipserviceconfig) - logger.debug('setting eip gateway to %s', gw) - opts.append(str(gw)) - - # get port/protocol from eipservice too - opts.append('1194') - #opts.append('80') - opts.append('udp') - - opts.append('--tls-client') - opts.append('--remote-cert-tls') - opts.append('server') - - # get ciphers ####################### - - ciphers = get_cipher_options( - eipserviceconfig=eipserviceconfig) - for cipheropt in ciphers: - opts.append(str(cipheropt)) - - # set user and group - opts.append('--user') - opts.append('%s' % user) - opts.append('--group') - opts.append('%s' % group) - - opts.append('--management-client-user') - opts.append('%s' % user) - opts.append('--management-signal') - - # set default options for management - # interface. unix sockets or telnet interface for win. - # XXX take them from the config object. - - if _platform == "Windows": - opts.append('--management') - opts.append('localhost') - # XXX which is a good choice? - opts.append('7777') - - if _platform in ("Linux", "Darwin"): - opts.append('--management') - - if socket_path is None: - socket_path = get_socket_path() - opts.append(socket_path) - opts.append('unix') - - opts.append('--script-security') - opts.append('2') - - if _platform == "Linux": - if has_updown_scripts(): - opts.append("--up") - opts.append(LINUX_UP_DOWN_SCRIPT) - opts.append("--down") - opts.append(LINUX_UP_DOWN_SCRIPT) - opts.append("--plugin") - opts.append(OPENVPN_DOWN_ROOT) - opts.append("'script_type=down %s'" % LINUX_UP_DOWN_SCRIPT) - - # certs - client_cert_path = eipspecs.client_cert_path(provider) - ca_cert_path = eipspecs.provider_ca_path(provider) - - # XXX FIX paths for MAC - opts.append('--cert') - opts.append(client_cert_path) - opts.append('--key') - opts.append(client_cert_path) - opts.append('--ca') - opts.append(ca_cert_path) - - # we cannot run in daemon mode - # with the current subp setting. - # see: https://leap.se/code/issues/383 - #if daemon is True: - #opts.append('--daemon') - - logger.debug('vpn options: %s', ' '.join(opts)) - return opts - - -def build_ovpn_command(debug=False, do_pkexec_check=True, vpnbin=None, - socket_path=None, **kwargs): - """ - build a string with the - complete openvpn invocation - - @rtype [string, [list of strings]] - @rparam: a list containing the command string - and a list of options. - """ - command = [] - use_pkexec = True - ovpn = None - - # XXX get use_pkexec from config instead. - - if _platform == "Linux" and use_pkexec and do_pkexec_check: - - # check for both pkexec - # AND a suitable authentication - # agent running. - logger.info('use_pkexec set to True') - - if not is_pkexec_in_system(): - logger.error('no pkexec in system') - raise eip_exceptions.EIPNoPkexecAvailable - - if not is_auth_agent_running(): - logger.warning( - "no polkit auth agent found. " - "pkexec will use its own text " - "based authentication agent. " - "that's probably a bad idea") - raise eip_exceptions.EIPNoPolkitAuthAgentAvailable - - command.append('pkexec') - - if vpnbin is None: - if _platform == "Darwin": - # XXX Should hardcode our installed path - # /Applications/LEAPClient.app/Contents/Resources/openvpn.leap - openvpn_bin = "openvpn.leap" - else: - openvpn_bin = "openvpn" - #XXX hardcode for darwin - ovpn = which(openvpn_bin) - else: - ovpn = vpnbin - if ovpn: - vpn_command = ovpn - else: - vpn_command = "openvpn" - command.append(vpn_command) - daemon_mode = not debug - - for opt in build_ovpn_options(daemon=daemon_mode, socket_path=socket_path, - **kwargs): - command.append(opt) - - # XXX check len and raise proper error - - if _platform == "Darwin": - OSX_ASADMIN = 'do shell script "%s" with administrator privileges' - # XXX fix workaround for Nones - _command = [x if x else " " for x in command] - # XXX debugging! - # XXX get openvpn log path from debug flags - _command.append('--log') - _command.append('/tmp/leap_openvpn.log') - return ["osascript", ["-e", OSX_ASADMIN % ' '.join(_command)]] - else: - return [command[0], command[1:]] - - -def check_vpn_keys(provider=None): - """ - performs an existance and permission check - over the openvpn keys file. - Currently we're expecting a single file - per provider, containing the CA cert, - the provider key, and our client certificate - """ - assert provider is not None - provider_ca = eipspecs.provider_ca_path(provider) - client_cert = eipspecs.client_cert_path(provider) - - logger.debug('provider ca = %s', provider_ca) - logger.debug('client cert = %s', client_cert) - - # if no keys, raise error. - # it's catched by the ui and signal user. - - if not os.path.isfile(provider_ca): - # not there. let's try to copy. - folder, filename = os.path.split(provider_ca) - if not os.path.isdir(folder): - mkdir_p(folder) - if provider_ca_file: - cacert = certs.where(provider_ca_file) - with open(provider_ca, 'w') as pca: - with open(cacert, 'r') as cac: - pca.write(cac.read()) - - if not os.path.isfile(provider_ca): - logger.error('key file %s not found. aborting.', - provider_ca) - raise eip_exceptions.EIPInitNoKeyFileError - - if not os.path.isfile(client_cert): - logger.error('key file %s not found. aborting.', - client_cert) - raise eip_exceptions.EIPInitNoKeyFileError - - for keyfile in (provider_ca, client_cert): - # bad perms? try to fix them - try: - check_and_fix_urw_only(keyfile) - except OSError: - raise eip_exceptions.EIPInitBadKeyFilePermError diff --git a/src/leap/eip/constants.py b/src/leap/eip/constants.py deleted file mode 100644 index 9af5a947..00000000 --- a/src/leap/eip/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -# not used anymore with the new JSONConfig.slug -EIP_CONFIG = "eip.json" -EIP_SERVICE_EXPECTED_PATH = "1/config/eip-service.json" diff --git a/src/leap/eip/eipconnection.py b/src/leap/eip/eipconnection.py deleted file mode 100644 index d012c567..00000000 --- a/src/leap/eip/eipconnection.py +++ /dev/null @@ -1,405 +0,0 @@ -""" -EIP Connection Class -""" -from __future__ import (absolute_import,) -import logging -import Queue -import sys -import time - -from dateutil.parser import parse as dateparse - -from leap.eip.checks import ProviderCertChecker -from leap.eip.checks import EIPConfigChecker -from leap.eip import config as eipconfig -from leap.eip import exceptions as eip_exceptions -from leap.eip.openvpnconnection import OpenVPNConnection - -logger = logging.getLogger(name=__name__) - - -class StatusMixIn(object): - - # a bunch of methods related with querying the connection - # state/status and displaying useful info. - # Needs to get clear on what is what, and - # separate functions. - # Should separate EIPConnectionStatus (self.status) - # from the OpenVPN state/status command and parsing. - - ERR_CONNREFUSED = False - - def connection_state(self): - """ - returns the current connection state - """ - return self.status.current - - def get_icon_name(self): - """ - get icon name from status object - """ - return self.status.get_state_icon() - - def get_leap_status(self): - return self.status.get_leap_status() - - def poll_connection_state(self): - """ - """ - try: - state = self.get_connection_state() - except eip_exceptions.ConnectionRefusedError: - # connection refused. might be not ready yet. - if not self.ERR_CONNREFUSED: - logger.warning('connection refused') - self.ERR_CONNREFUSED = True - return - if not state: - #logger.debug('no state') - return - (ts, status_step, - ok, ip, remote) = state - self.status.set_vpn_state(status_step) - status_step = self.status.get_readable_status() - return (ts, status_step, ok, ip, remote) - - def make_error(self): - """ - capture error and wrap it in an - understandable format - """ - # mostly a hack to display errors in the debug UI - # w/o breaking the polling. - #XXX get helpful error codes - self.with_errors = True - now = int(time.time()) - return '%s,LAUNCHER ERROR,ERROR,-,-' % now - - def state(self): - """ - Sends OpenVPN command: state - """ - state = self._send_command("state") - if not state: - return None - if isinstance(state, str): - return state - if isinstance(state, list): - if len(state) == 1: - return state[0] - else: - return state[-1] - - def vpn_status(self): - """ - OpenVPN command: status - """ - status = self._send_command("status") - return status - - def vpn_status2(self): - """ - OpenVPN command: last 2 statuses - """ - return self._send_command("status 2") - - # - # parse info as the UI expects - # - - def get_status_io(self): - status = self.vpn_status() - if isinstance(status, str): - lines = status.split('\n') - if isinstance(status, list): - lines = status - try: - (header, when, tun_read, tun_write, - tcp_read, tcp_write, auth_read) = tuple(lines) - except ValueError: - return None - - when_ts = dateparse(when.split(',')[1]).timetuple() - sep = ',' - # XXX clean up this! - tun_read = tun_read.split(sep)[1] - tun_write = tun_write.split(sep)[1] - tcp_read = tcp_read.split(sep)[1] - tcp_write = tcp_write.split(sep)[1] - auth_read = auth_read.split(sep)[1] - - # XXX this could be a named tuple. prettier. - return when_ts, (tun_read, tun_write, tcp_read, tcp_write, auth_read) - - def get_connection_state(self): - state = self.state() - if state is not None: - ts, status_step, ok, ip, remote = state.split(',') - ts = time.gmtime(float(ts)) - # XXX this could be a named tuple. prettier. - return ts, status_step, ok, ip, remote - - -class EIPConnection(OpenVPNConnection, StatusMixIn): - """ - Aka conductor. - Manages the execution of the OpenVPN process, auto starts, monitors the - network connection, handles configuration, fixes leaky hosts, handles - errors, etc. - Status updates (connected, bandwidth, etc) are signaled to the GUI. - """ - - # XXX change name to EIPConductor ?? - - def __init__(self, - provider_cert_checker=ProviderCertChecker, - config_checker=EIPConfigChecker, - *args, **kwargs): - #self.settingsfile = kwargs.get('settingsfile', None) - #self.logfile = kwargs.get('logfile', None) - self.provider = kwargs.pop('provider', None) - self._providercertchecker = provider_cert_checker - self._configchecker = config_checker - - self.error_queue = Queue.Queue() - - status_signals = kwargs.pop('status_signals', None) - self.status = EIPConnectionStatus(callbacks=status_signals) - - checker_signals = kwargs.pop('checker_signals', None) - self.checker_signals = checker_signals - - self.init_checkers() - - host = eipconfig.get_socket_path() - kwargs['host'] = host - - super(EIPConnection, self).__init__(*args, **kwargs) - - def connect(self, **kwargs): - """ - entry point for connection process - """ - # in OpenVPNConnection - self.try_openvpn_connection() - - def disconnect(self, shutdown=False): - """ - disconnects client - """ - self.terminate_openvpn_connection(shutdown=shutdown) - self.status.change_to(self.status.DISCONNECTED) - - def has_errors(self): - return True if self.error_queue.qsize() != 0 else False - - def init_checkers(self): - """ - initialize checkers - """ - self.provider_cert_checker = self._providercertchecker( - domain=self.provider) - self.config_checker = self._configchecker(domain=self.provider) - - def set_provider_domain(self, domain): - """ - sets the provider domain. - used from the first run wizard when we launch the run_checks - and connect process after having initialized the conductor. - """ - # This looks convoluted, right. - # We have to reinstantiate checkers cause we're passing - # the domain param that we did not know at the beginning - # (only for the firstrunwizard case) - self.provider = domain - self.init_checkers() - - def run_checks(self, skip_download=False, skip_verify=False): - """ - run all eip checks previous to attempting a connection - """ - logger.debug('running conductor checks') - - def push_err(exc): - # keep the original traceback! - exc_traceback = sys.exc_info()[2] - self.error_queue.put((exc, exc_traceback)) - - try: - # network (1) - if self.checker_signals: - for signal in self.checker_signals: - signal('checking encryption keys') - self.provider_cert_checker.run_all(skip_verify=skip_verify) - except Exception as exc: - push_err(exc) - try: - if self.checker_signals: - for signal in self.checker_signals: - signal('checking provider config') - self.config_checker.run_all(skip_download=skip_download) - except Exception as exc: - push_err(exc) - try: - self.run_openvpn_checks() - except Exception as exc: - push_err(exc) - - -class EIPConnectionStatus(object): - """ - Keep track of client (gui) and openvpn - states. - - These are the OpenVPN states: - CONNECTING -- OpenVPN's initial state. - WAIT -- (Client only) Waiting for initial response - from server. - AUTH -- (Client only) Authenticating with server. - GET_CONFIG -- (Client only) Downloading configuration options - from server. - ASSIGN_IP -- Assigning IP address to virtual network - interface. - ADD_ROUTES -- Adding routes to system. - CONNECTED -- Initialization Sequence Completed. - RECONNECTING -- A restart has occurred. - EXITING -- A graceful exit is in progress. - - We add some extra states: - - DISCONNECTED -- GUI initial state. - UNRECOVERABLE -- An unrecoverable error has been raised - while invoking openvpn service. - """ - CONNECTING = 1 - WAIT = 2 - AUTH = 3 - GET_CONFIG = 4 - ASSIGN_IP = 5 - ADD_ROUTES = 6 - CONNECTED = 7 - RECONNECTING = 8 - EXITING = 9 - - # gui specific states: - UNRECOVERABLE = 11 - DISCONNECTED = 0 - - def __init__(self, callbacks=None): - """ - EIPConnectionStatus is initialized with a tuple - of signals to be triggered. - :param callbacks: a tuple of (callable) observers - :type callbacks: tuple - """ - self.current = self.DISCONNECTED - self.previous = None - # (callbacks to connect to signals in Qt-land) - self.callbacks = callbacks - - def get_readable_status(self): - # XXX DRY status / labels a little bit. - # think we'll want to i18n this. - human_status = { - 0: 'disconnected', - 1: 'connecting', - 2: 'waiting', - 3: 'authenticating', - 4: 'getting config', - 5: 'assigning ip', - 6: 'adding routes', - 7: 'connected', - 8: 'reconnecting', - 9: 'exiting', - 11: 'unrecoverable error', - } - return human_status[self.current] - - def get_leap_status(self): - # XXX improve nomenclature - leap_status = { - 0: 'disconnected', - 1: 'connecting to gateway', - 2: 'connecting to gateway', - 3: 'authenticating', - 4: 'establishing network encryption', - 5: 'establishing network encryption', - 6: 'establishing network encryption', - 7: 'connected', - 8: 'reconnecting', - 9: 'exiting', - 11: 'unrecoverable error', - } - return leap_status[self.current] - - def get_state_icon(self): - """ - returns the high level icon - for each fine-grain openvpn state - """ - connecting = (self.CONNECTING, - self.WAIT, - self.AUTH, - self.GET_CONFIG, - self.ASSIGN_IP, - self.ADD_ROUTES) - connected = (self.CONNECTED,) - disconnected = (self.DISCONNECTED, - self.UNRECOVERABLE) - - # this can be made smarter, - # but it's like it'll change, - # so +readability. - - if self.current in connecting: - return "connecting" - if self.current in connected: - return "connected" - if self.current in disconnected: - return "disconnected" - - def set_vpn_state(self, status): - """ - accepts a state string from the management - interface, and sets the internal state. - :param status: openvpn STATE (uppercase). - :type status: str - """ - if hasattr(self, status): - self.change_to(getattr(self, status)) - - def set_current(self, to): - """ - setter for the 'current' property - :param to: destination state - :type to: int - """ - self.current = to - - def change_to(self, to): - """ - :param to: destination state - :type to: int - """ - if to == self.current: - return - changed = False - from_ = self.current - self.current = to - - # We can add transition restrictions - # here to ensure no transitions are - # allowed outside the fsm. - - self.set_current(to) - changed = True - - #trigger signals (as callbacks) - #print('current state: %s' % self.current) - if changed: - self.previous = from_ - if self.callbacks: - for cb in self.callbacks: - if callable(cb): - cb(self) diff --git a/src/leap/eip/exceptions.py b/src/leap/eip/exceptions.py deleted file mode 100644 index b7d398c3..00000000 --- a/src/leap/eip/exceptions.py +++ /dev/null @@ -1,175 +0,0 @@ -""" -Generic error hierarchy -Leap/EIP exceptions used for exception handling, -logging, and notifying user of errors -during leap operation. - -Exception hierarchy -------------------- -All EIP Errors must inherit from EIPClientError (note: move that to -a more generic LEAPClientBaseError). - -Exception attributes and their meaning/uses -------------------------------------------- - -* critical: if True, will abort execution prematurely, - after attempting any cleaning - action. - -* failfirst: breaks any error_check loop that is examining - the error queue. - -* message: the message that will be used in the __repr__ of the exception. - -* usermessage: the message that will be passed to user in ErrorDialogs - in Qt-land. - -TODO: - -* EIPClientError: - Should inherit from LeapException - -* gettext / i18n for user messages. - -""" -from leap.base.exceptions import LeapException -from leap.util.translations import translate - - -# This should inherit from LeapException -class EIPClientError(Exception): - """ - base EIPClient exception - """ - critical = False - failfirst = False - warning = False - - -class CriticalError(EIPClientError): - """ - we cannot do anything about it, sorry - """ - critical = True - failfirst = True - - -class Warning(EIPClientError): - """ - just that, warnings - """ - warning = True - - -class EIPNoPolkitAuthAgentAvailable(CriticalError): - message = "No polkit authentication agent could be found" - usermessage = translate( - "EIPErrors", - "We could not find any authentication " - "agent in your system.<br/>" - "Make sure you have " - "<b>polkit-gnome-authentication-agent-1</b> " - "running and try again.") - - -class EIPNoPkexecAvailable(Warning): - message = "No pkexec binary found" - usermessage = translate( - "EIPErrors", - "We could not find <b>pkexec</b> in your " - "system.<br/> Do you want to try " - "<b>setuid workaround</b>? " - "(<i>DOES NOTHING YET</i>)") - failfirst = True - - -class EIPNoCommandError(EIPClientError): - message = "no suitable openvpn command found" - usermessage = translate( - "EIPErrors", - "No suitable openvpn command found. " - "<br/>(Might be a permissions problem)") - - -class EIPBadCertError(Warning): - # XXX this should be critical and fail close - message = "cert verification failed" - usermessage = translate( - "EIPErrors", - "there is a problem with provider certificate") - - -class LeapBadConfigFetchedError(Warning): - message = "provider sent a malformed json file" - usermessage = translate( - "EIPErrors", - "an error occurred during configuratio of leap services") - - -class OpenVPNAlreadyRunning(CriticalError): - message = "Another OpenVPN Process is already running." - usermessage = translate( - "EIPErrors", - "Another OpenVPN Process has been detected. " - "Please close it before starting leap-client") - - -class HttpsNotSupported(LeapException): - message = "connection refused while accessing via https" - usermessage = translate( - "EIPErrors", - "Server does not allow secure connections") - - -class HttpsBadCertError(LeapException): - message = "verification error on cert" - usermessage = translate( - "EIPErrors", - "Server certificate could not be verified") - -# -# errors still needing some love -# - - -class EIPInitNoKeyFileError(CriticalError): - message = "No vpn keys found in the expected path" - usermessage = translate( - "EIPErrors", - "We could not find your eip certs in the expected path") - - -class EIPInitBadKeyFilePermError(Warning): - # I don't know if we should be telling user or not, - # we try to fix permissions and should only re-raise - # if permission check failed. - pass - - -class EIPInitNoProviderError(EIPClientError): - pass - - -class EIPInitBadProviderError(EIPClientError): - pass - - -class EIPConfigurationError(EIPClientError): - pass - -# -# Errors that probably we don't need anymore -# chase down for them and check. -# - - -class MissingSocketError(Exception): - pass - - -class ConnectionRefusedError(Exception): - pass - - -class EIPMissingDefaultProvider(Exception): - pass diff --git a/src/leap/eip/openvpnconnection.py b/src/leap/eip/openvpnconnection.py deleted file mode 100644 index bee8c010..00000000 --- a/src/leap/eip/openvpnconnection.py +++ /dev/null @@ -1,410 +0,0 @@ -""" -OpenVPN Connection -""" -from __future__ import (print_function) -from functools import partial -import logging -import os -import psutil -import shutil -import select -import socket -from time import sleep - -logger = logging.getLogger(name=__name__) - -from leap.base.connection import Connection -from leap.base.constants import OPENVPN_BIN -from leap.util.coroutines import spawn_and_watch_process -from leap.util.misc import get_openvpn_pids - -from leap.eip.udstelnet import UDSTelnet -from leap.eip import config as eip_config -from leap.eip import exceptions as eip_exceptions - - -class OpenVPNManagement(object): - - # TODO explain a little bit how management interface works - # and our telnet interface with support for unix sockets. - - """ - for more information, read openvpn management notes. - zcat `dpkg -L openvpn | grep management` - """ - - def _connect_to_management(self): - """ - Connect to openvpn management interface - """ - if hasattr(self, 'tn'): - self._close_management_socket() - self.tn = UDSTelnet(self.host, self.port) - - # XXX make password optional - # specially for win. we should generate - # the pass on the fly when invoking manager - # from conductor - - #self.tn.read_until('ENTER PASSWORD:', 2) - #self.tn.write(self.password + '\n') - #self.tn.read_until('SUCCESS:', 2) - if self.tn: - self._seek_to_eof() - return True - - def _close_management_socket(self, announce=True): - """ - Close connection to openvpn management interface - """ - logger.debug('closing socket') - if announce: - self.tn.write("quit\n") - self.tn.read_all() - self.tn.get_socket().close() - del self.tn - - def _seek_to_eof(self): - """ - Read as much as available. Position seek pointer to end of stream - """ - try: - b = self.tn.read_eager() - except EOFError: - logger.debug("Could not read from socket. Assuming it died.") - return - while b: - try: - b = self.tn.read_eager() - except EOFError: - logger.debug("Could not read from socket. Assuming it died.") - - def _send_command(self, cmd): - """ - Send a command to openvpn and return response as list - """ - if not self.connected(): - try: - self._connect_to_management() - except eip_exceptions.MissingSocketError: - #logger.warning('missing management socket') - return [] - try: - if hasattr(self, 'tn'): - self.tn.write(cmd + "\n") - except socket.error: - logger.error('socket error') - self._close_management_socket(announce=False) - return [] - try: - buf = self.tn.read_until(b"END", 2) - self._seek_to_eof() - blist = buf.split('\r\n') - if blist[-1].startswith('END'): - del blist[-1] - return blist - else: - return [] - except socket.error as exc: - logger.debug('socket error: %s' % exc.message) - except select.error as exc: - logger.debug('select error: %s' % exc.message) - - def _send_short_command(self, cmd): - """ - parse output from commands that are - delimited by "success" instead - """ - if not self.connected(): - self.connect() - self.tn.write(cmd + "\n") - # XXX not working? - buf = self.tn.read_until(b"SUCCESS", 2) - self._seek_to_eof() - blist = buf.split('\r\n') - return blist - - # - # random maybe useful vpn commands - # - - def pid(self): - #XXX broken - return self._send_short_command("pid") - - -class OpenVPNConnection(Connection, OpenVPNManagement): - """ - All related to invocation - of the openvpn binary. - It's extended by EIPConnection. - """ - - # XXX Inheriting from Connection was an early design idea - # but currently that's an empty class. - # We can get rid of that if we don't use it for sharing - # state with other leap modules. - - def __init__(self, - watcher_cb=None, - debug=False, - host=None, - port="unix", - password=None, - *args, **kwargs): - """ - :param watcher_cb: callback to be \ -called for each line in watched stdout - :param signal_map: dictionary of signal names and callables \ -to be triggered for each one of them. - :type watcher_cb: function - :type signal_map: dict - """ - #XXX FIXME - #change watcher_cb to line_observer - # XXX if not host: raise ImproperlyConfigured - - logger.debug('init openvpn connection') - self.debug = debug - self.ovpn_verbosity = kwargs.get('ovpn_verbosity', None) - - self.watcher_cb = watcher_cb - #self.signal_maps = signal_maps - - self.subp = None - self.watcher = None - - self.server = None - self.port = None - self.proto = None - - self.command = None - self.args = None - - # XXX get autostart from config - self.autostart = True - - # management interface init - self.host = host - if isinstance(port, str) and port.isdigit(): - port = int(port) - elif port == "unix": - port = "unix" - else: - port = None - self.port = port - self.password = password - - def run_openvpn_checks(self): - """ - runs check needed before launching - openvpn subprocess. will raise if errors found. - """ - logger.debug('running openvpn checks') - # XXX I think that "check_if_running" should be called - # from try openvpn connection instead. -- kali. - # let's prepare tests for that before changing it... - self._check_if_running_instance() - self._set_ovpn_command() - self._check_vpn_keys() - - def try_openvpn_connection(self): - """ - attempts to connect - """ - # XXX should make public method - if self.command is None: - raise eip_exceptions.EIPNoCommandError - if self.subp is not None: - logger.debug('cowardly refusing to launch subprocess again') - # XXX this is not returning ???!! - # FIXME -- so it's calling it all the same!! - - self._launch_openvpn() - - def connected(self): - """ - Returns True if connected - rtype: bool - """ - # XXX make a property - return hasattr(self, 'tn') - - def terminate_openvpn_connection(self, shutdown=False): - """ - terminates openvpn child subprocess - """ - if self.subp: - try: - self._stop_openvpn() - except eip_exceptions.ConnectionRefusedError: - logger.warning( - 'unable to send sigterm signal to openvpn: ' - 'connection refused.') - - # XXX kali -- - # XXX review-me - # I think this will block if child process - # does not return. - # Maybe we can .poll() for a given - # interval and exit in any case. - - RETCODE = self.subp.wait() - if RETCODE: - logger.error( - 'cannot terminate subprocess! Retcode %s' - '(We might have left openvpn running)' % RETCODE) - - if shutdown: - self._cleanup_tempfiles() - - def _cleanup_tempfiles(self): - """ - remove all temporal files - we might have left behind - """ - # if self.port is 'unix', we have - # created a temporal socket path that, under - # normal circumstances, we should be able to - # delete - - if self.port == "unix": - logger.debug('cleaning socket file temp folder') - - tempfolder = os.path.split(self.host)[0] - if os.path.isdir(tempfolder): - try: - shutil.rmtree(tempfolder) - except OSError: - logger.error('could not delete tmpfolder %s' % tempfolder) - - # checks - - def _check_if_running_instance(self): - """ - check if openvpn is already running - """ - openvpn_pids = get_openvpn_pids() - if openvpn_pids: - logger.debug('an openvpn instance is already running.') - logger.debug('attempting to stop openvpn instance.') - if not self._stop_openvpn(): - raise eip_exceptions.OpenVPNAlreadyRunning - return - else: - logger.debug('no openvpn instance found.') - - def _set_ovpn_command(self): - try: - command, args = eip_config.build_ovpn_command( - provider=self.provider, - debug=self.debug, - socket_path=self.host, - ovpn_verbosity=self.ovpn_verbosity) - except eip_exceptions.EIPNoPolkitAuthAgentAvailable: - command = args = None - raise - except eip_exceptions.EIPNoPkexecAvailable: - command = args = None - raise - - # XXX if not command, signal error. - self.command = command - self.args = args - - def _check_vpn_keys(self): - """ - checks for correct permissions on vpn keys - """ - try: - eip_config.check_vpn_keys(provider=self.provider) - except eip_exceptions.EIPInitBadKeyFilePermError: - logger.error('Bad VPN Keys permission!') - # do nothing now - # and raise the rest ... - - # starting and stopping openvpn subprocess - - def _launch_openvpn(self): - """ - invocation of openvpn binaries in a subprocess. - """ - #XXX TODO: - #deprecate watcher_cb, - #use _only_ signal_maps instead - - #logger.debug('_launch_openvpn called') - if self.watcher_cb is not None: - linewrite_callback = self.watcher_cb - else: - #XXX get logger instead - linewrite_callback = lambda line: logger.debug( - 'watcher: %s' % line) - - # the partial is not - # being applied now because we're not observing the process - # stdout like we did in the early stages. but I leave it - # here since it will be handy for observing patterns in the - # thru-the-manager updates (with regex) - observers = (linewrite_callback, - partial(lambda con_status, - line: linewrite_callback, self.status)) - subp, watcher = spawn_and_watch_process( - self.command, - self.args, - observers=observers) - self.subp = subp - self.watcher = watcher - - def _stop_openvpn(self): - """ - stop openvpn process - by sending SIGTERM to the management - interface - """ - # XXX method a bit too long, split - logger.debug("atempting to terminate openvpn process...") - if self.connected(): - try: - self._send_command("signal SIGTERM\n") - sleep(1) - if not self.subp: # XXX ??? - return True - except socket.error: - logger.warning('management socket died') - return - - #shutting openvpn failured - #try patching in old openvpn host and trying again - # XXX could be more than one! - process = self._get_openvpn_process() - if process: - logger.debug('process: %s' % process.name) - cmdline = process.cmdline - - manag_flag = "--management" - if isinstance(cmdline, list) and manag_flag in cmdline: - _index = cmdline.index(manag_flag) - self.host = cmdline[_index + 1] - self._send_command("signal SIGTERM\n") - - #make sure the process was terminated - process = self._get_openvpn_process() - if not process: - logger.debug("Existing OpenVPN Process Terminated") - return True - else: - logger.error("Unable to terminate existing OpenVPN Process.") - return False - - return True - - def _get_openvpn_process(self): - for process in psutil.process_iter(): - if OPENVPN_BIN in process.name: - return process - return None - - def get_log(self, lines=1): - log = self._send_command("log %s" % lines) - return log diff --git a/src/leap/eip/specs.py b/src/leap/eip/specs.py deleted file mode 100644 index c41fd29b..00000000 --- a/src/leap/eip/specs.py +++ /dev/null @@ -1,136 +0,0 @@ -from __future__ import (unicode_literals) -import os - -from leap import __branding -from leap.base import config as baseconfig - -# XXX move provider stuff to base config - -PROVIDER_CA_CERT = __branding.get( - 'provider_ca_file', - 'cacert.pem') - -provider_ca_path = lambda domain: str(os.path.join( - #baseconfig.get_default_provider_path(), - baseconfig.get_provider_path(domain), - 'keys', 'ca', - 'cacert.pem' -)) if domain else None - -default_provider_ca_path = lambda: str(os.path.join( - baseconfig.get_default_provider_path(), - 'keys', 'ca', - PROVIDER_CA_CERT -)) - -PROVIDER_DOMAIN = __branding.get('provider_domain', 'testprovider.example.org') - - -client_cert_path = lambda domain: unicode(os.path.join( - baseconfig.get_provider_path(domain), - 'keys', 'client', - 'openvpn.pem' -)) if domain else None - -default_client_cert_path = lambda: unicode(os.path.join( - baseconfig.get_default_provider_path(), - 'keys', 'client', - 'openvpn.pem' -)) - -eipconfig_spec = { - 'description': 'sample eipconfig', - 'type': 'object', - 'properties': { - 'provider': { - 'type': unicode, - 'default': u"%s" % PROVIDER_DOMAIN, - 'required': True, - }, - 'transport': { - 'type': unicode, - 'default': u"openvpn", - }, - 'openvpn_protocol': { - 'type': unicode, - 'default': u"tcp" - }, - 'openvpn_port': { - 'type': int, - 'default': 80 - }, - 'openvpn_ca_certificate': { - 'type': unicode, # path - 'default': default_provider_ca_path - }, - 'openvpn_client_certificate': { - 'type': unicode, # path - 'default': default_client_cert_path - }, - 'connect_on_login': { - 'type': bool, - 'default': True - }, - 'block_cleartext_traffic': { - 'type': bool, - 'default': True - }, - 'primary_gateway': { - 'type': unicode, - 'default': u"location_unknown", - #'required': True - }, - 'secondary_gateway': { - 'type': unicode, - 'default': u"location_unknown2" - }, - 'management_password': { - 'type': unicode - } - } -} - -eipservice_config_spec = { - 'description': 'sample eip service config', - 'type': 'object', - 'properties': { - 'serial': { - 'type': int, - 'required': True, - 'default': 1 - }, - 'version': { - 'type': int, - 'required': True, - 'default': 1 - }, - 'clusters': { - 'type': list, - 'default': [ - {"label": { - "en": "Location Unknown"}, - "name": "location_unknown"}] - }, - 'gateways': { - 'type': list, - 'default': [ - {"capabilities": { - "adblock": True, - "filter_dns": True, - "ports": ["80", "53", "443", "1194"], - "protocols": ["udp", "tcp"], - "transport": ["openvpn"], - "user_ips": False}, - "cluster": "location_unknown", - "host": "location.example.org", - "ip_address": "127.0.0.1"}] - }, - 'openvpn_configuration': { - 'type': dict, - 'default': { - "auth": None, - "cipher": None, - "tls-cipher": None} - } - } -} diff --git a/src/leap/eip/tests/__init__.py b/src/leap/eip/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/src/leap/eip/tests/__init__.py +++ /dev/null diff --git a/src/leap/eip/tests/data.py b/src/leap/eip/tests/data.py deleted file mode 100644 index a7fe1853..00000000 --- a/src/leap/eip/tests/data.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import unicode_literals -import os - -#from leap import __branding - -# sample data used in tests - -#PROVIDER = __branding.get('provider_domain') -PROVIDER = "testprovider.example.org" - -EIP_SAMPLE_CONFIG = { - "provider": "%s" % PROVIDER, - "transport": "openvpn", - "openvpn_protocol": "tcp", - "openvpn_port": 80, - "openvpn_ca_certificate": os.path.expanduser( - "~/.config/leap/providers/" - "%s/" - "keys/ca/cacert.pem" % PROVIDER), - "openvpn_client_certificate": os.path.expanduser( - "~/.config/leap/providers/" - "%s/" - "keys/client/openvpn.pem" % PROVIDER), - "connect_on_login": True, - "block_cleartext_traffic": True, - "primary_gateway": "location_unknown", - "secondary_gateway": "location_unknown2", - #"management_password": "oph7Que1othahwiech6J" -} - -EIP_SAMPLE_SERVICE = { - "serial": 1, - "version": 1, - "clusters": [ - {"label": { - "en": "Location Unknown"}, - "name": "location_unknown"} - ], - "gateways": [ - {"capabilities": { - "adblock": True, - "filter_dns": True, - "ports": ["80", "53", "443", "1194"], - "protocols": ["udp", "tcp"], - "transport": ["openvpn"], - "user_ips": False}, - "cluster": "location_unknown", - "host": "location.example.org", - "ip_address": "192.0.43.10"} - ] -} diff --git a/src/leap/eip/tests/test_checks.py b/src/leap/eip/tests/test_checks.py deleted file mode 100644 index f42a0eeb..00000000 --- a/src/leap/eip/tests/test_checks.py +++ /dev/null @@ -1,372 +0,0 @@ -from BaseHTTPServer import BaseHTTPRequestHandler -import copy -import json -try: - import unittest2 as unittest -except ImportError: - import unittest -import os -import time -import urlparse - -from mock import (patch, Mock) - -import requests - -from leap.base import config as baseconfig -from leap.base import pluggableconfig -from leap.base.constants import (DEFAULT_PROVIDER_DEFINITION, - DEFINITION_EXPECTED_PATH) -from leap.eip import checks as eipchecks -from leap.eip import specs as eipspecs -from leap.eip import exceptions as eipexceptions -from leap.eip.tests import data as testdata -from leap.testing.basetest import BaseLeapTest -from leap.testing.https_server import BaseHTTPSServerTestCase -from leap.testing.https_server import where as where_cert -from leap.util.fileutil import mkdir_f - - -class NoLogRequestHandler: - def log_message(self, *args): - # don't write log msg to stderr - pass - - def read(self, n=None): - return '' - - -class EIPCheckTest(BaseLeapTest): - - __name__ = "eip_check_tests" - provider = "testprovider.example.org" - maxDiff = None - - def setUp(self): - pass - - def tearDown(self): - pass - - # test methods are there, and can be called from run_all - - def test_checker_should_implement_check_methods(self): - checker = eipchecks.EIPConfigChecker(domain=self.provider) - - self.assertTrue(hasattr(checker, "check_default_eipconfig"), - "missing meth") - self.assertTrue(hasattr(checker, "check_is_there_default_provider"), - "missing meth") - self.assertTrue(hasattr(checker, "fetch_definition"), "missing meth") - self.assertTrue(hasattr(checker, "fetch_eip_service_config"), - "missing meth") - self.assertTrue(hasattr(checker, "check_complete_eip_config"), - "missing meth") - - def test_checker_should_actually_call_all_tests(self): - checker = eipchecks.EIPConfigChecker(domain=self.provider) - - mc = Mock() - checker.run_all(checker=mc) - self.assertTrue(mc.check_default_eipconfig.called, "not called") - self.assertTrue(mc.check_is_there_default_provider.called, - "not called") - self.assertTrue(mc.fetch_definition.called, - "not called") - self.assertTrue(mc.fetch_eip_service_config.called, - "not called") - self.assertTrue(mc.check_complete_eip_config.called, - "not called") - - # test individual check methods - - def test_check_default_eipconfig(self): - checker = eipchecks.EIPConfigChecker(domain=self.provider) - # no eip config (empty home) - eipconfig_path = checker.eipconfig.filename - self.assertFalse(os.path.isfile(eipconfig_path)) - checker.check_default_eipconfig() - # we've written one, so it should be there. - self.assertTrue(os.path.isfile(eipconfig_path)) - with open(eipconfig_path, 'rb') as fp: - deserialized = json.load(fp) - - # force re-evaluation of the paths - # small workaround for evaluating home dirs correctly - EIP_SAMPLE_CONFIG = copy.copy(testdata.EIP_SAMPLE_CONFIG) - EIP_SAMPLE_CONFIG['openvpn_client_certificate'] = \ - eipspecs.client_cert_path(self.provider) - EIP_SAMPLE_CONFIG['openvpn_ca_certificate'] = \ - eipspecs.provider_ca_path(self.provider) - self.assertEqual(deserialized, EIP_SAMPLE_CONFIG) - - # TODO: shold ALSO run validation methods. - - def test_check_is_there_default_provider(self): - checker = eipchecks.EIPConfigChecker(domain=self.provider) - # we do dump a sample eip config, but lacking a - # default provider entry. - # This error will be possible catched in a different - # place, when JSONConfig does validation of required fields. - - # passing direct config - with self.assertRaises(eipexceptions.EIPMissingDefaultProvider): - checker.check_is_there_default_provider(config={}) - - # ok. now, messing with real files... - # blank out default_provider - sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG) - sampleconfig['provider'] = None - eipcfg_path = checker.eipconfig.filename - mkdir_f(eipcfg_path) - with open(eipcfg_path, 'w') as fp: - json.dump(sampleconfig, fp) - #with self.assertRaises(eipexceptions.EIPMissingDefaultProvider): - # XXX we should catch this as one of our errors, but do not - # see how to do it quickly. - with self.assertRaises(pluggableconfig.ValidationError): - #import ipdb;ipdb.set_trace() - checker.eipconfig.load(fromfile=eipcfg_path) - checker.check_is_there_default_provider() - - sampleconfig = testdata.EIP_SAMPLE_CONFIG - #eipcfg_path = checker._get_default_eipconfig_path() - with open(eipcfg_path, 'w') as fp: - json.dump(sampleconfig, fp) - checker.eipconfig.load() - self.assertTrue(checker.check_is_there_default_provider()) - - def test_fetch_definition(self): - with patch.object(requests, "get") as mocked_get: - mocked_get.return_value.status_code = 200 - mocked_get.return_value.headers = { - 'last-modified': "Wed Dec 12 12:12:12 GMT 2012"} - mocked_get.return_value.json = DEFAULT_PROVIDER_DEFINITION - checker = eipchecks.EIPConfigChecker(fetcher=requests) - sampleconfig = testdata.EIP_SAMPLE_CONFIG - checker.fetch_definition(config=sampleconfig) - - fn = os.path.join(baseconfig.get_default_provider_path(), - DEFINITION_EXPECTED_PATH) - with open(fn, 'r') as fp: - deserialized = json.load(fp) - self.assertEqual(DEFAULT_PROVIDER_DEFINITION, deserialized) - - # XXX TODO check for ConnectionError, HTTPError, InvalidUrl - # (and proper EIPExceptions are raised). - # Look at base.test_config. - - def test_fetch_eip_service_config(self): - with patch.object(requests, "get") as mocked_get: - mocked_get.return_value.status_code = 200 - mocked_get.return_value.headers = { - 'last-modified': "Wed Dec 12 12:12:12 GMT 2012"} - mocked_get.return_value.json = testdata.EIP_SAMPLE_SERVICE - checker = eipchecks.EIPConfigChecker(fetcher=requests) - sampleconfig = testdata.EIP_SAMPLE_CONFIG - checker.fetch_eip_service_config(config=sampleconfig) - - def test_check_complete_eip_config(self): - checker = eipchecks.EIPConfigChecker() - with self.assertRaises(eipexceptions.EIPConfigurationError): - sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG) - sampleconfig['provider'] = None - checker.check_complete_eip_config(config=sampleconfig) - with self.assertRaises(eipexceptions.EIPConfigurationError): - sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG) - del sampleconfig['provider'] - checker.check_complete_eip_config(config=sampleconfig) - - # normal case - sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG) - checker.check_complete_eip_config(config=sampleconfig) - - -class ProviderCertCheckerTest(BaseLeapTest): - - __name__ = "provider_cert_checker_tests" - provider = "testprovider.example.org" - - def setUp(self): - pass - - def tearDown(self): - pass - - # test methods are there, and can be called from run_all - - def test_checker_should_implement_check_methods(self): - checker = eipchecks.ProviderCertChecker() - - # For MVS+ - self.assertTrue(hasattr(checker, "download_ca_cert"), - "missing meth") - self.assertTrue(hasattr(checker, "download_ca_signature"), - "missing meth") - self.assertTrue(hasattr(checker, "get_ca_signatures"), "missing meth") - self.assertTrue(hasattr(checker, "is_there_trust_path"), - "missing meth") - - # For MVS - self.assertTrue(hasattr(checker, "is_there_provider_ca"), - "missing meth") - self.assertTrue(hasattr(checker, "is_https_working"), "missing meth") - self.assertTrue(hasattr(checker, "check_new_cert_needed"), - "missing meth") - - def test_checker_should_actually_call_all_tests(self): - checker = eipchecks.ProviderCertChecker() - - mc = Mock() - checker.run_all(checker=mc) - # XXX MVS+ - #self.assertTrue(mc.download_ca_cert.called, "not called") - #self.assertTrue(mc.download_ca_signature.called, "not called") - #self.assertTrue(mc.get_ca_signatures.called, "not called") - #self.assertTrue(mc.is_there_trust_path.called, "not called") - - # For MVS - self.assertTrue(mc.is_there_provider_ca.called, "not called") - self.assertTrue(mc.is_https_working.called, - "not called") - self.assertTrue(mc.check_new_cert_needed.called, - "not called") - - # test individual check methods - - @unittest.skip - def test_is_there_provider_ca(self): - # XXX commenting out this test. - # With the generic client this does not make sense, - # we should dump one there. - # or test conductor logic. - checker = eipchecks.ProviderCertChecker() - self.assertTrue( - checker.is_there_provider_ca()) - - -class ProviderCertCheckerHTTPSTests(BaseHTTPSServerTestCase, BaseLeapTest): - provider = "testprovider.example.org" - - class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler): - responses = { - '/': ['OK', ''], - '/client.cert': [ - # XXX get sample cert - '-----BEGIN CERTIFICATE-----', - '-----END CERTIFICATE-----'], - '/badclient.cert': [ - 'BADCERT']} - - def do_GET(self): - path = urlparse.urlparse(self.path) - message = '\n'.join(self.responses.get( - path.path, None)) - self.send_response(200) - self.end_headers() - self.wfile.write(message) - - def test_is_https_working(self): - fetcher = requests - uri = "https://%s/" % (self.get_server()) - # bare requests call. this should just pass (if there is - # an https service there). - fetcher.get(uri, verify=False) - checker = eipchecks.ProviderCertChecker(fetcher=fetcher) - self.assertTrue(checker.is_https_working(uri=uri, verify=False)) - - # for local debugs, when in doubt - #self.assertTrue(checker.is_https_working(uri="https://github.com", - #verify=True)) - - # for the two checks below, I know they fail because no ca - # cert is passed to them, and I know that's the error that - # requests return with our implementation. - # We're receiving this because our - # server is dying prematurely when the handshake is interrupted on the - # client side. - # Since we have access to the server, we could check that - # the error raised has been: - # SSL23_READ_BYTES: alert bad certificate - with self.assertRaises(requests.exceptions.SSLError) as exc: - fetcher.get(uri, verify=True) - self.assertTrue( - "SSL23_GET_SERVER_HELLO:unknown protocol" in exc.message) - - # XXX FIXME! Uncomment after #638 is done - #with self.assertRaises(eipexceptions.EIPBadCertError) as exc: - #checker.is_https_working(uri=uri, verify=True) - #self.assertTrue( - #"cert verification failed" in exc.message) - - # get cacert from testing.https_server - cacert = where_cert('cacert.pem') - fetcher.get(uri, verify=cacert) - self.assertTrue(checker.is_https_working(uri=uri, verify=cacert)) - - # same, but get cacert from leap.custom - # XXX TODO! - - @unittest.skip - def test_download_new_client_cert(self): - # FIXME - # Magick srp decorator broken right now... - # Have to mock the decorator and inject something that - # can bypass the authentication - - uri = "https://%s/client.cert" % (self.get_server()) - cacert = where_cert('cacert.pem') - checker = eipchecks.ProviderCertChecker(domain=self.provider) - credentials = "testuser", "testpassword" - self.assertTrue(checker.download_new_client_cert( - credentials=credentials, uri=uri, verify=cacert)) - - # now download a malformed cert - uri = "https://%s/badclient.cert" % (self.get_server()) - cacert = where_cert('cacert.pem') - checker = eipchecks.ProviderCertChecker() - with self.assertRaises(ValueError): - self.assertTrue(checker.download_new_client_cert( - credentials=credentials, uri=uri, verify=cacert)) - - # did we write cert to its path? - clientcertfile = eipspecs.client_cert_path() - self.assertTrue(os.path.isfile(clientcertfile)) - certfile = eipspecs.client_cert_path() - with open(certfile, 'r') as cf: - certcontent = cf.read() - self.assertEqual(certcontent, - '\n'.join( - self.request_handler.responses['/client.cert'])) - os.remove(clientcertfile) - - def test_is_cert_valid(self): - checker = eipchecks.ProviderCertChecker() - # TODO: better exception catching - # should raise eipexceptions.BadClientCertificate, and give reasons - # on msg. - with self.assertRaises(Exception) as exc: - self.assertFalse(checker.is_cert_valid()) - exc.message = "missing cert" - - def test_bad_validity_certs(self): - checker = eipchecks.ProviderCertChecker() - certfile = where_cert('leaptestscert.pem') - self.assertFalse(checker.is_cert_not_expired( - certfile=certfile, - now=lambda: time.mktime((2038, 1, 1, 1, 1, 1, 1, 1, 1)))) - self.assertFalse(checker.is_cert_not_expired( - certfile=certfile, - now=lambda: time.mktime((1970, 1, 1, 1, 1, 1, 1, 1, 1)))) - - def test_check_new_cert_needed(self): - # check: missing cert - checker = eipchecks.ProviderCertChecker(domain=self.provider) - self.assertTrue(checker.check_new_cert_needed(skip_download=True)) - # TODO check: malformed cert - # TODO check: expired cert - # TODO check: pass test server uri instead of skip - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/eip/tests/test_config.py b/src/leap/eip/tests/test_config.py deleted file mode 100644 index 72ab3c8e..00000000 --- a/src/leap/eip/tests/test_config.py +++ /dev/null @@ -1,298 +0,0 @@ -from collections import OrderedDict -import json -import os -import platform -import stat - -try: - import unittest2 as unittest -except ImportError: - import unittest - -#from leap.base import constants -#from leap.eip import config as eip_config -#from leap import __branding as BRANDING -from leap.eip import config as eipconfig -from leap.eip.tests.data import EIP_SAMPLE_CONFIG, EIP_SAMPLE_SERVICE -from leap.testing.basetest import BaseLeapTest -from leap.util.fileutil import mkdir_p, mkdir_f - -_system = platform.system() - -#PROVIDER = BRANDING.get('provider_domain') -#PROVIDER_SHORTNAME = BRANDING.get('short_name') - - -class EIPConfigTest(BaseLeapTest): - - __name__ = "eip_config_tests" - provider = "testprovider.example.org" - - maxDiff = None - - def setUp(self): - pass - - def tearDown(self): - pass - - # - # helpers - # - - def touch_exec(self): - path = os.path.join( - self.tempdir, 'bin') - mkdir_p(path) - tfile = os.path.join( - path, - 'openvpn') - open(tfile, 'wb').close() - os.chmod(tfile, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) - - def write_sample_eipservice(self, vpnciphers=False, extra_vpnopts=None, - gateways=None): - conf = eipconfig.EIPServiceConfig() - mkdir_f(conf.filename) - if gateways: - EIP_SAMPLE_SERVICE['gateways'] = gateways - if vpnciphers: - openvpnconfig = OrderedDict({ - "auth": "SHA1", - "cipher": "AES-128-CBC", - "tls-cipher": "DHE-RSA-AES128-SHA"}) - if extra_vpnopts: - for k, v in extra_vpnopts.items(): - openvpnconfig[k] = v - EIP_SAMPLE_SERVICE['openvpn_configuration'] = openvpnconfig - - with open(conf.filename, 'w') as fd: - fd.write(json.dumps(EIP_SAMPLE_SERVICE)) - - def write_sample_eipconfig(self): - conf = eipconfig.EIPConfig() - folder, f = os.path.split(conf.filename) - if not os.path.isdir(folder): - mkdir_p(folder) - with open(conf.filename, 'w') as fd: - fd.write(json.dumps(EIP_SAMPLE_CONFIG)) - - def get_expected_openvpn_args(self, with_openvpn_ciphers=False): - """ - yeah, this is almost as duplicating the - code for building the command - """ - args = [] - eipconf = eipconfig.EIPConfig(domain=self.provider) - eipconf.load() - eipsconf = eipconfig.EIPServiceConfig(domain=self.provider) - eipsconf.load() - - username = self.get_username() - groupname = self.get_groupname() - - args.append('--client') - args.append('--dev') - #does this have to be tap for win?? - args.append('tun') - args.append('--persist-tun') - args.append('--persist-key') - args.append('--remote') - - args.append('%s' % eipconfig.get_eip_gateway( - eipconfig=eipconf, - eipserviceconfig=eipsconf)) - # XXX get port!? - args.append('1194') - # XXX get proto - args.append('udp') - args.append('--tls-client') - args.append('--remote-cert-tls') - args.append('server') - - if with_openvpn_ciphers: - CIPHERS = [ - "--tls-cipher", "DHE-RSA-AES128-SHA", - "--cipher", "AES-128-CBC", - "--auth", "SHA1"] - for opt in CIPHERS: - args.append(opt) - - args.append('--user') - args.append(username) - args.append('--group') - args.append(groupname) - args.append('--management-client-user') - args.append(username) - args.append('--management-signal') - - args.append('--management') - #XXX hey! - #get platform switches here! - args.append('/tmp/test.socket') - args.append('unix') - - args.append('--script-security') - args.append('2') - - if _system == "Linux": - UPDOWN_SCRIPT = "/etc/leap/resolv-update" - if os.path.isfile(UPDOWN_SCRIPT): - args.append('--up') - args.append('/etc/leap/resolv-update') - args.append('--down') - args.append('/etc/leap/resolv-update') - args.append('--plugin') - args.append('/usr/lib/openvpn/openvpn-down-root.so') - args.append("'script_type=down /etc/leap/resolv-update'") - - # certs - # XXX get values from specs? - args.append('--cert') - args.append(os.path.join( - self.home, - '.config', 'leap', 'providers', - '%s' % self.provider, - 'keys', 'client', - 'openvpn.pem')) - args.append('--key') - args.append(os.path.join( - self.home, - '.config', 'leap', 'providers', - '%s' % self.provider, - 'keys', 'client', - 'openvpn.pem')) - args.append('--ca') - args.append(os.path.join( - self.home, - '.config', 'leap', 'providers', - '%s' % self.provider, - 'keys', 'ca', - 'cacert.pem')) - return args - - # build command string - # these tests are going to have to check - # many combinations. we should inject some - # params in the function call, to disable - # some checks. - - def test_get_eip_gateway(self): - self.write_sample_eipconfig() - eipconf = eipconfig.EIPConfig(domain=self.provider) - - # default eipservice - self.write_sample_eipservice() - eipsconf = eipconfig.EIPServiceConfig(domain=self.provider) - - gateway = eipconfig.get_eip_gateway( - eipconfig=eipconf, - eipserviceconfig=eipsconf) - - # in spec is local gateway by default - self.assertEqual(gateway, '127.0.0.1') - - # change eipservice - # right now we only check that cluster == selected primary gw in - # eip.json, and pick first matching ip - eipconf._config.config['primary_gateway'] = "foo_provider" - newgateways = [{"cluster": "foo_provider", - "ip_address": "127.0.0.99"}] - self.write_sample_eipservice(gateways=newgateways) - eipsconf = eipconfig.EIPServiceConfig(domain=self.provider) - # load from disk file - eipsconf.load() - - gateway = eipconfig.get_eip_gateway( - eipconfig=eipconf, - eipserviceconfig=eipsconf) - self.assertEqual(gateway, '127.0.0.99') - - # change eipservice, several gateways - # right now we only check that cluster == selected primary gw in - # eip.json, and pick first matching ip - eipconf._config.config['primary_gateway'] = "bar_provider" - newgateways = [{"cluster": "foo_provider", - "ip_address": "127.0.0.99"}, - {'cluster': "bar_provider", - "ip_address": "127.0.0.88"}] - self.write_sample_eipservice(gateways=newgateways) - eipsconf = eipconfig.EIPServiceConfig(domain=self.provider) - # load from disk file - eipsconf.load() - - gateway = eipconfig.get_eip_gateway( - eipconfig=eipconf, - eipserviceconfig=eipsconf) - self.assertEqual(gateway, '127.0.0.88') - - def test_build_ovpn_command_empty_config(self): - self.touch_exec() - self.write_sample_eipservice() - self.write_sample_eipconfig() - - from leap.eip import config as eipconfig - from leap.util.fileutil import which - path = os.environ['PATH'] - vpnbin = which('openvpn', path=path) - #print 'path =', path - #print 'vpnbin = ', vpnbin - vpncommand, vpnargs = eipconfig.build_ovpn_command( - do_pkexec_check=False, vpnbin=vpnbin, - socket_path="/tmp/test.socket", - provider=self.provider) - self.assertEqual(vpncommand, self.home + '/bin/openvpn') - self.assertEqual(vpnargs, self.get_expected_openvpn_args()) - - def test_build_ovpn_command_openvpnoptions(self): - self.touch_exec() - - from leap.eip import config as eipconfig - from leap.util.fileutil import which - path = os.environ['PATH'] - vpnbin = which('openvpn', path=path) - - self.write_sample_eipconfig() - - # regular run, everything normal - self.write_sample_eipservice(vpnciphers=True) - vpncommand, vpnargs = eipconfig.build_ovpn_command( - do_pkexec_check=False, vpnbin=vpnbin, - socket_path="/tmp/test.socket", - provider=self.provider) - self.assertEqual(vpncommand, self.home + '/bin/openvpn') - expected = self.get_expected_openvpn_args( - with_openvpn_ciphers=True) - self.assertEqual(vpnargs, expected) - - # bad options -- illegal options - self.write_sample_eipservice( - vpnciphers=True, - # WE ONLY ALLOW vpn options in auth, cipher, tls-cipher - extra_vpnopts={"notallowedconfig": "badvalue"}) - vpncommand, vpnargs = eipconfig.build_ovpn_command( - do_pkexec_check=False, vpnbin=vpnbin, - socket_path="/tmp/test.socket", - provider=self.provider) - self.assertEqual(vpncommand, self.home + '/bin/openvpn') - expected = self.get_expected_openvpn_args( - with_openvpn_ciphers=True) - self.assertEqual(vpnargs, expected) - - # bad options -- illegal chars - self.write_sample_eipservice( - vpnciphers=True, - # WE ONLY ALLOW A-Z09\- - extra_vpnopts={"cipher": "AES-128-CBC;FOOTHING"}) - vpncommand, vpnargs = eipconfig.build_ovpn_command( - do_pkexec_check=False, vpnbin=vpnbin, - socket_path="/tmp/test.socket", - provider=self.provider) - self.assertEqual(vpncommand, self.home + '/bin/openvpn') - expected = self.get_expected_openvpn_args( - with_openvpn_ciphers=True) - self.assertEqual(vpnargs, expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/eip/tests/test_eipconnection.py b/src/leap/eip/tests/test_eipconnection.py deleted file mode 100644 index 163f8d45..00000000 --- a/src/leap/eip/tests/test_eipconnection.py +++ /dev/null @@ -1,216 +0,0 @@ -import glob -import logging -import platform -#import os -import shutil - -logging.basicConfig() -logger = logging.getLogger(name=__name__) - -try: - import unittest2 as unittest -except ImportError: - import unittest - -from mock import Mock, patch # MagicMock - -from leap.eip.eipconnection import EIPConnection -from leap.eip.exceptions import ConnectionRefusedError -from leap.eip import specs as eipspecs -from leap.testing.basetest import BaseLeapTest - -_system = platform.system() - -PROVIDER = "testprovider.example.org" - - -class NotImplementedError(Exception): - pass - - -@patch('OpenVPNConnection._get_or_create_config') -@patch('OpenVPNConnection._set_ovpn_command') -class MockedEIPConnection(EIPConnection): - - def _set_ovpn_command(self): - self.command = "mock_command" - self.args = [1, 2, 3] - - -class EIPConductorTest(BaseLeapTest): - - __name__ = "eip_conductor_tests" - provider = PROVIDER - - def setUp(self): - # XXX there's a conceptual/design - # mistake here. - # If we're testing just attrs after init, - # init shold not be doing so much side effects. - - # for instance: - # We have to TOUCH a keys file because - # we're triggerig the key checks FROM - # the constructor. me not like that, - # key checker should better be called explicitelly. - - # XXX change to keys_checker invocation - # (see config_checker) - - keyfiles = (eipspecs.provider_ca_path(domain=self.provider), - eipspecs.client_cert_path(domain=self.provider)) - for filepath in keyfiles: - self.touch(filepath) - self.chmod600(filepath) - - # we init the manager with only - # some methods mocked - self.manager = Mock(name="openvpnmanager_mock") - self.con = MockedEIPConnection() - self.con.provider = self.provider - - # XXX watch out. This sometimes is throwing the following error: - # NoSuchProcess: process no longer exists (pid=6571) - # because of a bad implementation of _check_if_running_instance - - self.con.run_openvpn_checks() - - def tearDown(self): - pass - - def doCleanups(self): - super(BaseLeapTest, self).doCleanups() - self.cleanupSocketDir() - del self.con - - def cleanupSocketDir(self): - ptt = ('/tmp/leap-tmp*') - for tmpdir in glob.glob(ptt): - shutil.rmtree(tmpdir) - - # - # tests - # - - def test_vpnconnection_defaults(self): - """ - default attrs as expected - """ - con = self.con - self.assertEqual(con.autostart, True) - # XXX moar! - - def test_ovpn_command(self): - """ - set_ovpn_command called - """ - self.assertEqual(self.con.command, - "mock_command") - self.assertEqual(self.con.args, - [1, 2, 3]) - - # config checks - - def test_config_checked_called(self): - # XXX this single test is taking half of the time - # needed to run tests. (roughly 3 secs for this only) - # We should modularize and inject Mocks on more places. - - oldcon = self.con - del(self.con) - config_checker = Mock() - self.con = MockedEIPConnection(config_checker=config_checker) - self.assertTrue(config_checker.called) - self.con.run_checks() - self.con.config_checker.run_all.assert_called_with( - skip_download=False) - - # XXX test for cert_checker also - self.con = oldcon - - # connect/disconnect calls - - def test_disconnect(self): - """ - disconnect method calls private and changes status - """ - self.con._disconnect = Mock( - name="_disconnect") - - # first we set status to connected - self.con.status.set_current(self.con.status.CONNECTED) - self.assertEqual(self.con.status.current, - self.con.status.CONNECTED) - - # disconnect - self.con.terminate_openvpn_connection = Mock() - self.con.disconnect() - self.con.terminate_openvpn_connection.assert_called_once_with( - shutdown=False) - self.con.terminate_openvpn_connection = Mock() - self.con.disconnect(shutdown=True) - self.con.terminate_openvpn_connection.assert_called_once_with( - shutdown=True) - - # new status should be disconnected - # XXX this should evolve and check no errors - # during disconnection - self.assertEqual(self.con.status.current, - self.con.status.DISCONNECTED) - - def test_connect(self): - """ - connect calls _launch_openvpn private - """ - self.con._launch_openvpn = Mock() - self.con.connect() - self.con._launch_openvpn.assert_called_once_with() - - # XXX tests breaking here ... - - def test_good_poll_connection_state(self): - """ - """ - #@patch -- - # self.manager.get_connection_state - - #XXX review this set of poll_state tests - #they SHOULD NOT NEED TO MOCK ANYTHING IN THE - #lower layers!! -- status, vpn_manager.. - #right now we're testing implementation, not - #behavior!!! - good_state = ["1345466946", "unknown_state", "ok", - "192.168.1.1", "192.168.1.100"] - self.con.get_connection_state = Mock(return_value=good_state) - self.con.status.set_vpn_state = Mock() - - state = self.con.poll_connection_state() - good_state[1] = "disconnected" - final_state = tuple(good_state) - self.con.status.set_vpn_state.assert_called_with("unknown_state") - self.assertEqual(state, final_state) - - # TODO between "good" and "bad" (exception raised) cases, - # we can still test for malformed states and see that only good - # states do have a change (and from only the expected transition - # states). - - def test_bad_poll_connection_state(self): - """ - get connection state raises ConnectionRefusedError - state is None - """ - self.con.get_connection_state = Mock( - side_effect=ConnectionRefusedError('foo!')) - state = self.con.poll_connection_state() - self.assertEqual(state, None) - - - # XXX more things to test: - # - called config routines during initz. - # - raising proper exceptions with no config - # - called proper checks on config / permissions - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/eip/tests/test_openvpnconnection.py b/src/leap/eip/tests/test_openvpnconnection.py deleted file mode 100644 index 95bfb2f0..00000000 --- a/src/leap/eip/tests/test_openvpnconnection.py +++ /dev/null @@ -1,161 +0,0 @@ -import logging -import os -import platform -import psutil -import shutil -#import socket - -logging.basicConfig() -logger = logging.getLogger(name=__name__) - -try: - import unittest2 as unittest -except ImportError: - import unittest - -from mock import Mock, patch # MagicMock - -from leap.eip import config as eipconfig -from leap.eip import openvpnconnection -from leap.eip import exceptions as eipexceptions -from leap.eip.udstelnet import UDSTelnet -from leap.testing.basetest import BaseLeapTest - -_system = platform.system() - - -class NotImplementedError(Exception): - pass - - -mock_UDSTelnet = Mock(spec=UDSTelnet) -# XXX cautious!!! -# this might be fragile right now (counting a global -# reference of calls I think. -# investigate this other form instead: -# http://www.voidspace.org.uk/python/mock/patch.html#start-and-stop - -# XXX redo after merge-refactor - - -@patch('openvpnconnection.OpenVPNConnection.connect_to_management') -class MockedOpenVPNConnection(openvpnconnection.OpenVPNConnection): - def __init__(self, *args, **kwargs): - self.mock_UDSTelnet = Mock() - super(MockedOpenVPNConnection, self).__init__( - *args, **kwargs) - self.tn = self.mock_UDSTelnet(self.host, self.port) - - def connect_to_management(self): - #print 'patched connect' - self.tn = mock_UDSTelnet(self.host, port=self.port) - - -class OpenVPNConnectionTest(BaseLeapTest): - - __name__ = "vpnconnection_tests" - - def setUp(self): - # XXX this will have to change for win, host=localhost - host = eipconfig.get_socket_path() - self.host = host - self.manager = MockedOpenVPNConnection(host=host) - - def tearDown(self): - pass - - def doCleanups(self): - super(BaseLeapTest, self).doCleanups() - self.cleanupSocketDir() - - def cleanupSocketDir(self): - # remove the socket folder. - # XXX only if posix. in win, host is localhost, so nothing - # has to be done. - if self.host: - folder, fpath = os.path.split(self.host) - try: - assert folder.startswith('/tmp/leap-tmp') # safety check - shutil.rmtree(folder) - except: - self.fail("could not remove temp file") - - del self.manager - - # - # tests - # - - def test_detect_vpn(self): - # XXX review, not sure if captured all the logic - # while fixing. kali. - openvpn_connection = openvpnconnection.OpenVPNConnection() - - with patch.object(psutil, "process_iter") as mocked_psutil: - mocked_process = Mock() - mocked_process.name = "openvpn" - mocked_process.cmdline = ["openvpn", "-foo", "-bar", "-gaaz"] - mocked_psutil.return_value = [mocked_process] - with self.assertRaises(eipexceptions.OpenVPNAlreadyRunning): - openvpn_connection._check_if_running_instance() - - openvpn_connection._check_if_running_instance() - - @unittest.skipIf(_system == "Windows", "lin/mac only") - def test_lin_mac_default_init(self): - """ - check default host for management iface - """ - self.assertTrue(self.manager.host.startswith('/tmp/leap-tmp')) - self.assertEqual(self.manager.port, 'unix') - - @unittest.skipUnless(_system == "Windows", "win only") - def test_win_default_init(self): - """ - check default host for management iface - """ - # XXX should we make the platform specific switch - # here or in the vpn command string building? - self.assertEqual(self.manager.host, 'localhost') - self.assertEqual(self.manager.port, 7777) - - def test_port_types_init(self): - oldmanager = self.manager - self.manager = MockedOpenVPNConnection(port="42") - self.assertEqual(self.manager.port, 42) - self.manager = MockedOpenVPNConnection() - self.assertEqual(self.manager.port, "unix") - self.manager = MockedOpenVPNConnection(port="bad") - self.assertEqual(self.manager.port, None) - self.manager = oldmanager - - def test_uds_telnet_called_on_connect(self): - self.manager.connect_to_management() - mock_UDSTelnet.assert_called_with( - self.manager.host, - port=self.manager.port) - - @unittest.skip - def test_connect(self): - raise NotImplementedError - # XXX calls close - # calls UDSTelnet mock. - - # XXX - # tests to write: - # UDSTelnetTest (for real?) - # HAVE A LOOK AT CORE TESTS FOR TELNETLIB. - # very illustrative instead... - - # - raise MissingSocket - # - raise ConnectionRefusedError - # - test send command - # - tries connect - # - ... tries? - # - ... calls _seek_to_eof - # - ... read_until --> return value - # - ... - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/eip/udstelnet.py b/src/leap/eip/udstelnet.py deleted file mode 100644 index 18e927c2..00000000 --- a/src/leap/eip/udstelnet.py +++ /dev/null @@ -1,38 +0,0 @@ -import os -import socket -import telnetlib - -from leap.eip import exceptions as eip_exceptions - - -class UDSTelnet(telnetlib.Telnet): - """ - a telnet-alike class, that can listen - on unix domain sockets - """ - - def open(self, host, port=23, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): - """Connect to a host. If port is 'unix', it - will open a connection over unix docmain sockets. - - The optional second argument is the port number, which - defaults to the standard telnet port (23). - - Don't try to reopen an already connected instance. - """ - self.eof = 0 - self.host = host - self.port = port - self.timeout = timeout - - if self.port == "unix": - # unix sockets spoken - if not os.path.exists(self.host): - raise eip_exceptions.MissingSocketError - self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - try: - self.sock.connect(self.host) - except socket.error: - raise eip_exceptions.ConnectionRefusedError - else: - self.sock = socket.create_connection((host, port), timeout) |