SSH 隧道:安全连接远程数据库的秘密通道

为什么数据库只监听 127.0.0.1?如何安全地远程访问?

December 27, 2024·16 min read·Yimin
#SSH#数据库#安全#端口转发#运维

数据库只监听 127.0.0.1,但你需要从本地连接它。SSH 隧道就是那条安全的秘密通道。

🎯 为什么需要 SSH 隧道?

真实场景:安全 vs 便利的矛盾

你有一台云服务器,上面运行着 PostgreSQL 和 Redis:

┌─────────────────────────────────────────────────────────────────┐
│                        云服务器                                  │
│                                                                 │
│   PostgreSQL:  127.0.0.1:5432  ← 只监听本地                     │
│   Redis:       127.0.0.1:6379  ← 只监听本地                     │
│                                                                 │
│   为什么这样配置?                                               │
│   ┌───────────────────────────────────────────────────────────┐ │
│   │ 安全!不暴露到公网,防止被扫描和攻击                        │ │
│   └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

问题:你想用 Navicat 连接数据库,怎么办?


三种方案对比

┌─────────────────────────────────────────────────────────────────┐
│                     方案一:暴露端口到公网                        │
│                                                                 │
│   配置: PostgreSQL 监听 0.0.0.0:5432                            │
│                                                                 │
│   互联网 ────────────────→ 服务器:5432                          │
│   任何人都能访问!                                               │
│                                                                 │
│   ❌ 极度危险                                                   │
│   ❌ 会被扫描器发现                                             │
│   ❌ 暴力破解风险                                               │
│   ❌ 可能泄露数据                                               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                     方案二:防火墙白名单                          │
│                                                                 │
│   配置: 只允许特定 IP 访问 5432 端口                             │
│                                                                 │
│   你的 IP ──────────────→ 服务器:5432 ✅                        │
│   其他 IP ──────────────→ 服务器:5432 ❌                        │
│                                                                 │
│   ⚠️ 稍微安全                                                  │
│   ❌ 家庭/办公网络 IP 经常变化                                  │
│   ❌ 需要频繁更新白名单                                         │
│   ❌ 数据传输未加密                                             │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                     方案三:SSH 隧道 ⭐                          │
│                                                                 │
│   配置: 数据库只监听 127.0.0.1,通过 SSH 访问                    │
│                                                                 │
│   你的电脑 ───[SSH 加密隧道]───→ 服务器 ───→ 127.0.0.1:5432     │
│                                                                 │
│   ✅ 最安全                                                     │
│   ✅ 数据全程加密                                               │
│   ✅ 复用 SSH 认证(密钥/密码)                                  │
│   ✅ 不暴露数据库端口                                           │
└─────────────────────────────────────────────────────────────────┘

🔍 SSH 隧道的工作原理

什么是 SSH 隧道?

SSH 隧道(SSH Tunnel)是一种通过 SSH 连接转发网络流量的技术。

┌─────────────────────────────────────────────────────────────────┐
│                      SSH 隧道原理图                              │
│                                                                 │
│  你的电脑                                    云服务器            │
│  ┌────────────────┐                    ┌────────────────┐       │
│  │                │                    │                │       │
│  │  Navicat       │                    │   PostgreSQL   │       │
│  │  连接          │                    │   127.0.0.1    │       │
│  │  127.0.0.1     │    SSH 加密隧道    │   :5432        │       │
│  │  :15432        │ =================> │                │       │
│  │       ↓        │                    │       ↑        │       │
│  │  ┌──────────┐  │                    │  ┌──────────┐  │       │
│  │  │ SSH 客户 │  │    TCP:22 加密     │  │ SSH 服务 │  │       │
│  │  │ 端       │ ←───────────────────→ │  │ 端       │  │       │
│  │  └──────────┘  │                    │  └──────────┘  │       │
│  │                │                    │                │       │
│  └────────────────┘                    └────────────────┘       │
│                                                                 │
│  数据流向:                                                       │
│  Navicat → 本地 15432 → SSH 加密 → 服务器 SSH → 服务器 5432     │
└─────────────────────────────────────────────────────────────────┘

核心概念:本地端口转发

SSH 隧道的核心是本地端口转发(Local Port Forwarding)

┌─────────────────────────────────────────────────────────────────┐
│                    本地端口转发命令                               │
│                                                                 │
│  ssh -L 15432:127.0.0.1:5432 user@server                        │
│       ↑  ↑      ↑        ↑                                      │
│       │  │      │        │                                      │
│       │  │      │        └── 远程服务端口                        │
│       │  │      └─────────── 远程服务地址(服务器视角)           │
│       │  └────────────────── 本地监听端口                        │
│       └───────────────────── 标志:Local 本地转发                │
│                                                                 │
│  效果:                                                          │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ 访问本地 127.0.0.1:15432                                   │  │
│  │    ↓                                                      │  │
│  │ 流量被 SSH 加密,发送到服务器                               │  │
│  │    ↓                                                      │  │
│  │ 服务器将流量转发到 127.0.0.1:5432(服务器本地的 PG)         │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

