-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
Allow ALPN fallback #45056
Comments
Pull request welcome but you'll want to read through #44755 because the question asked there is very similar to your feature request. Your proxy example wouldn't work (not unless you know in advance what your backend supports) because the SSL_select_next_proto() callback must return an answer immediately - you can't tell openssl "stand by while I look that up for you." |
Opened a PR: #45075 It does still sound like it'd be possible (and maybe even useful) to have an |
@pimterry I agree with @bnoordhuis that #45075 does not fully address your specific case. If the client sends |
In my specific case, my app (httptoolkit.com - HTTP-focused but not exclusive) is a general-purpose intercepting proxy, not a reverse proxy. I usually don't know who the remote end will be until after TLS negotiation has completed (e.g. in the HTTPS case I want to look at the In this example, negotiating All I really want to do is accept all TLS connections, and also use ALPN at the same time. Before that was sort-of possible, ish, but with Node 19 it will become effectively impossible. For more context: in my case what actually happens after the connection is a fun game with many options, but some examples:
There's a lot of other options here too, but much of this becomes impossible if you can never ever use ALPN without rejecting unrecognized protocols. |
Separately, details of my own use case aside, this discussion is making me realize that there really could be interesting improvements available in some of these cases if there was an ALPN callback instead, despite the complexity. That would especially help in the traditional reverse proxy case. You can't check upstream dynamically, but you could often have that upstream protocol compatibility data available synchronously, either by querying upstream at intervals, by caching results from incompatible ALPN negotiations, or by allowing protocols to be configured per-hostname manually. Adding a callback would support this, and could provide a neat extension point for all sorts of other advanced connection negotiation logic: const reverseProxy = tls.createServer({
// ...
ALPNCallback: ({ clientALPN, servername }) => {
return firstMatch(clientALPN, serverProtocols[servername]);
// Return string -> send that result. Return undefined -> fatal alert.
}
}); Is there any appetite for that? This would also solve my issue, and potentially support these use cases, so I'm happy to explore a PR for this if so. The main questions I see are:
|
The general idea seems okay to me but I have two questions about how it interacts with other features:
Yes. Reading JS properties from It's okay to read JS properties as long as you don't regress performance of the common case, hence the need for a guard.
Well... in a perfect world node would have a generalized way of making fields from the ClientHello available but we don't live in that world. ALPN and SNI are hopefully good enough for a first pass but with SNI in particular ECH is relevant. |
It looks like ALPN always runs after the cert CB, so SNICallback will always have completed already. That's confirmed in the docs at https://www.openssl.org/docs/man3.0/man3/SSL_select_next_proto.html#NOTES:
In my understanding, ECH wouldn't affect the ALPNCallback API described here. If you're handling an incoming TLS client hello with an ECH inner hello, you have to decrypt the inner hello before doing anything, and then you'd just call ALPNCallback & SNICallback with the decrypted inner parameters, just as we do now with non-encrypted parameters. Effectively there's extra steps internally before & after, but AFAICT the core ALPN/SNI/etc logic remains the same once you've successfully unwrapped the ECH. The SNICallback API might need to change to support more complex logic for ECH retries - i.e. what should it do if ECH decryption fails, since there'll almost always be an outer SNI extension available regardless, and a callback to set the cert & other params will still be useful in this case. That could reasonably be a separate None of that affects ALPN either way - if ECH decryption fails, there's no point doing ALPN. All you can do is return server hello with a cert for the outer SNI, and that will either trigger a handshake retry (either with different ECH keys or with ECH disabled) or it'll fail the handshake entirely. That's just the theory ofc - it's hard to say what the actual OpenSSL APIs for this will look like in practice. I don't think any work has started on that yet, and the protocol is technically still just a draft that could change anyway. The rest of those details make sense, thanks. I'll take a look at building out a new PR shortly for ALPNCallback, either today or early next week. |
New PR opened with the callback approach: #45190 |
There has been no activity on this feature request for 5 months and it is unlikely to be implemented. It will be closed 6 months after the last non-automated comment. For more information on how the project manages feature requests, please consult the feature request management document. |
This is actually already fixed - there's a good solution in #45190 that AFAIK is good to go, and it's just been stuck there waiting to merge for a few months. There are some questions in the PR description at the top of that PR to confirm before merging, but I think 'all of that is fine' would be a reasonable answer to those. It would be great to see this implemented, since this resolves some breakage caused by changes in Node 19, which made certain ALPN use cases impossible with Node. I'm currently avoiding upgrading my application beyond Node 18 because of this, and obviously I can't do that forever. Is there anything we can do to wrap this up? (For context: I've actually opened two independent PRs to fix this - #45075 and #45190 - but the latter is the more flexible and complete solution with no real downsides, so imo we should merge that and close the other) |
Fixed by #45190 |
What is the problem this feature will solve?
I want to create a TLS server that uses ALPN but accepts all protocols from the client.
There's all sorts of interesting cases for this, but the simplest is when the server is really a TLS-terminating proxy of some kind that's just forwarding data upstream. ALPN is necessary in these cases because some clients (e.g. GRPC) will completely fail to connect to a server that does not explicitly support their protocol of choice. I suspect that'll become even more common in future as ALPN becomes widespread outside HTTP/2.
My case is a little more specific: my server is a proxy that supports network debugging, and I specifically want to accept all protocols so that I can capture and debug unexpected traffic, rather than rejecting it at the door. I think this applies more widely though.
What is the feature you are proposing to solve the problem?
An additional TLS server option like
allowALPNFallback: <boolean>
.If set, at the end of the SelectALPNCallback callback if
status == OPENSSL_NPN_NEGOTIATED
thenSSL_TLSEXT_ERR_OK
should be returned instead ofSSL_TLSEXT_ERR_ALERT_FATAL
, thereby accepting theout
value set bySSL_select_next_proto
in the lines above.I believe this works because the feature described is already the default behaviour of OpenSSL's
SSL_select_next_proto
function, which setsout
in this callback to the client's first protocol preference by default if no matching server protocol is found. Docs here: https://www.openssl.org/docs/man3.0/man3/SSL_select_next_proto.html. The only reason that this doesn't work already is the additional check the Node adds, which sends an alert and kills the connection instead.What alternatives have you considered?
I've opened this because this behaviour was recently changed in Node 19 in #44031. Previously in many cases it was possible to at least detect and in some cases fudge these connections because the connection continued (without an ALPN response but without failure) despite the lack of ALPN protocol agreement. Now it will always be rejected explicitly, which makes this use case more complicated.
I do agree the new behaviour in #44031 is correct as the default, but I just don't think that the RFC quoted there mandates this strict behaviour for all TLS servers. As quoted in that PR, the RFC says:
In my case, the server effectively supports all protocols (i.e. it doesn't care, it's just proxying raw traffic) and so it's not required to send the alert - it's reasonable to accept the client's preference for unknown protocols.
A more general alternative would be an ALPN callback, to allow TLS servers to implement arbitrary logic like this or other ways to pick the ALPN protocol from JS directly. Imo that's interesting and all very cool, but not really necessary, and quite a bit more complicated, so probably not worthwhile. Could be worth considering if others see a good use cases for this though.
The text was updated successfully, but these errors were encountered: