Compare commits

...

23 Commits

Author SHA1 Message Date
slashtechno f241081e5b
Bump version to 0.1.4 2024-05-12 16:15:37 -05:00
dependabot[bot] b71f29e7d5
Bump urllib3 from 2.0.6 to 2.0.7 (#14)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.6...2.0.7)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-12 16:10:20 -05:00
dependabot[bot] 29e14a7043
Bump idna from 3.4 to 3.7 (#15)
Bumps [idna](https://github.com/kjd/idna) from 3.4 to 3.7.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.4...v3.7)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-12 16:10:12 -05:00
dependabot[bot] 9c12d2f1b1
Bump black from 23.7.0 to 24.3.0 (#16)
Bumps [black](https://github.com/psf/black) from 23.7.0 to 24.3.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.7.0...24.3.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-12 16:10:02 -05:00
slashtechno 20a46e4e1f
Update README.md with section for contributing 2024-05-12 15:57:14 -05:00
slashtechno e719461e3c
Add FUNDING.yml 2024-05-12 15:52:35 -05:00
dependabot[bot] 235eba5355
Bump urllib3 from 2.0.4 to 2.0.6 (#13)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.4 to 2.0.6.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.4...2.0.6)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 13:05:04 -05:00
slashtechno 5fd2ce94ab
Added flag 2023-08-19 15:05:41 -04:00
slashtechno fd5f951898
Bump version from `0.1.1` to `0.1.2` 2023-08-18 21:07:55 -04:00
deepsource-autofix[bot] c8e8ab7dda remove commented out code (#7)
* refactor: remove commented out code

It is recommended to remove any commented code in your codebase.

* style: Format code with black and isort

---------

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2023-08-18 21:07:46 -04:00
slashtechno 7e98125597
Duplicate hosts are now omitted 2023-08-18 21:06:25 -04:00
slashtechno 255994447a
Added badge to README.md 2023-08-08 16:33:41 -04:00
slashtechno 2292a9328e Merge remote-tracking branch 'origin/master' 2023-08-08 16:16:42 -04:00
slashtechno a560ae0b2f
Set version to 0.1.1 2023-08-08 16:14:41 -04:00
/techno 58418689eb
Create python-publish.yml 2023-08-08 16:08:56 -04:00
/techno 50683e1543
Add basic `async` support (#6) 2023-08-08 15:50:02 -04:00
/techno c64e2e139d
Merge pull request #5 from slashtechno/deepsource-transform-885222ec
format code with black and isort
2023-08-08 10:43:47 -04:00
deepsource-autofix[bot] 7931374e4e
style: format code with black and isort
Format code with black and isort

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

Details: None
2023-08-08 14:43:09 +00:00
slashtechno ce5c516de1 Added `--log-level` flag 2023-08-07 17:47:51 -04:00
slashtechno 6b8c0d3200 Uncomment installation in README.md 2023-08-07 17:47:51 -04:00
slashtechno e2ce13ebe2 Minor changes 2023-08-07 17:47:51 -04:00
/techno 61990cfa4b
Merge pull request #3 from slashtechno/deepsource-autofix-908ab65d
refactor unnecessary `else` / `elif` when `if` block has a `raise` statement
2023-08-06 19:04:03 -04:00
deepsource-autofix[bot] ed8a4755c1
refactor: refactor unnecessary `else` / `elif` when `if` block has a `raise` statement
`raise` causes control flow to be disrupted, as it will exit the block.
It is recommended to check other conditions using another `if` statement, and get rid of `else` statements as they are unnecessary.
2023-08-06 23:03:35 +00:00
9 changed files with 323 additions and 142 deletions

1
.github/FUNDING.yml vendored Normal file
View File

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

39
.github/workflows/python-publish.yml vendored Normal file
View File

@ -0,0 +1,39 @@
# 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,4 +1,5 @@
# 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
### What is this?
@ -6,7 +7,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/)
### Pre-requisites
### Prerequisites
* Python > 3.10
* A Cloudflare account with Zero Trust enabled
* A Cloudflare API tolken with the following permissions:
@ -15,11 +16,11 @@ This project was heavily inspired by [this blog post](https://blog.marcolancini.
* 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`
-->
`pip install cloudflare-gateway-adblocking`
### Usage
#### Setting Cloudflare credentials
@ -34,7 +35,7 @@ The following command line flags can be used to set the Cloudflare credentials:
* 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
#### 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.
@ -43,4 +44,10 @@ For example:
#### 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`
`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,34 +1,55 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.2 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]]
name = "black"
version = "23.7.0"
version = "24.4.2"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"},
{file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"},
{file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"},
{file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"},
{file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"},
{file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"},
{file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"},
{file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"},
{file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"},
{file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"},
{file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"},
{file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"},
{file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"},
{file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"},
{file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"},
{file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"},
{file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"},
{file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"},
{file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"},
{file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"},
{file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"},
{file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"},
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
{file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
{file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
{file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
{file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
{file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
{file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
{file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
{file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
{file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
{file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
{file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
{file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
{file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
{file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
{file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
{file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
{file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
{file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
{file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
{file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
]
[package.dependencies]
@ -38,10 +59,11 @@ packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
@ -165,15 +187,84 @@ files = [
{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]]
name = "idna"
version = "3.4"
version = "3.7"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
@ -303,6 +394,17 @@ files = [
{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]]
name = "tomli"
version = "2.0.1"
@ -314,15 +416,26 @@ files = [
{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]]
name = "urllib3"
version = "2.0.4"
version = "2.0.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.7"
files = [
{file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"},
{file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"},
{file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"},
{file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"},
]
[package.extras]
@ -348,4 +461,4 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "d1e33ec5def7b19f6fd09ac80725a9a9f24da3274b075ee37eb53fe2b2f54219"
content-hash = "6930b7a4d843d920177257c386b9793f74619ee1ec2ab1e7d263935e89e629c8"

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "cloudflare-gateway-adblocking"
version = "0.1.0"
version = "0.1.4"
description = "Serverless adblocking via Cloudflare Zero Trust Gateway"
authors = ["slastechno <77907286+slashtechno@users.noreply.github.com>"]
license = "MIT"
@ -18,11 +18,12 @@ python = "^3.10"
requests = "^2.31.0"
loguru = "^0.7.0"
python-dotenv = "^1.0.0"
httpx = "^0.24.1"
[tool.poetry.group.dev.dependencies]
ruff = "^0.0.281"
black = "^23.7.0"
black = ">=23.7,<25.0"
[build-system]
requires = ["poetry-core"]

View File

@ -2,6 +2,7 @@
# python -m src.__main__
# python -m src # also works because __main__ is the default module
import argparse
import asyncio
import os
from pathlib import Path
from sys import exit, stderr
@ -16,20 +17,6 @@ ACCOUNT_ID = None
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
# Set up argparse
@ -43,17 +30,27 @@ def main():
credential_args = argparser.add_argument_group("Cloudflare Credentials")
# 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(
"--account-id",
"-a",
help="Cloudflare account ID - environment variable: CLOUDFLARE_ACCOUNT_ID",
default=os.environ.get("CLOUDFLARE_ACCOUNT_ID"),
)
credential_args.add_argument(
"--token",
"-t",
help="Cloudflare API token - environment variable: CLOUDFLARE_TOKEN",
default=os.environ.get("CLOUDFLARE_TOKEN"),
)
# Add subcommands
@ -78,7 +75,7 @@ def main():
"--whitelists",
"-w",
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="whitelists", # Not really needed because the apply_whitelists function will default to this # noqa: E501
)
# Add subcommand: delete
delete_parser = subparsers.add_parser(
@ -88,6 +85,8 @@ def main():
args = argparser.parse_args()
# Set up logging
set_primary_logger(args.log_level)
logger.debug(args)
# Load variables
@ -95,15 +94,28 @@ def main():
global ACCOUNT_ID
TOKEN = args.token
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:
logger.error(
"No environment variables found. Please create a .env file or .envrc file"
) # noqa E501
exit(1)
logger.info("No credentials provided with flags")
if Path(".env").is_file():
logger.debug("Loading .env")
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)
# For debugging, print cwd
logger.debug(f"Current working directory: {Path.cwd()}")
try:
args.func(args)
except AttributeError as e:
except AttributeError:
logger.error("No subcommand specified")
argparser.print_help()
exit(1)
@ -114,19 +126,26 @@ def upload_to_cloudflare(args):
blocklists = upload.get_blocklists(args.blocklists)
blocklists = upload.apply_whitelists(blocklists, args.whitelists)
lists = upload.split_list(blocklists)
upload.upload_to_cloudflare(lists, ACCOUNT_ID, TOKEN)
cloud_lists = utils.get_lists(ACCOUNT_ID, TOKEN)
asyncio.run(upload.upload_to_cloudflare(lists, ACCOUNT_ID, TOKEN, args.timeout))
cloud_lists = utils.get_lists(ACCOUNT_ID, TOKEN, args.timeout)
cloud_lists = utils.filter_adblock_lists(cloud_lists)
upload.create_dns_policy(cloud_lists, ACCOUNT_ID, TOKEN)
upload.create_dns_policy(cloud_lists, ACCOUNT_ID, TOKEN, args.timeout)
def delete_from_cloudflare(args):
logger.info("Deleting from Cloudflare")
rules = utils.get_gateway_rules(ACCOUNT_ID, TOKEN)
delete.delete_adblock_policy(rules, ACCOUNT_ID, TOKEN)
lists = utils.get_lists(ACCOUNT_ID, TOKEN)
rules = utils.get_gateway_rules(ACCOUNT_ID, TOKEN, timeout = args.timeout)
delete.delete_adblock_policy(rules, ACCOUNT_ID, TOKEN, args.timeout)
lists = utils.get_lists(ACCOUNT_ID, TOKEN, args.timeout)
lists = utils.filter_adblock_lists(lists)
delete.delete_adblock_list(lists, ACCOUNT_ID, TOKEN)
asyncio.run(delete.delete_adblock_list(lists, ACCOUNT_ID, TOKEN, args.timeout))
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__":

View File

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

View File

@ -1,5 +1,7 @@
import asyncio
import pathlib
import httpx
import requests
from . import utils
@ -46,36 +48,36 @@ def split_list(blocklists):
return lists
def upload_to_cloudflare(lists, account_id: str, token: str) -> None:
for i, lst in enumerate(lists):
list_name = f"adblock-list-{i + 1}"
url = (
f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists"
)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
async def upload_to_cloudflare(lists, account_id: str, token: str, timeout:int = 10) -> None:
async with httpx.AsyncClient() as client:
for i, lst in enumerate(lists):
list_name = f"adblock-list-{i + 1}"
url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
data = {
"name": list_name,
"type": "DOMAIN",
"description": "A blocklist of ad domains",
# Writing this program, I have noticed how powerful list comprehension is.
"items": [
{
"value": x,
}
for x in lst
],
}
response = requests.post(url, headers=headers, json=data, timeout=10)
print(f"Uploaded {list_name} to Cloudflare")
if response.status_code != 200:
print(f"Error uploading {list_name}: {response.text}")
data = {
"name": list_name,
"type": "DOMAIN",
"description": "A blocklist of ad domains",
# Writing this program, I have noticed how powerful list comprehension is.
"items": [
{
"value": x,
}
for x in lst
],
}
response = await client.post(url, headers=headers, json=data, timeout=timeout)
print(f"Uploaded {list_name} to Cloudflare")
if response.status_code != 200:
print(f"Error uploading {list_name}: {response.text}")
exit(1)
def create_dns_policy(lists, account_id: str, token: str) -> None:
def create_dns_policy(lists, account_id: str, token: str, timeout = 10) -> None:
url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/rules"
headers = {
"Authorization": f"Bearer {token}",
@ -96,7 +98,7 @@ def create_dns_policy(lists, account_id: str, token: str) -> None:
"traffic": traffic,
"enabled": True,
}
response = requests.post(url, headers=headers, json=data, timeout=10)
response = requests.post(url, headers=headers, json=data, timeout=timeout)
if response.status_code != 200:
print(f"Error creating DNS policy: {response.text}")
@ -108,7 +110,7 @@ def main():
blocklists = get_blocklists()
blocklists = apply_whitelists(blocklists)
lists = split_list(blocklists)
upload_to_cloudflare(lists, account_id, token)
asyncio.run(upload_to_cloudflare(lists, account_id, token))
cloud_lists = utils.get_lists(account_id, token)
cloud_lists = utils.filter_adblock_lists(cloud_lists)
create_dns_policy(cloud_lists, account_id, token)

View File

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