为什么远程地址是 127.0.0.1?

┌─────────────────────────────────────────────────────────────────┐
│                   关键理解:视角问题                              │
│                                                                 │
│  命令: ssh -L 15432:127.0.0.1:5432 user@server                  │
│                     └─────────────┘                             │
│                     这个地址是从服务器的视角看的!                 │
│                                                                 │
│  服务器视角:                                                     │
│  ┌─────────────────────────────────────────────┐                │
│  │                服务器                        │                │
│  │                                             │                │
│  │   127.0.0.1:5432 → PostgreSQL 在监听这里    │                │
│  │                                             │                │
│  │   SSH 收到转发请求后,                       │                │
│  │   连接的是服务器本地的 127.0.0.1:5432       │                │
│  └─────────────────────────────────────────────┘                │
│                                                                 │
│  不是你电脑的 127.0.0.1,是服务器的 127.0.0.1!                  │
└─────────────────────────────────────────────────────────────────┘

🎨 Navicat 的 SSH 隧道实现

Navicat 帮你做了什么?

当你在 Navicat 中配置 SSH 隧道时,Navicat 自动完成了所有底层操作:

┌─────────────────────────────────────────────────────────────────┐
│                    Navicat 配置界面                              │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ General 标签页                                           │    │
│  │                                                         │    │
│  │   Host: 127.0.0.1        ← 目标数据库地址(服务器视角)   │    │
│  │   Port: 5432             ← 目标数据库端口                │    │
│  │   Password: ******       ← 数据库密码                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ SSH 标签页                                               │    │
│  │                                                         │    │
│  │   ☑ Use SSH Tunnel       ← 启用 SSH 隧道                │    │
│  │   Host: your-server-ip  ← SSH 服务器 IP                │    │
│  │   Port: 22               ← SSH 端口                     │    │
│  │   User: root             ← SSH 用户名                   │    │
│  │   Auth: Private Key      ← 认证方式                     │    │
│  │   Key: ~/.ssh/id_ed25519   ← 私钥文件路径                 │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

连接流程详解

┌─────────────────────────────────────────────────────────────────┐
│                     连接流程(7 步)                             │
│                                                                 │
│  第 1 步: 建立 SSH 连接                                         │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ Navicat → SSH 服务器 (your-server-ip:22)                 │  │
│  │ 使用私钥 ~/.ssh/id_ed25519 进行认证                          │  │
│  └───────────────────────────────────────────────────────────┘  │
│                           ↓                                     │
│  第 2 步: 请求端口转发                                          │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ Navicat 告诉 SSH:请把流量转发到 127.0.0.1:5432           │  │
│  └───────────────────────────────────────────────────────────┘  │
│                           ↓                                     │
│  第 3 步: 本地监听                                              │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ Navicat 在本地开启一个临时端口(如 127.0.0.1:随机端口)     │  │
│  └───────────────────────────────────────────────────────────┘  │
│                           ↓                                     │
│  第 4 步: 数据库连接                                            │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ Navicat 的数据库客户端连接到本地临时端口                    │  │
│  └───────────────────────────────────────────────────────────┘  │
│                           ↓                                     │
│  第 5 步: 流量转发                                              │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ 本地端口的流量 → SSH 加密 → 服务器 → 127.0.0.1:5432       │  │
│  └───────────────────────────────────────────────────────────┘  │
│                           ↓                                     │
│  第 6 步: 数据库响应                                            │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ PostgreSQL 响应 → SSH 加密返回 → Navicat 收到数据          │  │
│  └───────────────────────────────────────────────────────────┘  │
│                           ↓                                     │
│  第 7 步: 连接成功 ✅                                           │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ 你可以愉快地查询数据库了!                                  │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

🔐 安全性分析

为什么 SSH 隧道更安全?

┌─────────────────────────────────────────────────────────────────┐
│                     安全性对比                                   │
│                                                                 │
│  直接暴露数据库端口:                                             │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                                                           │  │
│  │  互联网 ─────────────────────────────→ PostgreSQL:5432    │  │
│  │                                                           │  │
│  │  攻击面:                                                  │  │
│  │    • 端口扫描器能发现                                     │  │
│  │    • 可被暴力破解密码                                     │  │
│  │    • 数据明文传输(除非配置 SSL)                          │  │
│  │    • 0-day 漏洞可直接利用                                 │  │
│  │                                                           │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                 │
│  SSH 隧道:                                                      │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                                                           │  │
│  │  互联网 ────→ SSH:22 ────→ 127.0.0.1:5432                │  │
│  │              (唯一入口)     (不对外暴露)                   │  │
│  │                                                           │  │
│  │  安全保障:                                                │  │
│  │    ✅ 只暴露 SSH 端口(22)                               │  │
│  │    ✅ SSH 协议成熟,安全性高                              │  │
│  │    ✅ 支持密钥认证(无密码)                              │  │
│  │    ✅ 所有流量加密传输                                    │  │
│  │    ✅ 数据库漏洞无法从外部利用                            │  │
│  │                                                           │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

