clue/reactphp-ssh-proxy

异步SSH代理连接器和转发器,通过SSH服务器隧道任何基于TCP/IP的协议,基于ReactPHP构建

v1.4.0 2022-09-02 12:54 UTC

This package is auto-updated.

Last update: 2024-08-30 01:23:19 UTC


README

CI status installs on Packagist

异步SSH代理连接器和转发器,通过SSH服务器隧道任何基于TCP/IP的协议,基于 ReactPHP 构建。

安全外壳 (SSH) 是一种安全网络协议,通常用于访问远程服务器的登录shell。其架构允许它在一个连接上使用多个安全通道。其中之一是创建“SSH隧道”,这通常用于通过中间代理(“代理”)隧道HTTP(S)流量,以隐藏原始地址(匿名性)或绕过地址封锁(地理封锁)。这可以用于隧道任何基于TCP/IP的协议(HTTP、SMTP、IMAP等),因此也允许您访问其他方式无法从外部访问的本地服务(例如,防火墙后面的数据库)。这个库通过在 ssh 客户端二进制程序周围创建一个轻量级进程包装器来实现,并为您提供了创建这些隧道连接的简单API。因为它实现了ReactPHP的标准 ConnectorInterface,所以可以简单地用它来代替普通连接器。这使得将SSH代理支持添加到几乎所有现有的高级协议实现变得相当简单。

  • 异步执行连接 - 并行发送任意数量的SSH代理请求,并在结果一出来就处理它们的响应。基于Promise的设计提供了一个处理无序响应和可能的连接错误的合理接口。
  • 标准接口 - 通过实现ReactPHP的标准 ConnectorInterface,允许轻松与现有高级组件集成。
  • 轻量级,SOLID设计 - 提供了一个足够好的抽象层,不会干扰您。它建立在经过良好测试的组件和公认的概念之上,而不是重新发明轮子。
  • 良好的测试覆盖率 - 随附自动化测试套件,并定期在实际SSH服务器上进行测试。

目录

支持我们

我们在开发、维护和更新我们出色的开源项目上投入了大量时间。您可以通过 成为GitHub赞助商 来帮助我们保持这种高质量的工作。赞助商将获得许多回报,有关详细信息,请参阅我们的 赞助页面

让我们共同把这些项目提升到新的水平! 🚀

快速入门示例

以下示例代码展示了如何使用此库通过远程SSH服务器向google.com发送安全的HTTPS请求。

<?php

require __DIR__ . '/vendor/autoload.php';

$proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false
));

$browser = new React\Http\Browser($connector);

$browser->get('https://google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
    var_dump($response->getHeaders(), (string) $response->getBody());
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

请参阅示例

API

SshProcessConnector

SshProcessConnector 负责通过使用作为代理服务器的中间SSH服务器创建到任何目的地的普通TCP/IP连接。

[you] -> [proxy] -> [destination]

该类作为ssh客户端二进制文件的轻量级进程包装实现,因此将为每个连接启动一个ssh进程。例如,如果您打开tcp://reactphp.org:80的连接,它将运行相当于ssh -W reactphp.org:80 [email protected]的命令,并转发其标准I/O流的数据。为了使这成为可能,您必须确保已安装合适的SSH客户端。在基于Debian/Ubuntu的系统上,您可能只需按如下方式安装它

sudo apt install openssh-client

其构造函数简单地接受一个SSH代理服务器URL。

$proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');

代理URL可能包含或不包含方案和端口定义。默认端口为22(SSH),但您可能需要根据您的SSH服务器配置使用自定义端口。

此类接受一个可选的LoopInterface|null $loop参数,可以用于将事件循环实例传递给此对象。您可以使用一个null值来使用默认循环。除非您确定要显式使用给定的事件循环实例,否则不应提供此值。

请注意,此类作为ssh客户端二进制文件的轻量级进程包装实现,并且将为每个连接启动一个ssh进程。如果您打开更多连接,它将为每个连接启动一个ssh进程。每个进程都需要一些时间来创建新的SSH连接,然后一直运行,直到连接关闭,因此建议您限制并发连接的总数。如果您计划只使用单个或少数连接(例如单个数据库连接),使用此类是推荐的方法。如果您计划创建多个连接或具有更多的连接(例如HTTP客户端),建议您使用SshSocksConnector

这是本包中的两个主要类之一。因为它实现了ReactPHP的标准ConnectorInterface,它可以简单地替换正常连接器使用。因此,它只提供了一个公共方法,即connect()方法。connect(string $uri): PromiseInterface<ConnectionInterface, Exception>方法可以用来建立流连接。它返回一个Promise,在成功时解决为ConnectionInterface,在出错时拒绝为Exception

这使得向几乎任何高级组件添加SSH代理支持变得相对简单。

- $acme = new AcmeApi($connector);
+ $proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');
+ $acme = new AcmeApi($proxy);

SshSocksConnector

SshSocksConnector 负责通过使用作为代理服务器的中间SSH服务器创建到任何目的地的普通TCP/IP连接。

[you] -> [proxy] -> [destination]

此类实现为一个轻量级进程包装器,围绕 ssh 客户端二进制文件,它会在需要时为多个连接启动一个 ssh 进程。例如,一旦您第一次 打开到 tcp://reactphp.org:80 的连接,它将运行等价的 ssh -D 1080 [email protected] 以在本地 SOCKS 代理服务器模式下运行 SSH 客户端,然后创建一个到该服务器进程的 SOCKS 客户端连接。您可以通过此进程创建任意数量的连接,它将在有任何打开的连接时保持此进程运行,并在空闲时自动关闭它。为此,您需要确保已安装合适的 SSH 客户端。在基于 Debian/Ubuntu 的系统上,您可以简单地这样安装它:

sudo apt install openssh-client

其构造函数简单地接受一个SSH代理服务器URL。

$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

代理URL可能包含或不包含方案和端口定义。默认端口为22(SSH),但您可能需要根据您的SSH服务器配置使用自定义端口。

此类接受一个可选的LoopInterface|null $loop参数,可以用于将事件循环实例传递给此对象。您可以使用一个null值来使用默认循环。除非您确定要显式使用给定的事件循环实例,否则不应提供此值。

请注意,此类实现为一个轻量级进程包装器,围绕 ssh 客户端二进制文件,并且它将为多个连接启动一个 ssh 进程。此进程需要一些时间来创建新的 SSH 连接,然后一直运行,直到最后一个连接关闭。如果您计划创建多个连接或具有更多的并发连接(例如 HTTP 客户端),则建议使用此类。如果您只计划使用单个或少数连接(例如单个数据库连接),则建议您使用 SshProcessConnector

此类的默认行为是在 127.0.0.1:1080 上监听以 SOCKS 代理服务器模式启动 SSH 客户端进程。如果此端口已被占用或您想使用此类多个实例连接到不同的 SSH 代理服务器,您可以可选地传递一个唯一的绑定地址,如下所示:

$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]?bind=127.1.1.1:1081');

多用户系统安全提示:此类将默认以本地 SOCKS 服务器模式启动 SSH 客户端进程,并且默认只接受来自 localhost 接口的连接。如果您正在多用户系统上运行,则同一系统上的其他用户可能能够连接到此代理服务器并在此之上创建连接。如果这适用于您的部署,建议您使用 `SshProcessConnector` 或者设置自定义防火墙规则以防止未经授权访问此端口。

这是本包中的两个主要类之一。因为它实现了ReactPHP的标准ConnectorInterface,它可以简单地替换正常连接器使用。因此,它只提供了一个公共方法,即connect()方法。connect(string $uri): PromiseInterface<ConnectionInterface, Exception>方法可以用来建立流连接。它返回一个Promise,在成功时解决为ConnectionInterface,在出错时拒绝为Exception

这使得向几乎任何高级组件添加SSH代理支持变得相对简单。

- $acme = new AcmeApi($connector);
+ $proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');
+ $acme = new AcmeApi($proxy);

使用

普通TCP连接

SSH 代理服务器通常用于向您的目标发送 HTTPS 请求。然而,这实际上是在更高的协议层执行的,并且此项目实际上本质上是一个通用目的的纯 TCP/IP 连接器。如上所述,您可以简单调用 connect() 方法在 SshProcessConnectorSshSocksConnector 上建立流式纯 TCP/IP 连接,并使用任何高级协议,如下所示:

$proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');
// or
$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) {
    $connection->write("EHLO local\r\n");
    $connection->on('data', function ($chunk) use ($connection) {
        echo $chunk;
    });
});

您可以直接使用 SshProcessConnectorSshSocksConnector,或者您可能想要将此连接器包装在 ReactPHP 的 Connector 中。

$proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');
// or
$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false
));

$connector->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) {
    $connection->write("EHLO local\r\n");
    $connection->on('data', function ($chunk) use ($connection) {
        echo $chunk;
    });
});

对于此示例,您可以使用 SshProcessConnectorSshSocksConnector。请注意,此项目实现为一个轻量级进程包装器,围绕 ssh 客户端二进制文件。虽然 SshProcessConnector 将为每个连接启动一个 ssh 进程,但 SshSocksConnector 将启动一个将被多个连接共享的 ssh 进程,有关更多详细信息,请参阅上面。

安全TLS连接

如果您想在与您的目标之间建立安全的 TLS 连接(以前称为 SSL),例如使用安全的 HTTPS 连接到您的目标站点,也可以使用 SshSocksConnector。您只需将此连接器包装在 ReactPHP 的 Connector 中即可。

$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false
));

$connector->connect('tls://smtp.googlemail.com:465')->then(function (React\Socket\ConnectionInterface $connection) {
    $connection->write("EHLO local\r\n");
    $connection->on('data', function ($chunk) use ($connection) {
        echo $chunk;
    });
});

请注意,TLS连接实际上完全由SSH代理客户端实现之外处理。目前SshProcessConnector不支持安全TLS连接,因为PHP的底层加密函数需要一个套接字资源,并且不适用于虚拟连接。作为替代方案,建议使用上面示例中给出的SshSocksConnector

HTTP请求

此库还允许您通过SSH代理服务器发送HTTP请求

为了发送HTTP请求,您首先需要添加ReactPHP的异步HTTP客户端的依赖。这允许您发送纯HTTP和TLS加密的HTTPS请求,如下所示

$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false
));

$browser = new React\Http\Browser($connector);

$browser->get('https://google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
    var_dump($response->getHeaders(), (string) $response->getBody());
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

我们建议使用SshSocksConnector,这适用于纯HTTP和TLS加密的HTTPS请求。当使用SshProcessConnector时,这只适用于纯文本HTTP请求。

有关更多详细信息,请参阅ReactPHP的HTTP客户端示例

数据库隧道

现在我们应该基本了解如何通过SSH代理服务器隧道传输任何基于TCP/IP的协议。除了用于访问“外部”服务之外,这尤其有用,因为它允许您从外部访问否则仅限于此SSH服务器本地的网络服务,例如受防火墙保护的数据库服务器。

例如,这允许我们将异步MySQL数据库客户端与上面所述的SSH代理服务器设置相结合,从而可以通过SSH隧道访问受防火墙保护的MySQL数据库服务器。以下是简要说明

$proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');

$uri = 'test:test@localhost/test';
$factory = new React\MySQL\Factory(null, $proxy);
$connection = $factory->createLazyConnection($uri);

$connection->query('SELECT * FROM book')->then(
    function (React\MySQL\QueryResult $command) {
        echo count($command->resultRows) . ' row(s) in set' . PHP_EOL;
    },
    function (Exception $error) {
        echo 'Error: ' . $error->getMessage() . PHP_EOL;
    }
);

$connection->quit();

有关更多详细信息,请参阅示例#21

此示例将自动启动ssh客户端二进制文件以创建到无法从外部访问的数据库服务器的连接。从数据库服务器的角度来看,这看起来就像是一个常规的本地连接。从本代码的角度来看,这将创建一个常规的本地连接,只是恰好使用安全的SSH隧道将其传输到远程服务器,因此您可以像向本地数据库服务器发送查询一样发送任何查询。

连接超时

默认情况下,SshProcessConnectorSshSocksConnector都不实现任何用于建立远程连接的超时。您的底层操作系统可能对挂起和/或空闲的TCP/IP连接施加限制,范围从几分钟到几小时不等。

许多用例需要更多对超时的控制,并且值通常要小得多,通常只在几秒钟的范围内。

您可以使用ReactPHP的Connector来装饰任何给定的ConnectorInterface实例。它提供相同的connect()方法,但如果连接尝试花费时间过长,则会自动拒绝底层连接尝试

$proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');
// or
$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false,
    'timeout' => 3.0
));

$connector->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
    // connection succeeded within 3.0 seconds
});

有关更多详细信息,请参阅示例

请注意,连接超时实际上完全由SSH代理客户端实现之外处理。

DNS解析

默认情况下,SshProcessConnectorSshSocksConnector都不执行任何DNS解析,而只是将您尝试连接到的任何主机名转发到远程代理服务器。因此,远程代理服务器负责通过DNS查找任何主机名(这种默认模式因此被称为“远程DNS解析”)。

作为替代方案,您还可以将目标IP发送到远程代理服务器。在此模式下,您必须坚持只使用IP(这通常不可行)或本地执行DNS查询,并仅传输解析后的目标IP(因此,此模式称为本地DNS解析)。

默认的远程DNS解析在以下情况下很有用:您的本地SshProcessConnectorSshSocksConnector无法解析目标主机名,因为它没有直接访问互联网,或者它不应该解析目标主机名,因为其出站DNS流量可能被拦截。

