Tuesday, 27 October 2026

Running static linked wireguard-{go,rs} on an old server

Why do I need Wireguard on an antiquated server?

Some time ago I took on admin of a club server running Debian 8 Jessie (EOL 17 Jun 2018) hosting a small PHP web app that's quite important to the club. With recent PHP2Shell vulnerabilities (CVE-2026-109618 and CVE-2026-109619), hosting the app on open internet is suicidal. Yes, there was CloudFlare proxy up front with WAF rules, but hey - WAF is temp fix and can by bypassed as new attack paths emerge, and I didn't want to take risk.

Best way to protect it, except wholesale system update to get to lated Debian 13, battling broken updates, breaking changes, etc. is to take the app off public network and expose on VPN. Fallback plan would be  mTLS for legitimate users but that would require installing software from long archived repos (good luck!)

Wireguard-go or Wireguard-rs?

No difference in real terms, but pick whichever one floats your boat - I know you have a favourite.

I got both, cross-compiled for cpu architecture I needed and linked statically so I didn't need anything more than binaries. This allowed me to work around installing DKMS kernel modules, wg-tools or other packages - basically a self-contained solution, as much as can be...

CAVEAT - neither of those two uses standard wireguard config files, instead you configure server by writing to a unix socket file, and format of those commands is different than normal config file, and rather poorly documented (hence this writeup).

Reality check, gotchas and getting it to work

To start with, Debian 8 has tun module, my server had netcat version that doesn't support -U flag needed to work with unix socket, so had to write python scripts to handle writes/reads, and Python 3 is old 3.4.x and doesn't support f-strings (introduced in 3.6) so had to use older format() approach.

The big PITA was that wireguard-go which I somehow left in place doesn't take keys in normal format, instead it's in hex - more fun, and don't forget the keys are Curve25519 based!

Key generaion:

Option 1 - install wg-tools on another host

That's what I've done being lazy

Option 2 - use openssl to generate key material (didn't work on old Debian though)

openssl genpkey -algorithm X25519 -outform DER | tail -c 32 | openssl base64 > privatekey

openssl pkey -inform DER -pubout -outform DER -in <(openssl pkey -inform DER -in <(openssl base64 -d -in privatekey | awk '{printf "\x30\x2e\x02\x01\x00\x30\x05\x06\x03\x2b\x65\x6e\x04\x22\x04\x20"}{print}') ) | tail -c 32 | openssl base64 > publickey

Convert keys from base64 format to hex acceptable in UAPI:

wg genkey > private.key && cat private.key 
wg pubkey < private.key > public.key && cat public.key
cat public.key | base64 -d | xxd -p -c 32

What you get from xxd is the hex string that needs to go into UAPI statement. 

Wireguard-go setup (brain dump)

Binary at /etc/wireguard/bin/wireguard-go, config files in /etc/wireguard folder.

UAPI interface is simplistic, but operational. First define private key of server, listening port and then each public_key is followed by settings for particular client, until next public_key is defined and so on. 

The /etc/wireguard/uapi.conf contains config commands that will be injected into socket:

set=1
private_key=88...1471
listen_port=51820
public_key=4b...673a
allowed_ip=10.1.1.2/32
public_key=2c...2122
allowed_ip=10.1.1.3/32
public_key=3e...0949
allowed_ip=10.1.1.4/32
public_key=80...2b17
allowed_ip=10.1.1.5/32

To start/reload wireguard I wrote a small script that first stops any running instance, starts clean one up, sets up IP networking on wg0 and then injects UAPI into socket using Python.

/etc/wireguard/run.sh:

#!/bin/bash
rm /var/run/wireguard/wg0.sock /var/run/wireguard/wireguard.pid 2>/dev/null
sleep 1
/etc/wireguard/bin/wireguard-go wg0
WG_PID=`ps ax | grep wireguard-go|grep -v grep | awk '{print $1}'`
echo $WG_PID > /var/run/wireguard/wireguard.pid
sleep 1
ip address add 10.1.1.1/24 dev wg0
ip link set up dev wg0
sleep 1
/usr/bin/python3 /etc/wireguard/wg-configure.py

/etc/wireguard/wg-configure.py:

import socket, sys
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("/var/run/wireguard/wg0.sock")
s.sendall(open("/etc/wireguard/uapi.conf", "rb").read())
s.close()

/etc/systemd/system/wireguard.service:

[Unit]
Description=WireGuard User-Space VPN (wg0)
After=network.target network-online.target ufw.service
Wants=network-online.target 
Before=ssh.service apache2.service postgresql.service  
 
[Service]
Type=forking
ExecStart=/etc/wireguard/run.sh 
 
# Tell systemd how to find and kill the background process when stopping the service
PIDFile=/var/run/wireguard/wireguard.pid
TimeoutSec=30
Restart=on-failure 
 
ExecStop=/sbin/ip link set down dev wg0
ExecStop=/bin/rm -f /var/run/wireguard/wg0.sock  /var/run/wireguard/wireguard.pid 
 
[Install]
WantedBy=multi-user.target

Sample client config:

[Interface]
PrivateKey = 8...w=
Address = 10.19.1.3/24

[Peer]
PublicKey = F...M=
AllowedIPs = 10.1.1.1/24
Endpoint = x.x.x.x:51820
PersistentKeepalive = 25

Finally set firewall rules to whatever is needed in given scenario. I wanted apache to listen on wg0 only, hence wireguard had to start before web and database servers.

Fun fact - deleting wg0.sock file stops the wireguard server - love the clean unix way of doing so :-)

