<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Opnsense on Zero-Entry</title>
    <link>https://zero-entry.co.za/tags/opnsense/</link>
    <description>Recent content in Opnsense on Zero-Entry</description>
    <generator>Hugo -- 0.147.7</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 24 Apr 2026 14:26:40 +0400</lastBuildDate>
    <atom:link href="https://zero-entry.co.za/tags/opnsense/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Inside My OPNsense Config</title>
      <link>https://zero-entry.co.za/posts/inside-my-opnsense-config/</link>
      <pubDate>Fri, 24 Apr 2026 12:00:00 +0200</pubDate>
      <guid>https://zero-entry.co.za/posts/inside-my-opnsense-config/</guid>
      <description>A walkthrough of the OPNsense box sitting at my internet edge — VLANs, DNS hijacking, DoH/DoT/DoQ blocking, CrowdSec bouncing, NetFlow, and the boring hardening that actually matters.</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve written about the home lab at a high level already. The firewall got glossed over in a paragraph and that&rsquo;s never quite sat right with me, because OPNsense is doing most of the work that stops the home lab from becoming somebody else&rsquo;s problem.</p>
<p>This post walks through the config as it currently runs. Not a tutorial. Not a clean-slate build guide. Just what&rsquo;s actually on the box, what each piece is doing, and which of the &ldquo;this looks clever&rdquo; choices turned out to be worth it.</p>
<h2 id="what-opnsense-replaced">What OPNsense replaced</h2>
<p>The previous edge was a consumer router running vendor firmware. It worked. It also had opinions about DNS, UPnP, and what &ldquo;internal&rdquo; meant that didn&rsquo;t line up with mine. The moment I wanted per-VLAN firewall rules and per-host DNS overrides, I&rsquo;d hit a wall.</p>
<p>OPNsense runs on a small x86 box with two NICs. One is WAN, one is a trunk into the switch carrying tagged VLANs. The whole thing idles under 15% CPU with everything I&rsquo;ve thrown at it.</p>
<h2 id="interfaces">Interfaces</h2>
<p>Three interfaces that matter, plus the ones that don&rsquo;t.</p>
<ul>
<li><strong>WAN</strong> — the public side. <code>spoofmac</code> is set, <code>blockpriv</code> and <code>blockbogons</code> are both on. Anything showing up from RFC1918 or reserved space on the WAN gets dropped before it hits a single rule. This is a default, but it&rsquo;s a default I&rsquo;ve seen disabled &ldquo;temporarily&rdquo; on other people&rsquo;s firewalls more times than I&rsquo;d like.</li>
<li><strong>LAN (192.168.1.0/24)</strong> — the trusted network. Hosts, servers, my workstation, the things I wouldn&rsquo;t want on a segment with a cheap IP camera.</li>
<li><strong>VLAN20_IOT (192.168.20.0/24)</strong> — everything that speaks a protocol I didn&rsquo;t write and can&rsquo;t audit. Cameras, smart switches, the TP-Link stuff, a Ring doorbell, an Apple TV. Anything that phones home on a schedule.</li>
<li><strong>VLAN30_GUEST (192.168.30.0/24)</strong> — guest Wi-Fi. Scoped tight and deliberately boring.</li>
</ul>
<p>The WireGuard interface exists as a stub. The actual WireGuard endpoint runs on Boreas (the Raspberry Pi I wrote about previously) and terminates on LAN. Port 51820/udp is port-forwarded through OPNsense to it. The virtual interface here is reserved for a future on-box tunnel I haven&rsquo;t needed yet.</p>
<h2 id="inbound-whats-actually-exposed">Inbound: what&rsquo;s actually exposed</h2>
<p>A small, deliberate list. If something isn&rsquo;t on this list, the outside internet can&rsquo;t see it.</p>
<ul>
<li><strong>32400/tcp</strong> → Alecto (Plex). Plex&rsquo;s own account server handles connection brokering; this is the fallback direct path.</li>
<li><strong>25/tcp, 993/tcp</strong> → Alecto (SMTP inbound, IMAP-over-TLS). Yes, a residential SMTP listener is a lightning rod. I accept the noise in exchange for owning my own mail flow.</li>
<li><strong>9001/tcp+udp</strong> → the Tor middle relay host. Advertised to the Tor directory as the relay&rsquo;s ORPort.</li>
<li><strong>443/tcp, 51820/udp</strong> → Boreas. 443 is Nginx Proxy Manager fronting Overseerr for friends and family behind Cloudflare. 51820/udp is the WireGuard handshake port for remote access.</li>
</ul>
<p>Port 80 exists as a disabled rule. I leave disabled-but-documented rules in the config on purpose; they&rsquo;re a paper trail for &ldquo;this used to be exposed, here&rsquo;s why it isn&rsquo;t anymore.&rdquo; Deleting them loses that context.</p>
<p>Nothing else is forwarded. No UPnP. No NAT-PMP. If a device wants to be reachable from outside, I add the rule by hand.</p>
<h2 id="vlan-policy">VLAN policy</h2>
<p>The LAN rule is broad: trusted hosts can reach anything. The two VLANs get narrower treatment.</p>
<p><strong>IoT VLAN (VLAN20)</strong></p>
<ul>
<li>Can reach the router for DNS (53) and NTP (123). Nothing else on LAN.</li>
<li>Can reach WAN on 80/443. That&rsquo;s it.</li>
<li>Blocked from every other internal subnet.</li>
</ul>
<p><strong>Guest VLAN (VLAN30)</strong></p>
<ul>
<li>Can reach WAN on 80/443. Nothing else.</li>
<li>Blocked from LAN, IoT, and the router&rsquo;s management ports.</li>
<li>Uses its own DHCP scope (192.168.30.50–200) with the router itself as DNS.</li>
</ul>
<p>Guest users regularly ask why their work VPN client doesn&rsquo;t connect. That&rsquo;s not a bug. Guest is guest; if somebody needs a real tunnel, they&rsquo;re not on guest.</p>
<h2 id="dns-is-the-interesting-part">DNS is the interesting part</h2>
<p>I run <strong>Dnsmasq</strong> (not Unbound) across all three interfaces. Domain is <code>home.lan</code>. Upstream is Cloudflare 1.1.1.1 and 1.0.0.1. DNSSEC is off, which I&rsquo;ll come back to. Authoritative DHCP is on, so DHCP leases produce DNS records for every host automatically.</p>
<p>The interesting part isn&rsquo;t the resolver. It&rsquo;s the firewall rules around it.</p>
<h3 id="forcing-iot-through-the-router">Forcing IoT through the router</h3>
<p>Most cheap smart devices ship hardcoded with Google DNS (8.8.8.8) or similar. They don&rsquo;t care what DHCP tells them; they use whatever the vendor baked in.</p>
<p>So there&rsquo;s a NAT redirect on the IoT VLAN that rewrites any outbound connection to <code>*:53</code> and sends it back to 192.168.1.1:53. The device thinks it&rsquo;s talking to Google. It&rsquo;s actually talking to OPNsense. From the device&rsquo;s point of view, nothing changes. From my point of view, every DNS lookup for every IoT device is now logged locally and subject to the same filters as everything else.</p>
<p>The alias that drives this (<code>TP_LINK</code>) started as one device and grew organically. It now covers the usual IoT suspects including the NVR.</p>
<h3 id="killing-the-dns-escape-hatches">Killing the DNS escape hatches</h3>
<p>DNS hijacking works until a device decides it would rather use DNS-over-HTTPS or DNS-over-TLS to bypass your resolver entirely. Modern OSes will happily do this without asking.</p>
<p>Three rules cover the escape routes:</p>
<ul>
<li><strong>block-dns-tls-853</strong> — drops TCP/853 outbound (DoT).</li>
<li><strong>block-doq-quic-853</strong> — drops UDP/853 outbound (DoQ).</li>
<li><strong>block-doh</strong> — drops outbound to an alias called <code>doh_providers</code>.</li>
</ul>
<p><code>doh_providers</code> is a dynamic URL table alias. It pulls from the <code>awesome-dnscrypt</code> DoH server list on a 30-minute refresh. The alias updates itself; I don&rsquo;t maintain it.</p>
<p>This isn&rsquo;t perfect. DoH hiding inside a general HTTPS flow on port 443 is indistinguishable from any other HTTPS traffic without deep inspection, and I&rsquo;m not terminating TLS at the edge. What the blocklist catches is lazy implementations that connect directly to a known public DoH provider&rsquo;s IP, which is most of them.</p>
<p>The goal isn&rsquo;t to stop a determined adversary. It&rsquo;s to stop my smart TV from quietly ignoring my DNS server because its firmware author thought that was a reasonable default.</p>
<h2 id="aliases-that-earn-their-keep">Aliases that earn their keep</h2>
<p>Most aliases are boring host groups. A few are worth calling out.</p>
<ul>
<li><strong>doh_providers</strong> — the dynamic DoH blocklist described above.</li>
<li><strong>crowdsec_blocklists / crowdsec6_blocklists</strong> — external aliases driven by CrowdSec. These hold the IPs that CrowdSec has decided are hostile. Any firewall rule referencing these aliases gets the current set of bad actors without me maintaining a blocklist manually.</li>
<li><strong>CLOUDFLARE_IPS</strong> — Cloudflare&rsquo;s edge ranges. Used to scope inbound rules so that services meant to sit behind Cloudflare only accept connections from Cloudflare&rsquo;s IP space. Anything hitting those services directly at my IP gets dropped.</li>
<li><strong>TP_LINK</strong> — the IoT forcing-function alias. Named after what started it. Now overloaded with everything in the &ldquo;must be hijacked for DNS&rdquo; bucket.</li>
<li><strong>TOR_RELAY</strong> — single-host alias pointing at the Tor machine. Useful for bundling rules even when the alias has one entry, because when the hostname inevitably moves, I change it in one place.</li>
</ul>
<h2 id="crowdsec-is-doing-the-heavy-lifting">CrowdSec is doing the heavy lifting</h2>
<p>The earlier home lab post mentioned Suricata. That&rsquo;s no longer what&rsquo;s on the box.</p>
<p>OPNsense now runs <strong>CrowdSec</strong> as the main threat-intel layer:</p>
<ul>
<li><strong>Agent enabled</strong> — parses local logs and identifies scanning, brute-force, and common exploit patterns.</li>
<li><strong>LAPI enabled</strong> — the local API that the bouncer talks to.</li>
<li><strong>Firewall bouncer enabled</strong> — actively drops traffic from IPs flagged by the agent or by the CrowdSec community feeds.</li>
<li><strong>Rules enabled</strong> — the community scenarios load and run automatically.</li>
</ul>
<p>CrowdSec is cheaper on CPU than Suricata was, and its blocklist model fits the home lab better than a full IDS pipeline. Suricata gave me floods of alerts I wasn&rsquo;t going to investigate. CrowdSec gives me &ldquo;this IP has been a problem elsewhere, dropped at the firewall.&rdquo; Less signal, but the signal it produces is directly actionable because the action already happened.</p>
<p>I lost some visibility moving away from Suricata — notably per-flow inspection rules for specific CVEs. That trade-off has been fine for this environment. If this were an enterprise edge I&rsquo;d run both.</p>
<h2 id="observability">Observability</h2>
<p>A firewall with no telemetry is a box with opinions you can&rsquo;t verify.</p>
<ul>
<li><strong>NetFlow v9</strong> is enabled on LAN and WAN, with WAN set to egress-only. Export target is a local collector at 127.0.0.1:2056 that ships flow records into the metrics stack on Alecto. Active timeout 30 min, inactive 15 sec.</li>
<li><strong>vnstat</strong> runs against the WAN interface for long-term bandwidth counters. It&rsquo;s the &ldquo;how much did I actually use this month&rdquo; graph, not a security tool.</li>
<li><strong>Zabbix agent</strong> is enabled, reporting to Alecto. CPU, memory, interface counters, states table, per-rule hits. The Zabbix server decides what to alert on; the firewall just emits data.</li>
</ul>
<p>The alerting philosophy is the same as the one in the honeypot post. The firewall is not the thing that should decide what&rsquo;s worth waking me up over. Its job is to produce honest signal. Somebody else&rsquo;s job is to filter it.</p>
<h2 id="hardening-that-isnt-glamorous">Hardening that isn&rsquo;t glamorous</h2>
<p>None of this is interesting to read. All of it matters more than the DoH blocklists.</p>
<ul>
<li><strong>Web GUI is HTTPS-only.</strong> HSTS is on. The cert is self-signed because the GUI is only reachable from LAN and I&rsquo;d rather pin the cert than introduce an ACME dependency for a single internal endpoint.</li>
<li><strong>Console menu is disabled.</strong> Physical access to the box doesn&rsquo;t immediately give you the OPNsense menu. It gives you a login prompt.</li>
<li><strong>NAT reflection is off</strong> — fewer surprises with internal traffic taking unexpected paths.</li>
<li><strong>SSH is bound to LAN only, group-restricted to <code>admins</code>.</strong> It&rsquo;s one of the few places I&rsquo;m still running password auth with root allowed. Not because I think it&rsquo;s fine, but because the exposure surface (LAN-only) makes it low-risk and a key-only migration is on the list below.</li>
<li><strong>Bogon updates</strong> run monthly. Reserved/unallocated prefixes change less often than people think, but they do change.</li>
<li><strong>Backups</strong> go to Google Drive via <code>os-gdrive-backup</code> on a schedule. The XML config is small enough that a month of daily snapshots costs nothing and saves hours if I ever have to rebuild.</li>
</ul>
<h2 id="gateway-monitoring">Gateway monitoring</h2>
<p>Single WAN, no failover. The gateway monitor pings 1.1.1.1 continuously with alert thresholds at 30% packet loss (low) and 35% (high), and 300ms latency (low). If the line is bad enough to notice, OPNsense will flag the gateway before I do.</p>
<p>No multi-WAN yet. The second ISP option in my area isn&rsquo;t worth the monthly cost for the uptime I actually need. If that changes, gateway groups are already half-configured.</p>
<h2 id="what-id-change">What I&rsquo;d change</h2>
<p>A few things are genuinely outstanding, not just aspirational.</p>
<ul>
<li><strong>Turn on DNSSEC.</strong> Dnsmasq supports it; I disabled it years ago when a misbehaving internal service was breaking on stale signatures. That reason is long gone.</li>
<li><strong>Move SSH to key-only, disable root login.</strong> The exposure is LAN-only today but the migration is trivial and overdue.</li>
<li><strong>Ship syslog off-box.</strong> Right now logs live on OPNsense and get rolled locally. A remote syslog destination (somewhere on LAN, not off-site) would survive a firewall rebuild and give me history during incident response.</li>
<li><strong>Terminate TLS on a reverse proxy for the services that sit behind Cloudflare.</strong> Right now the tunnel ends on Boreas with NPM, which is fine. Moving cert management to an ACME plugin on OPNsense for the few public services would remove a moving part.</li>
<li><strong>Split LAN.</strong> The LAN rule is still &ldquo;trusted hosts can reach everything.&rdquo; That worked when LAN was five devices. It&rsquo;s less defensible now. A management VLAN separate from a workstation VLAN is the next logical step.</li>
</ul>
<h2 id="closing">Closing</h2>
<p>The interesting parts of this config aren&rsquo;t the exotic features. They&rsquo;re the boring ones: a DNS policy that assumes devices will lie, per-VLAN egress rules that default to deny, inbound exposure that fits on a single screen, and telemetry that lets somebody else decide what&rsquo;s worth acting on.</p>
<p>OPNsense makes all of that reachable from a UI without punishing you for running CLI alongside it. That&rsquo;s a rarer combination than it sounds.</p>
<p>If you&rsquo;re standing one up from scratch and wondering where to start: segment the network, hijack DNS on the untrusted segments, block the DNS escape hatches, and wire up telemetry before you wire up rules. Everything else is refinement on top of that.</p>
]]></content:encoded>
    </item>
    <item>
      <title>30 Days of a Honeypot at Home</title>
      <link>https://zero-entry.co.za/posts/30-days-of-a-honeypot-at-home/</link>
      <pubDate>Sat, 18 Apr 2026 20:30:00 +0200</pubDate>
      <guid>https://zero-entry.co.za/posts/30-days-of-a-honeypot-at-home/</guid>
      <description>Standing up T-Pot on a segmented VLAN behind OPNsense, opening a curated set of ports to a residential IP, and writing down what 30 days of unfiltered internet traffic actually looked like.</description>
      <content:encoded><![CDATA[<p>I finally got around to putting a honeypot on the public side of my home connection. I wasn&rsquo;t trying to catch APTs. I wanted to see what hits a random residential IP when nothing is hiding it.</p>
<p>This is a notes post about standing it up, how it&rsquo;s contained, and what actually showed up in the logs after a month.</p>
<h2 id="why-bother">Why bother</h2>
<p>Most threat intelligence I read describes the internet as a battlefield. Every unpatched device is five minutes from compromise. Every IP gets 30,000 probes a day. The numbers are usually correct. They aren&rsquo;t useful unless you can map them to what your environment looks like.</p>
<p>I wanted my own baseline. Not a vendor&rsquo;s feed, not an aggregated report. What does my ISP-assigned IP attract, right now, and what does the traffic look like when you strip out the marketing spin.</p>
<p>Secondary reason: I wanted to segment the network, and a honeypot is a good forcing function. My homelab had been flat for too long. Nothing makes you VLAN a network faster than hanging something deliberately exposed off it.</p>
<h2 id="threat-model-and-ground-rules">Threat model and ground rules</h2>
<p>Before anything went online, I wrote down what I was willing to tolerate and what I wasn&rsquo;t.</p>
<p>Willing to accept:</p>
<ul>
<li>My IP appearing on scanning blocklists.</li>
<li>My ISP sending me a polite note. (They never did.)</li>
<li>Getting buried in logs I&rsquo;d then have to process.</li>
</ul>
<p>Not willing to accept:</p>
<ul>
<li>Anything the honeypot attracts touching my real LAN.</li>
<li>A honeypot compromise turning into a pivot into anything else I own.</li>
<li>Outbound traffic that looks like I&rsquo;m participating in someone else&rsquo;s botnet.</li>
</ul>
<p>Those three constraints drove every architectural decision that followed.</p>
<h2 id="architecture">Architecture</h2>
<p>The honeypot runs as a single VM on a secondary host, isolated on its own VLAN behind OPNsense. It has one purpose, no shared storage, no shared credentials, and nothing legitimate behind it. If it gets popped, I wipe the VM from snapshot and start again.</p>
<p>The physical picture:</p>
<ul>
<li><strong>Honey-VM</strong>: 4 vCPU, 8 GB RAM, 60 GB disk. Ubuntu 22.04 base, T-Pot on top.</li>
<li><strong>VLAN 66</strong>: dedicated &ldquo;DMZ-lite&rdquo;. No inter-VLAN access. DHCP scoped tight.</li>
<li><strong>OPNsense</strong>: port-forwards a curated set of TCP ports from WAN to the honey-VM.</li>
<li><strong>Suricata</strong> on the OPNsense WAN interface logs everything hitting those ports, independent of what the honeypot itself sees.</li>
<li><strong>Outbound rules on VLAN 66</strong>: no outbound except to a specific syslog collector and to Cloudflare DoH for DNS. No SSH out, no SMB out, no SMTP out, no arbitrary outbound anything.</li>
</ul>
<p>The last point is the one that matters most. A honeypot with open outbound is a honeypot that can participate in the abuse you&rsquo;re trying to study. If Cowrie accepts a shell and the intruder tries to <code>curl</code> a second-stage payload, they should fail at the network, not at the endpoint.</p>
<p>Ports exposed to the internet: 22, 23, 80, 443, 445, 1433, 2222, 3306, 3389, 5060, 5900, 8080. Nothing else.</p>
<h2 id="stack">Stack</h2>
<p>I used T-Pot for the heavy lifting. It&rsquo;s maintained, it aggregates sensible honeypots, and its dashboards are ready out of the box. The components that mattered for me:</p>
<ul>
<li><strong>Cowrie</strong> on 22 and 2222. SSH and Telnet. Logs full session transcripts.</li>
<li><strong>Dionaea</strong> on 445, 1433, 3306, 5060. Protocol emulation for SMB, MSSQL, MySQL, SIP.</li>
<li><strong>Heralding</strong> on anything else that smells like a login prompt.</li>
<li><strong>Honeytrap</strong> as the generic catch-all TCP listener.</li>
<li><strong>Snare / Tanner</strong> serving HTTP decoy content on 80 and 8080.</li>
<li><strong>Elasticsearch + Kibana</strong> for the dashboards, with data shipped out to my own Loki instance as a backup.</li>
</ul>
<p>T-Pot&rsquo;s internal firewalling is fine, but I don&rsquo;t rely on it. The OPNsense rules are the real enforcement. If T-Pot broke tomorrow, nothing on VLAN 66 would suddenly start talking to my real network.</p>
<h2 id="standing-it-up">Standing it up</h2>
<p>Provisioning was uneventful once the segmentation was in place.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># T-Pot install on a fresh Ubuntu 22.04</span>
</span></span><span class="line"><span class="cl">env bash -c <span class="s2">&#34;</span><span class="k">$(</span>curl -sL https://ghst.ly/tpot-install<span class="k">)</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>The installer handles Docker, pulls the containers, and wires up the reverse proxy for the Kibana side. The one thing I changed was restricting the admin web interface to the management VLAN, reachable only over WireGuard.</p>
<p>SSHD for actual host management got moved to a non-standard port on the management interface. Cowrie owns 22 on the WAN side.</p>
<p>Before opening the firewall, I ran a full scan from an external VPS against the advertised ports to make sure only the intended services responded, and that the banners looked realistic enough to not scream &ldquo;honeypot&rdquo; on the first handshake. A couple of Cowrie defaults were too obvious (the default SSH version string, for one) and needed tweaking. Anyone running OpenCanary fingerprints or Shodan&rsquo;s honeypot detector will still figure it out. Most bots won&rsquo;t.</p>
<h2 id="first-24-hours">First 24 hours</h2>
<p>I expected a slow ramp while DNS caches and scanners noticed the IP. That wasn&rsquo;t the experience.</p>
<p>Within 12 minutes of opening port 22, the first SSH login attempt came in. Inside the first hour: 340 SSH attempts from 47 unique source IPs. By the end of the first day: 8,200 SSH attempts, 1,100 HTTP requests, and around 60 SMB connections.</p>
<p>There is no onboarding period for a public IP. You&rsquo;re in the database already. Opening a port just tells the scanners that something is finally listening.</p>
<h2 id="what-30-days-actually-looked-like">What 30 days actually looked like</h2>
<p>Rough totals at the 30-day mark (Suricata plus T-Pot aggregated):</p>
<ul>
<li><strong>SSH and Telnet attempts</strong>: 412,000 across 22 and 2222, from 14,300 unique source IPs.</li>
<li><strong>HTTP requests to honeypot web roots</strong>: 28,400. Mostly scanner fingerprints and path probes.</li>
<li><strong>SMB connections</strong>: 3,900.</li>
<li><strong>MSSQL login attempts</strong>: 1,100.</li>
<li><strong>RDP connections</strong>: 9,700, almost all from a handful of subnets running NLA probes.</li>
<li><strong>SIP INVITE floods</strong>: two distinct campaigns, one targeting Asterisk defaults, one targeting a specific FreePBX module.</li>
</ul>
<p>Geography is the least interesting dimension and the one vendors love to lead with. Source IPs spread across 90+ countries. That maps to compromised hosts, not operator location. Treating a GeoIP heatmap as a map of threat actors is a mistake.</p>
<p>The credential side is more useful. Top ten SSH username/password combinations over the 30 days, in order:</p>
<ol>
<li><code>root</code> / <code>root</code></li>
<li><code>admin</code> / <code>admin</code></li>
<li><code>root</code> / <code>123456</code></li>
<li><code>root</code> / <code>password</code></li>
<li><code>admin</code> / <code>password</code></li>
<li><code>user</code> / <code>user</code></li>
<li><code>ubnt</code> / <code>ubnt</code></li>
<li><code>pi</code> / <code>raspberry</code></li>
<li><code>root</code> / <code>1234</code></li>
<li><code>support</code> / <code>support</code></li>
</ol>
<p>None of those will surprise anyone who&rsquo;s spent time on this. They confirm what the big honeypot operators have been saying for years: the low end of the attack surface is stuck on the same dictionary it&rsquo;s been stuck on for a decade, because it keeps working.</p>
<h2 id="what-the-payloads-looked-like">What the payloads looked like</h2>
<p>Cowrie logs full session transcripts, which is the part worth reading. Patterns I saw repeatedly:</p>
<ul>
<li><code>uname -a; cat /proc/cpuinfo; free -m</code> as environment fingerprinting before any payload drop. The bot wants to know whether it landed on an ARM router, a MIPS camera, or an x86 box, so it can pull the right binary.</li>
<li>A <code>wget</code> or <code>curl</code> chain pointing at a staging server, usually on port 80 over a direct IP with no DNS. Almost always a short-lived URL, dead within days.</li>
<li>A <code>chmod 777</code> on the downloaded binary, followed by <code>./&lt;binary&gt;</code> and a quick <code>rm</code> to clean up.</li>
<li>Busybox-style commands with shell tricks to survive minimal environments.</li>
</ul>
<p>Three payloads I captured and detonated in an isolated environment later:</p>
<ul>
<li>A Mirai variant targeting MIPS and ARM, with the usual hardcoded C2 list.</li>
<li>A Monero miner compiled for x86_64 with an embedded pool address and worker ID.</li>
<li>An XorDDoS dropper with the &ldquo;encrypted&rdquo; strings still trivially XOR-decodable against a one-byte key.</li>
</ul>
<p>Nothing novel. That&rsquo;s the point. The mass of the internet&rsquo;s attack noise is bots spraying five-year-old payloads at anything that looks like a vulnerable edge device. A residential IP running no listening services would never see this traffic because the TCP connections would simply RST. Exposing ports makes you legible to the layer that scans for this kind of target.</p>
<h2 id="the-quieter-more-interesting-traffic">The quieter, more interesting traffic</h2>
<p>Once you filter out the SSH brute force floor, and it is a floor of roughly 300 to 600 attempts per hour, the rest gets more varied.</p>
<p>Log4Shell probes still show up on HTTP. More than two years after the advisory, JNDI probes are a standing wave. Most point at self-hosted Burp collaborators or long-dead VPS callbacks. Somebody, somewhere, is still paying for a scanner that fires these shots and never checks whether anyone answered.</p>
<p>A handful of requests were clearly scripted against specific CVEs:</p>
<ul>
<li>GPON router authentication bypass (CVE-2018-10561 / 10562). Still hitting in 2026.</li>
<li>Various Ivanti and Fortinet path traversals.</li>
<li>Confluence OGNL injection strings.</li>
<li>Generic WordPress <code>xmlrpc.php</code> pingback fishing.</li>
</ul>
<p>The unusual one: a small cluster of requests that tried to negotiate TLS with an SNI matching a real banking domain. No credentials, no follow-up. Probably a scanner doing inventory for cert transparency lookups. Possibly something less innocent. I don&rsquo;t have enough data to tell, and that&rsquo;s the honest answer.</p>
<h2 id="operational-reality">Operational reality</h2>
<p>A honeypot is not a set-and-forget box. In the first week I burned an evening chasing false positives and another fixing log rotation before a partition filled. The real costs:</p>
<ul>
<li>Disk grows fast. Cowrie session logs plus Elasticsearch indices ate about 18 GB in 30 days. That&rsquo;s cheap, but it isn&rsquo;t free.</li>
<li>Containers drift. Watchtower handles the weekly pull. I still review breaking changes in the T-Pot release notes before merging.</li>
<li>Elasticsearch is memory-hungry and will swap itself into uselessness if you underprovision. 8 GB is the practical floor.</li>
<li>Alerting is where this gets useful. A 500-per-hour SSH baseline is noise. A successful Cowrie shell that persists for more than 30 seconds, or any outbound hit blocked at the OPNsense rule, is signal. Those page me. Everything else goes to a dashboard I check when I feel like it.</li>
</ul>
<p>The alerting config is where most of my ongoing time goes. Without it, the whole thing is a pretty dashboard.</p>
<h2 id="what-id-change">What I&rsquo;d change</h2>
<p>A few things I&rsquo;ll do on the next iteration:</p>
<p>Split the honeypot across two IPs. Run Cowrie on one, everything else on the other. The SSH noise crowds the indices and makes queries slower than they need to be.</p>
<p>Move the dashboards off the honey-VM entirely. Shipping to an external Loki instance is already half the work. The remaining Kibana stack on-box is there for convenience, not necessity.</p>
<p>Add a second outbound-blocking layer inside the VM itself. Defence in depth against a container escape I&rsquo;m still not fully satisfied with.</p>
<p>Log rolling PCAPs on a 7-day window. Right now I only have what Suricata and the honeypots chose to log. Full packet captures would let me revisit sessions I under-investigated at the time.</p>
<h2 id="does-this-change-what-i-do-at-work">Does this change what I do at work</h2>
<p>Partially. It doesn&rsquo;t change how I think about APT-level adversaries. Nothing I saw in 30 days would strain a reasonable environment.</p>
<p>What it changes is how I talk about the baseline. The background radiation of the internet is real, it&rsquo;s measurable, and it doesn&rsquo;t stop. Any machine with an unpatched edge service survives hours, not days. Any default credential on a public interface is already compromised. You just haven&rsquo;t noticed yet.</p>
<p>That isn&rsquo;t a marketing line. That&rsquo;s 412,000 login attempts across 30 days on one residential IP running an obvious honeypot.</p>
<h2 id="closing">Closing</h2>
<p>Segment first. Then break something on purpose, in a place where it can&rsquo;t reach anything you care about. The logs that come back are more honest than any vendor report.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Why I Still Run My Own Infrastructure at Home</title>
      <link>https://zero-entry.co.za/posts/why-i-still-run-my-own-infrastructure-at-home/</link>
      <pubDate>Sun, 11 Jan 2026 20:30:00 +0200</pubDate>
      <guid>https://zero-entry.co.za/posts/why-i-still-run-my-own-infrastructure-at-home/</guid>
      <description>A walkthrough of a self-hosted homelab built around OPNsense, Docker, and deliberate design choices — media, monitoring, VPN containment, and the lessons it took to get there.</description>
      <content:encoded><![CDATA[<h1 id="home-lab-overview-alecto-and-friends">Home Lab Overview: Alecto and Friends</h1>
<p>I&rsquo;ve always enjoyed tinkering with operating systems and finding ways they improve day-to-day life. I&rsquo;m not a cloud hater. Cloud services are useful and I still use them. I self-host because it&rsquo;s fun.</p>
<p>With most SaaS tools, you&rsquo;re limited by design choices you had no part in. My biggest self-hosted system is a Plex machine. I watch what I want, how I want, for roughly the cost of electricity. There&rsquo;s also been a serious learning component: networking, security, general IT practice. That alone has made it worth running.</p>
<h2 id="topology">Topology</h2>
<p>Starting at the internet edge and working inward:</p>
<p><strong>Router / Firewall</strong> I settled on OPNsense. It met and exceeded what I needed. The box runs intrusion detection, Unbound DNS, and a handful of other security-focused services.</p>
<p><strong>Switching</strong> Traffic hits a 24-port unmanaged gigabit switch with SFP ports. Nothing exotic, but most ports are in use.</p>
<p><strong>Flat Network</strong> The network is currently flat, so traffic flows directly to access points, servers, Raspberry Pis, NVR systems, gaming consoles, and everything else.</p>
<p>The topology is simple. The interesting part is what the devices are doing, not how complex the diagram looks.</p>
<h2 id="core-infrastructure">Core Infrastructure</h2>
<h3 id="alecto">Alecto</h3>
<p><strong>Hardware</strong></p>
<ul>
<li>Ryzen 7 1700X</li>
<li>1 TB NVMe</li>
<li>4 TB HDD</li>
<li>GTX 1050 Ti</li>
</ul>
<p>Nothing exotic, but it handles everything I need with around 85% idle time.</p>
<p><strong>Software</strong> Ubuntu LTS as the host OS, Docker for everything else: media acquisition, media consumption, networking, local services, metrics, and automation.</p>
<p>Docker makes backing up and restoring critical services significantly easier, which is the main reason I keep everything containerised.</p>
<h2 id="services">Services</h2>
<h3 id="media-acquisition">Media Acquisition</h3>
<p>The pipeline follows a simple request, acquire, process, library chain.</p>
<ul>
<li>Overseerr</li>
<li>Prowlarr</li>
<li>Sonarr / Radarr</li>
<li>qBittorrent</li>
<li>Deluge</li>
<li>Unpackerr</li>
<li>cross-seed</li>
</ul>
<p><strong>Prowlarr</strong> manages indexers. Private trackers have far less fake or malicious content than public ones, which matters later in the chain.</p>
<p><strong>Sonarr</strong> and <strong>Radarr</strong> handle TV and movies. Quality profiles are simple: HD and 4K. That has covered everything so far. Both monitor RSS feeds from configured indexers and push matched torrents to the downloader automatically.</p>
<p>I run two download clients. <strong>qBittorrent</strong> handles the entire arr stack. <strong>Deluge</strong> handles manual downloads and non-media content. Dynamic save paths split movies and TV cleanly for Plex.</p>
<p><strong>Unpackerr</strong> handles automatic extraction for downloads that arrive as archives. <strong>cross-seed</strong> finds identical or near-identical torrents across trackers and advertises that I already have the data, which improves speeds and availability for others.</p>
<p>The only recurring issue is Sonarr or Radarr occasionally grabbing a fake title. Aggressive regex-based filters have mostly resolved it.</p>
<h3 id="media-consumption">Media Consumption</h3>
<ul>
<li>Plex</li>
<li>Tautulli</li>
<li>Overseerr</li>
<li>Homepage</li>
</ul>
<p><strong>Plex</strong> is the primary player. Mature, stable, available on every device, and accessible for non-technical users. I&rsquo;ve tested Jellyfin and like it, but haven&rsquo;t switched.</p>
<p><strong>Tautulli</strong> gives visibility into Plex usage: playback activity, per-user bandwidth, transcoding load. That data makes decisions around limits and capacity easier.</p>
<p><strong>Overseerr</strong> lets users request titles themselves rather than messaging me. Requests still require approval, but that takes seconds instead of a back-and-forth conversation.</p>
<p><strong>Homepage</strong> is a single customisable dashboard with a high-level view of everything running. It doesn&rsquo;t replace Zabbix or Grafana for monitoring, but it&rsquo;s useful for day-to-day glancing.</p>
<h3 id="networking-and-vpn-containment">Networking and VPN Containment</h3>
<p>Torrent clients are routed through <strong>Gluetun</strong>, a dedicated VPN container running WireGuard. The downloaders have never touched my LAN directly and never see my public IP.</p>
<p>Gluetun runs in strict kill-switch mode. If the VPN drops, traffic stops. There&rsquo;s no fallback to my home connection. Given the provider&rsquo;s SLA, this hasn&rsquo;t been an issue in practice.</p>
<p>No inbound ports need to be open, which reduces exposure further. Speed degradation from the VPN hasn&rsquo;t been noticeable.</p>
<h2 id="observability">Observability</h2>
<p>Prometheus, Node Exporter, cAdvisor, and Grafana cover system-level metrics: CPU load, memory usage, container behaviour. Critical alerts go to Telegram. I&rsquo;m refining thresholds so only actionable issues send a notification.</p>
<h2 id="automation">Automation</h2>
<p><strong>Watchtower</strong> handles container updates on a schedule at 03:00. If an update breaks something, rolling back means redeploying from the same configuration paths. Docker&rsquo;s stateless container model makes that straightforward.</p>
<p><strong>Portainer</strong> handles anything that needs a UI.</p>
<h2 id="network-edge">Network Edge</h2>
<h3 id="opnsense">OPNsense</h3>
<p>OPNsense sits between the internet and all internal systems. UPnP is disabled. No device exposes itself automatically. Only explicitly required services are permitted outbound or inbound.</p>
<p>All traffic is statefully inspected. DNS is forced through Unbound. Suricata monitors inbound and outbound traffic for known malicious patterns. Devices can&rsquo;t quietly phone home, bypass DNS filtering, or accept unsolicited inbound connections without generating an alert.</p>
<p>Services get published deliberately, not accidentally exposed.</p>
<h3 id="boreas">Boreas</h3>
<p>A Raspberry Pi running Nginx Proxy Manager and WireGuard. This started as a workaround for a previous router that lacked VPN support. After moving to OPNsense, the separation made enough sense to keep.</p>
<p>Boreas is my remote access point back into the LAN. Nginx Proxy Manager exposes Overseerr to friends and family outside the local network. Both services sit behind Cloudflare, primarily to obscure my real IP.</p>
<p>The throughput on the Pi is better than you&rsquo;d expect for the hardware.</p>
<h3 id="chronos">Chronos</h3>
<p>A Tor middle relay running as a small contribution to online privacy. No exit node: the ISP complaints and CAPTCHA overhead aren&rsquo;t worth it. A middle relay provides value without the operational noise. It&rsquo;s low-maintenance and largely invisible once configured.</p>
<h2 id="failures-and-lessons">Failures and Lessons</h2>
<p>In the past year, two things actually broke:</p>
<ul>
<li>Disks filled up from log spam. Entirely my fault. Log rotation is properly configured now.</li>
<li>Incorrect or fake titles downloaded. Better filters and denied extension lists resolved most of it.</li>
</ul>
<p>Beyond that, issues have been minor misconfigurations and occasional reboots.</p>
<h2 id="what-id-do-differently">What I&rsquo;d Do Differently</h2>
<p>I&rsquo;d spread services across more hosts if I could go back. Some hardening measures were probably over-engineered, but I don&rsquo;t regret that trade-off. Deploying the arr stack earlier would have saved time.</p>
<h2 id="whats-next">What&rsquo;s Next</h2>
<p><strong>Hardware</strong>: managed switch, better access points, a more capable GPU for transcoding.</p>
<p><strong>Monitoring</strong>: a consolidated Zabbix dashboard with proper alerting.</p>
<p><strong>Networking</strong>: VLANs.</p>
<p>Self-hosted AI models aren&rsquo;t on the list. Cloud tools cover what I need without the overhead.</p>
<h2 id="closing">Closing</h2>
<p>Running this lab has mostly taught me patience. Getting multiple devices, containers, and services working together takes iteration. It&rsquo;s improved my understanding of networking and containerised systems more than any course I&rsquo;ve taken.</p>
<p>I keep running it because it&rsquo;s fun and I learn from it. That&rsquo;s enough reason.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
