SSH
介绍
SSH(Secure Shell,安全外壳)是一种网络安全协议,通过加密和认证机制实现安全的访问和文件传输等业务。
SSH(Secure Shell)是一种网络协议,用于加密方式远程登录到计算机系统,通常用于服务器管理。SSH客户端(如ssh)是用于发起远程连接的程序,而SSH服务器端(如sshd)是用于接收远程连接的程序。
架构
SSH客户端(ssh)
用途:用于从本地计算机发起到远程服务器的连接请求。
功能
远程登录:通过SSH协议连接到远程服务器。
文件传输:使用SCP或SFTP协议传输文件。
端口转发:通过SSH隧道实现端口转发,增加安全性。
远程命令执行:在远程服务器上执行命令。
SSH服务器端(sshd)
用途:在远程服务器上运行,监听并接受来自SSH客户端的连接请求。
功能
监听端口:默认监听22端口,但可以配置为其他端口。
认证管理:管理用户认证,支持密码、密钥等多种认证方式。
权限控制:控制用户对服务器的访问权限。
日志记录:记录连接日志,便于安全审计。
工作流程
客户端发起连接:SSH客户端(ssh)向服务器的SSH服务端口(默认22)发起连接请求。
服务器响应:SSH服务器端(sshd)接收到连接请求,并进行认证。
认证过程:用户输入用户名和密码或提供密钥,服务器验证通过后建立加密通道。
会话建立:一旦认证通过,双方建立加密会话,用户可以执行远程命令或进行文件传输。
操作
个人电脑链接服务器
查看自己电脑上的ssh情况:
ls -l ~/.ssh/id_*
找到公钥和私钥,然后把公钥发给服务器,然后使用
ssh root@192.168.0.55
这种登陆就可以直接进入服务器操作了。
使用ssh登陆需要密码,复制公钥到服务器之后就不需要密码了。
ssh-copy-id
修改配置文件的时候,要注意这个配置文件的特定的那一行是不是被注释了,否则无论怎样修改都是无法成功的了。
部署ssh-server服务
容器环境部署ssh-server服务器
在docker hub上找了一个ssh服务器:https://hub.docker.com/r/linuxserver/openssh-server
把提供的dokcer部署方式手动转换成k8s部署方式
-e参数 转换为deployment中的 env变量。
注意如果不设置用户名的话,用户名将自动设置为linuxserver.io
部署之后现在minikube上测试,成功:
然后放到测试环境,成功:
ssh能链接 但是端口转发有问题
11:41:41.578] Remote server is listening on port 35175
[11:41:41.578] Parsed server configuration: {"serverConfiguration":{"remoteListeningOn":{"port":35175},"osReleaseId":"alpine","arch":"x86_64","sshAuthSock":"","display":"","tmpDir":"/tmp","platform":"linux","execServerToken":"1a111111-111a-11a1-1a11-1a1111a1a1a1"},"downloadTime":1182,"installTime":401,"serverStartTime":45,"installUnpackCode":"success"}
[11:41:41.584] Starting forwarding server. local port 5879 -> socksPort 5862 -> remotePort 35175
[11:41:41.585] Forwarding server listening on port 5879
[11:41:41.585] Waiting for ssh tunnel to be ready
[11:41:41.586] [Forwarding server port 5879] Got connection 0
[11:41:41.588] Tunneled port 35175 to local port 5879
[11:41:41.588] Resolved "ssh-remote+192.168.0.55" to "port 5879"
[11:41:41.599] Initizing new exec server for ssh-remote+192.168.0.55
[11:41:41.599] Resolving exec server at port 5879
[11:41:41.620] >
[11:41:41.622] [Forwarding server port 5879] Got connection 1
[11:41:41.632] Failed to set up socket for dynamic port forward to remote port 35175: Socket closed. TCP port forwarding may be disabled, or the remote server may have crashed. See the VS Code Server log above for details.
[11:41:41.633] Failed to set up socket for dynamic port forward to remote port 35175: Socket closed. TCP port forwarding may be disabled, or the remote server may have crashed. See the VS Code Server log above for details.
[11:41:41.655] > channel 3: open failed: administratively prohibited: open failed
> channel 4: open failed: administratively prohibited: open failed
[11:41:41.656] 错误: 远程主机上似乎禁用了 TCP 端口转发。确保 sshd_config 具有 `AllowTcpForwarding yes`。如有需要,请与系统管理员联系。
端口转发是在sshd_config中配置的一个变量,AllowTcpForwarding no 现有的镜像并没有设置这个参数。
还有这两个参数:
在 SSH 配置中,GatewayPorts 和 X11Forwarding 是两个用于控制 SSH 服务特定功能的选项:
GatewayPorts:
默认值:no
作用:当设置为 yes 时,允许从任何地址(不仅仅是本地或指定的 ListenAddress)进行端口转发。这在需要从远程网络访问通过 SSH 隧道转发的服务时非常有用。
安全性:如果设置为 yes,可能会增加安全风险,因为它允许来自互联网的连接请求端口转发,这可能被用于绕过防火墙或 NAT 设备。
X11Forwarding:
默认值:no
作用:当设置为 yes 时,允许通过 SSH 连接转发 X11 应用程序的图形用户界面(GUI)。这意味着您可以在远程机器上运行图形界面应用程序,并将显示转发到本地机器。
使用场景:这在需要远程访问图形界面应用程序时非常有用,例如使用远程桌面或图形设计软件。
安全性:启用 X11 转发可能会带来安全风险,因为它可能会使您的本地桌面环境暴露给远程攻击者。确保仅在信任的网络和环境中使用,并使用适当的加密和认证机制。
1.首先考虑的解决方案是:修改之后,重新启动容器。
进入容器内部调整:
kubectl exec -it my-pod -- /bin/bash
进入之后,通过在对话框输入exit退出。
但是此时未做持久化,重启之后会消失,所以失败。
重启容器相关:
直接把pod删了,会根据deployment自动重启,删了deployment就不会了。
而且deployment本事也是一个pod。
2.然后考虑使用挂载的方式,使用本地的sshd_config文件。
重新连接后,新的sshd服务器私钥已修改,可能会出现以下报错:
ssh -p 30002 linuxserver.io@192.168.0.55
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:TUXOPI7PqhKaJM/PPr0sq75QCauUz1woRUYlZNwP7TU.
Please contact your system administrator.
Add correct host key in /Users/wpy/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /Users/wpy/.ssh/known_hosts:22
Host key for [192.168.0.55]:30002 has changed and you have requested strict checking.
Host key verification failed.
这个问题删除掉本地的对应公钥即可。
在本机电脑上,运行,之后直接修改即可。
vim ~/.ssh/known_hosts
接下来首先尝试本地挂载:即使用 hostPath
类型的卷。hostPath
允许您指定宿主机上的文件或目录,将其挂载到 Pod 中。
apiVersion: apps/v1
kind: Deployment
metadata:
name: openssh-server666
labels:
app: openssh-server666
spec:
replicas: 1
selector:
matchLabels:
app: openssh-server666
template:
metadata:
labels:
app: openssh-server666
spec:
containers:
- name: openssh-server666
image: linuxserver/openssh-server:latest
env:
- name: SUDO_ACCESS
value: "true"
- name: PASSWORD_ACCESS
value: "true"
- name: USER_PASSWORD
value: "123456" # 请替换为更强的密码
ports:
- containerPort: 2222
volumeMounts: # 添加卷挂载
- name: sshd-config-volume
mountPath: /etc/ssh
readOnly: true
volumes: # 添加卷定义
- name: sshd-config-volume
hostPath:
path: /path/to/sshconfig # 宿主机上的路径
type: Directory # 可以是 Directory 或 File,具体取决于您挂载的是目录还是文件
此时启动,查看pod状态发现启动不起来,采取查看日志的方式观察启动情况:
kubectl logs openssh-server666-cdc8cd994-dxn5g pod名
发现启动不起来是因为找不到host文件,使用pwd确定文件没问题,所以考虑放弃,改使用configmap。
ConfigMap 是 Kubernetes 中用于存储配置数据的 API 资源,这些配置数据可以被挂载到 Pod 中作为文件或者注入为环境变量。
创建的configmap中的每一项都会作为一个文件放到对应目录去。
创建:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
sshd_config: |
# SSH Daemon Configuration
Port 22
PermitRootLogin yes
# Other SSH configuration options...
app.conf: |
# Application Configuration
application.logPath=/var/log/myapp
application.timeout=30
# Other configuration files or data...
之后使用kubectl部署。
然后使用:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: my-image
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: my-configmap
restartPolicy: Always
my-configmap
中的所有键值对将被挂载到容器的 /etc/config
目录下,每个键对应一个文件。
也就是说直接在configmap中包含全部的文件内容即可。
在绑定完成后,运行deployment,发现容器启动不起来,使用log查看日志,发现:
User name is set to linuxserver.io
sudo is enabled with password.
sed: can't create temp file '/etc/ssh/sshd_configXXXXXX': Read-only file system
rm: cannot remove '/etc/ssh/..data': Read-only file system
rm: cannot remove '/etc/ssh/sshd_config': Read-only file system
rm: cannot remove '/etc/ssh/..2024_07_31_06_55_46.493918373/sshd_config': Read-only file system
ln: failed to create symbolic link '/etc/ssh/ssh_host_keys': Read-only file system
Could not save your private key in /etc/ssh/ssh_host_rsa_key.XXXXXXXXXX: Read-only file system
Could not save your private key in /etc/ssh/ssh_host_ecdsa_key.XXXXXXXXXX: Read-only file system
Could not save your private key in /etc/ssh/ssh_host_ed25519_key.XXXXXXXXXX: Read-only file system
ssh-keygen: generating new host keys: RSA ssh-keygen: generating new host keys: ECDSA ssh-keygen: generating new host keys: ED25519 SSH host public key(s):
cat: '/config/ssh_host_keys/ssh_host_*.pub': No such file or directory
sed: can't create temp file '/etc/ssh/sshd_configXXXXXX': Read-only file system
sed: can't create temp file '/etc/ssh/sshd_configXXXXXX': Read-only file system
sshd is listening on port 2222
sed: can't create temp file '/etc/ssh/sshd_configXXXXXX': Read-only file system
sed: can't create temp file '/etc/ssh/sshd_configXXXXXX': Read-only file system
User/password ssh access is enabled.
sed: can't create temp file '/etc/ssh/sshd_configXXXXXX': Read-only file system
[custom-init] No custom files found, skipping...
看起来,sshd文件创立需要往sshd_config写入文件,但是由于configmap只读的特性,而且不能即时更新(就是说我修改文件其他时候不会变化,不会触发reconcile),写不了了,所以容器没法启动。
故再换一种方式。
然后请教别人,像这种容器启动时需要配置文件,同时还需要修改的情况,可以考虑采用initcontiner实现:
apiVersion: v1
kind: Pod
metadata:
name: writable-configmap-pod
spec:
volumes:
- name: config
configMap:
name: myconfigmap
- name: writable-config
emptyDir: {}
initContainers:
- name: init-config
image: busybox
command: ["sh", "-c", "cp -r /config/* /writable-config/"]
volumeMounts:
- name: config
mountPath: /config
- name: writable-config
mountPath: /writable-config
containers:
- name: main-container
image: myimage
volumeMounts:
- name: writable-config
mountPath: /etc/config
这个 YAML 文件定义了一个 Kubernetes Pod,其主要目的是将一个 ConfigMap
的内容复制到一个可读写的 emptyDir
卷中,然后让主容器能够对这些配置文件进行读写操作。
一种独特的思路。
不过还是要把这个pod的相关属性全部复制到deployment中才能生效。
修改好后重新部署,此时如果一切正常,那么我应该看到configmap中的东西+新写入的东西,但是它这个新写入好像把原来的全覆盖了。(可以考虑sleep一下,然后进入查看是否正常)。
这也不行,最后考虑重新制作镜像。
在https://github.com/bearslyricattack/docker-openssh-server上重新拷贝项目,修改dockerfile,新增
echo 'AllowTcpForwarding yes' >> /etc/ssh/sshd_config && \
然后制作,完成后,本地docker login登陆,推送到dockerhub,推送之前需要输入命令:
docker tag open-ssh-server:tag bearslyricattack/open-ssh-server:tag
然后再从,测试环境上拉下来,本地运行可以,测试环境报错:
exec /init format
后来问了下,发现本机编译的版本和服务器上不一样。
查看已经构建的dokcer版本:
查看服务器架构版本:
arch
构建镜像的时候,按照制定架构构建镜像的命令:
docker build \
--no-cache \
--pull \
--platform linux/amd64 \
-t bearslyricattack/openssh-server-linux:latest .
然后,在构建镜像的时候写入新的行:
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config && \
echo 'AllowTcpForwarding yes' >> /etc/ssh/sshd_config && \
echo 'GatewayPorts yes' >> /etc/ssh/sshd_config && \
echo 'X11Forwarding yes' >> /etc/ssh/sshd_config && \
sed是替换 echo是写入
在配置文件中(不知道对于其他配置文件什么情况,还是只有这个),相同配置后面的会覆盖前面的。以最后一次出现为准。
在这样做了之后,客户端仍然连接不上去。这下不得不好好看看日志了。
看了日志发现,其实是连接成功了的,并且还起了一个服务。
但是他们会协商出一个全新的端口去做,这个端口是随机的。
对于这个问题有多种实现方案。
1.整体代理:
vscode ssh ext --TCP--> vscode-sealos-http-agent-client --HTTP--> sealos-higress-gateway --HTTP--> sealos-http-agent-server-in-pod --TCP--> vscode-server
2.拦截协商,用nodeport:
在启动之前先询问那些nodeport是可用的,获取可用的端口之后创建vscodeserver和service资源就行了
一个是集群整个的端口就三万个,另外就是这个还是限制了,nodeport 需要监听然后创建,刚才讨论的 websocket 方案,感觉才是正道
3.wstunnel:
不知道什么情况,用rust写的。在研究,不知情况。
后来研究明白了,就是allowtcpwording的问题(看来这个东西并不能实现覆盖),修改之后,又新增一个libstdc++的库就可以了。
写了一篇文章单独分析和解决此问题。
该部分任务完成。
go ssh公私钥生成
"golang.org/x/crypto/ssh"
func GenerateSSHKeyPair() ([]byte, []byte, error) {
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pemKey, err := ssh.MarshalPrivateKey(privKey, "")
if err != nil {
return nil, nil, err
}
privateKey := pem.EncodeToMemory(pemKey)
publicKey, err := ssh.NewPublicKey(pubKey)
if err != nil {
return nil, nil, err
}
sshPublicKey := ssh.MarshalAuthorizedKey(publicKey)
return sshPublicKey, privateKey, nil
}
SSH 隧道
隧道(Tunnel)
介绍:
隧道技术是一种允许在不同网络之间建立虚拟链路以传递数据的方法。它通过将一种协议的数据包封装在另一种协议的数据包中进行传输,从而实现数据的封装、传输和解封装的全过程。
隧道技术出现的目的主要是提高网络通信的适配性和安全性:
网络**互操作性**:允许不同网络协议或体系结构之间进行通信,特别是当它们不直接兼容时。
安全性:在不安全的网络(如互联网)上创建加密的、安全的通信通道,保护数据免受窃听和篡改。
原理
隧道代理的原因也可以用一句话来总结:
代理服务器和真正的服务器之间建立起 TCP 连接,然后在客户端和真正服务器端进行数据的直接转发。
图示如下:(图片来源:《HTTP 权威指南》,https://cizixs.com/2017/03/22/http-tunnel-proxy-and-golang-implementation/)
过程用文字描述如下:(图片来源:https://koalr.me/posts/suo5-a-hign-performace-http-socks/)
(a)客户端先发送 CONNECT 请求到隧道代理服务器,告诉它建立和服务器的 TCP 连接(因为是 TCP 连接,只需要 ip 和端口就行,不需要关注上层的协议类型)
(b,c)代理服务器成功和后端服务器建立 TCP 连接
(d)代理服务器返回
HTTP 200 Connection Established
报文,告诉客户端连接已经成功建立(e)这个时候就建立起了连接,所有发给代理的 TCP 报文都会直接转发,从而实现服务器和客户端的通信
另一张图示:
实现
隧道技术有很多种实现:通常每种实现都由一个客户端和一个服务端组成。
基于数据包的隧道协议有:
(1)IPsec
(2) GRE,通用路由封装协议,支持多种网络层协议和多路技术
(3)IP in IP,比GRE更小的负载头,并且适合只有负载一个IP流的情况。
(4)L2TP,数据链接层隧道协议
(5)MPLS,多协议标签交换
(6)GTP
(7)PPTP,点对点隧道协议
(8)PPPoE,基于以太网的点对点隧道
(9)PPPoA,基于ATM的点对点隧道
(10)IEEE 802.1Q,以太网VLANs
(11)DLSw,SNA负载互联网协议
(12)XOT
(13)IPv6穿隧:6to4、6in4、Teredo
(14)Anything In Anything (AYIYA; e.g. IPv6 over UDP over IPv4, IPv4 over IPv6, IPv6 over TCP IPv4, etc.)
(15) VPN
基于流的隧道协议有:
(1)传输层安全
(2)SSH
(3)SOCKS
(4)HTTP CONNECT 命令
(5)各式的电路层级的代理服务器协议,如Microsoft Proxy Server的Winsock Redirection Protocol或WinGate Winsock Redirection Service.
之前提到的两个开源项目:
本质上都是隧道技术的实现。
SSH隧道
介绍
SSH(Secure Shell)是一种安全的网络协议,用于在不安全的网络中建立安全连接。
SSH隧道(SSH Tunneling)则是在SSH协议的基础上,通过加密数据传输,在两个网络节点之间建立一个安全通道。这使得数据在传
输过程中不易被窃听、篡改或劫持,同时还能够绕过防火墙限制,实现对内部网络资源的访问。
可以理解为,**SSH隧道本身就是对隧道技术的一种实现,同时借助了SSH在两台主机之间的认证功能。**
类型
SSH隧道主要有三种类型:
本地端口转发(Local Port Forwarding): 将本地端口的流量转发到SSH服务器,再由SSH服务器转发到目标服务器。
远程端口转发(Remote Port Forwarding): 将SSH服务器端口的流量转发到本地计算机,再由本地计算机转发到目标服务器。
动态端口转发(Dynamic Port Forwarding): 创建一个本地 SOCKS 代理服务器,应用程序通过配置使用这个SOCKS代理,而SSH客户端负责把流量通过SSH连接转发出去。
本地端口转发
本地**端口转发,是将发送到本地端口的请求,转发到目标端口,这样就可以通过访问本地端口,来访问目标端口的服务。**
应用场景举例:
一般来讲,云主机的防火墙默认只开启了22端口,如果需要访问2000端口,则需要修改防火墙。为了保证安全,防火墙需要配置允许访问的IP地址。但是,云主机的公网IP 通常是网络提供商动态分配的,如果变更公网IP地址,防火墙配置就需要经常修改,造成不必要的麻烦。
假设远端云主机B1,IP为122.x.x.x,在上面运行了一个服务,端口为2000,本地主机A1需要访问这个服务。
可以在A1主机上,使用本地端口转发,将发送到A1主机3000端口上的请求转发到B1主机的2000端口。
ssh-L 3000:localhost:2000 root 122.x.x.x
这样,在本地主机A1上可以通过访问http://122.x.x.x:2000来访问远端云主机B1上的服务。
本地机器不能访问远程机器,中间机器可以访问远程机器,本地机器可以**ssh登录到中间机器,这种情况下可以使用本地转发。**
远程端口转发
远程**端口转发,是将发送到远端服务器的请求,转发到本地机器上。**
例如,将远程SSH服务器的5000端口流量转发到你的本地计算机的数据库端口3306上。
ssh -R 5000:localhost:3306 user@example.com -N
两个远程机器不能相互访问,但本地机器可以访问这两个远程机器,这个情况可以使用远程**端口转发。**
动态端口转发
在本地机器和远程机器之间建立一条通信隧道,本地机器特定**端口的流量会通过远程机器转发出去。**
动态端口转发与本地端口转发不同的是,它同时还使用了一层本地的代理服务器(有自己的ip和端口)中间层。
这样服务端看到的流量就是来源于代理服务器,一定程度上保护了客户端。
本地机器可以**ssh登录到远程机器,如果想让远程机器变成SOCK4和SOCK5代理服务器,这种情况可以使用动态端口转发。**
SOCKS(全称为"Socket Secure",最初称为"SOCKet Secure")是一种网络代理协议,用于在客户端和服务器之间传递网络连接的数据包。SOCKS作为一个代理,允许客户端通过代理服务器连接到目标服务器。它通常用于增强网络安全性和提供额外的网络层功能。SOCKS有两个主要版本:SOCKS4和SOCKS5。
SOCKS5:
版本:SOCKS5是SOCKS4的扩展和改进版本。
支持的协议:支持TCP和UDP协议,以及IPv4和IPv6地址。
认证:支持更复杂的认证机制,包括无认证、GSSAPI(Generic Security Service Application Program Interface)、CHAP(Challenge-Handshake Authentication Protocol)和用户名/密码认证。
命令:支持多种命令,包括标准的TCP连接、BIND命令(允许服务器主动连接到客户端)和UDP关联。
安全性:提供了更好的安全性,包括对数据包的完整性和认证的保护。
特性:支持通过代理进行域名解析,而不仅仅是IP地址。
SOCKS代理服务器常用于网络安全和访问控制,它们可以隐藏用户的真实IP地址,提供匿名访问网络的能力,并且可以作为防火墙的一部分来控制对特定资源的访问。由于SOCKS5提供了更全面的网络功能和更好的安全性,它比SOCKS4更受欢迎和广泛使用。