Discord webhook notifications

Fixes #46
This commit is contained in:
mrrfv
2023-11-08 22:42:02 +01:00
parent 034decc43d
commit e552a8730f
8 changed files with 72 additions and 1 deletions
+3
View File
@@ -8,3 +8,6 @@ FAST_MODE=0
# Multiline is supported: https://github.com/motdotla/dotenv#multiline-values # Multiline is supported: https://github.com/motdotla/dotenv#multiline-values
# ALLOWLIST_URLS= # ALLOWLIST_URLS=
# BLOCKLIST_URLS= # BLOCKLIST_URLS=
# Optional Discord webhook URL to send a message to when a script is done running or if an error occurs.
# DISCORD_WEBHOOK_URL=
+2
View File
@@ -30,6 +30,7 @@ Cloudflare Gateway allows you to create custom rules to filter HTTP, DNS, and ne
3. Cloudflare email, API key (NOT the API token), and account ID 3. Cloudflare email, API key (NOT the API token), and account ID
4. A file containing the domains you want to block - **max 300,000 domains for the free plan** - in the working directory named `blocklist.txt`. Mullvad provides awesome [DNS blocklists](https://github.com/mullvad/dns-blocklists) that work well with this project. A script that downloads recommended blocklists, `download_lists.js`, is included. 4. A file containing the domains you want to block - **max 300,000 domains for the free plan** - in the working directory named `blocklist.txt`. Mullvad provides awesome [DNS blocklists](https://github.com/mullvad/dns-blocklists) that work well with this project. A script that downloads recommended blocklists, `download_lists.js`, is included.
5. Optional: You can whitelist domains by putting them in a file `allowlist.txt`. You can also use the `get_recomended_whitelist.sh` Bash script to get the recommended whitelists. 5. Optional: You can whitelist domains by putting them in a file `allowlist.txt`. You can also use the `get_recomended_whitelist.sh` Bash script to get the recommended whitelists.
6. Optional: A Discord (or similar) webhook URL to send notifications to.
### Running locally ### Running locally
@@ -55,6 +56,7 @@ Please note that the GitHub Action downloads the recommended blocklists and whit
- `CLOUDFLARE_ACCOUNT_ID`: Your Cloudflare account ID - `CLOUDFLARE_ACCOUNT_ID`: Your Cloudflare account ID
- `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. 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. - `PING_URL`: /Optional/ The HTTP(S) URL to ping (using curl) after the GitHub Action has successfully updated your filters. Useful for monitoring.
- `DISCORD_WEBHOOK_URL`: /Optional/ The Discord (or similar) webhook URL to send notifications to. Good for monitoring as well.
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:
+2
View File
@@ -49,6 +49,7 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
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 }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
FAST_MODE: ${{ vars.FAST_MODE }} FAST_MODE: ${{ vars.FAST_MODE }}
- name: Create new rules and lists - name: Create new rules and lists
@@ -57,6 +58,7 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
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 }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
FAST_MODE: ${{ vars.FAST_MODE }} FAST_MODE: ${{ vars.FAST_MODE }}
- name: Send ping request - name: Send ping request
+3
View File
@@ -1,4 +1,5 @@
import { createZeroTrustRule, getZeroTrustLists } from "./lib/api.js"; import { createZeroTrustRule, getZeroTrustLists } from "./lib/api.js";
import { notifyWebhook } from "./lib/helpers.js";
const { result: lists } = await getZeroTrustLists(); const { result: lists } = await getZeroTrustLists();
const wirefilterExpression = lists.reduce((previous, current) => { const wirefilterExpression = lists.reduce((previous, current) => {
@@ -10,3 +11,5 @@ const wirefilterExpression = lists.reduce((previous, current) => {
console.log("Creating rule..."); console.log("Creating rule...");
// Remove the trailing ' or ' // Remove the trailing ' or '
await createZeroTrustRule(wirefilterExpression.slice(0, -4)); await createZeroTrustRule(wirefilterExpression.slice(0, -4));
// Send a notification to the webhook
await notifyWebhook("CF Gateway Rule Create script finished running");
+3
View File
@@ -1,4 +1,5 @@
import { deleteZeroTrustRule, getZeroTrustRules } from "./lib/api.js"; import { deleteZeroTrustRule, getZeroTrustRules } from "./lib/api.js";
import { notifyWebhook } from "./lib/helpers.js";
const { result: rules } = await getZeroTrustRules(); const { result: rules } = await getZeroTrustRules();
const cgpsRule = rules.find(({ name }) => name === "CGPS Filter Lists"); const cgpsRule = rules.find(({ name }) => name === "CGPS Filter Lists");
@@ -14,3 +15,5 @@ const cgpsRule = rules.find(({ name }) => name === "CGPS Filter Lists");
console.log(`Deleting rule ${cgpsRule.name}...`); console.log(`Deleting rule ${cgpsRule.name}...`);
await deleteZeroTrustRule(cgpsRule.id); await deleteZeroTrustRule(cgpsRule.id);
})(); })();
// Send a notification to the webhook
await notifyWebhook("CF Gateway Rule Create script finished running");
+5 -1
View File
@@ -12,7 +12,7 @@ import {
LIST_ITEM_SIZE, LIST_ITEM_SIZE,
PROCESSING_FILENAME, PROCESSING_FILENAME,
} from "./lib/constants.js"; } from "./lib/constants.js";
import { normalizeDomain } from "./lib/helpers.js"; import { normalizeDomain, notifyWebhook } from "./lib/helpers.js";
import { import {
extractDomain, extractDomain,
isComment, isComment,
@@ -143,8 +143,12 @@ console.log("\n\n");
if (FAST_MODE) { if (FAST_MODE) {
await createZeroTrustListsAtOnce(domains); await createZeroTrustListsAtOnce(domains);
// TODO: make this less repetitive
await notifyWebhook(`CF List Create script finished running (${domains.length} domains, ${numberOfLists} lists)`);
return; return;
} }
await createZeroTrustListsOneByOne(domains); await createZeroTrustListsOneByOne(domains);
await notifyWebhook(`CF List Create script finished running (${domains.length} domains, ${numberOfLists} lists)`);
})(); })();
+5
View File
@@ -4,6 +4,7 @@ import {
getZeroTrustLists, getZeroTrustLists,
} from "./lib/api.js"; } from "./lib/api.js";
import { FAST_MODE } from "./lib/constants.js"; import { FAST_MODE } from "./lib/constants.js";
import { notifyWebhook } from "./lib/helpers.js";
(async () => { (async () => {
const { result: lists } = await getZeroTrustLists(); const { result: lists } = await getZeroTrustLists();
@@ -32,8 +33,12 @@ import { FAST_MODE } from "./lib/constants.js";
if (FAST_MODE) { if (FAST_MODE) {
await deleteZeroTrustListsAtOnce(cgpsLists); await deleteZeroTrustListsAtOnce(cgpsLists);
// TODO: make this less repetitive
await notifyWebhook(`CF List Delete script finished running (${cgpsLists.length} lists)`);
return; return;
} }
await deleteZeroTrustListsOneByOne(cgpsLists); await deleteZeroTrustListsOneByOne(cgpsLists);
await notifyWebhook(`CF List Delete script finished running (${cgpsLists.length} lists)`);
})(); })();
+49
View File
@@ -19,6 +19,53 @@ if (!globalThis.fetch) {
globalThis.fetch = (await import("node-fetch")).default; globalThis.fetch = (await import("node-fetch")).default;
} }
/**
* Sends a message to a Discord-compatible webhook.
* @param {url|string} url The webhook URL.
* @param {string} message The message to be sent.
* @returns {Promise}
*/
async function sendMessageToWebhook(url, message) {
// Create the payload object with the message
const payload = { content: message };
// Send a POST request to the webhook url with the payload as JSON
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
// Check if the request was successful
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
} else {
return true;
}
} catch (error) {
console.error('Error sending message to webhook:', error);
return false;
}
}
/**
* Sends a CGPS notification to a Discord-compatible webhook.
* Automatically checks if the webhook URL exists.
* @param {string} msg The message to be sent.
* @returns {Promise}
*/
export async function notifyWebhook(msg) {
// Check if the webhook URL exists
const webhook_url = process.env.DISCORD_WEBHOOK_URL;
if (webhook_url || !webhook_url.startsWith('http')) {
// Send the message to the webhook
await sendMessageToWebhook(webhook_url, `CGPS: ${msg}`);
}
// Not logging the lack of a webhook URL since it's not a feature everyone would use
}
/** /**
* Fires request to the specified URL. * Fires request to the specified URL.
* @param {string} url The URL to which the request will be fired. * @param {string} url The URL to which the request will be fired.
@@ -53,6 +100,8 @@ const request = async (url, options) => {
}); });
if (!response.ok) { if (!response.ok) {
// Send a message to the Discord webhook if it exists
await notifyWebhook(`An HTTP error has occurred (${response.status}) while making a web request. Please check the logs for further details.`);
throw new Error(`HTTP error! Status: ${response.status}`); throw new Error(`HTTP error! Status: ${response.status}`);
} }