-
Notifications
You must be signed in to change notification settings - Fork 313
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
Fix HTTP TLS client certificate verification #551
Fix HTTP TLS client certificate verification #551
Conversation
Currently Rally fails with "bad certificate" when Elasticsearch requires verification of client certificates. Fix bug, add tests and update documentation with more examples.
Hi, In the case ES is configured to require client certificate (mutual-tls) and I run rally with "verify_certs:false" (instead of true) the code will enter by this line and the client certificate and key will never be loaded in the ssl_context Is it right? In my opinion even if rally(ES client) is not verifying ES(server) certificate, it should be able to connect using client certificate and key. Ramón |
Hey @rdiez-stratio, I see what you mean. Yes what you are suggesting wouldn't work with this PR. It could be implemented by setting Practically speaking, is it a common use case to use unverified HTTPS from Rally to ES but have ES request verification from the client? I mean, again practically speaking, the only thing required for the verification of the server is to just pass the CA crt (if the server certificate has been created using a private CA), which shouldn't be something sensitive. Not verifying the server cert when the client initiates the connection is an insecure configuration, so that's why I am a bit ambivalent about supporting it, esp since supplying the ca is so simple, but I will think about it a bit more. |
As discussed in elastic#551 (comment), decouple verification of server certs from having the ability to present client certificates. Also enhance and improve unit tests and adjust docs.
@rdiez-stratio with the latest commit 8607190, I have tried to decouple the logic, as discussed earlier, You should be able to present the client certs, regardless of the value of |
@dliappis really appreciate the change. thanks. |
docs/command_line_reference.rst
Outdated
**TLS/SSL Certificate Verification** | ||
|
||
Server certificate verification is controlled with the ``verify_certs`` boolean. To disable use ``verify_certs:false``. | ||
If ``verify_certs:true``, Rally will attempt to verify the certificates of Elasticsearch. If they are private certificates, you will also need to supply the private CA certificate using ``ca_certs:'/path/to/cacert.pem'``. |
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.
Suggestion: "Rally will attempt to verify the certificate provided by Elasticsearch"?
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.
👍
docs/command_line_reference.rst
Outdated
|
||
**TLS/SSL Examples** | ||
|
||
* Enable SSL, verify server certificates using public certificate: ``--client-options="use_ssl:true,verify_certs:true"``. Note that you don't need to set ``ca_cert`` (which defines the path to the root certificates). Rally does this automatically for you. |
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.
Should we say "public CA" here instead of "public certificate"?
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.
👍
esrally/client.py
Outdated
client_cert = self.client_options.pop("client_cert", False) | ||
client_key = self.client_options.pop("client_key", False) | ||
|
||
if bool(client_cert) != bool(client_key): |
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.
Why are you converting to a bool here? Can't we just avoid the default value alltogether and check whether both are not None
?
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.
This is a XOR
, as this section needs to execute when one of the settings has been set and the other hasn't, which is something we don't allow. It could be moved to utils/opts.py
?
esrally/client.py
Outdated
self.logger.info("SSL support: on") | ||
self.client_options["scheme"] = "https" | ||
|
||
self.ssl_context = create_ssl_context(cafile=self.client_options.pop("ca_certs", certifi.where())) | ||
self.ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=self.client_options.pop("ca_certs", certifi.where())) |
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.
Why is the SSL context created differently now? Is this to avoid the import on elasticsearch.connection
? Also, is it correct to use the SSL purpose CLIENT_AUTH
in all cases?
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.
This is required to enable client authentication, if requested by the server.
Using ssl.create_default_context
, because the one in elasticsearch-py is a simple wrapper for it, but only passes kwargs and won't pass ssl.Purpose.CLIENT_AUTH
.
esrally/client.py
Outdated
self.ssl_context.verify_mode = ssl.CERT_NONE | ||
self.ssl_context.check_hostname = False |
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.
Why is the order changed here?
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.
This is mandatory. When specifying check_hostname
the verify_mode needs to be set already otherwise:
ValueError: check_hostname needs a SSL context with either CERT_OPTIONAL or CERT_REQUIRED
esrally/client.py
Outdated
self.logger.error( | ||
"Supplied client-options contain only one of client_cert/client_key. " | ||
"If your Elasticsearch setup requires client certificate verification both need to be supplied. " | ||
"See https://esrally.readthedocs.io/en/stable/command_line_reference.html?highlight=client_options#id2" |
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 am not sure we should use links directly here? If we do this I think we should use our DOC_LINK
variable (in esrally/__init__.py
). Also, shouldn't we present this to the user on the command line instead of just logging it?
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.
We are already doing it here so I just copied this behavior.
Should it go the the exception following a few lines after then?
esrally/client.py
Outdated
) | ||
defined_client_ssl_option = "client_key" if client_key else "client_cert" | ||
missing_client_ssl_option = "client_cert" if client_key else "client_key" | ||
raise exceptions.InvalidSyntax("'{}' is missing from client-options but '{}' " |
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.
Admittedly, there are no hard definitions which exception is used for what purpose but I tend to use InvalidSyntax
only for errors in files like tracks or car definitions, not so much with command line parameters. I'd suggest we use SystemSetupError
.
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.
👍
esrally/client.py
Outdated
"has been specified".format(defined_client_ssl_option, | ||
missing_client_ssl_option)) | ||
elif client_cert and client_key: | ||
self.logger.info("SSL client authentication: on") |
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.
Can we please also add a log statement when this feature is turned off (we do that for the other cases as well)?
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.
We can, but I think it would be a bit weird to warn about something when no corresponding arguments have been passed. I mean this would appear when client_cert
+ client_key
haven't been passed, so, maybe it would be too much info for the user to see that a certain feature hasn't been enabled when the arguments influencing it haven't been passed to begin with?
hosts = [{"host": "127.0.0.1", "port": 9200}] | ||
client_options = { | ||
"use_ssl": True, | ||
"verify_certs": True, | ||
"http_auth": ("user", "password") | ||
} | ||
# make a copy so we can verify later that the factory did not modify it | ||
original_client_options = dict(client_options) | ||
original_client_options = deepcopy(client_options) |
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.
Great catch!
docs/command_line_reference.rst
Outdated
|
||
**TLS/SSL Certificate Verification** | ||
|
||
Server certificate verification is controlled with the ``verify_certs`` boolean. To disable use ``verify_certs:false``. |
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.
Maybe mention that it is turned on by default?
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.
Great point, indeed.
5320f66
to
a30296d
Compare
@danielmitterdorfer I tried to address all the comments, would appreciate another pass when you have time. |
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.
Thanks for the iteration. I think we're almost there. I left one more comment.
esrally/client.py
Outdated
console.println( | ||
"'{}' is missing from client-options but '{}' has been specified.\n" | ||
"If your Elasticsearch setup requires client certificate verification both need to be supplied.\n" | ||
"Read the documentation at {}//command_line_reference.html?highlight=client_options#id2\n".format( |
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.
Is the double slash intentional? Also, I'd rather use a more stable on-page anchor than #id2
?
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.
Ugh sure it's by accident. Ditched the highlight and left the pure link in 4e7f434
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.
Thanks @dliappis! LGTM
我的ES是7.2.0版本,使用了xpack加密,使用下面这条命令执行esrally: 报错: |
Currently Rally fails with "bad certificate" when Elasticsearch
requires verification of client certificates.
Fix bug, add tests and update documentation with more
examples.
Closes #550