Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

9 changed files with 142 additions and 323 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
github: [slashtechno]

View File

@ -1,39 +0,0 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Upload Python Package
on:
release:
types: [published]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

View File

@ -1,5 +1,4 @@
# Cloudflare Gateway Adblocking # Cloudflare Gateway Adblocking
![PyPI](https://img.shields.io/pypi/v/cloudflare-gateway-adblocking?style=for-the-badge&logo=python&link=https%3A%2F%2Fpypi.org%2Fproject%2Fcloudflare-gateway-adblocking%2F)
Serverless adblocking via Cloudflare Zero Trust Gateway Serverless adblocking via Cloudflare Zero Trust Gateway
### What is this? ### What is this?
@ -7,7 +6,7 @@ This is a serverless adblocking solution that uses Cloudflare's Zero Trust Gatew
This project was heavily inspired by [this blog post](https://blog.marcolancini.it/2022/blog-serverless-ad-blocking-with-cloudflare-gateway/) This project was heavily inspired by [this blog post](https://blog.marcolancini.it/2022/blog-serverless-ad-blocking-with-cloudflare-gateway/)
### Prerequisites ### Pre-requisites
* Python > 3.10 * Python > 3.10
* A Cloudflare account with Zero Trust enabled * A Cloudflare account with Zero Trust enabled
* A Cloudflare API tolken with the following permissions: * A Cloudflare API tolken with the following permissions:
@ -16,11 +15,11 @@ This project was heavily inspired by [this blog post](https://blog.marcolancini.
* Access: Apps and Policies: Edit * Access: Apps and Policies: Edit
* A device with the WARP client installed and configured to use a Zero Trust account * A device with the WARP client installed and configured to use a Zero Trust account
<!--
### Installation ### Installation
#### From PyPi #### From PyPi
`pip install cloudflare-gateway-adblocking` `pip install cloudflare-gateway-adblocking`
-->
### Usage ### Usage
#### Setting Cloudflare credentials #### Setting Cloudflare credentials
@ -35,7 +34,7 @@ The following command line flags can be used to set the Cloudflare credentials:
* Cloudflare Token: `--token` / `-t` * Cloudflare Token: `--token` / `-t`
#### Passing blocklists #### 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. 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 # 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. 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 #### Uploading blocklists and creating a firewall policy
To upload the blocklists to Cloudflare and create a firewall policy, use the `upload` subcommand. To upload the blocklists to Cloudflare and create a firewall policy, use the `upload` subcommand.
@ -45,9 +44,3 @@ For example:
To delete the blocklists from Cloudflare and delete the firewall policy, use the `delete` subcommand. To delete the blocklists from Cloudflare and delete the firewall policy, use the `delete` subcommand.
For example: For example:
`cloudflare-gateway-adblocking delete` `cloudflare-gateway-adblocking delete`
### Help
For help, use the `--help` flag.
### Contributing
* [Sponsoring](https://github.com/sponsors/slashtechno) via GitHub
* Contributing code via a pull request
* Reporting encoutered issues

177
poetry.lock generated
View File

@ -1,55 +1,34 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
[[package]]
name = "anyio"
version = "3.7.1"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.7"
files = [
{file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
{file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
]
[package.dependencies]
exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
[package.extras]
doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"]
test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (<0.22)"]
[[package]] [[package]]
name = "black" name = "black"
version = "24.4.2" version = "23.7.0"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"},
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"},
{file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"},
{file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"},
{file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"},
{file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"},
{file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"},
{file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"},
{file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"},
{file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"},
{file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"},
{file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"},
{file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"},
{file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"},
{file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"},
{file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"},
{file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"},
{file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"},
{file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"},
{file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"},
{file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"},
{file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"},
] ]
[package.dependencies] [package.dependencies]
@ -59,11 +38,10 @@ packaging = ">=22.0"
pathspec = ">=0.9.0" pathspec = ">=0.9.0"
platformdirs = ">=2" platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
colorama = ["colorama (>=0.4.3)"] colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"] uvloop = ["uvloop (>=0.15.2)"]
@ -187,84 +165,15 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
] ]
[[package]]
name = "exceptiongroup"
version = "1.1.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"},
{file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "0.17.3"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.7"
files = [
{file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"},
{file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"},
]
[package.dependencies]
anyio = ">=3.0,<5.0"
certifi = "*"
h11 = ">=0.13,<0.15"
sniffio = "==1.*"
[package.extras]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
[[package]]
name = "httpx"
version = "0.24.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.7"
files = [
{file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"},
{file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"},
]
[package.dependencies]
certifi = "*"
httpcore = ">=0.15.0,<0.18.0"
idna = "*"
sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.7" version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
] ]
[[package]] [[package]]
@ -394,17 +303,6 @@ files = [
{file = "ruff-0.0.281.tar.gz", hash = "sha256:bab2cdfa78754315cccc2b4d46ad6181aabb29e89747a3b135a4b85e11baa025"}, {file = "ruff-0.0.281.tar.gz", hash = "sha256:bab2cdfa78754315cccc2b4d46ad6181aabb29e89747a3b135a4b85e11baa025"},
] ]
[[package]]
name = "sniffio"
version = "1.3.0"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
files = [
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
]
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
@ -416,26 +314,15 @@ files = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
] ]
[[package]]
name = "typing-extensions"
version = "4.11.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.0.7" version = "2.0.4"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"},
{file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"},
] ]
[package.extras] [package.extras]
@ -461,4 +348,4 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "6930b7a4d843d920177257c386b9793f74619ee1ec2ab1e7d263935e89e629c8" content-hash = "d1e33ec5def7b19f6fd09ac80725a9a9f24da3274b075ee37eb53fe2b2f54219"

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "cloudflare-gateway-adblocking" name = "cloudflare-gateway-adblocking"
version = "0.1.4" 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"
@ -18,12 +18,11 @@ python = "^3.10"
requests = "^2.31.0" requests = "^2.31.0"
loguru = "^0.7.0" loguru = "^0.7.0"
python-dotenv = "^1.0.0" python-dotenv = "^1.0.0"
httpx = "^0.24.1"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
ruff = "^0.0.281" ruff = "^0.0.281"
black = ">=23.7,<25.0" black = "^23.7.0"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]

View File

@ -2,7 +2,6 @@
# python -m src.__main__ # python -m src.__main__
# python -m src # also works because __main__ is the default module # python -m src # also works because __main__ is the default module
import argparse import argparse
import asyncio
import os import os
from pathlib import Path from pathlib import Path
from sys import exit, stderr from sys import exit, stderr
@ -17,6 +16,20 @@ ACCOUNT_ID = None
def main(): def main():
# Setup logging
logger.remove()
# ^10 is a formatting directive to center with a padding of 10
logger_format = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> |<level>{level: ^10}</level>| <level>{message}</level>" # noqa E501
logger.add(stderr, format=logger_format, colorize=True, level="DEBUG")
# Load .env if it exists
# This must precede the argparse setup as env variables are used as default values
if Path(".env").is_file():
dotenv.load_dotenv()
logger.info("Loaded .env file")
else:
logger.info("No .env file found")
# Parse arguments # Parse arguments
# Set up argparse # Set up argparse
@ -30,27 +43,17 @@ def main():
credential_args = argparser.add_argument_group("Cloudflare Credentials") credential_args = argparser.add_argument_group("Cloudflare Credentials")
# Add arguments # Add arguments
# General arguments
argparser.add_argument(
"--log-level", "-l", help="Log level", default="INFO"
) # noqa E501
argparser.add_argument(
"--timeout",
help="Timeout for requests",
default=None,
type = int
)
# Credential arguments
credential_args.add_argument( credential_args.add_argument(
"--account-id", "--account-id",
"-a", "-a",
help="Cloudflare account ID - environment variable: CLOUDFLARE_ACCOUNT_ID", help="Cloudflare account ID - environment variable: CLOUDFLARE_ACCOUNT_ID",
default=os.environ.get("CLOUDFLARE_ACCOUNT_ID"),
) )
credential_args.add_argument( credential_args.add_argument(
"--token", "--token",
"-t", "-t",
help="Cloudflare API token - environment variable: CLOUDFLARE_TOKEN", help="Cloudflare API token - environment variable: CLOUDFLARE_TOKEN",
default=os.environ.get("CLOUDFLARE_TOKEN"),
) )
# Add subcommands # Add subcommands
@ -75,7 +78,7 @@ def main():
"--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="whitelists", # Not really needed because the apply_whitelists function will default to this # noqa: E501 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(
@ -85,8 +88,6 @@ def main():
args = argparser.parse_args() args = argparser.parse_args()
# Set up logging
set_primary_logger(args.log_level)
logger.debug(args) logger.debug(args)
# Load variables # Load variables
@ -94,28 +95,15 @@ def main():
global ACCOUNT_ID global ACCOUNT_ID
TOKEN = args.token TOKEN = args.token
ACCOUNT_ID = args.account_id ACCOUNT_ID = args.account_id
# Check if variables are set
# Check if credentials are set, if they are not, attempt to load from environment variables and .env # noqa E501
if TOKEN is None or ACCOUNT_ID is None: if TOKEN is None or ACCOUNT_ID is None:
logger.info("No credentials provided with flags") logger.error(
if Path(".env").is_file(): "No environment variables found. Please create a .env file or .envrc file"
logger.debug("Loading .env") ) # noqa E501
dotenv.load_dotenv(Path(Path.cwd() / ".env"))
else:
logger.debug("No .env file found")
try:
TOKEN = os.environ["CLOUDFLARE_TOKEN"]
ACCOUNT_ID = os.environ["CLOUDFLARE_ACCOUNT_ID"]
logger.info("Loaded credentials from environment variables")
except KeyError:
logger.error("No credentials provided")
argparser.print_help()
exit(1) exit(1)
# For debugging, print cwd
logger.debug(f"Current working directory: {Path.cwd()}")
try: try:
args.func(args) args.func(args)
except AttributeError: except AttributeError as e:
logger.error("No subcommand specified") logger.error("No subcommand specified")
argparser.print_help() argparser.print_help()
exit(1) exit(1)
@ -126,26 +114,19 @@ def upload_to_cloudflare(args):
blocklists = upload.get_blocklists(args.blocklists) blocklists = upload.get_blocklists(args.blocklists)
blocklists = upload.apply_whitelists(blocklists, args.whitelists) blocklists = upload.apply_whitelists(blocklists, args.whitelists)
lists = upload.split_list(blocklists) lists = upload.split_list(blocklists)
asyncio.run(upload.upload_to_cloudflare(lists, ACCOUNT_ID, TOKEN, args.timeout)) upload.upload_to_cloudflare(lists, ACCOUNT_ID, TOKEN)
cloud_lists = utils.get_lists(ACCOUNT_ID, TOKEN, args.timeout) cloud_lists = utils.get_lists(ACCOUNT_ID, TOKEN)
cloud_lists = utils.filter_adblock_lists(cloud_lists) cloud_lists = utils.filter_adblock_lists(cloud_lists)
upload.create_dns_policy(cloud_lists, ACCOUNT_ID, TOKEN, args.timeout) upload.create_dns_policy(cloud_lists, ACCOUNT_ID, TOKEN)
def delete_from_cloudflare(args): def delete_from_cloudflare(args):
logger.info("Deleting from Cloudflare") logger.info("Deleting from Cloudflare")
rules = utils.get_gateway_rules(ACCOUNT_ID, TOKEN, timeout = args.timeout) rules = utils.get_gateway_rules(ACCOUNT_ID, TOKEN)
delete.delete_adblock_policy(rules, ACCOUNT_ID, TOKEN, args.timeout) delete.delete_adblock_policy(rules, ACCOUNT_ID, TOKEN)
lists = utils.get_lists(ACCOUNT_ID, TOKEN, args.timeout) lists = utils.get_lists(ACCOUNT_ID, TOKEN)
lists = utils.filter_adblock_lists(lists) lists = utils.filter_adblock_lists(lists)
asyncio.run(delete.delete_adblock_list(lists, ACCOUNT_ID, TOKEN, args.timeout)) delete.delete_adblock_list(lists, ACCOUNT_ID, TOKEN)
def set_primary_logger(log_level):
logger.remove()
# ^10 is a formatting directive to center with a padding of 10
logger_format = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> |<level>{level: ^10}</level>| <level>{message}</level>" # noqa E501
logger.add(stderr, format=logger_format, colorize=True, level=log_level)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,21 +1,19 @@
import asyncio # This is a scriprt to undo the changes made by adblock-zerotrust.py
import httpx
import requests import requests
from . import utils from . import utils
async def delete_adblock_list(lists: dict, account_id: str, token: str, timeout:int = 10): def delete_adblock_list(lists: dict, account_id: str, token: str):
try: try:
async with httpx.AsyncClient() as client:
for lst in lists: for lst in lists:
url = f'https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists/{lst["id"]}' url = f'https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists/{lst["id"]}'
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
"Content-Type": "application/json", "Content-Type": "application/json",
} }
response = await client.delete(url, headers=headers, timeout=timeout) response = requests.delete(url, headers=headers, timeout=10)
if response.status_code != 200: if response.status_code != 200:
print(f"Error deleting list: {response.text}") print(f"Error deleting list: {response.text}")
else: else:
@ -27,7 +25,7 @@ async def delete_adblock_list(lists: dict, account_id: str, token: str, timeout:
raise e raise e
def delete_adblock_policy(policies: dict, account_id: str, token: str, timeout:int = 10): def delete_adblock_policy(policies: dict, account_id: str, token: str):
for policy in policies: for policy in policies:
if policy["name"] == "Block Ads": if policy["name"] == "Block Ads":
url = f'https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/rules/{policy["id"]}' url = f'https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/rules/{policy["id"]}'
@ -35,7 +33,7 @@ def delete_adblock_policy(policies: dict, account_id: str, token: str, timeout:i
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
"Content-Type": "application/json", "Content-Type": "application/json",
} }
response = requests.delete(url, headers=headers, timeout=timeout) response = requests.delete(url, headers=headers, timeout=10)
if response.status_code != 200: if response.status_code != 200:
print(f"Error deleting policy: {response.text}") print(f"Error deleting policy: {response.text}")
else: else:
@ -50,7 +48,7 @@ def main():
token = input("Enter your Cloudflare API token: ") token = input("Enter your Cloudflare API token: ")
rules = utils.get_gateway_rules(account_id, token) rules = utils.get_gateway_rules(account_id, token)
asyncio.run(utils.delete_adblock_list(rules, account_id, token)) delete_adblock_policy(rules, account_id, token)
lists = utils.get_lists(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, account_id, token)

View File

@ -1,7 +1,5 @@
import asyncio
import pathlib import pathlib
import httpx
import requests import requests
from . import utils from . import utils
@ -48,11 +46,12 @@ def split_list(blocklists):
return lists return lists
async def upload_to_cloudflare(lists, account_id: str, token: str, timeout:int = 10) -> None: def upload_to_cloudflare(lists, account_id: str, token: str) -> None:
async with httpx.AsyncClient() as client:
for i, lst in enumerate(lists): for i, lst in enumerate(lists):
list_name = f"adblock-list-{i + 1}" list_name = f"adblock-list-{i + 1}"
url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists" url = (
f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists"
)
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
"Content-Type": "application/json", "Content-Type": "application/json",
@ -70,14 +69,13 @@ async def upload_to_cloudflare(lists, account_id: str, token: str, timeout:int =
for x in lst for x in lst
], ],
} }
response = await client.post(url, headers=headers, json=data, timeout=timeout) response = requests.post(url, headers=headers, json=data, timeout=10)
print(f"Uploaded {list_name} to Cloudflare") print(f"Uploaded {list_name} to Cloudflare")
if response.status_code != 200: if response.status_code != 200:
print(f"Error uploading {list_name}: {response.text}") print(f"Error uploading {list_name}: {response.text}")
exit(1)
def create_dns_policy(lists, account_id: str, token: str, timeout = 10) -> None: def create_dns_policy(lists, account_id: str, token: str) -> None:
url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/rules" url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/rules"
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
@ -98,7 +96,7 @@ def create_dns_policy(lists, account_id: str, token: str, timeout = 10) -> None:
"traffic": traffic, "traffic": traffic,
"enabled": True, "enabled": True,
} }
response = requests.post(url, headers=headers, json=data, timeout=timeout) response = requests.post(url, headers=headers, json=data, timeout=10)
if response.status_code != 200: if response.status_code != 200:
print(f"Error creating DNS policy: {response.text}") print(f"Error creating DNS policy: {response.text}")
@ -110,7 +108,7 @@ def main():
blocklists = get_blocklists() blocklists = get_blocklists()
blocklists = apply_whitelists(blocklists) blocklists = apply_whitelists(blocklists)
lists = split_list(blocklists) lists = split_list(blocklists)
asyncio.run(upload_to_cloudflare(lists, account_id, token)) upload_to_cloudflare(lists, account_id, token)
cloud_lists = utils.get_lists(account_id, token) 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, account_id, token) create_dns_policy(cloud_lists, account_id, token)

View File

@ -19,6 +19,7 @@ class Config:
def token(self, token): def token(self, token):
if token is None: if token is None:
raise ValueError("No token provided") raise ValueError("No token provided")
else:
url = "https://api.cloudflare.com/client/v4/user/tokens/verify" url = "https://api.cloudflare.com/client/v4/user/tokens/verify"
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
@ -27,6 +28,7 @@ class Config:
response = requests.get(url, headers=headers, timeout=10) response = requests.get(url, headers=headers, timeout=10)
if response.json()["success"] is False: if response.json()["success"] is False:
raise ValueError("Invalid token") raise ValueError("Invalid token")
else:
# Token needs the following scopes: # Token needs the following scopes:
# Zero Trust: Read/Edit # Zero Trust: Read/Edit
# Account Firewall Access Rules: Read/Edit # Account Firewall Access Rules: Read/Edit
@ -41,6 +43,7 @@ class Config:
def account_id(self, account_id): def account_id(self, account_id):
if account_id is None: if account_id is None:
raise ValueError("No account ID provided") raise ValueError("No account ID provided")
else:
# Possibly make a request to lists to check if the account ID exists # Possibly make a request to lists to check if the account ID exists
self._account_id = account_id self._account_id = account_id
@ -67,31 +70,31 @@ def convert_to_list(file: pathlib.Path) -> list:
"ip6-allnodes", "ip6-allnodes",
"ip6-allrouters", "ip6-allrouters",
"ip6-allhosts", "ip6-allhosts",
"0.0.0.0", # skipcq: BAN-B104 "0.0.0.0",
] ]
matches = [ matches = [
re.search(r"^(?:127\.0\.0\.1|0\.0\.0\.0|::1)\s+(.+?)(?:\s+#.+)?$", line) re.search(r"^(?:127\.0\.0\.1|0\.0\.0\.0|::1)\s+(.+?)(?:\s+#.+)?$", line)
for line in f for line in f
] ]
hosts = { hosts = [
match.group(1) match.group(1)
for match in matches for match in matches
if match and match.group(1) not in loopback if match and match.group(1) not in loopback
} ]
# print(f"First 5 hosts: {hosts[:5]}") print(f"First 5 hosts: {hosts[:5]}")
return list(hosts) return hosts
# General Utils # General Utils
def get_lists(account_id, token, timeout = 10) -> dict: def get_lists(account_id, token) -> dict:
url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists" url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists"
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
"Content-Type": "application/json", "Content-Type": "application/json",
} }
response = requests.get(url, headers=headers, timeout=timeout) response = requests.get(url, headers=headers, timeout=10)
if response.status_code != 200: if response.status_code != 200:
print(f"Error getting lists: {response.text}") print(f"Error getting lists: {response.text}")
return response.json()["result"] return response.json()["result"]
@ -111,13 +114,13 @@ def filter_adblock_lists(lists: dict) -> dict:
return adblock_lists return adblock_lists
def get_gateway_rules(account_id, token, timeout = 10) -> dict: def get_gateway_rules(account_id, token) -> dict:
url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/rules" url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/rules"
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
"Content-Type": "application/json", "Content-Type": "application/json",
} }
response = requests.get(url, headers=headers, timeout=timeout) response = requests.get(url, headers=headers, timeout=10)
if response.status_code != 200: if response.status_code != 200:
print(f"Error getting lists: {response.text}") print(f"Error getting lists: {response.text}")
return response.json()["result"] return response.json()["result"]