├── Dockerfile ├── LICENSE ├── README.md ├── ssh-client.py └── templates └── deployment.yaml /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.6 2 | RUN apk update && apk add openssh-client bash 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 George Fleury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SSH Tunnel as a Kubernetes Service 2 | 3 | Creates a Kubernetes Service/Deployment/ConfigMap allowing to have SSH tunnels as Kubernetes services. 4 | For development environments with Kubernetes. 5 | 6 | 7 | ## Example: 8 | Run: 9 | 10 | ``` 11 | $ ssh-client.py 3306:internal-mysql.example.com:3306 admin@bastion-host.example.com ~/.ssh/id_rsa ssh-tunnel-service-mysql 12 | $ kubectl apply -f ssh-tunnel-service-mysql.yaml 13 | ``` 14 | 15 | Next step your Kubernetes cluster should have a service available with the hostname `ssh-tunnel-service-mysql.svc.cluster.local` listening to the port 3306. 16 | 17 | 18 | # Licence 19 | MIT 20 | 21 | 22 | -------------------------------------------------------------------------------- /ssh-client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from jinja2 import FileSystemLoader, Environment 4 | 5 | def render_from_template(directory, template_name, **kwargs): 6 | loader = FileSystemLoader(directory) 7 | env = Environment(loader=loader) 8 | template = env.get_template(template_name) 9 | return template.render(kwargs) 10 | 11 | 12 | 13 | if len(sys.argv) < 3: 14 | print('Use: {} PPORT:host:DPORT user@destination_host ssh_private_key_file k8s_service_name'.format(sys.argv[0])) 15 | exit() 16 | 17 | servicePort = sys.argv[1].partition(":")[0] 18 | 19 | with open(sys.argv[3], 'r') as myfile: 20 | sshKey = myfile.read() 21 | 22 | varsz = { 23 | "serviceName": sys.argv[4], 24 | "servicePort": servicePort, 25 | "sshKey": sshKey, 26 | "sshUserHost": sys.argv[2], 27 | "sshProxyParameter": sys.argv[1] 28 | } 29 | 30 | deployment = render_from_template("templates", "deployment.yaml", values=varsz) 31 | 32 | with open(sys.argv[4] + ".yaml", 'w') as myfile: 33 | myfile.write(deployment) 34 | 35 | -------------------------------------------------------------------------------- /templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ values.serviceName }} 5 | labels: 6 | run: {{ values.serviceName }} 7 | spec: 8 | ports: 9 | - port: {{ values.servicePort }} 10 | protocol: TCP 11 | selector: 12 | run: {{ values.serviceName }} 13 | --- 14 | apiVersion: v1 15 | kind: ConfigMap 16 | metadata: 17 | name: {{ values.serviceName }} 18 | namespace: default 19 | data: 20 | ssh_key: | 21 | {{ values.sshKey |indent(4, True) }} 22 | --- 23 | apiVersion: apps/v1 24 | kind: Deployment 25 | metadata: 26 | name: {{ values.serviceName }} 27 | spec: 28 | selector: 29 | matchLabels: 30 | run: {{ values.serviceName }} 31 | replicas: 1 32 | template: 33 | metadata: 34 | labels: 35 | run: {{ values.serviceName }} 36 | spec: 37 | containers: 38 | - name: {{ values.serviceName }} 39 | image: gfleury/ssh-client 40 | ports: 41 | - containerPort: {{ values.servicePort }} 42 | command: ["ssh"] 43 | args: 44 | - "-i" 45 | - "/etc/sshkeys/ssh_key" 46 | - "-o" 47 | - "UserKnownHostsFile=/dev/null" 48 | - "-o" 49 | - "StrictHostKeyChecking=no" 50 | - "-N" 51 | - "-L" 52 | - "0.0.0.0:{{ values.sshProxyParameter }}" 53 | - "{{ values.sshUserHost }}" 54 | volumeMounts: 55 | - name: sshkey-volume 56 | mountPath: /etc/sshkeys 57 | volumes: 58 | - name: sshkey-volume 59 | configMap: 60 | name: {{ values.serviceName }} 61 | defaultMode: 256 62 | restartPolicy: Always --------------------------------------------------------------------------------