diff options
author | Tomás Touceda <chiiph@leap.se> | 2014-07-18 11:22:47 -0300 |
---|---|---|
committer | Tomás Touceda <chiiph@leap.se> | 2014-07-18 11:22:47 -0300 |
commit | 159dbe295148975bdfe9a50f871254aa9adf2328 (patch) | |
tree | 5b679b7f617c4bc09c25a4c369e77156f0ff3e2c /pkg | |
parent | 7858d83af4a09ab00f6ba33dd8dbcf07ade101ce (diff) | |
parent | 312746bc9b77f0f738ccf2192d81ab94fdf9d6ba (diff) |
Merge branch 'release-0.6.0'0.6.0
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.icns Binary files differnew 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() |