Внешние БД: пустой список таблиц = доступ ко всем (allowlist опционален)
Основная отсечка прав — на уровне пользователя внешней БД (GRANT SELECT); allowlist в подключении теперь опциональное дополнение (было обязательным).
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
//
|
||||
// Безопасность:
|
||||
// - пароль шифруется AES-256-GCM, по API не отдаётся (только маска);
|
||||
// - шлюз: только SELECT, без мульти-выражений, allowlist таблиц на подключение,
|
||||
// принудительный LIMIT (обёртка подзапросом), statement_timeout 10 с;
|
||||
// - шлюз: только SELECT, без мульти-выражений, принудительный LIMIT (обёртка подзапросом),
|
||||
// statement_timeout 10 с; опциональный allowlist таблиц (пустой = все таблицы);
|
||||
// - каждый запрос пишется в external_db_query_log (кто/когда/SQL/строки/мс);
|
||||
// - рекомендуется заводить во внешней БД пользователя с реальным GRANT SELECT
|
||||
// только на нужные таблицы (defence in depth — шлюз не заменяет права БД).
|
||||
// - основной рубеж — пользователь внешней БД с реальным GRANT SELECT ( defence in depth:
|
||||
// шлюз не заменяет права БД).
|
||||
|
||||
import type { Express, Response } from "express";
|
||||
import { Router } from "express";
|
||||
@@ -270,13 +270,14 @@ export function registerExternalDbRoutes(app: Express): void {
|
||||
|
||||
const cleaned = validateReadOnly(sqlText);
|
||||
const allowed = (conn.allowed_tables ?? []).map((t) => String(t).toLowerCase());
|
||||
if (allowed.length === 0) {
|
||||
return res.status(403).json({ error: "Для подключения не задан список разрешённых таблиц" });
|
||||
}
|
||||
const usedTables = extractTableNames(stripStringLiterals(cleaned));
|
||||
const denied = usedTables.filter((t) => !allowed.includes(t));
|
||||
if (denied.length > 0) {
|
||||
return res.status(403).json({ error: `Таблицы не в allowlist: ${denied.join(", ")}` });
|
||||
// Пустой список = доступ ко ВСЕМ таблицам (ограничение — опционально;
|
||||
// основной рубеж — права самого пользователя внешней БД, GRANT SELECT).
|
||||
if (allowed.length > 0) {
|
||||
const usedTables = extractTableNames(stripStringLiterals(cleaned));
|
||||
const denied = usedTables.filter((t) => !allowed.includes(t));
|
||||
if (denied.length > 0) {
|
||||
return res.status(403).json({ error: `Таблицы не в allowlist: ${denied.join(", ")}` });
|
||||
}
|
||||
}
|
||||
|
||||
const wrapped = `SELECT * FROM (${cleaned}) AS __gateway_q LIMIT ${MAX_ROWS}`;
|
||||
|
||||
Reference in New Issue
Block a user