跳到主要内容

SSH安全通道端口代理转发组件

如果运维人员只给你通过SSH公钥的方式授权访问,该服务器并不开放我们需要的服务的端口号时,我们该怎么做才能访问到对应的服务呢?

其实上面的问题我们在平时工作中经常遇到,为了服务的数据安全性考虑,一般不会直接将端口号开放,我们只能通过登录服务器后使用命令才可以进行操作,目前市面上也有很多的工具支持SSH公钥的方式连接,比如:DataGrip、Navicat、Redis Desktop Manager、MongoDB Compass等工具。

那如果我们的本地项目需要连接到远程服务的数据库、Redis或者MongoDB时该怎么去做呢?

GitHub源码:minbox-projects/ssh-agent

1. 使用场景

SSH-Agent就是来解决这类问题的,主要原理是在本地创建一个端口通过SSH方式登录后与远程服务器的端口号进行绑定,这样我们连接本地的端口号时就可以访问到远程服务器的服务了。

2. 组件依赖

<dependency>
<groupId>org.minbox.framework</groupId>
<artifactId>api-boot-starter-ssh-agent</artifactId>
</dependency>
提示

如果未添加ApiBoot版本依赖,请访问版本依赖查看添加方式。

3. 配置参数

application.yml/properties配置文件内的配置前缀为:api.boot.ssh-agent,通过api.boot.ssh-agent.configs配置多个代理信息,每个代理配置的参数如下表所示:

参数名默认值描述
username-配置连接服务器的用户名
password-配置连接服务器的密码(仅限密码认证方式)
authentication-methodSSH_PRIVATE_KEY认证方式,参数值:
USERNAME_PASSWORD:用户名密码方式
SSH_PRIVATE_KEY:SSH秘钥方式
server-ip-配置连接服务器的IP地址
ssh-port22配置连接服务器的SSH端口号
key-typersaSSH秘钥加密类型,参数值:
rsa:RSA类型加密
ed25519:ED25519类型加密
ssh-private-key-path~/.ssh/id_rsaSSH方式认证秘钥地址
ssh-known-hosts-path~/.ssh/known_hosts配置已知主机配置文件地址
local-port-本地代理的端口号
forward-target-port-配置转发到远程服务器的端口号
forward-target-ip127.0.0.1登录后的服务器IP地址,如果是局域网,可以配置内网IP地址
addition-jsch认证需要的额外附加参数列表

4. 实现原理

ApiBoot SSH Agent通过封装jsch来实现代理SSH端口,并且与代理端口保持长连接实现数据不间断的转发。

4.1 AgentConnection

AgentConnection是一个接口,ssh-agent提供了该接口的默认实现类DefaultAgentConnection,通过实例化DefaultAgentConnection就可以实现代理SSH端口,部分源码如下所示:

try {
AgentConnection connection = new DefaultAgentConnection(config);
this.connections.add(connection);
connection.connect();
} catch (Exception e) {
log.error("Connection:{}:{},try agent failure.", config.getServerIp(), config.getForwardTargetPort(), e);
}

4.2 SshAgentServletContextListener

ApiBootAgentConnection进行了封装,通过监听ServletContextEvent事件的ServletContextListener监听器来实现项目启动成功后根据配置的代理参数进行开启代理SSH通道。

提示

不过,通过SshAgentServletContextListener有个缺陷!!!

只能正常启动项目时才可以代理SSH端口转发通道,如果我们使用Junit来进行单元测试时虽然会启动项目但是并不会触发ServletContextEvent事件,因此在执行单元的时候会提示无法连接,针对这个问题请查阅在junit5中使用

5. 认证方式

目前登录服务器的认证方式有两种:

  • 用户名 + 密码:直接通过用户名密码的方式登录远程服务器,不需要走SSH
  • 用户名 + 秘钥:需要通过SSH连接远程服务器,远程服务器需要配置授权的公钥(默认方式,也建议使用这种,安全系数好高很多)

6. 在Junit5中使用

步骤一:继承SshAgentJunitTest单元测试类

@SpringBootTest
class SimulationServiceApiApplicationTests extends SshAgentJunitTest {
@Test
void contextLoads() {
//...
}
}
提示

在上面代码示例中,单元测试类SimulationServiceApiApplicationTests继承自SshAgentJunitTest后,在执行contextLoads()方法时,执行前会自动开启SSH代理连接,执行后会自动关闭代理连接。

步骤二:添加ssh-agent.yml配置文件

ssh-agent.yml用于配置ssh代理连接列表,位于src/test/resources目录下,如果不创建该配置文件运行单元测试方法时会抛出异常。

ssh-agent.yml
configs:
# 代理转发MySQL
- username: developer
serverIp: x.x.x.x
localPort: 3306
forwardTargetPort: 59500
# 代理转发Redis
- username: developer
serverIp: x.x.x.x
localPort: 6379
forwardTargetPort: 59504
# 代理转发mongo
- username: developer
serverIp: x.x.x.x
localPort: 27017
forwardTargetPort: 59503

7. 代理日志

在项目启动时如果在application.yml/.properties文件内配置的服务器连接信息没有问题,控制台会输出绑定转发端口成功的日志,如下所示:

Port forwarding binding is completed, local port : 3307, forward IP: 127.0.0.1, forward port : 3306

8. 代理目标局域网端口

有一种情况:管理员给你开通了一个跳板机访问权限,你可以通过ssh方式登录该跳板机,但是你需要访问的具体服务在另外一台或者多台服务器上,具体服务的机器跟跳板机是一个局域网,针对这个情况我们怎么使用ApiBoot SshAgent来配置呢?

假如服务器部署环境如下:

跳板机:192.168.1.100
MySQL服务:192.168.1.101
Redis服务:192.168.1.102
MongoDB服务:192.168.1.103

针对上述服务部署环境,对应的代理配置如下:

application.yml
api:
boot:
ssh-agent:
configs:
# 代理转发MySQL
# ssh方式登录的跳板机用户名
- username: developer
# ssh方式登录的跳板机服务器IP
server-ip: 192.168.1.100
# 本地访问代理服务的端口号
# 代理成功后访问127.0.0.1:3306等于访问192.168.1.101:3306
local-port: 3306
# 转发目标的局域网服务IP
forward-target-ip: 192.168.1.101
# 转发目标的局域网服务Port
forward-target-port: 3306
# 代理转发Redis
- username: developer
server-ip: 192.168.1.100
local-port: 6379
forward-target-ip: 192.168.1.102
forward-target-port: 6379
# 代理转发MongoDB
- username: developer
server-ip: 192.168.1.100
local-port: 27017
forward-target-ip: 192.168.1.103
forward-target-port: 27017

9. 常见问题

9.1 JSCH秘钥连接异常

如果第一次使用ApiBoot SshAgent控制台输出以下错误信息,证明生成open-ssh秘钥的方式有点问题。

ERROR: Failed to authenticate with public key
com.jcraft.jsch.JSchException: invalid privatekey: [B@5b5361f0

针对上述问题,我们只需要重新生成一次open-ssh公钥密钥对即可,生成时传递-m参数为:PEM.

ssh-keygen -m PEM