Project 8: Firewall Basics (PF or IPFW)

Build a minimal firewall policy that allows SSH and blocks everything else.

Quick Reference

Attribute Value
Difficulty Level 3
Time Estimate 2-3 weekends
Main Programming Language Shell (sh) (Alternatives: csh, Python)
Alternative Programming Languages sh, csh, Python
Coolness Level Level 3
Business Potential Level 2
Prerequisites Projects 1-5 complete, SSH working
Key Topics PF/IPFW, rc.conf, rulesets, testing

1. Learning Objectives

By completing this project, you will:

  1. Enable a firewall at boot (PF or IPFW).
  2. Write a minimal ruleset that permits SSH and blocks all else.
  3. Test rules safely without locking yourself out.
  4. Document a recovery path if the ruleset fails.

2. All Theory Needed (Per-Concept Breakdown)

Concept 1: Packet Filtering Models (PF vs IPFW)

Fundamentals FreeBSD supports multiple firewall frameworks, most commonly PF and IPFW. Both inspect packets and apply rules to allow, block, or redirect traffic. PF uses a ruleset with default block semantics and explicit pass rules, while IPFW uses a numbered rule list that is evaluated in order. The mental model is simple: packets enter an interface, the firewall evaluates rules, and the first matching rule decides the outcome. For this project, you need to understand how rule order and defaults affect whether SSH is allowed or denied.

In practice, write a short checklist for firewall rule evaluation and confirm it after each reboot. This keeps the concept concrete and prevents accidental drift between sessions.

Deep Dive into the concept PF (Packet Filter) originated in OpenBSD and is valued for clear, declarative rules. A typical PF ruleset uses a default deny policy (block everything) and then adds explicit pass rules for allowed services. PF rules are evaluated top-down, and the quick keyword forces immediate action. PF also supports stateful filtering, which means it can track established connections and automatically allow return traffic. This is why a simple rule like pass in on egress proto tcp to port 22 keep state can permit SSH safely.

IPFW (IP Firewall) is native to FreeBSD and uses a numbered rule list. Rules are evaluated in numeric order, and the first match applies. This model is precise but can be verbose. IPFW can be stateful using keep-state or check-state rules. The two systems are different but conceptually similar: you define a default deny, then allow what you need. The choice often comes down to preference and documentation. For a learning project, PF is concise; IPFW is closer to FreeBSD’s historical defaults. Either is acceptable as long as you understand the logic.

The critical danger in firewalling is self-inflicted lockout. If you apply a ruleset that blocks SSH while connected over SSH, you will lose access. The safe practice is to keep a console session open, apply rules temporarily first, and only then enable at boot. Both PF and IPFW can load rules without enabling them permanently, which is ideal for testing. In PF, pfctl -f /etc/pf.conf loads rules, while pfctl -e enables filtering. In IPFW, ipfw add manipulates rules directly, and you can flush them with ipfw -f flush if needed.

Finally, firewalls are about intent. You must decide which ports to allow, and everything else should be denied. In this project, the minimal policy is to allow SSH inbound, allow established outbound traffic, and block everything else inbound. This is a clean, defendable baseline that matches real-world best practices.

Operationally, firewall rule evaluation is easiest to keep stable when you treat it as a small contract between configuration, tooling, and observable outputs. Write down the exact files that own the state and the commands that reveal the current truth. Then verify the contract at three points: immediately after you make the change, after a reboot, and after a deliberate disturbance such as restarting services or reloading modules. FreeBSD rewards this discipline because it rarely hides state; if something changes, it is usually in a file you control. Make a habit of collecting a before-and-after snapshot of commands and outputs so you can explain which change caused which effect.

At scale, firewall rule evaluation is also about failure containment. Identify what must remain available when something breaks and design a safe escape hatch. For example, keep console access for firewall changes, keep a previous boot environment for upgrades, or keep a dataset snapshot before risky edits. The same pattern applies across domains: define invariants, define the rollback path, and then only proceed when you can trigger that rollback quickly. Finally, test the failure path while the system is healthy; you learn more from a controlled rollback than from an emergency. This perspective turns the lab exercise into an operational capability you can trust on production systems.

How this fits on projects

Definitions & key terms

  • PF -> Packet Filter firewall.
  • IPFW -> FreeBSD IP firewall.
  • Stateful -> Tracks established connections.
  • Default deny -> Block everything not explicitly allowed.

Mental model diagram

Packet -> Rule evaluation -> Pass or Block

How it works (step-by-step, with invariants and failure modes)

  1. Ruleset is loaded into firewall.
  2. Firewall evaluates packets in order.
  3. First matching rule applies.
  4. State tracking allows return traffic.

Invariants:

  • Rule order matters.
  • Default rule applies if no match.

Failure modes:

  • SSH blocked by default rule.
  • Rules loaded but firewall disabled.

Minimal concrete example

pfctl -f /etc/pf.conf
pfctl -e

Common misconceptions

  • “Allowing outbound is enough.” -> Inbound SSH still needs explicit allow.
  • “Order doesn’t matter.” -> It matters for both PF and IPFW.

Check-your-understanding questions

  1. Why is stateful filtering important?
  2. How does PF decide which rule applies?
  3. What is the risk of enabling firewall at boot before testing?

Check-your-understanding answers

  1. It allows return traffic without extra rules.
  2. PF evaluates top-down and uses the first match (or quick).
  3. You can lock yourself out permanently.

Real-world applications

  • Default deny policies for servers.
  • Secure perimeter for SSH and management services.

Where you’ll apply it

  • Section 3.2 Functional Requirements
  • Section 5.10 Phase 2
  • Also used in: P10 Update Runbook

References

  • “Mastering FreeBSD and OpenBSD Security” (Ch. 8)
  • FreeBSD Handbook: Firewalls

Key insights A firewall is policy expressed in rule order; test before you commit.

Summary Understanding PF/IPFW models prevents lockouts and makes security explicit.

Homework/Exercises to practice the concept

  1. Write a PF ruleset that allows SSH only.
  2. Convert that ruleset into IPFW rules.
  3. Practice enabling and disabling safely.

Solutions to the homework/exercises

  1. Default block + pass in on port 22.
  2. IPFW allow rule for port 22, then deny all.
  3. Use console access and temporary loads.

Concept 2: Service Enablement and Safe Testing

Fundamentals Firewalls on FreeBSD are enabled through rc.conf and, in some cases, loader.conf. PF needs pf_enable="YES" and a rules file, while IPFW requires firewall_enable="YES" and a ruleset specification. Safe testing requires loading rules temporarily, verifying SSH access, and only then enabling the firewall at boot. This project relies on these operational habits to avoid locking yourself out.

In practice, write a short checklist for safe firewall enablement and confirm it after each reboot. This keeps the concept concrete and prevents accidental drift between sessions.

In practice, rehearse the steps on a disposable VM so you can recognize normal outputs and failure signals quickly.

Deep Dive into the concept Service enablement for firewalls is a two-step process: configure the rules, then enable the service. For PF, the default configuration file is /etc/pf.conf, and you can test it with pfctl -nf /etc/pf.conf (syntax check). Loading rules with pfctl -f does not automatically enable PF; you must pfctl -e. This separation allows you to validate rules before enforcement. In IPFW, rules are loaded directly into the kernel rule table; you can list them with ipfw list and remove them with ipfw -f flush. The enablement step is controlled by rc.conf, which loads your ruleset at boot.

Safe testing is non-negotiable. The correct workflow is to open a console session, apply the rules temporarily, then test SSH from another host. If SSH fails, you revert immediately. Only after a successful test do you enable the firewall in rc.conf. You should also plan a recovery path, such as booting into single-user mode or disabling the firewall from the console. This is how you avoid being locked out of your own system.

FreeBSD also allows firewall configuration at the loader level for early boot protection. This is rarely required in labs but is important in production. For this project, you will focus on rc.conf enablement and runtime testing. The key is to document the exact commands for enabling, disabling, and verifying the firewall so you can execute them under pressure.

Operationally, safe firewall enablement is easiest to keep stable when you treat it as a small contract between configuration, tooling, and observable outputs. Write down the exact files that own the state and the commands that reveal the current truth. Then verify the contract at three points: immediately after you make the change, after a reboot, and after a deliberate disturbance such as restarting services or reloading modules. FreeBSD rewards this discipline because it rarely hides state; if something changes, it is usually in a file you control. Make a habit of collecting a before-and-after snapshot of commands and outputs so you can explain which change caused which effect.

At scale, safe firewall enablement is also about failure containment. Identify what must remain available when something breaks and design a safe escape hatch. For example, keep console access for firewall changes, keep a previous boot environment for upgrades, or keep a dataset snapshot before risky edits. The same pattern applies across domains: define invariants, define the rollback path, and then only proceed when you can trigger that rollback quickly. Finally, test the failure path while the system is healthy; you learn more from a controlled rollback than from an emergency. This perspective turns the lab exercise into an operational capability you can trust on production systems.

Also, document the specific signals you will treat as success or failure for safe firewall enablement. For access or policy topics, that might be a deliberate allow case and a deliberate deny case that is correctly logged. For workflow topics, it might be a version change plus a service health check. Writing down these signals forces you to define what working actually means and prevents you from moving forward on assumptions.

