Notes on setting up my mac to work with Cloudflare

I use my work desktop to serve a website and enable collaborators to access data files for upcoming publications. My work is using Cloudflare to shield web-traffic, so we have to set up the server to be compliant. These are my step-by-step notes to get this to work, which stem from

and interactions with IT.

NOTE: I already had a working web-server with SSL certificates obtained from letsencrypt (certbot) and httpd-vhosts.conf and httpd-ssl.conf files configured to point to the .pem files. The main feature of these notes are the creation of scripts to correctly configure the restricted IP ranges with the Mac Firewall in the /etc/pf.conf file, restart the firewall with the restrictions added, and make sure this happens upon reboot as well as an OS upgrade (assuming the upgrade does not remove the launchctl load file.plist we create).

  • Requirement 1: Distinct server DNS record DNS record:

    • If you do not yet have a DNS record, create a request at iprequest.lbl.gov. If the name of your server to the outside world is <my_computer>.lbl.gov, IT recommends picking <my_computer>-local.lbl.gov for the DNS record
    • If you already have <my_computer>.lbl.gov and are changing to use cloudflare, instead of requesting a new record, you can request to RENAME the existing record, and simultaneously submit the CNAME - Requirement 2
  • Requirement 2: Distinct web site DNS record

    • Again from iprequest.lbl.gov, request a CNAME for the site, which will be your old site name if you already have one, <my_computer>.lbl.gov with a CNAME to <my_computer>-local.lbl.gov
    • Also request a change of TTL from 43200 to 600 as instructed
  • Requirement 3: Serving content

    • Your site must be functional (ie, you are already running Apache/PHP and have an SSL certificate from letsencrypt (certbot))
  • Requirement 4: SSL configured on 443/tcp

  • At this point, complete the Cloudflare signup form from https://commons.lbl.gov/display/cpp/Open+a+Web+Server+to+the+Internet

  • After this, we need to enable the firewall and add restrict access to Cloudflare IP ranges

    • To turn on the firewall, on OS Ventura, I do System Settings > Network > Firewall and enable it. I have some allowances.

The IP range restriction can be learned from https://www.cloudflare.com/ips/, linked from LBL Cloudflare IP ranges. We need to edit the /etc/pf.conf file. But importantly, we have to make sure this file is correctly edited upon reboot, and also upon a mac OS re-install or update. The latter especially can delete the pf alterations we have made. Therefore, I have created a script which will correctly add the required restrictions to the /etc/pf.conf file, and also used launchctl to launch the script upon reboot.

NOTE: with the script, I querry cloudflare to get the latest IP address that are OK, rather than rely on a static file which could become outdated.

  • First, the script that will modify the /etc/pf.conf file is given below, make_pf.conf.sh. I have placed this script in $HOME/bin and made it executable,

    chmod +x $HOME/bin/make_pf.conf.sh

  • for this script to work, I had to brew install lynx
  • Second, I created a launch script in /Library/LaunchDaemons/<MEMORABLE_NAME>.lbl_iplist.plist which can be found below.
  • NOTE, names in < > are names you should replace, such as <MEMORABLE_NAME>
  • Now, have this script be launched at load

    sudo launchctl load /Library/LaunchDaemons/<MEMORABLE_NAME>.lbl_iplist.plist

  • Reboot your computer, and if all has gone well, your /etc/pf.conf file will look like the new /etc/pf.conf - everything beginning with # rules to host at LBL is added by the script. I got the instructions for creating the launchctl script from this stackexchange. I created a /Library/LaunchDaemons file as I want the system to do this on reboot with sys privileges.
  • Additional reading on launchctl I found helpful
  • Further, from off site, the following terminal commands should all return nothing, or errors
    $nc -zv <YOUR_STATIC_IP> 80
    nc: connectx to <YOUR_STATIC_IP> port 80 (tcp) failed: Operation timed out
    $ curl -k https://<YOUR_STATIC_IP>
    curl: (28) Failed to connect to <YOUR_STATIC_IP> port 443 after 75452 ms: Couldn't connect to server
    $curl -s --head https://<YOUR_STATIC_IP>

    The last one should return nothing.

new /etc/pf.conf file

<USER>@<COMPUTER_NAME>:$ cat /etc/pf.conf

#
# Default PF configuration file.
#
# This file contains the main ruleset, which gets automatically loaded
# at startup.  PF will not be automatically enabled, however.  Instead,
# each component which utilizes PF is responsible for enabling and disabling
# PF via -E and -X as documented in pfctl(8).  That will ensure that PF
# is disabled only when the last enable reference is released.
#
# Care must be taken to ensure that the main ruleset does not get flushed,
# as the nested anchors rely on the anchor point defined here. In addition,
# to the anchors loaded by this file, some system services would dynamically
# insert anchors into the main ruleset. These anchors will be added only when
# the system service is used and would removed on termination of the service.
#
# See pf.conf(5) for syntax.
#

