diff options
Diffstat (limited to 'pkg')
| -rwxr-xr-x | pkg/linux/bitmask-root | 554 | ||||
| -rwxr-xr-x | pkg/linux/resolv-update | 90 | ||||
| -rwxr-xr-x | pkg/linux/update-resolv-conf | 58 | ||||
| -rw-r--r-- | pkg/osx/Info.plist | 34 | ||||
| -rw-r--r-- | pkg/osx/bitmask.icns | bin | 0 -> 47303 bytes | |||
| -rw-r--r-- | pkg/requirements.pip | 2 | ||||
| -rwxr-xr-x | pkg/tuf/init.py | 102 | ||||
| -rwxr-xr-x | pkg/tuf/release.py | 114 | 
8 files changed, 471 insertions, 483 deletions
| diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 5367a31c..58f9a103 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -47,37 +47,27 @@ import traceback  cmdcheck = subprocess.check_output -## -## CONSTANTS -## +# +# CONSTANTS +#  VERSION = "1"  SCRIPT = "bitmask-root"  NAMESERVER = "10.42.0.1"  BITMASK_CHAIN = "bitmask" +BITMASK_CHAIN_NAT_OUT = "bitmask" +BITMASK_CHAIN_NAT_POST = "bitmask_postrouting"  IP = "/bin/ip"  IPTABLES = "/sbin/iptables"  IP6TABLES = "/sbin/ip6tables" -RESOLVCONF_SYSTEM_BIN = "/sbin/resolvconf" -RESOLVCONF_LEAP_BIN = "/usr/local/sbin/leap-resolvconf" -  OPENVPN_USER = "nobody"  OPENVPN_GROUP = "nogroup"  LEAPOPENVPN = "LEAPOPENVPN"  OPENVPN_SYSTEM_BIN = "/usr/sbin/openvpn"  # Debian location  OPENVPN_LEAP_BIN = "/usr/local/sbin/leap-openvpn"  # installed by bundle - -""" -The path to the script to update resolv.conf -""" -# XXX We have to check if we have a valid resolvconf, and use -# the old resolv-update if not. -LEAP_UPDATE_RESOLVCONF_FILE = "/etc/leap/update-resolv-conf" -LEAP_RESOLV_UPDATE = "/etc/leap/resolv-update" -  FIXED_FLAGS = [      "--setenv", "LEAPOPENVPN", "1",      "--nobind", @@ -133,9 +123,9 @@ if DEBUG:  syslog.openlog(SCRIPT) -## -## UTILITY -## +# +# UTILITY +#  def is_valid_address(value): @@ -150,37 +140,10 @@ def is_valid_address(value):          socket.inet_aton(value)          return True      except Exception: -        print("%s: ERROR: MALFORMED IP: %s!" % (SCRIPT, value)) +        log("%s: ERROR: MALFORMED IP: %s!" % (SCRIPT, value))          return False -def has_system_resolvconf(): -    """ -    Return True if resolvconf is found in the system. - -    :rtype: bool -    """ -    return os.path.isfile(RESOLVCONF) - - -def has_valid_update_resolvconf(): -    """ -    Return True if a valid update-resolv-conf script is found in the system. - -    :rtype: bool -    """ -    return os.path.isfile(LEAP_UPDATE_RESOLVCONF_FILE) - - -def has_valid_leap_resolv_update(): -    """ -    Return True if a valid resolv-update script is found in the system. - -    :rtype: bool -    """ -    return os.path.isfile(LEAP_RESOLV_UPDATE) - -  def split_list(_list, regex):      """      Split a list based on a regex: @@ -235,135 +198,6 @@ def get_process_list():      return filter(None, res) -class Daemon(object): -    """ -    A generic daemon class. -    """ -    def __init__(self, pidfile, stdin='/dev/null', -                 stdout='/dev/null', stderr='/dev/null'): -        self.stdin = stdin -        self.stdout = stdout -        self.stderr = stderr -        self.pidfile = pidfile - -    def daemonize(self): -        """ -        Do the UNIX double-fork magic, see Stevens' "Advanced -        Programming in the UNIX Environment" for details (ISBN 0201563177) -        http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 -        """ -        try: -            pid = os.fork() -            if pid > 0: -                # exit first parent -                sys.exit(0) -        except OSError, e: -            sys.stderr.write( -                "fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) -            sys.exit(1) - -        # decouple from parent environment -        os.chdir("/") -        os.setsid() -        os.umask(0) - -        # do second fork -        try: -            pid = os.fork() -            if pid > 0: -                # exit from second parent -                sys.exit(0) -        except OSError, e: -            sys.stderr.write( -                "fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) -            sys.exit(1) - -        # redirect standard file descriptors -        sys.stdout.flush() -        sys.stderr.flush() -        si = file(self.stdin, 'r') -        so = file(self.stdout, 'a+') -        se = file(self.stderr, 'a+', 0) -        os.dup2(si.fileno(), sys.stdin.fileno()) -        os.dup2(so.fileno(), sys.stdout.fileno()) -        os.dup2(se.fileno(), sys.stderr.fileno()) - -        # write pidfile -        atexit.register(self.delpid) -        pid = str(os.getpid()) -        file(self.pidfile, 'w+').write("%s\n" % pid) - -    def delpid(self): -        """ -        Delete the pidfile. -        """ -        os.remove(self.pidfile) - -    def start(self, *args): -        """ -        Start the daemon. -        """ -        # Check for a pidfile to see if the daemon already runs -        try: -            pf = file(self.pidfile, 'r') -            pid = int(pf.read().strip()) -            pf.close() -        except IOError: -            pid = None - -        if pid: -            message = "pidfile %s already exist. Daemon already running?\n" -            sys.stderr.write(message % self.pidfile) -            sys.exit(1) - -        # Start the daemon -        self.daemonize() -        self.run(args) - -    def stop(self): -        """ -        Stop the daemon. -        """ -        # Get the pid from the pidfile -        try: -            pf = file(self.pidfile, 'r') -            pid = int(pf.read().strip()) -            pf.close() -        except IOError: -            pid = None - -        if not pid: -            message = "pidfile %s does not exist. Daemon not running?\n" -            sys.stderr.write(message % self.pidfile) -            return  # not an error in a restart - -        # Try killing the daemon process -        try: -            while 1: -                os.kill(pid, signal.SIGTERM) -                time.sleep(0.1) -        except OSError, err: -            err = str(err) -            if err.find("No such process") > 0: -                if os.path.exists(self.pidfile): -                    os.remove(self.pidfile) -            else: -                print(str(err)) -                sys.exit(1) - -    def restart(self): -        """ -        Restart the daemon. -        """ -        self.stop() -        self.start() - -    def run(self): -        """ -        This should  be overridden by derived classes. -        """ - -  def run(command, *args, **options):      """      Run an external command. @@ -376,55 +210,85 @@ def run(command, *args, **options):        `detach`: If True, run in detached process.        `input`: If True, open command for writing stream to, returning the Popen                 object. +      `throw`: If True, raise an exception if there is an error instead +               of bailing.      """      parts = [command]      parts.extend(args) -    if TEST or DEBUG: -        print("%s run: %s " (SCRIPT, " ".join(parts))) +    debug("%s run: %s " % (SCRIPT, " ".join(parts)))      _check = options.get("check", True)      _detach = options.get("detach", False)      _input = options.get("input", False)      _exitcode = options.get("exitcode", False) +    _throw = options.get("throw", False) -    if not _check or _detach or _input: +    if not (_check or _throw) or _detach or _input:          if _input:              return subprocess.Popen(parts, stdin=subprocess.PIPE)          else: -            # XXX ok with return None ??              subprocess.Popen(parts) +            return None      else:          try:              devnull = open('/dev/null', 'w')              subprocess.check_call(parts, stdout=devnull, stderr=devnull)              return 0          except subprocess.CalledProcessError as exc: -            if DEBUG: -                logger.exception(exc)              if _exitcode: +                if exc.returncode != 1: +                    # 0 or 1 is to be expected, but anything else +                    # should be logged. +                    debug("ERROR: Could not run %s: %s" % +                          (exc.cmd, exc.output), exception=exc)                  return exc.returncode +            elif _throw: +                raise exc              else:                  bail("ERROR: Could not run %s: %s" % (exc.cmd, exc.output),                       exception=exc) -def bail(msg=None, exception=None): +def log(msg=None, exception=None, priority=syslog.LOG_INFO):      """ -    Abnormal exit. +    print and log warning message or exception.      :param msg: optional error message.      :type msg: str +    :param msg: optional exception. +    :type msg: Exception +    :param msg: syslog level +    :type msg: one of LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, +               LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG      """      if msg is not None:          print("%s: %s" % (SCRIPT, msg)) -        syslog.syslog(syslog.LOG_ERR, msg) +        syslog.syslog(priority, msg)      if exception is not None: -        traceback.print_exc() +        if TEST or DEBUG: +            traceback.print_exc() +        syslog.syslog(priority, traceback.format_exc()) + + +def debug(msg=None, exception=None): +    """ +    Just like log, but is skipped unless DEBUG. Use syslog.LOG_INFO +    even for debug messages (we don't want to miss them). +    """ +    if TEST or DEBUG: +        log(msg, exception) + + +def bail(msg=None, exception=None): +    """ +    abnormal exit. like log(), but exits with error status code. +    """ +    log(msg, exception)      exit(1) -## -## OPENVPN -## +# +# OPENVPN +#  def get_openvpn_bin(): @@ -457,21 +321,21 @@ def parse_openvpn_flags(args):                  if required_params:                      flag_params = flag[1:]                      if len(flag_params) != len(required_params): -                        print("%s: ERROR: not enough params for %s" % -                              (SCRIPT, flag_name)) +                        log("%s: ERROR: not enough params for %s" % +                            (SCRIPT, flag_name))                          return None                      for param, param_type in zip(flag_params, required_params):                          if PARAM_FORMATS[param_type](param):                              result.append(param)                          else: -                            print("%s: ERROR: Bad argument %s" % -                                  (SCRIPT, param)) +                            log("%s: ERROR: Bad argument %s" % +                                (SCRIPT, param))                              return None              else: -                print("WARNING: unrecognized openvpn flag %s" % flag_name) +                log("WARNING: unrecognized openvpn flag %s" % flag_name)          return result      except Exception as exc: -        print("%s: ERROR PARSING FLAGS: %s" % (SCRIPT, exc)) +        log("%s: ERROR PARSING FLAGS: %s" % (SCRIPT, exc))          if DEBUG:              logger.exception(exc)          return None @@ -490,8 +354,8 @@ def openvpn_start(args):          OPENVPN = get_openvpn_bin()          flags = [OPENVPN] + FIXED_FLAGS + openvpn_flags          if DEBUG: -            print("%s: running openvpn with flags:" % (SCRIPT,)) -            print(flags) +            log("%s: running openvpn with flags:" % (SCRIPT,)) +            log(flags)          # note: first argument to command is ignored, but customarily set to          # the command.          os.execv(OPENVPN, flags) @@ -516,84 +380,9 @@ def openvpn_stop(args):          pid = found_leap_openvpn[0][0]          os.kill(int(pid), signal.SIGTERM) -## -## DNS -## - - -def get_resolvconf_bin(): -    """ -    Return the path for either the system resolvconf or the one the -    bundle has put there. -    """ -    if os.path.isfile(RESOLVCONF_SYSTEM_BIN): -        return RESOLVCONF_SYSTEM_BIN - -    # the bundle option should be removed from the debian package. -    if os.path.isfile(RESOLVCONF_LEAP_BIN): -        return RESOLVCONF_LEAP_BIN - -RESOLVCONF = get_resolvconf_bin() - - -class NameserverSetter(Daemon): -    """ -    A daemon that will add leap nameserver inside the tunnel -    to the system `resolv.conf` -    """ - -    def run(self, *args): -        """ -        Run when daemonized. -        """ -        if args: -            ip_address = args[0] -            self.set_dns_nameserver(ip_address) - -    def set_dns_nameserver(self, ip_address): -        """ -        Add the tunnel DNS server to `resolv.conf` - -        :param ip_address: the ip to add to `resolv.conf` -        :type ip_address: str -        """ -        if os.path.isfile(RESOLVCONF): -            process = run(RESOLVCONF, "-a", "bitmask", input=True) -            process.communicate("nameserver %s\n" % ip_address) -        else: -            bail("ERROR: package openresolv or resolvconf not installed.") - -nameserver_setter = NameserverSetter('/tmp/leap-dns-up.pid') - - -class NameserverRestorer(Daemon): -    """ -    A daemon that will restore the previous nameservers. -    """ - -    def run(self, *args): -        """ -        Run when daemonized. -        """ -        self.restore_dns_nameserver() - -    def restore_dns_nameserver(self): -        """ -        Remove tunnel DNS server from `resolv.conf` -        """ -        if os.path.isfile(RESOLVCONF): -            run(RESOLVCONF, "-d", "bitmask") -        else: -            print("%s: ERROR: package openresolv " -                  "or resolvconf not installed." % -                  (SCRIPT,)) - -nameserver_restorer = NameserverRestorer('/tmp/leap-dns-down.pid') - - -## -## FIREWALL -## +# +# FIREWALL +#  def get_gateways(gateways): @@ -703,29 +492,63 @@ def ip6tables(*args, **options):      """      run_iptable_with_check(IP6TABLES, *args, **options) +# +# NOTE: these tests to see if a chain exists might incorrectly return false. +# This happens when there is an error in calling `iptables --list bitmask`. +# +# For this reason, when stopping the firewall, we do not trust the +# output of ipvx_chain_exists() but instead always attempt to delete +# the chain. +# + -def ipv4_chain_exists(table): +def ipv4_chain_exists(chain, table=None):      """ -    Check if a given chain exists. +    Check if a given chain exists. Only returns true if it actually exists, +    but might return false if it exists and iptables failed to run. -    :param table: the table to check against -    :type table: str +    :param chain: the chain to check against +    :type chain: str      :rtype: bool      """ -    code = run(IPTABLES, "--list", table, "--numeric", exitcode=True) -    return code == 0 +    if table is not None: +        code = run(IPTABLES, "-t", table, +                   "--list", chain, "--numeric", exitcode=True) +    else: +        code = run(IPTABLES, "--list", chain, "--numeric", exitcode=True) +    if code == 0: +        return True +    elif code == 1: +        return False +    else: +        log("ERROR: Could not determine state of iptable chain") +        return False -def ipv6_chain_exists(table): +def ipv6_chain_exists(chain):      """ -    Check if a given chain exists. +    see ipv4_chain_exists() -    :param table: the table to check against -    :type table: str +    :param chain: the chain to check against +    :type chain: str      :rtype: bool      """ -    code = run(IP6TABLES, "--list", table, "--numeric", exitcode=True) -    return code == 0 +    code = run(IP6TABLES, "--list", chain, "--numeric", exitcode=True) +    if code == 0: +        return True +    elif code == 1: +        return False +    else: +        log("ERROR: Could not determine state of iptable chain") +        return False + + +def enable_ip_forwarding(): +    """ +    ip_fowarding must be enabled for the firewall to work. +    """ +    with open('/proc/sys/net/ipv4/ip_forward', 'w') as f: +        f.write('1\n')  def firewall_start(args): @@ -740,29 +563,57 @@ def firewall_start(args):      local_network_ipv6 = get_local_network_ipv6(default_device)      gateways = get_gateways(args) -    # add custom chain "bitmask" to front of OUTPUT chain +    # add custom chain "bitmask" to front of OUTPUT chain for both +    # the 'filter' and the 'nat' tables.      if not ipv4_chain_exists(BITMASK_CHAIN):          ip4tables("--new-chain", BITMASK_CHAIN) +    if not ipv4_chain_exists(BITMASK_CHAIN_NAT_OUT, 'nat'): +        ip4tables("--table", "nat", "--new-chain", BITMASK_CHAIN_NAT_OUT) +    if not ipv4_chain_exists(BITMASK_CHAIN_NAT_POST, 'nat'): +        ip4tables("--table", "nat", "--new-chain", BITMASK_CHAIN_NAT_POST)      if not ipv6_chain_exists(BITMASK_CHAIN):          ip6tables("--new-chain", BITMASK_CHAIN) +    ip4tables("--table", "nat", "--insert", "OUTPUT", +              "--jump", BITMASK_CHAIN_NAT_OUT) +    ip4tables("--table", "nat", "--insert", "POSTROUTING", +              "--jump", BITMASK_CHAIN_NAT_POST)      iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN) -    # allow DNS over VPN -    for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]: -        ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", -                  "--dport", "53", "--destination", allowed_dns, -                  "--jump", "ACCEPT") - -    # block DNS requests to anyone but the service provider or localhost -    # (when we actually route ipv6, we will need DNS rules for it too) -    ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", -              "--jump", "REJECT") - -    # allow traffic to IPs on local network +    # route all ipv4 DNS over VPN +    # (note: NAT does not work with ipv6 until kernel 3.7) +    enable_ip_forwarding() +    # allow dns to localhost +    ip4tables("-t", "nat", "--append", BITMASK_CHAIN, "--protocol", "udp", +              "--dest", "127.0.1.1,127.0.0.1", "--dport", "53", +              "--jump", "ACCEPT") +    # rewrite all outgoing packets to use VPN DNS server +    # (DNS does sometimes use TCP!) +    ip4tables("-t", "nat", "--append", BITMASK_CHAIN_NAT_OUT, "-p", "udp", +              "--dport", "53", "--jump", "DNAT", "--to", NAMESERVER+":53") +    ip4tables("-t", "nat", "--append", BITMASK_CHAIN_NAT_OUT, "-p", "tcp", +              "--dport", "53", "--jump", "DNAT", "--to", NAMESERVER+":53") +    # enable masquerading, so that DNS packets rewritten by DNAT will +    # have the correct source IPs +    ip4tables("-t", "nat", "--append", BITMASK_CHAIN_NAT_POST, +              "--protocol", "udp", "--dport", "53", "--jump", "MASQUERADE") +    ip4tables("-t", "nat", "--append", BITMASK_CHAIN_NAT_POST, +              "--protocol", "tcp", "--dport", "53", "--jump", "MASQUERADE") + +    # allow local network traffic      if local_network_ipv4: +        # allow local network destinations          ip4tables("--append", BITMASK_CHAIN,                    "--destination", local_network_ipv4, "-o", default_device,                    "--jump", "ACCEPT") +        # allow local network sources for DNS +        # (required to allow local network DNS that gets rewritten by NAT +        #  to get passed through so that MASQUERADE can set correct source IP) +        ip4tables("--append", BITMASK_CHAIN, +                  "--source", local_network_ipv4, "-o", default_device, +                  "-p", "udp", "--dport", "53", "--jump", "ACCEPT") +        ip4tables("--append", BITMASK_CHAIN, +                  "--source", local_network_ipv4, "-o", default_device, +                  "-p", "tcp", "--dport", "53", "--jump", "ACCEPT")          # allow multicast Simple Service Discovery Protocol          ip4tables("--append", BITMASK_CHAIN,                    "--protocol", "udp", @@ -800,8 +651,7 @@ def firewall_start(args):                   "--log-level", "7")      # for now, ensure all other ipv6 packets get rejected (regardless of -    # device) -    # (not sure why, but "-p any" doesn't work) +    # device). not sure why, but "-p any" doesn't work.      ip6tables("--append", BITMASK_CHAIN, "-p", "tcp", "--jump", "REJECT")      ip6tables("--append", BITMASK_CHAIN, "-p", "udp", "--jump", "REJECT") @@ -812,19 +662,89 @@ def firewall_start(args):  def firewall_stop():      """ -    Stop the firewall. +    Stop the firewall. Because we really really always want the firewall to +    be stopped if at all possible, this function is cautious and contains a +    lot of trys and excepts. + +    If there were any problems, we raise an exception at the end. This allows +    the calling code to retry stopping the firewall. Stopping the firewall +    can fail if iptables is being run by another process (only one iptables +    command can be run at a time).      """ -    iptables("--delete", "OUTPUT", "--jump", BITMASK_CHAIN) -    if ipv4_chain_exists(BITMASK_CHAIN): -        ip4tables("--flush", BITMASK_CHAIN) -        ip4tables("--delete-chain", BITMASK_CHAIN) -    if ipv6_chain_exists(BITMASK_CHAIN): -        ip6tables("--flush", BITMASK_CHAIN) -        ip6tables("--delete-chain", BITMASK_CHAIN) +    ok = True -## -## MAIN -## +    # -t filter -D OUTPUT -j bitmask +    try: +        iptables("--delete", "OUTPUT", "--jump", BITMASK_CHAIN, throw=True) +    except subprocess.CalledProcessError as exc: +        debug("INFO: not able to remove bitmask firewall from OUTPUT chain " +              "(maybe it is already removed?)", exc) +        ok = False + +    # -t nat -D OUTPUT -j bitmask +    try: +        ip4tables("-t", "nat", "--delete", "OUTPUT", +                  "--jump", BITMASK_CHAIN_NAT_OUT, throw=True) +    except subprocess.CalledProcessError as exc: +        debug("INFO: not able to remove bitmask firewall from OUTPUT chain " +              "in 'nat' table (maybe it is already removed?)", exc) +        ok = False + +    # -t nat -D POSTROUTING -j bitmask_postrouting +    try: +        ip4tables("-t", "nat", "--delete", "POSTROUTING", +                  "--jump", BITMASK_CHAIN_NAT_POST, throw=True) +    except subprocess.CalledProcessError as exc: +        debug("INFO: not able to remove bitmask firewall from POSTROUTING " +              "chain in 'nat' table (maybe it is already removed?)", exc) +        ok = False + +    # -t filter --delete-chain bitmask +    try: +        ip4tables("--flush", BITMASK_CHAIN, throw=True) +        ip4tables("--delete-chain", BITMASK_CHAIN, throw=True) +    except subprocess.CalledProcessError as exc: +        debug("INFO: not able to flush and delete bitmask ipv4 firewall " +              "chain (maybe it is already destroyed?)", exc) +        ok = False + +    # -t nat --delete-chain bitmask +    try: +        ip4tables("-t", "nat", "--flush", BITMASK_CHAIN_NAT_OUT, throw=True) +        ip4tables("-t", "nat", "--delete-chain", +                  BITMASK_CHAIN_NAT_OUT, throw=True) +    except subprocess.CalledProcessError as exc: +        debug("INFO: not able to flush and delete bitmask ipv4 firewall " +              "chain in 'nat' table (maybe it is already destroyed?)", exc) +        ok = False + +    # -t nat --delete-chain bitmask_postrouting +    try: +        ip4tables("-t", "nat", "--flush", BITMASK_CHAIN_NAT_POST, throw=True) +        ip4tables("-t", "nat", "--delete-chain", +                  BITMASK_CHAIN_NAT_POST, throw=True) +    except subprocess.CalledProcessError as exc: +        debug("INFO: not able to flush and delete bitmask ipv4 firewall " +              "chain in 'nat' table (maybe it is already destroyed?)", exc) +        ok = False + +    # -t filter --delete-chain bitmask (ipv6) +    try: +        ip6tables("--flush", BITMASK_CHAIN, throw=True) +        ip6tables("--delete-chain", BITMASK_CHAIN, throw=True) +    except subprocess.CalledProcessError as exc: +        debug("INFO: not able to flush and delete bitmask ipv6 firewall " +              "chain (maybe it is already destroyed?)", exc) +        ok = False + +    if not (ok or ipv4_chain_exists or ipv6_chain_exists): +        raise Exception("firewall might still be left up. " +                        "Please try `firewall stop` again.") + + +# +# MAIN +#  def main(): @@ -858,23 +778,20 @@ def main():          elif command == "firewall_start":              try:                  firewall_start(args) -                nameserver_setter.start(NAMESERVER)              except Exception as ex:                  if not is_restart: -                    nameserver_restorer.start()                      firewall_stop()                  bail("ERROR: could not start firewall", ex)          elif command == "firewall_stop":              try:                  firewall_stop() -                nameserver_restorer.start()              except Exception as ex:                  bail("ERROR: could not stop firewall", ex)          elif command == "firewall_isup":              if ipv4_chain_exists(BITMASK_CHAIN): -                print("%s: INFO: bitmask firewall is up" % (SCRIPT,)) +                log("%s: INFO: bitmask firewall is up" % (SCRIPT,))              else:                  bail("INFO: bitmask firewall is down") @@ -884,8 +801,7 @@ def main():          bail("ERROR: No such command")  if __name__ == "__main__": -    if DEBUG: -        logger.debug(" ".join(sys.argv)) +    debug(" ".join(sys.argv))      main() -    print("%s: done" % (SCRIPT,)) +    log("done")      exit(0) diff --git a/pkg/linux/resolv-update b/pkg/linux/resolv-update deleted file mode 100755 index c308b788..00000000 --- a/pkg/linux/resolv-update +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash -# -# Parses options from openvpn to update resolv.conf -# -# The only way to enforce that a linux system will not leak DNS -# queries is to replace /etc/resolv.conf with a file that only -# has the DNS resolver specified by the VPN. -# -# That is what this script does. This is what resolvconf is for, -# but sadly it does not always work. -# -# Example envs set from openvpn: -# foreign_option_1='dhcp-option DNS 193.43.27.132' -# foreign_option_2='dhcp-option DNS 193.43.27.133' -# foreign_option_3='dhcp-option DOMAIN be.bnc.ch' -# - -function up() { - -  comment=$( -cat <<SETVAR -# -# This is a temporary resolv.conf set by the Bitmask in order to -# strictly enforce that DNS lookups are secured by the VPN. -# -# When Bitmask quits or the VPN connection it manages is dropped, -# this file will be replace with the regularly scheduled /etc/resolv.conf -# -# If you want custom entries to appear in this file while Bitmask is running, -# put them in /etc/leap/resolv-head or /etc/leap/resolv-tail. These files -# should only be writable by root. -# - -SETVAR -) - -  if [ -f /etc/leap/resolv-head ] ; then -    custom_head=$(cat /etc/leap/resolv-head) -  else -    custom_head="" -  fi - -  if [ -f /etc/leap/resolv-tail ] ; then -    custom_tail=$(cat /etc/leap/resolv-tail) -  else -    custom_tail="" -  fi - -  for optionname in ${!foreign_option_*} ; do -    option="${!optionname}" -    echo $option -    part1=$(echo "$option" | cut -d " " -f 1) -    if [ "$part1" == "dhcp-option" ] ; then -      part2=$(echo "$option" | cut -d " " -f 2) -      part3=$(echo "$option" | cut -d " " -f 3) -      if [ "$part2" == "DNS" ] ; then -        IF_DNS_NAMESERVERS="$IF_DNS_NAMESERVERS $part3" -      fi -      if [ "$part2" == "DOMAIN" ] ; then -        IF_DNS_SEARCH="$IF_DNS_SEARCH $part3" -      fi -    fi -  done -  R="" -  for SS in $IF_DNS_SEARCH ; do -          R="${R}search $SS -" -  done -  for NS in $IF_DNS_NAMESERVERS ; do -          R="${R}nameserver $NS -" -  done -  cp /etc/resolv.conf /etc/resolv.conf.bak -  echo "$comment -$custom_head -$R -$custom_tail" > /etc/resolv.conf -} - -function down() { -  if [ -f /etc/resolv.conf.bak ] ; then -    cat /etc/resolv.conf.bak > /etc/resolv.conf -    rm /etc/resolv.conf.bak -  fi -} - -case $script_type in -  up)   up   ;; -  down) down ;; -esac diff --git a/pkg/linux/update-resolv-conf b/pkg/linux/update-resolv-conf deleted file mode 100755 index 76c69413..00000000 --- a/pkg/linux/update-resolv-conf +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -#  -# Parses DHCP options from openvpn to update resolv.conf -# To use set as 'up' and 'down' script in your openvpn *.conf: -# up /etc/leap/update-resolv-conf -# down /etc/leap/update-resolv-conf -# -# Used snippets of resolvconf script by Thomas Hood and Chris Hanson. -# Licensed under the GNU GPL.  See /usr/share/common-licenses/GPL.  -#  -# Example envs set from openvpn: -# -#     foreign_option_1='dhcp-option DNS 193.43.27.132' -#     foreign_option_2='dhcp-option DNS 193.43.27.133' -#     foreign_option_3='dhcp-option DOMAIN be.bnc.ch' -# - -[ -x /sbin/resolvconf ] || exit 0 -[ "$script_type" ] || exit 0 -[ "$dev" ] || exit 0 - -split_into_parts() -{ -	part1="$1" -	part2="$2" -	part3="$3" -} - -case "$script_type" in -  up) -	NMSRVRS="" -	SRCHS="" -	for optionvarname in ${!foreign_option_*} ; do -		option="${!optionvarname}" -		echo "$option" -		split_into_parts $option -		if [ "$part1" = "dhcp-option" ] ; then -			if [ "$part2" = "DNS" ] ; then -				NMSRVRS="${NMSRVRS:+$NMSRVRS }$part3" -			elif [ "$part2" = "DOMAIN" ] ; then -				SRCHS="${SRCHS:+$SRCHS }$part3" -			fi -		fi -	done -	R="" -	[ "$SRCHS" ] && R="search $SRCHS -" -	for NS in $NMSRVRS ; do -        	R="${R}nameserver $NS -" -	done -	echo -n "$R" | /sbin/resolvconf -a "${dev}.openvpn" -	;; -  down) -	/sbin/resolvconf -d "${dev}.openvpn" -	;; -esac - diff --git a/pkg/osx/Info.plist b/pkg/osx/Info.plist index e90d920a..dc427c4a 100644 --- a/pkg/osx/Info.plist +++ b/pkg/osx/Info.plist @@ -2,21 +2,23 @@  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  <plist version="1.0">  <dict> -	<key>CFBundleDisplayName</key> -	<string>leap-client</string> -	<key>CFBundleExecutable</key> -	<string>MacOS/app</string> -	<key>CFBundleIconFile</key> -	<string>icon-windowed.icns</string> -	<key>CFBundleInfoDictionaryVersion</key> -	<string>6.0</string> -	<key>CFBundleName</key> -	<string>leap-client</string> -	<key>CFBundlePackageType</key> -	<string>APPL</string> -	<key>CFBundleShortVersionString</key> -	<string>1</string> -	<key>LSBackgroundOnly</key> -	<false/> +        <key>CFBundleDisplayName</key> +        <string>Bitmask</string> +        <key>CFBundleExecutable</key> +        <string>app</string> +        <key>CFBundleIconFile</key> +        <string>bitmask.icns</string> +        <key>CFBundleInfoDictionaryVersion</key> +        <string>6.0</string> +        <key>CFBundleName</key> +        <string>Bitmask</string> +        <key>CFBundlePackageType</key> +        <string>APPL</string> +        <key>CFBundleShortVersionString</key> +        <string>1</string> +        <key>LSBackgroundOnly</key> +        <false/> +        <key>CFBundleIdentifier</key> +        <string>se.leap.bitmask</string>  </dict>  </plist> diff --git a/pkg/osx/bitmask.icns b/pkg/osx/bitmask.icnsBinary files differ new file mode 100644 index 00000000..7cc3e752 --- /dev/null +++ b/pkg/osx/bitmask.icns diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 3d6b33a3..bf05aa28 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -19,6 +19,8 @@ python-daemon # this should not be needed for Windows.  keyring  zope.proxy +pyzmq +  leap.common>=0.3.7  leap.soledad.client>=0.5.0  leap.keymanager>=0.3.8 diff --git a/pkg/tuf/init.py b/pkg/tuf/init.py new file mode 100755 index 00000000..7300da0a --- /dev/null +++ b/pkg/tuf/init.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# init.py +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +""" +Tool to initialize a TUF repo. + +The keys can be generated with: +    openssl genrsa -des3 -out private.pem 4096 +The public key can be exported with: +    openssl rsa -in private.pem -outform PEM -pubout -out public.pem +""" + +import sys + +from tuf.repository_tool import create_new_repository +from tuf.repository_tool import import_rsa_privatekey_from_file +from tuf.repository_tool import import_rsa_publickey_from_file + + +def usage(): +    print ("Usage:  %s repo root_private_key root_pub_key targets_pub_key" +           " timestamp_pub_key") % (sys.argv[0],) + + +def main(): +    if len(sys.argv) < 6: +        usage() +        return + +    repo_path = sys.argv[1] +    root_priv_path = sys.argv[2] +    root_pub_path = sys.argv[3] +    targets_pub_path = sys.argv[4] +    timestamp_pub_path = sys.argv[5] +    repo = Repo(repo_path, root_priv_path) +    repo.build(root_pub_path, targets_pub_path, timestamp_pub_path) + +    print "%s/metadata.staged/root.json is ready" % (repo_path,) + + +class Repo(object): +    """ +    Repository builder class +    """ + +    def __init__(self, repo_path, key_path): +        """ +        Constructor + +        :param repo_path: path where the repo lives +        :type repo_path: str +        :param key_path: path where the private root key lives +        :type key_path: str +        """ +        self._repo_path = repo_path +        self._key = import_rsa_privatekey_from_file(key_path) + +    def build(self, root_pub_path, targets_pub_path, timestamp_pub_path): +        """ +        Create a new repo + +        :param root_pub_path: path where the public root key lives +        :type root_pub_path: str +        :param targets_pub_path: path where the public targets key lives +        :type targets_pub_path: str +        :param timestamp_pub_path: path where the public timestamp key lives +        :type timestamp_pub_path: str +        """ +        repository = create_new_repository(self._repo_path) + +        pub_root_key = import_rsa_publickey_from_file(root_pub_path) +        repository.root.add_verification_key(pub_root_key) +        repository.root.load_signing_key(self._key) + +        pub_target_key = import_rsa_publickey_from_file(targets_pub_path) +        repository.targets.add_verification_key(pub_target_key) +        repository.snapshot.add_verification_key(pub_target_key) +        repository.targets.compressions = ["gz"] +        repository.snapshot.compressions = ["gz"] + +        pub_timestamp_key = import_rsa_publickey_from_file(timestamp_pub_path) +        repository.timestamp.add_verification_key(pub_timestamp_key) + +        repository.write_partial() + + +if __name__ == "__main__": +    main() diff --git a/pkg/tuf/release.py b/pkg/tuf/release.py new file mode 100755 index 00000000..c4abcd0d --- /dev/null +++ b/pkg/tuf/release.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# release.py +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +""" +Tool to generate TUF related files after a release + +The 'repo' folder should contain two folders: +  - 'metadata.staged' with all the jsons from the previows release +  - 'targets' where the release targets are +""" + +import datetime +import os.path +import sys + +from tuf.repository_tool import load_repository +from tuf.repository_tool import import_rsa_privatekey_from_file +from tuf.repository_tool import import_rsa_publickey_from_file + +""" +Days until the expiration of targets.json and snapshot.json. After this ammount +of days the TUF client won't accept this files. +""" +EXPIRATION_DAYS = 90 + + +def usage(): +    print "Usage:  %s repo key" % (sys.argv[0],) + + +def main(): +    if len(sys.argv) < 3: +        usage() +        return + +    repo_path = sys.argv[1] +    key_path = sys.argv[2] +    targets = Targets(repo_path, key_path) +    targets.build() + +    print "%s/metadata.staged/(targets|snapshot).json[.gz] are ready" % \ +          (repo_path,) + + +class Targets(object): +    """ +    Targets builder class +    """ + +    def __init__(self, repo_path, key_path): +        """ +        Constructor + +        :param repo_path: path where the repo lives +        :type repo_path: str +        :param key_path: path where the private targets key lives +        :type key_path: str +        """ +        self._repo_path = repo_path +        self._key = import_rsa_privatekey_from_file(key_path) + +    def build(self): +        """ +        Generate snapshot.json[.gz] and targets.json[.gz] +        """ +        self._repo = load_repository(self._repo_path) +        self._load_targets() + +        self._repo.targets.load_signing_key(self._key) +        self._repo.snapshot.load_signing_key(self._key) +        self._repo.targets.compressions = ["gz"] +        self._repo.snapshot.compressions = ["gz"] +        self._repo.snapshot.expiration = ( +            datetime.datetime.now() + +            datetime.timedelta(days=EXPIRATION_DAYS)) +        self._repo.targets.expiration = ( +            datetime.datetime.now() + +            datetime.timedelta(days=EXPIRATION_DAYS)) +        self._repo.write_partial() + +    def _load_targets(self): +        """ +        Load a list of targets +        """ +        targets_path = os.path.join(self._repo_path, 'targets') +        target_list = self._repo.get_filepaths_in_directory( +            targets_path, +            recursive_walk=True, +            followlinks=True) + +        for target in target_list: +            octal_file_permissions = oct(os.stat(target).st_mode)[3:] +            custom_file_permissions = { +                'file_permissions': octal_file_permissions +            } +            self._repo.targets.add_target(target, custom_file_permissions) + + +if __name__ == "__main__": +    main() | 
