| import path from 'node:path'; |
| import fs from 'node:fs'; |
| import process from 'node:process'; |
| import dns from 'node:dns'; |
| import Handlebars from 'handlebars'; |
| import ipMatching from 'ip-matching'; |
| import isDocker from 'is-docker'; |
|
|
| import { getIpFromRequest } from '../express-common.js'; |
| import { color, getConfigValue, safeReadFileSync } from '../util.js'; |
|
|
| const whitelistPath = path.join(process.cwd(), './whitelist.txt'); |
| const enableForwardedWhitelist = !!getConfigValue('enableForwardedWhitelist', false, 'boolean'); |
| const whitelistDockerHosts = !!getConfigValue('whitelistDockerHosts', true, 'boolean'); |
| |
| let whitelist = getConfigValue('whitelist', []); |
|
|
| if (fs.existsSync(whitelistPath)) { |
| try { |
| let whitelistTxt = fs.readFileSync(whitelistPath, 'utf-8'); |
| whitelist = whitelistTxt.split('\n').filter(ip => ip).map(ip => ip.trim()); |
| } catch (e) { |
| |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function validateWhitelist(entries) { |
| const validEntries = []; |
|
|
| for (const entry of entries) { |
| try { |
| |
| ipMatching.getMatch(entry); |
| validEntries.push(entry); |
| } catch (e) { |
| console.warn(`Whitelist ${color.red('Warning')}: Ignoring invalid entry ${color.yellow(entry)} - ${e.message}`); |
| } |
| } |
|
|
| return validEntries; |
| } |
|
|
| whitelist = validateWhitelist(whitelist); |
|
|
| |
| |
| |
| |
| |
| function getForwardedIp(req) { |
| if (!enableForwardedWhitelist) { |
| return undefined; |
| } |
|
|
| |
| if (req.headers['x-real-ip']) { |
| return req.headers['x-real-ip'].toString(); |
| } |
|
|
| |
| if (req.headers['x-forwarded-for']) { |
| const ipList = req.headers['x-forwarded-for'].toString().split(',').map(ip => ip.trim()); |
| return ipList[0]; |
| } |
|
|
| |
| return undefined; |
| } |
|
|
| |
| |
| |
| |
| async function addDockerHostsToWhitelist() { |
| if (!whitelistDockerHosts || !isDocker()) { |
| return; |
| } |
|
|
| const whitelistHosts = ['host.docker.internal', 'gateway.docker.internal']; |
|
|
| for (const entry of whitelistHosts) { |
| try { |
| const result = await dns.promises.lookup(entry); |
| console.info(`Resolved whitelist hostname ${color.green(entry)} to IPv${result.family} address ${color.green(result.address)}`); |
| whitelist.push(result.address); |
| } catch (e) { |
| console.warn(`Failed to resolve whitelist hostname ${color.red(entry)}: ${e.message}`); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| export default async function getWhitelistMiddleware() { |
| const forbiddenWebpage = Handlebars.compile( |
| safeReadFileSync('./public/error/forbidden-by-whitelist.html') ?? '', |
| ); |
|
|
| const noLogPaths = [ |
| '/favicon.ico', |
| ]; |
|
|
| await addDockerHostsToWhitelist(); |
|
|
| return function (req, res, next) { |
| const clientIp = getIpFromRequest(req); |
| const forwardedIp = getForwardedIp(req); |
| const userAgent = req.headers['user-agent']; |
|
|
| |
| |
| |
| |
| |
| |
| function isIPInWhitelist(whitelist, ip) { |
| return whitelist.some(x => ipMatching.matches(ip, ipMatching.getMatch(x))); |
| } |
|
|
| |
| if (!isIPInWhitelist(whitelist, clientIp) |
| || forwardedIp && !isIPInWhitelist(whitelist, forwardedIp) |
| ) { |
| |
| const ipDetails = forwardedIp |
| ? `${clientIp} (forwarded from ${forwardedIp})` |
| : clientIp; |
|
|
| if (!noLogPaths.includes(req.path)) { |
| console.warn( |
| color.red( |
| `Blocked connection from ${ipDetails}; User Agent: ${userAgent}\n\tTo allow this connection, add its IP address to the whitelist or disable whitelist mode by editing config.yaml in the root directory of your TavernIntern installation.\n`, |
| ), |
| ); |
| } |
|
|
| return res.status(403).send(forbiddenWebpage({ ipDetails })); |
| } |
| next(); |
| }; |
| } |
|
|