Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Dynamic proxy based on SNI #241

Open
vk496 opened this issue Aug 24, 2024 · 5 comments
Open

Dynamic proxy based on SNI #241

vk496 opened this issue Aug 24, 2024 · 5 comments

Comments

@vk496
Copy link

vk496 commented Aug 24, 2024

Hello.

Related to #240, there are any way to route traffic based on the SNI? For example:

Instead of static configuration like this:


	layer4 {
		:18000 {
			@app1 tls sni app1.internal
			route @app1 {
				tls
				proxy 192.168.1.155:3333
			}

			@app2 tls sni app2.internal
			route @app2 {
				tls
				proxy 192.168.1.156:3333
			}

			@app3 tls sni app3.internal
			route @app3 {
				tls
				proxy 192.168.1.157:3333
			}

			@insecure http
			@secure tls

			route @insecure @secure {
				proxy localhost:443
			}
		}
	}

Have the app routed directly based on the SNI. Maybe similar to this:


	layer4 {
		:18000 {
                        #regex or simple wildcard
			@app tls sni app[0-9]{1,2}.internal
			route @app {
				tls
				proxy ${sni}:3333 # or maybe object access ${app.tls.sni}
			}

			@insecure http
			@secure tls

			route @insecure @secure {
				proxy localhost:443
			}
		}
	}

The idea behind is that there will be multiple instances of the app, and the client could route based on the SNI value. Im not sure how ports treatement could be possible (or regex/parser of the sni), but at least using the sni directly could reduce a lot the config redundancy.

@mholt
Copy link
Owner

mholt commented Aug 24, 2024

So basically the SNI matcher needs to support regex, I guess? Or a new sni_regexp matcher?

@vk496
Copy link
Author

vk496 commented Aug 24, 2024

So basically the SNI matcher needs to support regex, I guess? Or a new sni_regexp matcher?

That's one of the things. And the other, reference those attributes, so it can be "reused". Even better, if we're able to use basic functions like split or array acces, we could "construct" proxy destination from sni string. Something like:

# sni example: app02_3333_.internal
@app tls sni app[0-9]{1,2}_[0-9]{1,4}_.internal
	route @app {
		tls
		proxy ${app.tls.sni.split("_")[0]}:${app.tls.sni.split("_")[1]}
	}

Im not fluent in Go, but the idea is simple: regex on SNI for matching and then accessing what was matched (and if possible, manipulate/parse it)

@vnxme
Copy link
Collaborator

vnxme commented Sep 9, 2024

Hi! My 2 cents:

  • proxy ${sni}:3333 # or maybe object access ${app.tls.sni} is already implemented by tls matcher, use {l4.tls.server_name}.
  • sni (sub)matcher is a part of Caddy, not caddy-l4. Whether it is an expanded syntax for sni matcher or a new sni_regexp matcher, it would be more logical to put it to modules/caddytls/matchers.go of the mainline.

Even better, if we're able to use basic functions like split or array acces, we could "construct" proxy destination from sni string.

You may also implement your own sni_advanced matcher with any syntax you like that perfectly satisfies your needs and build Caddy with it.

@sorenisanerd
Copy link

I'm using something like this in my Caddyfile to perform SRV lookups to identify backends for HTTP:

*.srv.example.com {
	map {host} {consul_service} {
		~(.*)\.srv\.example\.com$ "${1}.service.consul"
	}
	reverse_proxy {
		dynamic srv {consul_service} {
			resolvers "10.10.10.10"
		}
	}
}

Being able to do the same for L4 would be amazing.

@lelemka0
Copy link

I'm trying out the new sni_regexp matcher, but I ran into 2 problems.

1. Unable to obtain matching results

layer4 {
	:2000 {
		@test tls sni_regexp ^([0-9]{1})\.test$
		route @test {
			proxy AAA{tls.regexp.1}:9000
		}
	}
}
{"level":"error","ts":1737391062.3122277,"logger":"layer4","msg":"handling connection","remote":"****","error":"dial tcp: lookup AAA on 127.0.0.53:53: no such host"}

2. proxy cannot accept a dynamic port number (placeholders in port)

@test tls sni_regexp ^([0-9]{1})\.test$
route @test {
	proxy localhost:900{tls.regexp.1}
}
Error: loading initial config: loading new config: loading layer4 app module: provision layer4: server 'srv0': route 0: position 0: loading module 'proxy': provision layer4.handlers.proxy: upstream 0: invalid start port: strconv.ParseUint: parsing "900{tls.regexp.1}": invalid syntax

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants