first commit

This commit is contained in:
Blake Ridgway
2026-03-07 21:16:51 -06:00
parent 21bd542469
commit 03fcf37beb
33 changed files with 3532 additions and 0 deletions

173
content/posts/pf-vlans.md Normal file
View File

@@ -0,0 +1,173 @@
---
title: "Setting Up pf with VLANs"
date: 2025-02-10
tags: [pf, networking, openbsd]
slug: pf-vlans
description: "Configuring OpenBSD pf.conf with VLAN segmentation — separating servers, desktop, IoT, and game traffic with sensible firewall rules."
draft: false
---
Network segmentation is one of the first things to get right. Once it's working, everything
else builds on top of it. Once it's broken, debugging why `ssh` works but `rsync` doesn't
becomes a special kind of misery.
This post covers the VLAN setup and the pf rules that go with it.
## The VLAN Layout
Five VLANs, each on a different subnet:
| VLAN | ID | Subnet | Purpose |
|------|-----|---------------|--------------------------------|
| mgmt | 1 | 10.0.1.0/24 | Switches, OOB, firewall mgmt |
| srv | 10 | 10.0.10.0/24 | Servers (srv01, srv02) |
| desk | 20 | 10.0.20.0/24 | Desktop and personal devices |
| game | 30 | 10.0.30.0/24 | Game clients and VMs |
| iot | 40 | 10.0.40.0/24 | Untrusted / IoT / Guest |
The physical layout: one NIC on fw01 is trunked to the main switch. OpenBSD VLAN interfaces
(`vlan10`, `vlan20`, etc.) are configured on top of it. Each VLAN interface gets an IP address
in its respective subnet and acts as the default gateway for devices in that VLAN.
## Configuring VLAN Interfaces
In `/etc/hostname.em1` (the trunked NIC):
```
up
```
Then individual VLAN interface files, e.g. `/etc/hostname.vlan10`:
```
vlandev em1 vlanid 10
inet 10.0.10.1 255.255.255.0
up
```
Repeat for each VLAN. After a reboot (or `sh /etc/netstart`), `ifconfig` should show
all the VLAN interfaces up with their addresses.
## The pf Configuration
This is a simplified version of the actual `pf.conf`. The real one has more rules, but
this captures the structure.
```
# /etc/pf.conf
# --- Interfaces ---
ext_if = "em0" # WAN
vlan_mgmt = "vlan1"
vlan_srv = "vlan10"
vlan_desk = "vlan20"
vlan_game = "vlan30"
vlan_iot = "vlan40"
# --- Tables ---
table <martians> const { 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, \
172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/4, 240.0.0.0/4 }
# --- Options ---
set block-policy drop
set loginterface $ext_if
set skip on lo
# --- Normalization ---
match in all scrub (no-df random-id max-mss 1440)
# --- Default deny ---
block all
# --- Antispoofing ---
antispoof for $ext_if inet
# --- Block martians on WAN ---
block in quick on $ext_if from <martians> to any
block out quick on $ext_if from any to <martians>
# --- Inbound: allow public traffic to services ---
pass in on $ext_if proto tcp to port 80 keep state
pass in on $ext_if proto tcp to port 443 keep state
pass in on $ext_if proto tcp to port 22 keep state
pass in on $ext_if proto udp to port 51820 keep state # WireGuard
# --- Allow all outbound from firewall ---
pass out on $ext_if all keep state
# --- Management VLAN: full access ---
pass in on $vlan_mgmt all keep state
pass out on $vlan_mgmt all keep state
# --- Server VLAN: allow inter-VLAN from desk to srv ---
pass in on $vlan_srv all keep state
pass in on $vlan_desk to $vlan_srv keep state
# Block srv -> desk (servers shouldn't initiate to desktop)
block in on $vlan_srv to 10.0.20.0/24
# --- Desktop VLAN: full internet, access to servers ---
pass in on $vlan_desk all keep state
pass out on $vlan_desk all keep state
# --- Game VLAN: internet only, no access to other VLANs ---
pass in on $vlan_game proto { tcp udp } to port { 80 443 } keep state
pass in on $vlan_game proto udp keep state # game protocols
block in on $vlan_game to 10.0.0.0/8 # no access to RFC1918
# --- IoT VLAN: internet only, fully isolated ---
pass in on $vlan_iot proto tcp to port { 80 443 } keep state
pass in on $vlan_iot proto udp to port 53 keep state # DNS
block in on $vlan_iot to 10.0.0.0/8
```
## The Key Design Decisions
**Default deny with explicit allows** is the only sane approach. Start with `block all` and
add passes for what you actually need. Never the other way around.
**IoT is fully isolated.** Smart home devices get internet access and nothing else. They
cannot reach any other VLAN. If one of them is compromised, the blast radius is just
"attacker can make API calls from your IP." Annoying, not catastrophic.
**Game VLAN blocks RFC1918.** Game clients and VMs get internet but cannot reach any
private address space. This isolates them from everything internal.
**Servers can't initiate to desktop.** A compromised service on srv01 shouldn't be able to
reach my desktop. The server VMs serve; they don't call home.
## relayd for Reverse Proxying
External traffic hits fw01 on port 80/443. `relayd(8)` forwards it to srv01. The pf rules
above allow the initial connection in; relayd handles the rest.
Minimal `/etc/relayd.conf`:
```
# TLS termination and forwarding
http protocol "https" {
tls { keypair ridgwaysystems.org }
pass request header "X-Forwarded-For" value "$REMOTE_ADDR"
pass
}
relay "web" {
listen on egress port 443 tls
protocol "https"
forward to 10.0.10.10 port 8080 check http "/" code 200
}
```
Let's Encrypt certificates via `acme-client(1)` handle the TLS side. A daily cron job
runs `acme-client` and sends SIGHUP to relayd when certs are renewed.
## Debugging
When rules aren't working as expected, `pfctl -ss` shows current state table entries.
`tcpdump -i pflog0` shows what pf is logging. `pfctl -sr` shows the active ruleset.
The most common mistake: forgetting that `block` rules need `quick` to stop rule evaluation
immediately, while without `quick` pf continues evaluating and the last matching rule wins.
Learn this early.
## What's Next
With the network segmented, the next step is getting services deployed on srv01 — starting
with httpd and this website.

