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 # 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?
@ -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/) 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 * 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:
@ -15,11 +16,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
@ -34,7 +35,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.
@ -43,4 +44,10 @@ For example:
#### Deleting blocklists and firewall policy #### Deleting blocklists and firewall policy
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,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]] [[package]]
name = "black" name = "black"
version = "23.7.0" version = "24.4.2"
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-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, {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_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, {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_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, {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-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
{file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
{file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, {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_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, {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_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
{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_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
{file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
{file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, {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_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
{file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
{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-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
{file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
{file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
{file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, {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_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
{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-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
{file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
{file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
{file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
] ]
[package.dependencies] [package.dependencies]
@ -38,10 +59,11 @@ 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)"] d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
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)"]
@ -165,15 +187,84 @@ 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.4" version = "3.7"
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.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
] ]
[[package]] [[package]]
@ -303,6 +394,17 @@ 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"
@ -314,15 +416,26 @@ 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.4" version = "2.0.7"
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.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"},
{file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"},
] ]
[package.extras] [package.extras]
@ -348,4 +461,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 = "d1e33ec5def7b19f6fd09ac80725a9a9f24da3274b075ee37eb53fe2b2f54219" content-hash = "6930b7a4d843d920177257c386b9793f74619ee1ec2ab1e7d263935e89e629c8"

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.4"
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,11 +18,12 @@ 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.0" black = ">=23.7,<25.0"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]

View File

@ -2,6 +2,7 @@
# 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
@ -16,20 +17,6 @@ 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
@ -43,17 +30,27 @@ 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
@ -78,7 +75,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="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 # Add subcommand: delete
delete_parser = subparsers.add_parser( delete_parser = subparsers.add_parser(
@ -88,6 +85,8 @@ 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
@ -95,15 +94,28 @@ 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.error( logger.info("No credentials provided with flags")
"No environment variables found. Please create a .env file or .envrc file" if Path(".env").is_file():
) # noqa E501 logger.debug("Loading .env")
exit(1) 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: try:
args.func(args) args.func(args)
except AttributeError as e: except AttributeError:
logger.error("No subcommand specified") logger.error("No subcommand specified")
argparser.print_help() argparser.print_help()
exit(1) exit(1)
@ -114,19 +126,26 @@ 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)
upload.upload_to_cloudflare(lists, ACCOUNT_ID, TOKEN) asyncio.run(upload.upload_to_cloudflare(lists, ACCOUNT_ID, TOKEN, args.timeout))
cloud_lists = utils.get_lists(ACCOUNT_ID, TOKEN) cloud_lists = utils.get_lists(ACCOUNT_ID, TOKEN, args.timeout)
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) upload.create_dns_policy(cloud_lists, ACCOUNT_ID, TOKEN, args.timeout)
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) rules = utils.get_gateway_rules(ACCOUNT_ID, TOKEN, timeout = args.timeout)
delete.delete_adblock_policy(rules, ACCOUNT_ID, TOKEN) delete.delete_adblock_policy(rules, ACCOUNT_ID, TOKEN, args.timeout)
lists = utils.get_lists(ACCOUNT_ID, TOKEN) lists = utils.get_lists(ACCOUNT_ID, TOKEN, args.timeout)
lists = utils.filter_adblock_lists(lists) 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__": 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 import requests
from . import utils 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: try:
for lst in lists: async with httpx.AsyncClient() as client:
url = f'https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists/{lst["id"]}' for lst in lists:
headers = { url = f'https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/lists/{lst["id"]}'
"Authorization": f"Bearer {token}", headers = {
"Content-Type": "application/json", "Authorization": f"Bearer {token}",
} "Content-Type": "application/json",
response = requests.delete(url, headers=headers, timeout=10) }
if response.status_code != 200: response = await client.delete(url, headers=headers, timeout=timeout)
print(f"Error deleting list: {response.text}") if response.status_code != 200:
else: print(f"Error deleting list: {response.text}")
print(f'Deleted list {lst["name"]}') else:
print(f'Deleted list {lst["name"]}')
except TypeError as e: except TypeError as e:
if str(e) == "'NoneType' object is not iterable": if str(e) == "'NoneType' object is not iterable":
print("No lists found") print("No lists found")
@ -25,7 +27,7 @@ def delete_adblock_list(lists: dict, account_id: str, token: str):
raise e 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: 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"]}'
@ -33,7 +35,7 @@ def delete_adblock_policy(policies: dict, account_id: str, token: str):
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
"Content-Type": "application/json", "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: if response.status_code != 200:
print(f"Error deleting policy: {response.text}") print(f"Error deleting policy: {response.text}")
else: else:
@ -48,7 +50,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)
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.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,5 +1,7 @@
import asyncio
import pathlib import pathlib
import httpx
import requests import requests
from . import utils from . import utils
@ -46,36 +48,36 @@ def split_list(blocklists):
return lists return lists
def upload_to_cloudflare(lists, account_id: str, token: str) -> None: async def upload_to_cloudflare(lists, account_id: str, token: str, timeout:int = 10) -> None:
for i, lst in enumerate(lists): async with httpx.AsyncClient() as client:
list_name = f"adblock-list-{i + 1}" for i, lst in enumerate(lists):
url = ( list_name = f"adblock-list-{i + 1}"
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", }
}
data = { data = {
"name": list_name, "name": list_name,
"type": "DOMAIN", "type": "DOMAIN",
"description": "A blocklist of ad domains", "description": "A blocklist of ad domains",
# Writing this program, I have noticed how powerful list comprehension is. # Writing this program, I have noticed how powerful list comprehension is.
"items": [ "items": [
{ {
"value": x, "value": x,
} }
for x in lst for x in lst
], ],
} }
response = requests.post(url, headers=headers, json=data, timeout=10) response = await client.post(url, headers=headers, json=data, timeout=timeout)
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) -> 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" url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/gateway/rules"
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
@ -96,7 +98,7 @@ def create_dns_policy(lists, account_id: str, token: str) -> None:
"traffic": traffic, "traffic": traffic,
"enabled": True, "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: if response.status_code != 200:
print(f"Error creating DNS policy: {response.text}") print(f"Error creating DNS policy: {response.text}")
@ -108,7 +110,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)
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.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,21 +19,19 @@ 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}", "Content-Type": "application/json",
"Content-Type": "application/json", }
} 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") # Token needs the following scopes:
else: # Zero Trust: Read/Edit
# Token needs the following scopes: # Account Firewall Access Rules: Read/Edit
# Zero Trust: Read/Edit # Access Apps and Policies: Read/Edit
# Account Firewall Access Rules: Read/Edit self._token = token
# Access Apps and Policies: Read/Edit
self._token = token
@property @property
def account_id(self): def account_id(self):
@ -43,9 +41,8 @@ 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
# List Utils # List Utils
@ -70,31 +67,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", "0.0.0.0", # skipcq: BAN-B104
] ]
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 hosts return list(hosts)
# General Utils # 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" 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=10) response = requests.get(url, headers=headers, timeout=timeout)
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"]
@ -114,13 +111,13 @@ def filter_adblock_lists(lists: dict) -> dict:
return adblock_lists 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" 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=10) response = requests.get(url, headers=headers, timeout=timeout)
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"]