Merge pull request #9949 from Budibase/fix/blacklist

Blacklisting
This commit is contained in:
Michael Drury 2023-03-20 10:33:11 +00:00 committed by GitHub
commit 34e8009f08
6 changed files with 107 additions and 0 deletions

View File

@ -0,0 +1,54 @@
import dns from "dns"
import net from "net"
import env from "../environment"
import { promisify } from "util"
let blackListArray: string[] | undefined
const performLookup = promisify(dns.lookup)
async function lookup(address: string): Promise<string[]> {
if (!net.isIP(address)) {
// need this for URL parsing simply
if (!address.startsWith("http")) {
address = `https://${address}`
}
address = new URL(address).hostname
}
const addresses = await performLookup(address, {
all: true,
})
return addresses.map(addr => addr.address)
}
export async function refreshBlacklist() {
const blacklist = env.BLACKLIST_IPS
const list = blacklist?.split(",") || []
let final: string[] = []
for (let addr of list) {
const trimmed = addr.trim()
if (!net.isIP(trimmed)) {
const addresses = await lookup(trimmed)
final = final.concat(addresses)
} else {
final.push(trimmed)
}
}
blackListArray = final
}
export async function isBlacklisted(address: string): Promise<boolean> {
if (!blackListArray) {
await refreshBlacklist()
}
if (blackListArray?.length === 0) {
return false
}
// no need for DNS
let ips: string[]
if (!net.isIP(address)) {
ips = await lookup(address)
} else {
ips = [address]
}
return !!blackListArray?.find(addr => ips.includes(addr))
}

View File

@ -0,0 +1 @@
export * from "./blacklist"

View File

@ -0,0 +1,46 @@
import { refreshBlacklist, isBlacklisted } from ".."
import env from "../../environment"
describe("blacklist", () => {
beforeAll(async () => {
env._set(
"BLACKLIST_IPS",
"www.google.com,192.168.1.1, 1.1.1.1,2.2.2.2/something"
)
await refreshBlacklist()
})
it("should blacklist 192.168.1.1", async () => {
expect(await isBlacklisted("192.168.1.1")).toBe(true)
})
it("should allow 192.168.1.2", async () => {
expect(await isBlacklisted("192.168.1.2")).toBe(false)
})
it("should blacklist www.google.com", async () => {
expect(await isBlacklisted("www.google.com")).toBe(true)
})
it("should handle a complex domain", async () => {
expect(
await isBlacklisted("https://www.google.com/derp/?something=1")
).toBe(true)
})
it("should allow www.microsoft.com", async () => {
expect(await isBlacklisted("www.microsoft.com")).toBe(false)
})
it("should blacklist an IP that needed trimming", async () => {
expect(await isBlacklisted("1.1.1.1")).toBe(true)
})
it("should blacklist 1.1.1.1/something", async () => {
expect(await isBlacklisted("1.1.1.1/something")).toBe(true)
})
it("should blacklist 2.2.2.2", async () => {
expect(await isBlacklisted("2.2.2.2")).toBe(true)
})
})

View File

@ -104,6 +104,7 @@ const environment = {
SMTP_PORT: parseInt(process.env.SMTP_PORT || ""), SMTP_PORT: parseInt(process.env.SMTP_PORT || ""),
SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS, SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS,
DISABLE_JWT_WARNING: process.env.DISABLE_JWT_WARNING, DISABLE_JWT_WARNING: process.env.DISABLE_JWT_WARNING,
BLACKLIST_IPS: process.env.BLACKLIST_IPS,
/** /**
* Enable to allow an admin user to login using a password. * Enable to allow an admin user to login using a password.
* This can be useful to prevent lockout when configuring SSO. * This can be useful to prevent lockout when configuring SSO.

View File

@ -25,6 +25,7 @@ export * as locks from "./redis/redlockImpl"
export * as utils from "./utils" export * as utils from "./utils"
export * as errors from "./errors" export * as errors from "./errors"
export { default as env } from "./environment" export { default as env } from "./environment"
export * as blacklist from "./blacklist"
export { SearchParams } from "./db" export { SearchParams } from "./db"
// Add context to tenancy for backwards compatibility // Add context to tenancy for backwards compatibility
// only do this for external usages to prevent internal // only do this for external usages to prevent internal

View File

@ -19,6 +19,7 @@ import { formatBytes } from "../utilities"
import { performance } from "perf_hooks" import { performance } from "perf_hooks"
import FormData from "form-data" import FormData from "form-data"
import { URLSearchParams } from "url" import { URLSearchParams } from "url"
import { blacklist } from "@budibase/backend-core"
const BodyTypes = { const BodyTypes = {
NONE: "none", NONE: "none",
@ -398,6 +399,9 @@ class RestIntegration implements IntegrationBase {
this.startTimeMs = performance.now() this.startTimeMs = performance.now()
const url = this.getUrl(path, queryString, pagination, paginationValues) const url = this.getUrl(path, queryString, pagination, paginationValues)
if (await blacklist.isBlacklisted(url)) {
throw new Error("Cannot connect to URL.")
}
const response = await fetch(url, input) const response = await fetch(url, input)
return await this.parseResponse(response, pagination) return await this.parseResponse(response, pagination)
} }