<?xml version="1.0" encoding="UTF-8"?>
<!--
    pfSense / OPNsense Hardened Baseline Configuration
    ==================================================
    Template for a production pfSense (2.7+) or OPNsense (24.x+) deployment.
    CIS-aligned, includes firewall policy, interfaces, NAT, DHCP, DNS, VPN,
    IDS, and hardened system defaults. Adapt placeholders before deploying.

    Import via Diagnostics > Backup & Restore > Restore configuration,
    or paste selected sections into Diagnostics > Edit File.

    Built by Hak at VantagePoint Networks. MIT licensed.

    PLACEHOLDERS TO REPLACE BEFORE DEPLOY:
      {{HOSTNAME}}          e.g. fw-hq-01
      {{DOMAIN}}            e.g. example.internal
      {{WAN_IF}}             physical interface name (igb0, em0, etc.)
      {{LAN_IF}}             physical interface name
      {{DMZ_IF}}             physical interface name
      {{GUEST_IF}}           physical interface name
      {{WAN_IP}} / mask      static WAN IP if not DHCP
      {{LAN_NET}}            e.g. 10.10.0.0/24
      {{DMZ_NET}}            e.g. 10.20.0.0/24
      {{GUEST_NET}}          e.g. 10.90.0.0/24
      {{ADMIN_SUBNET}}       management CIDR with full access
      {{NTP_SERVERS}}        e.g. time.cloudflare.com 0.pool.ntp.org
      {{SYSLOG_SERVER}}      central SIEM
      {{SNMP_LOCATION}}      physical location string
