Resolve hostnames from whitelist

This commit is contained in:
Cohee
2025-02-27 23:14:57 +02:00
parent f43b42544b
commit 0bc4396427
2 changed files with 64 additions and 0 deletions

View File

@@ -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

View File

@@ -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