Metadata-Version: 2.4
Name: pyssh-tunnel
Version: 0.1.0
Summary: Manage multiple tunnels via YAML configuration files and flexibly select to run one or more tunnels through command-line arguments.
Project-URL: Homepage, https://gitee.com/thirsd/pyssh_tunnel
Project-URL: Repository, https://gitee.com/thirsd/pyssh_tunnel.git
Author-email: thirsd <thirsd@sina.com>
License-Expression: MIT
Requires-Python: >=3.10
Requires-Dist: paramiko<4.0.0,>=3.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: sshtunnel>=0.4.0
Description-Content-Type: text/markdown



# SSH 隧道管理器

基于 **Python 3.10** 实现，支持 **本地 (local)**、**远程 (remote)** 和 **动态 (dynamic)** 三种端口转发模式。通过 YAML 配置文件管理多个隧道，并通过命令行参数灵活选择运行一个或多个隧道。

## 一、SSH 的转发模式介绍

SSH有三种将目标主机的服务“映射”到本地访问的方式，分别是本地端口转发、远程端口转发和动态端口转发。三种方式对比如下：

| 类型             | 常用参数 | 核心功能                      | 典型应用场景                                       |
| :--------------- | :------- | :---------------------------- | :------------------------------------------------- |
| **本地端口转发** | `-L`     | 访问远程网络中的服务          | 想访问公司内网数据库、远程服务器上的Web服务        |
| **远程端口转发** | `-R`     | 将本地服务暴露给外部网络      | 把本地的个人网站、开发服务展示给外网同事看         |
| **动态端口转发** | `-D`     | 建立一个加密的SOCKS代理服务器 | 不固定转发端口，让浏览器或应用程序通过代理安全上网 |

### 1.1 Local本地转发- 将远程的服务映射本地

通过一个跳板机 (`your-jump-server.com`)，将内网中 `internal-server.local` 的 80 端口（Web 服务）映射到你本地电脑的 8080 端口。

#### 配置示例

```yaml
tunnels:
  - id: web_server                     # 唯一标识符，用于命令行选择（-c web_server）
    name: "本地访问内网Web服务"          # 人类可读的描述，仅用于展示
    type: local                        # 指定为本地端口转发模式（对应 ssh -L）
    
    # --- SSH 连接参数（跳板机）---
    ssh_host: "your-jump-server.com"   # 跳板机的域名或 IP（你能够直接 SSH 到的公网服务器）
    ssh_port: 22                       # 跳板机的 SSH 端口，默认 22
    ssh_user: "your_username"          # 登录跳板机的用户名
    ssh_pkey: "~/.ssh/id_rsa"          # 私钥路径（推荐），也可以改用 ssh_password 字段
    
    # --- 转发规则（本地 → 内网目标）---
    local_port: 8080                   # 你本地电脑上将要监听的端口（例如访问 http://localhost:8080）
    remote_host: "internal-server.local"  # 内网目标的地址（从跳板机的视角能访问到的内网主机名或 IP）
    remote_port: 80                    # 内网目标上真正提供服务的端口（这里是 HTTP 服务）
```

#### 工作机制图解

```
你的电脑 (本地)               跳板机 (SSH Server)              内网服务器
+-------------+              +------------------+           +-----------------+
|             | SSH 隧道加密  |                  |  明文转发  |                 |
| localhost:8080| ==========> | your-jump-server | ---------> | internal-server:80 |
|             | (动态端口)     |                  |   (端口80)  |                 |
+-------------+              +------------------+           +-----------------+
```

1. **建立 SSH 连接**：脚本（或 `ssh` 命令）用你的私钥登录跳板机。
2. **本地监听**：在你的电脑上启动一个 TCP 监听，端口为 `local_port` (8080)。
3. **流量转发**：当有程序访问 `localhost:8080` 时，SSH 客户端会通过加密隧道将数据发送给跳板机。
4. **跳板机请求**：跳板机解密后，作为客户端去连接 `remote_host:remote_port` (`internal-server.local:80`)。
5. **数据回传**：目标服务的响应原路返回。

