Lets be frank, the Internet is simply unusable with all the ads floating around.
I use the uBlock Origin extension in my browser, as do most of the people reading this genre of articles, but the same is not true for the majority of the population, including other members of my family. So in order to enhance their web browsing experience I decided to block ads at the DNS level.
But why stop there I thought, why not also improve their privacy while I’m at it. So I also decided to setup a VPN. Now let me clarify some things here, I’m not a big fan of VPNs, the way they’re advertised by the big companies, here’s a great video by Tom Scott explaining what I mean. But they also have their use cases, some of which are:
- Prohibiting ISPs from collecting data on my browsing patterns
- Circumvent internet censorship
- Connect to my home network from anywhere
I took a look at apps like Blokada and DNS66 which grant you device wide ad blocking on mobile devices. On Android the way this works is, by creating an internal VPN on the phone, so that all traffic from the device can be routed via it, and it has a file with all the blacklisted domains, to filter out traffic.
But there’s a caveat with this approach. I cannot use another VPN to route my traffic, due to an Android limitation.
There can be only one VPN connection running at the same time. The existing interface is deactivated when a new one is created.
This means my browsing patterns are still accessible to my ISP. So, I started looking for ad blocking DNS servers, so that I could point Android’s global DNS to it. I found AdGuard and PiHole to be the top projects. Hosting these at home, on Raspberry Pi seemed like a plausible solution, but then again one can’t use it while traveling. The solution is to obviously host it on a publicly accessible server. Amongst the two I found AdGuard more appealing due to the following reasons
- It has out of the box support for DNS-over-TLS
- It maintains a single file for its entire configuration
- It’s written in Golang, and is much lighter on resources compared to PiHole
You can find more about their differences here. Both are great projects, but AdGuard met my requirements perfectly.
When it comes to VPN, I did not even consider using OpenVPN, WireGuard was the obvious choice, because of it’s speed, smaller, easily auditable codebase (not that I was going to audit it, but still), cross platform compatibility and integration into the Linux Kernel.
Being a big fan of Kubernetes and maintaining infrastructure as code, I wanted a way to be able to easily deploy and version control my deployment. After much searching I stumbled upon, kilo, a network overlay built on WireGuard for Kubernetes. It could do exactly what I wanted, while also enhancing the security of my cluster, by encrypting the inter pod communication, and allowing me to build secure clusters, over nodes spanning multiple cloud providers. Also it would give me the added benefit of easily debugging applications deployed on my Kubernetes Cluster, since when connected I would be a peer on the network, thereby getting access to the all the private IPs of the deployments, services etc. You can watch this talk by Lucas Servén Marín to know more.
Without further ado, let’s jump right into the setup. I’ll be explaining the steps for setting it up on a k3s cluster. You may need to modify them as per your cluster. After deploying k3s, the 1st thing which needs to be done is to setup kilo. First download the manifest for kilo on k3s.
curl -LO https://raw.githubusercontent.com/squat/kilo/master/manifests/kilo-k3s.yaml
Now you need to modify and add
- --mesh-granularity=full to the
DaemonSet section under the
args for the
... containers: - name: kilo image: squat/kilo args: - --kubeconfig=/etc/kubernetes/kubeconfig - --hostname=$(NODE_NAME) - --mesh-granularity=full env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName ...
This is done to ensure all our nodes are meshed together regardless of the datacenter. Then simply apply the manifest.
kubectl apply -f kilo-k3s.yaml
This will later be useful for setting up WireGuard VPN. More on this later. Now we can proceed to setup AdGuard. Here is the spec for AdGuard.
apiVersion: apps/v1 kind: Deployment metadata: name: adguardhome spec: selector: matchLabels: app: adguardhome replicas: 1 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 0 template: metadata: labels: app: adguardhome spec: volumes: - name: tls-cert-secret secret: secretName: production-tls-cert - name: adguard-config hostPath: path: "/path/to/store/conf" type: DirectoryOrCreate - name: adguard-logs hostPath: path: "/path/to/store/work" type: DirectoryOrCreate containers: - name: adguardhome image: adguard/adguardhome:v0.102.0 ports: # Regular DNS Port - containerPort: 53 hostPort: 53 protocol: UDP - containerPort: 53 hostPort: 53 protocol: TCP # DNS over TLS - containerPort: 853 hostPort: 853 protocol: TCP volumeMounts: - name: tls-cert-secret mountPath: /certs - name: adguard-config mountPath: /opt/adguardhome/conf - name: adguard-logs mountPath: /opt/adguardhome/work terminationGracePeriodSeconds: 20 --- apiVersion: v1 kind: Service metadata: name: adguardhome labels: app: adguardhome spec: type: ClusterIP selector: app: adguardhome ports: - port: 80 # targetPort: 3000 targetPort: 80 protocol: TCP
RollingUpdate is intentionally configured to not wait till a new pod is up, and directly terminate the existing pod during deploys. On the surface it seems like an anti-pattern, but since I’m using the
hostPort directive, a new Pod wouldn’t get scheduled unless port
53 was available on the host for it to bind to, so the existing Pod has to terminate before a new Pod can be deployed.
Also I initially intended to use a
ConfigMap for holding the
AdGuardHome.yml, but there was some issue with AdGuard trying to write to it while initally comming up, but since
ConfigMap‘s are moundted as
ReadOnly, Pod creation used to fail, so I decided to go with a Volume instead, until I could figure out the issue.
For the initial setup, the AdGuard admin UI will be accessible on port 3000, so you’ll have to switch the
3000 in the
adguardhome service initially, access the admin UI, setup the password, and then revert the
You may also enable
DNS Settings for guaranteeing authenticity of DNS responses by signing them, and making tampering detectable.
The volume mounts for
tls-cert-secret are only necessary if you want to enable DNS-over-TLS. And you need to configure your ingress resource before mounting it here.
For enabling DNS-over-TLS, on the AdGuard admin UI you can goto
Encryption Settings ->
Enable Encryption, and put in the Certificate path as
/certs/tls.crt and the Key path as
/certs/tls.key. Again let me re-iterate that your Ingress resource needs to be configured properly and you need to have a valid TLS certificate for the domain you’re hosting AdGuard on.
On Android phones for Android Pie and later, you may goto
WiFi and Internet ->
Private DNS. Select
Private DNS Hostname Provider and set it to the domain name to the once you’ve configured on. You may also configure it on your home router to ensure all the devices get DNS level Ad Blocking.
This concludes the AdGuard part of the setup.
Now coming to setting up WireGuard.
I’ll be referring to the k3s cluster as the server and the local laptop as the client from here on.
You’ll need WireGaurd installed on both your server and clinet machine. Follow the steps as per your distribution to install the same. If you’re using a bleeding edge distro like Archlinux or Gentoo, you don’t need to do anything on the server side, since WireGuard is already baked into the kernel at this point. On the client side however you’ll need to install it for getting the command line client to enable / disable the interface.
Another useful tool to have on the client side is
kgctl. You can install it using
go get github.com/squat/kilo/cmd/kgctl
Now we need to create a private and a public key pair on the client.
wg genkey | tee privatekey | wg pubkey > publickey
This’ll create 2 files with the respective key contents. This key pair needs to be authorized on the server. You can do this simply by creating a peer resource. Create a file named
apiVersion: kilo.squat.ai/v1alpha1 kind: Peer metadata: name: archie spec: allowedIPs: - 10.120.120.1/32 # This is just and example, you can use any valid available CIDR here publicKey: CLIENT_PUBLIC_KEY # Enter the public key here, the one you just generated persistentKeepalive: 10
Finally apply the manifest
kubectl apply -f archie.yaml
allowedIPs should be a valid CIDR, which is available on both the server and the client. Now we can use the
kgctl tool to generate the
peer section of the client WireGuard config.
kgctl showconf peer archie
This will return something like
[Peer] AllowedIPs = 10.42.0.0/24, 10.42.0.0/32, 10.4.0.1/32 Endpoint = YOUR_SERVER_IP:51820 PersistentKeepalive = 10 PublicKey = SERVER_PUBLIC_KEY
Create a file on the client named
adgaurd.yaml at the location
/etc/wireguard and add the following contents to it
[Interface] Address = 10.120.120.1/32 # Use the same CIDR whitelisted in the Peer manifest PrivateKey = CLINET_PRIVATE_KEY # The one you generated above on your client DNS = YOUR_SERVER_IP # Enter the IP of the server to block ads, FQDN don't work on Android for some reason [Peer] AllowedIPs = 0.0.0.0/0, ::/0 # This modification is important to route all the traffic from your machine via wireguard interface Endpoint = YOUR_SERVER_IP:51820 PersistentKeepalive = 10 PublicKey = SERVER_PUBLIC_KEY # as recieved from the above config
Now to enable the VPN on your clinet machine you may use
wg-quick up adguard
You can verify you’re connected to the VPN server by visiting ifconfig.io. It should show your IP as the IP of your server.
To disconnect, you may use
wg-quick down adguard
Using the same process outlined above you can add multiple Peers and create multiple configs. You can also install and use the
qrencode utility to convert the same config to a QR code for easy scanning on mobile phones.
qrencode -t ansiutf8 < /etc/wireguard/adguard.conf
This will print a QR code right in your terminal. You can install the WireGuard Android App on your phone and this scan this QR code to import the config.
And viola, whenever you’re connected, your ISP can’t snoop in on the websites you’re visiting, add to that all your traffic will be filtered out for ads, while also routing your data through the server for protection against malicious actors in your network, and for the cherry on top, you can have all your devices connected and talking to each other regardless of the location / network they are on.