-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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
KAFKA-4493: Throw InvalidTransportLayerException when a plaintext client receives a TLS response #5940
base: trunk
Are you sure you want to change the base?
KAFKA-4493: Throw InvalidTransportLayerException when a plaintext client receives a TLS response #5940
Conversation
…ent receives a TLS response
It would be better if the broker did the right thing here. Have we considered that? Given the client upgrades take forever, any fix on the client side is less effective in the short/medium term. |
I had not considered that. Thanks for the suggestion @ijuma. After some digging around I find that the first place where we the server detects that something is wrong is from a raised kafka/clients/src/main/java/org/apache/kafka/common/network/SslTransportLayer.java Line 863 in 4e90af3
It seems like we want to preserve the behavior of flushing the bytes to the client. For example, if the client's private key is invalid, we would want to send the alert message back. |
Thanks for the investigation. My concern is that this is a bit of a layering violation. The plaintext code now has to know about TLS. What about other protocols that can cause the same issue? It would be better if the plaintext code could validate based on what it knows about its own protocol. |
I agree. What do you think would be a better approach? Maybe we can throw an exception if we receive a message size above a certain threshold. That would catch this scenario where the plaintext layer thinks it's receiving a 3mb message. It sounds reasonable to me since newly-established connections should first negotiate |
That could work, but the current code is checking all reads, right? Not just the first response. |
No, the current code checks only the very first read due to the |
One thing to keep in mind is that we don't use |
@ijuma From reading the code and quickly testing locally, I believe that inter-broker communication starts with either
It seems like the UpdateMetadataRequest is likely to go over 1MB in size. From a very rough calculation (basically multiplying the most frequently-variable piece of that request - UpdateMetadataPartitionState), I estimate that we will go over the 1MB threshold of UpdateMetadataRequest size once we reach around 20k partitions for the entire cluster. Perhaps we could raise the MB check limit here? My initial message had said that in such a misconfiguration, the broker thinks it needs to allocate 352MB for the request. A simple 100MB check should suffice I think |
cc @ijuma thoughts on this? |
int readSize = socketChannel.read(dst); | ||
|
||
if (!completedRead && ((ByteBuffer) dst.rewind()).getInt() > 1_000_000) // first responses should negotiate API_VERSION and be small in size | ||
throw new IllegalTransportLayerStateException("Received a first response larger than 1MB (Is this a plaintext response?)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For your consideration: message could be more direct .
Is this a plaintext response?
--> The broker expects SSL, is your client configured for SSL correctly?
(or something of that nature)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this layer we don't know whether the broker expects SSL. The broker is actually configured to expect plaintext in this scenario and we don't know whether the response here is SSL. As @ijuma said earlier, that is a layering violation so I think it's better to keep the current message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stanislavkozlovski understood. My proposed wording was aggressively making assumptions, but perhaps we could find a middle ground that still directs users to check their port? The issue with Received a first response larger than 1MB (Is this a plaintext response?)
is that users won't know what to DO with that response. It would be great to reword it accurately to give some proposed action/suggestions to the user...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack. Any other suggestions? Would something like (Please verify that the configuration is correct. It may be that we are not connecting to a plaintext port)
work better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding an extra "if" statement on every call to read()
seems like a very heavy price to pay for this feature. We should look at where we're using the total length and check there, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cmccabe @ijuma Doesn't the compiler optimize the if check?
I do not know how to proceed with this change.
@astubbs's example wouldn't work for the common case where we try to allocate ~352MBs and it would also be appleid to every receive we get, whereas here we are trying to scope it to the first receive for a specific channel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you referring to my fix? or my problem? I just would like my problem solved :) I'm not concerned about my fix - I was just mainly trying to demonstrate the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you wanted to minimize the cost, you could move the exception and second part of the if to a separate method so that it's not in the hot path. Btw, calling rewind
on the ByteBuffer
mutates it, which seems generally unsafe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I thought the previous discussion indicates that 1 MB might be too low of a check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the right time to check this would be when we originally create the NetworkReceive object, not in the hot path for every read. Also, whatever we choose as the "upper limit" for a size to be interpreted as valid needs to become the new upper limit for message.max.bytes
(which in turn, means that maybe this needs a KIP, since it changes a public config)
test this please |
Currently, if a client if misconfigured to not use
ssl
and conencts to a broker with SSL listeners, the broker responds with a TLS Alert response.The client interprets the first 4 TLS alert response bytes as the size of the response, resulting in it trying to allocate
352518912
bytes.This either raises an
OutOfMemory
exception or stalls on theallocate()
call. Either way, we should fatally throw a more clear exception so the user can know what is wrong.I personally spent more time than I'm comfortable sharing debugging this issue and I'd like to see it improved.
Implementation
On the first read of a
PlaintextTransportLayer
channel, we check against the pre-defined TLS alert headers, which consist of the alert byte0x15
and the TLS version0301
,0302
,0303
.I believe that it should be alright to close such responses since we would never expect to receive a 3mb response on the first response in a newly-established connection since we should at least verify
API_VERSIONS
first.I'm not entirely certain whether this warrants creation of a KIP since we are raising a new exception -
InvalidTransportLayerException
. That is not publicly exposed, right?cc @rajinisivaram for review