diff --git a/qubes-network-server.spec b/qubes-network-server.spec index 8b60494..2c26060 100644 --- a/qubes-network-server.spec +++ b/qubes-network-server.spec @@ -3,7 +3,7 @@ %define mybuildnumber %{?build_number}%{?!build_number:1} Name: qubes-network-server -Version: 0.1.0 +Version: 0.1.1 Release: %{mybuildnumber}%{?dist} Summary: Turn your Qubes OS into a network server BuildArch: noarch diff --git a/qubesroutingmanager/__init__.py b/qubesroutingmanager/__init__.py index 4a33adc..7e46176 100644 --- a/qubesroutingmanager/__init__.py +++ b/qubesroutingmanager/__init__.py @@ -65,9 +65,11 @@ ADDRESS_FAMILY_IPV6 = "ip6" ADDRESS_FAMILY_IPV4 = "ip" TABLE_NAME = "qubes" FORWARD_CHAIN_NAME = "forward" +POSTROUTING_CHAIN_NAME = "postrouting" ROUTING_MANAGER_CHAIN_NAME = "qubes-routing-manager" +ROUTING_MANAGER_POSTROUTING_CHAIN_NAME = "qubes-routing-manager-postrouting" NFTABLES_CMD = "nft" -ADD_RULE_AFTER_THIS_RULE = "custom-forward" +ADD_FORWARD_RULE_AFTER_THIS_RULE = "custom-forward" def get_table(address_family: ADDRESS_FAMILIES, table: str) -> NFTablesOutput: @@ -202,109 +204,157 @@ def setup_plain_forwarding_for_address(source: str, enable: bool, family: int) - forward_chain = [x for x in existing_chains if x["name"] == FORWARD_CHAIN_NAME][ 0 ] + postrouting_chain = [ + x for x in existing_chains if x["name"] == POSTROUTING_CHAIN_NAME + ][0] except IndexError: logging.warn( - "No forward chain in table %s, not setting up forwarding", TABLE_NAME + "No forward or postrouting chains in table %s, not setting up forwarding", + TABLE_NAME, ) return - qubes_routing_manager_chain: None | Chain = None - try: - qubes_routing_manager_chain = [ - x for x in existing_chains if x["name"] == ROUTING_MANAGER_CHAIN_NAME - ].pop() - except IndexError: - pass - - if not qubes_routing_manager_chain: - logging.info( - "Adding %s chain to table %s", ROUTING_MANAGER_CHAIN_NAME, TABLE_NAME - ) - add_chain(af, TABLE_NAME, ROUTING_MANAGER_CHAIN_NAME) - - qubes_routing_manager_rule: None | Rule = None - try: - qubes_routing_manager_rule = [ - x - for x in existing_rules - if x["chain"] == forward_chain["name"] - and x["family"] == af - and len(x["expr"]) == 1 - and x["expr"][0].get("jump", {}).get("target") == ROUTING_MANAGER_CHAIN_NAME - ].pop() - except IndexError: - pass - - if not qubes_routing_manager_rule: + for chain_name in [ + ROUTING_MANAGER_CHAIN_NAME, + ROUTING_MANAGER_POSTROUTING_CHAIN_NAME, + ]: + chain: None | Chain = None try: - custom_forwarding_rule = [ - x - for x in existing_rules - if x["chain"] == forward_chain["name"] - and len(x["expr"]) == 1 - and x["expr"][0].get("jump", {}).get("target") - == ADD_RULE_AFTER_THIS_RULE - ][0] + chain = [x for x in existing_chains if x["name"] == chain_name].pop() except IndexError: - logging.warn( - "No state forwarding rule in chain %s of table %s, not setting up forwarding", - forward_chain["name"], + pass + + if not chain: + logging.info( + "Adding %s chain to table %s and counter to chain", + chain_name, TABLE_NAME, ) - logging.info( - "Adding rule to jump to %s to table %s after jump to %s", - ROUTING_MANAGER_CHAIN_NAME, - TABLE_NAME, - ADD_RULE_AFTER_THIS_RULE, - ) - append_rule_after( - af, - TABLE_NAME, - forward_chain["name"], - custom_forwarding_rule["handle"], - "jump", - ROUTING_MANAGER_CHAIN_NAME, - ) - append_counter_at_end( - af, - TABLE_NAME, - ROUTING_MANAGER_CHAIN_NAME, + add_chain(af, TABLE_NAME, chain_name) + append_counter_at_end( + af, + TABLE_NAME, + chain_name, + ) + + def is_forward_jump_to_custom_forward(rule): + return ( + rule["chain"] == forward_chain["name"] + and len(rule["expr"]) == 1 + and rule["expr"][0].get("jump", {}).get("target") + == ADD_FORWARD_RULE_AFTER_THIS_RULE ) - address_rules = [ - x - for x in existing_rules - if x["chain"] == ROUTING_MANAGER_CHAIN_NAME - and len(x["expr"]) == 2 - and x["expr"][0].get("match", {}).get("op", {}) == "==" - and x["expr"][0]["match"].get("left", {}).get("payload", {}).get("protocol", "") - == af - and x["expr"][0]["match"]["left"]["payload"].get("field", "") == "daddr" - and x["expr"][0].get("match", {}).get("right", []) == source - and "accept" in x["expr"][1] - ] + def is_postrouting_lo_accept(rule): + return ( + rule["chain"] == postrouting_chain["name"] + and len(rule["expr"]) == 2 + and rule["expr"][0].get("match", {}).get("op", "") == "==" + and rule["expr"][0] + .get("match", {}) + .get("left", {}) + .get("meta", {}) + .get("key") + == "oif" + and rule["expr"][0].get("match", {}).get("right", "") == "lo" + and "accept" in rule["expr"][1] + ) - if enable and not address_rules: - logging.info( - "Adding accept rule on chain %s to allow traffic to %s.", + for parent_chain, child_chain_name, previous_rule_detector in [ + ( + forward_chain, ROUTING_MANAGER_CHAIN_NAME, - source, + is_forward_jump_to_custom_forward, + ), + ( + postrouting_chain, + ROUTING_MANAGER_POSTROUTING_CHAIN_NAME, + is_postrouting_lo_accept, + ), + ]: + jump_rule: None | Rule = None + try: + jump_rule = [ + x + for x in existing_rules + if x["chain"] == parent_chain["name"] + and x["family"] == af + and len(x["expr"]) == 1 + and x["expr"][0].get("jump", {}).get("target") == child_chain_name + ].pop() + except IndexError: + pass + + if not jump_rule: + try: + previous_rule = [ + x for x in existing_rules if previous_rule_detector(x) + ][0] + except IndexError: + logging.warn( + "Cannot find appropriate previous rule in chain %s of table %s, not setting up forwarding", + parent_chain["name"], + TABLE_NAME, + ) + logging.info( + "Adding rule to jump from chain %s to chain %s in table %s", + parent_chain["name"], + child_chain_name, + TABLE_NAME, + ) + append_rule_after( + af, + TABLE_NAME, + parent_chain["name"], + previous_rule["handle"], + "jump", + child_chain_name, + ) + + def detect_ip_rule(rule: Rule, chain_name: str, ip: str, mode: str): + return ( + rule["chain"] == chain_name + and len(rule["expr"]) == 2 + and rule["expr"][0].get("match", {}).get("op", {}) == "==" + and rule["expr"][0]["match"] + .get("left", {}) + .get("payload", {}) + .get("protocol", "") + == af + and rule["expr"][0]["match"]["left"]["payload"].get("field", "") == mode + and rule["expr"][0].get("match", {}).get("right", []) == ip + and "accept" in rule["expr"][1] ) - append_rule_at_end( - af, - TABLE_NAME, - ROUTING_MANAGER_CHAIN_NAME, - af, - "daddr", - source, - "accept", - ) - elif not enable and address_rules: - logging.info( - "Removing %s accept rules from chain %s to stop traffic to %s.", - len(address_rules), - ROUTING_MANAGER_CHAIN_NAME, - source, - ) - for rule in reversed(sorted(address_rules, key=lambda r: r["handle"])): - delete_rule(af, TABLE_NAME, ROUTING_MANAGER_CHAIN_NAME, rule["handle"]) + + for chain_name, mode in [ + (ROUTING_MANAGER_CHAIN_NAME, "daddr"), + (ROUTING_MANAGER_POSTROUTING_CHAIN_NAME, "saddr"), + ]: + address_rules = [ + x for x in existing_rules if detect_ip_rule(x, chain_name, source, mode) + ] + + if enable and not address_rules: + logging.info( + "Adding accept rule on chain %s for %s.", + chain_name, + source, + ) + append_rule_at_end( + af, + TABLE_NAME, + chain_name, + af, + mode, + source, + "accept", + ) + elif not enable and address_rules: + logging.info( + "Removing %s accept rules from chain %s for %s.", + len(address_rules), + chain_name, + source, + ) + for rule in reversed(sorted(address_rules, key=lambda r: r["handle"])): + delete_rule(af, TABLE_NAME, chain_name, rule["handle"]) diff --git a/qubesroutingmanager/fixtures/fully_added.json b/qubesroutingmanager/fixtures/fully_added.json new file mode 100644 index 0000000..c640853 --- /dev/null +++ b/qubesroutingmanager/fixtures/fully_added.json @@ -0,0 +1,1580 @@ +{ + "nftables": [ + { + "metainfo": { + "version": "1.0.5", + "release_name": "Lester Gooch #4", + "json_schema_version": 1 + } + }, + { + "table": { + "family": "ip", + "name": "qubes", + "handle": 1 + } + }, + { + "set": { + "family": "ip", + "name": "downstream", + "table": "qubes", + "type": "ipv4_addr", + "handle": 3, + "elem": [ + "10.137.0.10", + "10.250.4.13" + ] + } + }, + { + "set": { + "family": "ip", + "name": "allowed", + "table": "qubes", + "type": [ + "ifname", + "ipv4_addr" + ], + "handle": 4, + "elem": [ + { + "concat": [ + "vif6.0", + "10.137.0.10" + ] + }, + { + "concat": [ + "vif12.0", + "10.250.4.13" + ] + } + ] + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "prerouting", + "handle": 1, + "type": "filter", + "hook": "prerouting", + "prio": -300, + "policy": "accept" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "antispoof", + "handle": 2 + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "postrouting", + "handle": 60, + "type": "nat", + "hook": "postrouting", + "prio": 100, + "policy": "accept" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "input", + "handle": 61, + "type": "filter", + "hook": "input", + "prio": 0, + "policy": "drop" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "forward", + "handle": 62, + "type": "filter", + "hook": "forward", + "prio": 0, + "policy": "accept" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "custom-input", + "handle": 63 + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "custom-forward", + "handle": 64 + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "dnat-dns", + "handle": 87, + "type": "nat", + "hook": "prerouting", + "prio": -100, + "policy": "accept" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "qubes-routing-manager", + "handle": 105 + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "qubes-routing-manager-postrouting", + "handle": 1050 + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "prerouting", + "handle": 5, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "goto": { + "target": "antispoof" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "prerouting", + "handle": 6, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "saddr" + } + }, + "right": "@downstream" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "antispoof", + "handle": 7, + "expr": [ + { + "match": { + "op": "==", + "left": { + "concat": [ + { + "meta": { + "key": "iifname" + } + }, + { + "payload": { + "protocol": "ip", + "field": "saddr" + } + } + ] + }, + "right": "@allowed" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "antispoof", + "handle": 8, + "expr": [ + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 65, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 66, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 680, + "expr": [ + { + "jump": { + "target": "qubes-routing-manager-postrouting" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "qubes-routing-manager-postrouting", + "handle": 660, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "saddr" + } + }, + "right": "10.250.4.13" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 67, + "expr": [ + { + "masquerade": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 90, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 91, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 92, + "expr": [ + { + "masquerade": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 109, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 110, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 111, + "expr": [ + { + "masquerade": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 68, + "expr": [ + { + "jump": { + "target": "custom-input" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 69, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 70, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "right": 68 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 71, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 72, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "l4proto" + } + }, + "right": "icmp" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 73, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 74, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "reject": { + "type": "icmp", + "expr": "host-prohibited" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 75, + "expr": [ + { + "counter": { + "packets": 22933, + "bytes": 1253148 + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 93, + "expr": [ + { + "jump": { + "target": "custom-input" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 94, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 95, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "right": 68 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 96, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 97, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "l4proto" + } + }, + "right": "icmp" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 98, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 99, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "reject": { + "type": "icmp", + "expr": "host-prohibited" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 100, + "expr": [ + { + "counter": { + "packets": 1648, + "bytes": 90088 + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 112, + "expr": [ + { + "jump": { + "target": "custom-input" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 113, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 114, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "right": 68 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 115, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 116, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "l4proto" + } + }, + "right": "icmp" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 117, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 118, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "reject": { + "type": "icmp", + "expr": "host-prohibited" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 119, + "expr": [ + { + "counter": { + "packets": 947, + "bytes": 51684 + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 76, + "expr": [ + { + "jump": { + "target": "custom-forward" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 138, + "expr": [ + { + "jump": { + "target": "qubes-routing-manager" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 77, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 78, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 79, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 105866, + "bytes": 6788164 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 101, + "expr": [ + { + "jump": { + "target": "custom-forward" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 102, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 103, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 104, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 120, + "expr": [ + { + "jump": { + "target": "custom-forward" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 121, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 122, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 123, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "dnat-dns", + "handle": 88, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "daddr" + } + }, + "right": "10.139.1.1" + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "right": 53 + } + }, + { + "dnat": { + "addr": "10.250.7.2" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "dnat-dns", + "handle": 89, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "daddr" + } + }, + "right": "10.139.1.1" + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": 53 + } + }, + { + "dnat": { + "addr": "10.250.7.2" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "qubes-routing-manager", + "handle": 140, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "daddr" + } + }, + "right": "10.250.4.13" + } + }, + { + "accept": null + } + ] + } + } + ] +} \ No newline at end of file diff --git a/qubesroutingmanager/fixtures/partially_added.json b/qubesroutingmanager/fixtures/partially_added.json new file mode 100644 index 0000000..5654c4e --- /dev/null +++ b/qubesroutingmanager/fixtures/partially_added.json @@ -0,0 +1,1532 @@ +{ + "nftables": [ + { + "metainfo": { + "version": "1.0.5", + "release_name": "Lester Gooch #4", + "json_schema_version": 1 + } + }, + { + "table": { + "family": "ip", + "name": "qubes", + "handle": 1 + } + }, + { + "set": { + "family": "ip", + "name": "downstream", + "table": "qubes", + "type": "ipv4_addr", + "handle": 3, + "elem": [ + "10.137.0.10", + "10.250.4.13" + ] + } + }, + { + "set": { + "family": "ip", + "name": "allowed", + "table": "qubes", + "type": [ + "ifname", + "ipv4_addr" + ], + "handle": 4, + "elem": [ + { + "concat": [ + "vif6.0", + "10.137.0.10" + ] + }, + { + "concat": [ + "vif12.0", + "10.250.4.13" + ] + } + ] + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "prerouting", + "handle": 1, + "type": "filter", + "hook": "prerouting", + "prio": -300, + "policy": "accept" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "antispoof", + "handle": 2 + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "postrouting", + "handle": 60, + "type": "nat", + "hook": "postrouting", + "prio": 100, + "policy": "accept" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "input", + "handle": 61, + "type": "filter", + "hook": "input", + "prio": 0, + "policy": "drop" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "forward", + "handle": 62, + "type": "filter", + "hook": "forward", + "prio": 0, + "policy": "accept" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "custom-input", + "handle": 63 + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "custom-forward", + "handle": 64 + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "dnat-dns", + "handle": 87, + "type": "nat", + "hook": "prerouting", + "prio": -100, + "policy": "accept" + } + }, + { + "chain": { + "family": "ip", + "table": "qubes", + "name": "qubes-routing-manager", + "handle": 105 + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "prerouting", + "handle": 5, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "goto": { + "target": "antispoof" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "prerouting", + "handle": 6, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "saddr" + } + }, + "right": "@downstream" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "antispoof", + "handle": 7, + "expr": [ + { + "match": { + "op": "==", + "left": { + "concat": [ + { + "meta": { + "key": "iifname" + } + }, + { + "payload": { + "protocol": "ip", + "field": "saddr" + } + } + ] + }, + "right": "@allowed" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "antispoof", + "handle": 8, + "expr": [ + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 65, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 66, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 67, + "expr": [ + { + "masquerade": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 90, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 91, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 92, + "expr": [ + { + "masquerade": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 109, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 110, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "postrouting", + "handle": 111, + "expr": [ + { + "masquerade": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 68, + "expr": [ + { + "jump": { + "target": "custom-input" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 69, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 70, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "right": 68 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 71, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 72, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "l4proto" + } + }, + "right": "icmp" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 73, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 74, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "reject": { + "type": "icmp", + "expr": "host-prohibited" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 75, + "expr": [ + { + "counter": { + "packets": 22933, + "bytes": 1253148 + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 93, + "expr": [ + { + "jump": { + "target": "custom-input" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 94, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 95, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "right": 68 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 96, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 97, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "l4proto" + } + }, + "right": "icmp" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 98, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 99, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "reject": { + "type": "icmp", + "expr": "host-prohibited" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 100, + "expr": [ + { + "counter": { + "packets": 1648, + "bytes": 90088 + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 112, + "expr": [ + { + "jump": { + "target": "custom-input" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 113, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 114, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "right": 68 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 115, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 116, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "l4proto" + } + }, + "right": "icmp" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 117, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iif" + } + }, + "right": "lo" + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 118, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "reject": { + "type": "icmp", + "expr": "host-prohibited" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "input", + "handle": 119, + "expr": [ + { + "counter": { + "packets": 947, + "bytes": 51684 + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 76, + "expr": [ + { + "jump": { + "target": "custom-forward" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 138, + "expr": [ + { + "jump": { + "target": "qubes-routing-manager" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 77, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 78, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 79, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 105866, + "bytes": 6788164 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 101, + "expr": [ + { + "jump": { + "target": "custom-forward" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 102, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 103, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 104, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 120, + "expr": [ + { + "jump": { + "target": "custom-forward" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 121, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "invalid" + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 122, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related" + ] + } + }, + { + "accept": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "forward", + "handle": 123, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifgroup" + } + }, + "right": 2 + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": null + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "dnat-dns", + "handle": 88, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "daddr" + } + }, + "right": "10.139.1.1" + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "right": 53 + } + }, + { + "dnat": { + "addr": "10.250.7.2" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "dnat-dns", + "handle": 89, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "daddr" + } + }, + "right": "10.139.1.1" + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": 53 + } + }, + { + "dnat": { + "addr": "10.250.7.2" + } + } + ] + } + }, + { + "rule": { + "family": "ip", + "table": "qubes", + "chain": "qubes-routing-manager", + "handle": 140, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "daddr" + } + }, + "right": "10.250.4.13" + } + }, + { + "accept": null + } + ] + } + } + ] +} \ No newline at end of file diff --git a/qubesroutingmanager/test_firewalling.py b/qubesroutingmanager/test_firewalling.py index 7892c69..eb7014f 100644 --- a/qubesroutingmanager/test_firewalling.py +++ b/qubesroutingmanager/test_firewalling.py @@ -1,1549 +1,20 @@ +import os + from unittest import mock from qubesroutingmanager import setup_plain_forwarding_for_address -ALREADY_ADDED = """ -{ - "nftables": [ - { - "metainfo": { - "version": "1.0.5", - "release_name": "Lester Gooch #4", - "json_schema_version": 1 - } - }, - { - "table": { - "family": "ip", - "name": "qubes", - "handle": 1 - } - }, - { - "set": { - "family": "ip", - "name": "downstream", - "table": "qubes", - "type": "ipv4_addr", - "handle": 3, - "elem": [ - "10.137.0.10", - "10.250.4.13" - ] - } - }, - { - "set": { - "family": "ip", - "name": "allowed", - "table": "qubes", - "type": [ - "ifname", - "ipv4_addr" - ], - "handle": 4, - "elem": [ - { - "concat": [ - "vif6.0", - "10.137.0.10" - ] - }, - { - "concat": [ - "vif12.0", - "10.250.4.13" - ] - } - ] - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "prerouting", - "handle": 1, - "type": "filter", - "hook": "prerouting", - "prio": -300, - "policy": "accept" - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "antispoof", - "handle": 2 - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "postrouting", - "handle": 60, - "type": "nat", - "hook": "postrouting", - "prio": 100, - "policy": "accept" - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "input", - "handle": 61, - "type": "filter", - "hook": "input", - "prio": 0, - "policy": "drop" - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "forward", - "handle": 62, - "type": "filter", - "hook": "forward", - "prio": 0, - "policy": "accept" - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "custom-input", - "handle": 63 - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "custom-forward", - "handle": 64 - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "dnat-dns", - "handle": 87, - "type": "nat", - "hook": "prerouting", - "prio": -100, - "policy": "accept" - } - }, - { - "chain": { - "family": "ip", - "table": "qubes", - "name": "qubes-routing-manager", - "handle": 105 - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "prerouting", - "handle": 5, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "goto": { - "target": "antispoof" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "prerouting", - "handle": 6, - "expr": [ - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "ip", - "field": "saddr" - } - }, - "right": "@downstream" - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "antispoof", - "handle": 7, - "expr": [ - { - "match": { - "op": "==", - "left": { - "concat": [ - { - "meta": { - "key": "iifname" - } - }, - { - "payload": { - "protocol": "ip", - "field": "saddr" - } - } - ] - }, - "right": "@allowed" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "antispoof", - "handle": 8, - "expr": [ - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 65, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oifgroup" - } - }, - "right": 2 - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 66, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oif" - } - }, - "right": "lo" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 67, - "expr": [ - { - "masquerade": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 90, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oifgroup" - } - }, - "right": 2 - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 91, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oif" - } - }, - "right": "lo" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 92, - "expr": [ - { - "masquerade": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 109, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oifgroup" - } - }, - "right": 2 - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 110, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oif" - } - }, - "right": "lo" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "postrouting", - "handle": 111, - "expr": [ - { - "masquerade": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 68, - "expr": [ - { - "jump": { - "target": "custom-input" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 69, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": "invalid" - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 70, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "udp", - "field": "dport" - } - }, - "right": 68 - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 71, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": [ - "established", - "related" - ] - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 72, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "l4proto" - } - }, - "right": "icmp" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 73, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iif" - } - }, - "right": "lo" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 74, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "reject": { - "type": "icmp", - "expr": "host-prohibited" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 75, - "expr": [ - { - "counter": { - "packets": 22933, - "bytes": 1253148 - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 93, - "expr": [ - { - "jump": { - "target": "custom-input" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 94, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": "invalid" - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 95, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "udp", - "field": "dport" - } - }, - "right": 68 - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 96, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": [ - "established", - "related" - ] - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 97, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "l4proto" - } - }, - "right": "icmp" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 98, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iif" - } - }, - "right": "lo" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 99, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "reject": { - "type": "icmp", - "expr": "host-prohibited" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 100, - "expr": [ - { - "counter": { - "packets": 1648, - "bytes": 90088 - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 112, - "expr": [ - { - "jump": { - "target": "custom-input" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 113, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": "invalid" - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 114, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "udp", - "field": "dport" - } - }, - "right": 68 - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 115, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": [ - "established", - "related" - ] - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 116, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "l4proto" - } - }, - "right": "icmp" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 117, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iif" - } - }, - "right": "lo" - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 118, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "iifgroup" - } - }, - "right": 2 - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "reject": { - "type": "icmp", - "expr": "host-prohibited" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "input", - "handle": 119, - "expr": [ - { - "counter": { - "packets": 947, - "bytes": 51684 - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 76, - "expr": [ - { - "jump": { - "target": "custom-forward" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 138, - "expr": [ - { - "jump": { - "target": "qubes-routing-manager" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 77, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": "invalid" - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 78, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": [ - "established", - "related" - ] - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 79, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oifgroup" - } - }, - "right": 2 - } - }, - { - "counter": { - "packets": 105866, - "bytes": 6788164 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 101, - "expr": [ - { - "jump": { - "target": "custom-forward" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 102, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": "invalid" - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 103, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": [ - "established", - "related" - ] - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 104, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oifgroup" - } - }, - "right": 2 - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 120, - "expr": [ - { - "jump": { - "target": "custom-forward" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 121, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": "invalid" - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 122, - "expr": [ - { - "match": { - "op": "in", - "left": { - "ct": { - "key": "state" - } - }, - "right": [ - "established", - "related" - ] - } - }, - { - "accept": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "forward", - "handle": 123, - "expr": [ - { - "match": { - "op": "==", - "left": { - "meta": { - "key": "oifgroup" - } - }, - "right": 2 - } - }, - { - "counter": { - "packets": 0, - "bytes": 0 - } - }, - { - "drop": null - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "dnat-dns", - "handle": 88, - "expr": [ - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "ip", - "field": "daddr" - } - }, - "right": "10.139.1.1" - } - }, - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "udp", - "field": "dport" - } - }, - "right": 53 - } - }, - { - "dnat": { - "addr": "10.250.7.2" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "dnat-dns", - "handle": 89, - "expr": [ - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "ip", - "field": "daddr" - } - }, - "right": "10.139.1.1" - } - }, - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "tcp", - "field": "dport" - } - }, - "right": 53 - } - }, - { - "dnat": { - "addr": "10.250.7.2" - } - } - ] - } - }, - { - "rule": { - "family": "ip", - "table": "qubes", - "chain": "qubes-routing-manager", - "handle": 140, - "expr": [ - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "ip", - "field": "daddr" - } - }, - "right": "10.250.4.13" - } - }, - { - "accept": null - } - ] - } - } - ] -} -""" +def get_fixture(name): + with open(os.path.join(os.path.dirname(__file__), "fixtures", name)) as f: + return f.read() -def mock_collector(): +def mock_collector(output: str): final_args = [] class MockedPopen: def __init__(self, args, **kwargs): - final_args.append(args) + final_args.append(args[3:]) self.args = args self.returncode = 0 @@ -1554,7 +25,7 @@ def mock_collector(): pass def communicate(self, input=None, timeout=None): - stdout = ALREADY_ADDED + stdout = output stderr = "" self.returncode = 1 return stdout, stderr @@ -1565,12 +36,54 @@ def mock_collector(): return final_args, MockedPopen -def test_forwarding_does_not_add_twice(): - args, MockedPopen = mock_collector() +def test_partial_add_completes_the_add(): + got, MockedPopen = mock_collector(get_fixture("partially_added.json")) expected = [ - ["nft", "-n", "-j", "list", "table", "ip", "qubes"], + ["list", "table", "ip", "qubes"], + ["add", "chain", "ip", "qubes", "qubes-routing-manager-postrouting"], + [ + "add", + "rule", + "ip", + "qubes", + "qubes-routing-manager-postrouting", + "counter", + ], + [ + "add", + "rule", + "ip", + "qubes", + "postrouting", + "position", + "66", + "jump", + "qubes-routing-manager-postrouting", + ], + [ + "add", + "rule", + "ip", + "qubes", + "qubes-routing-manager-postrouting", + "ip", + "saddr", + "10.250.4.13", + "accept", + ], ] with mock.patch("subprocess.Popen", MockedPopen): setup_plain_forwarding_for_address("10.250.4.13", True, 4) - assert args == expected + assert got == expected + + +def test_forwarding_does_not_add_twice(): + got, MockedPopen = mock_collector(get_fixture("fully_added.json")) + expected = [ + ["list", "table", "ip", "qubes"], + ] + with mock.patch("subprocess.Popen", MockedPopen): + setup_plain_forwarding_for_address("10.250.4.13", True, 4) + + assert got == expected