Official site launch very soon, hurrah!
Amazon EC2 is a great resource for cheap virtual servers to do simple things, like DNS or (low bandwidth) VPNs. I had the need this morning to set up a DNS server for a company which needed to blacklist a list of domains. The simplest way to do this is by editing all the computers’ hostfiles, but that method leaves a lot to be desired. Namely, blocking entire domains (as opposed to single subdomains), and deploying changes. Centralizing in a single place makes the job instant, immediate, and in the end, faster.
The following are the steps I used to set this up on an EC2 server. All command line instructions are followed by a single command you can run to execute the step. There is a full script below, at the end of the post, containing all steps from when you first login to SSH ("Login to root") to the end.
I am not going to go into the details of setting up an EC2 instance, as that information can be found elsewhere. I will also be skipping over some of the more obvious steps. Just create a default EC2 instance with the “Amazon Linux AMI”, and I will list all the changes that need to be made beyond that.
$TTL 14400@ IN SOA dns.yourdomain.com. dns.yourdomain.com ( 2003052800 86400 300 604800 3600 )@ IN NS dns.yourdomain.com.@ IN A 1.1.1.1* IN A 1.1.1.1
YOURDOMAIN="dns.yourdomain.com"; YOURIP="1.1.1.1";echo -ne "\$TTL 14400\n@ IN SOA $YOURDOMAIN. $YOURDOMAIN ( 2003052800 86400 300 604800 3600 )\n@ IN NS $YOURDOMAIN.\n@ IN A $YOURIP\n* IN A $YOURIP" > /var/named/blacklisted.db;
RewriteEngine onRewriteCond %{REQUEST_URI} !index.htmlRewriteCond %{REQUEST_URI} !AddRules/RewriteRule ^(.*)$ /index.html [L]
<?php//Get old domains$BlockedFile='/var/named/blacklisted.conf';$CurrentZones=Array();foreach(explode("\n", file_get_contents($BlockedFile)) as $Line) if(preg_match('/^zone "([\w\._-]+)"/', $Line, $Results)) $CurrentZones[]=$Results[1];//List domainsif(isset($_REQUEST['List'])) return print implode('', $CurrentZones);//Get new domainsif(!isset($_REQUEST['Domains'])) return print 'Missing Domains';$Domains=$_REQUEST['Domains'];if(!preg_match('/^[\w\._-]+(,[\w\._-]+)*$/uD', $Domains)) return print 'Invalid domains string';$Domains=explode(',', $Domains);//Remove domainsif(isset($_REQUEST['Remove'])){ $CurrentZones=array_flip($CurrentZones); foreach($Domains as $Domain) unset($CurrentZones[$Domain]); $FinalDomainList=array_keys($CurrentZones);}else //Combine domains $FinalDomainList=array_unique(array_merge($Domains, $CurrentZones));//Output to the file$FinalDomainData=Array();foreach($FinalDomainList as $Domain) $FinalDomainData[]= "zone \"$Domain\" { type master; file \"blacklisted.db\"; };";file_put_contents($BlockedFile, implode("\n", $FinalDomainData));//Reload namedprint `sudo /var/www/html/AddRules/restart_named`;?>
AuthType BasicAuthName "Admins Only"AuthUserFile "/var/www/html/AddRules/.htpasswd"require valid-user
To permanently set “localhost” as the resolver DNS, add “DNS1=localhost” to “/etc/sysconfig/network-scripts/ifcfg-eth0”. I have not yet confirmed this edit.
Soon after setting up this DNS server, it started getting hit by a DNS amplification attack. As the server is being used as a client’s DNS server, turning off recursion is not available. The best solution is to limit the people who can query the name server via an access list (usually a specific subnet), but that would very often not be an option either. The solution I currently have in place, which I have not actually verified if it works, is to add a forced-forward rule which only makes external requests to the name server provided by Amazon. To do this, get the name server’s IP from /etc/resolv.conf (it should be commented from an earlier step). Then add the following to your named.conf in the “options” section.
forwarders { DNS_SERVER_IP; }; forward only;
After I added this rule, external DNS requests stopped going through completely. To fix this, I turned “dnssec-validation” to “no” in the named.conf. Don’t forget to restart the service once you have made your changes.
#User defined variablesVARIABLES_SET=0; #Set this to 1 to allow the script to runYOUR_DOMAIN="localhost";YOUR_IP="1.1.1.1";BLOCKED_ERROR_MESSAGE="Domain is blocked";ADDRULES_USERNAME="YourUserName";ADDRULES_PASSWORD="YourPassword";#Confirm script is ready to runif [ $VARIABLES_SET != 1 ]; then echo 'Variables need to be set in the script'; exit 1;fiif [ `whoami` != 'root' ]; then echo 'Must be root to run script. When running the script, add "sudo" before it to' \ 'run as root'; exit 1;fi#Allow root logincat /home/ec2-user/.ssh/authorized_keys > /root/.ssh/authorized_keys;perl -pi -e 's/^\s*#?\s*PermitRootLogin.*$/PermitRootLogin yes/igm' /etc/ssh/sshd_config;service sshd reload;#Install servicesyum -y install bind httpd php;chkconfig httpd on;chkconfig named on;service httpd start;service named start;#Set the DNS server to be usable by other computersperl -pi -e 's/^(\s*(?:listen-on port 53|allow-query)\s*{).*$/$1 any; };/igm' \ /etc/named.conf;service named reload;#Create/link the blacklist filesecho -ne '\ninclude "/var/named/blacklisted.conf";' >> /etc/named.conf;touch /var/named/blacklisted.conf;#Create the blacklist zone fileecho -ne "\$TTL 14400@ IN SOA $YOUR_DOMAIN. $YOUR_DOMAIN ( 2003052800 86400 300 604800 3600 )@ IN NS $YOUR_DOMAIN.@ IN A $YOUR_IP* IN A $YOUR_IP" > /var/named/blacklisted.db;#Fix the permissions on the blacklist fileschgrp named /var/named/blacklisted.*;chmod 660 /var/named/blacklisted.*;#Set the server’s domain resolution name serversperl -pi -e 's/^(?!;)/;/gm' /etc/resolv.conf;echo -ne '\nnameserver localhost' >> /etc/resolv.conf;#Run a testecho 'zone "example.com" { type master; file "blacklisted.db"; };' >> \ /var/named/blacklisted.conf;service named reload;FOUND_IP=`dig -t A example.com | grep -ioP "^example\.com\..*?"'in\s+a\s+[\d\.:]+' | \ grep -oP '[\d\.:]+$'`;if [ "$YOUR_IP" == "$FOUND_IP" ]then echo 'Success: Example domain matches your given IP' > /dev/stderr;else echo 'Warning: Example domain does not match your given IP' > /dev/stderr;fi#Have the server return a message when a blacklisted domain is accessedecho "$BLOCKED_ERROR_MESSAGE" > /var/www/html/index.html;perl -0777 -pi -e 's~(<Directory "/var/www/html">.*?\n\s*AllowOverride).*?\n~$1 All~s' \ /etc/httpd/conf/httpd.conf;echo -n 'RewriteEngine onRewriteCond %{REQUEST_URI} !index.htmlRewriteCond %{REQUEST_URI} !AddRules/RewriteRule ^(.*)$ /index.html [L]' > /var/www/html/.htaccess;service httpd graceful;#Create a script that allows apache to refresh the name server’s settingsmkdir /var/www/html/AddRules;echo '/sbin/service named reload' > /var/www/html/AddRules/restart_named;chmod 755 /var/www/html/AddRules/restart_named;echo 'apache ALL=(root) NOPASSWD:/var/www/html/AddRules/restart_namedDefaults!/var/www/html/AddRules/restart_named !requiretty' >> /etc/sudoers;#Create a script that allows the user to add, remove, and list the blacklisted domainsecho -n $'<?php//Get old domains$BlockedFile=\'/var/named/blacklisted.conf\';$CurrentZones=Array();foreach(explode("\\n", file_get_contents($BlockedFile)) as $Line) if(preg_match(\'/^zone "([\\w\\._-]+)"/\', $Line, $Results)) $CurrentZones[]=$Results[1];//List domainsif(isset($_REQUEST[\'List\'])) return print implode(\'\', $CurrentZones);//Get new domainsif(!isset($_REQUEST[\'Domains\'])) return print \'Missing Domains\';$Domains=$_REQUEST[\'Domains\'];if(!preg_match(\'/^[\\w\\._-]+(,[\\w\\._-]+)*$/uD\', $Domains)) return print \'Invalid domains string\';$Domains=explode(\',\', $Domains);//Remove domainsif(isset($_REQUEST[\'Remove\'])){ $CurrentZones=array_flip($CurrentZones); foreach($Domains as $Domain) unset($CurrentZones[$Domain]); $FinalDomainList=array_keys($CurrentZones);}else //Combine domains $FinalDomainList=array_unique(array_merge($Domains, $CurrentZones));//Output to the file$FinalDomainData=Array();foreach($FinalDomainList as $Domain) $FinalDomainData[]="zone \\"$Domain\\" { type master; file \\"blacklisted.db\\"; };";file_put_contents($BlockedFile, implode("\\n", $FinalDomainData));//Reload namedprint `sudo /var/www/html/AddRules/restart_named`;?>' > /var/www/html/AddRules/index.php;usermod -a -G named apache;service httpd graceful;#Password protect the domain update scriptecho -n 'AuthType BasicAuthName "Admins Only"AuthUserFile "/var/www/html/AddRules/.htpasswd"require valid-user' > /var/www/html/AddRules/.htaccess;htpasswd -bc /var/www/html/AddRules/.htpasswd "$ADDRULES_USERNAME" "$ADDRULES_PASSWORD";echo 'Script complete';