Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

OpenVPN matcher #251

Merged
merged 1 commit into from
Oct 12, 2024
Merged

OpenVPN matcher #251

merged 1 commit into from
Oct 12, 2024

Conversation

vnxme
Copy link
Collaborator

@vnxme vnxme commented Oct 2, 2024

Summary

This PR introduces an advanced OpenVPN matcher capable of:

  • matching any of the existing control channel security modes (plain, auth, crypt and crypt2);
  • matching based on a digest algorithm (auth mode);
  • matching based on a group key (auth and crypt modes);
  • matching based on a server key (crypt2 mode);
  • matching based on client keys (crypt2 mode);
  • matching both TCP and UDP connections.

These features allow Caddy to multiplex a plethora of diversely configured OpenVPN server instances on a single port. It is a unique OpenVPN routing solution that goes beyond what is offered by SSLH, Haproxy, Nginx, Traefik and OpenVPN internal port-sharing.

Sample config

{
	layer4 {
		:8843 {
			@plain openvpn {
				modes plain
			}
			route @plain {
				proxy localhost:1194
			}
			@auth_sha256 openvpn {
				modes auth
				auth_digest sha256
				group_key_direction normal
				group_key_file /etc/openvpn/ta.key
			}
			route @auth_sha256 {
				proxy localhost:1195
			}
			@auth_sha512 openvpn {
				modes auth
				auth_digest sha512
				group_key_direction inverse
				group_key_file /etc/openvpn/ta.key
			}
			route @auth_sha512 {
				proxy another.machine.local:1295
			}
			@crypt_key1 openvpn {
				modes crypt
				group_key_file /etc/openvpn/ta.key
			}
			route @crypt_key1 {
				proxy localhost:1196
			}
			@crypt_key2 openvpn {
				modes crypt
				group_key_file /etc/openvpn/ta2.key
			}
			route @crypt_key2 {
				proxy another.machine.local:1296
			}
			@crypt2_server openvpn {
				modes crypt2
				server_key_file /etc/openvpn/v2-server.key
			}
			route @crypt2_server {
				proxy localhost:1197
			}
			@crypt2_clients openvpn {
				modes crypt2
				client_key_file /etc/openvpn/v2-client1.key
				client_key_file /etc/openvpn/v2-client2.key
				client_key_file /etc/openvpn/v2-client3.key
			}
			route @crypt2_clients {
				proxy another.machine.local:1297
			}
			route {
				tls
				proxy localhost:8080
			}
		}
		udp/:8843 {
			@anymode openvpn {
				modes plain auth crypt crypt2
			}
			route @anymode {
				proxy udp/localhost:1199
			}
			route {
				echo
			}
		}
	}
}

Disclaimer

  • I have tested this matcher with OpenVPN Server 2.6.8 (Linux), OpenVPN Client 3.5.0 (Windows), Viscosity 1.10.2 (macOS). Please share details and your config should you encounter any issues.
  • As documented in the code, digest support (auth mode) is limited to a number of Golang's standard ones, i.e. some exotic algorithms like SM3 and Whirlpool are not (yet?) included. There is a special case of MD5+SHA1 which is implemented by this matcher but its HMACs don't match those of OpenVPN clients I tested.
  • OpenVPN has to renegotiate connections on Caddy restart. For UDP, after the last packet the client received from the server, up to 120 seconds (default renegotiation interval) may pass before a previously established connection goes live again. TCP connections are usually renegotiated much faster.

@mholt
Copy link
Owner

mholt commented Oct 2, 2024

Wow, this is impressive!

Looks like this may require Go 1.22. That's OK if so, I just want to verify that is intentional.

I have not the ability to test this myself, but I am happy to merge this in for others to test, when you are ready. 💯 Thank you!

@mholt mholt added the enhancement New feature or request label Oct 2, 2024
@vnxme
Copy link
Collaborator Author

vnxme commented Oct 3, 2024

Looks like this may require Go 1.22. That's OK if so, I just want to verify that is intentional.

It is slices.Concat that prevents compiling with Go 1.21. It is used in one place though, so I can rewrite it. I wouldn't call it intentional, but forward-looking - I don't know a reason for sticking to old versions here given the mainline code already requires Go 1.22.

By the way, what do you think of switching to Go 1.22/1.23 in caddy-l4 go.mod? I suppose we would also have to update checks then.

@mholt
Copy link
Owner

mholt commented Oct 4, 2024

Ah, right, I forgot that 1.21 is 2 versions old now. Yeah, we can upgrade the Go versions in the tests and Go.mod to accommodate the slices package. 👍 Want to do that in a new PR? Or I can do it.

@vnxme
Copy link
Collaborator Author

vnxme commented Oct 5, 2024

I've prepared #253. I will rebase this PR once you approve and merge that dependencies upgrade.

I suggest we merge this PR only after #247 is resolved (whether with #249 that works fine for me or anything else). Otherwise the first users of this matcher won't be happy with their broken UDP-based OpenVPN tunnels.

Copy link
Owner

@mholt mholt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing. Thanks so much -- I've merged the PR with the patch, so I will merge this now! 🚀

@mholt mholt merged commit 5764d70 into mholt:master Oct 12, 2024
6 checks passed
@Juoper
Copy link

Juoper commented Nov 6, 2024

@vnxme trying to figure out the different modes you made available, when i want all my openvpn traffic to be routed to 10.10.10.10 could i just use this config:
because currently i only get EOF errors
{ layer4 { :443 { @plain openvpn { modes plain } route @plain { proxy 10.10.10.10:443 } } } } }

@vnxme
Copy link
Collaborator Author

vnxme commented Nov 6, 2024

@Juoper Does it work if you don’t set modes at all (comment out this line)? If you use UDP, modify your listener accordingly. Besides, make sure you don’t serve any https websites on the same port - if you do, then you will need a listener_wrapper for TCP traffic (and disable QUIC if you also need UDP).

@Juoper
Copy link

Juoper commented Nov 7, 2024

i serve https on the same port, could you assist me to tell me how i can implement a listener_wrapper

@vnxme
Copy link
Collaborator Author

vnxme commented Nov 7, 2024

@Juoper The following config will likely satisfy your needs.

{
	servers {
		listener_wrappers {
			layer4 {
				@ovpn openvpn
				route @ovpn {
					# note that a listener wrapper will proxy only TCP connections
					# since the current listener_wrappers implementation ignores UDP
					proxy tcp/10.10.10.10:443
				}
			}
			tls
		}
	}
}

example.com {
	# your options
}

@Juoper
Copy link

Juoper commented Nov 7, 2024

Thanks a lot, this is working now, maybe someone could add some example like this to the docs?

@vnxme
Copy link
Collaborator Author

vnxme commented Nov 8, 2024

@Juoper The caddy-l4 module is very flexible, and it's barely possible to describe all the combinations of the matchers and handlers it includes. But feel free to submit a PR and/or add examples to the wiki.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants