ioc32 / openhrc

Open Household Router Contraption
Other
13 stars 4 forks source link

Integrate hosts based filtering? #17

Open saghul opened 5 years ago

saghul commented 5 years ago

https://github.com/StevenBlack/hosts

ioc32 commented 5 years ago

👍

As discussed offline, I'd like to give this a try too!

Adding to the one you shared, we can consider the following feeds/providers:

We can either redirect to httpd or DROP/REJECT with pf.

Stuff for the upcoming bromanceathon!

saghul commented 4 years ago

https://github.com/gbxyz/unbound-block-hosts for converting the hosts file to unbound. https://deadc0de.re/articles/unbound-blocking-ads.htmlsimpler version using awk

ioc32 commented 3 years ago

We can use those feeds to either drop requests at the network (pf(4)) or DNS (unbound(8)).

Dropping at the firewall will give us increased visibility via pflog(4) but we'd have to resolve all these low-TTL RRs daily or weekly.

A quick data point here:

goya$ time dig -f /tmp/hosts +tcp @8.8.8.8
<snip>
;; Query time: 105 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Sep 11 16:22:38 CEST 2020
;; MSG SIZE  rcvd: 91

  139m56.98s real     0m12.43s user     0m16.50s system

openhrc$ time dig -f /tmp/hosts.block +tcp @10.0.0.1
<snip>
^C                    # cause it was only half-way there
  196m59.59s real     0m14.16s user     0m18.18s system

Dropping at the resolver is easier to integrate as we only need to re-format the host file from upstream and reload unbound(8) but we do lose some of the visibility as we cannot directly see the blocked requests ¯_(ツ)_/¯

DNS names come and go much more rapidly than IPs but I guess losing some efficacy in our filtering approach is fine since our game here is convenience (remove ads, etc), not security (remove low-rep, fast-flux or newly observed domains). At $JOB, I'd probably go for a solution based on DNS RPZ, webserver and firewall but in our contraption, I think I'd go for the simpler DNS route which should be good enough and have fewer moving parts.

If we see a reason to, we can always change this in the future.

In terms of what to serve, ideally I think we should serve NXDOMAIN to skip the connection attempt. Otherwise, 127.0.0.1 or 0.0.0.0 will have to do. Their meaning seems to be implementation-specific:

goya$ curl -v 0.0.0.0
*   Trying 0.0.0.0:80...
* Immediate connect fail for 0.0.0.0: Invalid argument
* Closing connection 0
curl: (7) Couldn't connect to server
goya$ curl -v 127.0.0.1       
*   Trying 127.0.0.1:80...
* connect to 127.0.0.1 port 80 failed: Connection refused
* Failed to connect to 127.0.0.1 port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused

monet:~> curl -v 0.0.0.0
*   Trying 0.0.0.0:80...
* TCP_NODELAY set
* connect to 0.0.0.0 port 80 failed: Connection refused
* Failed to connect to 0.0.0.0 port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to 0.0.0.0 port 80: Connection refused
monet:~> curl -v 127.0.0.1
*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* connect to 127.0.0.1 port 80 failed: Connection refused
* Failed to connect to 127.0.0.1 port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused

There's also the thing that something might already be bound to 127.0.0.1:80 or 127.0.0.1:443.

Let me try to set it up using RPZ instead!

ioc32 commented 3 years ago

So I followed this.

Added the following to the server section:

    module-config: "respip validator iterator"
    rpz:
        name: rpz.home.lan
        zonefile: "/var/unbound/rpz.home.lan.zone"

And populated the fake zone:

openhrc# head /var/unbound/rpz.home.lan.zone  
$ORIGIN rpz.home.lan
n2019cov.000webhostapp.com CNAME .
webmail-who-int.000webhostapp.com CNAME .
010sec.com CNAME .
01mspmd5yalky8.com CNAME .
0byv9mgbn0.com CNAME .
ns6.0pendns.org CNAME .
dns.0pengl.com CNAME .
ios.0pengl.com CNAME .
0x4fc271.tk CNAME .

openhrc# tail /var/unbound/rpz.home.lan.zone  
zenzuu.com CNAME .
zeus.developershed.com CNAME .
zeusclicks.com CNAME .
zlp6s.pw CNAME .
zm232.com CNAME .
zmedia.com CNAME .
zpu.samsungelectronics.com CNAME .
zqtk.net CNAME .
zukxd6fkxqn.com CNAME .
zy16eoat1w.com CNAME .

These are the posible actions:

Action RR type and RDATA
NXDOMAIN CNAME .
NODATA CNAME *.
PASSTHRU CNAME rpz-passthru.
DROP CNAME rpz-drop.

Applied the config by restarting, cause of the modules:

openhrc# rcctl restart unbound  
unbound(ok)
unbound(ok)

We get, from a cold cache:

monet:~> dig @10.0.0.1 a n2019cov.000webhostapp.com

; <<>> DiG 9.11.2 <<>> @10.0.0.1 a n2019cov.000webhostapp.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 25874
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;n2019cov.000webhostapp.com.    IN      A

;; Query time: 1 msec
;; SERVER: 10.0.0.1#53(10.0.0.1)
;; WHEN: Fri Sep 11 19:51:43 CEST 2020
;; MSG SIZE  rcvd: 55

monet:~> dig @8.8.8.8 a n2019cov.000webhostapp.com

; <<>> DiG 9.11.2 <<>> @8.8.8.8 a n2019cov.000webhostapp.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38759
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;n2019cov.000webhostapp.com.    IN      A

;; ANSWER SECTION:
n2019cov.000webhostapp.com. 3599 IN     CNAME   us-east-1.route-1.000webhost.awex.io.
us-east-1.route-1.000webhost.awex.io. 59 IN A   145.14.145.148

;; Query time: 25 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Sep 11 19:51:46 CEST 2020
;; MSG SIZE  rcvd: 121

TL;DR it seems to just work from base

ioc32 commented 3 years ago

We can use something naive like this to be run daily (until it breaks):

#!/bin/ksh

set -e

URL=https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
RPZ_ZONE=rpz.home.lan
TMP_ZONE=`mktemp -t $RPZ_ZONE-zone.XXXXXXXXXX`
TMP_HOSTS=`mktemp -t $RPZ_ZONE-hosts.XXXXXXXXXX`
DEST_ZONE=/var/unbound/${RPZ_ZONE}.zone

ftp -MV -o $TMP_HOSTS $URL
awk -v RPZ_ZONE="$RPZ_ZONE." \
    'BEGIN {printf "$ORIGIN %s\n",RPZ_ZONE;
                  printf "%s IN SOA ns1.%s admin.%s 0000000001 86400 7200 604800 300\n",RPZ_ZONE,RPZ_ZONE,RPZ_ZONE}
    /^0\.0\.0\.0.+[a-z]$/ {printf "%s CNAME .\n",$NF}' $TMP_HOSTS > $TMP_ZONE

nsd-checkzone $RPZ_ZONE $TMP_ZONE >/dev/null
mv -f $TMP_ZONE $DEST_ZONE
chown _unbound:_unbound $DEST_ZONE
unbound-control -q reload

rm -f $TMP_HOSTS $TMP_ZONE

It will not output anything unless there are errors, which will be wrapped in an email and sent to the local root mailbox.

Tested it a bit already.