SSH 密钥认证 vs 密码认证

┌─────────────────────────────────────────────────────────────────┐
│                     认证方式对比                                 │
│                                                                 │
│  密码认证:                                                      │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  用户名 + 密码 → 服务器验证                                │  │
│  │                                                           │  │
│  │  风险:                                                    │  │
│  │    • 密码可能被暴力破解                                   │  │
│  │    • 密码可能泄露(钓鱼、键盘记录)                        │  │
│  │    • 传输过程中虽然加密,但仍有风险                        │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                 │
│  密钥认证 ⭐:                                                   │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                                                           │  │
│  │  私钥(你电脑上)          公钥(服务器上)                 │  │
│  │  ┌──────────────┐         ┌──────────────┐               │  │
│  │  │ id_ed25519   │ ←─配对─→ │ authorized   │               │  │
│  │  │ (绝不外传)   │         │ _keys        │               │  │
│  │  └──────────────┘         └──────────────┘               │  │
│  │                                                           │  │
│  │  认证流程:                                                │  │
│  │    1. 服务器发送随机挑战                                  │  │
│  │    2. 客户端用私钥签名                                    │  │
│  │    3. 服务器用公钥验证签名                                │  │
│  │    4. 私钥从不离开你的电脑!                              │  │
│  │                                                           │  │
│  │  优势:                                                    │  │
│  │    ✅ 无法暴力破解                                        │  │
│  │    ✅ 私钥不传输                                          │  │
│  │    ✅ 可禁用密码登录,只允许密钥                          │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

🛠️ 命令行方式(进阶)

手动建立 SSH 隧道

如果不用 Navicat,你也可以手动建立隧道:

# PostgreSQL 隧道
ssh -L 15432:127.0.0.1:5432 -N -f root@your-server-ip -i ~/.ssh/id_ed25519

# Redis 隧道  
ssh -L 16379:127.0.0.1:6379 -N -f root@your-server-ip -i ~/.ssh/id_ed25519

参数解释:

┌─────────────────────────────────────────────────────────────────┐
│                     SSH 隧道参数                                 │
│                                                                 │
│  -L 15432:127.0.0.1:5432                                        │
│     │     │           │                                         │
│     │     │           └── 远程端口                               │
│     │     └────────────── 远程地址(服务器视角的 localhost)     │
│     └──────────────────── 本地端口(你电脑上监听的端口)         │
│                                                                 │
│  -N    不执行远程命令,只做端口转发                              │
│  -f    后台运行                                                  │
│  -i    指定私钥文件                                              │
│                                                                 │
│  建立后:                                                        │
│    连接 127.0.0.1:15432 → 等于连接服务器的 PostgreSQL            │
│    连接 127.0.0.1:16379 → 等于连接服务器的 Redis                 │
└─────────────────────────────────────────────────────────────────┘

使用示例

# 1. 建立隧道
ssh -L 15432:127.0.0.1:5432 -N -f root@your-server-ip -i ~/.ssh/id_ed25519

# 2. 用 psql 连接(就像连本地数据库一样)
psql -h 127.0.0.1 -p 15432 -U postgres -d postgres

# 3. 或用 redis-cli 连接 Redis
ssh -L 16379:127.0.0.1:6379 -N -f root@your-server-ip -i ~/.ssh/id_ed25519
redis-cli -h 127.0.0.1 -p 16379 -a "your_password"

🚨 常见问题与解决方案

1. 连接失败:Failed to resolve hostname

┌─────────────────────────────────────────────────────────────────┐
│  错误: Failed to resolve hostname xxx (nodename not known)       │
│                                                                 │
│  原因:                                                          │
│    • 代理软件(ClashX、Surge)劫持了 DNS 解析                    │
│    • 网络配置问题                                               │
│                                                                 │
│  解决:                                                          │
│    1. 检查代理软件设置,排除 SSH 流量                            │
│    2. 使用 IP 地址而非域名                                      │
│    3. 检查 /etc/hosts 配置                                      │
└─────────────────────────────────────────────────────────────────┘

2. 连接失败:Connection refused

