Block tor traffic in cloudflare firewall

![tor logo] ({filename}/static/images/tor.png)

If you don’t want to allow access to your server through the tor network you can ask nicely or just add every malicious looking client to a list. I tried to come up with a better solution.

I started with a script that blocked incomming connections on a loadbalancer (can also be used on a webserver) Then I realized that if you use cloudclare in front of that the tcp connections come from cloudare and not from the tor endpoints. So I needed to rebuild it to add the rules to the cloudflrae firewall.

I started of the same way, create a variable which contains all the tor ip adresses. Then It get’s a bit more complex, working with a json api and bash is often not without struggle. The code is commented, if that is not enough contact me and I will try to make i more readable. Use it, cron it, love it ;-)


echo "Tor endpoint list loading"
TORLIST=$(curl -s |grep ExitAddress | awk '{print $2 }' | sort | uniq)

ZONE=""   # can be left empty this script will pick the first one from your account for you
KEY=""    # API key can be found at:
EMAIL=""  # email adress used to login to cloudflare

# If the ZONE is not defined get the first one from the api
if [ -z "$ZONE" ]; then
  ZONE=$( curl -s -X GET "" \
  -H "Content-Type:application/json" \
  -H "X-Auth-Key:${KEY}" \
  -H "X-Auth-Email:${EMAIL}" | python -m json.tool |grep -A 1 development_mode | grep id | awk '{print $2}' | sed 's/"//g' |sed 's/,//g')
  echo "Add zone string to config for faster result and less api calls"
  echo ${ZONE}

echo "Retrieving active firewall rules"
# The api is paged so we first qeury the api and check how many apges we have
PAGES=$(curl -s -X GET "${ZONE}/firewall/access_rules/rules?mode=block&configuration_target=ip&page=1&per_page=1000&order=scope_type&direction=desc&match=all " \
  -H "Content-Type:application/json" \
  -H "X-Auth-Key:${KEY}" \
  -H "X-Auth-Email:${EMAIL}" | python -m json.tool |  grep  total_pages | awk '{print $2}')

# for every page gat the response rinse out the bullshit keep the ip adresses
# Because we can have multiple pages I cocatinate the srings into IP_LIST
for ((page=1; $page <= ${PAGES}; page++)); do
  DATA=$(curl -s -X GET "${ZONE}/firewall/access_rules/rules?mode=block&configuration_target=ip&page=${page}&per_page=1000&order=scope_type&direction=desc&match=all " \
  -H "Content-Type:application/json" \
  -H "X-Auth-Key:${KEY}" \
  -H "X-Auth-Email:${EMAIL}" | python -m json.tool |  grep value | awk '{print $2}')


# Now we have a list with all the tor ip exit nodes, and all the ip adresses in the firewall.
# I creatd the next loop to take all the tor endpoints and check if they are already in the firewall.
# If the tor ip is not found in the firewall list it will be added to the firewall
# There is a sleep in the loop to keep the api from blocking your reqeusts (max 1200 per 5 min.)

for ip in ${TORLIST}; do
  sleep 0.3

  IN_FIREWALL=$(echo ${IP_LIST} | grep ${ip})

  if [ -z "$IN_FIREWALL" ]; then
    echo "adding ${ip} to blocklist"
    curl -s -X POST "${ZONE}/firewall/access_rules/rules" \
    -H "Content-Type:application/json" \
    -H "X-Auth-Key:${KEY}" \
    -H "X-Auth-Email:${EMAIL}" \
    --data '{"mode":"block","configuration":{"target":"ip","value":"'"${ip}"'"},"notes":"This site is not available through tor endpoints"}' > /dev/null
# else
#   echo "already in blocklist"

Stein van Broekhoven
Cloud & Open-Source magician 🧙‍♂️

I try to find the KISS in complex systems and share it with the world.

comments powered by Disqus