-->
<pfsense>
  <version>23.5</version>
  <lastchange/>

  <!-- =====================================================================
       SYSTEM + HARDENING
       ===================================================================== -->
  <system>
    <optimization>normal</optimization>
    <hostname>{{HOSTNAME}}</hostname>
    <domain>{{DOMAIN}}</domain>
    <dnsserver>9.9.9.9</dnsserver>
    <dnsserver>1.1.1.1</dnsserver>
    <dnsserver>149.112.112.112</dnsserver>
    <dnsallowoverride>0</dnsallowoverride>
    <timezone>Etc/UTC</timezone>
    <timeservers>{{NTP_SERVERS}}</timeservers>
    <webgui>
      <protocol>https</protocol>
      <port>8443</port>
      <max-procs>2</max-procs>
      <nodnsrebindcheck>0</nodnsrebindcheck>
      <nohttpreferercheck>0</nohttpreferercheck>
      <loginautocomplete>0</loginautocomplete>
      <disableconsolemenu/>
      <ssl-certref>webgui-cert-ref</ssl-certref>
      <disablehttpredirect>1</disablehttpredirect>
      <althostnames></althostnames>
      <session_timeout>20</session_timeout>
      <logincss>1b4f2d;</logincss>
    </webgui>
    <disablenatreflection>1</disablenatreflection>
    <disablesegmentationoffloading>1</disablesegmentationoffloading>
    <disablelargereceiveoffloading>1</disablelargereceiveoffloading>
    <ipv6allow/>
    <powerd_ac_mode>hadp</powerd_ac_mode>
    <powerd_battery_mode>hadp</powerd_battery_mode>
    <powerd_normal_mode>hadp</powerd_normal_mode>
    <bogons>
      <interval>monthly</interval>
    </bogons>
    <hn_altq_enable>0</hn_altq_enable>
    <ssh>
      <enable>enabled</enable>
      <port>22</port>
      <sshdkeyonly>enabled</sshdkeyonly>
      <sshdagentforwarding>1</sshdagentforwarding>
    </ssh>
    <serialspeed>115200</serialspeed>
    <primaryconsole>serial</primaryconsole>
    <disablechecksumoffloading>1</disablechecksumoffloading>

    <user>
      <name>admin</name>
      <scope>user</scope>
      <groupname>admins</groupname>
      <password>$2y$10$REPLACE_BCRYPT_HASH</password>
      <uid>0</uid>
      <priv>user-shell-access</priv>
      <authorizedkeys>CHANGE_ME_BASE64_SSH_PUBKEY</authorizedkeys>
      <expires>2027-12-31</expires>
      <descr>Platform admin - MFA required via TOTP</descr>
    </user>

    <group>
      <name>admins</name>
      <description>System administrators</description>
      <scope>system</scope>
      <gid>1999</gid>
      <priv>page-all</priv>
    </group>
  </system>

  <!-- =====================================================================
       INTERFACES
       ===================================================================== -->
  <interfaces>
    <wan>
      <if>{{WAN_IF}}</if>
      <descr>WAN</descr>
      <enable>1</enable>
      <spoofmac/>
      <blockpriv>1</blockpriv>
      <blockbogons>1</blockbogons>
      <ipaddr>{{WAN_IP}}</ipaddr>
      <subnet>30</subnet>
      <gateway>WAN_GW</gateway>
      <mtu>1500</mtu>
    </wan>

    <lan>
      <if>{{LAN_IF}}</if>
      <descr>LAN</descr>
      <enable>1</enable>
      <ipaddr>{{LAN_NET_FIRST_IP}}</ipaddr>
      <subnet>24</subnet>
    </lan>

    <opt1>
      <if>{{DMZ_IF}}</if>
      <descr>DMZ</descr>
      <enable>1</enable>
      <ipaddr>{{DMZ_NET_FIRST_IP}}</ipaddr>
      <subnet>24</subnet>
    </opt1>

    <opt2>
      <if>{{GUEST_IF}}</if>
      <descr>GUEST</descr>
      <enable>1</enable>
      <ipaddr>{{GUEST_NET_FIRST_IP}}</ipaddr>
      <subnet>24</subnet>
    </opt2>
  </interfaces>

  <!-- =====================================================================
       ALIASES — use these in firewall rules for clarity
       ===================================================================== -->
  <aliases>
    <alias>
      <name>ADMIN_NET</name>
      <type>network</type>
      <address>{{ADMIN_SUBNET}}</address>
      <descr>Approved admin source networks</descr>
    </alias>
    <alias>
      <name>RFC1918</name>
      <type>network</type>
      <address>10.0.0.0/8 172.16.0.0/12 192.168.0.0/16</address>
      <descr>All RFC1918 space</descr>
    </alias>
    <alias>
      <name>SIEM</name>
      <type>host</type>
      <address>{{SYSLOG_SERVER}}</address>
      <descr>Central syslog / SIEM</descr>
    </alias>
    <alias>
      <name>DNS_UPSTREAM</name>
      <type>host</type>
      <address>9.9.9.9 1.1.1.1 149.112.112.112</address>
      <descr>Approved upstream resolvers</descr>
    </alias>
    <alias>
      <name>BOGON_EGRESS_BLOCK</name>
      <type>network</type>
      <address>0.0.0.0/8 127.0.0.0/8 169.254.0.0/16 224.0.0.0/4 240.0.0.0/4</address>
      <descr>Never allow egress to these</descr>
    </alias>
    <alias>
      <name>MGMT_TCP</name>
      <type>port</type>
      <address>22 8443 161</address>
      <descr>Admin ports - never expose to WAN</descr>
    </alias>
  </aliases>

  <!-- =====================================================================
       FIREWALL RULES
       Order matters - first match wins.
       ===================================================================== -->
  <filter>

    <!-- ================= WAN INGRESS (default-deny inherited) ============ -->
    <rule>
      <type>block</type>
      <interface>wan</interface>
      <protocol>any</protocol>
      <source><network>BOGON_EGRESS_BLOCK</network></source>
      <destination><any/></destination>
      <descr>Drop bogons at WAN ingress</descr>
      <log>1</log>
    </rule>
    <rule>
      <type>block</type>
      <interface>wan</interface>
      <protocol>any</protocol>
      <source><network>RFC1918</network></source>
      <destination><any/></destination>
      <descr>Drop RFC1918 arriving at WAN</descr>
      <log>1</log>
    </rule>

    <!-- ================= LAN RULES ======================================= -->
    <rule>
      <type>block</type>
      <interface>lan</interface>
      <protocol>tcp</protocol>
      <source><any/></source>
      <destination>
        <network>lan</network>
        <port>MGMT_TCP</port>
      </destination>
      <descr>Block LAN clients hitting firewall admin unless in ADMIN_NET</descr>
      <log>1</log>
    </rule>
    <rule>
      <type>pass</type>
      <interface>lan</interface>
      <protocol>any</protocol>
      <source><network>ADMIN_NET</network></source>
      <destination>
        <network>lan</network>
        <port>MGMT_TCP</port>
      </destination>
      <descr>Admin sources may reach firewall admin</descr>
    </rule>
    <rule>
      <type>pass</type>
      <interface>lan</interface>
      <protocol>udp</protocol>
      <source><network>lan</network></source>
      <destination>
        <address>DNS_UPSTREAM</address>
        <port>53</port>
      </destination>
      <descr>LAN to approved DNS only</descr>
    </rule>
    <rule>
      <type>block</type>
      <interface>lan</interface>
      <protocol>udp</protocol>
      <source><network>lan</network></source>
      <destination>
        <any/>
        <port>53</port>
      </destination>
      <descr>Block DNS to any other server (prevent bypass)</descr>
      <log>1</log>
    </rule>
    <rule>
      <type>pass</type>
      <interface>lan</interface>
      <protocol>tcp</protocol>
      <source><network>lan</network></source>
      <destination>
        <any/>
        <port>80 443</port>
      </destination>
      <descr>Standard web egress</descr>
    </rule>
    <rule>
      <type>block</type>
      <interface>lan</interface>
      <protocol>any</protocol>
      <source><any/></source>
      <destination><network>opt2</network></destination>
      <descr>LAN cannot reach GUEST</descr>
      <log>1</log>
    </rule>
    <rule>
      <type>block</type>
      <interface>lan</interface>
      <protocol>any</protocol>
      <source><any/></source>
      <destination><network>opt1</network></destination>
      <descr>LAN cannot initiate to DMZ (DMZ is published via NAT)</descr>
      <log>1</log>
    </rule>

    <!-- ================= GUEST RULES ===================================== -->
    <rule>
      <type>pass</type>
      <interface>opt2</interface>
      <protocol>udp</protocol>
      <source><network>opt2</network></source>
      <destination>
        <address>DNS_UPSTREAM</address>
        <port>53</port>
      </destination>
      <descr>Guest DNS to upstream resolvers</descr>
    </rule>
    <rule>
      <type>pass</type>
      <interface>opt2</interface>
      <protocol>tcp</protocol>
      <source><network>opt2</network></source>
      <destination>
        <any/>
        <port>80 443</port>
      </destination>
      <descr>Guest web egress</descr>
    </rule>
    <rule>
      <type>block</type>
      <interface>opt2</interface>
      <protocol>any</protocol>
      <source><any/></source>
      <destination><network>RFC1918</network></destination>
      <descr>Guest cannot reach any RFC1918 except own subnet</descr>
      <log>1</log>
    </rule>

    <!-- ================= DMZ RULES ======================================= -->
    <rule>
      <type>pass</type>
      <interface>opt1</interface>
      <protocol>udp</protocol>
      <source><network>opt1</network></source>
      <destination>
        <address>DNS_UPSTREAM</address>
        <port>53</port>
      </destination>
      <descr>DMZ DNS</descr>
    </rule>
    <rule>
      <type>block</type>
      <interface>opt1</interface>
      <protocol>any</protocol>
      <source><network>opt1</network></source>
      <destination><network>lan</network></destination>
      <descr>DMZ cannot reach LAN</descr>
      <log>1</log>
    </rule>
  </filter>

  <!-- =====================================================================
       NAT
       ===================================================================== -->
  <nat>
    <outbound>
      <mode>hybrid</mode>
    </outbound>
    <rule>
      <source><network>lan</network></source>
      <destination><any/></destination>
      <interface>wan</interface>
      <descr>Outbound NAT LAN</descr>
    </rule>
    <rule>
      <source><network>opt1</network></source>
      <destination><any/></destination>
      <interface>wan</interface>
      <descr>Outbound NAT DMZ</descr>
    </rule>
    <rule>
      <source><network>opt2</network></source>
      <destination><any/></destination>
      <interface>wan</interface>
      <descr>Outbound NAT Guest</descr>
    </rule>
  </nat>

  <!-- =====================================================================
       DHCP - narrow scope, reservations preferred
       ===================================================================== -->
  <dhcpd>
    <lan>
      <enable>1</enable>
      <range>
        <from>{{LAN_NET_DHCP_FROM}}</from>
        <to>{{LAN_NET_DHCP_TO}}</to>
      </range>
      <defaultleasetime>3600</defaultleasetime>
      <maxleasetime>7200</maxleasetime>
      <dnsserver>{{LAN_NET_FIRST_IP}}</dnsserver>
      <gateway>{{LAN_NET_FIRST_IP}}</gateway>
      <denyunknown>0</denyunknown>
    </lan>
    <opt2>
      <enable>1</enable>
      <range>
        <from>{{GUEST_NET_DHCP_FROM}}</from>
        <to>{{GUEST_NET_DHCP_TO}}</to>
      </range>
      <defaultleasetime>3600</defaultleasetime>
      <maxleasetime>7200</maxleasetime>
      <dnsserver>1.1.1.1</dnsserver>
      <dnsserver>9.9.9.9</dnsserver>
      <gateway>{{GUEST_NET_FIRST_IP}}</gateway>
    </opt2>
  </dhcpd>

  <!-- =====================================================================
       DNS RESOLVER (Unbound)
       ===================================================================== -->
  <unbound>
    <enable/>
    <port>53</port>
    <active_interface>lan,opt1,opt2</active_interface>
    <outgoing_interface>wan</outgoing_interface>
    <system_domain_local_zone_type>transparent</system_domain_local_zone_type>
    <forwarding>1</forwarding>
    <regdhcp>1</regdhcp>
    <regdhcpstatic>1</regdhcpstatic>
    <enablessl>1</enablessl>
    <dnssec/>
    <custom_options>
      server:
        harden-below-nxdomain: yes
        harden-referral-path: yes
        harden-glue: yes
        harden-dnssec-stripped: yes
        hide-identity: yes
        hide-version: yes
        prefetch: yes
        prefetch-key: yes
        qname-minimisation: yes
        use-caps-for-id: yes
        aggressive-nsec: yes
        deny-any: yes
    </custom_options>
  </unbound>

  <!-- =====================================================================
       SYSLOG TO CENTRAL SIEM
       ===================================================================== -->
  <syslog>
    <nentries>500</nentries>
    <remoteserver>{{SYSLOG_SERVER}}:514</remoteserver>
    <sourceip/>
    <ipproto>ipv4</ipproto>
    <filter>1</filter>
    <dhcp>1</dhcp>
    <auth>1</auth>
    <system>1</system>
    <logall>1</logall>
    <logfilesize>512000</logfilesize>
    <rotatecount>7</rotatecount>
    <enable>1</enable>
  </syslog>

  <!-- =====================================================================
       SNMP v3 ONLY
       ===================================================================== -->
  <snmpd>
    <syslocation>{{SNMP_LOCATION}}</syslocation>
    <syscontact>netops@{{DOMAIN}}</syscontact>
    <rocommunity>REPLACE_WITH_V3</rocommunity>
    <enable>0</enable>
    <comment>SNMP v1/v2c disabled - use v3 via package</comment>
  </snmpd>

  <!-- =====================================================================
       IPSEC BASELINE - remote access + site-to-site skeleton
       ===================================================================== -->
  <ipsec>
    <enable>1</enable>
    <preferredoldsa/>
    <async_crypto>1</async_crypto>
    <!-- Add tunnels per site; template left intentionally blank -->
  </ipsec>

  <!-- =====================================================================
       PACKAGES TO INSTALL AFTER RESTORE
       ===================================================================== -->
  <!--
       Required:
         - pfBlockerNG / OPNsense geoip + DNSBL + threat feeds
         - Suricata or Snort (IDS/IPS on WAN + DMZ)
         - openvpn-client-export (if OpenVPN) or WireGuard (preferred)
         - acme (Let's Encrypt for webGUI cert rotation)
         - sudo (limit shell access to named admins)
  -->

</pfsense>
