Set firewall rules for windows 10 update

I want to set firewall rules that only allow updating windows 10.
Currently, the rules limit outgoing internet connections to:

dl.delivery.mp.microsoft.com
download.microsoft.com
update.microsoft.com
windowsupdate.microsoft.com
windowsupdate.com
wustat.windows.com
ntservicepack.microsoft.com
go.microsoft.com
download.windowsupdate.com

This is my approximation of the official list (Step 2 - Configure WSUS | Microsoft Docs).

But windows update would start then immediately stop and say it can’t connect to the windows update service. Previously when I block all out-going connection, windows update would scan for a long time before failing to update.

How to set the firewall rules for windows 10 update?

I try to look at the logs of sys-firewall qube, but I can’t find the log for the connections denied by the firewall.

In a terminal of the sys-firewall qube, ps faux | grep wall says
the qube is running /usr/bin/python3 /usr/sbin/qubes-firewall instead of firewalld.

ps faux | grep ip says ipv6_addrconf

I don’t know how to make it log the connections that it blocked.

systemctl should show the services that the qube is running. One of the services is qubes-iptables.service.

sudo journalctl -u qubes-firewall.service as described in The Qubes Firewall | Qubes OS seems to show something useful:

sys-firewall qubes-firewall[491]: 
    Failed to parse rules for <ip of the Win10 qube> 
        (Failed to resolve windowsupdate.com: 
            [Errno -2] Name or service not known), 
    blocking traffic

But this is not talking about the ip and port of the packets dropped.

It probably means that the qube can’t parse the firewall rules that I give it.

I am using the minimal fedora 33 template for the sys-firewall qube.

Maybe… I will see the logs if I install more packages into the sys-firewall qube?