How this fits on projects

Definitions & key terms

  • pf.conf -> PF ruleset file.
  • firewall_enable -> rc.conf flag for IPFW.
  • pf_enable -> rc.conf flag for PF.
  • loader.conf -> Boot-time kernel module configuration.

Mental model diagram

Rules file -> Syntax check -> Load -> Test -> Enable at boot

How it works (step-by-step, with invariants and failure modes)

  1. Write ruleset.
  2. Check syntax.
  3. Load rules temporarily.
  4. Test SSH access.
  5. Enable at boot if successful.

Invariants:

  • Always keep a console session open.
  • Test before enabling at boot.

Failure modes:

  • Lockout due to missing SSH pass rule.
  • Syntax errors preventing firewall from loading.

Minimal concrete example

pfctl -nf /etc/pf.conf
pfctl -f /etc/pf.conf

Common misconceptions

  • “Enabling in rc.conf is enough.” -> Rules must be correct and tested.
  • “I can recover later.” -> Without console access, recovery can be painful.

Check-your-understanding questions

  1. Why run a syntax check before enabling PF?
  2. What is the safest way to test a firewall change?
  3. How do you disable PF quickly?

Check-your-understanding answers

  1. To avoid loading invalid rules that block traffic.
  2. Keep console open and test SSH from another host.
  3. pfctl -d disables PF.

Real-world applications

  • Securing production servers while avoiding downtime.
  • Change control practices for network policies.

Where you’ll apply it

  • Section 3.2 Functional Requirements
  • Section 5.10 Phase 2
  • Also used in: P10 Update Runbook

References

  • “Mastering FreeBSD and OpenBSD Security” (Ch. 8)
  • FreeBSD Handbook: pf and ipfw

Key insights Firewall changes must be tested before they are permanent.

Summary Safe enablement is a discipline: validate, test, then persist.

Homework/Exercises to practice the concept

  1. Write and validate a PF ruleset with pfctl -n.
  2. Enable PF temporarily and test SSH.
  3. Disable PF and confirm connectivity restoration.

Solutions to the homework/exercises

  1. Syntax check should report no errors.
  2. SSH should succeed if rule is correct.
  3. pfctl -d restores access.

3. Project Specification

3.1 What You Will Build

A minimal firewall ruleset (PF or IPFW) that allows SSH and blocks all other inbound traffic, with documented enablement and recovery steps.

3.2 Functional Requirements

  1. Firewall enabled at boot.
  2. Ruleset loaded with default deny.
  3. SSH allowed inbound.
  4. All other inbound blocked.
  5. Recovery path documented.

3.3 Non-Functional Requirements

  • Performance: Firewall rules apply with minimal latency.
  • Reliability: Rules persist after reboot.
  • Usability: Recovery steps are simple and tested.

3.4 Example Usage / Output

$ pfctl -sr
block all
pass in on egress proto tcp to port 22 keep state

3.5 Data Formats / Schemas / Protocols

  • pf.conf
    set skip on lo
    block all
    pass in on egress proto tcp to port 22 keep state
    pass out all keep state
    

3.6 Edge Cases

  • SSH blocked by incorrect interface name.
  • Ruleset loads but firewall disabled.
  • Typo in pf.conf prevents load.

3.7 Real World Outcome

A FreeBSD VM that is reachable only via SSH and otherwise closed to inbound traffic.

3.7.1 How to Run (Copy/Paste)

pfctl -nf /etc/pf.conf
pfctl -f /etc/pf.conf
pfctl -e

3.7.2 Golden Path Demo (Deterministic)

  • Use PF and a fixed ruleset.
  • Test SSH from a known host.

3.7.3 If CLI: provide an exact terminal transcript

$ pfctl -sr
block all
pass in on egress proto tcp to port 22 keep state
pass out all keep state

$ echo $?
0

Failure demo (deterministic)

$ pfctl -f /etc/pf.conf
pfctl: /etc/pf.conf:4: syntax error

$ echo $?
1

4. Solution Architecture

4.1 High-Level Design

Ruleset -> Test -> Enable -> Verify -> Document recovery

4.2 Key Components

Component Responsibility Key Decisions
Ruleset Defines policy PF or IPFW
rc.conf Boot enablement pf_enable or firewall_enable
Recovery plan Prevent lockout Console access

4.3 Data Structures (No Full Code)

FirewallPolicy
- default: block
- allow_inbound: [22/tcp]
- allow_outbound: all

4.4 Algorithm Overview

Key Algorithm: Safe Firewall Enablement

  1. Write ruleset.
  2. Syntax check.
  3. Load rules temporarily.
  4. Test SSH.
  5. Enable at boot.

