Compare commits

..

No commits in common. "3c7f61fb37b0e979494277934cbb35129a9331ed" and "df2a598c9e61d44b87826e538410d61f2d51064a" have entirely different histories.

10 changed files with 40 additions and 128 deletions

View File

@ -1,13 +0,0 @@
version = 1
[[analyzers]]
name = "python"
[analyzers.meta]
runtime_version = "3.x.x"
[[transformers]]
name = "black"
[[transformers]]
name = "isort"

View File

@ -1,2 +1,2 @@
CLOUDFLARE_ACCOUNT_ID= export CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_TOKEN= export CLOUDFLARE_TOKEN=

1
.gitignore vendored
View File

@ -8,4 +8,3 @@ tmp.py
.venv .venv
dist/ dist/
.ruff_cache/ .ruff_cache/
hosts.txt

10
.vscode/settings.json vendored
View File

@ -1,10 +1,4 @@
{ {
"python.languageServer": "Pylance", // Ruff is used for linting but Pylance still is useful for intellisense "python.languageServer": "Pylance",
"python.analysis.ignore": [ "python.analysis.ignore": [ "*" ] // Ruff is used for linting but Pylance still is useful
"*"
],
"python.analysis.exclude": [
"."
],
"python.linting.enabled": false // https://github.com/microsoft/vscode-python/wiki/Migration-to-Python-Tools-Extensions
} }

View File

