-
Notifications
You must be signed in to change notification settings - Fork 90
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
New module_utils openssh #213
New module_utils openssh #213
Conversation
else: | ||
self.__size = self.__algorithm_parameters[self.__keytype]['default_size'] | ||
|
||
self.__generate_keypair() |
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 it would be better to have class methods with speaking names to do these tasks, i.e. Asymmetric_Keypair.load()
to load a keypair and Asymmetric_Keypair.generate()
to generate one.
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.
Have you thought about this one?
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 remove _keypair from these method names, but I may be missing the meaning of your suggestion.
Was the intention that these be readable as actions solely in the context of initialization or that the methods also be exposed for end users? I intentionally wanted to present asymmetric keypairs as immutable single-use objects hence why the loading/generating mechanisms are obscured.
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 offer them as class methods, i.e. you can do AsymmetricKeypair.load(filename)
to load a keypair, which will return an AsymmetricKeypair
instance. This is more expressive then using AsymmetricKeypair(path=filename)
IMO.
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.
(See for example https://code-maven.com/slides/python/class-methods-alternative-constructor for this kind of pattern.)
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, that does open the possibility of an empty object and makes the faux overloaded constructor less confusing. I will spend some time on that. Thanks
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 could declare the constructor private (by docstring, no idea whether Python allows to do that explicitly without using ugly hacks) to avoid having an empty keypair.
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 considering the possibility of an empty object a benefit.
That would allow for the truthiness of the class to be evaluated in a more obvious (pythonic) way.
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.
Hmm, I think it's a lot easier to let only None
represent an empty key, instead of having an instance of the class which can represent an empty key. While such an empty value could simplify some cases, it in general makes code more complicated, since it is harder to ensure that a key is actually specified. (For example, it is basically impossible to specify this requirement with the type system, which is IMO a huge disadvantage.)
|
||
class Asymmetric_Keypair(object): | ||
def __init__(self, path=None, keytype='rsa', size=None, passphrase=None): | ||
"""Container for newly generated asymmetric keypairs or those loaded from existing files |
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 should be part of the class's docstring, not of the __init__
docstring. It describes what the class does, not what __init__
does. (Same for the other class.)
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, generate/load are now exposed as class methods and docstrings are cleaned up.
Updated unit tests to use generate/load as well just for illustration.
The change c227733 is not what I meant. It basically reverts the PR to what it was at the beginning (with the class methods thin wrappers around The
The 'hard work' should be done by |
I was avoiding that because it required re-scoping the helper/utility functions, but the changes are incorporated as of the latest commit. As a side note, how do I clear this sanity check? I didn't change how Also the affected sign method which requires |
/rebuild_failed |
|
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 it took so long to review this!
elif isinstance(privatekey, ec.EllipticCurvePrivateKey): | ||
keytype = 'ecdsa' | ||
elif isinstance(privatekey, Ed25519PrivateKey): | ||
keytype = 'ed25519' |
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 needs a final else
block. It should either raise an exception, or set keytype
to a concrete value.
|
||
:signature: signature to verify | ||
:data: byteslike data signed by the provided signature | ||
""" |
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 it is a better API to let verify()
raise an exception in case verification fails, and return nothing in case validation succeeds, i.e. do the same thing as cryptography also does.
@classmethod | ||
def encode_openssh_privatekey(cls, asym_keypair): |
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.
@classmethod | |
def encode_openssh_privatekey(cls, asym_keypair): | |
@staticmethod | |
def encode_openssh_privatekey(asym_keypair): |
@classmethod | ||
def encode_openssh_publickey(cls, asym_keypair, comment): |
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.
@classmethod | |
def encode_openssh_publickey(cls, asym_keypair, comment): | |
@staticmethod | |
def encode_openssh_publickey(asym_keypair, comment): |
self.__comment = comment | ||
|
||
def __eq__(self, other): | ||
return self.fingerprint == other.fingerprint and self.comment == other.comment |
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 rather compare the keys themselves, instead of only comparing the fingerprint. Or are some of the key objects not comparable?
(Also this ignores the passphrase.)
""" | ||
|
||
self.__asym_keypair.update_passphrase(passphrase) | ||
self.__openssh_privatekey = type(self).encode_openssh_privatekey(self.__asym_keypair) |
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.
self.__openssh_privatekey = type(self).encode_openssh_privatekey(self.__asym_keypair) | |
self.__openssh_privatekey = OpenSSH_Keypair.encode_openssh_privatekey(self.__asym_keypair) |
No worries, this is definitely not a high priority and the reviews are very helpful. Everything suggested makes sense and I will incorporate the changes. Regarding the I can investigate more on the special case of ed25519 and probably just ask over on the cryptography side if they have any suggestions. Thanks. |
Ah, that reminds me that I already had to solve that problem once ;-) See |
Thanks, I made use of your example.
|
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.
Let's get this merged! 🎉
@Ajpantuso thanks for contributing this! |
Thanks for your help! |
SUMMARY
Utilities for working with OpenSSH key pairs abstracted from
cryptography
.The intention is to offer these utilities as an alternative to invoking ssh-keygen.
ISSUE TYPE
COMPONENT NAME
plugins/module_utils/crypto/cryptography_openssh.py
tests/unit/plugins/module_utils/crypto/test_cryptography_openssh.py
ADDITIONAL INFORMATION
This exposes two classes, Asymmetric_Keypair for encapsulating asymmetric keypairs supported by
cryptography
and OpenSSH_Keypiar for the OpenSSH encoded/formatted representation of a keypair, mimicking some of the features offered by ssh-keygen.Attributes of both classes for the most part are exposed as read-only properties to reinforce that keys are immutable.
However comments applied to OpenSSH formatted public keys can be updated.
Since ed25519 is only available from
cryptography
>=2.6 the utilities will only successfully import if that condition is met.A soft requirement is
cryptography
>=3.0 for OpenSSH formatted private keys, but ssh-keygen is not consistent about formatting private keys this way depending on version either.Text encoding is set to UTF-8 globally in the module and could be changed to ASCII if neccessary.
SHA256 is used as the standard hash algorithm to align with current ssh-keygen versions.
I removed a couple of unit tests for directly comparing ssh-keygen to
cryptography
generated keys and vise versa because I wasn't sure how best to implement command invocation outside of an Ansible Module. (Direct comparisons are positive in my local test environment for whatever that is worth)Definitely looking for input on this one as this is for the most part just a straight encapsulation of the
cryptography
library with most of the effort spent on how to present it as a utility in this collection.