Skip to content

Commit adf5c73

Browse files
author
tangyi
committed
<feat> 添加数据库隔离功能,支持跨数据库访问限制,更新配置和文档说明,增强安全性
1 parent 70bdd20 commit adf5c73

File tree

7 files changed

+343
-3
lines changed

7 files changed

+343
-3
lines changed

README.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This project is a MySQL query server based on the MCP framework, supporting real
1818
- 丰富的MySQL元数据与结构查询API
1919
- 自动事务管理与回滚
2020
- 多级SQL风险控制与注入防护
21+
- **数据库隔离安全**:防止跨数据库访问,支持三级访问控制
2122
- 敏感信息自动隐藏与自定义
2223
- 灵活的环境变量配置
2324
- 完善的日志与错误处理
@@ -29,6 +30,7 @@ This project is a MySQL query server based on the MCP framework, supporting real
2930
- Rich MySQL metadata & schema query APIs
3031
- Automatic transaction management & rollback
3132
- Multi-level SQL risk control & injection protection
33+
- **Database Isolation Security**: Prevents cross-database access with 3-level access control
3234
- Automatic and customizable sensitive info masking
3335
- Flexible environment variable configuration
3436
- Robust logging & error handling
@@ -146,6 +148,8 @@ Default endpoint: http://127.0.0.1:3000/sse
146148
| MAX_SQL_LENGTH | 最大SQL语句长度 / Max SQL length | 5000 |
147149
| BLOCKED_PATTERNS | 阻止的SQL模式(逗号分隔) / Blocked SQL patterns | (空/empty) |
148150
| ENABLE_QUERY_CHECK | 启用查询安全检查 / Enable query check (true/false) | true |
151+
| **ENABLE_DATABASE_ISOLATION** | **启用数据库隔离 / Enable database isolation (true/false)** | **false** |
152+
| **DATABASE_ACCESS_LEVEL** | **数据库访问级别 / Database access level (strict/restricted/permissive)** | **permissive** |
149153
| LOG_LEVEL | 日志级别(DEBUG/INFO/...) / Log level | DEBUG |
150154