Tuesday, 15 March 2022

AdGuard Home and time based rules

I have to say, I really like AdGuard Home (AGH)... and I am a bit torn between PiHole and AGH. PiHole seems to have better dashboard and allows to drill more into logs, but AGH has more features, so in a way it is horses for courses.

Recently a new use case came up for me - to block certain websites/services based on time of day. Think of it as technical layer of parental controls. I call it technical layer, because I know a conversation with a child is way more effective than any technical solution. At the same time kids being kids (even the most obedient and respectful ones) will sooner or later try to see if something is really blocked or is dad bluffing. Let them... it's good they try.

AGH API

AGH has a working API that is documented here. If you paste the contents of openapi.yaml into web based Swagger Editor, you will be able to easily navigate through the API docs.

AGH requires user to authenticate when using the API, so let's assume our username is admin and password is password. Invoking API is as simple as adding a HTTP header and encoding admin:password as Base64 string to include in the header - you can use CyberChef for this.

curl -H 'Authorization: Basic YWRtaW46cGFzc3dvcmQK' ...

 Here we are interested in 2 API endpoints:

  • /control/filtering/add_url
  • /control/filtering/remove_url
Swagger Editor shows us exact invocation method with examples:

Sunday, 5 January 2020

Mikrotik + Pi Zero + Pi-hole = advertising sinkhole with fail-safe

Components

  • Mikrotik router with USB port - I tested on RB2011UiAS-2HnD-IN and hAP ac models
  • RouterOS in modern version - I tested with long term (6.44.6)
  • Raspberry Pi Zero - I use old one without "W", with 4GB microSD card running latest Raspbian 10 Buster (minimal, without GUI!)
  • Short micro-USB data cable - because many cheap cables don't do data

Pi Zero actually has more than enough power to run Pi-hole serving even quite large home/family network and running it completely self-contained off Mikrotik seems to work great!


Initial setup

  1. Download and burn the latest Raspbian onto the SD card - I used for this Etcher and 2019-09-26-raspbian-buster-lite.img
  2. Connect SD card to a PC and in partition called boot edit two files to enable Ethernet gadget:
    1. config.txt - at the very end of the file add a line saying dtoverlay=dwc2
    2. cmdline.txt - add modules-load=dwc2,g_ether directly after 'rootwait' and before any other parameters that may (or not) be there
  3. Boot up RPi powering from PC using the port marked as USB on the board - not the PWR IN; it's the one in the centre - only that one does power + gadget
  4. After all boots up, you should be able to run ssh pi@raspberrypi.local (thanks mDNS!) with password raspberry
  5. On the RPi create file called /etc/modprobe.d/g_ether.conf with the following content (single line of text)
    options g_ether idVendor=0x05ac idProduct=0x1402 iProduct=Pi0 iManufacturer=Raspberry
    NOTE - This is required for RPi to show up as LTE interface on Mikrotik!

Saturday, 30 November 2019

Hacking smart plugs for fun and profit

Smarter, the smart way...

You'd like to have your home a bit smarter but not spend a ton of money and you don't like the fact that you have to trust an unnamed 3rd-party company with your data and more importantly access to things that can trigger kinetic actions in your household.

Chinese company Itead is the maker of well known Sonoff devices (do not confuse with audio gear from Sonos). They have a vast range of wifi controlled relays, in various formats and sizes - overall very cool stuff. There is a really nice video on youtube that will give you more idea about what I mean by this.
One of the best parts of Sonoff devices is that they use ESP8266 as the main driver chip and attach relays to it, so being ESP8266 based they are very hackable! That's just what we need!

These days, Itead and others are making smart devices using the same tested pattern and rebrand them under hundreds of names. You will find on Amazon various devices using the same management app (usualy eWeLink or Smart Life), which means OEM devices - good for us!

