Merge pull request #3578 from SillyTavern/whitelist-hosts Whitelist Docker hosts

f1f7a14349fa14b25d488d570deb8f19bb5f0354

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

Signed
6 files changed, +48 -11Ignore whitespace
default/config.yaml+2 -0
@@ -42,6 +42,8 @@ enableForwardedWhitelist: true
4242whitelist:
4343 - ::1
4444 - 127.0.0.1
45+# Automatically whitelist Docker host and gateway IPs
46+whitelistDockerHosts: true
4547# Toggle basic authentication for endpoints
4648basicAuthMode: false
4749# Basic authentication credentials
docker/docker-compose.yml+3 -1
@@ -1,10 +1,12 @@
1-version: "3"
21services:
32 sillytavern:
43 build: ..
54 container_name: sillytavern
65 hostname: sillytavern
76 image: ghcr.io/sillytavern/sillytavern:latest
7+ environment:
8+ - NODE_ENV=production
9+ - FORCE_COLOR=1
810 ports:
911 - "8000:8000"
1012 volumes:
docker/docker-entrypoint.sh+3 -0
@@ -5,5 +5,8 @@ if [ ! -e "config/config.yaml" ]; then
55 cp -r "default/config.yaml" "config/config.yaml"
66fi
77
8+# Execute postinstall to auto-populate config.yaml with missing values
9+npm run postinstall
10+
811# Start the server
912exec node server.js --listen "$@"
server.js+9 -6
@@ -41,7 +41,7 @@ import {
4141
4242import getWebpackServeMiddleware from './src/middleware/webpack-serve.js';
4343import basicAuthMiddleware from './src/middleware/basicAuth.js';
4444import whitelistMiddlewaregetWhitelistMiddleware from './src/middleware/whitelist.js';
4545import accessLoggerMiddleware, { getAccessLogPath, migrateAccessLog } from './src/middleware/accessLogWriter.js';
4646import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js';
4747import initRequestProxy from './src/request-proxy.js';
@@ -125,7 +125,8 @@ if (cliArgs.listen && cliArgs.basicAuthMode) {
125125}
126126
127127if (cliArgs.whitelistMode) {
128- app.use(whitelistMiddleware());
128+ const whitelistMiddleware = await getWhitelistMiddleware();
129+ app.use(whitelistMiddleware);
129130}
130131
131132if (cliArgs.listen) {
@@ -254,10 +255,12 @@ async function preSetupTasks() {
254255 // Print formatted header
255256 console.log();
256257 console.log(`SillyTavern ${version.pkgVersion}`);
257- console.log(version.gitBranch ? `Running '${version.gitBranch}' (${version.gitRevision}) - ${version.commitDate}` : '');
258+ if (version.gitBranch) {
258- if (version.gitBranch && !version.isLatest && ['staging', 'release'].includes(version.gitBranch)) {
259+ console.log(`Running '${version.gitBranch}' (${version.gitRevision}) - ${version.commitDate}`);
259- console.log('INFO: Currently not on the latest commit.');
260+ if (!version.isLatest && ['staging', 'release'].includes(version.gitBranch)) {
260- console.log(' Run \'git pull\' to update. If you have any merge conflicts, run \'git reset --hard\' and \'git pull\' to reset your branch.');
261+ console.log('INFO: Currently not on the latest commit.');
262+ console.log(' Run \'git pull\' to update. If you have any merge conflicts, run \'git reset --hard\' and \'git pull\' to reset your branch.');
263+ }
261264 }
262265 console.log();
263266
src/middleware/whitelist.js+31 -3
@@ -1,14 +1,18 @@
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';
56import ipMatching from 'ip-matching';
7+import isDocker from 'is-docker';
68
79import { getIpFromRequest } from '../express-common.js';
810import { color, getConfigValue, safeReadFileSync } from '../util.js';
911
1012const whitelistPath = path.join(process.cwd(), './whitelist.txt');
1113const enableForwardedWhitelist = !!getConfigValue('enableForwardedWhitelist', false, 'boolean');
14+const whitelistDockerHosts = !!getConfigValue('whitelistDockerHosts', true, 'boolean');
15+/** @type {string[]} */
1216let whitelist = getConfigValue('whitelist', []);
1317
1418if (fs.existsSync(whitelistPath)) {
@@ -46,10 +50,32 @@ function getForwardedIp(req) {
4650}
4751
4852/**
53+ * Resolves the IP addresses of Docker hostnames and adds them to the whitelist.
54+ * @returns {Promise<void>} Promise that resolves when the Docker hostnames are resolved
55+ */
56+async function addDockerHostsToWhitelist() {
57+ if (!whitelistDockerHosts || !isDocker()) {
58+ return;
59+ }
60+
61+ const whitelistHosts = ['host.docker.internal', 'gateway.docker.internal'];
62+
63+ for (const entry of whitelistHosts) {
64+ try {
65+ const result = await dns.promises.lookup(entry);
66+ console.info(`Resolved whitelist hostname ${color.green(entry)} to IPv${result.family} address ${color.green(result.address)}`);
67+ whitelist.push(result.address);
68+ } catch (e) {
69+ console.warn(`Failed to resolve whitelist hostname ${color.red(entry)}: ${e.message}`);
70+ }
71+ }
72+}
73+
74+/**
4975 * Returns a middleware function that checks if the client IP is in the whitelist.
5076 * @returns {Promise<import('express').RequestHandler>} ThePromise that resolves to the middleware function
5177 */
5278export default async function whitelistMiddlewaregetWhitelistMiddleware() {
5379 const forbiddenWebpage = Handlebars.compile(
5480 safeReadFileSync('./public/error/forbidden-by-whitelist.html') ?? '',
5581 );
@@ -58,6 +84,8 @@ export default function whitelistMiddleware() {
5884 '/favicon.ico',
5985 ];
6086
87+ await addDockerHostsToWhitelist();
88+
6189 return function (req, res, next) {
6290 const clientIp = getIpFromRequest(req);
6391 const forwardedIp = getForwardedIp(req);
src/users.js+0 -1
@@ -504,7 +504,6 @@ export function toAvatarKey(handle) {
504504 */
505505export async function initUserStorage(dataRoot) {
506506 console.log('Using data root:', color.green(dataRoot));
507- console.log();
508507 await storage.init({
509508 dir: path.join(dataRoot, '_storage'),
510509 ttl: false, // Never expire