View File

@@ -0,0 +1,98 @@
---
title: "The Hardware: What's in the Rack"
date: 2025-01-28
tags: [hardware, homelab]
slug: the-hardware
description: "A tour of the physical hardware — SuperMicro 1U firewall, Dell R720 primary server, Dell R710 secondary, and the desktop control node."
draft: false
---
Before getting into software configuration, it's worth documenting the physical layer. What's
actually in the rack, why those machines, and what each one does.
## The Firewall: SuperMicro 1U
The firewall is a SuperMicro 1U server with a Xeon E3-1230v2 and 16GB ECC RAM. This runs
OpenBSD and handles everything at the network edge:
- **pf** — stateful packet filtering, VLAN routing
- **relayd** — reverse proxy, TLS termination for external services
- **WireGuard** — VPN for remote access
- **unbound** — recursive DNS resolver for internal clients
- **dhcpd** — DHCP for all VLANs
The E3-1230v2 is modest by current standards but easily handles firewall workloads. More
importantly, it supports AES-NI (important for VPN throughput) and runs cool and quiet.
Multiple NICs: one for WAN, one trunked to the main switch carrying tagged VLANs for each
network segment.
## The Primary Server: Dell R720
The R720 is the workhorse. Dual Xeon E5-2600 series processors, 64GB ECC RAM, a few SSDs
for the OS and data volumes.
This runs OpenBSD and hosts:
- **httpd** — web server for this site
- **Gitea** — self-hosted git
- **OpenSMTPD** — email (outbound, plus some inbound)
- **Prometheus + Grafana** — metrics collection and dashboards
- **Matrix** (Conduit) — self-hosted chat
- **Various smaller services** — RSS aggregator, bookmarks, etc.
The R720 is loud. Server-grade loud. It's in a separate room with the rack, so this is
tolerable. The fans throttle down after a few minutes under light load, but under any real
IO they spin up fast.
Power draw is significant — plan for 150-300W depending on load. Not a machine you run
on a home circuit without thinking about it.
## The Secondary Server: Dell R710
The R710 is older — Xeon 5500/5600 series — but has more RAM slots, currently at 48GB.
It runs a mix of OpenBSD base with some Linux VMs managed by `vmm(4)`.
Primary roles:
- **nsd** — authoritative DNS for ridgwaysystems.org zones
- **Linux VMs** — game servers (Minecraft, Valheim, etc.), running in `vmm(4)`
- **Jellyfin** — media server
- **Backup target** — receiving rsync backups from srv01
The R710 is even louder than the R720 under load. Old server hardware wasn't designed with
home environments in mind. ILO (Dell iDRAC) makes remote management workable — I rarely
need to touch it physically.
## The Desktop: Daily Driver and Ansible Control Node
Standard desktop setup: Ryzen, 32GB RAM, NVMe storage, running Linux.
This is the Ansible control node. All infrastructure changes go through playbooks on this
machine and push to the servers. The goal is to get to a point where I could wipe any server
and bring it back up cleanly with `ansible-playbook`.
Not there yet — the playbooks are more "documentation that happens to be executable" than
a fully idempotent rebuild-from-scratch system. That's the project.
## Why Old Server Hardware?
Two reasons: price and ECC RAM.
A Dell R720 can be had for $200-400 on eBay depending on configuration. For that price you
get two server-grade CPUs, ECC RAM, hot-swap storage bays, iDRAC out-of-band management,
and hardware RAID if you want it. Nothing in the consumer space touches this value per
dollar for a home server.
The tradeoffs are power consumption and noise. For a rack in a utility room or basement,
those are manageable.
ECC RAM matters for a storage server. Silent corruption from a bit flip in a RAID controller
or filesystem is the worst kind of failure — the kind you don't notice until you need the
data. ECC doesn't eliminate all failure modes, but it eliminates the commonest one.
## What's Next
Next up: the pf configuration and VLAN setup. This is where most of the interesting work
happens — separating untrusted IoT devices from servers, routing WireGuard traffic, and
setting up relayd to proxy external services.