#### 关键注意事项

| 字段                         | 注意点                                                       |
| ---------------------------- | ------------------------------------------------------------ |
| `ssh_host`                   | 必须是你能够 **直接 SSH 登录** 的机器，它通常拥有访问内网其他机器的网络权限。 |
| `remote_host`                | 这个地址是 **从跳板机角度** 能解析/访问的。可以是内网 IP（如 `192.168.1.100`），也可以是内网域名。 |
| `local_port`                 | 确保本地 8080 端口没有被其他程序占用（可用 `netstat -tulnp \| grep 8080` 检查）。 |
| `ssh_pkey` vs `ssh_password` | 强烈建议使用密钥认证（更安全）；如果必须用密码，在配置中写 `ssh_password: "your_password"`（注意密码会明文存在配置文件中）。 |

#### 实际效果对比

- **没有隧道时**：你无法直接访问 `http://internal-server.local`，因为它位于内网，不可路由。
- **有了这个隧道后**：你在浏览器打开 `http://localhost:8080`，就能看到内网 Web 服务的内容。对内网服务器来说，请求的来源是跳板机。



### 1.2 Remote 转发 — 将本地服务暴露到外网

**作用**：通过公网跳板机，将你**本地电脑上的服务**暴露给外部网络访问。类似 `ssh -R`。

#### 配置示例

```yaml
tunnels:
  - id: expose_api
    name: "暴露本地Flask服务给公网"
    type: remote
    ssh_host: "public-server.com"    # 公网跳板机（有固定IP）
    ssh_port: 22
    ssh_user: "deploy"
    ssh_pkey: "~/.ssh/id_rsa"
    remote_port: 2222                # 跳板机上对外监听的端口
    local_host: "localhost"          # 本地服务的地址（通常为 localhost 或 127.0.0.1）
    local_port: 5000                 # 本地服务的端口
    # remote_bind_address: "0.0.0.0" # 可选：让跳板机监听所有网络接口（需服务器 GatewayPorts yes）
```

#### 字段详解

| 字段                      | 含义                                                         |
| ------------------------- | ------------------------------------------------------------ |
| `type: remote`            | 指定为远程端口转发模式                                       |
| `ssh_host/port/user/pkey` | 连接公网跳板机的参数（同 local）                             |
| `remote_port`             | **跳板机上**将要监听的端口。外部用户访问 `跳板机IP:remote_port` 即连接到你的本地服务 |
| `local_host`              | 你本地服务的地址。通常是 `localhost` 或 `127.0.0.1`，也可以是同一局域网的其他机器 |
| `local_port`              | 你本地服务的端口（例如 Flask 的 5000）                       |
| `remote_bind_address`     | 可选。默认跳板机只监听 `127.0.0.1`（仅本机可访问）。设为 `"0.0.0.0"` 后，外网任何人都可访问。**需要跳板机 SSH 服务配置 `GatewayPorts yes`** |

#### 工作机制

```
你的电脑 (本地)               跳板机 (公网)                    外部用户
+-------------+              +------------------+            +-------------+
|             | SSH 隧道加密  |                  |  普通TCP    |             |
| localhost:5000| <========= | public-server:2222| <--------- | 任意客户端   |
| (Flask服务)  | (动态端口)   | (监听公网)         |            |             |
+-------------+              +------------------+            +-------------+
```

1. 你建立 SSH 连接到公网跳板机，并请求在跳板机上监听 `remote_port`。
2. 当外部用户访问 `跳板机IP:remote_port` 时，跳板机将连接通过 SSH 隧道**反向**转发到你本地的 `local_host:local_port`。
3. 你的本地服务处理请求，响应原路返回。

#### 典型使用场景

