diff options
Diffstat (limited to 'src/leap/eip/config.py')
-rw-r--r-- | src/leap/eip/config.py | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/src/leap/eip/config.py b/src/leap/eip/config.py new file mode 100644 index 00000000..917871da --- /dev/null +++ b/src/leap/eip/config.py @@ -0,0 +1,398 @@ +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 |