View File

@@ -0,0 +1,85 @@
---
title: "Why OpenBSD for a Homelab"
date: 2025-01-15
tags: [openbsd, homelab]
slug: why-openbsd
description: "The case for running OpenBSD as the foundation of a homelab — security model, pf, clean base system, and the value of good documentation."
draft: false
---
A few people have asked why I chose OpenBSD for this homelab instead of the usual suspects —
Proxmox, TrueNAS, some flavor of Linux. The short answer: I wanted to actually understand what
my infrastructure is doing, and OpenBSD forces that in a way nothing else does.
## The Security Model Is Different
Most operating systems bolt security on. OpenBSD builds it in.
`pledge(2)` and `unveil(2)` are the clearest expression of this. Programs declare up front exactly
what syscalls they'll use and which filesystem paths they'll touch. The kernel enforces it. If a
daemon gets compromised, the blast radius is bounded by what it pledged. This isn't theoretical —
the base system uses these everywhere.
Compare that to a typical Linux daemon running as root (or even a non-root user) with access to
the full filesystem. The attack surface is enormous by default.
## pf Is the Best Firewall I've Used
I've spent time with iptables, nftables, and a few others. `pf(4)` is in a different category.
The rule syntax reads like English. State tracking is intelligent by default. `relayd(8)` handles
reverse proxying and TLS termination in a way that integrates naturally with pf. The whole
networking stack hangs together coherently.
Here's a minimal pf.conf to give a sense of the syntax:
```
set skip on lo
block all
pass in on egress proto tcp to port { 80 443 } keep state
pass in on egress proto tcp to port 22 keep state
pass out all keep state
```
That's readable. You can audit that in two minutes.
## The Base System Is Clean
OpenBSD ships a complete, minimal base system. No package manager drama, no systemd, no
six-hundred-dependency init system. The base is coherent. Everything in `/usr/bin` and
`/sbin` has been reviewed and fits together.
When I install a service, I know exactly what I'm adding on top of a well-understood foundation.
On a typical Linux distro, I'm never quite sure what's lurking in the base.
## The Documentation Is Accurate
This is underrated. OpenBSD man pages are written by the people who wrote the code. They are
current. They are correct. When the man page says `pledge(2)` takes these promises in this
order, that is exactly what happens.
How many times have you followed a Linux man page only to find it describes behavior from four
kernel versions ago? Or read documentation that's accurate for one distro but not another?
With OpenBSD, the man page is the specification. This matters enormously when you're learning
a new tool or debugging an obscure issue at 2am.
## The Tradeoffs
OpenBSD isn't the right choice for everything. ZFS isn't in the base (use FreeBSD for that).
The package ecosystem is smaller than Linux. Some software just doesn't run on OpenBSD, or
runs with limitations.
For a homelab where the goal is to *understand* the infrastructure rather than just consume
services, these tradeoffs are worth it. The constraint of a smaller, more deliberate ecosystem
means you end up with a leaner, more auditable system.
If you want to run Kubernetes on your homelab, OpenBSD is the wrong choice. If you want to
actually know what your firewall is doing and why, it's worth the investment.
## What's Next
The next post covers the hardware — what's actually in the rack and why those particular
machines. After that, I'll get into the pf configuration and VLAN setup.