151155
> 注/Note: 部分云MySQL需指定`DB_AUTH_PLUGIN``mysql_native_password`
@@ -185,9 +189,47 @@ When using `caching_sha2_password`, the `cryptography` package is required (alre
185189
pip install cryptography
186190
```
187191

188-
详细配置指南请参考:[MySQL 8.0 认证插件支持指南](docs/mysql8_authentication.md)
189192

190-
For detailed configuration guide, see: [MySQL 8.0 Authentication Plugin Support Guide](docs/mysql8_authentication.md)
193+
### 数据库隔离安全 / Database Isolation Security
194+
195+
本系统提供强大的数据库隔离功能,防止跨数据库访问,确保数据安全。
196+
197+
This system provides robust database isolation features to prevent cross-database access and ensure data security.
198+
199+
#### 访问级别 / Access Levels
200+
201+
| 级别 / Level | 允许访问 / Allowed Access | 适用场景 / Use Case |
202+
|-------------|---------------------------|-------------------|
203+
| **strict** | 仅指定数据库 / Only specified database | 生产环境 / Production |
204+
| **restricted** | 指定数据库 + 系统库 / Specified + system databases | 开发环境 / Development |
205+
| **permissive** | 所有数据库 / All databases | 测试环境 / Testing |
206+
207+
#### 启用数据库隔离 / Enable Database Isolation
208+
209+
```bash
210+
# Docker 启用严格模式 / Docker with strict mode
211+
docker run -d \
212+
-e MYSQL_DATABASE=your_database \
213+
-e ENABLE_DATABASE_ISOLATION=true \
214+
-e DATABASE_ACCESS_LEVEL=strict \
215+
mangooer/mysql-mcp-server-sse:latest
216+
217+
# 生产环境自动启用 / Auto-enable in production
218+
docker run -d \
219+
-e ENV_TYPE=production \
220+
-e MYSQL_DATABASE=your_database \
221+
mangooer/mysql-mcp-server-sse:latest
222+
```
223+
224+
**安全效果 / Security Effects**
225+
- ✅ 阻止 `SHOW DATABASES` / Blocks `SHOW DATABASES`
226+
- ✅ 阻止 `SELECT * FROM mysql.user` / Blocks `SELECT * FROM mysql.user`
227+
- ✅ 阻止 `SHOW TABLES FROM other_db` / Blocks `SHOW TABLES FROM other_db`
228+
- ✅ 允许当前数据库操作 / Allows current database operations
229+
230+
> 🔒 **重要**:生产环境(`ENV_TYPE=production`)会自动启用数据库隔离,使用 `restricted` 模式。
231+
>
232+
> 🔒 **Important**: Production environment (`ENV_TYPE=production`) automatically enables database isolation with `restricted` mode.
191233
192234
---
193235

@@ -222,14 +264,20 @@ For detailed configuration guide, see: [MySQL 8.0 Authentication Plugin Support
222264
- 多级SQL风险等级(LOW/MEDIUM/HIGH/CRITICAL)
223265
- SQL注入与危险操作拦截
224266
- WHERE子句强制检查
267+
- **数据库隔离安全**:三级访问控制(strict/restricted/permissive)
268+
- **跨数据库访问防护**:阻止未授权的数据库访问
225269
- 敏感信息自动隐藏(支持自定义字段)
226270
- 生产环境默认只允许低风险操作
271+
- **生产环境自动启用数据库隔离**
227272

228273
- Multi-level SQL risk levels (LOW/MEDIUM/HIGH/CRITICAL)
229274
- SQL injection & dangerous operation interception
230275
- Mandatory WHERE clause check
276+
- **Database Isolation Security**: 3-level access control (strict/restricted/permissive)
277+
- **Cross-database Access Protection**: Blocks unauthorized database access
231278
- Automatic sensitive info masking (customizable fields)
232279
- Production allows only low-risk operations by default
280+
- **Auto-enable database isolation in production**
233281

234282
---
235283

@@ -261,6 +309,18 @@ A: 设置SENSITIVE_INFO_FIELDS,如SENSITIVE_INFO_FIELDS=password,token
261309
Q: How to customize sensitive fields?
262310
A: Set SENSITIVE_INFO_FIELDS, e.g. SENSITIVE_INFO_FIELDS=password,token
263311

312+
### Q: 如何启用数据库隔离?
313+
A: 设置ENABLE_DATABASE_ISOLATION=true和DATABASE_ACCESS_LEVEL=strict,或使用ENV_TYPE=production自动启用。
314+
315+
Q: How to enable database isolation?
316+
A: Set ENABLE_DATABASE_ISOLATION=true and DATABASE_ACCESS_LEVEL=strict, or use ENV_TYPE=production for auto-enable.
317+
318+
### Q: 数据库隔离后无法查询系统表?
319+
A: strict模式禁止系统表访问,可改为restricted模式,或检查是否确实需要系统表访问权限。
320+
321+
Q: Cannot query system tables after enabling database isolation?
322+
A: strict mode blocks system table access. Use restricted mode or verify if system table access is actually needed.
323+
264324
### Q: limit参数报错?
265325
A: limit必须为非负整数。
266326

example.env

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ BLOCKED_PATTERNS=
4646
# 是否启用查询安全检查
4747
ENABLE_QUERY_CHECK=true
4848

49+
# 数据库隔离配置
50+
# 是否启用数据库隔离(防止跨数据库访问)
51+
ENABLE_DATABASE_ISOLATION=false
52+
# 数据库访问级别: strict(严格), restricted(限制), permissive(宽松)
53+
# - strict: 只能访问指定的数据库
54+
# - restricted: 可以访问指定数据库和系统库(information_schema, mysql等)
55+
# - permissive: 可以访问所有数据库(默认)
56+
# 注意:生产环境(ENV_TYPE=production)会自动启用数据库隔离并设为restricted模式
57+
DATABASE_ACCESS_LEVEL=permissive
58+
4959
# 日志配置
5060
# DEBUG, INFO, WARNING, ERROR, CRITICAL
5161
LOG_LEVEL=DEBUG

src/config.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ class SecurityConfig:
109109

110110
# 查询检查
111111
ENABLE_QUERY_CHECK = os.getenv('ENABLE_QUERY_CHECK', 'true').lower() in ('true', 'yes', '1')
112+
113+
# 数据库隔离配置
114+
ENABLE_DATABASE_ISOLATION = os.getenv('ENABLE_DATABASE_ISOLATION', 'false').lower() in ('true', 'yes', '1')
115+
DATABASE_ACCESS_LEVEL = os.getenv('DATABASE_ACCESS_LEVEL', 'permissive').lower()
116+
117+
# 生产环境强制数据库隔离
118+
if ENV_TYPE == EnvironmentType.PRODUCTION and not os.getenv('DATABASE_ACCESS_LEVEL'):
119+
DATABASE_ACCESS_LEVEL = 'restricted' # 生产环境默认使用限制模式
120+
ENABLE_DATABASE_ISOLATION = True
121+
logger = __import__('logging').getLogger("mysql_server")
122+
logger.info("生产环境自动启用数据库隔离,访问级别设为 restricted")
112123

113124
# SQL操作配置
114125
class SQLConfig:
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
"""
2+
数据库范围检查器
3+
用于检测和限制SQL查询中的跨数据库访问
4+
"""
5+
import re
6+
import logging
7+
from typing import Set, Optional, List, Tuple
8+
from enum import Enum
9+
10+
logger = logging.getLogger("mysql_server")
11+
12+
class DatabaseAccessLevel(Enum):
13+
"""数据库访问级别"""
14+
STRICT = "strict" # 严格模式:只能访问指定数据库
15+
RESTRICTED = "restricted" # 限制模式:允许访问指定数据库和系统库
16+
PERMISSIVE = "permissive" # 宽松模式:允许访问所有数据库(默认)
17+
18+
class DatabaseScopeViolation(Exception):
19+
"""数据库范围违规异常"""
20+
pass
21+
22+
class DatabaseScopeChecker:
23+
"""数据库范围检查器"""
24+
25+
# 系统数据库列表
26+
SYSTEM_DATABASES = {
27+
'information_schema',
28+
'mysql',
29+
'performance_schema',
30+
'sys'
31+
}
32+
33+
# 跨数据库查询模式
34+
CROSS_DB_PATTERNS = [
35+
# database.table 格式
36+
r'\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b',
37+
# SHOW TABLES FROM database
38+
r'\bSHOW\s+(?:FULL\s+)?TABLES\s+FROM\s+([a-zA-Z_][a-zA-Z0-9_]*)\b',
39+
# USE database
40+
r'\bUSE\s+([a-zA-Z_][a-zA-Z0-9_]*)\b',
41+
# SELECT ... FROM database.table
42+
r'\bFROM\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b',
43+
# JOIN database.table
44+
r'\bJOIN\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b',
45+
# INSERT INTO database.table
46+
r'\bINTO\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b',
47+
# UPDATE database.table
48+
r'\bUPDATE\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b',
49+
# DELETE FROM database.table
50+
r'\bDELETE\s+FROM\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b',
51+
]
52+
53+
def __init__(self, allowed_database: Optional[str] = None,
54+
access_level: DatabaseAccessLevel = DatabaseAccessLevel.PERMISSIVE):
55+
"""
56+
初始化数据库范围检查器
57+
58+
Args:
59+
allowed_database: 允许访问的数据库名称
60+
access_level: 访问级别
61+
"""
62+
self.allowed_database = allowed_database
63+
self.access_level = access_level
64+
self.is_enabled = allowed_database is not None and access_level != DatabaseAccessLevel.PERMISSIVE
65+
66+
logger.debug(f"数据库范围检查器初始化: 允许数据库={allowed_database}, 访问级别={access_level.value}, 启用={self.is_enabled}")
67+
68+
def check_query(self, sql_query: str) -> Tuple[bool, List[str]]:
69+
"""
70+
检查SQL查询是否违反数据库范围限制
71+
72+
Args:
73+
sql_query: SQL查询语句
74+
75+
Returns:
76+
(是否允许, 违规详情列表)
77+
"""
78+
if not self.is_enabled:
79+
return True, []
80+
81+
violations = []
82+
83+
# 提取查询中涉及的数据库
84+
referenced_databases = self._extract_databases(sql_query)
85+
86+
for db_name in referenced_databases:
87+
if not self._is_database_allowed(db_name):
88+
violations.append(f"不允许访问数据库: {db_name}")
89+
90+
# 检查特殊查询类型
91+
special_violations = self._check_special_queries(sql_query)
92+
violations.extend(special_violations)
93+
94+
is_allowed = len(violations) == 0
95+
96+
if violations:
97+
logger.warning(f"数据库范围检查失败: {violations}")
98+
99+
return is_allowed, violations
100+
101+
def _extract_databases(self, sql_query: str) -> Set[str]:
102+
"""提取SQL查询中涉及的数据库名称"""
103+
databases = set()
104+
105+
# 标准化SQL(转换为大写,去除多余空格)
106+
normalized_sql = re.sub(r'\s+', ' ', sql_query.upper().strip())
107+
108+
for pattern in self.CROSS_DB_PATTERNS:
109+
matches = re.finditer(pattern, normalized_sql, re.IGNORECASE)
110+
for match in matches:
111+
# 第一个捕获组通常是数据库名
112+
if match.groups():
113+
db_name = match.group(1).lower()
114+
# 过滤掉非数据库名的匹配(如函数名等)
115+
if self._is_valid_database_name(db_name):
116+
databases.add(db_name)
117+
118+
return databases
119+
120+
def _is_valid_database_name(self, name: str) -> bool:
121+
"""检查是否是有效的数据库名称"""
122+
# 数据库名称规则:字母、数字、下划线,不能以数字开头
123+
return bool(re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name))
124+
125+
def _is_database_allowed(self, db_name: str) -> bool:
126+
"""检查数据库是否被允许访问"""
127+
db_name_lower = db_name.lower()
128+
129+
# 检查是否是允许的主数据库
130+
if self.allowed_database and db_name_lower == self.allowed_database.lower():
131+
return True
132+
133+
# 根据访问级别决定是否允许系统数据库
134+
if self.access_level == DatabaseAccessLevel.RESTRICTED:
135+
if db_name_lower in self.SYSTEM_DATABASES:
136+
return True
137+
138+
return False
139+
140+
def _check_special_queries(self, sql_query: str) -> List[str]:
141+
"""检查特殊类型的查询"""
142+
violations = []
143+
normalized_sql = sql_query.upper().strip()
144+
145+
# 检查SHOW DATABASES查询
146+
if re.search(r'\bSHOW\s+DATABASES\b', normalized_sql):
147+
if self.access_level == DatabaseAccessLevel.STRICT:
148+
violations.append("严格模式下不允许执行 SHOW DATABASES")
149+
150+
# 检查USE语句
151+
if re.search(r'\bUSE\s+', normalized_sql):
152+
violations.append("不允许使用 USE 语句切换数据库")
153+
154+
# 检查系统表访问
155+
system_table_patterns = [
156+
r'\bmysql\.user\b',
157+
r'\bmysql\.db\b',
158+
r'\binformation_schema\.',
159+
r'\bperformance_schema\.',
160+
r'\bsys\.'
161+
]
162+
163+
for pattern in system_table_patterns:
164+
if re.search(pattern, normalized_sql, re.IGNORECASE):
165+
if self.access_level == DatabaseAccessLevel.STRICT:
166+
violations.append(f"严格模式下不允许访问系统表")
167+
break
168+
169+
return violations
170+
171+
def get_allowed_databases(self) -> Set[str]:
172+
"""获取允许访问的数据库列表"""
173+
allowed = set()
174+
175+
if self.allowed_database:
176+
allowed.add(self.allowed_database.lower())
177+
178+
if self.access_level == DatabaseAccessLevel.RESTRICTED:
179+
allowed.update(self.SYSTEM_DATABASES)
180+
181+
return allowed
182+
183+
def is_cross_database_query(self, sql_query: str) -> bool:
184+
"""检查是否是跨数据库查询"""
185+
referenced_dbs = self._extract_databases(sql_query)
186+
return len(referenced_dbs) > 0
187+
188+
# 便捷函数
189+
def create_database_checker(allowed_database: Optional[str] = None,
190+
access_level: str = "permissive") -> DatabaseScopeChecker:
191+
"""
192+
创建数据库范围检查器的便捷函数
193+
194+
Args:
195+
allowed_database: 允许访问的数据库名称
196+
access_level: 访问级别字符串 (strict/restricted/permissive)
197+
198+
Returns:
199+
DatabaseScopeChecker实例
200+
"""
201+
try:
202+
level = DatabaseAccessLevel(access_level.lower())
203+
except ValueError:
204+
logger.warning(f"无效的访问级别: {access_level},使用默认的 permissive")
205+
level = DatabaseAccessLevel.PERMISSIVE
206+
207+
return DatabaseScopeChecker(allowed_database, level)

0 commit comments

Comments
 (0)