Blocking non-tor traffic from sys-net / sys-firewall, script in /usr/local/sbin of template not found in service qube

<!--- Before asking, did you search first? Press 🔍 at the upper right to search. ---->

I am attempting to implement the protections found in this post. Where sys-net and sys-firewall are configured to drop any traffic that is not loopback.

I have little experience with Linux so my implementation may be flawed (following an LLM generated guide, with my own tweaks). The setup is as follows

I duplicated the TemplateVM debian-13-xfce as sys-net already uses it. In the template VM I have a service found at /etc/systemd/system/firewall-by-hostname.service. The intention of this service, as I understand it, is to run the /usr/local/sbin/check_hostname-firewall_rules.sh script before network services come online and apply the firewall rules if the hostname fits.

I’ve included the specific configs for what I’ve setup below.

My primary issue is the template vm seems to execute these steps successfully, I can check the systemd service after a boot with sudo journalctl -b -u firewall-by-hostname.serviceand see success entries and checking the firewall rules they’ve applied. However, when I run the sys-net qube based on the new template vm the same status check returns an error that the check_hostname-firewall_rules.sh script does not exist. Cding into the directory shows it’s empty. Why is the script not appearing in the sys-net qube?

My secondary question, will the current configuration perform the expected function of blocking all non-tor traffic? I’m concerned about the ct state established, related accept rule. Does this rule even make sense? Please remember the contents of the configs were generated by an LLM as I could not write my own, but I typed everything myself and tried to understand everything I could. Any help regarding implementing this desired quality would be much appreciated :slight_smile:

The configuration for the service is below:

[Unit]
Description=Apply firewall rules based on hostname check
Before=network-pre.target
Wants=network-pre.target
DefaultDependencies=yes

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/check_hostname-firewall_rules.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

The contents of check_hostname-firewall_rules.sh are:

#!/bin/bash

# ======
# check_hostname-firewall_rules.sh
# Runs in boot before networking is established
# Check hostname for sys-net or sys-firewall and blocks outbound connections if it matches
# ======

set -euo pipefail

TARGET_HOSTNAMES=("sys-net" "sys-firewall" "localhost") # Localhost is for testing while on template vm

RULESET_FILE="/etc/nftables.conf"

CURRENT_HOSTNAME=$(hostname)
HOSTNAME_MATCHED=false

for target in "${TARGET_HOSTNAMES[@]}"; do
    if [[ "${CURRENT_HOSTNAME}" == "${target}" ]]; then
        HOSTNAME_MATCHED=true
        break
    fi
done

if [[ "${HOSTNAME_MATCHED}" != true ]]; then
    echo "Hostname does not match, skipping firewall rules"
    exit 0
fi

echo "Hostname matched, applying firewall rules"

if [[ -f "${RULESET_FILE" ]]; then
    nft -f "${RULESET_FILE}"
    echo "Firewall rules loaded"
else
    echo "ERROR: Ruleset file not found" >&2
    exit 1
fi

The config for the /etc/nftables.conf are

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy accept;
        
        # Allow loopback
        iif "lo" accept

        # Allow established/related
        ct state established, related accept

        # Allow ICMP
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept
    }
    chain forward {
        # Drop forward traffic, I think
        type filter hook forward priority 0; policy drop;
    }
    chain output {
        type filter hook output priority 0; policy drop;

        # Allow loopback
        oif "lo" accept

        # Allow established/related
        ct state established, related accept
    }
}

The path /usr/local/ is owned by the vm/template itself. When you create a vm from a template, you get a “blank” setup.

If you only care about the action of having the script copied from template→vm on its creation, you could place the script in the template on /usr/local.orig/ - that directory will be copied to /usr/local/ on vm creation.

Consider that it is only a copy on creation and not like /etc/ where a modification in the template also affects the vm.

That said - I don’t know if your firewall config will work, but you could possibly delete the service and modify the firewall on the template since it would also be available in the vm.

Thank you, I’m definitely having some confusions about what gets moved into an app qube when created from a template. It was my initial understanding that everything from a template was carried over, this has now been augmented that the /home directory of the parent is not transferred to the child. I also now understand that once an app qube has been created everything in it’s /home, /usr/local, and /rw/config persist across reboots. Does this mean the qube must be deleted and recreated if desired changes from the template map here? Such as contents in the parent’s /usr/local.orig?

I am still quite confused on what exactly gets transfered when using a template for a qube. I am getting tripped up on the documentation’s wording. It says

Once an app qube has been created, any changes in its /home, /usr/local, or /rw/config directories will be persistent across reboots, which means that any files stored there will still be available after restarting the app qube. No changes in any other directories in app qubes persist in this manner. If you would like to make changes in other directories which do persist in this manner, you must make those changes in the parent template.

However in the table describing inheritance and persistence it says app qubes inherit “/etc/skel to /home; /usr/local.orig to /usr/local” .

So so far, /home is not copied except the parent’s content of /etc/skel, it is implied but not explicitly stated that a templates /usr/local is also not copied unless it’s put in /usr/local.orig, /home and /usr/local are persistent across reboots so changes made to the tempalte will not apply until you delete and recreate the qube… am I at all crazy for being very confused by this? What exactly is transfered from templates to app qubes, when are the changes applied, is shutting down an app qube then changing its template not how to apply the changes?
Maybe I’m just frustrated but it seems templates tie intrinsically to the foundation using and customizing qubes and yet I’ve formed multiple misconceptions about how they work after separate reads of the documentation. I know there is a section of the docs describing installing apps that describes some of this but it doesn’t describe what of the file system is transferred. In fact the deepest information I found, even on a forum post, was to use a package manager for your desired apps. For a situation like myself, where I’m not trying to install an app, this is very very confusing :frowning: . Thank you for listening to my rant, I’m off to take another crack solving the problem.

After moving the script to /etc/tor_nftables and editing the systemd service respectively, I now have the service and script running as expected in sys-net. Attempting ping 8.8.8.8 from sys-net no longer succeeds which is a win*. Unfortunately, I do not believe I have the rest of the system configured properly. As I am unable to ping 8.8.8.8 from any qube :slight_smile: . With any luck, this is just something to figure out with the firewall ruleset. Am rolling back to the non-modified version of “debian-13-xfce” to experiment with this.