sshpiper 在 Kubernetes 上的应用
sshpiper 在 Kubernetes 上的应用
介绍
一个反向代理目标服务器的 proxy,客户端想请求某个 ssh 服务器,直接请求的是 sshpiper 服务,再经由 sshpiper 服务转发到对应的 ssh 服务器,相当于一个中间人。
一开始并不理解这种组件的用处,但实际用了之后感觉还是蛮有意思的。
设想有这样一种场景,你有多个 ssh 服务器,你可能要不停切换 ssh 服务器,这过程中有可能使用不同的 ssh 秘钥来连接。而且如果是想从公司外网连进来,ssh 服务器的端口要对外网开放,会有很大的安全隐患。
ssh 可以通过密码、秘钥两种方式鉴权连接,密码方式相对简单,我也主要是使用秘钥模式连接的,这里主要介绍秘钥连接。
原理
完成整个连接过程,需要有两套秘钥(即两套公钥私钥),我们这里分别称为 PublicKey_X,PrivateKey_X,PublicKey_Y,PrivateKey_Y,其中:
PrivateKey_X由客户端持有PublicKey_X由 sshpiper 持有(在 k8s 中由集群保存)PrivateKey_Y由 sshpiper 持有(在 k8s 中由集群保存)PublicKey_Y由 ssh 服务器持有 (写入.ssh/authorized_keys中)
客户端持 PrivateKey_X ssh 请求 sshpiper,sshpiper 使用 PublicKey_X 进行校验,校验之后 sshpiper 持 PrivateKey_Y 请求服务器,服务器持 PublicKey_Y 进行校验。
此处提及的 ssh 密钥,公钥均为 ssh-rsa XXXXXX 形式,私钥均为 pem 形式,即类似:-----BEGIN PRIVATE KEY----- 开头,-----END PRIVATE KEY----- 结尾
优点(个人总结)
- ssh 服务器均可以不暴露外网端口,所有外网请求由 sshpiper 代理即可。
- 可以使用同一套
PrivateKey_X和PublicKey_X登录多台服务器。 - 可以利用
PublicKey_X,PrivateKey_X更好地控制 ssh 访问服务器的权限。
Kubernetes 上的应用
目标:运行一个可以通过 sshpiper 访问的 pod。
安装
在 kubernetes Pod 中使用 sshpiper,先按照此处文档在集群中安装和部署 sshpiper 服务:
https://github.com/tg123/sshpiper/tree/master/plugin/kubernetes
我使用了手动安装,共两步:
- 安装 CRD
- 启动 sshpiper 服务
启动 sshpiper 服务时,使用如下 yaml 配置:
# sshpiper service
---
apiVersion: v1
kind: Service
metadata:
name: sshpiper
spec:
selector:
app: sshpiper
ports:
- protocol: TCP
port: 2222
targetPort: 2222
nodePort: 30022
type: NodePort
---
apiVersion: v1
data:
server_key: | # 此配置暂时没发现用处,直接使用官方提供的样例中的值即可
LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0KYjNCbGJuTnphQzFyWlhrdGRqRUFBQUFBQkc1dmJtVUFBQUFFYm05dVpRQUFBQUFBQUFBQkFBQUFNd0FBQUF0emMyZ3RaVwpReU5UVXhPUUFBQUNCWUhWV01lNzVDZ3Rzdm5rOWlTekJFU3hSdjdMb3U3K0tVbndmb3VnNzcxZ0FBQUpEQnArS0d3YWZpCmhnQUFBQXR6YzJndFpXUXlOVFV4T1FBQUFDQllIVldNZTc1Q2d0c3ZuazlpU3pCRVN4UnY3TG91NytLVW53Zm91Zzc3MWcKQUFBRUJKSDU3eTFaRTUxbVo2a2VsWUR0eDQ1ajBhZGdsUk5CY0pZOE94YTY4TEJWZ2RWWXg3dmtLQzJ5K2VUMkpMTUVSTApGRy9zdWk3djRwU2ZCK2k2RHZ2V0FBQUFEV0p2YkdsaGJrQjFZblZ1ZEhVPQotLS0tLUVORCBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0K
kind: Secret
metadata:
name: sshpiper-server-key
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sshpiper-deployment
labels:
app: sshpiper
spec:
replicas: 1
selector:
matchLabels:
app: sshpiper
template:
metadata:
labels:
app: sshpiper
spec:
serviceAccountName: sshpiper-account
containers:
- name: sshpiper
imagePullPolicy: IfNotPresent
image: farmer1992/sshpiperd:latest
ports:
- containerPort: 2222
env:
- name: PLUGIN
value: "kubernetes"
- name: SSHPIPERD_SERVER_KEY
value: "/serverkey/ssh_host_ed25519_key"
- name: SSHPIPERD_LOG_LEVEL
value: "trace"
volumeMounts:
- name: sshpiper-server-key
mountPath: "/serverkey/"
readOnly: true
volumes:
- name: sshpiper-server-key
secret:
secretName: sshpiper-server-key
items:
- key: server_key
path: ssh_host_ed25519_key
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: sshpiper-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: ["sshpiper.com"]
resources: ["pipes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-sshpiper
subjects:
- kind: ServiceAccount
name: sshpiper-account
roleRef:
kind: Role
name: sshpiper-reader
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sshpiper-account
- 官方样例这里 Service 使用了默认的 ClusterIP 类型,但如果要通过外网访问,需要使用 LoadBalancer 或 NodePort 类型。我使用了 NodePort 类型,并映射给了 30022 端口
- 官方样例设置了一个 Secret
sshpiper-server-key, 但并没有说明其用处。不过这个似乎对使用 sshpiper 没有影响,直接填充官方给的默认值即可。默认值在 ReadMe 中没有给出,可以看上面给的 github 路径下的sample.yaml文件
启动成功后如下图:

镜像准备
由于需要访问 ssh 服务,所有业务 Pod 的基础镜像需要运行 ssh 服务并暴露 ssh 接口。还需要将 PublicKey_Y 写入目标容器的 .ssh/authorized_keys 文件中。
这里我用官方使用的 lscr.io/linuxserver/openssh-server:latest 镜像,它提供了 PUBLIC_KEY 环境变量,在启动时自动将其中配置的 public_key 写入 .ssh/authorized_keys 文件中。
除此之外,亦可以通过设置 ConfigMap,将 public_key 挂载进容器的路径下。
启动容器
apiVersion: apps/v1
kind: Deployment
metadata:
name: host-publickey
spec:
replicas: 3
selector:
matchLabels:
app: host-publickey
template:
metadata:
labels:
app: host-publickey
spec:
containers:
- name: host-publickey
image: lscr.io/linuxserver/openssh-server:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 2222
env:
- name: USER_NAME
value: "user"
- name: PUBLIC_KEY
value: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDgMF4AKRaRf3V2+6T7rluYW37t5TwuQDcdT966jKhKNHBkLHuT/YhBuWkpHuGR3Wh3S3zGAZ73vZ8zJHXsOPmBakkxPa9lqSHMj7Y0mN/0XvpcIHIdphzKUiEIP65N6OG2ZtYaZYti8wDNs1rW+V2Vx5IlOcT8IiNQ5FNvOozS9w=="
---
apiVersion: v1
kind: Service
metadata:
name: host-publickey
spec:
selector:
app: host-publickey
ports:
- protocol: TCP
port: 2222
USER_NAME字段会为你自动创建对应的用户。PUBLIC_KEY字段填写的是 ssh 公钥明文,非 pem 形式。这里填入的就是上面所说的PublicKey_Y
创建 pipe
pipe 就是第一步安装中安装的 CRD。它负责定义 from 和 to 的相关信息,以及保存前面提到的 PublicKey_X 和 PrivateKey_Y
apiVersion: v1
data:
ssh-privatekey: |
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWGdJQkFBS0JnUURnTUY0QUtSYVJmM1YyKzZUN3JsdVlXMzd0NVR3dVFEY2RUOTY2aktoS05IQmtMSHVUCi9ZaEJ1V2twSHVHUjNXaDNTM3pHQVo3M3ZaOHpKSFhzT1BtQmFra3hQYTlscVNITWo3WTBtTi8wWHZwY0lISWQKcGh6S1VpRUlQNjVONk9HMlp0WWFaWXRpOHdETnMxclcrVjJWeDVJbE9jVDhJaU5RNUZOdk9velM5d0lEQVFBQgpBb0dCQU5ZcG5rS3MvYUEwa0hQL1pOWUE5QU1SdEtseHlSR3R5bmkzNmQ5dnF2eG9KODJxS010dzhROUlIY3RvCmNyZXpPSzV0Y0Y1L0FldE1PNTdSZjgwUGlGaHNvenowWnJkU2dzNXNZa2N4aS9CQTNBa05UNXh4aVo0STQxOEoKRStVemZnVDFOT0Y5bnNzbWoxQWVnNlJ1d3RYbVJkWElRUm1wcEtVVjBNcENERGJoQWtFQStiTk0wQzhxaHFpeQpyVWg0VFpSUWhGc0FyRTMzL1NoVzNobU5pdUd0Qjc3QnVXSFBWVDlnaDFpV0Mwby9CV0FSZUlSR05WbUdhZGtTClUxZCswdk8wdVFKQkFPWFlUT1dLZEd6MWZKVGJNWDdnNUVsRmV0NVpEV3hwZFEzWWpiUEQzRDg2cDNaN1JrVHMKQ1RQdDd1VjVzZXpZZWJKK1B5SnI5WVEzOXBybm02S0xUUzhDUUhVZEpYL1hQMmpkSXNDblp0VnNKTCtQTnllWgpnaUNZbFBXaW9vSnJDbzdCWjNjZGF2TWV3SlY2ZFJWaWcyQndDSUd2K0lYNU1WUGYzZnA4NVJ6bjlQRUNRUURiClhvU1dHSDFpZVRLOGlEQkhYckhEMVJLZUlQU1U0bG9jS3ZHai8yMjQwMng5d3M2Z2ZYK1RGcWFLVW9vaytiKzkKUW8xVGR5TFBYUEo3aWs2YTVzVjFBa0VBN1FPc1lPZ0FXeFZzbCttdFBva3VkS3FUa2lYaDNVNFp5Si8zWDZ5YgpwVjVkQWtLQlRMdjVwMFdEQlcyVDB5UmpaMGNDME01ckkyMHI2ejdnWktCeFJnPT0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0=
kind: Secret
metadata:
name: host-publickey-key
type: kubernetes.io/ssh-auth
---
apiVersion: sshpiper.com/v1beta1
kind: Pipe
metadata:
name: pipe-publickey
annotations:
privatekey_field_name: ssh-privatekey # this is optional, default is ssh-privatekey
spec:
from:
- username: "test"
authorized_keys_data: "c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFERThzZnBiOXkweVNRMTRsaWpQNnc5QWg2UEF6SC9hdGdXVDB5c3NZL29aSGJONDlwUHk4OWt1NC9ndmUwWEZOcE5HMGN2aThOQ3J5aDdNNDBZWnN1KzlXY1BpR2RXRnVuVG4xMGhWWmVQaGhTQk5WUVByMU16dy9MNHpTb3U2amozdWh4aHI1a3pNdi9pbWY1WFFHT2U5WEVKaTBoK29lbVlPUkxybUNvKzhWUFkvb29SL2tIY3J5L3ZuVVdoek1GYzNXMC9Pck80Q2ZvUlBnc1VabGREVnZmU0toUnlQQllISkJhaHUza0xLWC9VRk9ZRnRzd2lZTWtMWHpwY0JjSmJnUDE0RHFaNGIxZEhmZHp5MGZCYThyTVJFWEI5NHErNlhnV1cvRDlKbUQzaURQd2pRengySVRTZXRCSUlhYjlvYWkzRWd0TTdDQk13ZE5tdk5mQXQgNzYwNzUyNTgwQHFxLmNvbQo="
to:
host: 10-244-3-30.default.pod.cluster.local:2222
username: "user"
private_key_secret:
name: host-publickey-key
ignore_hostkey: true
Piper 中所填写的 privateKey,
- Secret 中定义即为
PrivateKey_Y,经过 base64 编码,用于填写在 to 中,作为 sshpiper 与目标服务器之间的校验。 - Pipe.spec.from.username 是 ssh 请求 sshpiper 的 username,如:
ssh username@sshpiper-ip -p 30022 -i private_key.pem。这里亦可以填写正则匹配,需要设置Pipe.spec.from.username_regex_match: true - Pipe.spec.from.authorized_keys_data 保存
PublicKey_X的 base64 编码,用于客户端发起请求时,sshpiper 会取的这个公钥与用户的private_key.pem进行验证 - Pipe.spec.to.host 是 k8s 集群中任何能路由到 Pod 的方式,可以直接是 Pod 的 ip,也可以是集群内部支持的域名,可以被集群 DNS 解析,也可以是 Pod 的 Service 对应的域名或路由。参考:https://kubernetes.io/zh-cn/docs/concepts/services-networking/dns-pod-service/#pod
- Pipe.spec.to.username 为登录到目标服务器上所用的用户名,这个用户名需要存在于目标服务器
- Pipe.spec.to.private_key_secret 指定了上面定义的 Secret,用于 sshpiper 与目标服务器之间的校验。
注意:
- 如果
Pipe.spec.to.host填写的是 Service 路由,那么每次 ssh 时,进入的 Pod 可能时 Service 管理下的任何一个 Pod。- 此处所填公钥私钥,均为其 base64 编码形式。
上面我使用 pod-ip-addres.namespace.pod.cluster-domain.example 方式作为 to.host 。创建 pipe 之后,通过命令 ssh test@sshpiperIP -p 30022 -i ~/.ssh/id_rsa

- 如果 sshpiper Service 为 NodePort 类型,这里 sshpiperIP 即为 nodeIP,port 即为 nodePort
- 如果真实的生产环境中,这里肯定要用上 LoadBalance,或者 nginx、ingress 等组件,以及配置域名解析等方式来代替明文 NodeIP。
应用场景和业务流程
- 有长期运行的 Pod,而且用户可能频繁进入 Pod 内部进行调试和开发工作。
- 此时用户相当于 client。
- 一般要搭配 ssh 管理模块使用。
- 创建 ssh 密钥,将私钥返回给用户,作为 client 的
PrivateKey_X,提醒用户妥善保管 - ssh 模块保存公钥,即
PublicKey_X,但不保存PrivateKey_X,私钥一旦丢失,密钥将无法正常使用。 - 创建 Pod 时,列出已有 ssh 密钥,用户可选择自己手中已持有的密钥,也可以新创建一对 ssh 密钥。
- 点击确认,系统后台再生成一对 ssh 密钥,作为
PublicKey_Y和PrivateKey_Y。 - 将
PublicKey_Y绑定入 Podauthorized_keys中,同时使用PublicKey_Y和PublicKey_X创建 Pipe 资源
- 创建 ssh 密钥,将私钥返回给用户,作为 client 的
- 创建成功后,用户可使用所持
PrivateKey_X成功登入 Pod 中。
sshpiper 在 Kubernetes 上的应用的更多相关文章
- 微服务开发有道之把项目迁移到Kubernetes上的5个小技巧
我们将在本文中提供5个诀窍帮你将项目迁移到Kubernetes上,这些诀窍来源于过去12个月中OpenFaas社区的经验.下文的内容与Kubernetes 1.8兼容,并且已经应用于OpenFaaS ...
- 在Kubernetes上运行SAP UI5应用(下): 一个例子体会Kubernetes内容器的高可用性和弹性伸缩
上一篇文章 在Kubernetes上运行SAP UI5应用(上),我介绍了如何在Docker里运行一个简单的SAP UI5应用,并且已经成功地将一个包含了这个UI5应用的docker镜像上传到Dock ...
- 深度学习框架TensorFlow在Kubernetes上的实践
什么是TensorFlow TensorFlow是谷歌在去年11月份开源出来的深度学习框架.开篇我们提到过AlphaGo,它的开发团队DeepMind已经宣布之后的所有系统都将基于TensorFlow ...
- 在Kubernetes上运行SAP UI5应用(上)
2018年只剩最后30天了.Jerry在2017年的最后一天,曾经立下一个目标:这个微信公众号在2018年保证至少每周发布一篇SAP原创技术文章. 从Jerry在后台统计的2018全年文章数量来看,这 ...
- 利用 istio 来对运行在 Kubernetes 上的微服务进行管理
尝试在一个准生产环境下,利用 istio 来对运行在 Kubernetes 上的微服务进行管理. 这一篇是第一篇,将一些主要的坑和环境准备工作. 内容较多,因此无法写成手把手教程,希望读者有一定 Ku ...
- 实例演示:如何在Kubernetes上大规模运行CI/CD
本周四晚上8:30,第二期k3s在线培训如约开播!本期课程将介绍k3s的核心架构,如高可用架构以及containerd.一起来进阶探索k3s吧! 报名及观看链接:http://z-mz.cn/PmwZ ...
- 把Spring Cloud Data Flow部署在Kubernetes上,再跑个任务试试
1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! Spring Cloud Data Flow在本地跑得好好的,为什么要部署在Kubernetes上呢?主要是因为Kubern ...
- 在阿里云托管kubernetes上利用 cert-manager 自动签发 TLS 证书[无坑版]
前言 排错的过程是痛苦的也是有趣的. 运维乃至IT,排错能力是拉开人与人之间的重要差距. 本篇会记录我的排错之旅. 由来 现如今我司所有业务都运行在阿里云托管kubernetes环境上,因为前端需要对 ...
- 在Kubernetes上部署应用时我们常忽略的几件事
根据我的经验,大多数人(使用Helm或手动yaml)将应用程序部署到Kubernetes上,然后认为他们就可以一直稳定运行. 然而并非如此,实际使用过程还是遇到了一些"陷阱",我希 ...
- 在kubernetes上运行WASM负载
在kubernetes上运行WASM负载 WASM一般用在前端业务中,但目前有扩展到后端服务的趋势.本文使用Krustlet 将WASM服务部署到kubernetes. 简介 Krustlet 是一个 ...
随机推荐
- Spring中事务嵌套这么用一定得注意了!!
前言 最近项目上有一个使用事务相对复杂的业务场景报错了.在绝大多数情况下,都是风平浪静,没有问题.其实内在暗流涌动,在有些异常情况下就会报错,这种偶然性的问题很有可能就会在暴露到生产上造成事故,那究竟 ...
- Java8 Stream流的合并
最近的需求里有这样一个场景,要校验一个集合中每个对象的多个Id的有效性.比如一个Customer对象,有3个Id:id1,id2,id3,要把这些Id全部取出来,然后去数据库里查询它是否存在. @Da ...
- 2023-02-17:sdl是跨平台的多媒体开发库,请问用go语言如何调用?
2023-02-17:sdl是跨平台的多媒体开发库,请问用go语言如何调用? 答案2023-02-17: 用 github.com/moonfdd/sdl2-go 这个库. 这是我自己写的golang ...
- 2022-03-30:有m个同样的苹果,认为苹果之间无差别, 有n个同样的盘子,认为盘子之间也无差别, 还有,比如5个苹果如果放进3个盘子, 那么1、3、1和1、1、3和3、1、1的放置方法,也认为是
2022-03-30:有m个同样的苹果,认为苹果之间无差别, 有n个同样的盘子,认为盘子之间也无差别, 还有,比如5个苹果如果放进3个盘子, 那么1.3.1和1.1.3和3.1.1的放置方法,也认为是 ...
- Selenium - 元素操作(5) - iframe切换
Selenium - 元素操作 iframe切换 很多时候定位元素时候总是提示元素定位不到的问题,明明元素就在那里,这个时候就要关注你所 定位的元素是否在frame和iframe里面: frame标签 ...
- import MySQLdb as Database ModuleNotFoundError: No module named ‘MySQLdb‘
import MySQLdb as Database ModuleNotFoundError: No module named 'MySQLdb' import MySQLdb as Database ...
- 【重学C++】04 | 说透C++右值引用(上)
文章首发 [重学C++]04 | 说透C++右值引用(上) 引言 大家好,我是只讲技术干货的会玩code,今天是[重学C++]的第四讲,在前面<03 | 手撸C++智能指针实战教程>中,我 ...
- 2014年蓝桥杯C/C++大学B组省赛真题(切面条)
题目描述: 一根高筋拉面,中间切一刀,可以得到2根面条. 如果先对折1次,中间切一刀,可以得到3根面条. 如果连续对折2次,中间切一刀,可以得到5根面条. 那么,连续对折10次,中间切一刀,会得到多少 ...
- 商业智能 (BI) 对企业中每个员工的 5 大好处
本文由葡萄城技术团队于博客园原创并首发.葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 众所周知,商业智能 (BI) 是探索企业数据价值的强大工具,能够帮助企业做出明智的决策.提高绩效 ...
- 从 pheatmap 无缝迁移至 ComplexHeatmap
pheatmap 是一个非常受欢迎的绘制热图的 R 包.ComplexHeatmap 包即是受之启发而来.你可以发现Heatmap()函数中很多参数都与pheatmap()相同.在 pheatmap ...