mirror of
				https://github.com/Rudd-O/qubes-network-server.git
				synced 2025-11-04 13:39:06 +01:00 
			
		
		
		
	Bump version and prepare to package.
This commit is contained in:
		
							parent
							
								
									4e522c56a7
								
							
						
					
					
						commit
						efdda01dd0
					
				
							
								
								
									
										1
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Makefile
									
									
									
									
									
								
							@ -20,3 +20,4 @@ srpm: dist
 | 
			
		||||
install:
 | 
			
		||||
	install -Dm 755 src/usr/bin/qvm-static-ip -t $(DESTDIR)/$(BINDIR)/
 | 
			
		||||
	install -Dm 644 src/usr/lib64/python2.7/site-packages/qubes/modules/*.py -t $(DESTDIR)/$(LIBDIR)/python2.7/site-packages/qubes/modules
 | 
			
		||||
	install -Dm 644 src/usr/lib64/python2.7/site-packages/qubes/modules/qubes-appvm-firewall -t $(DESTDIR)/$(LIBDIR)/python2.7/site-packages/qubes/modules
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
%define mybuildnumber %{?build_number}%{?!build_number:1}
 | 
			
		||||
 | 
			
		||||
Name:           qubes-network-server
 | 
			
		||||
Version:        0.0.6
 | 
			
		||||
Version:        0.0.7
 | 
			
		||||
Release:        %{mybuildnumber}%{?dist}
 | 
			
		||||
Summary:        Turn your Qubes OS into a network server
 | 
			
		||||
BuildArch:      noarch
 | 
			
		||||
@ -38,6 +38,7 @@ make install DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} LIBDIR=%{_libdir}
 | 
			
		||||
%files
 | 
			
		||||
%attr(0755, root, root) %{_bindir}/qvm-static-ip
 | 
			
		||||
%attr(0644, root, root) %{_libdir}/python2.7/site-packages/qubes/modules/*.py*
 | 
			
		||||
%attr(0644, root, root) %{_libdir}/python2.7/site-packages/qubes/modules/qubes-appvm-firewall
 | 
			
		||||
%doc README.md TODO
 | 
			
		||||
 | 
			
		||||
%changelog
 | 
			
		||||
 | 
			
		||||
@ -104,15 +104,13 @@ class QubesVm(OriginalQubesVm):
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def start(self, verbose = False, preparing_dvm = False, start_guid = True,
 | 
			
		||||
              notify_function = None, mem_required = None):
 | 
			
		||||
        if dry_run:
 | 
			
		||||
            return
 | 
			
		||||
        xid = OriginalQubesVm.start(self, verbose, preparing_dvm, start_guid, notify_function, mem_required)
 | 
			
		||||
        if not preparing_dvm:
 | 
			
		||||
            self.adjust_proxy_arp(verbose=verbose, notify_function=notify_function)
 | 
			
		||||
            self.adjust_own_firewall_rules()
 | 
			
		||||
        return xid
 | 
			
		||||
    def start_qrexec_daemon(self, verbose=False, notify_function=None):
 | 
			
		||||
        ret = OriginalQubesVm.start_qrexec_daemon(self, verbose=verbose, notify_function=notify_function)
 | 
			
		||||
        if self.type not in ['AppVM', 'HVM']:
 | 
			
		||||
            self.deploy_appvm_firewall(verbose=verbose, notify_function=notify_function)
 | 
			
		||||
        self.adjust_proxy_arp(verbose=verbose, notify_function=notify_function)
 | 
			
		||||
        self.adjust_own_firewall_rules()
 | 
			
		||||
        return ret
 | 
			
		||||
 | 
			
		||||
    def unpause(self):
 | 
			
		||||
        self.log.debug('unpause()')
 | 
			
		||||
@ -397,4 +395,42 @@ class QubesVm(OriginalQubesVm):
 | 
			
		||||
        finally:
 | 
			
		||||
            f.close()
 | 
			
		||||
 | 
			
		||||
    def deploy_appvm_firewall(self, verbose = False, notify_function=None):
 | 
			
		||||
        def n(msg):
 | 
			
		||||
            if notify_function:
 | 
			
		||||
                notify_function("info", msg)
 | 
			
		||||
            elif verbose:
 | 
			
		||||
                print >> sys.stderr, "-->", msg
 | 
			
		||||
 | 
			
		||||
        n("Deploying AppVM firewall...")
 | 
			
		||||
 | 
			
		||||
        appvm_firewall_path = os.path.join(
 | 
			
		||||
            os.path.dirname(__file__),
 | 
			
		||||
            "qubes-appvm-firewall"
 | 
			
		||||
        )
 | 
			
		||||
        pill = textwrap.dedent(
 | 
			
		||||
            """
 | 
			
		||||
            set -e
 | 
			
		||||
            tmp=$(mktemp)
 | 
			
		||||
            trap 'rm -f "$tmp"' EXIT
 | 
			
		||||
            cat > "$tmp" << "EOF"
 | 
			
		||||
            %s
 | 
			
		||||
            EOF
 | 
			
		||||
            chmod +x "$tmp"
 | 
			
		||||
            "$tmp" deploy
 | 
			
		||||
            """
 | 
			
		||||
        ) % open(appvm_firewall_path).read()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            p = self.run("bash", user="root", gui=False, wait=True, passio_popen=True, autostart=False)
 | 
			
		||||
            p.stdin.write(pill)
 | 
			
		||||
            p.stdin.close()
 | 
			
		||||
            out = p.stdout.read()
 | 
			
		||||
            retcode = p.wait()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            n("Could not deploy the AppVM firewall on the VM: %s" % e)
 | 
			
		||||
        if retcode != 0:
 | 
			
		||||
            n("Could not deploy the AppVM firewall on the VM (return status %s): %s" % (retcode, out))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_qubes_vm_class(QubesVm)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										217
									
								
								src/usr/lib64/python2.7/site-packages/qubes/modules/qubes-appvm-firewall
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										217
									
								
								src/usr/lib64/python2.7/site-packages/qubes/modules/qubes-appvm-firewall
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,217 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
import collections
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
UNITPATH = "/usr/lib/systemd/system/qubes-appvm-firewall.service"
 | 
			
		||||
DEPPATH = "/run/fortress/qubes-appvm-firewall"
 | 
			
		||||
KEY = '/qubes-fortress-iptables-rules'
 | 
			
		||||
CHAIN = 'FORTRESS-INPUT'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ReadError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def watch(key):
 | 
			
		||||
    subprocess.check_call(['qubesdb-watch', key])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read(key):
 | 
			
		||||
    try:
 | 
			
		||||
        return subprocess.check_output(['qubesdb-read', '-r', key])
 | 
			
		||||
    except subprocess.CalledProcessError as e:
 | 
			
		||||
        logging.error("error reading key %s: %s", key, e)
 | 
			
		||||
        raise ReadError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Table(object):
 | 
			
		||||
 | 
			
		||||
    header = None
 | 
			
		||||
    chains = None
 | 
			
		||||
    rules = None
 | 
			
		||||
    footer = None
 | 
			
		||||
    original_chains = None
 | 
			
		||||
    original_rules = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, text):
 | 
			
		||||
        lines = text.splitlines(True)
 | 
			
		||||
        self.header = ''
 | 
			
		||||
        self.chains = collections.OrderedDict()
 | 
			
		||||
        self.rules = []
 | 
			
		||||
        self.footer = ''
 | 
			
		||||
        mode = "header"
 | 
			
		||||
        for line in lines:
 | 
			
		||||
            if mode == "header":
 | 
			
		||||
                if line.startswith(":"):
 | 
			
		||||
                    self.chains.update([line[1:].split(" ", 1)])
 | 
			
		||||
                    mode = "chains"
 | 
			
		||||
                else:
 | 
			
		||||
                    self.header += line
 | 
			
		||||
            elif mode == "chains":
 | 
			
		||||
                if line.startswith("-"):
 | 
			
		||||
                    self.rules.append(line)
 | 
			
		||||
                    mode = "rules"
 | 
			
		||||
                else:
 | 
			
		||||
                    self.chains.update([line[1:].split(" ", 1)])
 | 
			
		||||
            elif mode == "rules":
 | 
			
		||||
                if line.startswith("COMMIT"):
 | 
			
		||||
                    self.footer += line
 | 
			
		||||
                    mode = "footer"
 | 
			
		||||
                else:
 | 
			
		||||
                    self.rules.append(line)
 | 
			
		||||
            else: # mode == "footer":
 | 
			
		||||
                self.footer += line
 | 
			
		||||
        self.original_chains = collections.OrderedDict(self.chains.items())
 | 
			
		||||
        self.original_rules = list(self.rules)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.render()
 | 
			
		||||
 | 
			
		||||
    def render(self, old=False):
 | 
			
		||||
        if old:
 | 
			
		||||
            chains = self.original_chains
 | 
			
		||||
            rules = self.original_rules
 | 
			
		||||
        else:
 | 
			
		||||
            chains = self.chains
 | 
			
		||||
            rules = self.rules
 | 
			
		||||
        return (
 | 
			
		||||
                    self.header
 | 
			
		||||
                    + "".join(":%s %s" % x for x in chains.items())
 | 
			
		||||
                    + "".join(rules)
 | 
			
		||||
                    + self.footer
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    def dirty(self):
 | 
			
		||||
        return self.render() != self.render(True)
 | 
			
		||||
 | 
			
		||||
    def ensure_chain_present(self, name):
 | 
			
		||||
        if name not in self.chains:
 | 
			
		||||
            logging.info("Adding chain %s", name)
 | 
			
		||||
            self.chains[name] = '- [0:0]\n'
 | 
			
		||||
 | 
			
		||||
    def clear_chain(self, name):
 | 
			
		||||
        for n, rule in reversed(list(enumerate(self.rules))):
 | 
			
		||||
            if rule.startswith("-A %s " % name):
 | 
			
		||||
                self.rules.pop(n)
 | 
			
		||||
 | 
			
		||||
    def add_rule(self, rule, after_rule):
 | 
			
		||||
        original_after_rule = after_rule
 | 
			
		||||
        if not rule.endswith("\n"):
 | 
			
		||||
            rule += "\n"
 | 
			
		||||
        if not after_rule.endswith("\n"):
 | 
			
		||||
            after_rule += "\n"
 | 
			
		||||
        inserted = False
 | 
			
		||||
        if rule in self.rules:
 | 
			
		||||
            return
 | 
			
		||||
        for n, exrule in enumerate(self.rules):
 | 
			
		||||
            if exrule == after_rule:
 | 
			
		||||
                logging.info("Inserting rule %s", rule.strip())
 | 
			
		||||
                self.rules.insert(n + 1, rule)
 | 
			
		||||
                inserted = True
 | 
			
		||||
                break
 | 
			
		||||
        if not inserted:
 | 
			
		||||
            logging.error("Could not insert rule %s", rule.strip())
 | 
			
		||||
            raise KeyError(original_after_rule)
 | 
			
		||||
 | 
			
		||||
    def replace_rules(self, chain, ruletext):
 | 
			
		||||
        for rule in ruletext.splitlines():
 | 
			
		||||
            if not rule.strip(): continue
 | 
			
		||||
            if not rule.startswith("-A %s " % chain):
 | 
			
		||||
                raise ValueError(
 | 
			
		||||
                    "rule %s is not for chain %s" % (
 | 
			
		||||
                        rule.strip(),
 | 
			
		||||
                        chain,
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
        self.ensure_chain_present(chain)
 | 
			
		||||
        self.clear_chain(chain)
 | 
			
		||||
        for rule in ruletext.splitlines():
 | 
			
		||||
            if rule.startswith("-A %s " % chain):
 | 
			
		||||
                self.rules.append(rule + "\n")
 | 
			
		||||
 | 
			
		||||
    def commit(self):
 | 
			
		||||
        if not self.dirty():
 | 
			
		||||
            return
 | 
			
		||||
        text = self.render()
 | 
			
		||||
        cmd = ['iptables-restore']
 | 
			
		||||
        p = subprocess.Popen(
 | 
			
		||||
            cmd,
 | 
			
		||||
            stdin=subprocess.PIPE,
 | 
			
		||||
            stdout=subprocess.PIPE,
 | 
			
		||||
            stderr=subprocess.STDOUT,
 | 
			
		||||
        )
 | 
			
		||||
        out, _ = p.communicate(text)
 | 
			
		||||
        w = p.wait()
 | 
			
		||||
        if w != 0:
 | 
			
		||||
            logging.error("Rule changes commit failed with status %s: %s", w, out)
 | 
			
		||||
            raise subprocess.CalledProcessError(w, cmd, out)
 | 
			
		||||
        self.original_chains = collections.OrderedDict(self.chains.items())
 | 
			
		||||
        self.original_rules = list(self.rules)
 | 
			
		||||
        logging.info("Rule changes committed")
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def filter_from_iptables(klass):
 | 
			
		||||
        r = subprocess.check_output(['iptables-save', '-t', 'filter'])
 | 
			
		||||
        t = klass(r)
 | 
			
		||||
        return t
 | 
			
		||||
 | 
			
		||||
def deploy():
 | 
			
		||||
    if not os.path.isdir(os.path.dirname(DEPPATH)):
 | 
			
		||||
        os.makedirs(os.path.dirname(DEPPATH))
 | 
			
		||||
    shutil.copyfile(__file__, DEPPATH)
 | 
			
		||||
    os.chmod(DEPPATH, 0755)
 | 
			
		||||
    service = '''[Unit]
 | 
			
		||||
Description=Qubes AppVM firewall updater
 | 
			
		||||
After=qubes-iptables.service qubes-firewall.service
 | 
			
		||||
Before=qubes-network.service network.target
 | 
			
		||||
 | 
			
		||||
[Service]
 | 
			
		||||
Type=simple
 | 
			
		||||
ExecStart=%s main
 | 
			
		||||
''' % DEPPATH
 | 
			
		||||
    if not os.path.isfile(UNITPATH) or open(UNITPATH, "rb").read() != service:
 | 
			
		||||
        open(UNITPATH, "wb").write(service)
 | 
			
		||||
        subprocess.check_call(['systemctl', '--system', 'daemon-reload'])
 | 
			
		||||
    subprocess.check_call(['systemctl', 'restart', os.path.basename(UNITPATH)])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    logging.basicConfig(level=logging.INFO)
 | 
			
		||||
    t = Table.filter_from_iptables()
 | 
			
		||||
    t.ensure_chain_present(CHAIN)
 | 
			
		||||
    t.add_rule('-A INPUT -j %s' % CHAIN, '-A INPUT -i lo -j ACCEPT')
 | 
			
		||||
    try:
 | 
			
		||||
        newrules = read(KEY)
 | 
			
		||||
        t.replace_rules(CHAIN, newrules)
 | 
			
		||||
    except ReadError:
 | 
			
		||||
        # Key may not exist at this time.
 | 
			
		||||
        logging.warning("Qubes DB key %s does not yet exist", KEY)
 | 
			
		||||
    t.commit()
 | 
			
		||||
    logging.info("Startup complete")
 | 
			
		||||
    while True:
 | 
			
		||||
        watch(KEY)
 | 
			
		||||
        try:
 | 
			
		||||
            newrules = read(KEY)
 | 
			
		||||
        except ReadError:
 | 
			
		||||
            # Key may have been deleted.
 | 
			
		||||
            logging.warning("Qubes DB key %s could not be read", KEY)
 | 
			
		||||
            continue
 | 
			
		||||
        logging.info("Rule changes detected")
 | 
			
		||||
        try:
 | 
			
		||||
            t.replace_rules(CHAIN, newrules)
 | 
			
		||||
            t.commit()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            logging.exception("Rule changes could not be committed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    cmd = sys.argv[1]
 | 
			
		||||
except IndexError:
 | 
			
		||||
    cmd = 'main'
 | 
			
		||||
cmd = locals()[cmd]
 | 
			
		||||
sys.exit(cmd())
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user