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:
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<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 2Requirement 2: Distinct web site DNS record
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
Requirement 3: Serving content
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
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
brew install lynx
/Library/LaunchDaemons/<MEMORABLE_NAME>.lbl_iplist.plist
which can be found below.< >
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
/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.launchctl
I found helpful
$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.
/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
#!/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
<?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>