#
# com.apple anchor point
#
scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"

# rules to host at LBL
#
# Restrict all HTTP (port 80) access
block return in proto tcp from any to any port 80

# Restrict HTTPS (port 443) access
block return in proto tcp from any to any port 443
# IPv4
pass inet proto tcp from 173.245.48.0/20 to any port 443 no state
pass inet proto tcp from 103.21.244.0/22 to any port 443 no state
pass inet proto tcp from 103.22.200.0/22 to any port 443 no state
pass inet proto tcp from 103.31.4.0/22 to any port 443 no state
pass inet proto tcp from 141.101.64.0/18 to any port 443 no state
pass inet proto tcp from 108.162.192.0/18 to any port 443 no state
pass inet proto tcp from 190.93.240.0/20 to any port 443 no state
pass inet proto tcp from 188.114.96.0/20 to any port 443 no state
pass inet proto tcp from 197.234.240.0/22 to any port 443 no state
pass inet proto tcp from 198.41.128.0/17 to any port 443 no state
pass inet proto tcp from 162.158.0.0/15 to any port 443 no state
pass inet proto tcp from 104.16.0.0/13 to any port 443 no state
pass inet proto tcp from 104.24.0.0/14 to any port 443 no state
pass inet proto tcp from 172.64.0.0/13 to any port 443 no state
pass inet proto tcp from 131.0.72.0/22 to any port 443 no state

# IPv6
pass inet6 proto tcp from 2400:cb00::/32 to any port 443 no state
pass inet6 proto tcp from 2606:4700::/32 to any port 443 no state
pass inet6 proto tcp from 2803:f800::/32 to any port 443 no state
pass inet6 proto tcp from 2405:b500::/32 to any port 443 no state
pass inet6 proto tcp from 2405:8100::/32 to any port 443 no state
pass inet6 proto tcp from 2a06:98c0::/29 to any port 443 no state
pass inet6 proto tcp from 2c0f:f248::/32 to any port 443 no state
make_pf.conf.sh
#!/bin/bash

# NOTE: we make an assumption about the structure of the pf.conf file, 
#       which should be verified with future mac OS versions (post ventura)

# For this launch script to work, you must first
# brew install lynx

FILE=/etc/pf.conf
BK=${FILE}.bak

if [[ `tail -1 $FILE | grep com.apple | wc -l` -ne 1 ]]; then
    # we assume the final line is: load anchor "com.apple" from "/etc/pf.anchors/com.apple"
    # if FILE does not end this way, it is modified, restore from BK
    if [[ -e $BK ]]; then
        cp $BK $FILE
    else
        echo "does not exist: $BK"
        echo "$FILE appears modified"
        echo "we need manual intervention"
        exit
    fi
else
    # if FILE looks original and BK does not exist - maybe deleted from an OS update
    # then remake the BK
    if [[ ! -e $BK ]]; then
        cp $FILE $BK
    fi
fi

# Now, we remake the file with restrictions
# make sure we can talk to the internet
while ! ping -c1 -W1 1.1.1.1 &> /dev/null ; do
    sleep 1
done

# add IP-v4 restrictions
cat >> ${FILE} <<- EOM

# rules to host at LBL
#
# Restrict all HTTP (port 80) access
block return in proto tcp from any to any port 80

# Restrict HTTPS (port 443) access
block return in proto tcp from any to any port 443
# IPv4
EOM

for ip in $(/usr/local/bin/lynx -dump -nolist https://www.cloudflare.com/ips-v4); do
  echo "pass inet proto tcp from $ip to any port 443 no state" >> ${FILE}
done

# add IP-v6 restrictions
cat >> ${FILE} <<- EOM

# IPv6
EOM

for ip in $(/usr/local/bin/lynx -dump -nolist https://www.cloudflare.com/ips-v6); do
    echo "pass inet6 proto tcp from $ip to any port 443 no state" >> ${FILE}
done

# load the new rules
pfctl -f $FILE

# restart the firewall
pfctl -E
launch script
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>Label</key>
   <string><MEMORABLE_NAME>.lbl_iplist</string>
   <key>ProgramArguments</key>
   <array><string>/Users/<ADMIN_USER_NAME>/bin/make_pf.conf.sh</string></array>
   <key>RunAtLoad</key>
   <true/>
   <key>StandardErrorPath</key>
   <string>/var/log/<MEMORABLE_NAME>.lbl_iplist.err</string>
   <key>StandardOutPath</key>
   <string>/var/log/<MEMORABLE_NAME>.lbl_iplist.out</string>
</dict>
</plist>