Learn › Firewalls ›
Written for Rasputin 2026.08.5
Declaring firewall rules instead of editing them
Intermediate · about 35 minutes · hands-on lab in a folder you create
Before this: How a firewall decides.
In the track: Network engineer.
What you need
- The beginner lesson, How a firewall decides. It covers zones, rules, default policies and why the first matching rule wins.
- A terminal on macOS or Linux. On Windows, WSL (Windows Subsystem for Linux) gives you a Linux terminal. The lab was run on macOS 15, Debian 12 and BusyBox, a minimal Linux toolset, with the same output on all three. It was not tested on Windows.
sh,printf,cat,cp,mv,grepandcksum, already part of macOS and Linux. Nothing to install, and no administrator rights.
You do not need Rasputin hardware, Rasputin code, or an account anywhere. Changing a real firewall needs administrator rights, so the lab builds one out of text files.
The situation
Rasputin manages one firewall running OpenWrt, an open-source operating system for routers. OpenWrt keeps its rules in its own configuration files and ships its own admin web interface. So the same rules can be changed from two places: from Rasputin, and on the box itself.
That raises two questions before any design. Where does the true copy of the rules live? And when the box and that copy disagree, who decides which one is right?
The decision, and what lost
You declare what you want, and Rasputin compares that with what the box runs. In the manual’s
words, “the tabs do not configure your firewall. They edit a list of intents on the control
plane.” An intent is one declared rule or port forward. APPLY compiles the list, pushes it
to the firewall and records a fingerprint of what it sent: a short value computed from the
content, which changes whenever the content does. RECONCILE fetches the fingerprint of what the
firewall is running and compares the two. When they differ, the state is DRIFT.
Reconcile reports drift; it does not repair it. The manual: “Reconcile does not repair. In a mode where Rasputin manages the firewall it is a read.” To keep a change made on the box, you write it into an intent and apply. To discard it, you apply the intents you have.
| Alternative | Why it lost |
|---|---|
| Edit the firewall’s own configuration directly | The firewall-integration design, an internal record, gives three reasons. The one this lesson is about: “With direct UCI editing, drift would be ’the user changed it on purpose’ 90% of the time and worth nothing as a signal.” UCI is OpenWrt’s configuration system. The other two: intents survive a change of firewall software (“the compiler changes, not the intents”), and most people “want a form”, not a config file. |
| Let reconcile repair drift itself | The design keeps repair out of reconcile, which “avoids the ‘reconcile randomly mutated my config’ surprise.” |
| Push each intent as soon as you save it | Not settled. The design lists it as an open question. |
What it cost
Keeping a change made on the box means typing it twice. A rule or port forward changed in the
firewall’s own interface shows as DRIFT at the next reconcile, which runs every five minutes by
default. Not every setting on the box is compared, so some changes never show as drift. The
manual: “There is no ‘adopt this change’ button.” You recreate the change as an intent, then
apply.
The list is not the firewall. “Delete does not un-push.” Deleting an intent removes it from the list; the rule stays live on the box until the list is next applied. And an apply is not an undo: “nothing stores what the firewall held before.”
Taking over costs the stock rules. Rasputin owns the rule and port-forward sections outright, and an apply replaces them wholesale, including the stock rules OpenWrt shipped with. Rasputin re-creates three of them as ordinary intents, seeded at most once per cluster.
Drift needed a precise definition. On the first hardware bench, a firewall nobody had touched
showed as drifted, because its factory rules did not match the intents. The code’s comment
records it: “reconcile ran before any apply and the UI showed a drift banner on an untouched
firewall.” Drift now requires a prior apply, and a firewall Rasputin has never applied to reads
PENDING.
What Rasputin does not do here
Rasputin never writes zones, default policies or the forwarding rules between zones. Rules run in
the order they were created, the first match wins, and the interface cannot reorder them. The
intrusion detection on the same box only detects; it never blocks, and no intent is enforced by
it. In Join my existing network mode, APPLY pushes nothing and reports success, and
RECONCILE is not a read: it idles the firewall node.
Try it
You will build a firewall out of text files, change it by hand, and handle that change two ways: repair it automatically, or flag it and decide.
1. Make a sandbox.
mkdir firewall-intents
cd firewall-intents
2. Set up the box and your intents.
printf '%s\n' 'Allow-DHCP-Renew wan fw 68 accept' 'Allow-Ping wan fw icmp accept' > firewall.txt
printf '%s\n' 'Allow-Ping wan fw icmp accept' 'block-mail lan wan 25 reject' > intents.txt
firewall.txt stands for what the firewall runs, starting with its stock rules. intents.txt is
your declared list. Each line is: name, from zone, to zone, port or protocol, action. fw means
the firewall itself.
3. Write apply and status. Paste the whole block at once:
cat > apply.sh <<'EOF'
cp intents.txt firewall.txt
cksum < firewall.txt > pushed.txt
echo "applied"
EOF
cat > status.sh <<'EOF'
if [ ! -f pushed.txt ]; then
echo "PENDING: never applied"
elif [ "$(cksum < firewall.txt)" != "$(cat pushed.txt)" ]; then
echo "DRIFT: the firewall changed since the last apply"
elif [ "$(cksum < intents.txt)" != "$(cat pushed.txt)" ]; then
echo "PENDING: intents not applied yet"
else
echo "IN SYNC"
fi
EOF
cat > apply.sh <<'EOF' writes every line up to EOF into the file. cp replaces the whole
file, as an apply replaces whole sections. cksum < firewall.txt prints a checksum and a size:
the fingerprint. pushed.txt keeps the fingerprint of what was last pushed. status.sh asks
three questions in order: was anything ever applied; does the firewall still match what was
pushed; do the intents? Drift is checked before pending, as in Rasputin.
4. Check a firewall you have never applied to. The two files differ. Predict the status:
sh status.sh
PENDING: never applied
The files disagree, but nothing was pushed, so there is nothing to have drifted from. Without the first check, this script would repeat the bench’s mistake.
5. Apply.
sh apply.sh
sh status.sh
cat firewall.txt
applied
IN SYNC
Allow-Ping wan fw icmp accept
block-mail lan wan 25 reject
Allow-DHCP-Renew is gone. Nobody deleted it: the apply replaced the whole file.
6. Change the box by hand. Suppose you open the firewall’s own interface and add a rule on purpose, to fix something tonight:
echo 'guest-dns guest fw 53 accept' >> firewall.txt
sh status.sh
DRIFT: the firewall changed since the last apply
>> adds a line to the end of a file.
7. Option one: repair drift automatically.
cat > autofix.sh <<'EOF'
case "$(sh status.sh)" in
DRIFT*) sh apply.sh ;;
esac
sh status.sh
EOF
sh autofix.sh
grep -c guest firewall.txt
applied
IN SYNC
0
case … DRIFT*) runs the apply only when the status starts with DRIFT. grep -c counts
matching lines. The status is green, your fix is gone, and nothing records that it existed.
8. Option two: flag it and decide. Make the change again, then keep it by adding it to the intents:
echo 'guest-dns guest fw 53 accept' >> firewall.txt
sh status.sh
echo 'guest-dns guest fw 53 accept' >> intents.txt
sh status.sh
sh apply.sh
sh status.sh
DRIFT: the firewall changed since the last apply
DRIFT: the firewall changed since the last apply
applied
IN SYNC
Look at the second line. The firewall and the intents now hold the same lines, yet the status
still says drift. It compares the firewall with what was pushed, and nobody pushed that rule.
Discarding the change instead would be sh apply.sh alone.
9. Delete an intent.
grep -v block-mail intents.txt > edited.txt
mv edited.txt intents.txt
sh status.sh
grep -c block-mail firewall.txt
sh apply.sh
grep -c block-mail firewall.txt
PENDING: intents not applied yet
1
applied
0
grep -v keeps every line that does not match, and mv puts the result back. The rule left your
list at once and left the firewall only at the apply.
10. Clean up.
cd ..
rm -rf firewall-intents
rm -rf deletes the folder and everything in it without asking. It cannot be undone, so check
you typed firewall-intents.
Check yourself
- In step 5, what removed
Allow-DHCP-Renew? - What breaks if
status.shchecks pending before drift, and someone changes the box while you have edits you have not applied? - In step 8, once the rule was in
intents.txttoo, both files matched. Why isDRIFTstill the right answer? - In Rasputin, you delete a port forward to close a port. Is the port closed?
- A teammate proposes a nightly job that re-applies every firewall’s intents “to keep them honest”. What does it cost, and what would you run instead?
Answers
- The apply. It replaces the whole rule set, stock rules included.
- The status reads
PENDING, you apply, and the hand-made change is overwritten without ever having been shown to you. - Matching files are not the same as a pushed state. The rule reached the box by hand, and only an apply makes it part of what Rasputin pushed.
- Not yet. The intent is gone, but the forward stays live until the intent list is next applied.
- It is step 7 on a timer: every change made on a box is erased overnight, deliberate or not, with nothing recorded. Run the comparison on the timer and leave the apply to a person.
Where to go next
- The whole model, with its states: How the Firewall section works, in Rasputin’s manual.
- Writing a rule that fires: Write a firewall rule covers rule order and the three seeded rules.
- The code: drift’s definition in
store.go, and the apply and reconcile steps injobs.go.
Reviewed 2026-09-12. Something wrong or unclear in this lesson? Tell us.