#devops #nomad #hashicorp #pihole
Pi-Hole can be used not only to filter aggressive ads, but also unwanted content such as phishing, and malware or to block specific sites such as Facebook or TikTok.
In this article, we’ll explore how to configure Pi-hole in Nomad and how to select the lists for blocking.
tl;dr; scroll to the bottom of the article has full example, example also on github.
Software versions
Software versions used for this post:
- Hashicorp’s Nomad 1.9.2(cluster, standalone or dev)
- Docker 24
Choosing blocklists
The most important step in configuring a Pi-Hole is to select which content you wish to filter. Two excellent indexes you can look at are blocklist project on Github and Firebog. More lists and indexes can easily be found on the internet.
In this example, we’ll be using the malware list and the phishing list.
Templates
For this network job, we’ll be using 3 templates:
- List of blocklists
- List of individual DNS overrides
- Script to pipe blocklists into the sqlite database
template {
data = <<EOH
https://github.com/blocklistproject/Lists/blob/master/malware.txt
https://github.com/blocklistproject/Lists/blob/master/phishing.txt
EOH
perms = "755"
destination = "local/sources.list"
}
Note in the above example, each blocklist is on it’s on line. You simply need to replace in all the lists you want to use for filtering.
You may want to have some custom domains configured, so we configure the following template for this very purpose:
template {
data = <<EOH
#!/bin/bash
192.168.1.1 something.example.net
0.0.0.0 thatonewebsiteiwannablock.example.net
EOH
perms = "444"
destination = "local/custom.list"
}
The final template is a simple script for loading up the list names into sqlite, the template is only listed in the final example for brevity.
Environment stanza
The environment stanza for this job WEBPASSWORD and DNSMASQ_LISTENING.
WEBPASSWORD should contain a unique password for accessing the web interfaface.
DNSMASQ_LISTENING, just set it to “all”, this tells Pi-Hole to bind to all interfaces and leaves the networking to Nomad.
Network stanza
network {
port "dns" {
to = 53
static = 5454
}
port "web" {
to = 80
}
}
Note: The above network block is what I am using for my example, you can change this stanza to suite your needs.
I have defined two ports, web and DNS. The web port is good to expose in case I want to login to the pihole web interface.
I have DNS on a static port 5454 just to make it easy to test but not conflict with any existing DNS services on the nomad client.
In my home network setup, where I have pihole filtering one of my Wifi networks, the network stanza looks like this:
network {
port "dns" {
static = 53
to = 53
host_network = "private"
}
port "web" {
to = 80
host_network = "private"
}
}
In my home Nomad cluster host_network “private” actually represents the private side of my router. This means that in effect the above is going to run on 192.168.34.1:53 which is the DNS server announced on one of my home Wifi networks.
Really, the network stanza will really depend on your setup and how you are reaching DNS. You may need to review the DNSMASQ_LISTENING environment variable as well.
Task stanza
The last piece of all of this is of course the task stanza. In this case, we’re using docker and the image pihole/pihole.
We can see that ports, entrypoint, and volumes attributes are set referring to stanzas from earlier.
In this case, we are using the latest image, though best practice is always to peg to a specific version so you can control version updates.
task "server" {
driver = "docker"
config {
cap_add=["net_bind_service","chown"]
image = "pihole/pihole:latest"
entrypoint = ["/local/entrypoint.sh"]
volumes =["local/custom.list:/etc/pihole/custom.list"]
ports = [
"dns", "web"
]
}
End result
Here is the full job:
job "pi-hole" {
group "pi-hole" {
disconnect {
lost_after = "24h"
}
network {
port "dns" {
static = 53
}
port "web" {
to = 80
}
}
task "server" {
driver = "docker"
config {
image = "pihole/pihole:latest"
entrypoint = ["/local/entrypoint.sh"]
volumes = ["local/custom.list:/etc/pihole/custom.list"]
ports = [
"dns", "web"
]
}
template {
data = <<EOH
#!/bin/bash
192.168.1.1 something.example.net
EOH
perms = "444"
destination = "local/custom.list"
}
template {
data = <<EOH
https://github.com/blocklistproject/Lists/blob/master/malware.txt
https://github.com/blocklistproject/Lists/blob/master/phishing.txt
EOH
perms = "755"
destination = "local/sources.list"
}
template {
data = <<EOH
#!/bin/bash
sqlite3 /etc/pihole/gravity.db "DELETE FROM adlist;"
for url in $(cat /local/sources.list)
do
echo "Adding $url to pihole!";
sqlite3 /etc/pihole/gravity.db "INSERT INTO adlist (address, enabled, comment) VALUES ('$url',1,'');"
done
exec /s6-init
EOH
perms = "755"
destination = "local/entrypoint.sh"
}
env {
DNSMASQ_LISTENING = "all"
WEBPASSWORD = "REPLACE_THIS_WITH_A_SECURE_PASSWORD"
}
}
}
}
How do we test this, quick dig command:
dig -p5454 google.com @192.168.34.1
In my case, the job is running on 192.168.34.1, but you’ll want to change the IP to the nomad client running your job. The expect answer is a valid and reachable
Now try running dig against domains you are looking to block and you see the IP as ‘0.0.0.0’, this confirms the domain is blocked.
Conclusion
So what’s next? In my case I run the job on port 53 on the IP address that the DHCP server serves as the DNS address. With DHCP you can push clients to use the DNS server of your choice, but the port has to be port 53. So unless you have a proxy of some sort redirecting requests to your PiHole, you’ll need to run it on port 53.
Let me know how it works out in the comments below!
Leave a Reply