-
Notifications
You must be signed in to change notification settings - Fork 101
Two-way SSL support #99
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
Conversation
Transport was not defined. I believe it should have been self.xmlrpc_transport and just got missed during a merge. Anyway, this causes the pylint job to build and means that if a user needs to fallback to xml_rpc, it won't break. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
You may now specify confluence_ca_cert in the conf.py If confluence_ssl_disable is set to True, this option is ignored. Pulled out the session setup, because it will get more complicated as we support more SSL options. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
Client certificates can now be provided as either a tuple or a single file. This supports unencrypted and encrypted files, however there is a catch with encrypted files because a password will be prompted if an encrypted cert is provided. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
The SSLError is a subclass of ConnectionError so it needs to come before it in the except block. Before this commit the error message indicated a connection problem but if the error is an SSL error we should not fall back to the XML RPC interface. In addition, we give a more descriptive message about the underlying problem. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
c3cbc02 to
8173491
Compare
In order to support encrypted certificates, we need to use the TransportAdapter exposed by urllib3. If requests ever adds native support for encrypted keys then we can remove the SSLAdapter and just use the native API. see: psf/requests#2519 for more information. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
6510304 to
17d0536
Compare
|
Okay, I'm now just seeing if I can get RPC stuff working, but I think the Rest Interface is done. I've tested with the following:
|
Everything that works from the SSL perspective now also works with the XML-RPC interface. Including: * Certificate Authority Specification * Client Certificates Encrypted & Unencrypted Signed-off-by: Logan Jones <loganasherjones@gmail.com>
This commit fixes an error where if a user disabled ssl verification but then provided a client-cert, it would break because you cannot set verify_mode to CERT_NONE if check_hostname is not also disabled. So, this fixes that bug. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
Signed-off-by: Logan Jones <loganasherjones@gmail.com>
This prevents a stacktrace from happening during the sphinx-build and raises a more helpful error message. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
|
Okay, I believe this is now ready to go. Short of any changes you all would like to see. I've tested this with the following:
|
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.
Some recommended change to begin with, this absolutely needs complimentary tests as well to keep the coverage
doc/configuration.rst
Outdated
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| A boolean value to explicitly disable any verification of SSL certificates when | ||
| A boolean value to explicitly disable verification of sever SSL certificates when |
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.
server
| """) | ||
|
|
||
| if c.confluence_ca_cert: | ||
| if (not os.path.isfile(c.confluence_ca_cert) and |
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.
you don't need parenthesis on this line
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.
Sorry, I'm used to the google style-guide. Would you prefer to see a backslash?
| proper file path. | ||
| """) | ||
|
|
||
| if c.confluence_client_cert: |
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.
good idea, I like this as an API +1
| if not os.path.isfile(cert_file): | ||
| errState = True | ||
| if log: | ||
| ConfluenceLogger.error( |
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 not raise in this condition instead of having a special error condition and logic flow? If the error is non-recoverable then you should raise the error
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 didn’t raise an error for consistencies sake. None of the above error conditions actually raise an error, they just set the errorState to True which in turn raises an error. I assumed this was what Sphinx wanted.
I’d be happy to change it, but I think I should also change all the other error conditions in this file 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.
I think what was submitted is correct (with respect to setting the error flag and logging the error). This is to allow multiple configuration items to be verified (and output at once) before a configuration exception (ConfluenceConfigurationError) is thrown (in the parent class).
| self.https = (self.scheme == 'https') | ||
| self.proxy = None | ||
| self.timeout = timeout | ||
| if isinstance(client_cert, tuple) and len(client_cert) >= 2: |
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 conditional is confusing. Recommend checking for len of client_cert once and once only, refactor this to validate that client_cert is tuple and break into a conditional statement or using a ternary operator.
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 did think about this. Do you have a recommended place to format the client cert?
My initial thought was during config validation. But I didn’t think it fit well there from an immutability standpoint.
| self._certfile = client_cert[0] | ||
| self._keyfile = None | ||
| else: | ||
| self._certfile = client_cert[0] |
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 would use native Python unpacking for this, eg. self._certfile, self._keyfile = client_cert
| if self.disable_ssl_validation: | ||
| import ssl | ||
| context = ssl._create_unverified_context() | ||
| context = self._setup_ssl_context() |
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.
there is still a condition where user would want a non-verified SSL connection surely?
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 still supported. If they specify to disable ssl support, we can still return a valid ssl context with check_hostname set to false and the verify_mode set to ssl.CERT_NONE.
| self._certfile = cert[0] | ||
| self._keyfile = None | ||
| else: | ||
| self._certfile = cert[0] |
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.
Use native unpacking
|
@loganasherjones thanks for the contribution! I've made some recommendations about code fixes and logic conditions. This needs unit tests before we can merge it, look at the existing test suite and leverage the Tox runtime to see how things are working now.. |
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 feedback. When I get some time I will write the tests and fix the issues raised.
| if not os.path.isfile(cert_file): | ||
| errState = True | ||
| if log: | ||
| ConfluenceLogger.error( |
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 didn’t raise an error for consistencies sake. None of the above error conditions actually raise an error, they just set the errorState to True which in turn raises an error. I assumed this was what Sphinx wanted.
I’d be happy to change it, but I think I should also change all the other error conditions in this file as well.
| self.https = (self.scheme == 'https') | ||
| self.proxy = None | ||
| self.timeout = timeout | ||
| if isinstance(client_cert, tuple) and len(client_cert) >= 2: |
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 did think about this. Do you have a recommended place to format the client cert?
My initial thought was during config validation. But I didn’t think it fit well there from an immutability standpoint.
| if self.disable_ssl_validation: | ||
| import ssl | ||
| context = ssl._create_unverified_context() | ||
| context = self._setup_ssl_context() |
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 still supported. If they specify to disable ssl support, we can still return a valid ssl context with check_hostname set to false and the verify_mode set to ssl.CERT_NONE.
1. Fixed typo for 'server.' 2. Made publisher tests available to be run with client certs. tested on my local confluence, and they ran successfully. 3. Changed the client_cert loading during config validation time. I'm not sold on how I did this because I'm editing the config during the validation step, but it means I can remove a lot of error checking in other places (like the rest client and xml rpc client). 4. Wrote unit tests for the configuration validation changes. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
|
Okay, I've updated with tests for the config portion. I still have an outstanding question on the parenthesis on line 127 of config. I'm happy to change it to match whatever style you want, if you want a backslash or can recommend a better way, I can change it. I'm happy to answer more questions in regards to disabling the SSL validation during XML RPC publishing if you have questions. As for tests for the rest/rpc publishing portion. I added the ability to test these things in the Hopefully this is 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.
@loganasherjones, thanks! I'm liking this PR so far.
I have noted at least two (or three) minor changes. I put in a slew of optional comments, which you can ignore if you desire (just me being nitpicky).
| James Knight <james.d.knight@live.com> | ||
| John Teasdale <teasdale@uber.com> | ||
| Thomas Malcher <malcher@student.tugraz.at> | ||
| Logan Jones <loganasherjones@gmail.com> |
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.
(optional) I'd prefer if the list of authors was sorted in a lexicographical fashion.
doc/configuration.rst
Outdated
| confluence_timeout = 10 | ||
| confluence_ca_cert |
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.
(optional) I'd prefer if the provided configuration options sorted in a lexicographical fashion (even if it separates them) in whatever category they are placed (the category you've chosen works for me).
doc/configuration.rst
Outdated
|
|
||
| Provide a CA certificate to use for server cert authentication. Can either be a | ||
| file or a path. If it is a path, the directory must have been processed using | ||
| the c_rehash utility supplied with OpenSSL. By default, verification is turned |
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.
While this directory requirement appears to exists based of requests, does it also apply to the ssl library (when using XML-RPC)? I would suggest, instead of outlining the conditions imposed by the network libraries, just point to their API instead (i.e. indicate that it takes a file or OpenSSL-compatible CA directory and link to both the requests API and ssl API for more information).
doc/configuration.rst
Outdated
|
|
||
| .. code-block:: python | ||
| confluence_ca_cert = '/path/to/ca.crt' |
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.
(optional) I'd would be nice to use a more platform independent path in the example (even though this example matches what requests uses). If you like what it's currently set to, that's fine.
| app.add_config_value('confluence_publish_prefix', None, False) | ||
| """Timeout for network-related calls (publishing).""" | ||
| app.add_config_value('confluence_timeout', None, False) | ||
| """File/path to Certificate Authority""" |
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.
(optional) I'd prefer if the provided configuration options sorted in a lexicographical fashion (even if it separates them). I would not be opposed if you want to introduce a sub-section (like the documentation) and have category:
"""(advanced configuration - publishing)"""
<your-new-options>
| from .exceptions import ConfluenceCertificateError | ||
| from .std.confluence import API_REST_BIND_PATH | ||
|
|
||
| class SSLAdapter(HTTPAdapter): |
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.
(optional) While I know PEP 8 (and maybe Google's Python guidelines) promote "CapWords" style, I'd prefer the convention SslAdapter. If you want to leave it as is or if @tonybaloney prefers the "CapWords" style, that's fine by me.
| keyfile=self._keyfile, | ||
| password=self._password) | ||
| except ssl.SSLError as ex: | ||
| ConfluenceCertificateError(ex) |
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.
Isn't this missing raise?
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! Nice catch.
| kwargs['ssl_context'] = context | ||
| return super(SSLAdapter, self).init_poolmanager(*args, **kwargs) | ||
|
|
||
|
|
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.
(trivial) No need for two lines.
| self._conf('confluence_server_url', 'CB_URL', DEFAULT_TEST_URL) | ||
| self._conf('confluence_server_user', 'CB_USR', DEFAULT_TEST_USER) | ||
| self._conf('confluence_space_name', 'CB_SPC', DEFAULT_TEST_SPACE) | ||
| self._conf('confluence_ca_cert', 'CB_CA') |
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.
(note) Heads up that the test_publisher.py class is about to be re-written. You can leave these changes in if you'd like, but just note these changes may or may not exist in the future (if it matters to you).
| builder.config.confluence_publish = False | ||
| builder.config.confluence_client_cert = None | ||
| builder.config.confluence_server_url = None | ||
| builder.config.confluence_space_name = 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.
Missing trailing line?
1. Fixed lexographical ordering problems. 2. Fixed whitespacing issues. 3. Fixed missing raise. 4. Refactored ca_cert path checking. 5. Updated docs to link to relevant ssl/requests sections. Signed-off-by: Logan Jones <loganasherjones@gmail.com>
|
Okie dokie. I think feedback from both @tonybaloney and @jdknight have been implemented. Let me know if there is anything else! |
|
@loganasherjones, much appreciated. Everything looks good for me (unless I'm overlooking something 😅). I'll let @tonybaloney drive it from here, unless he states otherwise. Edit: |
|
Yep, it's a 👍 from me. Great work. Merging |
|
Thanks for all the help! Looking forward to the next release :) |
This branch will implement client-side certificate support, so that users running a client-authenticated confluence server can utilize this package.
Here is what is left to be done: