-
Notifications
You must be signed in to change notification settings - Fork 114
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
Add support for negotiating IMAP, SMTP & HTTP on 443 #255
base: main
Are you sure you want to change the base?
Conversation
This PR adds support for negotiating IMAP, SMTP & HTTP on the configured HTTPS port using TLS ALPN. This is intended to be useful for deploying Mox as a chatmail server. The upstream implementation of chatmail servers uses `imap` and `smtp` as the “next protocol” values for IMAP and SMTP, respectively: https://github.com/deltachat/chatmail/blob/main/cmdeploy/src/cmdeploy/nginx/nginx.conf.j2#L16-L17 To test, configure Mox as is standard for the `mox localserve` mode, but add this block under `Listeners.local`: ```sconf AutoconfigHTTPS: Enabled: true Port: 1443 ``` Then run Mox and use OpenSSL’s s_client mode to connect to the local instance: ``` > openssl s_client -quiet -connect localhost:1443 -alpn smtp depth=0 O = mox localserve, CN = localhost verify error:num=18:self signed certificate verify return:1 depth=0 O = mox localserve, CN = localhost verify return:1 220 localhost ESMTP mox 7f5e108+modifications HELO test 550 5.5.0 your ehlo domain does not resolve to an IP address (htqp11_GJOmHEhSs_Y03eg) QUIT 221 2.0.0 okay thanks bye ^C > openssl s_client -quiet -crlf -connect localhost:1443 -alpn imap depth=0 O = mox localserve, CN = localhost verify error:num=18:self signed certificate verify return:1 depth=0 O = mox localserve, CN = localhost verify return:1 * OK [CAPABILITY IMAP4rev2 IMAP4rev1 ENABLE LITERAL+ IDLE SASL-IR BINARY UNSELECT UIDPLUS ESEARCH SEARCHRES MOVE UTF8=ACCEPT LIST-EXTENDED SPECIAL-USE LIST-STATUS AUTH=SCRAM-SHA-256-PLUS AUTH=SCRAM-SHA-256 AUTH=SCRAM-SHA-1-PLUS AUTH=SCRAM-SHA-1 AUTH=CRAM-MD5 ID APPENDLIMIT=9223372036854775807 CONDSTORE QRESYNC STATUS=SIZE QUOTA QUOTA=RES-STORAGE AUTH=PLAIN] mox imap c1 STARTTLS c1 BAD STARTTLS unrecognized syntax/command: tls already active ^C > openssl s_client -quiet -crlf -connect localhost:1443 depth=0 O = mox localserve, CN = localhost verify error:num=18:self signed certificate verify return:1 depth=0 O = mox localserve, CN = localhost verify return:1 GET / HTTP/1.1 Host: localhost HTTP/1.1 404 Not Found Content-Type: text/plain; charset=utf-8 X-Content-Type-Options: nosniff Date: Mon, 25 Nov 2024 07:28:00 GMT Content-Length: 19 404 page not found ^C ``` As part of this change, I had to `go get golang.org/x/net/http2`. (The default `http.Server` supports HTTP2 if you leave it alone, but that built-in support is deactivated if I set `TLSNextProto` to a non-nil value. To ensure that Mox continues to support HTTP2, [the Go documentation](https://pkg.go.dev/net/[email protected]#hdr-HTTP_2) directs folks with "…more complex configurations…" to import x/net/http2.) Go decided that it also wanted to update a bunch of other dependencies while it was at it. This has caused the large number of dependency updates. I can revert these and attempt a more surgical addition of the http2 library, if you’d like. There are also two major deficiencies in this code that I’d like advice on correcting: 1. Right now, this ALPN feature is enabled when Mox is configured to provide Autoconfigure services to mail clients. I chose to do this because it was relatively straightforward to implement. However, the resulting behavior is extremely non-obvious. **How would you recommend exposing the ALPN feature in the configuration?** 2. I’m not sure what the best way to expose the private `serve()` functions in `imapserver` and `smtpserver` are. The current implementation creates a public function called `ServeConn()` in each module that just calls the private `serve()` function with all the same arguments, but this feels redundant. **Would you recommend making the `serve()` functions public, using public wrappers but with a more limited set of parameters, or something else entirely?** Thanks!
Nice, that's quick. About enabling the ALPN feature: What about a new field in the configs for "submissions" and "imaps", to enable it on the https port. https://github.com/mjl-/mox/blob/v0.0.13/config/config.go#L168 and IMAPS just below. "EnableOnHTTPS bool" or something, with comment explaining ALPN. About the serve() functions. I think we can make them public for now. We can change it later, we don't have to guarantee that those functions are completely stable. We should pass in the parameters from the listener. That's why it would be good to enable this ALPN feature in each submissions/imaps section of the listener config. The parameters should come from that listener. Adding golang.org/x/net/http2 and the dependency updates are fine. I thought we could perhaps just use a regular tls listeners, and accept connections ourselves and look at the nextproto to call the right function. But that's probably not the best approach. The net/http Server has methods for shutting down etc. I don't think we're using them at the moment, but may in the future. And there may be additional http/2 state management in the http2 package that we would miss if handling connections ourselves. I saw the TODO where NextProtos is set. We should probably clone the TLS config before modifying, and only append to nextprotos, not overwrite it. The config may come from the autotls/ package, with adds the acme alpn nextproto for verification. |
I started with the one I thought might be easiest, to introduce myself to the codebase :P
Yeah, that's what I was thinking about too—I hesitated because the listener configurations for IMAP and Submissions both need to somehow inform the HTTPS listener to change its behavior, which is a bit architecturally messy. What do you think about this plan:
Ahhh, okay! Makes sense. If my suggestion above for returning parameters from the IMAP and SMTP listeners to pass into the HTTP listener sounds good, I can move the NextProtos modification into the HTTP server's
Actually, rather than returning a struct, the IMAP and SMTP listeners could return a function pointer. Then that function needs only to ask for the TLS configuration and the network connection, and it could contain the other arguments configured by the listener as part of its closure. Then the
Yeah, HTTP/2 is a complicated protocol with several interlinks into TLS—doing the negotiation manually is an excellent way to introduce subtle, bizarre bugs. |
Yes, that plan makes sense.
At first reading, just exporting the serve functions seems more mox-like. But if that has complications, the other option could be better. |
I think this is the smallest I can make this patch.
I chose to go with the (I also updated the testing instructions in the main PR description.) How does this look?
|
Approach looks good. Documentation looks sufficient, and warnings aren't too Rebasing will cause some trouble (sorry) with the recent addition of TLS TLS client cert auth won't work with the https-alpn connections. I don't Some nits:
It could also be useful to track in logging & metrics that a connection came It would be useful to add some tests. Maybe in the integration test, enabling |
The solution I chose was to avoid setting up a TLS connection if one was already provided to the `serve()` functions.
Also this required a new attribute on the IMAP and SMTP connection structs, and made it easier to avoid using the reflection tools.
Great!
I implemented this as a "viaHTTPS" flag, because then that also makes the protocol metrics & logging easier.
I think both of these are fixed!
Yep! I originally fixed the merge conflict by using reflection to avoid setting up TLS when the connection is already TLS, but the "viaHTTPS" method enabled the logging/metrics to be easy to implement, and also meant I didn't need reflection.
I'm taking a look at this now—I've found the integration tests, but I'm not yet sure I understand how they work. I'll keep poking at it, and let you know if I'm not able to figure it out soon. |
This PR adds support for negotiating IMAP, SMTP & HTTP on the configured HTTPS port using TLS ALPN. This is intended to be useful for deploying Mox as a chatmail server. The upstream implementation of chatmail servers uses
imap
andsmtp
as the “next protocol” values for IMAP and SMTP, respectively: https://github.com/deltachat/chatmail/blob/main/cmdeploy/src/cmdeploy/nginx/nginx.conf.j2#L16-L17(These testing instructions have been updated as of 775c2f2)
To test, configure Mox as is standard for the
mox localserve
mode, but make these three changes toListeners.local
:s/1443/443/g
(skip this one to verify that the warning message appears)Then run Mox and use OpenSSL’s s_client mode to connect to the local instance:
As part of this change, I had to
go get golang.org/x/net/http2
. (The defaulthttp.Server
supports HTTP2 if you leave it alone, but that built-in support is deactivated if I setTLSNextProto
to a non-nil value. To ensure that Mox continues to support HTTP2, the Go documentation directs folks with "…more complex configurations…" to import x/net/http2.) Go decided that it also wanted to update a bunch of other dependencies while it was at it. This has caused the large number of dependency updates. I can revert these and attempt a more surgical addition of the http2 library, if you’d like.There are also two major deficiencies in this code that I’d like advice on correcting:
serve()
functions inimapserver
andsmtpserver
are. The current implementation creates a public function calledServeConn()
in each module that just calls the privateserve()
function with all the same arguments, but this feels redundant. Would you recommend making theserve()
functions public, using public wrappers but with a more limited set of parameters, or something else entirely?Thanks!