From 0bc4396427612fd5e590a78c2792d080cea482c8 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:14:57 +0200 Subject: [PATCH] Resolve hostnames from whitelist --- default/config.yaml | 1 + src/middleware/whitelist.js | 63 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/default/config.yaml b/default/config.yaml index c05cc9371..fd980224a 100644 --- a/default/config.yaml +++ b/default/config.yaml @@ -42,6 +42,7 @@ enableForwardedWhitelist: true whitelist: - ::1 - 127.0.0.1 + - host.docker.internal # Toggle basic authentication for endpoints basicAuthMode: false # Basic authentication credentials diff --git a/src/middleware/whitelist.js b/src/middleware/whitelist.js index dea47782a..9427b3ba8 100644 --- a/src/middleware/whitelist.js +++ b/src/middleware/whitelist.js @@ -1,7 +1,9 @@ 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 ipRegex from 'ip-regex'; import ipMatching from 'ip-matching'; import { getIpFromRequest } from '../express-common.js'; @@ -20,6 +22,8 @@ if (fs.existsSync(whitelistPath)) { } } +await resolveHostnames(); + /** * Get the client IP address from the request headers. * @param {import('express').Request} req Express request object @@ -45,6 +49,65 @@ function getForwardedIp(req) { return undefined; } +/** + * Checks if a string is a valid hostname according to RFC 1123 + * @param {string} hostname The string to test + * @returns {boolean} True if the string is a valid hostname + */ +function isValidHostname(hostname) { + const hostnameRegex = /^(([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$/i; + return hostnameRegex.test(hostname); +} + +/** + * Checks if a string is an IP address, CIDR notation, or IP wildcard + * @param {string} entry The string to test + * @returns {boolean} True if the string matches any IP format + */ +function isIpFormat(entry) { + // Match CIDR notation (e.g. 192.168.0.0/24) + if (entry.includes('/')) { + return true; + } + + // Match exact IP address + if (ipRegex({ exact: true }).test(entry)) { + return true; + } + + // Match IPv4 with wildcards (e.g. 192.168.*.* or 192.168.0.*) + const ipWildcardRegex = /^(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$/; + return ipWildcardRegex.test(entry); +} + +/** + * Validate the IP addresses in the whitelist. + * Hostnames are resolved to IP addresses. + */ +async function resolveHostnames() { + for (let i = 0; i < whitelist.length; i++) { + try { + const entry = whitelist[i]; + + // Skip if entry appears to be an IP address, CIDR notation, or IP wildcard + if (isIpFormat(entry)) { + continue; + } + + if (isValidHostname(entry)) { + const result = await dns.promises.lookup(entry); + + if (result.address) { + console.info('Resolved whitelist hostname', entry, 'to IP address', result.address); + whitelist[i] = result.address; + } + } + } catch { + // Ignore errors when resolving hostnames + } + } +} + /** * Returns a middleware function that checks if the client IP is in the whitelist. * @returns {import('express').RequestHandler} The middleware function