summaryrefslogtreecommitdiff
path: root/src/leap/eip/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/eip/config.py')
-rw-r--r--src/leap/eip/config.py552
1 files changed, 262 insertions, 290 deletions
diff --git a/src/leap/eip/config.py b/src/leap/eip/config.py
index 6118c9de..917871da 100644
--- a/src/leap/eip/config.py
+++ b/src/leap/eip/config.py
@@ -1,186 +1,153 @@
-import ConfigParser
-import grp
import logging
import os
import platform
-import socket
+import re
+import tempfile
-from leap.util.fileutil import (which, mkdir_p,
- check_and_fix_urw_only)
+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__)
-logger.setLevel('DEBUG')
-
-# XXX move exceptions to
-# from leap.eip import exceptions as eip_exceptions
-
-
-class EIPNoPkexecAvailable(Exception):
- pass
-
+provider_ca_file = BRANDING.get('provider_ca_file', None)
-class EIPNoPolkitAuthAgentAvailable(Exception):
- pass
+_platform = platform.system()
-class EIPInitNoProviderError(Exception):
- pass
+class EIPConfig(baseconfig.JSONLeapConfig):
+ spec = eipspecs.eipconfig_spec
+ def _get_slug(self):
+ eipjsonpath = baseconfig.get_config_file(
+ 'eip.json')
+ return eipjsonpath
-class EIPInitBadProviderError(Exception):
- pass
+ def _set_slug(self, *args, **kwargs):
+ raise AttributeError("you cannot set slug")
+ slug = property(_get_slug, _set_slug)
-class EIPInitNoKeyFileError(Exception):
- pass
+class EIPServiceConfig(baseconfig.JSONLeapConfig):
+ spec = eipspecs.eipservice_config_spec
-class EIPInitBadKeyFilePermError(Exception):
- pass
+ 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")
-OPENVPN_CONFIG_TEMPLATE = """#Autogenerated by eip-client wizard
-remote {VPN_REMOTE_HOST} {VPN_REMOTE_PORT}
+ slug = property(_get_slug, _set_slug)
-client
-dev tun
-persist-tun
-persist-key
-proto udp
-tls-client
-remote-cert-tls server
-cert {LEAP_EIP_KEYS}
-key {LEAP_EIP_KEYS}
-ca {LEAP_EIP_KEYS}
-"""
+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_config_dir():
+def get_eip_gateway(eipconfig=None, eipserviceconfig=None):
"""
- get the base dir for all leap config
- @rparam: config path
- @rtype: string
+ return the first host in eip service config
+ that matches the name defined in the eip.json config
+ file.
"""
- # TODO
- # check for $XDG_CONFIG_HOME var?
- # get a more sensible path for win/mac
- # kclair: opinion? ^^
- return os.path.expanduser(
- os.path.join('~',
- '.config',
- 'leap'))
-
-
-def get_config_file(filename, folder=None):
+ # 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):
"""
- concatenates the given filename
- with leap config dir.
- @param filename: name of the file
- @type filename: string
- @rparam: full path to config file
+ gathers optional cipher options from eip-service config.
+ :param eipserviceconfig: EIPServiceConfig instance
"""
- path = []
- path.append(get_config_dir())
- if folder is not None:
- path.append(folder)
- path.append(filename)
- return os.path.join(*path)
+ 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
-def get_default_provider_path():
- default_subpath = os.path.join("providers",
- "default")
- default_provider_path = get_config_file(
- '',
- folder=default_subpath)
- return default_provider_path
+LINUX_UP_DOWN_SCRIPT = "/etc/leap/resolv-update"
+OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-down-root.so"
-def validate_ip(ip_str):
+def has_updown_scripts():
"""
- raises exception if the ip_str is
- not a valid representation of an ip
+ checks the existence of the up/down scripts
"""
- socket.inet_aton(ip_str)
+ # 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 check_or_create_default_vpnconf(config):
- """
- checks that a vpn config file
- exists for a default provider,
- or creates one if it does not.
- ATM REQURES A [provider] section in
- eip.cfg with _at least_ a remote_ip value
- """
- default_provider_path = get_default_provider_path()
-
- if not os.path.isdir(default_provider_path):
- mkdir_p(default_provider_path)
-
- conf_file = get_config_file(
- 'openvpn.conf',
- folder=default_provider_path)
-
- if os.path.isfile(conf_file):
- return
- else:
- logger.debug(
- 'missing default openvpn config\n'
- 'creating one...')
-
- # We're getting provider from eip.cfg
- # by now. Get it from a list of gateways
- # instead.
-
- try:
- remote_ip = config.get('provider',
- 'remote_ip')
- validate_ip(remote_ip)
-
- except ConfigParser.NoOptionError:
- raise EIPInitNoProviderError
-
- except socket.error:
- # this does not look like an ip, dave
- raise EIPInitBadProviderError
-
- if config.has_option('provider', 'remote_port'):
- remote_port = config.get('provider',
- 'remote_port')
- else:
- remote_port = 1194
-
- default_subpath = os.path.join("providers",
- "default")
- default_provider_path = get_config_file(
- '',
- folder=default_subpath)
-
- if not os.path.isdir(default_provider_path):
- mkdir_p(default_provider_path)
-
- conf_file = get_config_file(
- 'openvpn.conf',
- folder=default_provider_path)
-
- # XXX keys have to be manually placed by now
- keys_file = get_config_file(
- 'openvpn.keys',
- folder=default_provider_path)
-
- ovpn_config = OPENVPN_CONFIG_TEMPLATE.format(
- VPN_REMOTE_HOST=remote_ip,
- VPN_REMOTE_PORT=remote_port,
- LEAP_EIP_KEYS=keys_file)
-
- with open(conf_file, 'wb') as f:
- f.write(ovpn_config)
-
-
-def build_ovpn_options(daemon=False):
+def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
"""
build a list of options
to be passed in the
@@ -195,17 +162,57 @@ def build_ovpn_options(daemon=False):
# 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 = os.getlogin()
- gid = os.getgroups()[-1]
- group = grp.getgrgid(gid).gr_name
+ user = baseconfig.get_username()
+ group = baseconfig.get_groupname()
opts = []
- #moved to config files
- #opts.append('--persist-tun')
- #opts.append('--persist-key')
+ 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')
@@ -221,30 +228,44 @@ def build_ovpn_options(daemon=False):
# interface. unix sockets or telnet interface for win.
# XXX take them from the config object.
- ourplatform = platform.system()
- if ourplatform in ("Linux", "Mac"):
- opts.append('--management')
- opts.append('/tmp/.eip.sock')
- opts.append('unix')
- if ourplatform == "Windows":
+ if _platform == "Windows":
opts.append('--management')
opts.append('localhost')
# XXX which is a good choice?
opts.append('7777')
- # remaining config options will go in a file
-
- # NOTE: we will build this file from
- # the service definition file.
- # XXX override from --with-openvpn-config
+ if _platform in ("Linux", "Darwin"):
+ opts.append('--management')
- opts.append('--config')
+ if socket_path is None:
+ socket_path = get_socket_path()
+ opts.append(socket_path)
+ opts.append('unix')
- default_provider_path = get_default_provider_path()
- ovpncnf = get_config_file(
- 'openvpn.conf',
- folder=default_provider_path)
- opts.append(ovpncnf)
+ 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.
@@ -252,17 +273,16 @@ def build_ovpn_options(daemon=False):
#if daemon is True:
#opts.append('--daemon')
+ logger.debug('vpn options: %s', ' '.join(opts))
return opts
-def build_ovpn_command(config, debug=False):
+def build_ovpn_command(debug=False, do_pkexec_check=True, vpnbin=None,
+ socket_path=None, **kwargs):
"""
build a string with the
complete openvpn invocation
- @param config: config object
- @type config: ConfigParser instance
-
@rtype [string, [list of strings]]
@rparam: a list containing the command string
and a list of options.
@@ -271,18 +291,18 @@ def build_ovpn_command(config, debug=False):
use_pkexec = True
ovpn = None
- if config.has_option('openvpn', 'use_pkexec'):
- use_pkexec = config.get('openvpn', 'use_pkexec')
- if platform.system() == "Linux" and use_pkexec:
+ # XXX get use_pkexec from config instead.
+
+ if _platform == "Linux" and use_pkexec and do_pkexec_check:
- # XXX check for both pkexec (done)
+ # 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 EIPNoPkexecAvailable
+ raise eip_exceptions.EIPNoPkexecAvailable
if not is_auth_agent_running():
logger.warning(
@@ -290,101 +310,48 @@ def build_ovpn_command(config, debug=False):
"pkexec will use its own text "
"based authentication agent. "
"that's probably a bad idea")
- raise EIPNoPolkitAuthAgentAvailable
+ raise eip_exceptions.EIPNoPolkitAuthAgentAvailable
command.append('pkexec')
- if config.has_option('openvpn',
- 'openvpn_binary'):
- ovpn = config.get('openvpn',
- 'openvpn_binary')
- if not ovpn and config.has_option('DEFAULT',
- 'openvpn_binary'):
- ovpn = config.get('DEFAULT',
- 'openvpn_binary')
-
+ 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:
- command.append(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):
+ for opt in build_ovpn_options(daemon=daemon_mode, socket_path=socket_path,
+ **kwargs):
command.append(opt)
# XXX check len and raise proper error
- return [command[0], command[1:]]
-
-
-def get_sensible_defaults():
- """
- gathers a dict of sensible defaults,
- platform sensitive,
- to be used to initialize the config parser
- @rtype: dict
- @rparam: default options.
- """
-
- # this way we're passing a simple dict
- # that will initialize the configparser
- # and will get written to "DEFAULTS" section,
- # which is fine for now.
- # if we want to write to a particular section
- # we can better pass a tuple of triples
- # (('section1', 'foo', '23'),)
- # and config.set them
-
- defaults = dict()
- defaults['openvpn_binary'] = which('openvpn')
- defaults['autostart'] = 'true'
-
- # TODO
- # - management.
- return defaults
-
-
-def get_config(config_file=None):
- """
- temporary method for getting configs,
- mainly for early stage development process.
- in the future we will get preferences
- from the storage api
-
- @rtype: ConfigParser instance
- @rparam: a config object
- """
- # TODO
- # - refactor out common things and get
- # them to util/ or baseapp/
-
- defaults = get_sensible_defaults()
- config = ConfigParser.ConfigParser(defaults)
-
- if not config_file:
- fpath = get_config_file('eip.cfg')
- if not os.path.isfile(fpath):
- dpath, cfile = os.path.split(fpath)
- if not os.path.isdir(dpath):
- mkdir_p(dpath)
- with open(fpath, 'wb') as configfile:
- config.write(configfile)
- config_file = open(fpath)
-
- #TODO
- # - convert config_file to list;
- # look in places like /etc/leap/eip.cfg
- # for global settings.
- # - raise warnings/error if bad options.
-
- # at this point, the file should exist.
- # errors would have been raised above.
-
- config.readfp(config_file)
-
- return config
+ 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(config):
+def check_vpn_keys(provider=None):
"""
performs an existance and permission check
over the openvpn keys file.
@@ -392,35 +359,40 @@ def check_vpn_keys(config):
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)
- keyopt = ('provider', 'keyfile')
-
- # XXX at some point,
- # should separate between CA, provider cert
- # and our certificate.
- # make changes in the default provider template
- # accordingly.
-
- # get vpn keys
- if config.has_option(*keyopt):
- keyfile = config.get(*keyopt)
- else:
- keyfile = get_config_file(
- 'openvpn.keys',
- folder=get_default_provider_path())
- logger.debug('keyfile = %s', keyfile)
+ logger.debug('provider ca = %s', provider_ca)
+ logger.debug('client cert = %s', client_cert)
# if no keys, raise error.
- # should be catched by the ui and signal user.
+ # 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(keyfile):
+ if not os.path.isfile(client_cert):
logger.error('key file %s not found. aborting.',
- keyfile)
- raise EIPInitNoKeyFileError
-
- # check proper permission on keys
- # bad perms? try to fix them
- try:
- check_and_fix_urw_only(keyfile)
- except OSError:
- raise EIPInitBadKeyFilePermError
+ 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