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.
Creating the instance
For the first year, for the instance type, you might as well use a t2.micro, as it is free. After that, a t2.nano (which is a new lower level) currently at $56.94/year ($0.0065/Hour), should be fine.
After you select your instance type, click “Review and Launch” to launch the instance with all of the defaults.
After the confirmation screen, it will ask you to create a key pair. You can see other tutorials about this and how it enables you to log into your instance.
Edit the security group
Next, you need to edit the security group for your instance to allow incoming connections.
Go to “Instances” under the “Instances” group on the left menu, and click your instance.
In the bottom of the window, in the “Descriptions” tab, click the link next to “Security Groups”, which will bring you to the proper group in the security groups tab.
Right click it and “Edit inbound Rules”.
Make sure it has the following rules with Source=Anywhere: ALL ICMP [For pinging], SSH, HTTP, DNS (UDP), DNS (TCP)
Assign a permanent IP to your instance
To do this, click the “Elastic IPs” under “Network & Security” in the left menu.
Click “Allocate New Address”.
After creating it, right click the new address, then “Associate Address”, and assign it to your new instance.
You should probably set this IP up as an A record somewhere. I will refer to this IP as dns.yourdomain.com from now on.
Login to root
SSH into your instance as the ec2-user via “ssh ec2-user@dns.yourdomain.com”. If in windows, you could also use putty.
Sudo into root via “sudo su”.
Allow root login
At this point, I recommend setting it up so you can directly root into the server. Warning: some people consider this a security risk.
Copy your key pair(s) to the root user via “cat /home/ec2-user/.ssh/authorized_keys > /root/.ssh/authorized_keys”
Set SSHD to permit root logins by changing the PermitRootLogin variable to “yes” in /etc/ssh/sshd_config. A quick command to do this is “perl -pi -e 's/^\s*#?\s*PermitRootLogin.*$/PermitRootLogin yes/igm' /etc/ssh/sshd_config”, and then reload the SSHD config with “service sshd reload”. Make sure to attempt to directly log into SSH as root before exiting your current session to make sure you haven’t locked yourself out.
Install apache (the web server), bind/named (the DNS server), and PHP (a scripting language)
yum -y install bind httpd php
Start and set services to run at boot
service httpd start; service named start; chkconfig httpd on; chkconfig named on;
Set the DNS server to be usable by other computers
Edit /etc/named.conf and change the 2 following lines to have the value “any”: “listen-on port 53” and “allow-query”
perl -pi -e 's/^(\s*(?:listen-on port 53|allow-query)\s*{).*$/$1 any; };/igm' /etc/named.conf; service named reload;
Point the DNS server to the blacklist files
This is done by adding “include "/var/named/blacklisted.conf";” to /etc/named.conf
Put the following into /var/named/blacklisted.db . Make sure to change dns.yourdomain.com to your domain (or otherwise, “localhost”), and 1.1.1.1 to dns.yourdomain.com’s (your server’s) IP address. Make sure to keep all periods intact.
$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
The first 2 lines tell the server the domains belong to it. The 3rd line sets the base blacklisted domain to your server’s IP. The 4th line sets all subdomains of the blacklisted domain to your server’s IP.
This can be done via (Update the first line with your values)
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;
Fix the permissions on the blacklist files
chgrp named /var/named/blacklisted.*; chmod 660 /var/named/blacklisted.*;
Set the server’s domain resolution name servers
The server always needs to look at itself before other DNS servers. To do this, comment out everything in /etc/resolv.conf and add to it “nameserver localhost”. This is not the best solution. I’ll find something better later.
At this point, it’s a good idea to make sure the DNS server is working as intended. So first, we’ll add an example domain to the DNS server.
Add the following to /var/named/blacklisted.conf and restart named to get the server going with example.com: “zone "example.com" { type master; file "blacklisted.db"; };”
echo 'zone "example.com" { type master; file "blacklisted.db"; };' >> /var/named/blacklisted.conf; service named reload;
Ping “test.example.com” and make sure it’s IP is your server’s IP
Set your computer’s DNS to your server’s IP in your computer’s network settings, ping “test.example.com” from your computer, and make sure the returned IP is your server’s IP. If it works, you can restore your computer’s DNS settings.
Have the server return a message when a blacklisted domain is accessed
Add your message to /var/www/html
echo 'Domain is blocked' > /var/www/html/index.html
Set all URL paths to show the message by adding the following to the /var/www/html/.htaccess file
Turn on AllowOverride in the /etc/httpd/conf/httpd.conf for the document directory (/var/www/html/) via “ perl -0777 -pi -e 's~(<Directory "/var/www/html">.*?\n\s*AllowOverride).*?\n~$1 All~s' /etc/httpd/conf/httpd.conf”
Start the server via “service httpd graceful”
Create a script that allows apache to refresh the name server’s settings
Create a script at /var/www/html/AddRules/restart_named with “/sbin/service named reload” and set it to executable
Allow the user to run the script as root by adding to /etc/sudoers “apache ALL=(root) NOPASSWD: /var/www/html/AddRules/restart_named” and “Defaults!/var/www/html/AddRules/restart_named !requiretty”
Create a script that allows the user to add, remove, and list the blacklisted domains
Add the following to /var/www/html/AddRules/index.php (one line command not given. You can use “nano” to create it)
<?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']))returnprintimplode('<br>', $CurrentZones);//Get new domainsif(!isset($_REQUEST['Domains']))returnprint'Missing Domains';$Domains=$_REQUEST['Domains'];if(!preg_match('/^[\w\._-]+(,[\w\._-]+)*$/uD', $Domains))returnprint'Invalid domains string';$Domains=explode(',', $Domains);//Remove domainsif(isset($_REQUEST['Remove'])){$CurrentZones=array_flip($CurrentZones);foreach($Domainsas$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($FinalDomainListas$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`;?>
Add the “apache” user to the “named” group so the script can update the list of domains in /var/named/blacklisted.conf via “usermod -a -G named apache; service httpd graceful;”
Run the domain update script
To add a domain (separate by commas): http://dns.yourdomain.com/AddRules/?Domains=domain1.com,domain2.com
To remove a domain (add “Remove&” after the “?”): http://dns.yourdomain.com/AddRules/?Remove&Domains=domain1.com,domain2.com
To list the domains: http://dns.yourdomain.com/AddRules/?List
Warning: Putting the password file in an http accessible directory is a security risk. I just did this for sake of organization.
Create the user+password via “htpasswd -bc /var/www/html/AddRules/.htpasswd USERNAME” and then entering the password
[Edit on 2016-01-30 @ noon]
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.
Security Issue
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.
Make sure to run this as root (login as root or sudo it)
Download the script here. Make sure to chmod and sudo it when running. “chmod +x dnsblacklist_install.sh; sudo ./dnsblacklist_install.sh;”
#User defined variables
VARIABLES_SET=0; #Set this to 1 to allow the script to run
YOUR_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 ];thenecho'Variables need to be set in the script';exit 1;fiif [ `whoami`!='root' ];thenecho'Must be root to run script. When running the script, add "sudo" before it to' \
'run as root';exit 1;fi#Allow root login
cat /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 services
yum -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 computers
perl -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 files
chgrp named /var/named/blacklisted.*;
chmod 660 /var/named/blacklisted.*;#Set the server’s domain resolution name servers
perl -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" ]
thenecho'Success: Example domain matches your given IP'> /dev/stderr;elseecho'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 settings
mkdir /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(\'<br>\', $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';