BIND: RPZ

From OnnoWiki
Jump to navigation Jump to search
Using Domain Name Service Response Policy Zones (DNS RPZ) With Shallalists


MAY 8, 2014, 7:05 PM - Mark Page [+markpage]

I'll be demonstrating the usage of DNS RPZ as a content filtering mechanism for local area networks (LANs) using Shallalists. DNS filtering could be applicable to anyone from K-12 schools seeking CIPA compliance, to small-to-medium businesses wanting additional, non-invasive malware protection. This should be in no way considered a definitive RPZ guide; we'll only be scratching the surface of what RPZ can really do.

Domain Name Service Response Policy Zones (DNS RPZ) is a method that allows a nameserver administrator to overlay custom information on top of the global DNS to provide alternate responses to queries. It is currently implemented in the ISC BIND nameserver (9.8 or later). Another generic name for the DNS RPZ functionality is "DNS firewall". For more info see: https://dnsrpz.info/

Shalla's Blacklists is a collection of URL lists grouped into several categories intended for the usage with URL filters like SquidGuard or Dansguardian. Shalla's Blacklists are free of charge for personal use. For more info see: http://www.shallalist.de/.

We'll be using Ubuntu Server 14.04 with BIND 9.9.5-3 for all our examples. This articles assumes the reader has met these requirements and is comfortable working from the terminal. For a basic BIND and Ubuntu installation guide, see http://www.tecmint.com/install-dns-server-in-ubuntu-14-04/.

BIND Configuration for RPZ This is a sample of a BIND options configuration file (/etc/bind/named.conf.options). We'll be using Google's DNS servers as our forwarders, and a zone named "srpz.zone" for our response policy. Our actual RPZ DB file will be "db.srpz.local":

options {
    directory "/var/cache/bind";

    forwarders {
        8.8.8.8;
        8.8.4.4;
    };

    dnssec-validation auto;
    auth-nxdomain no;
    listen-on-v6 { any; };
   
    check-names master ignore;
    check-names slave  ignore;

    response-policy { zone "srpz.zone"; };
};

zone "srpz.zone" {
    type master;
    file "/etc/bind/db.srpz.local";
    allow-query { any; };
    allow-update { none; };
};

Note: Setting the "check-names" options to "ignore" is not generally recommended for use on DNS servers as these are used to check compliance with the RFCs defining valid host names. We need to disable this checking to deal with inconsistencies in the blacklists we'll be converting later. For our purposes, this will not create any security issues as we're not allowing zone transfers.

The "named.conf.options" shown here may be downloaded from the files section at the bottom of this page.

Building the Zone File We have a few choices on how to handle requests to sites we're blocking. One method (the easiest) is to return an NXDOMAIN response to the client, prompting a "web page not available" or similar error.

The second method is to define a valid CNAME or A record in the DNS response pointing to a host known by a secondary DNS server, or allowed upstream. This is often referred to as a "walled garden", and is usually a simple web page telling the user the URL was blocked.

First, we'll setup the header for our zone file (/etc/bind/zone.srpz.local):

$TTL    604800
@       IN      SOA     localhost.local. hostmaster.local. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
@       IN      NS      localhost.local.
@       IN      A       127.0.0.1
@       IN      AAAA    ::1
;

Further entries will consist of CNAME records to redirect the requested domain, and domain wildcards. Here's an example of the two lines needed to configure the RPZ to return NXDOMAIN:

badsite.com IN CNAME .
*.badsite.com IN CNAME .

Should we wish to redirect the user to a block page, we would add an entry for a host known by our secondary or upstream resolver:

badsite.com IN CNAME goodsite.com.
*.badsite.com IN CNAME goodsite.com.

Or use an A record pointing to an IP Address:

badsite.com IN A 192.168.2.1
*.badsite.com IN A 192.168.2.1


Granted, the examples shown here are greatly simplified. This would be fine (and manageable) if one were maintaining a few sites. Next we'll look at what it takes to add curated block lists to our RPZ setup.

Adding CNAME Entries Using Shallalists Download the tarball from Shalla's website and extract it to /tmp using:

wget http://www.shallalist.de/Downloads/shallalist.tar.gz -O /tmp/shallalist.tar.gz tar -xf /tmp/shallalist.tar.gz -C /tmp/

When it's done, you should have the directory "BL" in /tmp (/tmp/BL). Within this folder will be many subfolders, each titled by a category grouping (porn, gambling, spyware, etc.), with the file "domains" in each subfolder. The "domains" file is a list of, well, domains. Our task is to walk the desired category directories and build a list of the domains we wish to deny from each "domains" file, then convert the list into an RPZ (zone file).