Complexity Analysis:

  • Time: O(rule count)
  • Space: O(rule count)

5. Implementation Guide

5.1 Development Environment Setup

# Keep console open for recovery

5.2 Project Structure

firewall-lab/
+-- pf.conf
+-- enablement-notes.md
+-- recovery.md

5.3 The Core Question You’re Answering

“How do I secure a FreeBSD system by default?”

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. Packet filter models (PF/IPFW)
  2. Safe enablement and recovery

5.5 Questions to Guide Your Design

  1. Which firewall will you use (PF or IPFW)?
  2. Which inbound services should be allowed?
  3. How will you recover if SSH is blocked?

5.6 Thinking Exercise

Lockout Drill

Write a ruleset that blocks SSH, then recover using console.

5.7 The Interview Questions They’ll Ask

  1. Why use PF or IPFW on FreeBSD?
  2. How do you enable firewall rules at boot?
  3. What is a default deny policy?
  4. How do you test your rules?
  5. How do you avoid locking yourself out?

5.8 Hints in Layers

Hint 1: Start with default deny Allow only SSH.

Hint 2: Test before enabling Use pfctl -n.

Hint 3: Keep console open Always have a recovery path.

Hint 4: Document recovery Write the exact steps to disable firewall.

5.9 Books That Will Help

Topic Book Chapter
Firewalls “Mastering FreeBSD and OpenBSD Security” Ch. 8

5.10 Implementation Phases

Phase 1: Ruleset Draft (2-3 hours)

Goals: Write minimal rules. Tasks:

  1. Draft PF or IPFW rules.
  2. Validate syntax. Checkpoint: no syntax errors.

Phase 2: Safe Testing (2-3 hours)

Goals: Verify SSH access. Tasks:

  1. Load rules temporarily.
  2. Test SSH from another host. Checkpoint: SSH still works.

Phase 3: Enablement (2-3 hours)

Goals: Persist rules across reboot. Tasks:

  1. Enable firewall in rc.conf.
  2. Reboot and retest. Checkpoint: firewall active after reboot.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Firewall PF, IPFW PF Concise rules
Default policy allow, deny deny Secure baseline
Recovery method console, single-user console Fastest

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Syntax Tests Validate rules pfctl -n
Connectivity Tests SSH access ssh admin@vm
Persistence Tests After reboot pfctl -sr

6.2 Critical Test Cases

  1. Ruleset loads without errors.
  2. SSH allowed from another host.
  3. All other inbound blocked using a port scan.

6.3 Test Data

Allowed port: 22/tcp
Blocked ports: 80, 443

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
SSH blocked Connection refused Add pass rule
Wrong interface Rules ineffective Use correct egress iface
Firewall not enabled Rules not enforced Set rc.conf flags

7.2 Debugging Strategies

  • Temporary load: test rules without enabling at boot.
  • Use pfctl -sr: confirm active rules.

7.3 Performance Traps

  • Very large rulesets can slow packet processing.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a rule to allow HTTP and test.
  • Log blocked packets for debugging.

8.2 Intermediate Extensions

  • Create separate rules for LAN vs WAN.
  • Add rate-limiting for SSH attempts.

8.3 Advanced Extensions

  • Build a stateful NAT gateway.
  • Integrate firewall rules with jail networking.

9. Real-World Connections

9.1 Industry Applications

  • Server hardening: default deny firewalls.
  • Compliance: least-exposed service policies.
  • PF: packet filter system.
  • IPFW: FreeBSD firewall.

9.3 Interview Relevance

  • Explaining firewall rule evaluation.
  • Safe change deployment practices.

10. Resources

10.1 Essential Reading

  • “Mastering FreeBSD and OpenBSD Security” by Korff, Hope, Potter - Ch. 8
  • FreeBSD Handbook - PF and IPFW

10.2 Video Resources

  • “PF Firewall Basics” - community talks

10.3 Tools & Documentation

  • pfctl(8): PF control utility
  • ipfw(8): IPFW control utility

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain default deny.
  • I can describe PF/IPFW rule order.
  • I can recover from a lockout.

11.2 Implementation

  • Firewall enabled and tested.
  • SSH allowed, others blocked.
  • Recovery steps documented.

11.3 Growth

  • I can extend the ruleset safely.
  • I can explain firewall choices in interviews.
  • I can teach safe firewall testing.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Firewall enabled with default deny.
  • SSH allowed inbound.
  • Recovery plan documented.

Full Completion:

  • Firewall persists after reboot.
  • Lockout drill completed and recovered.

Excellence (Going Above & Beyond):

  • Rate-limiting or logging added.
  • Separate rules for LAN/WAN created.