Routing SSH traffic with Traefik v2

Is there a document on how to route such ssh traffic with traefik v2?
many thanks!

Hello,

Thanks Idez! Is there an example for it. And I understand that routing tcp traffic like ssh wont work base on domain. It should be per port routing.

Exactly, yeah. SSH doesn’t use TLS, therefore we cant route SSH based on domain. You have to create an own entrypoint (port) then per SSH you want to proxy basically.

Hi SantoDE,

Do you have any example?
Thanks!

Sadly, not at hand. I'll add that to my ToDo list :slight_smile:

What you will need is a seperate entrypoint and a seperate router attached to each of this entrypoints (https://docs.traefik.io/v2.0/routing/routers/). The rule in the router then has to be rule = "Host(`*)"

then it should work :slight_smile:

Here is a "quick-and-dirty" example with Gitea, a ligthweight Git Server,
which exposes both HTTP and SSH using Traefik v2.0.0-beta1.

The SSH part is handle through a TCP router.

version: '3'

services:
  edge:
    image: traefik:v2.0.0-beta1
    command:
      - --providers.docker
      - --entrypoints.http.address=:80
      - --entrypoints.ssh.address=:22
      - --api
    ports:
      - "80:80"
      - "8080:8080"
      - "22:22"
    labels:
      - "traefik.enable=false"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"

  gitea:
    image: gitea/gitea:1.9
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.gitea-web.rule=Host(`localhost`)"
      - "traefik.http.routers.gitea-web.entrypoints=http"
      - "traefik.http.routers.gitea-web.service=gitea-web-svc"
      - "traefik.http.services.gitea-web-svc.loadbalancer.server.port=3000"
      - "traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)"
      - "traefik.tcp.routers.gitea-ssh.entrypoints=ssh"
      - "traefik.tcp.routers.gitea-ssh.service=gitea-ssh-svc"
      - "traefik.tcp.services.gitea-ssh-svc.loadbalancer.server.port=22"

Hope it helps.

9 Likes

Hello, regarding the HostSNI rule on TCP routers, could we use the subdomain? I have tried and see it works with a wildcard (*) rule but failed on domain rule, here is my example

## Static configuration
[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.ssh]
    address = ":2222"

## Dynamic configuration
[tcp.routers]
  [tcp.routers.ssh]
    entryPoints = ["ssh"]
    rule = "HostSNI(`ssh.example.com`)" # <~~ this one will terminal any ssh request from putty
    service = "ssh"

[tcp.services]
  [tcp.services.ssh.loadBalancer]
    [[tcp.services.ssh.loadBalancer.servers]]
      address = "127.0.0.1:22"
1 Like

Hi @nhvu1988, using the rule HostSNI() with something else than the wildcard (*) does not work with SSH.

The reason is that SSH does not support SNI (Server Name Indication): https://stackoverflow.com/questions/52455205/does-the-ssh-protocol-send-the-remote-name-to-the-remote-machine.

So the only possibility is to have one SSH service per entrypoint (e.g. per port).

1 Like

If I use the below it gives me a red exclamation mark for servers for the URL. Everything else is green.

[tcp.services]
  [tcp.services.ssh.loadBalancer]
    [[tcp.services.ssh.loadBalancer.servers]]
      address = "192.168.*.*:22"

Saying that "SSH does not support SNI" is correct but a bit misleading. The thing is, SNI is a TLS protocol term, and SSH has nothing to do with TLS protocol.

Traefik supports routing per host via SNI because it's the only standard way to do that over TCP, but TCP itself does not have SNI, so you have to use TLS.

Now the question, how the heck can you use TLS for an arbitrary TCP connection? If you start googling you will see that TLS is most often associated with protocols HTTPs, IMAP, STMP and others.

SSH is certainly not among them. Hence, the conclusion that you can only have one SSH service per entrypoint.

You can workaround this if you wrap your SSH connection in TLS with stunnel, I tried it and it works. It's just not very practical, because you will have to run stunnel on every SSH client to do the wrapping, and you still will have to use multiple ports, only this time on the client.

1 Like

Hi! I'm a bit late to the party, but found this via google, trying to do the same thing.
For those who have not found a suitable solution, maybe you could use sslh, which is an ssh/tls multiplexer. I have it working and just wanted to see whether I can skip sslh from now on, now that I found traefik. So the answer is no, I'll be using sslh together with traefik.

sslh can distinguish several protocols and forward connection attempts to the configured handler:
ssh, openvpn, tinc, xmpp, http, tls, ssl, adb, socks5, other.

You can find it at https://github.com/yrutschle/sslh or in the repo of the linux distribution of your choice.

HAProxy allows you to do this without requiring an additional tool (sslh). An example can be found here:

https://coolaj86.com/articles/adventures-in-haproxy-tcp-tls-https-ssh-openvpn/

Or perhaps easier to digest

https://blog.chmd.fr/ssh-over-ssl-episode-4-a-haproxy-based-configuration.html

Short: This is achieved is by inspecting the TCP stream, if it starts with SSH-2.0 which conform RFC-4253 ...

1 Like

Ping @juliens on this (peaking first SSH bytes), as food for thought.

Challenge on this, @darkl0rd, is the maintenance effort around the bazilions protocol that could have a specific "bytes peak" and an associated behavior.

I do agree, if you do pursue this path I think you should restrict it to only SSH or make it flexible so that it can be configured by the end user what string to look for when peeking.

Have you a working exemple ?