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
vault: cache the transport certificate on client #476
Conversation
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 work!
I left a couple of comments and I'm going to update the issue with to action items.
ipaclient/plugins/vault.py
Outdated
| raise | ||
| with tempfile.NamedTemporaryFile(dir=TRANSPORT_CERT_CACHE_PATH, | ||
| delete=False) as f: | ||
| f.write(data) |
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.
It's safer to close or at least flush the file.
Feel free to ignore the problem for now and replace the function later with David's helper function from PR #467.
ipaclient/plugins/vault.py
Outdated
| set_cached_transport_cert(api.env.domain, transport_cert) | ||
| else: | ||
| transport_cert_der = ( | ||
| transport_cert.public_bytes(serialization.Encoding.DER)) |
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.
Please move this functionality to a plugin or some other place. I like to fetch and cache the KRA transport cert every time Custodia is started. The API should have an optional argument to force a refresh.
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 not what was requested in the ticket...
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.
Anyway, what's the point? Is there a design or PoC for Custodia that I can take a look at?
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.
It was obvious to me that the certificate is retrieved and cached in the main process at start-up. It's faster and avoids so many issues with concurrent reads and writes in concurrent processes. In fact it was so obvious to me that I did not consider any other approach when I wrote the ticket.
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.
Added in-memory caching in the last update.
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.
An in-memory cache does not work for Custodia because it is a forking webserver. It has to be an API call that ignores any caches (file) and downloads the KRA transport cert from remote server.
ipaclient/plugins/vault.py
Outdated
| if e.errno != errno.ENOENT: | ||
| raise | ||
| except Exception as e: | ||
| logger.warning("Failed to load %s: %s", filename, e) |
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.
Please use exc_info=True everywhere in order to get a full traceback. It makes debugging much more pleasant.
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.
OK.
ipaclient/plugins/vault.py
Outdated
| except (errors.InternalError, | ||
| errors.ExecutionError, | ||
| errors.GenericError): | ||
| del_cached_transport_cert(api.env.domain) |
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 removes the cached key in any case of error. That's OK for a PoC. For the final version we should be more clever and only delete the certificate when it is not recognized by the KRA. I'll figure out the exact error 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.
KRA subsystem doesn't report invalid KRA transport cert correctly, https://fedorahosted.org/pki/ticket/2597
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.
The cache is deleted only in case of an unexpected error, which is why there is a list of them, not in case of any error.
The KRA bug should definitely be fixed, but we should still cache the cert properly even for older servers.
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 work! exc_info() can lead to memory-leak like reference cycles. See inline comment
ipaclient/plugins/vault.py
Outdated
| except (errors.InternalError, | ||
| errors.ExecutionError, | ||
| errors.GenericError): | ||
| raise _TransportCertInvalid(sys.exc_info()) |
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.
sys.exc_info() is problematic. It returns a stack frame object, which usually creates a reference cycle that keeps all local objects alive. If any object has __del__ method, then Python 2 is unable to break the reference cycle. It's rather bad for long running processes because it is a kind of memory leak.
Here are two reasonable well written blog postings that I just found
- http://engineering.hearsaysocial.com/2013/06/16/circular-references-in-python/
- https://cosmicpercolator.com/2016/01/13/exception-leaks-in-python-2-and-3/
Proposal:
class _TransportCertInvalid(Exception):
def __init__(self, exception):
self.inner = exception
and
except (errors.InternalError,
errors.ExecutionError,
errors.GenericError) as e:
root_logger.debug("some message", exc_info=True)
raise _TransportCertInvalid(e)
later:
six.reraise(type(e.inner), e.inner, 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.
There is no local variable which would hold the reference to the sys.exc_info() result or the _TransportCertInvalid exception. Is raising the exception enough to create a reference to it?
ipaclient/plugins/vault.py
Outdated
| """ | ||
| domain = self.api.env.domain | ||
| dirname = TRANSPORT_CERT_CACHE_PATH | ||
| basename = DNSName(domain).ToASCII() + '.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.
What do you think about DNSName(domain).ToASCII() + '-kra-transport.pem' to make the purpose of the cert clear?
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.
Seems a bit redundant to me, since the full path is ~/.cache/ipa/kra-transport-certs/$DOMAIN.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.
Scratch my proposal. I didn't notice that you are using an extra directory just for KRA transport certs. Sorry.
ipaclient/plugins/vault.py
Outdated
| except _TransportCertInvalid: | ||
| _transport_cert_cache.pop(domain, None) | ||
| try: | ||
| os.remove(filename) |
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.
Minor nit pick: os.unlink. Unix developers don't remove files. We unlink names from inodes. :)
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.
Right 😄
|
I didn't see your comment regarding the in-memory cache because github hid the section after your most recent push. The in-memory cache doesn't work for Custodia because Custodia is a forking webserver. Requests are handled in one-shot client processes. I must be able to forcefully download the certificate in the main process, before it starts listening on incoming requests. |
|
Calling |
|
needs rebase |
Cache the KRA transport certificate on disk (in ~/.cache/ipa) as well as in memory. https://fedorahosted.org/freeipa/ticket/6652
|
Works for me |
|
master:
|
https://fedorahosted.org/freeipa/ticket/6652