@ -1,46 +1,2 @@
# Cloudflare Gateway Adblocking # Cloudflare Gateway Adblocking
Serverless adblocking via Cloudflare Zero Trust Gateway Serverless adblocking via Cloudflare Zero Trust Gateway
### What is this?
This is a serverless adblocking solution that uses Cloudflare's Zero Trust Gateway to block ads by parsing a hosts file and creating a firewall rule to block the domains. It can be used as an alternative to Pi-Hole or other adblocking solutions.
This project was heavily inspired by [this blog post](https://blog.marcolancini.it/2022/blog-serverless-ad-blocking-with-cloudflare-gateway/)
### Pre-requisites
* Python > 3.10
* A Cloudflare account with Zero Trust enabled
* A Cloudflare API tolken with the following permissions:
* Zero Trust: Edit
* Account Firewall Access Rules: Edit
* Access: Apps and Policies: Edit
* A device with the WARP client installed and configured to use a Zero Trust account
<!--
### Installation
#### From PyPi
`pip install cloudflare-gateway-adblocking`
-->
### Usage
#### Setting Cloudflare credentials
##### Environment variables
The following environment variables can be used to set the Cloudflare credentials:
* `CLOUDFLARE_ACCOUNT_ID`
* `CLOUDFLARE_TOKEN`
These can either be set in the environment or in a `.env` file in the current working directory.
#### Command line flags
The following command line flags can be used to set the Cloudflare credentials:
* Cloudflare Account ID: `--account-id` / `-a`
* Cloudflare Token: `--token` / `-t`
#### Passing blocklists
Blocklists can be passed to the program via the command line flag `--blocklist` / `-b`. This flag can either point to a hosts file or a directory containing hosts files. If this flag is not passed, the program will look for a file or directory named `blocklists` in the current working directory.
# Passing whitelists
Whitelists can be passed to the program via the command line flag `--whitelist` / `-w`. This flag can either point to a hosts file or a directory containing hosts files. If this flag is not passed, then if a file or directory named `whitelists` exists in the current working directory, it will be used. Domains in this whitelist will be excluded from the blocklists.
#### Uploading blocklists and creating a firewall policy
To upload the blocklists to Cloudflare and create a firewall policy, use the `upload` subcommand.
For example:
`cloudflare-gateway-adblocking upload`
#### Deleting blocklists and firewall policy
To delete the blocklists from Cloudflare and delete the firewall policy, use the `delete` subcommand.
For example:
`cloudflare-gateway-adblocking delete`

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "cloudflare-gateway-adblocking" name = "cloudflare-gateway-adblocking"
version = "0.1.0" version = "0.1.9"
description = "Serverless adblocking via Cloudflare Zero Trust Gateway" description = "Serverless adblocking via Cloudflare Zero Trust Gateway"
authors = ["slastechno <77907286+slashtechno@users.noreply.github.com>"] authors = ["slastechno <77907286+slashtechno@users.noreply.github.com>"]
license = "MIT" license = "MIT"

View File

@ -1,15 +1,13 @@
# To run from the root project directory, run the following command: # To run from the root project directory, run the following command:
# python -m src.__main__ # python -m src.__main__
# python -m src # also works because __main__ is the default module from loguru import logger
from .utils import upload, delete, utils
import argparse import argparse
import os import os
from pathlib import Path
from sys import exit, stderr
import dotenv import dotenv
from loguru import logger from sys import exit, stderr
from pathlib import Path
from .utils import delete, upload, utils
TOKEN = None TOKEN = None
ACCOUNT_ID = None ACCOUNT_ID = None
@ -72,13 +70,13 @@ def main():
"--blocklists", "--blocklists",
"-b", "-b",
help="Either a blocklist hosts file or a directory containing blocklist hosts files", # noqa E501 help="Either a blocklist hosts file or a directory containing blocklist hosts files", # noqa E501
default="blocklists", # Not really needed because the get_blocklists function will default to this # noqa: E501 default="blocklists",
) )
upload_parser.add_argument( upload_parser.add_argument(
"--whitelists", "--whitelists",
"-w", "-w",
help="Either a whitelist hosts file or a directory containing whitelist hosts files", # noqa E501 help="Either a whitelist hosts file or a directory containing whitelist hosts files", # noqa E501
default="whitelist.txt", # Not really needed because the apply_whitelists function will default to this # noqa: E501 default="whitelist.txt", # Need to change this so it's optional
) )
# Add subcommand: delete # Add subcommand: delete
delete_parser = subparsers.add_parser( delete_parser = subparsers.add_parser(
@ -104,9 +102,8 @@ def main():
try: try:
args.func(args) args.func(args)
except AttributeError as e: except AttributeError as e:
logger.error("No subcommand specified") logger.debug(e)
argparser.print_help() argparser.print_help()
exit(1)
def upload_to_cloudflare(args): def upload_to_cloudflare(args):

View File

@ -1,7 +1,6 @@
# This is a scriprt to undo the changes made by adblock-zerotrust.py # This is a scriprt to undo the changes made by adblock-zerotrust.py
import requests import requests
from . import utils from . import utils
@ -44,14 +43,11 @@ def delete_adblock_policy(policies: dict, account_id: str, token: str):
def main(): def main():
account_id = input("Enter your Cloudflare account ID: ") rules = utils.get_gateway_rules()
token = input("Enter your Cloudflare API token: ") delete_adblock_policy(rules)
lists = utils.get_lists()
rules = utils.get_gateway_rules(account_id, token)
delete_adblock_policy(rules, account_id, token)
lists = utils.get_lists(account_id, token)
lists = utils.filter_adblock_lists(lists) lists = utils.filter_adblock_lists(lists)
delete_adblock_list(lists, account_id, token) delete_adblock_list(lists)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,11 +1,9 @@
import requests
from . import utils
import pathlib import pathlib
import requests
from . import utils def get_blocklists(hosts_path: str = None):
def get_blocklists(hosts_path: str = "blocklists"):
blocklists = [] blocklists = []
hosts_path = pathlib.Path(hosts_path) hosts_path = pathlib.Path(hosts_path)
if hosts_path.is_file(): if hosts_path.is_file():
@ -18,7 +16,7 @@ def get_blocklists(hosts_path: str = "blocklists"):
return blocklists return blocklists
def apply_whitelists(blocklists, whitelist: str = "whitelists"): def apply_whitelists(blocklists, whitelist: str = None):
# If whitelist is a file, convert it to a list. # If whitelist is a file, convert it to a list.
# If whitelist is a directory, convert all files in it to a list and combine them. # If whitelist is a directory, convert all files in it to a list and combine them.
# If it does not exist, return the original blocklists # If it does not exist, return the original blocklists
@ -102,16 +100,13 @@ def create_dns_policy(lists, account_id: str, token: str) -> None:
def main(): def main():
account_id = input("Enter your Cloudflare account ID: ")
token = input("Enter your Cloudflare API token: ")
blocklists = get_blocklists() blocklists = get_blocklists()
blocklists = apply_whitelists(blocklists) blocklists = apply_whitelists(blocklists)
lists = split_list(blocklists) lists = split_list(blocklists)
upload_to_cloudflare(lists, account_id, token) upload_to_cloudflare(lists)
cloud_lists = utils.get_lists(account_id, token) cloud_lists = utils.get_lists()
cloud_lists = utils.filter_adblock_lists(cloud_lists) cloud_lists = utils.filter_adblock_lists(cloud_lists)
create_dns_policy(cloud_lists, account_id, token) create_dns_policy(cloud_lists)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,5 +1,4 @@
import pathlib import pathlib
import re
import requests import requests
@ -54,34 +53,23 @@ class Config:
# Convert a hosts file to a simple hostname list # Convert a hosts file to a simple hostname list
def convert_to_list(file: pathlib.Path) -> list: def convert_to_list(file: pathlib.Path) -> list:
with open(file, "r") as f: with open(file, "r") as f:
# Loop through the file and using regex, only get the domain names # Don't read commented lines; strip whitespace;
# Remove the prefixed loopback domain and suffixed comments # remove 127.0.0.1 from beginning of line;
# Remove any empty strings # ignore lines with "localhost"; ignore lines with "::1";
loopback = [ # ignore newlines
"localhost",
"::1",
"localhost.localdomain",
"broadcasthost",
"local",
"ip6-localhost",
"ip6-loopback",
"ip6-localnet",
"ip6-mcastprefix",
"ip6-allnodes",
"ip6-allrouters",
"ip6-allhosts",
"0.0.0.0",
]
matches = [
re.search(r"^(?:127\.0\.0\.1|0\.0\.0\.0|::1)\s+(.+?)(?:\s+#.+)?$", line)
for line in f
]
hosts = [ hosts = [
match.group(1) i[10:].strip()
for match in matches for i in f.readlines()
if match and match.group(1) not in loopback if not i.startswith("#") and "localhost" not in i and "::1" not in i
] ]
print(f"First 5 hosts: {hosts[:5]}") # Equivalent to:
# for x in f.readlines():
# if not x.startswith('#') and 'localhost' not in x and '::1' not in x:
# hosts.append(x[10:].strip())
# If there are any empty strings in the list, remove them
# For some reason, whitelist seems to still be present
hosts = [i for i in hosts if i != ""]
return hosts return hosts