mirror of
https://github.com/Nezumi-2711/cloudflare-gateway-pihole-scripts.git
synced 2026-09-22 13:38:37 +00:00
Merge pull request #44 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_ID=
|
||||||
CLOUDFLARE_ACCOUNT_EMAIL=
|
|
||||||
CLOUDFLARE_LIST_ITEM_LIMIT=300000
|
CLOUDFLARE_LIST_ITEM_LIMIT=300000
|
||||||
DRY_RUN=0
|
DRY_RUN=0
|
||||||
FAST_MODE=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.
|
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:
|
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_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. Default to 300,000. Optional if you are using the free plan.
|
||||||
- `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.
|
|
||||||
- `PING_URL`: /Optional/ The HTTP(S) URL to ping (using curl) after the GitHub Action has successfully updated your filters. Useful for monitoring.
|
- `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:
|
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
|
- name: Delete old rules and lists
|
||||||
run: npm run cloudflare-delete
|
run: npm run cloudflare-delete
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_EMAIL: ${{ secrets.CLOUDFLARE_ACCOUNT_EMAIL }}
|
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
CLOUDFLARE_LIST_ITEM_LIMIT: ${{ secrets.CLOUDFLARE_LIST_ITEM_LIMIT }}
|
CLOUDFLARE_LIST_ITEM_LIMIT: ${{ secrets.CLOUDFLARE_LIST_ITEM_LIMIT }}
|
||||||
FAST_MODE: ${{ vars.FAST_MODE }}
|
FAST_MODE: ${{ vars.FAST_MODE }}
|
||||||
@@ -55,8 +54,7 @@ jobs:
|
|||||||
- name: Create new rules and lists
|
- name: Create new rules and lists
|
||||||
run: npm run cloudflare-create
|
run: npm run cloudflare-create
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_EMAIL: ${{ secrets.CLOUDFLARE_ACCOUNT_EMAIL }}
|
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
CLOUDFLARE_LIST_ITEM_LIMIT: ${{ secrets.CLOUDFLARE_LIST_ITEM_LIMIT }}
|
CLOUDFLARE_LIST_ITEM_LIMIT: ${{ secrets.CLOUDFLARE_LIST_ITEM_LIMIT }}
|
||||||
FAST_MODE: ${{ vars.FAST_MODE }}
|
FAST_MODE: ${{ vars.FAST_MODE }}
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ const wirefilterExpression = lists.reduce((previous, current) => {
|
|||||||
return `${previous} any(dns.domains[*] in \$${current.id}) or `;
|
return `${previous} any(dns.domains[*] in \$${current.id}) or `;
|
||||||
}, "");
|
}, "");
|
||||||
|
|
||||||
|
console.log("Creating rule...");
|
||||||
// Remove the trailing ' or '
|
// Remove the trailing ' or '
|
||||||
await createZeroTrustRule(wirefilterExpression.slice(0, -4));
|
await createZeroTrustRule(wirefilterExpression.slice(0, -4));
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ const cgpsRule = rules.find(({ name }) => name === "CGPS Filter Lists");
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Deleting rule ${cgpsRule.name}`);
|
console.log(`Deleting rule ${cgpsRule.name}...`);
|
||||||
await deleteZeroTrustRule(cgpsRule.id);
|
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("\n\n");
|
||||||
console.log(`Number of processed domains: ${processedDomainCount}`);
|
console.log(`Number of processed domains: ${processedDomainCount}`);
|
||||||
console.log(`Number of duplicate domains: ${duplicateDomainCount}`);
|
console.log(`Number of duplicate domains: ${duplicateDomainCount}`);
|
||||||
console.log(`Number of unnecessary domains: ${unnecessaryDomainCount}`);
|
console.log(`Number of unnecessary domains: ${unnecessaryDomainCount}`);
|
||||||
console.log(`Number of blocked domains: ${domains.length}`);
|
console.log(`Number of blocked domains: ${domains.length}`);
|
||||||
console.log(`Number of allowed domains: ${allowedDomainCount}`);
|
console.log(`Number of allowed domains: ${allowedDomainCount}`);
|
||||||
console.log(
|
console.log(`Number of lists to be created: ${numberOfLists}`);
|
||||||
`Number of lists to be created: ${Math.ceil(domains.length / LIST_ITEM_SIZE)}`
|
|
||||||
);
|
|
||||||
console.log("\n\n");
|
console.log("\n\n");
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -137,6 +137,10 @@ console.log("\n\n");
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Creating ${numberOfLists} lists for ${domains.length} domains...`
|
||||||
|
);
|
||||||
|
|
||||||
if (FAST_MODE) {
|
if (FAST_MODE) {
|
||||||
await createZeroTrustListsAtOnce(domains);
|
await createZeroTrustListsAtOnce(domains);
|
||||||
return;
|
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.`
|
`Got ${lists.length} lists, ${cgpsLists.length} of which are CGPS lists that will be deleted.`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(`Deleting ${cgpsLists.length} lists...`);
|
||||||
|
|
||||||
if (FAST_MODE) {
|
if (FAST_MODE) {
|
||||||
await deleteZeroTrustListsAtOnce(cgpsLists);
|
await deleteZeroTrustListsAtOnce(cgpsLists);
|
||||||
return;
|
return;
|
||||||
|
|||||||
+8
-4
@@ -16,13 +16,17 @@ https://dash.cloudflare.com/1234567890abcdef1234567890abcdef
|
|||||||
|
|
||||||
In this example, `1234567890abcdef1234567890abcdef` is the account ID.
|
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`
|
### `CLOUDFLARE_LIST_ITEM_LIMIT`
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -1,6 +1,5 @@
|
|||||||
import { LIST_ITEM_SIZE } from "./constants.js";
|
import { LIST_ITEM_SIZE } from "./constants.js";
|
||||||
import { requestGateway } from "./helpers.js";
|
import { requestGateway } from "./helpers.js";
|
||||||
import { sleep } from "./utils.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets Zero Trust lists.
|
* Gets Zero Trust lists.
|
||||||
@@ -47,7 +46,6 @@ export const createZeroTrustListsOneByOne = async (items) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await createZeroTrustList(listName, chunk);
|
await createZeroTrustList(listName, chunk);
|
||||||
await sleep();
|
|
||||||
totalListNumber--;
|
totalListNumber--;
|
||||||
listNumber++;
|
listNumber++;
|
||||||
console.log(`Created "${listName}" list - ${totalListNumber} left`);
|
console.log(`Created "${listName}" list - ${totalListNumber} left`);
|
||||||
@@ -62,7 +60,6 @@ export const createZeroTrustListsOneByOne = async (items) => {
|
|||||||
* @param {string[]} items The domains.
|
* @param {string[]} items The domains.
|
||||||
*/
|
*/
|
||||||
export const createZeroTrustListsAtOnce = async (items) => {
|
export const createZeroTrustListsAtOnce = async (items) => {
|
||||||
const totalListNumber = Math.ceil(items.length / LIST_ITEM_SIZE);
|
|
||||||
const requests = [];
|
const requests = [];
|
||||||
|
|
||||||
for (let i = 0, listNumber = 1; i < items.length; i += LIST_ITEM_SIZE) {
|
for (let i = 0, listNumber = 1; i < items.length; i += LIST_ITEM_SIZE) {
|
||||||
@@ -77,7 +74,7 @@ export const createZeroTrustListsAtOnce = async (items) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(requests);
|
await Promise.all(requests);
|
||||||
console.log(`Created ${totalListNumber} lists`);
|
console.log("Created lists successfully");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error occurred while creating lists - ${err.toString()}`);
|
console.error(`Error occurred while creating lists - ${err.toString()}`);
|
||||||
}
|
}
|
||||||
@@ -105,7 +102,6 @@ export const deleteZeroTrustListsOneByOne = async (lists) => {
|
|||||||
for (const { id, name } of lists) {
|
for (const { id, name } of lists) {
|
||||||
try {
|
try {
|
||||||
await deleteZeroTrustList(id);
|
await deleteZeroTrustList(id);
|
||||||
await sleep();
|
|
||||||
totalListNumber--;
|
totalListNumber--;
|
||||||
console.log(`Deleted ${name} list - ${totalListNumber} left`);
|
console.log(`Deleted ${name} list - ${totalListNumber} left`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -125,7 +121,7 @@ export const deleteZeroTrustListsAtOnce = async (lists) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(requests);
|
await Promise.all(requests);
|
||||||
console.log(`Deleted ${lists.length} lists`);
|
console.log("Deleted lists successfully");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error occurred while deleting lists - ${err.toString()}`);
|
console.error(`Error occurred while deleting lists - ${err.toString()}`);
|
||||||
}
|
}
|
||||||
@@ -147,8 +143,9 @@ export const getZeroTrustRules = () =>
|
|||||||
* @param {string} wirefilterExpression The expression to be used for the rule.
|
* @param {string} wirefilterExpression The expression to be used for the rule.
|
||||||
* @returns {Promise<Object>}
|
* @returns {Promise<Object>}
|
||||||
*/
|
*/
|
||||||
export const createZeroTrustRule = (wirefilterExpression) =>
|
export const createZeroTrustRule = async (wirefilterExpression) => {
|
||||||
requestGateway("/rules", {
|
try {
|
||||||
|
await requestGateway("/rules", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: "CGPS Filter Lists",
|
name: "CGPS Filter Lists",
|
||||||
@@ -161,6 +158,12 @@ export const createZeroTrustRule = (wirefilterExpression) =>
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("Created rule successfully");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error occurred while creating rule - ${err.toString()}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a Zero Trust rule.
|
* Deletes a Zero Trust rule.
|
||||||
*
|
*
|
||||||
@@ -168,7 +171,14 @@ export const createZeroTrustRule = (wirefilterExpression) =>
|
|||||||
* @param {number} id The ID of the rule to be deleted.
|
* @param {number} id The ID of the rule to be deleted.
|
||||||
* @returns {Promise<Object>}
|
* @returns {Promise<Object>}
|
||||||
*/
|
*/
|
||||||
export const deleteZeroTrustRule = (id) =>
|
export const deleteZeroTrustRule = async (id) => {
|
||||||
requestGateway(`/rules/${id}`, {
|
try {
|
||||||
|
await requestGateway(`/rules/${id}`, {
|
||||||
method: "DELETE",
|
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();
|
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;
|
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
|
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;
|
: undefined;
|
||||||
|
|
||||||
export const USER_DEFINED_BLOCKLIST_URLS = process.env.BLOCKLIST_URLS
|
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;
|
: undefined;
|
||||||
|
|
||||||
export const RECOMMENDED_ALLOWLIST_URLS = [
|
export const RECOMMENDED_ALLOWLIST_URLS = [
|
||||||
|
|||||||
+23
-9
@@ -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) {
|
if (!globalThis.fetch) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -20,20 +26,30 @@ if (!globalThis.fetch) {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
const request = async (url, options) => {
|
const request = async (url, options) => {
|
||||||
if (!API_TOKEN || !ACCOUNT_ID || !ACCOUNT_EMAIL) {
|
if (!(API_TOKEN || API_KEY) || !ACCOUNT_ID) {
|
||||||
throw new Error(
|
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 response = await fetch(url, {
|
const headers = API_TOKEN
|
||||||
headers: {
|
? {
|
||||||
Authorization: `Bearer ${API_TOKEN}`,
|
Authorization: `Bearer ${API_TOKEN}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
Authorization: `Bearer ${API_KEY}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
"X-Auth-Email": ACCOUNT_EMAIL,
|
"X-Auth-Email": ACCOUNT_EMAIL,
|
||||||
"X-Auth-Key": API_TOKEN,
|
"X-Auth-Key": API_KEY,
|
||||||
},
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
|
headers: {
|
||||||
|
...options.headers,
|
||||||
|
...headers,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -42,8 +58,6 @@ const request = async (url, options) => {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
console.log(`HTTP request succeeded: ${data.success}`);
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,6 @@ if (!globalThis.fetch) {
|
|||||||
globalThis.fetch = (await import("node-fetch")).default;
|
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.
|
* Checks if the value is a valid domain.
|
||||||
* @param {string} value The value to be checked.
|
* @param {string} value The value to be checked.
|
||||||
|
|||||||
Reference in New Issue
Block a user