Example of multiplexing ssh-over-tls to different hosts
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
- Download
xcaddy - make it executable
- put it in your PATH
(pathmancan help if you're unfamiliar with that)
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
- example-a.duckdns.org
- TLS is terminated
- ssh goes to localhost on port 22
- http goes to localhost http on port 3000
- host header match to prevent SNI/Host mismatch attack
- the same is true for exampl-b.duckdns.org
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?
@mholt Any idea how to get that to listen on port 80 and respond to http strategy ACME challenges?
Use the http app to do that.