Compare commits

...

10 Commits

Author SHA1 Message Date
/techno 3c7f61fb37
Delete utils.py 2023-08-06 18:37:17 -04:00
slashtechno b9013e051d
Merge branch 'master' into restructure-project 2023-08-06 18:36:34 -04:00
deepsource-autofix[bot] 79ca52987b
style: Format code with black and isort 2023-08-06 22:26:16 +00:00
slashtechno 3a18a4536c
Updated `main()` for `delete.py` and `upload.py`
Also added information on whitelists to `README.md`
2023-08-06 18:25:55 -04:00
/techno 263a970edb
Merge pull request #2 from slashtechno/deepsource-transform-200a61ec
format code with black and isort
2023-08-06 17:55:45 -04:00
deepsource-autofix[bot] 7cad46cf8a
style: Format code with black and isort 2023-08-06 21:55:19 +00:00
/techno 0ec3e62c03
Updated README.md 2023-08-06 17:50:44 -04:00
/techno bc94e67635
Use regex for extracting hosts 2023-08-06 17:50:33 -04:00
deepsource-autofix[bot] 4bb9c47e6c
style: format code with black and isort
Format code with black and isort

This commit fixes the style issues introduced in cdbd7dd according to the output
from Black and isort.

Details: https://app.deepsource.com/gh/slashtechno/cloudflare-gateway-adblocking/transform/f9f81019-cc0f-4300-8f90-5884a2afa39a/
2023-08-06 18:46:14 +00:00
deepsource-io[bot] cdbd7dd369
ci: Add .deepsource.toml 2023-08-06 18:46:02 +00:00
10 changed files with 129 additions and 41 deletions

13
.deepsource.toml Normal file
View File

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

View File

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

1
.gitignore vendored
View File

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

10
.vscode/settings.json vendored
View File

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

View File

@ -1,2 +1,46 @@
# 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.9" version = "0.1.0"
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,13 +1,15 @@
# 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__
from loguru import logger # python -m src # also works because __main__ is the default module
from .utils import upload, delete, utils
import argparse import argparse
import os import os
import dotenv
from sys import exit, stderr
from pathlib import Path from pathlib import Path
from sys import exit, stderr
import dotenv
from loguru import logger
from .utils import delete, upload, utils
TOKEN = None TOKEN = None
ACCOUNT_ID = None ACCOUNT_ID = None
@ -70,13 +72,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", default="blocklists", # Not really needed because the get_blocklists function will default to this # noqa: E501
) )
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", # Need to change this so it's optional default="whitelist.txt", # Not really needed because the apply_whitelists function will default to this # noqa: E501
) )
# Add subcommand: delete # Add subcommand: delete
delete_parser = subparsers.add_parser( delete_parser = subparsers.add_parser(
@ -102,8 +104,9 @@ def main():
try: try:
args.func(args) args.func(args)
except AttributeError as e: except AttributeError as e:
logger.debug(e) logger.error("No subcommand specified")
argparser.print_help() argparser.print_help()
exit(1)
def upload_to_cloudflare(args): def upload_to_cloudflare(args):

View File

@ -1,6 +1,7 @@
# 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
@ -43,11 +44,14 @@ def delete_adblock_policy(policies: dict, account_id: str, token: str):
def main(): def main():
rules = utils.get_gateway_rules() account_id = input("Enter your Cloudflare account ID: ")
delete_adblock_policy(rules) token = input("Enter your Cloudflare API token: ")
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) delete_adblock_list(lists, account_id, token)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,9 +1,11 @@
import requests
from . import utils
import pathlib import pathlib
import requests
def get_blocklists(hosts_path: str = None): from . import utils
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():
@ -16,7 +18,7 @@ def get_blocklists(hosts_path: str = None):
return blocklists return blocklists
def apply_whitelists(blocklists, whitelist: str = None): def apply_whitelists(blocklists, whitelist: str = "whitelists"):
# 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
@ -100,13 +102,16 @@ 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) upload_to_cloudflare(lists, account_id, token)
cloud_lists = utils.get_lists() cloud_lists = utils.get_lists(account_id, token)
cloud_lists = utils.filter_adblock_lists(cloud_lists) cloud_lists = utils.filter_adblock_lists(cloud_lists)
create_dns_policy(cloud_lists) create_dns_policy(cloud_lists, account_id, token)
if __name__ == "__main__": if __name__ == "__main__":

View File

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