┌─────────────────────────────────────────────────────────────────┐
│  错误: Connection refused                                        │
│                                                                 │
│  可能原因:                                                       │
│    • 数据库服务未运行                                           │
│    • 端口配置错误                                               │
│    • 数据库未监听指定地址                                       │
│                                                                 │
│  排查命令:                                                       │
│    # SSH 到服务器后检查                                         │
│    docker ps | grep postgres                                    │
│    docker ps | grep redis                                       │
│    netstat -tlnp | grep 5432                                    │
└─────────────────────────────────────────────────────────────────┘

3. 认证失败:Permission denied

┌─────────────────────────────────────────────────────────────────┐
│  错误: Permission denied (publickey)                             │
│                                                                 │
│  可能原因:                                                       │
│    • 私钥路径错误                                               │
│    • 私钥文件权限不正确                                         │
│    • 服务器上没有对应的公钥                                     │
│                                                                 │
│  排查步骤:                                                       │
│    1. 确认私钥路径正确                                          │
│    2. 检查权限: chmod 600 ~/.ssh/id_ed25519                       │
│    3. 测试 SSH: ssh -i ~/.ssh/id_ed25519 root@your-server-ip     │
└─────────────────────────────────────────────────────────────────┘

4. 连接超时

┌─────────────────────────────────────────────────────────────────┐
│  错误: Connection timed out                                      │
│                                                                 │
│  可能原因:                                                       │
│    • 服务器防火墙阻止 22 端口                                   │
│    • 网络不通                                                   │
│    • SSH 服务未运行                                             │
│                                                                 │
│  排查:                                                          │
│    ping your-server-ip          # 检查网络连通性               │
│    telnet your-server-ip 22     # 检查 SSH 端口是否开放        │
└─────────────────────────────────────────────────────────────────┘

🎯 最佳实践

1. 数据库安全配置

┌─────────────────────────────────────────────────────────────────┐
│  推荐配置:                                                       │
│                                                                 │
│  PostgreSQL (postgresql.conf):                                  │
│    listen_addresses = '127.0.0.1'   # 只监听本地                │
│                                                                 │
│  Redis (redis.conf):                                            │
│    bind 127.0.0.1                   # 只监听本地                │
│    requirepass <strong_password>    # 设置密码                  │
│                                                                 │
│  Docker Compose:                                                │
│    ports:                                                       │
│      - "127.0.0.1:5432:5432"       # 只绑定到 localhost         │
└─────────────────────────────────────────────────────────────────┘

2. SSH 安全加固

┌─────────────────────────────────────────────────────────────────┐
│  /etc/ssh/sshd_config 推荐配置:                                  │
│                                                                 │
│    PermitRootLogin prohibit-password  # 禁止 root 密码登录      │
│    PasswordAuthentication no          # 禁止密码认证            │
│    PubkeyAuthentication yes           # 启用密钥认证            │
│    Port 22                            # 可改为非标准端口         │
│                                                                 │
│  效果: 只能通过密钥登录,极大提升安全性                          │
└─────────────────────────────────────────────────────────────────┘

3. 使用 SSH Config 简化连接

# ~/.ssh/config

Host myserver
    HostName your-server-ip
    User root
    IdentityFile ~/.ssh/id_ed25519
    
    # 可选:自动建立隧道
    LocalForward 15432 127.0.0.1:5432
    LocalForward 16379 127.0.0.1:6379

配置后只需:ssh myserver 即可自动建立所有隧道。


🚀 总结

核心价值

没有 SSH 隧道有 SSH 隧道
🔓 数据库端口暴露🔐 数据库端口隐藏
😰 可能被扫描攻击😊 只暴露 SSH 端口
📡 数据明文传输🔒 全程加密传输
🔑 需要数据库认证🔑 SSH + 数据库双重认证

工作原理图解

                你的电脑                        云服务器
            ┌─────────────┐                ┌─────────────┐
            │             │                │             │
            │  Navicat    │                │  PostgreSQL │
            │  ↓          │                │  127.0.0.1  │
            │  本地端口   │                │  :5432      │
            │  ↓          │   SSH 隧道     │  ↑          │
            │  SSH 客户端 │ =============> │  SSH 服务端 │
            │             │   (加密)       │             │
            └─────────────┘                └─────────────┘
            
数据流: Navicat → 本地端口 → SSH加密 → 服务器SSH → 本地DB

记忆口诀

🔐 数据库只听 127,外面根本进不来
🚇 SSH 隧道是通道,加密传输最安全
🔑 密钥认证更可靠,私钥永远不外传
📍 记住视角很重要,127 是服务器的

SSH 隧道是连接远程数据库的最佳实践。

它让你既能方便地管理数据库,又不牺牲安全性。现在,你的数据库连接安全吗? 🛡️

SSH 隧道:安全连接远程数据库的秘密通道