- **演示/调试**：把本地开发的网站临时给同事看，无需部署到服务器。
- **穿透 NAT**：你的电脑在公司内网或家庭宽带（无公网IP），通过一台有公网IP的云主机让外部访问本地服务。
- **远程访问内网设备**：本地有一个监控摄像头（RTSP 流），通过远程转发让外网观看。

#### 注意事项

1. **跳板机需允许 TCP 转发**：`/etc/ssh/sshd_config` 中确保 `AllowTcpForwarding yes`。
2. **监听地址问题**：默认 `remote_bind_address` 为空（或 `127.0.0.1`），意味着只有登录到跳板机本机才能访问 `remote_port`。如果要公网访问，必须：
   - 设置 `remote_bind_address: "0.0.0.0"`
   - 并在 `sshd_config` 中开启 `GatewayPorts yes`（重启 sshd）。
3. **防火墙**：跳板机的防火墙需放开 `remote_port` 的入站连接。
4. **安全性**：暴露的服务应具备认证机制，防止未授权访问。



### 1.3 Dynamic 转发— SOCKS5 代理

**作用**：在本地创建一个 SOCKS5 代理端口，所有支持 SOCKS 的应用可动态请求代理访问任意目标。类似 `ssh -D`。

#### 配置示例

```yaml
tunnels:
  - id: socks_proxy
    name: "SOCKS5代理"
    type: dynamic
    ssh_host: "my-server.com"
    ssh_port: 22
    ssh_user: "proxy_user"
    ssh_pkey: "~/.ssh/proxy_key"
    local_port: 1080                # 本地 SOCKS 代理端口
    # remote_bind_address: ""       # 动态转发无需此项
```

#### 字段详解

| 字段              | 含义                                                  |
| ----------------- | ----------------------------------------------------- |
| `type: dynamic`   | 指定为动态端口转发模式                                |
| `local_port`      | 你在**本地电脑**上开启的 SOCKS5 代理端口（例如 1080） |
| 其他 SSH 连接参数 | 同 local，用于登录跳板机                              |

#### 工作机制

```
你的电脑 (本地)                跳板机 (SSH Server)                 互联网/内网
+------------------------+    +------------------+            +-----------------+
| SOCKS5代理 localhost:1080| => | my-server.com     | ========> | 任意目标:任意端口 |
| (浏览器/curl配置代理)    |    | (解密请求，按需连接)|            | (根据应用请求)  |
+------------------------+    +------------------+            +-----------------+
```

1. SSH 客户端在本地 `localhost:1080` 启动一个 SOCKS5 代理服务器。
2. 应用程序（浏览器、curl、即时通讯工具等）配置使用该代理（`socks5://127.0.0.1:1080`）。
3. 应用程序发起连接时，会先通过 SOCKS 协议告诉代理“我要连接的主机和端口”。
4. SSH 客户端将这一请求加密后发给跳板机，由跳板机真正去连接目标服务器。
5. 跳板机返回的数据原路返回。

#### 典型使用场景

- **浏览器翻墙/访问内网**：配置浏览器 SOCKS 代理，即可访问跳板机所在网络内的任意网站（如公司内部 Wiki、GitLab）。
- **命令行工具**：使用 `proxychains` 或 `curl --socks5` 让命令行程序走代理。
- **临时调试**：一个代理端口可访问多种服务，无需逐个配置 local 转发。

#### 与 local 转发的本质区别

| 维度                   | local                            | dynamic                                     |
| ---------------------- | -------------------------------- | ------------------------------------------- |
| 目标指定               | 预先固定 `主机:端口`             | 动态由应用告知                              |
| 应用要求               | 无需特殊支持                     | **必须支持 SOCKS5** 或配合 proxychains 使用 |
| 一个端口能连多少个目标 | 1个                              | 无数个                                      |
| 典型用途               | 固定服务映射（数据库、内网站点） | 浏览网页、多种服务通吃                      |

#### 注意事项

