-
Notifications
You must be signed in to change notification settings - Fork 136
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 python 3.12 and 3.13 support #707
base: master
Are you sure you want to change the base?
Conversation
The traceback on my fork is: kmip/tests/integration/conftest.py:49:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
kmip/pie/client.py:173: in open
self.proxy.open()
kmip/services/kmip_client.py:285: in open
six.reraise(*last_error)
.tox/integration/lib/python3.9/site-packages/six.py:719: in reraise
raise value
kmip/services/kmip_client.py:274: in open
self.socket.connect((self.host, self.port))
/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/ssl.py:1376: in connect
self._real_connect(addr, False)
/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/ssl.py:1367: in _real_connect
self.do_handshake()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <ssl.SSLSocket [closed] fd=-1, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
block = False
@_sslcopydoc
def do_handshake(self, block=False):
self._check_connected()
timeout = self.gettimeout()
try:
if timeout == 0.0 and block:
self.settimeout(None)
> self._sslobj.do_handshake()
E ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1129)
/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/ssl.py:1343: SSLCertVerificationError
_ ERROR at setup of TestProxyKmipClientIntegration.test_x509_certificate_register_get_destroy _
request = <SubRequest 'simple' for <TestCaseFunction test_asymmetric_key_pair_create_get_destroy>>
@pytest.fixture(scope="class")
def simple(request):
config = request.config.getoption("--config")
client = pclient.ProxyKmipClient(config=config)
> client.open()
kmip/tests/integration/conftest.py:49:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
kmip/pie/client.py:173: in open
self.proxy.open()
kmip/services/kmip_client.py:285: in open
six.reraise(*last_error)
.tox/integration/lib/python3.9/site-packages/six.py:719: in reraise
raise value
kmip/services/kmip_client.py:274: in open
self.socket.connect((self.host, self.port))
/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/ssl.py:1376: in connect
self._real_connect(addr, False)
/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/ssl.py:1367: in _real_connect
self.do_handshake()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <ssl.SSLSocket [closed] fd=-1, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
block = False
@_sslcopydoc
def do_handshake(self, block=False):
self._check_connected()
timeout = self.gettimeout()
try:
if timeout == 0.0 and block:
self.settimeout(None)
> self._sslobj.do_handshake()
E ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1129)
/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/ssl.py:1343: SSLCertVerificationError |
context.check_hostname = False | ||
if self.certfile: | ||
context.load_cert_chain(self.certfile, self.keyfile) | ||
self.socket = context.wrap_socket( |
Check failure
Code scanning / CodeQL
Use of insecure SSL/TLS version
keyfile = self.config.settings.get('key_path') | ||
context.load_cert_chain(certfile, keyfile=keyfile) | ||
|
||
self._socket = context.wrap_socket( |
Check failure
Code scanning / CodeQL
Use of insecure SSL/TLS version
The problem with your mocking is that you can't mock I've edited the test changes for your patch (and not your code changes, which looks good) thusly:
And I've confirmed the tests pass with Python 3.9-3.12. |
This reverts commit 4618b1d.
Thanks @s-t-e-v-e-n-k, new build in progress on my fork. |
Still no joy: |
@blink1073 were you able to make this work? |
No, I haven't been able to figure out the testing portion. |
kmip/services/kmip_client.py
Outdated
@@ -285,13 +285,14 @@ def open(self): | |||
six.reraise(*last_error) | |||
|
|||
def _create_socket(self, sock): | |||
self.socket = ssl.wrap_socket( | |||
purpose = ssl.Purpose.SERVER_AUTH | |||
context = ssl.create_default_context(purpose=purpose, capath=self.ca_certs) |
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 should be cafile=...
instead of capath=...
, because the underlying configuration parameter points to a file.
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.
Fixed
kmip/services/server/server.py
Outdated
self._socket = ssl.wrap_socket( | ||
purpose = ssl.Purpose.CLIENT_AUTH | ||
capath = self.config.settings.get('ca_path') | ||
context = ssl.create_default_context(purpose=purpose, capath=capath) |
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 should be cafile=capath
instead of capath=capath
, because the underlying configuration parameter points to a file. (The naming is unfortunately inconsistent between the Python ssl module and pykmip.)
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.
Fixed
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 no longer pays attention to the configuration settings self.cert_reqs
and self.ssl_versions
. That's probably okay in practice, since the defaults are good, but these settings are documented, so they should work.
One option might be to not use ssl.create_default_context()
and instead use ssl.SSLContext
directly and apply these settings. Like
context = ssl.SSLContext(self.ssl_version)
context.verify_mode = self.cert_reqs
if self.ca_certs:
context.load_verify_locations(self.ca_certs)
This is just about as much code as before but it reproduces the previous behavior more precisely.
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.
Done
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.
Similar to the client, we should make sure that this change does not lose any previously working configuration settings. I see for example that ssl_version=self.auth_suite.protocol
and ciphers=self.auth_suite.ciphers
don't get used anymore.
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.
Updated
kmip/services/server/server.py
Outdated
self._socket = ssl.wrap_socket( | ||
purpose = ssl.Purpose.CLIENT_AUTH | ||
capath = self.config.settings.get('ca_path') | ||
context = ssl.create_default_context(purpose=purpose, capath=capath) |
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 must set
context.verify_mode=ssl.CERT_REQUIRED
after creating context
. This matches what the previous code did. Without this, the server won't ask the client to send a certificate, and without a client certificate this will later fail at higher levels (such as
PyKMIP/kmip/services/server/session.py
Line 144 in 4d3b5a5
certificate = auth.get_certificate_from_connection( |
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.
Done
Thanks @petere! I believe I've addressed your feedback. |
kmip/services/kmip_client.py
Outdated
context.verify_mode = self.cert_reqs | ||
if self.ca_certs: | ||
context.load_verify_locations(self.ca_certs) | ||
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.
This line doesn't appear to be needed. Probably left over from a previous version that used create_default_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.
Removed
if self.ca_certs: | ||
context.load_verify_locations(self.ca_certs) | ||
context.check_hostname = False | ||
if self.certfile: |
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'm testing combinations of "forgetting" to specify certfile
or keyfile
in the configuration file. With this code, if you specify certfile
but not keyfile
, you'll get an exception from load_cert_chain
. If you specify keyfile
but not certfile
, this code is skipped and you'll get whatever the SSL stack ends up deciding. The old module-level wrap_socket
had an additional check
if keyfile and not certfile:
raise ValueError("certfile must be specified")
to prevent this. Maybe this would be good to have here 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.
Done
kmip/services/server/server.py
Outdated
@@ -287,17 +287,24 @@ def interrupt_handler(trigger, frame): | |||
for cipher in auth_suite_ciphers: | |||
self._logger.debug(cipher) | |||
|
|||
self._socket = ssl.wrap_socket( | |||
cafile = self.config.settings.get('ca_path') | |||
context = ssl.SSLContext(self.ssl_version) |
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 should be
context = ssl.SSLContext(self.auth_suite.protocol)
(wrongly copy-pasted from client code?)
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.
Fixed
kmip/services/server/server.py
Outdated
cafile = self.config.settings.get('ca_path') | ||
context = ssl.SSLContext(self.ssl_version) | ||
context.verify_mode = ssl.CERT_REQUIRED | ||
if auth_suite_ciphers: |
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 should be self.auth_suite.ciphers
. I'm not sure exactly what the relationship between that and auth_suite_ciphers
is, but this one crashes.
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.
Fixed
kmip/services/server/server.py
Outdated
context.load_verify_locations(cafile) | ||
certfile = self.config.settings.get('certificate_path') | ||
|
||
if certfile: |
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.
As with the client, it's worth checking here about how different combinations of not specifying certificate or key behave. For example, the old module-level wrap_socket()
had a check
if server_side and not certfile:
raise ValueError("certfile must be specified for server-side "
"operations")
so omitting the certificate would have been a hard 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.
Fixed
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 I had in mind was like
if certfile:
keyfile = self.config.settings.get('key_path')
context.load_cert_chain(certfile, keyfile=keyfile)
+ else:
+ raise ValueError("certfile must be specified for server-side operations")
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.
Fixed
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 instead let the context raise an appropriate error. This has the benefit of letting us mock the context behavior in the tests.
kmip/services/server/server.py
Outdated
keyfile = self.config.settings.get('key_path') | ||
context.load_cert_chain(certfile, keyfile=keyfile) | ||
else: | ||
raise ValueError("certfile must be specified for server-side operations") |
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.
What we don't have here is the equivalent of the client-side check
if self.keyfile and not self.certfile:
raise ValueError("certfile must be specified")
but it also turns out that the higher levels already check that both certificate and key are specified:
kmip.core.exceptions.ConfigurationError: Setting 'key_path' is missing from the configuration file.
kmip.core.exceptions.ConfigurationError: Setting 'certificate_path' is missing from the configuration file.
So it's probably not necessary.
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, it looks like this causes the test suite to fail in TestKmipServer.test_start
:
ValueError: certfile must be specified for server-side operations
So either this code or the test setup would need adjustments.
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.
Removed from the server
@petere is there anything else you'd like me to try? |
Other than the single comment I just added (which I just got my looking at my local copy, but it's been a while since I played with this), I am content with this version. How we get from that to getting this merged and released, I don't know. |
This is now ready for review. cc @arp102. |
The workflows are all passing on my fork: https://github.com/blink1073/PyKMIP/actions/runs/12119149288?pr=1 |
Fixes #706