mholt / caddy-l4

Layer 4 (TCP/UDP) app for Caddy
Apache License 2.0
910 stars 69 forks source link

Example of multiplexing ssh-over-tls to different hosts #101

Open coolaj86 opened 1 year ago

coolaj86 commented 1 year ago

I've got an example of multiplexing SSH over HTTPS to different hosts.

If there's a way to maintain the same functionality, but simplify the config, I'd love to know how to do it.

Is there some sort of template system I could use to say "here's a ruleset" and then "apply that ruleset, but for these hostnames"?

I believe the caddyfile has something like that.

1. Install xcaddy

pushd /tmp/

curl -o ./xcaddy_0.3.2_linux_amd64.tar.gz \
    -L https://github.com/caddyserver/xcaddy/releases/download/v0.3.2/xcaddy_0.3.2_linux_amd64.tar.gz

tar xvf ./xcaddy_0.3.2_linux_amd64.tar.gz

chmod a+x caddy

mkdir -p ~/bin/
mv ./caddy ~/bin/caddy

popd

2. Build with layer4

Needs layer4, l4tls, l4ssh, l4proxy.

We'll be using duckdns for real TLS certs since many clients don't issue SNI at all in -k, --insecure mode, and therefore cannot test against self-issued certs without putting the cert in the client's chain.

#!/bin/sh

export XCADDY_SETCAP=1
#export XCADDY_SUDO=0
export XCADDY_SKIP_CLEANUP=1

xcaddy build \
    --with github.com/mholt/caddy-l4/layer4 \
    --with github.com/mholt/caddy-l4/modules/l4tls \
    --with github.com/mholt/caddy-l4/modules/l4subroute \
    --with github.com/mholt/caddy-l4/modules/l4http \
    --with github.com/mholt/caddy-l4/modules/l4ssh \
    --with github.com/mholt/caddy-l4/modules/l4proxy \
    --with github.com/caddy-dns/duckdns

2b. How to run

Assuming an .env with the DUCKDNS_API_TOKEN:

caddy run --envfile ./.env --config ./config.json

3. Multiplexing HTTPS and SSH

Configure SSH Client

You can use openssl s_client, socat, sclient, or one of many other tls-terminating tools. Demonstrating with sclient because it is purpose-built for this use case and works on Windows.

~/.ssh/config:

Host example-a
    Hostname example-a.duckdns.org
    ProxyCommand sclient %h

Host example-b
    Hostname example-b.duckdns.org
    ProxyCommand sclient %h

Configure Caddy

This feels a little too verbose. Is there a some sort of ruleset that could be created and then applied per-host?

{
  "logging": {
    "logs": {
      "default": {
        "level": "DEBUG"
      }
    }
  },
  "apps": {
    "layer4": {
      "servers": {
        "my-sshttp": {
          "listen": ["0.0.0.0:443"],
          "routes": [
            {
              "match": [
                {
                  "tls": {}
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "match": [
                        {
                          "tls": {
                            "sni": ["example-a.duckdns.org"]
                          }
                        }
                      ],
                      "handle": [
                        {
                          "handler": "tls",
                          "connection_policies": [
                            {
                              "alpn": ["http/1.1"]
                            }
                          ]
                        },
                        {
                          "handler": "subroute",
                          "routes": [
                            {
                              "match": [{ "ssh": {} }],
                              "handle": [
                                {
                                  "handler": "proxy",
                                  "upstreams": [{ "dial": ["localhost:22"] }]
                                }
                              ]
                            },
                            {
                              "match": [
                                {
                                  "http": [
                                    { "host": ["example-a.duckdns.org"] }
                                  ]
                                }
                              ],
                              "handle": [
                                {
                                  "handler": "proxy",
                                  "upstreams": [{ "dial": ["localhost:3000"] }]
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "match": [
                        {
                          "tls": {
                            "sni": ["example-b.duckdns.org"]
                          }
                        }
                      ],
                      "handle": [
                        {
                          "handler": "tls",
                          "connection_policies": [
                            {
                              "alpn": ["http/1.1"]
                            }
                          ]
                        },
                        {
                          "handler": "subroute",
                          "routes": [
                            {
                              "match": [{ "ssh": {} }],
                              "handle": [
                                {
                                  "handler": "proxy",
                                  "upstreams": [{ "dial": ["10.0.0.222:22"] }]
                                }
                              ]
                            },
                            {
                              "match": [
                                {
                                  "http": [
                                    { "host": ["example-b.duckdns.org"] }
                                  ]
                                }
                              ],
                              "handle": [
                                {
                                  "handler": "proxy",
                                  "upstreams": [{ "dial": ["10.0.0.222:3000"] }]
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    },
    "tls": {
      "certificates": {
        "automate": [
          "localhost",
          "192.168.0.4",
          "example-a.duckdns.org",
          "example-b.duckdns.org"
        ]
      },
      "automation": {
        "policies": [
          {
            "subjects": ["localhost", "192.168.0.4"],
            "issuers": [
              {
                "module": "internal"
              }
            ]
          },
          {
            "subjects": ["example-a.duckdns.org", "example-b.duckdns.org"],
            "issuers": [
              {
                "challenges": {
                  "dns": {
                    "provider": {
                      "api_token": "{env.DUCKDNS_API_TOKEN}",
                      "name": "duckdns"
                    }
                  }
                },
                "module": "acme"
              },
              {
                "challenges": {
                  "dns": {
                    "provider": {
                      "api_token": "{env.DUCKDNS_API_TOKEN}",
                      "name": "duckdns"
                    }
                  }
                },
                "module": "zerossl"
              }
            ]
          }
        ]
      }
    }
  }
}

TODO

How to pass HTTP traffic through to Caddy's normal http handler? Perhaps related to https://github.com/mholt/caddy-l4/issues/70 and https://github.com/mholt/caddy-l4/pull/78?

How to simplify?

coolaj86 commented 1 year ago

@mholt Any idea how to get that to listen on port 80 and respond to http strategy ACME challenges?

francislavoie commented 1 year ago

Use the http app to do that.