Resolve hostnames from whitelist

0bc4396427612fd5e590a78c2792d080cea482c8

Cohee <18619528+Cohee1207@users.noreply.github.com>

2 files changed, +64 -0Ignore whitespace
default/config.yaml+1 -0
@@ -42,6 +42,7 @@ enableForwardedWhitelist: true
4242whitelist:
4343 - ::1
4444 - 127.0.0.1
45+ - host.docker.internal
4546# Toggle basic authentication for endpoints
4647basicAuthMode: false
4748# Basic authentication credentials
src/middleware/whitelist.js+63 -0
@@ -1,7 +1,9 @@
11import path from 'node:path';
22import fs from 'node:fs';
33import process from 'node:process';
4+import dns from 'node:dns';
45import Handlebars from 'handlebars';
6+import ipRegex from 'ip-regex';
57import ipMatching from 'ip-matching';
68
79import { getIpFromRequest } from '../express-common.js';
@@ -20,6 +22,8 @@ if (fs.existsSync(whitelistPath)) {
2022 }
2123}
2224
25+await resolveHostnames();
26+
2327/**
2428 * Get the client IP address from the request headers.
2529 * @param {import('express').Request} req Express request object
@@ -46,6 +50,65 @@ function getForwardedIp(req) {
4650}
4751
4852/**
53+ * Checks if a string is a valid hostname according to RFC 1123
54+ * @param {string} hostname The string to test
55+ * @returns {boolean} True if the string is a valid hostname
56+ */
57+function isValidHostname(hostname) {
58+ 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;
59+ return hostnameRegex.test(hostname);
60+}
61+
62+/**
63+ * Checks if a string is an IP address, CIDR notation, or IP wildcard
64+ * @param {string} entry The string to test
65+ * @returns {boolean} True if the string matches any IP format
66+ */
67+function isIpFormat(entry) {
68+ // Match CIDR notation (e.g. 192.168.0.0/24)
69+ if (entry.includes('/')) {
70+ return true;
71+ }
72+
73+ // Match exact IP address
74+ if (ipRegex({ exact: true }).test(entry)) {
75+ return true;
76+ }
77+
78+ // Match IPv4 with wildcards (e.g. 192.168.*.* or 192.168.0.*)
79+ const ipWildcardRegex = /^(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$/;
80+ return ipWildcardRegex.test(entry);
81+}
82+
83+/**
84+ * Validate the IP addresses in the whitelist.
85+ * Hostnames are resolved to IP addresses.
86+ */
87+async function resolveHostnames() {
88+ for (let i = 0; i < whitelist.length; i++) {
89+ try {
90+ const entry = whitelist[i];
91+
92+ // Skip if entry appears to be an IP address, CIDR notation, or IP wildcard
93+ if (isIpFormat(entry)) {
94+ continue;
95+ }
96+
97+ if (isValidHostname(entry)) {
98+ const result = await dns.promises.lookup(entry);
99+
100+ if (result.address) {
101+ console.info('Resolved whitelist hostname', entry, 'to IP address', result.address);
102+ whitelist[i] = result.address;
103+ }
104+ }
105+ } catch {
106+ // Ignore errors when resolving hostnames
107+ }
108+ }
109+}
110+
111+/**
49112 * Returns a middleware function that checks if the client IP is in the whitelist.
50113 * @returns {import('express').RequestHandler} The middleware function
51114 */