The qvm-firewall command in dom0 shows the firewall rules, as described in (Firewall VM with Custom Rules behind a VPN = No Internet - #16 by some.user7882)

$ qvm-firewall <name of the qube>
NO  ACTION  HOST                          PROTOCOL  PORT(S)  SPECIAL TARGET  ICMP TYPE  EXPIRE  COMMENT
0   accept  dl.delivery.mp.microsoft.com  -         -        -               -          -       -
1   accept  download.microsoft.com        -         -        -               -          -       -
2   accept  update.microsoft.com          -         -        -               -          -       -
3   accept  windowsupdate.microsoft.com   -         -        -               -          -       -
4   accept  windowsupdate.com             -         -        -               -          -       -
5   accept  wustat.windows.com            -         -        -               -          -       -
6   accept  ntservicepack.microsoft.com   -         -        -               -          -       -
7   accept  go.microsoft.com              -         -        -               -          -       -
8   accept  download.windowsupdate.com    -         -        -               -          -       -
9   accept  -                             -         -        dns             -          -       -
10  accept  -                             icmp      -        -               -          -       -
11  drop    -                             -         -        -               -          -       - 

Running netstat in the sys-firewall vm shows nothing, although a disposable vm is browsing the qubes os discourse.

On the other hand, running netstat -a -n directly in the disposable vm shows several connections.

Why would running netstat -a -n in the sys-firewall vm show nothing?

I want to probe the ip that the Windows 10 VM visits. But I can’t run netstat in the Windows 10 VM, since it is not Linux.

see this part: “Note that if you specify a rule by DNS name it will be resolved to IP(s) at the moment of applying the rules,”

the hostnames that you are using probably have many ip addresses, so you are probably just allowing one of many ip addresses for that name.

you probably want to create a proxy network that you can throttle with a domain name based whitelist like squid

@ddevz Thanks I didn’t notice that.

Mostly I want to stop Windows 10 from spying on me.

I can understand your desire. Good luck with it. I think creating some kind of “sys-proxy” based on squid is the way to go. I’ve used squid (outside of qubes) before to lock a VM/computer down to specific domain names so I know this is a real function of squid. You’d just have to figure out the qubes networking part of it (which i’m not familiar enough with yet to be helpful with).

I noticed someone trying to use squid with qubes not for locking down the network but to try to cache network things. It’s not exactly what you want, but maybe one or two lines could be useful: GitHub - rustybird/qubes-updates-cache: Squid-based package update cache for Qubes, transparently rewriting URLs to .onion or HTTPS

If you get something working, come back and let us know!

Actually, I had another idea that might help more, that comes in 2 parts:
Part 1: First, confirm that the load balancing is the issue. for example ping update.microsoft.com and look at the ip address, Then ping it from another computer that uses a differnt DNS cache. if the ip address keeps changing, then it’s load balanced.

Part 2: change the name of your question to something like “How to let load balanced servers through the firewall?”. There are probably people who know how sys-net and sys-fireall work who are seeing the word windows, assuming it’s a windows issue and not bothering to read this thread.

A bit more: I looked a little bit at my running sys-net and sys-firewall qubes and I suspect that when you set a “netVM” for a “AppVM” that it just sets the gateway for that AppVM to the IP off the specified netVM. It also looks like sys-firewall is just a bunch of iptables rules that then forwards to it’s NetVM (like sys-net).

This implies to me that squid would have to be set up in a sys-squid-firewall as a transparent proxy, and not just a socks proxy, or something like that. I have never set up a transparent proxy, but searching on “squid transparent proxy” turns up many instructions for setting this kind of thing up.

Note that you would have to hard code the squid “whitelist rules” into the sys-squid-firewall VM instead of into the GUI. (assuming you aren’t up for figuring out how qubes communicates the firewall config information)

Also, qubes “services” is probably how you would avoid having to start squid from the terminal each time you booted sys-squid-firewall

I’ll primarily answer your first question for information purposes - how to get logs of blocked connections by qubes-firewall, then follow up with some additional notes regarding the debugging you’ve already done.

The qubes-firewall uses nftables to configure allowed and denied hosts. What you can do is modify a nft rule to add logging which will go to your kernel log (dmesg or journalctl).

  1. sudo nft list ruleset -a

  2. Look for the handle number in the ‘reject with icmp type admin-prohibited’ of the right chain

  3. Replace the rejection rule with a counter + log + reject statement

There is an ipv4 qubes-firewall table, a forward chain, and the forward chain references chains specific to each downstream qube connected to it. There’s also an ipv6 qubes-firewall table…but to keep things simple I’ll leave it out. If you have ipv6 enabled in your environment, ensure both address families are accounted for.

If I have a qube connected to sys-firewall named “onlycloudflare” and I want it to only have access to IP address 1.1.1.1, I could do the following to get more debugging info out:

Showing the current chains:

sudo nft list -a table qubes-firewall

Will show something like this, where qbs-10-138-26-180 is the “onlycloudflare” qube’s IP address:

table ip qubes-firewall {
        chain forward {
                type filter hook forward priority filter; policy drop;
                ct state established,related accept
                iifname != "vif*" accept
                ip saddr 10.138.26.180 jump qbs-10-138-26-180
        }

        chain qbs-10-138-26-180 {
                ip daddr 1.1.1.1 accept # handle 10 
                ip daddr { 10.139.1.1-10.139.1.2 } tcp dport 53 accept # handle 11
                ip daddr { 10.139.1.1-10.139.1.2 } udp dport 53 accept # handle 12
                ip protocol icmp accept # handle 13
                reject with icmp type admin-prohibited # handle 14
                reject with icmp type admin-prohibited # handle 15
        }
}

Replace the rule with handle 14 to add logging and a counter:

sudo nft replace rule qubes-firewall qbs-10-138-26-180 handle 14 counter log prefix \"[qubes-firewall-BLOCKED] \" reject with icmp type admin-prohibited

If you want stats and logs for all initial packets (using the ‘info’ level; by default it uses ‘warn’ because nobody logs everything),

sudo nft insert rule qubes-firewall qbs-10-138-26-180 counter log prefix \"[qubes-firewall-eval] \" level info

This will now have rules such as:

    chain qbs-10-138-26-180 {
            counter packets 0 bytes 0 log prefix "[qubes-firewall-eval] " level info # handle 42
            ip daddr 1.1.1.1 accept # handle 10 
            ip daddr { 10.139.1.1-10.139.1.2 } tcp dport 53 accept # handle 11
            ip daddr { 10.139.1.1-10.139.1.2 } udp dport 53 accept # handle 12
            ip protocol icmp accept # handle 13
            counter packets 0 bytes 0 log prefix "[qubes-firewall-BLOCKED] " reject with icmp type admin-prohibited # handle 14
            reject with icmp type admin-prohibited # handle 15
    }

In sys-firewall, await kernel logs with: sudo dmesg -w

In “onlycloudflare” qube, run wget commands to allowed and denied addresses:

wget https://1.1.1.2

Logs should arise:

[689.942410] [qubes-firewall-eval] IN=vif31.0 OUT=eth0 MAC=fe:ff:ff:ff:ff:ff:00:16:3e:5e:6c:00:08:00 SRC=10.138.26.180 DST=1.1.1.2 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=54098 DF PROTO=TCP SPT=57174 DPT=443 WINDOW=64240 RES=0x00 SYN URGP=0

[689.942530] [qubes-firewall-BLOCKED]: IN=vif31.0 OUT=eth0 MAC=fe:ff:ff:ff:ff:ff:00:16:3e:5e:6c:00:08:00 SRC=10.138.26.180 DST=1.1.1.2 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=54098 DF PROTO=TCP SPT=57174 DPT=443 WINDOW=64240 RES=0x00 SYN URGP=0

Something not blocked will not have a corresponding qubes-firewall-BLOCKED printk:

wget https://1.1.1.1

[704.271777] [qubes-firewall-eval] IN=vif31.0 OUT=eth0 MAC=fe:ff:ff:ff:ff:ff:00:16:3e:5e:6c:00:08:00 SRC=10.138.26.180 DST=1.1.1.1 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=43637 DF PROTO=TCP SPT=35744 DPT=443 WINDOW=64240 RES=0x00 SYN URGP=0

Showing the counters you can manually diff packets that came in (rule 1) and were blocked (before the ‘reject’) - this shows of 2 initial packets to be forwarded, 1 was blocked:

    chain qbs-10-138-26-180 {
            counter packets 2 bytes 104 log prefix "[qubes-firewall-eval] "
            ip daddr 1.1.1.1 accept
            ip daddr { 10.139.1.1-10.139.1.2 } tcp dport 53 accept
            ip daddr { 10.139.1.1-10.139.1.2 } udp dport 53 accept
            ip protocol icmp accept
            counter packets 1 bytes 52 log prefix "[qubes-firewall-BLOCKED]: " reject with icmp type admin-prohibited
            reject with icmp type admin-prohibited
    }

Now to provide insight to your earlier debugging:

  • qubes-iptables is a service that establishes the baseline iptables rules for all Qubes
  • qubes-firewall is a service that runs additional user-specified custom firewall rules at FirewallVM boot, as well as dynamically activating firewall rules for attached downstream qubes.
  • sys-firewall acts as a router; you will not see IP connections that are being forwarded on behalf of another qube with netstat. If you want to see those, you want to look into conntrack table - sudo cat /proc/net/nf_conntrack or sudo conntrack -L from conntrack-tools package in Fedora.
  • netstat is typically already available in Windows, netstat /an
  • The journalctl -u qubes-firewall output tells you the real reason why nothing worked: “Failed to resolve windowsupdate.comblocking traffic”…while the second-level domain name is valid, there is no IP address associated with the windowsupdate.com. The WSUS document specifies *.windowsupdate.com - an host name such as this is not possible to specify using the Qubes Firewall - you must know each individual domain. Some additional notes on the qubes firewall: How is the QubesOS firewall implemented? - #13

Overall, @ddevz has provided valuable input and I agree with the advice - use a proxy…because filtering by IP address gets very messy if the hosts utilize load balancing and CNAME masquerading with very tight DNS TTLs.

One such mechanism (using tinyproxy, something already available in QubesOS by default) was discussed in a guide devised by @Rooftop: Restricting a Qube to selected websites

1 Like