1. **应用支持**：并非所有程序都能原生使用 SOCKS5。老旧的数据库客户端、部分命令行工具需要搭配 `proxychains` 或类似工具。
2. **DNS 解析**：默认 DNS 解析是在本地完成的，可能导致内网域名无法解析。可以配置远程 DNS（如 `ssh -D 1080 -o "ProxyCommand=none"` 或使用 `--socks5-hostname` 在 curl 中）。
3. **服务端无额外配置**：动态转发只需要服务端 `AllowTcpForwarding yes`（通常默认开启），无需 `GatewayPorts`。



## 二、配置说明

- **本地转发** (`type: local`)：将远程服务映射到本地端口，类似 `ssh -L`
- **远程转发** (`type: remote`)：将本地服务暴露到远程服务器，类似 `ssh -R`
- **动态转发** (`type: dynamic`)：创建 SOCKS5 代理，类似 `ssh -D`

> 远程转发如需监听所有网络接口，需在远程 SSH 服务器上设置 `GatewayPorts yes`，
> 并在配置中添加 `remote_bind_address: "0.0.0.0"`。

config.yaml示例：

```yaml
tunnels:
  - id: web_server
    name: "本地访问内网Web服务"
    type: local
    ssh_host: "your-jump-server.com"
    ssh_port: 22
    ssh_user: "your_username"
    ssh_pkey: "~/.ssh/id_rsa"      # 或使用密码
    local_port: 8080
    remote_host: "internal-server.local"
    remote_port: 80

  - id: expose_api
    name: "暴露本地Flask服务到公网"
    type: remote
    ssh_host: "public-server.com"
    ssh_port: 22
    ssh_user: "deploy"
    ssh_password: "your_password"
    remote_port: 2222
    local_host: "localhost"
    local_port: 5000
    # remote_bind_address: "0.0.0.0"   # 如需监听所有接口（需服务器GatewayPorts yes）

  - id: socks_proxy
    name: "SOCKS5代理"
    type: dynamic
    ssh_host: "my-server.com"
    ssh_port: 22
    ssh_user: "proxy_user"
    ssh_pkey: "~/.ssh/proxy_key"
    local_port: 1080
```



## 三、使用方法

​	根据实际环境修改 `config.yaml` 中的隧道配置。

### 3.1 从项目中运行

1. 列出所有可用的隧道：
   ```bash
   uv run pyssh_tunnel -l
   ```

2. 启动一个或多个隧道（通过 `-c` 指定 ID，可重复）：
   ```bash
   uv run pyssh_tunnel -c web_server -c socks_proxy
   ```

3. 按 `Ctrl+C` 停止所有隧道。

### 3.2 安装pyssh_tunnel

1. 使用`pip install pyssh_tunnel`

2. 使用命令行执行

   ```bash
   pyssh_tunnel -c web_server -c socks_proxy
   ```

## 四、命令行参数

- `-c, --config-id`：要启动的隧道 ID（可多次使用）
- `-l, --list`：列出所有隧道配置
- `--config-file`：指定配置文件路径（默认 `config.yaml`）
- `--log-file`：日志文件路径（默认输出到控制台）
- `--log-level`：日志级别（DEBUG/INFO/WARNING/ERROR）



```
$ pyssh_tunnel -h
usage: pyssh_tunnel [-h] [-c IDS] [-l] [--config-file CONFIG_FILE] [--log-file LOG_FILE]
                    [--log-level {DEBUG,INFO,WARNING,ERROR}]

SSH隧道管理器

options:
  -h, --help            show this help message and exit
  -c IDS, --config-id IDS
                        选择要启动的隧道ID (可多次使用)
  -l, --list            列出所有可用隧道配置
  --config-file CONFIG_FILE
                        配置文件路径 (默认: config.yaml)
  --log-file LOG_FILE   日志文件路径
  --log-level {DEBUG,INFO,WARNING,ERROR}
                        日志级别
```



该实现支持同时运行多个不同类型的隧道，每个隧道独立运行在后台线程中，收到中断信号时会优雅关闭所有连接。