Chapter 1: Build Your Practice Lab

Create and seal the lab network

Phases 03 and 04: build the virtual switch rhcebr0 with the lab's addresses and DNS names, then add a small nftables firewall that makes it one-way. This is what makes the lab safe to run on your home network.

Phase 03 · Create the lab network (~5 min)

  • Create the rhcebr0 bridge at 172.25.250.254/24 with DHCP, DNS and NAT.

LXD UI: start in the default project

The project selector (top left) must say default: networks made here are shared with every project. Then go to Networks → Create network.

LXD UI: fill in the form

FieldValue
TypeBridge
Namerhcebr0
DescriptionKernel Path sealed lab network
IPv4 address172.25.250.254/24
IPv4 NATOn
IPv6 addressnone
DNS domain (in the DNS section)lab.example.com

LXD UI: check the YAML configuration

Open the YAML configuration tab and make it match this. The raw.dnsmasq lines point content.example.com and materials.example.com at the utility VM. Then click Create.

name: rhcebr0
type: bridge
description: Kernel Path sealed lab network
config:
  ipv4.address: 172.25.250.254/24
  ipv4.nat: "true"
  ipv4.dhcp.ranges: 172.25.250.100-172.25.250.199   # .8 to .13 stay free for fixed IPs
  ipv6.address: none
  dns.domain: lab.example.com
  raw.dnsmasq: |-
    host-record=content.example.com,172.25.250.8
    host-record=materials.example.com,172.25.250.8

Host: confirm the bridge is up

You will see both of the host's addresses, as explained in the overview.

ip -brief -4 addr
# enp1s0    UP   <HOST_LAN_IP>/24     ← your LAN side (name and /prefix vary; Wi-Fi shows wlp… or wlx…)
# rhcebr0   UP   172.25.250.254/24     ← the lab gateway

How the seal works

The seal is a small nftables table on the host that only looks at traffic touching rhcebr0. Your local network and the rest of the host are untouched. It has three chains, one for each direction traffic can take:

The map in the overview lets you trace each of these flows.

Phase 04 · Seal the lab network (~10 min)

  • Load an nftables table that makes rhcebr0 one-way, on every boot.

Host: create the rules file

#!/usr/sbin/nft -f
# Seals the RHCE lab bridge (rhcebr0). Loaded by rhce-isolate.service.
# The first two lines let this file be reloaded safely.
table inet rhce_isolate
delete table inet rhce_isolate

table inet rhce_isolate {

  # Traffic passing THROUGH the host (between the lab and anything else)
  chain forward {
    type filter hook forward priority -10; policy accept;

    iifname "rhcebr0" oifname "rhcebr0" accept                   # VM to VM
    oifname "rhcebr0" ct state established,related accept         # replies to the VMs
    oifname "rhcebr0" drop                                        # anything new INTO the lab

    iifname "rhcebr0" ip daddr { 10.0.0.0/8, 172.16.0.0/12,
                                 192.168.0.0/16, 100.64.0.0/10 } drop   # lab to private/VPN ranges
    # everything else from the lab (the internet) is allowed
  }

  # Traffic FROM the host itself TO the lab
  chain output {
    type filter hook output priority -10; policy accept;

    oifname "rhcebr0" udp sport 67 accept                         # DHCP replies to VMs
    oifname "rhcebr0" ct state established,related accept         # DNS replies to VMs
    oifname "rhcebr0" drop                                        # host can't start connections
  }

  # Traffic FROM the lab TO the host itself
  chain input {
    type filter hook input priority -10; policy accept;

    iifname "rhcebr0" udp dport { 53, 67 } accept                 # DNS + DHCP
    iifname "rhcebr0" tcp dport 53 accept                         # DNS over TCP
    iifname "rhcebr0" drop                                        # nothing else on the host
  }
}
ChainControlsResult
forwardTraffic passing through the hostNobody from your LAN gets in; the lab can't reach your LAN; the lab can reach the internet
outputTraffic the host sends to the labThe host can't ssh or ping the VMs. It only hands out IPs and answers DNS.
inputTraffic the lab sends to the hostVMs can only ask the host for an IP and name lookups. No access to its SSH, LXD UI or anything else.

Host: create a service that loads it on boot

[Unit]
Description=Seal the RHCE lab network (rhcebr0)
After=network-pre.target snap.lxd.daemon.service
Wants=network-pre.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/nft -f /etc/nftables-rhce.nft
ExecStop=/usr/sbin/nft delete table inet rhce_isolate

[Install]
WantedBy=multi-user.target

Host: check the syntax, then turn it on

sudo nft -c -f /etc/nftables-rhce.nft          # -c = check only; no output means OK
sudo systemctl daemon-reload
sudo systemctl enable --now rhce-isolate.service
sudo nft list table inet rhce_isolate          # shows the three chains

Host: only if ufw is active, let the lab through it

ufw's default policy blocks the lab's DHCP and DNS requests and its internet traffic. That only shows up later as a confusing failure, so add these now:

sudo ufw status | head -1                                   # "Status: inactive"? skip this step
sudo ufw allow in on rhcebr0 to any port 67 proto udp       # DHCP: VMs get their IPs
sudo ufw allow in on rhcebr0 to any port 53                 # DNS: lab names resolve
sudo ufw route allow in on rhcebr0                          # forwarding: VMs reach the internet
sudo ufw reload

These rules only add permissions on rhcebr0. The seal still drops everything aimed at your local network. More detail in troubleshooting.