NOTE:
Since 2019 more and more devices switch from ESP82xx to Realtek chips - the OEM ecosystem is also a curse. For example in 2018 I bought Teckin SP27 smart plug - worked perfect... bought some SP27 and SP23 this week and they run Realtek, so DO NOT BUY those if you want to reflash :-)

What works? I got this set and as of late November 2019 it was still ESP82xx based. Others reported at the same time that this is also working. Basically depends on which batch you get and a bit of luck :-)

Tuesday, 29 January 2019

More range, more fun - ADS-B setup updates

Quick post today...

I've updated my ADS-B receiving station about a week ago and today, most likely due to radio propagation conditions change, I set a new detection range record of 241nm (or 466km) - here's the view from my FlightRadar24 dashboard, and the day's not over yet!


Strangely most detections are due South today, which wasn't the case over the rest of the week (most are N-NW due to window where the antenna is being exactly North facing). This is the magic of radio propagation - band conditions change constantly, sometimes in most surprising way.

Right, so what what were the updates I made last week? I made only one...

Saturday, 17 November 2018

Slimming down 1-node Elastic cluster

If you ever ran Elastic Search especially quick and dirty - single node and default config, you will notice the health is always showing yellow and that it's a proper hog for the system. Well, yes, it will be, especially in default config, as my good friend Justin Borland pointed out.

I'm a complete newbie when it comes to Elastic, deployed few in Docker containers to quickly ingest data and dig in with Kibana, but that was it. Luckily for me Justin is absolute beast when it comes to all things Elastic - he just looked at my node and right on the spot explained what's wrong with it and how to fix/improve.

Basically my default setup was running 5 shards for each of the indices stored in the system, and I had quite a few daily indices already there - we're talking months of DNS research data and web spider runs across thousands of websites... all repeated daily. This means the optimisation to be really effective needs to also deal with what's in there, not just new data I will be adding.

Plan:
  1. Change default template to run only 1 shard and 0 replicas - it's a single node deployment, so anything more complex doesn't make much sense.
  2. Use reindex API to rewrite all of existing indices as single shard versions, the deleting the old ones using 5 shards - there's no other way to do it than through reindexing.
  3. My indices are treated append-only on the day, then become read-only, so we can merge the segments - leaving technical details behind, this will mean no random access later, just linear file reads, but that's perfectly acceptable in my particular use scenario.

Let's do it!

Friday, 2 November 2018

Solution - Rancher 2 (k8s), private registry, self-signed certificates

Since Rancher switched to Kubernetes in version 2.x, I'm exposed to a lot of stupidity and limitations k8s introduced, but I can live with that, at least for a moment... What I couldn't accept was that I could no longer use my private registry (with self-signed certificate) that works perfectly fine with older Rancher (1.6 - before move to k8s).

That is now resolved!

My cluster setup


  • Rancher 2 cluster (based on Kubernetes), all running on latest RancherOS
  • Private registry available only within the LAB network - hence self-signed certificate
  • Registry has an internal host name, resolvable via internal DNS server
  • Registry does not require user accounts, so no need for credentials, but self-signed certificate prevents it from working, resulting with following error when image is pulled
x509: certificate signed by unknown authority

Dead ends


First of all, please ignore RancherOS documentation - last one I found was for version 1.2, current RancherOS is 1.4.2... anyway, it no longer works (it did for older RancherOS and Rancher 1.6 though, but new Rancher is more Kubernetes than anything else). In my research I also read a bunch of bug reports, feature requests, stack exchange articles, etc... mostly waste of time, but they gave me a good idea on rabbit holes to avoid. Some of the more useful reads are here and here, I also have a feeling this will be useful for me quite soon.
Another trick I noticed was that if I followed RancherOS docs above, the registry CA key was overwritten with something else on node reboot.

Solution (a.k.a "works for me")


Go old school Linux admin style:

  1. SSH to the RancherOS node (user is rancher@<node>), having your private CA certificate at hand
  2. As user rancheros try docker pull <registry:port>/<my image> - you should get a CA error
  3. Check your /etc/resolv.conf - mine was regularly overwritten by dhcp but it was not writing name servers correctly - this should be easily fixed by writing what you want to /etc/resolv.conf.tail (in hopes dhcp will append it when it regenerates resolv.conf).
  4. Now the key element - edit the OS wide trusted CA list (hint hint - may disappear after sudo ros os upgrade, but this can be fixed with sudo chattr +i /etc/resolv.conf)  and add your CA certificate there. Running vi /etc/ssl/certs/ca-certificates.crt and copy'n'paste does the trick!
  5. Try docker pull again, now it worked for me.