<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Projects on Alex Fetzner</title><link>https://fetzner.me/en/projects/</link><description>Recent content in Projects on Alex Fetzner</description><generator>Hugo -- 0.152.0</generator><language>en-us</language><copyright>MIT</copyright><lastBuildDate>Thu, 01 Jan 2026 20:43:30 -0600</lastBuildDate><atom:link href="https://fetzner.me/en/projects/index.xml" rel="self" type="application/rss+xml"/><item><title>This website!</title><link>https://fetzner.me/en/projects/fetznerdotme/</link><pubDate>Thu, 01 Jan 2026 20:43:30 -0600</pubDate><author>Alex Fetzner</author><guid>https://fetzner.me/en/projects/fetznerdotme/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<h2 id="youre-looking-at-it">You&rsquo;re looking at it!<a href="#youre-looking-at-it" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<h3 id="right-now">Right now!<a href="#right-now" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<h4 id="innit-neat">Innit neat?!<a href="#innit-neat" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h4>
<p>As you can see at the footer, it&rsquo;s made with <a href="https://gohugo.io/">Hugo</a> and uses a theme <a href="https://github.com/1bl4z3r/hermit-V2">Hermit-V2</a>.
I&rsquo;m not much of a front-end web developer, so I gravitate toward tools that let me focus on writing content in Markdown and generate static HTML/CSS as output.
Because of that, this page isn’t really about front-end design&mdash;it’s about how the site is hosted and the principles behind it.</p>
<p>Since the website is self-hosted, my primary objectives are:</p>
<ol>
<li>Minimizing attack area</li>
<li>Isolating the web-server should it get compromised</li>
</ol>
<p>I&rsquo;m trepid about giving away too many details about my security posture, but I feel confident enough in my stack that I can disclose the following:</p>
<h3 id="minimizing-attack-area">Minimizing attack area<a href="#minimizing-attack-area" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>The website is intentionally simple.
It&rsquo;s a statically served HTML/CSS/JavaScript site with no server-side application logic or database.
This significantly reduces the complexity and attack surface compared to a dynamic website.</p>
<p>Public traffic is managed by a reverse proxy that</p>
<ul>
<li>Hides my IP address</li>
<li>provides some protection against denial-of-service and other network attacks</li>
<li>minimizes the traffic I have to allow from the internet into my network</li>
</ul>
<p>As such, the public facing part of my server does as little as possible and only as much as necessary.</p>
<h3 id="isolating-and-containing-the-server">Isolating and containing the server<a href="#isolating-and-containing-the-server" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>The entire stack uses the principal of least privilege:</p>
<ul>
<li>The web server runs in a rootless container of an unprivileged user.</li>
<li>It has only read-only access to only the files it needs.</li>
<li>It also runs with OS-level mandatory access controls.</li>
</ul>
<p>Further, the server itself is isolated from the rest of my network using VLAN-segregation and
a firewall enforces minimal access to the WAN using egress filtering, and zero access to the LAN.</p>
<blockquote>
<p>My design philosophy is to assume a breach will happen and minimize its impact when it does.</p>
</blockquote>
]]></content></item><item><title>OPNSense Firewall</title><link>https://fetzner.me/en/projects/opnsense_firewall/</link><pubDate>Wed, 01 Oct 2025 20:16:51 -0600</pubDate><author>Alex Fetzner</author><guid>https://fetzner.me/en/projects/opnsense_firewall/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<h2 id="status-ongoing">Status: Ongoing<a href="#status-ongoing" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<h3 id="whats-opnsense">What&rsquo;s OPNSense?<a href="#whats-opnsense" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p><a href="https://opnsense.org/#">OPNSense</a> is an open-source, Free BSD-based, firewall OS.</p>
<p>You might&rsquo;ve picked up that this is similar to <a href="https://fetzner.me/en/projects/watchguard_openwrt/">my OpenWRT/T35 project</a>.
In fact, the original intention was to use the T35 as a firewall as is it originally was using OPNSense.
I was discouraged by the fact that OPNSense uses FreeBSD, and although it is Unix-like, I am less familiar with BSD compared to Linux.
On hitting a roadblock getting the internal network-switch to work on my T35 using OpenWRT, I decided to bite the bullet and buy a OPNSense-suitable appliance.
I bought a Dell Wyse with two NICs and installed OPNSense onto it.</p>
<p>OPNSense has richer firewall rules than OpenWRT, and a larger computational footprint.
In summary, OpenWRT is for embedded routers whereas OPNSense is a full-fledged firewall.</p>
<p>Before I had my OPNSense firewall, I had a Raspberry Pi 4 running IPFire.
I was underwhelmed by the features and limitations of IPFire, but principally, the RPi 4 has only one network port and an SD card for storage.
I was bottlenecking my network by using a USB-Ethernet adapter for one of the interfaces.
Also, IPFire has a limitation that it can only have one VLAN assigned per interface, which disrupted my plans to segregate my LAN, WLAN, DMZ, and work VLANS on the same interface.</p>
<h3 id="what-i-am-doing-with-opnsense">What I am doing with OPNSense<a href="#what-i-am-doing-with-opnsense" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>I host a server (including <a href="https://fetzner.me/en/projects/fetznerdotme/">this website</a>) and I wanted to segregate traffic to and from my server from the rest of my network.
This is accomplishable with VLAN segregation and the NetGear VLAN-aware switch I have. It also lets me segregate and monitor my work VLAN and WLAN&rsquo;s traffic.</p>
<p>OPNSense has intrusion detection and prevention baked into it, which I&rsquo;ve yet to fully explore, but I utilize it.</p>
<p>I also wanted to host a <a href="https://www.wireguard.com/">wireguard</a> peer to serve as a relay and ingress for my private LAN.
The WireGuard tunnel lets me stream video, VNC, file-sharing, and ssh from and between my trusted devices and my LAN.</p>
<p>Lastly, OPNSense can host a DNS server, which was the original catalyst for using IPFire. That way I can address my servers, PC, laptop, and phone by domain name instead of IP; very handy!</p>
]]></content></item><item><title>Porting OpenWRT to a WatchGuard Firebox T35</title><link>https://fetzner.me/en/projects/watchguard_openwrt/</link><pubDate>Mon, 01 Sep 2025 18:11:11 -0600</pubDate><author>Alex Fetzner</author><guid>https://fetzner.me/en/projects/watchguard_openwrt/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<h2 id="status-ongoing">Status: Ongoing<a href="#status-ongoing" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<h3 id="whats-openwrt">What&rsquo;s OpenWRT?<a href="#whats-openwrt" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p><a href="https://openwrt.org/#welcome_to_the_openwrt_project">OpenWRT</a> is an open-source, Linux-based, community-developed, router OS for embedded devices.</p>
<h3 id="whats-a-firebox-t35">What&rsquo;s a Firebox T35?<a href="#whats-a-firebox-t35" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>A Firebox T35 is a hardware firewall developed by WatchGuard. Its a 5-port, gigabyte firebox designed for small business and offices.
It was declared end-of-life by WatchGuard in 2025, with support no longer being provided, nor security firmware/software updates.
Because of it&rsquo;s end-of-life, it&rsquo;s no longer useful as a security appliance for production use.</p>
<h3 id="why-then-install-a-router-os-on-a-firewall">Why then, install a router OS on a firewall?<a href="#why-then-install-a-router-os-on-a-firewall" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>One can find a glut of used, T35s on E-Bay for as little as $20 that are physically in perfect working order.
A friend of mine gave me her used T35 to tinker with.
I was initially interested in installing OPNSense, an open-source firewall OS based on Open-BSD, on the hardware.
The T35, uses the NXP/Freescale T1024 SoC which uses a PowerPC64 architecture.
While the OpenBSD kernel and OPNSense supports PPC64 architecture, and even the QoriQ family of chips the T35 uses,
at the beginning of the project I was mistaken that it was not compatible with PPC64.
Neither OpenWRT nor OPNSense support the T35 with readily installed image.</p>
<p>I decided that it was a worthwhile endeavor to contribute support for the T35 to the OpenWRT project.
Further, the T35 is part of the T-series, a series of similar tabletop firewalls, and development work on the T35 would likely
lead to easier development for support on the other devices in the series.
As of the time of writing, the only WatchGuard product with OpenWRT support is the m300.</p>
<h3 id="first-steps">First steps<a href="#first-steps" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>Gratefully, the T35 has a number of features that make it easier to reverse-engineer and develop on.
Namely, the storage is a removable, writable mSATA SSD.
None of the drive is encrypted (that I encountered, at least).
The first thing I did was remove the drive and capture an image of the entire SSD and each of the partitions.
On booting the WatchGuard, the user is greeted by a U-Boot boot-loader selection menu to boot into standard or recovery mode from the first three partitions.
The partitions each contain a kernel u-image and a device tree.
As expected, the boot-loader boots the kernel in the selected partition.</p>
<p>There is a u-boot command line available where one could change the boot parameters and command-line, but it is password protected.
Ideally, I would reflash the boot-loader so that I could edit it, but a mistake would likely brick the board without a way for me to boot again.
Instead, my plan was to write the OpenWRT kernel and file-system to the SSD partitioned and named the same way such that the existing boot-loader would boot it none the wiser that it was actually booting OpenWRT instead.</p>
<h3 id="getting-a-linux-command-line-on-the-stock-image">Getting a Linux command-line on the stock image<a href="#getting-a-linux-command-line-on-the-stock-image" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>There was much information that would be useful the collect at runtime on the stock image of the T35, namely the information in the <code>/proc</code> file-system. The difficulty is, the shell that one gets in a T35, even when logged in as the admin user, is not a standard Linux shell where one could read and edit files; it&rsquo;s a secure network-appliance shell that deliberately makes it difficult to escape and execute standard Linux commands.
My first attempt was the swap out the <code>/sbin/init</code> process so my script would collect the information I needed and dump it to a file on the disk.
The two problems were:</p>
<ol>
<li>Without the original <code>init</code> process running, much of the info I needed was uninitialized&hellip; whoda guessed that?</li>
<li>The file-system was a <code>tempfs</code> so all writes, even to an external USB drive, were ephemeral</li>
</ol>
<p>Back to the drawing board&hellip;</p>
<p>Fortunately, the WatchGuard engineers sensibly named the user the CLI process runs as user <code>cli</code> and the shell it runs is <code>/usr/bin/cli</code>.
I swapped the file <code>/usr/bin/cli</code> with a BusyBox shell executable, and after logging in through the normal WatchGuard log-in, it launched me a BusyBox shell where I could read and list the files I needed from the <code>/proc</code> tree.
The WatchGuard stock kernel left <code>CONFIG_IKCONFIG_PROC=y</code> in their <code>.config</code>, so I was able to get the original kernel configuration from the running, original stock image.</p>
]]></content></item></channel></rss>