Merge pull request #3578 from SillyTavern/whitelist-hosts Whitelist Docker hosts
Signed| @@ -42,6 +42,8 @@ enableForwardedWhitelist: true | ||
| 42 | 42 | whitelist: |
| 43 | 43 | - ::1 |
| 44 | 44 | - 127.0.0.1 |
| 45 | +# Automatically whitelist Docker host and gateway IPs | |
| 46 | +whitelistDockerHosts: true | |
| 45 | 47 | # Toggle basic authentication for endpoints |
| 46 | 48 | basicAuthMode: false |
| 47 | 49 | # Basic authentication credentials |
| @@ -1,10 +1,12 @@ | ||
| 1 | -version: "3" | |
| 2 | 1 | services: |
| 3 | 2 | sillytavern: |
| 4 | 3 | build: .. |
| 5 | 4 | container_name: sillytavern |
| 6 | 5 | hostname: sillytavern |
| 7 | 6 | image: ghcr.io/sillytavern/sillytavern:latest |
| 7 | + environment: | |
| 8 | + - NODE_ENV=production | |
| 9 | + - FORCE_COLOR=1 | |
| 8 | 10 | ports: |
| 9 | 11 | - "8000:8000" |
| 10 | 12 | volumes: |
| @@ -5,5 +5,8 @@ if [ ! -e "config/config.yaml" ]; then | ||
| 5 | 5 | cp -r "default/config.yaml" "config/config.yaml" |
| 6 | 6 | fi |
| 7 | 7 | |
| 8 | +# Execute postinstall to auto-populate config.yaml with missing values | |
| 9 | +npm run postinstall | |
| 10 | + | |
| 8 | 11 | # Start the server |
| 9 | 12 | exec node server.js --listen "$@" |
| @@ -41,7 +41,7 @@ import { | ||
| 41 | 41 | |
| 42 | 42 | import getWebpackServeMiddleware from './src/middleware/webpack-serve.js'; |
| 43 | 43 | import basicAuthMiddleware from './src/middleware/basicAuth.js'; |
| 44 | 44 | import whitelistMiddlewaregetWhitelistMiddleware from './src/middleware/whitelist.js'; |
| 45 | 45 | import accessLoggerMiddleware, { getAccessLogPath, migrateAccessLog } from './src/middleware/accessLogWriter.js'; |
| 46 | 46 | import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js'; |
| 47 | 47 | import initRequestProxy from './src/request-proxy.js'; |
| @@ -125,7 +125,8 @@ if (cliArgs.listen && cliArgs.basicAuthMode) { | ||
| 125 | 125 | } |
| 126 | 126 | |
| 127 | 127 | if (cliArgs.whitelistMode) { |
| 128 | - app.use(whitelistMiddleware()); | |
| 128 | + const whitelistMiddleware = await getWhitelistMiddleware(); | |
| 129 | + app.use(whitelistMiddleware); | |
| 129 | 130 | } |
| 130 | 131 | |
| 131 | 132 | if (cliArgs.listen) { |
| @@ -254,10 +255,12 @@ async function preSetupTasks() { | ||
| 254 | 255 | // Print formatted header |
| 255 | 256 | console.log(); |
| 256 | 257 | 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 | + } | |
| 261 | 264 | } |
| 262 | 265 | console.log(); |
| 263 | 266 | |
| @@ -1,14 +1,18 @@ | ||
| 1 | 1 | import path from 'node:path'; |
| 2 | 2 | import fs from 'node:fs'; |
| 3 | 3 | import process from 'node:process'; |
| 4 | +import dns from 'node:dns'; | |
| 4 | 5 | import Handlebars from 'handlebars'; |
| 5 | 6 | import ipMatching from 'ip-matching'; |
| 7 | +import isDocker from 'is-docker'; | |
| 6 | 8 | |
| 7 | 9 | import { getIpFromRequest } from '../express-common.js'; |
| 8 | 10 | import { color, getConfigValue, safeReadFileSync } from '../util.js'; |
| 9 | 11 | |
| 10 | 12 | const whitelistPath = path.join(process.cwd(), './whitelist.txt'); |
| 11 | 13 | const enableForwardedWhitelist = !!getConfigValue('enableForwardedWhitelist', false, 'boolean'); |
| 14 | +const whitelistDockerHosts = !!getConfigValue('whitelistDockerHosts', true, 'boolean'); | |
| 15 | +/** @type {string[]} */ | |
| 12 | 16 | let whitelist = getConfigValue('whitelist', []); |
| 13 | 17 | |
| 14 | 18 | if (fs.existsSync(whitelistPath)) { |
| @@ -46,10 +50,32 @@ function getForwardedIp(req) { | ||
| 46 | 50 | } |
| 47 | 51 | |
| 48 | 52 | /** |
| 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 | +/** | |
| 49 | 75 | * Returns a middleware function that checks if the client IP is in the whitelist. |
| 50 | 76 | * @returns {Promise<import('express').RequestHandler>} ThePromise that resolves to the middleware function |
| 51 | 77 | */ |
| 52 | 78 | export default async function whitelistMiddlewaregetWhitelistMiddleware() { |
| 53 | 79 | const forbiddenWebpage = Handlebars.compile( |
| 54 | 80 | safeReadFileSync('./public/error/forbidden-by-whitelist.html') ?? '', |
| 55 | 81 | ); |
| @@ -58,6 +84,8 @@ export default function whitelistMiddleware() { | ||
| 58 | 84 | '/favicon.ico', |
| 59 | 85 | ]; |
| 60 | 86 | |
| 87 | + await addDockerHostsToWhitelist(); | |
| 88 | + | |
| 61 | 89 | return function (req, res, next) { |
| 62 | 90 | const clientIp = getIpFromRequest(req); |
| 63 | 91 | const forwardedIp = getForwardedIp(req); |
| @@ -504,7 +504,6 @@ export function toAvatarKey(handle) { | ||
| 504 | 504 | */ |
| 505 | 505 | export async function initUserStorage(dataRoot) { |
| 506 | 506 | console.log('Using data root:', color.green(dataRoot)); |
| 507 | - console.log(); | |
| 508 | 507 | await storage.init({ |
| 509 | 508 | dir: path.join(dataRoot, '_storage'), |
| 510 | 509 | ttl: false, // Never expire |