mirror of
https://github.com/Nezumi-2711/cloudflare-gateway-pihole-scripts.git
synced 2026-09-22 13:38:37 +00:00
Merge pull request #43 from hlqviet/refactor/use-api-token-instead-of-api-key
Use Cloudflare API Token with scoped permissions in favour of API Key for better security
This commit is contained in:
+1
-2
@@ -1,6 +1,5 @@
|
||||
CLOUDFLARE_API_KEY=
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_ACCOUNT_EMAIL=
|
||||
CLOUDFLARE_LIST_ITEM_LIMIT=300000
|
||||
DRY_RUN=0
|
||||
FAST_MODE=0
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -51,10 +51,9 @@ Please note that the GitHub Action downloads the recommended blocklists and whit
|
||||
1. Create a new empty, private repository. Forking or public repositories are discouraged, but supported - although the script never leaks your API keys and GitHub Actions secrets are automatically redacted from the logs, it's better to be safe than sorry.
|
||||
2. Create the following GitHub Actions secrets in your repository settings:
|
||||
|
||||
- `CLOUDFLARE_API_KEY`: Your Cloudflare API key
|
||||
- `CLOUDFLARE_API_TOKEN`: Your Cloudflare API Token with Zero Trust read and edit permissions
|
||||
- `CLOUDFLARE_ACCOUNT_ID`: Your Cloudflare account ID
|
||||
- `CLOUDFLARE_ACCOUNT_EMAIL`: Your Cloudflare account email
|
||||
- `CLOUDFLARE_LIST_ITEM_LIMIT`: The maximum number of blocked domains allowed for your Cloudflare Zero Trust plan. Use 300000 for the free plan or if you're unsure.
|
||||
- `CLOUDFLARE_LIST_ITEM_LIMIT`: The maximum number of blocked domains allowed for your Cloudflare Zero Trust plan. Default to 300,000. Optional if you are using the free plan.
|
||||
- `PING_URL`: /Optional/ The HTTP(S) URL to ping (using curl) after the GitHub Action has successfully updated your filters. Useful for monitoring.
|
||||
|
||||
3. Create the following GitHub Actions variables in your repository settings if you desire:
|
||||
|
||||
@@ -46,8 +46,7 @@ jobs:
|
||||
- name: Delete old rules and lists
|
||||
run: npm run cloudflare-delete
|
||||
env:
|
||||
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
|
||||
CLOUDFLARE_ACCOUNT_EMAIL: ${{ secrets.CLOUDFLARE_ACCOUNT_EMAIL }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_LIST_ITEM_LIMIT: ${{ secrets.CLOUDFLARE_LIST_ITEM_LIMIT }}
|
||||
FAST_MODE: ${{ vars.FAST_MODE }}
|
||||
@@ -55,8 +54,7 @@ jobs:
|
||||
- name: Create new rules and lists
|
||||
run: npm run cloudflare-create
|
||||
env:
|
||||
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
|
||||
CLOUDFLARE_ACCOUNT_EMAIL: ${{ secrets.CLOUDFLARE_ACCOUNT_EMAIL }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_LIST_ITEM_LIMIT: ${{ secrets.CLOUDFLARE_LIST_ITEM_LIMIT }}
|
||||
FAST_MODE: ${{ vars.FAST_MODE }}
|
||||
|
||||
@@ -7,5 +7,6 @@ const wirefilterExpression = lists.reduce((previous, current) => {
|
||||
return `${previous} any(dns.domains[*] in \$${current.id}) or `;
|
||||
}, "");
|
||||
|
||||
console.log("Creating rule...");
|
||||
// Remove the trailing ' or '
|
||||
await createZeroTrustRule(wirefilterExpression.slice(0, -4));
|
||||
|
||||
@@ -11,6 +11,6 @@ const cgpsRule = rules.find(({ name }) => name === "CGPS Filter Lists");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Deleting rule ${cgpsRule.name}`);
|
||||
console.log(`Deleting rule ${cgpsRule.name}...`);
|
||||
await deleteZeroTrustRule(cgpsRule.id);
|
||||
})();
|
||||
|
||||
+7
-3
@@ -118,15 +118,15 @@ await readFile(resolve(`./${blocklistFilename}`), (line, rl) => {
|
||||
}
|
||||
});
|
||||
|
||||
const numberOfLists = Math.ceil(domains.length / LIST_ITEM_SIZE);
|
||||
|
||||
console.log("\n\n");
|
||||
console.log(`Number of processed domains: ${processedDomainCount}`);
|
||||
console.log(`Number of duplicate domains: ${duplicateDomainCount}`);
|
||||
console.log(`Number of unnecessary domains: ${unnecessaryDomainCount}`);
|
||||
console.log(`Number of blocked domains: ${domains.length}`);
|
||||
console.log(`Number of allowed domains: ${allowedDomainCount}`);
|
||||
console.log(
|
||||
`Number of lists to be created: ${Math.ceil(domains.length / LIST_ITEM_SIZE)}`
|
||||
);
|
||||
console.log(`Number of lists to be created: ${numberOfLists}`);
|
||||
console.log("\n\n");
|
||||
|
||||
(async () => {
|
||||
@@ -137,6 +137,10 @@ console.log("\n\n");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Creating ${numberOfLists} lists for ${domains.length} domains...`
|
||||
);
|
||||
|
||||
if (FAST_MODE) {
|
||||
await createZeroTrustListsAtOnce(domains);
|
||||
return;
|
||||
|
||||
@@ -28,6 +28,8 @@ import { FAST_MODE } from "./lib/constants.js";
|
||||
`Got ${lists.length} lists, ${cgpsLists.length} of which are CGPS lists that will be deleted.`
|
||||
);
|
||||
|
||||
console.log(`Deleting ${cgpsLists.length} lists...`);
|
||||
|
||||
if (FAST_MODE) {
|
||||
await deleteZeroTrustListsAtOnce(cgpsLists);
|
||||
return;
|
||||
|
||||
+8
-4
@@ -16,13 +16,17 @@ https://dash.cloudflare.com/1234567890abcdef1234567890abcdef
|
||||
|
||||
In this example, `1234567890abcdef1234567890abcdef` is the account ID.
|
||||
|
||||
### `CLOUDFLARE_API_KEY`
|
||||
### `CLOUDFLARE_API_TOKEN`
|
||||
|
||||
The Cloudflare API key can be found in the [Cloudflare dashboard](https://dash.cloudflare.com/) too. Click on your profile picture (or user icon) in the top right corner, then click on "API Tokens" in the sidebar. Scroll down to the "API Keys" section and click on "View" next to the Global API Key.
|
||||
Cloudflare API Token can be created in your [Cloudflare profile](https://dash.cloudflare.com/profile/api-tokens):
|
||||
|
||||
### `CLOUDFLARE_ACCOUNT_EMAIL`
|
||||
1. Click "Create Token" and click "Get Started" in the "Create Custom Token" row.
|
||||
2. Enter any name for your token
|
||||
3. Add Zero Trust Read and Edit permissions for your account
|
||||
4. Click "Continue to summary" and click "Create Token"
|
||||
5. You will see the created API Token
|
||||
|
||||
The Cloudflare account email is the email address you use to log in to the Cloudflare dashboard.
|
||||

|
||||
|
||||
### `CLOUDFLARE_LIST_ITEM_LIMIT`
|
||||
|
||||
|
||||
+33
-23
@@ -1,6 +1,5 @@
|
||||
import { LIST_ITEM_SIZE } from "./constants.js";
|
||||
import { requestGateway } from "./helpers.js";
|
||||
import { sleep } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Gets Zero Trust lists.
|
||||
@@ -47,7 +46,6 @@ export const createZeroTrustListsOneByOne = async (items) => {
|
||||
|
||||
try {
|
||||
await createZeroTrustList(listName, chunk);
|
||||
await sleep();
|
||||
totalListNumber--;
|
||||
listNumber++;
|
||||
console.log(`Created "${listName}" list - ${totalListNumber} left`);
|
||||
@@ -62,7 +60,6 @@ export const createZeroTrustListsOneByOne = async (items) => {
|
||||
* @param {string[]} items The domains.
|
||||
*/
|
||||
export const createZeroTrustListsAtOnce = async (items) => {
|
||||
const totalListNumber = Math.ceil(items.length / LIST_ITEM_SIZE);
|
||||
const requests = [];
|
||||
|
||||
for (let i = 0, listNumber = 1; i < items.length; i += LIST_ITEM_SIZE) {
|
||||
@@ -77,7 +74,7 @@ export const createZeroTrustListsAtOnce = async (items) => {
|
||||
|
||||
try {
|
||||
await Promise.all(requests);
|
||||
console.log(`Created ${totalListNumber} lists`);
|
||||
console.log("Created lists successfully");
|
||||
} catch (err) {
|
||||
console.error(`Error occurred while creating lists - ${err.toString()}`);
|
||||
}
|
||||
@@ -105,7 +102,6 @@ export const deleteZeroTrustListsOneByOne = async (lists) => {
|
||||
for (const { id, name } of lists) {
|
||||
try {
|
||||
await deleteZeroTrustList(id);
|
||||
await sleep();
|
||||
totalListNumber--;
|
||||
console.log(`Deleted ${name} list - ${totalListNumber} left`);
|
||||
} catch (err) {
|
||||
@@ -125,7 +121,7 @@ export const deleteZeroTrustListsAtOnce = async (lists) => {
|
||||
|
||||
try {
|
||||
await Promise.all(requests);
|
||||
console.log(`Deleted ${lists.length} lists`);
|
||||
console.log("Deleted lists successfully");
|
||||
} catch (err) {
|
||||
console.error(`Error occurred while deleting lists - ${err.toString()}`);
|
||||
}
|
||||
@@ -147,19 +143,26 @@ export const getZeroTrustRules = () =>
|
||||
* @param {string} wirefilterExpression The expression to be used for the rule.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export const createZeroTrustRule = (wirefilterExpression) =>
|
||||
requestGateway("/rules", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
name: "CGPS Filter Lists",
|
||||
description:
|
||||
"Filter lists created by Cloudflare Gateway Pi-hole Scripts. Avoid editing this rule. Changing the name of this rule will break the script.",
|
||||
enabled: true,
|
||||
action: "block",
|
||||
filters: ["dns"],
|
||||
traffic: wirefilterExpression,
|
||||
}),
|
||||
});
|
||||
export const createZeroTrustRule = async (wirefilterExpression) => {
|
||||
try {
|
||||
await requestGateway("/rules", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
name: "CGPS Filter Lists",
|
||||
description:
|
||||
"Filter lists created by Cloudflare Gateway Pi-hole Scripts. Avoid editing this rule. Changing the name of this rule will break the script.",
|
||||
enabled: true,
|
||||
action: "block",
|
||||
filters: ["dns"],
|
||||
traffic: wirefilterExpression,
|
||||
}),
|
||||
});
|
||||
|
||||
console.log("Created rule successfully");
|
||||
} catch (err) {
|
||||
console.error(`Error occurred while creating rule - ${err.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a Zero Trust rule.
|
||||
@@ -168,7 +171,14 @@ export const createZeroTrustRule = (wirefilterExpression) =>
|
||||
* @param {number} id The ID of the rule to be deleted.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export const deleteZeroTrustRule = (id) =>
|
||||
requestGateway(`/rules/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
export const deleteZeroTrustRule = async (id) => {
|
||||
try {
|
||||
await requestGateway(`/rules/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
console.log("Deleted rule successfully");
|
||||
} catch (err) {
|
||||
console.error(`Error occurred while deleting rule - ${err.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
+11
-4
@@ -2,7 +2,15 @@ import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export const API_TOKEN = process.env.CLOUDFLARE_API_KEY;
|
||||
if (process.env.CLOUDFLARE_API_KEY) {
|
||||
console.warn(
|
||||
"Using Global API Key is very risky for your Cloudflare account. It is strongly recommended to create an API Token with scoped permissions instead."
|
||||
);
|
||||
}
|
||||
|
||||
export const API_KEY = process.env.CLOUDFLARE_API_KEY;
|
||||
|
||||
export const API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;
|
||||
|
||||
export const ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID;
|
||||
|
||||
@@ -33,12 +41,11 @@ export const LIST_TYPE = {
|
||||
};
|
||||
|
||||
export const USER_DEFINED_ALLOWLIST_URLS = process.env.ALLOWLIST_URLS
|
||||
// .filter(x => x) removes empty items from the URL arrays
|
||||
? process.env.ALLOWLIST_URLS.split("\n").filter(x => x)
|
||||
? process.env.ALLOWLIST_URLS.split("\n").filter((x) => x)
|
||||
: undefined;
|
||||
|
||||
export const USER_DEFINED_BLOCKLIST_URLS = process.env.BLOCKLIST_URLS
|
||||
? process.env.BLOCKLIST_URLS.split("\n").filter(x => x)
|
||||
? process.env.BLOCKLIST_URLS.split("\n").filter((x) => x)
|
||||
: undefined;
|
||||
|
||||
export const RECOMMENDED_ALLOWLIST_URLS = [
|
||||
|
||||
+25
-11
@@ -1,4 +1,10 @@
|
||||
import { ACCOUNT_EMAIL, ACCOUNT_ID, API_HOST, API_TOKEN } from "./constants.js";
|
||||
import {
|
||||
ACCOUNT_EMAIL,
|
||||
ACCOUNT_ID,
|
||||
API_HOST,
|
||||
API_KEY,
|
||||
API_TOKEN,
|
||||
} from "./constants.js";
|
||||
|
||||
if (!globalThis.fetch) {
|
||||
console.warn(
|
||||
@@ -20,20 +26,30 @@ if (!globalThis.fetch) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const request = async (url, options) => {
|
||||
if (!API_TOKEN || !ACCOUNT_ID || !ACCOUNT_EMAIL) {
|
||||
if (!(API_TOKEN || API_KEY) || !ACCOUNT_ID) {
|
||||
throw new Error(
|
||||
"One or more required secrets have not been added: CLOUDFLARE_API_KEY, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_ACCOUNT_EMAIL"
|
||||
"The following secrets are required: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID"
|
||||
);
|
||||
}
|
||||
|
||||
const headers = API_TOKEN
|
||||
? {
|
||||
Authorization: `Bearer ${API_TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Email": ACCOUNT_EMAIL,
|
||||
"X-Auth-Key": API_KEY,
|
||||
};
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Email": ACCOUNT_EMAIL,
|
||||
"X-Auth-Key": API_TOKEN,
|
||||
},
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
...headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -42,8 +58,6 @@ const request = async (url, options) => {
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
console.log(`HTTP request succeeded: ${data.success}`);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,13 +7,6 @@ if (!globalThis.fetch) {
|
||||
globalThis.fetch = (await import("node-fetch")).default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleeps for a specified amount of time.
|
||||
* @param {number} [ms=350] The amount of time in ms.
|
||||
*/
|
||||
export const sleep = (ms = 350) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
/**
|
||||
* Checks if the value is a valid domain.
|
||||
* @param {string} value The value to be checked.
|
||||
|
||||
Reference in New Issue
Block a user