Neuter masquerading of outbound traffic from VMs in routing mode.

This commit is contained in:
Manuel Amador (Rudd-O) 2024-02-06 03:54:35 +00:00
parent e9e65f7da1
commit 7b5cae5b0e
5 changed files with 3310 additions and 1635 deletions

View File

@ -3,7 +3,7 @@
%define mybuildnumber %{?build_number}%{?!build_number:1} %define mybuildnumber %{?build_number}%{?!build_number:1}
Name: qubes-network-server Name: qubes-network-server
Version: 0.1.0 Version: 0.1.1
Release: %{mybuildnumber}%{?dist} Release: %{mybuildnumber}%{?dist}
Summary: Turn your Qubes OS into a network server Summary: Turn your Qubes OS into a network server
BuildArch: noarch BuildArch: noarch

View File

@ -65,9 +65,11 @@ ADDRESS_FAMILY_IPV6 = "ip6"
ADDRESS_FAMILY_IPV4 = "ip" ADDRESS_FAMILY_IPV4 = "ip"
TABLE_NAME = "qubes" TABLE_NAME = "qubes"
FORWARD_CHAIN_NAME = "forward" FORWARD_CHAIN_NAME = "forward"
POSTROUTING_CHAIN_NAME = "postrouting"
ROUTING_MANAGER_CHAIN_NAME = "qubes-routing-manager" ROUTING_MANAGER_CHAIN_NAME = "qubes-routing-manager"
ROUTING_MANAGER_POSTROUTING_CHAIN_NAME = "qubes-routing-manager-postrouting"
NFTABLES_CMD = "nft" 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: 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][ forward_chain = [x for x in existing_chains if x["name"] == FORWARD_CHAIN_NAME][
0 0
] ]
postrouting_chain = [
x for x in existing_chains if x["name"] == POSTROUTING_CHAIN_NAME
][0]
except IndexError: except IndexError:
logging.warn( 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 return
qubes_routing_manager_chain: None | Chain = None for chain_name in [
try: ROUTING_MANAGER_CHAIN_NAME,
qubes_routing_manager_chain = [ ROUTING_MANAGER_POSTROUTING_CHAIN_NAME,
x for x in existing_chains if x["name"] == ROUTING_MANAGER_CHAIN_NAME ]:
].pop() chain: None | Chain = None
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:
try: try:
custom_forwarding_rule = [ chain = [x for x in existing_chains if x["name"] == chain_name].pop()
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]
except IndexError: except IndexError:
logging.warn( pass
"No state forwarding rule in chain %s of table %s, not setting up forwarding",
forward_chain["name"], if not chain:
logging.info(
"Adding %s chain to table %s and counter to chain",
chain_name,
TABLE_NAME, TABLE_NAME,
) )
logging.info( add_chain(af, TABLE_NAME, chain_name)
"Adding rule to jump to %s to table %s after jump to %s", append_counter_at_end(
ROUTING_MANAGER_CHAIN_NAME, af,
TABLE_NAME, TABLE_NAME,
ADD_RULE_AFTER_THIS_RULE, chain_name,
) )
append_rule_after(
af, def is_forward_jump_to_custom_forward(rule):
TABLE_NAME, return (
forward_chain["name"], rule["chain"] == forward_chain["name"]
custom_forwarding_rule["handle"], and len(rule["expr"]) == 1
"jump", and rule["expr"][0].get("jump", {}).get("target")
ROUTING_MANAGER_CHAIN_NAME, == ADD_FORWARD_RULE_AFTER_THIS_RULE
)
append_counter_at_end(
af,
TABLE_NAME,
ROUTING_MANAGER_CHAIN_NAME,
) )
address_rules = [ def is_postrouting_lo_accept(rule):
x return (
for x in existing_rules rule["chain"] == postrouting_chain["name"]
if x["chain"] == ROUTING_MANAGER_CHAIN_NAME and len(rule["expr"]) == 2
and len(x["expr"]) == 2 and rule["expr"][0].get("match", {}).get("op", "") == "=="
and x["expr"][0].get("match", {}).get("op", {}) == "==" and rule["expr"][0]
and x["expr"][0]["match"].get("left", {}).get("payload", {}).get("protocol", "") .get("match", {})
== af .get("left", {})
and x["expr"][0]["match"]["left"]["payload"].get("field", "") == "daddr" .get("meta", {})
and x["expr"][0].get("match", {}).get("right", []) == source .get("key")
and "accept" in x["expr"][1] == "oif"
] and rule["expr"][0].get("match", {}).get("right", "") == "lo"
and "accept" in rule["expr"][1]
)
if enable and not address_rules: for parent_chain, child_chain_name, previous_rule_detector in [
logging.info( (
"Adding accept rule on chain %s to allow traffic to %s.", forward_chain,
ROUTING_MANAGER_CHAIN_NAME, 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, for chain_name, mode in [
TABLE_NAME, (ROUTING_MANAGER_CHAIN_NAME, "daddr"),
ROUTING_MANAGER_CHAIN_NAME, (ROUTING_MANAGER_POSTROUTING_CHAIN_NAME, "saddr"),
af, ]:
"daddr", address_rules = [
source, x for x in existing_rules if detect_ip_rule(x, chain_name, source, mode)
"accept", ]
)
elif not enable and address_rules: if enable and not address_rules:
logging.info( logging.info(
"Removing %s accept rules from chain %s to stop traffic to %s.", "Adding accept rule on chain %s for %s.",
len(address_rules), chain_name,
ROUTING_MANAGER_CHAIN_NAME, source,
source, )
) append_rule_at_end(
for rule in reversed(sorted(address_rules, key=lambda r: r["handle"])): af,
delete_rule(af, TABLE_NAME, ROUTING_MANAGER_CHAIN_NAME, rule["handle"]) 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"])

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff