You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// run command directlyerr:=session.Run("ls")
// run command and get output from stdoutoutput, err:=session.Output("ls")
// run command and get output from stdout and stderroutput, err:=session.CombinedOutput("ls")
// just initiate the call, not waiting for the execution resulterr:=session.Start("ls")
client (stdin) ---websocket--> server (stdin) ---ssh--> server (stdin)
server (stdout) ---ssh--> server (stdout) ---websocket--> client (stdout)
server (stderr) ---ssh--> server (stderr) ---websocket--> client (stderr)
Check this out: https://github.com/zhouhaibing089/ssh-over-websocket
实在不知道起个什么名, 就罗列了一堆看似牛逼的术语. 对于此文呢, 我也实在无法理出章法, 故但求随意, 倘若读者能稍微学到一点东西, 或是因此而有个什么创新的想法, 我便觉得此文有点意义, 心中便有些许满足.
简单来说呢, 通过阅读此文, 你可能会收获:
kubectl exec
的实现原理.Protocol Upgrade
Protocol upgrade是HTTP/1.1协议中的一部分(在HTTP 2中它被显式禁止了). 该机制允许服务端指引客户端做协议切换, 并且主要是用于切换至WebSocket.
WebSocket这个协议提供了一个在TCP连接上做全双工的通信方式, 此处全双工是关键, 它意味着客户端和服务端能够互相发送消息. 在HTTP的通信模式中, 服务端基本完全处于一个被动模式中: 等待客户端请求, 然后发送回应. 这种模式对于很多应用就显得很不友好, 比如说聊天软件, 服务端就没办法主动推送消息至客户端.
WebSocket就可以解决此问题, 而Protocol Upgrade就是从HTTP通向WebSocket的大门. 我们来看一个简单的服务端实现:
这里使用了
github.com/gorilla/websocket
这个库, 所以欲知详情, 可移步这里. 值得指出的是: websocket提供了多种消息类型, 上面的例子中,conn.NextReader()
返回的第一个参数指明了消息类型, 而在conn.WriteMessage(..
中, 我们也显示指定了消息类型为websocket.BinaryMessage
. 所有的消息类型包括:在我们移步下一个主题前, 我们再简单描述一下如何写一个客户端:
其中
ws://
指明我们要访问的websocket协议, 如果服务端启用了https的话, 我们需要使用wss://
. 可以看到, 我们也可以指定额外的HTTP头部信息, 比如如果服务端需要做认证的话, 我们就可以在此指定认证消息了.拿到
conn
之后, 客户端的调用方式和服务端的调用方式无异, 皆为NextReader
和WriteMessage
.SSH Client
得益于golang.org/x/crypto/ssh这个库, 我们可以非常轻松地构建一个ssh客户端. 简单来说, 创建一个ssh会话包括以下几个步骤:
认证信息
我们以私钥为例:
在该处例子中, 我们先读取本地的私钥, 解析成一个signer, 然后用该signer创建client配置. 在ssh库中也提供了一其他的认证方式, 比如用户名密码这种方式:
创建连接以及会话
这个非常直接:
ssh.Dial
处理了建立连接以及握手协议.ssh.NewSession
创建一个新的通道, 该通道主要用来远程执行程序. 如果我们只是非常简单的调用一条命令, 那这个时候已经开始用session
来执行了:但是如果我们要模拟一个完整的交互式会话 我们就需要使用
session.Shell
方法了.请求虚拟终端
session.Shell
会启动一个登录shell, 在这个shell中, 我们可以交互式地执行命令, 不过通常来说我们都会需要在虚拟终端中来运行shell.这段代码先是设置了一些关键的终端参数.
ssh.ECHO
表示要输出接收到的输入. 然后session.RequestPty
中, 我们指定了TERM
环境变量为linux
, 也有一些其他的值可选, 比如xterm
, 但是貌似linux
可以和vim协作得最好.<height>
和<width>
指定了我们期望的终端大小.开启终端之后, 我们还需要获取该会话的标准输入输出才能与之交互:
Proxy SSH
至此, 我们已经知道了怎么做协议升级, 也知道怎么创建一个SSH会话, 那我们可不可以把这两者结合起来呢? 比如说我们是不是可以在websocket上代理ssh连接呢?
也就是说, 我们可以在服务端建立好ssh连接, 再把客户端的标准输入全部通过websocket发送给服务端, 服务端再pipe到ssh连接上, 同理, 我们把ssh的标准输出再通过websocket全部发给客户端. 最后从客户端的角度来说, 就相当于可以完全控制这个ssh会话了.
客户端的标准输入
一个很简单的拷贝标准输入实现:
同理, 其他输出流可以同理来代理.
但是该实现有一个问题, 就是一些控制字符没办法发送. 比如tab键, 上述方式没办法指引服务端在收到tab按键时自动补全, 又比如你在远端运行vim, 上下左右键也会导致输出怪样. 解决方法是将stdin切换至Raw模式:
窗口大小
窗口大小也是一个用户体验的重要一方面, 不然你打开vim, 只能使用办个屏幕岂不是很无奈. 我们上面知道, 在请求虚拟终端的时候, 可以指定窗口大小, 所以我们只要把客户端的窗口大小发送给服务端就可以了:
初始大小我们可以通过把width和height通过http参数传给服务端.
可是我们经常调整窗口大小, 比如一会全屏, 一会半屏, 我们怎么通知服务端呢?
也就是我们可以通过signal来获悉窗口大小改变事件, 每次发生该事件时, 我们重新获取窗口大小, 并将新的大小信息通过
websocket.TextMessage
来发送给服务端, 而在服务端可以处理该消息, 并且调整会话终端大小:至此, 我们已经理清楚了要实现一个ssh代理所需要知道的所有细节了.
总结
那回到开头, 为什么说看完此文也就顺带理解了
kubectl exec
的实现原理了呢, 因为如果我们把WebSocket换成SPDY协议, 再把SSH协议换成container runtime的输入输出流, 整个过程就几乎一模一样了.那做这样一个SSH Proxy有啥用处呢?
那还有什么理由使用teleport呢?
The text was updated successfully, but these errors were encountered: