在 Kubernetes 裡面起一台 SSH 跳板機

山不轉路轉,沒有跳板就自己開

當我們需要對遠端機器進行操作時,經常會被防火牆卡住,這時候就需要透過代理伺服器來解決問題。在眾多手段中 SSH tunnel 又特別順手,作為一個歷史悠久且可靠的管道,為數不少的服務、軟體都與它都有相當程度的整合,進而帶來極高的靈活性。

然後我遇到的問題是該環境裡面沒有建置公用的 SSH 跳板機(jump server / bastion machine)。山不轉路轉,沒有公用的機器就手動開一個!

手邊能夠迅速開一個機器起來的手段變是 Kubernetes,所以本文的目標就是利用該 Kubernete 叢集作為我的跳板機;又因為我的目標不是接觸叢集上的資源,所以 kubectl port-forward 指令並不滿足我的需求。

設置 SSH 伺服器

我打算直接開一個新的 Pod 來當伺服器,容器則直接拉網路上的資源而非自建,然後我選擇了 linuxserver/openssh-server

設置公鑰

我們需要把自己的公鑰放進伺服器端的 authorized_keys 才能登入。

上面選用的容器提供了以下幾個環境變數可用於設置公鑰:

-e PUBLIC_KEY=yourpublickey `#optional` \
-e PUBLIC_KEY_FILE=/path/to/file `#optional` \
-e PUBLIC_KEY_DIR=/path/to/directory/containing/_only_/pubkeys `#optional` \
-e PUBLIC_KEY_URL=https://github.com/username.keys `#optional` \

我想偷懶,所以直接走 PUBLIC_KEY,等等直接丟在 Pod 環境變數上。

開放 SSH tunnel

根據 sshd_config (5) 說明,sshd 預設是沒有開放 SSH tunnel 功能的,故我們需要去修改 /etc/ssh/sshd_config

上述選用的容器提供了 Custom Scripts 功能,當我們將腳本放到指定路徑下、容器會在啟動時自動執行,這大幅地減少設置與重新載入 sshd 的麻煩。

開個 ConfigMap 來存腳本:

---
apiVersion: v1
kind: ConfigMap
metadata:
name: bastion
data:
ssh-init: |
#!/bin/bash
sed -i 's/#AllowAgentForwarding yes/AllowAgentForwarding yes/g' /etc/ssh/sshd_config
sed -i 's/AllowTcpForwarding no/AllowTcpForwarding yes/g' /etc/ssh/sshd_config
sed -i 's/GatewayPorts no/GatewayPorts yes/g' /etc/ssh/sshd_config
sed -i 's/PermitTunnel no/PermitTunnel yes/g' /etc/ssh/sshd_config
sed -i 's/X11Forwarding no/X11Forwarding yes/g' /etc/ssh/sshd_config

理論上這裡的唯一一項必須是 PermitTunnel 要打開,但上面的腳本多開了些權限方便做事。

設定 Deployment

最後就是開個 Deployment 來上架 SSH 伺服器:

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bastion
spec:
selector:
matchLabels:
app.kubernetes.io/name: bastion
app.kubernetes.io/instance: bastion
template:
metadata:
labels:
app.kubernetes.io/name: bastion
app.kubernetes.io/instance: bastion
spec:
containers:
- name: openssh-server
image: linuxserver/openssh-server:latest
ports:
- containerPort: 2222
env:
- name: PUBLIC_KEY
value: ssh-ed25519 REDACTED
volumeMounts:
- name: custom-cont-init
mountPath: /custom-cont-init.d
volumes:
- name: custom-cont-init
configMap:
name: bastion

介紹一下這裡的幾個寫死的參數:

  • containerPort 設定為 2222,是為 linuxserver/openssh-server 的預設 SSH 接口
  • 環境變數中的 PUBLIC_KEY 就是稍後要連線進去用的公鑰
  • volumeMounts 的路徑(/custom-cont-init.d)是此容器放置 Custom Scripts 的位置,我們把 ConfigMap 接到這個位子上、在容器在初始化階段時裡面放置的腳本會自動被執行

再順便開個 Service 等等連線可以更順手:

---
apiVersion: v1
kind: Service
metadata:
name: bastion
spec:
type: ClusterIP
ports:
- port: 2222
targetPort: 2222
protocol: TCP
name: ssh
selector:
app.kubernetes.io/name: bastion
app.kubernetes.io/instance: bastion

連線

這還是免不了需要使用到 port-forward 指令:

$ kube port-forward svc/bastion 2222:2222

這樣就會把本地端的 2222 阜轉到容器的 2222 阜上去,接下來嘗試登入看看:

$ ssh linuxserver.io@localhost -p 2222
ED25519 key fingerprint is SHA256:REDACTED.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:2222' (ED25519) to the list of known hosts.
Welcome to OpenSSH Server
bastion-544d566fdc-pzs2k:~$

連線參數的部分,所使用的容器的預設使用者名稱為 linuxserver.io,然後連到本機端 2222 阜應該很好理解。

成功!然後就可以開始讓各種工具走 localhost:2222 來建 SSH 隧道了。