Recently I had to change some parameters on my LAN setup, so I decided to document here the process and tools that I used and explain why I’m doing this things that aren’t required for fastest network but for better privacy.
Let’s be honest who likes to see popups, banners and ads in every site that we use? Because of that I’m always used extensions for browser like AdBlocker Ultimate, Privacy Badger, Don’t track me Google and uBlock origin. And it was okay for just desktop browsering with Ad blocking, then I realized that most of ads that I was receiving was in my Android phone (Xiaomi Redmi Note 9s, then Samsumg S21 FE) and there is no “ad blocker” for a whole Operating System neither specific apps like Whatsapp, Instagram, Youtube, etc…
At the moment I was already confortable with network concepts like DNS, Firewalls, HTTP, servers and linux to work as Web Developer, deploying applications on managed clouds and setting up public and private networks. Fortunately, youtube channels that I was following (and follow yet) published contents about “Ads Protected Home Network” like Diolinux, Fabio Akita and Slackjeff. So I decided to see what they’re talking about.
Setup
For this setup I will be using my old HP notebook with:
- CPU: Intel i5-3230M 2.60GHz (2 cores)
- Memory: 8gb
- Storage: 120gb SSD Sata
- OS: Debian Bookworm
This is a minimal Debian installation with net iso with bare packages to get apt and network out-of-the-box.
Through this post we’ll install and setup:
The only thing that we’ll need to setup manually is SSH, because every other program will be automated through Ansible.
We could have done everything in this tutorial manually through SSH or created a bash script to automate all steps, but I chose Ansible for two reasons: to learn and because it is more modular to run all together or isolated tasks.
SSH
If you choose root password in installation you wouldn’t have sudo
command,
if you want it you’ll have to run as root:
su - # switch to root user
apt update # update repositories
apt install sudo # install sudo package
adduser <username> sudo # add <usernam> to sudo group
Anyway, with sudo permission or as root user, install openssh-server
package:
apt install openssh-server
Now we have ssh server installed and sshd
running. You can try ssh connection with:
ssh <username>@<ip-address> # example: ssh user@192.168.50.9
Note that we were asked to type user password to connect this time. As we will be connecting through ssh many times and automating with Ansible, it’s highly recommended to generate pair keys for authentication instead passwords. We can do it running our desktop (not the debian server):
ssh-keygen -t rsa -b 4096 -C "<any comment you want>" # generate rsa public/private key with 4096 bytes and comment
ls -la ~/.ssh # check if your keys are here
ssh-copy-id -i ~/.ssh/<publicKey>.pub <username>@<ipAddress> # copy your public rsa key to debian server
Now that we have our RSA keys configured, we can change ssh server settings in /etc/ssh/sshd_config
with:
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config \ # disables login as root
&& sed -i 's/#MaxAuthTries 6/MaxAuthTries 3/' /etc/ssh/sshd_config \ # set max authentication tries to 3
&& sed -i 's/#MaxSessions 10/MaxSessions 3/' /etc/ssh/sshd_config \ # set max sessions to 3
&& sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config \
&& sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config \ # disable password authentication
&& sed -i 's/#PermitEmptyPasswords no/PermitEmptyPasswords no/' /etc/ssh/sshd_config \
&& sed -i 's/X11Forwarding yes/X11Forwarding no/' /etc/sshd_config
systemctl restart sshd # restart sshd service to apply changes
From now on, we’ll be using our private rsa key to authenticate on ssh server.
For Ansible automation, we need python installed on our server:
apt install python3
Ansible
Instead of connecting on SSH manually and running commands step by step, we can create tasks in ansible to do that for us, keeping track in our git repository.
In our desktop, we will create a repository:
mkdir homelab
git init -b main
The first thing we need to do is install ansible in our host to be the control node, the source where sends commands to configure hosts
# Install python on Debian-based distros
sudo apt install python3 python3-pip pipx
# Install python on Arch-based distros
sudo pacman -S python python-pip python-pipx
# Setup pipx
pix ensurepath
# Install ansible
pipx install --include-deps ansible
Generate the scaffolding for tasks:
mkdir roles
cd roles
ansible-galaxy init docker
ansible-galaxy init pihole
ansible-galaxy init firewall
ansible-galaxy init cloudflared
ansible-galaxy init heimdall
Firewall
As we’ll make it a DNS server, for security it’s recommended that we allow just the necessary ports for communication.
In this case, we’ll install ufw
package and enable ssh, http/https and dns ports
only.
We’ll setup Firewall roles/firewall/tasks/main.yaml
with:
Note that ssh port is not the default 22 and does not accept any host, that because we want to reduce the possible connections to the server through ssh
---
- name: install firewall
apt:
name:
- ufw
state: present
update_cache: yes
- name: enable ssh port
community.general.ufw:
rule: allow
port: "{{ ufw_ssh_port }}"
proto: tcp
direction: in
from_ip: "{{ ufw_ssh_from_ip }}"
state: enabled
to_ip: "{{ ufw_ssh_to_ip }}"
- name: enable http port
community.general.ufw:
rule: allow
port: 80
proto: tcp
- name: enable https port
community.general.ufw:
rule: allow
port: 443
proto: tcp
- name: enable dns tcp port
community.general.ufw:
rule: allow
port: 53
proto: tcp
- name: enable dns udp port
community.general.ufw:
rule: allow
port: 53
proto: udp
- name: enable ipv6 range rule
community.general.ufw:
rule: allow
port: 546:547
proto: udp
- name: enable firewall
community.general.ufw:
state: enabled
Docker
To isolate each application environments, we’ll use Docker containers instead of running on bare metal.
With the directory roles/docker
created by ansible-galaxy, we’ll setup Docker
in roles/docker/tasks/main.yaml
with:
---
- name: install libs
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- python3-pip
- virtualenv
- python3-setuptools
- python3-jsondiff
- python3-requests
state: present
update_cache: yes
- name: install docker gpg key
apt_key:
url: https://download.docker.com/linux/debian/gpg
state: present
- name: Add docker repository
apt_repository:
repo: deb https://download.docker.com/linux/debian "{{ ansible_distribution_release }}" stable
state: present
- name: install docker
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
- name: setup docker user and group
ansible.builtin.user:
name: "{{ ansible_user }}"
state: present
groups: docker
append: true
Cloudflared
We could use any service that give us DoH (DNS over HTTPS), in this case I chose Cloudflared because I have previous experience in managing it. But it could be Unbound for example.
We’ll setup Cloudflared in roles/cloudflared/tasks/main.yaml
with:
---
- name: run cloudflared container
docker_container:
name: cloudflared
state: started
image: docker.io/cloudflare/cloudflared:latest
command: proxy-dns
network_mode: host
env:
TUNNEL_DNS_UPSTREAM: "https://1.1.1.1/dns-query,https://1.0.0.1/dns-query"
TUNNEL_DNS_PORT: "5053"
TUNNEL_DNS_ADDRESS: "0.0.0.0"
TUNNEL_METRICS: "{{ cloudflared_metrics }}"
restart_policy: unless-stopped
Using network_mode: host
means that we don’t have to deal with NAT and port
forwarding, in this configuration we are telling to cloudflared
to listen DNS
requests on port 5053 and redirect them to nameservers 1.1.1.1 or 1.0.0.1 from
Cloudflare, applying DoH.
PiHole
PiHole works as DNS (Domain Name Server) middleware for our network with the purpose to encrypt the plain text data sent by our devices to the internet via our ISP (Internet Service Provider).
We’ll setup PiHole in roles/pihole/tasks/main.yaml
with:
---
- name: run pihole docker container
docker_container:
state: started
name: pihole
image: docker.io/pihole/pihole:latest
network_mode: host
env:
DNSMASQ_USER: pihole
TZ: America/Sao_Paulo
PIHOLE_DNS_: 127.0.0.1#5053
WEBTHEME: default-darker
WEBPASSWORD: "{{ pihole_webpassword }}"
WEB_PORT: "{{ pihole_webport }}"
volumes:
- "/home/{{ ansible_user }}/pihole:/etc/pihole"
- "/home/{{ ansible_user }}/etc-dnsmasq.d:/etc/dnsmasq.d"
restart_policy: unless-stopped
This container also is running on network_mode: host
, so the ports 53/udp,
53/tcp, 546/udp, 547/udp and the one choosen for pihole_webport
will be
available on host.