如上所述,SshProcessConnectorSshSocksConnector默认使用远程DNS解析。然而,将它们包裹在ReactPHP的Connector中实际上执行本地DNS解析,除非明确指定否则。鉴于远程DNS解析假定是首选模式,所有其他示例都明确禁用DNS解析,如下所示

$proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');
// or
$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false
));

如果您想显式使用本地DNS解析,可以使用以下代码

$proxy = new Clue\React\SshProxy\SshProcessConnector('[email protected]');
// or
$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

// set up Connector which uses Google's public DNS (8.8.8.8)
$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => '8.8.8.8'
));

请注意,本地DNS解析实际上完全由SSH代理客户端实现之外的组件处理。

密码认证

请注意,此类作为ssh客户端二进制的轻量级进程包装器实现。它假设您已经通过命令行验证可以像这样访问您的SSH代理服务器

# test SSH access
ssh [email protected] echo hello

因为此类旨在用于创建任意数量的连接,所以它不提供交互式请求密码的方式。同样,出于安全原因,ssh客户端二进制也不提供在命令行中“传递”密码的方式。这意味着您强烈建议为此功能最佳运行而设置基于公钥的身份验证,无需密码。

此外,此库提供了一种以相对不安全的方式传递密码的方法,如果您的用例绝对需要此功能。在继续之前,请查阅您的SSH文档,以了解为什么这可能是个坏主意,以及为什么基于公钥的身份验证通常是更好的选择。如果您的SSH代理服务器需要密码认证,您可以将用户名和密码作为SSH代理服务器URL的一部分传递,如下所示

$proxy = new Clue\React\SshProxy\SshProcessConnector('alice:[email protected]');
// or
$proxy = new Clue\React\SshProxy\SshSocksConnector('alice:[email protected]');

为此要工作,您必须安装sshpass二进制文件。在基于Debian/Ubuntu的系统上,您可以简单地安装它,如下所示

sudo apt install sshpass

请注意,如果它们包含特殊字符,用户名和密码必须进行百分编码

$user = 'he:llo';
$pass = 'p@ss';
$url = rawurlencode($user) . ':' . rawurlencode($pass) . '@example.com';

$proxy = new Clue\React\SshProxy\SshProcessConnector($url);

安装

建议通过Composer安装此库。您是Composer的新手?新来者?

本项目遵循SemVer。这将安装最新支持版本

composer require clue/reactphp-ssh-proxy:^1.4

有关版本升级的详细信息,请参阅变更日志

本项目旨在在任何平台上运行,因此不要求任何PHP扩展,并支持在PHP 5.3(通过当前PHP 8+和HHVM)上运行。强烈建议使用此项目的最新支持版本。

本项目作为ssh客户端二进制的轻量级进程包装器实现,因此您必须确保已安装合适的SSH客户端。在基于Debian/Ubuntu的系统上,您可以简单地安装它,如下所示

sudo apt install openssh-client

此外,如果您使用密码认证(不推荐),则必须安装sshpass二进制文件。在基于Debian/Ubuntu的系统上,您可以简单地安装它,如下所示

sudo apt install sshpass

目前不支持在Windows上运行

测试

要运行测试套件,您首先需要克隆此仓库,然后通过Composer安装所有依赖项(Composer)

composer install

要运行测试套件,请转到项目根目录并运行

vendor/bin/phpunit

测试套件包含一些需要实际SSH代理服务器的测试。除非您配置了SSH登录凭据以创建一些实际的测试连接,否则这些测试将被跳过。您可以设置SSH_PROXY环境变量,并在其前面加一个空格,以确保您的登录凭据不会被像这样存储在bash历史记录中

 export SSH_PROXY=alice:[email protected]
vendor/bin/phpunit

许可证

本项目采用宽松的MIT许可证发布。

您知道吗?我可以提供定制开发服务,并为发布和贡献颁发赞助发票。请联系我(@clue)了解详情。

更多

  • 如果您想了解ConnectorInterface及其常见实现的外观,请参阅底层react/socket组件的文档。
  • 如果您想了解更多关于处理数据流的信息,请参阅底层react/stream组件的文档。
  • 作为SSH代理服务器的替代方案,您也可能想考虑使用SOCKS5或SOCKS4(a)代理。您可能想使用clue/reactphp-socks,它也提供了一个相同的ConnectorInterface实现,因此支持任何代理协议应该相对简单。
  • 作为SSH代理服务器的另一种替代方案,您也可能想考虑使用HTTP CONNECT代理。您可能想使用clue/reactphp-http-proxy,它也提供了一个相同的ConnectorInterface实现