There are a few problems we'll have to deal with along the way. First, we'll need to ensure there are no duplicate entries in the zone file. It's possible (nay, probable) that a domain may fall into more than one selected category, possibly creating duplicate CNAME records pointing to different destinations; BIND would complain and refuse to load the zone. Second, we need to be sure that the entries are [reasonably] RFC compliant.

It's also worth noting that some of the categories are quite large. The porn category alone contains over 800,000 domains as of this writing. And because we're making two entries per domain, a second for the wildcard entry, our RPZ file could easily exceed 100MB.

To piece this all together, we're going to use a Perl script that will:

   Build an array of the category "domains" directories/files we wish to include.
   Open and read the contents of each selected "domains" file into a second array, 
   Walk the secondary array and build a hash, indexed by the domain names to remove duplicates.
   Output a usable RPZ file.

At a most minimal level, here's what that looks like:

Assuming you've downloaded and extracted the tarball from Shalla, we're ready to get started. After running the script above, you'll be left with the file "db.srpz.local" in your current working directory. To add or remove categories, simply edit the @categories array.

To use this script, download the above to your server using:

wget "https://gist.githubusercontent.com/voodoojello/38e979a3b130aa9262af/raw/5cf74542dbe5b3af9d62ea07e31f724a13921bf7/make-shalla-rpz.pl"

To output an NXDOMAIN return (site not found) to the client, run this script with no arguments:

perl make-shalla-rpz.pl

To output redirection to a specific CNAME destination (block page) to the client, use "CNAME" and the host name as arguments:

perl make-shalla-rpz.pl CNAME some.redirect.host

To output redirection to a specific CNAME destination based on the category in which the URL was found, use the CATEGORY macro (case sensitive):

perl make-shalla-rpz.pl CNAME CATEGORY.redirect.host

To output redirection to a specific IP address to the client ("A" record), use "A" and the address as the arguments:

perl make-shalla-rpz.pl A 192.168.2.1

Install the RPZ zone file using:

sudo cp ./db.srpz.local /etc/bind/.

Since we may be loading BIND for the first time with our new zone changes, we'll need to restart the service:

sudo /etc/init.d/bind9 restart

For future updates, have BIND reload the zone using rndc:

sudo rndc reload srpz.zone


make-shalla-rpz.pl

#!/usr/bin/perl -w
#
# Shallalist to DNS RPZ 
# Author: Mark Page [m.e.page_at_gmail.com]
# Modified: Sun May 11 06:19:05 CDT 2014
#
# Examples:
#   perl make-shalla-rpz.pl (no arg, creates NXDOMAIN CNAME ".")
#   perl make-shalla-rpz.pl A 192.168.2.1 (creates "A" redirect)
#   perl make-shalla-rpz.pl CNAME nowhere.local (creates "CNAME" redirect)
#   perl make-shalla-rpz.pl CNAME CATEGORY.local (creates category "CNAME" redirect)
#
use strict;
use warnings;

my ($urls);
my @categories = ('porn','warez','anonvpn','spyware','redirector',);

for my $c (0 .. (scalar(@categories) - 1)) {
 	open (my $list,'<',"/tmp/BL/$categories[$c]/domains");
 	chomp(my @domains = <$list>); 
 	close($list);
 	
 	for my $d (0 .. (scalar(@domains) - 1)) {
		$urls->{lc($domains[$d])} = $categories[$c];
	}
}

open (my $db,'>',"./db.srpz.local");
print $db '$TTL    604800
@       IN      SOA     localhost.local. hostmaster.local. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
@       IN      NS      localhost.local.
@       IN      A       127.0.0.1
@       IN      AAAA    ::1
;
';

while (my ($key, $value) = each(%$urls) ) {
	my $redirect = 'CNAME .';

	if (defined($ARGV[0]) and defined($ARGV[1])) {
		$redirect = uc($ARGV[0]) . ' ' . $ARGV[1];
		if ($ARGV[1] =~ m/CATEGORY/) {
			$redirect =~ s/CATEGORY/$value/;
		}
	}

	if (substr($key,0,1) ne '.') {
		print $db $key . ' IN ' . $redirect . "\n";
		print $db '*.' . $key . ' IN ' . $redirect . "\n";
	}
}
close($db);

exit;

__END__



Referensi