版本: v1.0.1
更新日期: 2025-12-29
实现方式: ssh2库(支持密码和私钥认证)
SSH隧道(SSH Tunneling)也称为SSH端口转发,是一种通过SSH协议在本地和远程服务器之间建立加密通道的技术。
-
连接防火墙后的数据库
- 数据库位于内网,无法直接访问
- 通过跳板机(Bastion Host)访问内网MongoDB
-
加密不安全的网络连接
- 在公网环境下安全访问MongoDB
- 防止数据传输被窃听
-
统一安全访问入口
- 所有数据库访问都通过SSH隧道
- 集中管理访问权限
你的应用 → SSH隧道(本地端口) → SSH服务器 → 内网MongoDB
(本地) (加密传输) (跳板机) (目标数据库)
monSQLize v1.0.1+ 已包含ssh2依赖,无需额外安装。
const MonSQLize = require('monsqlize');
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'mydb',
config: {
// SSH隧道配置
ssh: {
host: 'bastion.example.com',
port: 22,
username: 'deploy',
password: 'your-password', // SSH密码
},
// MongoDB连接配置(自动从URI解析remoteHost和remotePort)
uri: 'mongodb://mongouser:mongopass@internal-mongo:27017/mydb'
}
});
await msq.connect(); // 自动建立SSH隧道
// ... 使用MongoDB
await msq.close(); // 自动关闭SSH隧道const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'mydb',
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa', // 私钥路径
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://mongouser:mongopass@internal-mongo:27017/mydb'
}
});| 配置项 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
host |
string | ✅ 是 | - | SSH服务器地址 |
port |
number | ❌ 否 | 22 | SSH端口 |
username |
string | ✅ 是 | - | SSH用户名 |
password |
string | - | SSH密码(密码认证) | |
privateKey |
string | - | 私钥内容(私钥认证) | |
privateKeyPath |
string | - | 私钥文件路径(私钥认证) | |
passphrase |
string | ❌ 否 | - | 私钥密码(如果私钥有密码保护) |
localPort |
number | ❌ 否 | 随机 | 本地监听端口 |
readyTimeout |
number | ❌ 否 | 20000 | SSH连接超时(毫秒) |
keepaliveInterval |
number | ❌ 否 | 30000 | 心跳间隔(毫秒) |
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
uri |
string | ✅ 是 | MongoDB连接URI(内网地址) |
remoteHost |
string | MongoDB服务器地址(可从URI自动解析) | |
remotePort |
number | MongoDB端口(可从URI自动解析) | |
options |
object | ❌ 否 | MongoDB连接选项 |
自动解析规则:
- ✅ 推荐:只配置
uri,系统会自动从URI中解析remoteHost和remotePort ⚠️ 特殊情况:如果URI中的地址与实际MongoDB服务器地址不同,才需要显式指定
示例对比:
// ✅ 推荐:自动解析(99%的场景)
config: {
ssh: { host: 'bastion', username: 'user', password: 'pass' },
uri: 'mongodb://user:pass@internal-mongo:27017/mydb'
// remoteHost和remotePort自动从URI解析为:internal-mongo:27017
}
// ⚠️ 显式指定(特殊场景:URI与实际地址不同)
config: {
ssh: { host: 'bastion', username: 'user', password: 'pass' },
uri: 'mongodb://user:pass@loadbalancer:27017/mydb', // 使用负载均衡地址
remoteHost: 'actual-mongo-server', // 实际MongoDB服务器
remotePort: 27017
}const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
password: 'your-password',
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://mongouser:mongopass@internal-mongo:27017/production'
}
});
await msq.connect();
const users = msq.collection('users');
const count = await users.count({});
console.log('用户总数:', count);
await msq.close();const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: 'bastion.example.com',
port: 22,
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa', // 支持 ~ 符号
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://mongouser:mongopass@10.0.1.100:27017/production'
}
});const fs = require('fs');
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
privateKey: fs.readFileSync('/path/to/id_rsa', 'utf8'), // 直接传私钥内容
},
uri: 'mongodb://mongouser:mongopass@internal-mongo:27017/production'
}
});const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa',
passphrase: 'your-key-password', // 私钥密码
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://mongouser:mongopass@internal-mongo:27017/production'
}
});const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa',
localPort: 27018, // 固定本地端口
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://mongouser:mongopass@internal-mongo:27017/production'
}
});
// MongoDB将通过 localhost:27018 连接const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: 'bastion.example.com',
port: 2222, // 自定义SSH端口
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa',
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://mongouser:mongopass@internal-mongo:27017/production'
}
});const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa',
readyTimeout: 30000, // SSH连接超时30秒
keepaliveInterval: 60000, // 心跳间隔60秒
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://mongouser:mongopass@internal-mongo:27017/production',
options: {
serverSelectionTimeoutMS: 10000, // MongoDB选择服务器超时
connectTimeoutMS: 10000, // MongoDB连接超时
}
}
});错误信息:
Error: Timed out while waiting for handshake
可能原因:
- SSH服务器地址错误或无法访问
- SSH端口被防火墙阻止
- 网络不稳定
解决方案:
// 1. 检查SSH服务器是否可访问
// 在终端运行:ssh user@host -p port
// 2. 增加超时时间
config: {
ssh: {
// ... 其他配置
readyTimeout: 60000, // 增加到60秒
}
}错误信息:
Error: All configured authentication methods failed
可能原因:
- 用户名或密码错误
- 私钥路径错误或权限不正确
- SSH服务器不允许该认证方式
解决方案:
# 1. 测试SSH登录
ssh user@host -p port
# 2. 检查私钥权限
chmod 600 ~/.ssh/id_rsa
# 3. 使用密码认证测试
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
password: 'your-password', // 测试密码认证
}
}错误信息:
MongoServerSelectionError: Server selection timed out
可能原因:
- MongoDB地址或端口错误
- MongoDB认证失败
- MongoDB服务未运行
解决方案:
// 1. 确认MongoDB地址和端口
// 在SSH服务器上运行:nc -zv internal-mongo 27017
// 2. 检查MongoDB URI
config: {
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://user:pass@host:port/db?authSource=admin'
}
// 3. 添加directConnection选项(如果是副本集)
config: {
uri: 'mongodb://user:pass@host:port/db?directConnection=true',
options: {
directConnection: true,
}
}错误信息:
Error: listen EADDRINUSE: address already in use
解决方案:
// 指定不同的本地端口
config: {
ssh: {
// ... 其他配置
localPort: 27019, // 使用其他端口
}
}
// 或者不指定(自动分配随机端口)
config: {
ssh: {
// ... 其他配置
// localPort 不设置
}
}✅ 推荐:
ssh: {
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa',
}❌ 不推荐:
ssh: {
username: 'deploy',
password: 'plain-text-password', // 明文密码不安全
}const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: process.env.SSH_HOST,
username: process.env.SSH_USER,
privateKeyPath: process.env.SSH_KEY_PATH,
},
// 自动从URI解析remoteHost和remotePort
uri: process.env.MONGO_URI
}
});# 生成SSH密钥对
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_mongo
# 设置私钥权限
chmod 600 ~/.ssh/id_rsa_mongo
# 复制公钥到SSH服务器
ssh-copy-id -i ~/.ssh/id_rsa_mongo.pub user@bastion.example.comconst msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa',
readyTimeout: 30000,
keepaliveInterval: 30000,
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://user:pass@internal-mongo:27017/production',
options: {
maxPoolSize: 10, // 最大连接数
minPoolSize: 2, // 最小连接数
serverSelectionTimeoutMS: 10000,
connectTimeoutMS: 10000,
socketTimeoutMS: 45000,
}
}
});try {
await msq.connect();
// 使用MongoDB
} catch (err) {
if (err.message.includes('Authentication')) {
console.error('SSH认证失败,请检查用户名和密码/私钥');
} else if (err.message.includes('Timed out')) {
console.error('SSH连接超时,请检查网络和服务器状态');
} else if (err.message.includes('MongoServerSelectionError')) {
console.error('MongoDB连接失败,请检查MongoDB地址和认证信息');
} else {
console.error('未知错误:', err);
}
} finally {
await msq.close();
}const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'production',
logger: {
info: (msg, meta) => console.log('[INFO]', msg, meta),
warn: (msg, meta) => console.warn('[WARN]', msg, meta),
error: (msg, meta) => console.error('[ERROR]', msg, meta),
debug: (msg, meta) => console.debug('[DEBUG]', msg, meta),
},
config: {
ssh: {
host: 'bastion.example.com',
username: 'deploy',
privateKeyPath: '~/.ssh/id_rsa',
},
// 自动从URI解析remoteHost和remotePort
uri: 'mongodb://user:pass@internal-mongo:27017/production'
}
});
// 输出示例:
// [INFO] 🔐 Establishing SSH tunnel for MongoDB...
// [INFO] ✅ SSH connection established [MongoDB]
// [INFO] ✅ SSH tunnel ready [MongoDB] { localPort: 56789, remote: 'internal-mongo:27017' }A: 有轻微影响,但通常可忽略不计。
- SSH隧道建立时间:2-5秒
- 数据传输性能损失:<5%(加密开销)
- 适用于大部分应用场景
A: 支持。每个MonSQLize实例可以建立独立的SSH隧道。
const msq1 = new MonSQLize({ config: { ssh: { host: 'bastion1' } } });
const msq2 = new MonSQLize({ config: { ssh: { host: 'bastion2' } } });
await msq1.connect(); // SSH隧道1
await msq2.connect(); // SSH隧道2(独立)A: 不会。需要手动重连。
建议实现重连逻辑:
async function connectWithRetry(msq, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
await msq.connect();
return;
} catch (err) {
if (i === maxRetries - 1) throw err;
console.log(`连接失败,${3-i}秒后重试...`);
await new Promise(resolve => setTimeout(resolve, 3000));
}
}
}A: 不直接支持。需要在SSH服务器上配置ProxyJump。
SSH配置示例(~/.ssh/config):
Host final-server
HostName internal-mongo.local
User mongouser
ProxyJump bastion1,bastion2
A: 完全支持。monSQLize使用ssh2库(纯JavaScript实现),无需系统SSH客户端。
A: 私钥认证更安全且推荐。
| 认证方式 | 安全性 | 便利性 | 推荐场景 |
|---|---|---|---|
| 密码认证 | ✅ 高 | 开发/测试环境 | |
| 私钥认证 | ✅ 高 | 生产环境 |
文档版本: v1.3.2
最后更新: 2025-12-22