Repository: ansible/ansible

Issue:
Add option for openssl_publickey to use ssh-rsa format
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
openssl_publickey

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0.0
  config file = /Users/.../ansible.cfg
  configured module search path = [u'./ansible/library']
  python version = 2.7.13 (default, Dec 17 2016, 23:03:43) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
MacOS X 10.11.6


##### SUMMARY
<!--- Explain the problem briefly -->

We are using openssl_publickey to create OpenSSH keys and for that we need the ssh-rsa format. Currently I'm still having to use `ssh-keygen -y -f FILE` to do this. It would be convenient to have an option to create the public key in ssh-rsa format instead of PEM format.



Discussion:
cc @Spredzy
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/crypto/openssl_publickey.py b/lib/ansible/modules/crypto/openssl_publickey.py
--- a/lib/ansible/modules/crypto/openssl_publickey.py
+++ b/lib/ansible/modules/crypto/openssl_publickey.py
@@ -46,6 +46,13 @@
         choices: [ True, False ]
         description:
             - Should the key be regenerated even it it already exists
+    format:
+        required: false
+        default: PEM
+        choices: [ PEM, OpenSSH ]
+        description:
+            - The format of the public key.
+        version_added: "2.4"
     path:
         required: true
         description:
@@ -57,10 +64,16 @@
 '''
 
 EXAMPLES = '''
-# Generate an OpenSSL public key.
+# Generate an OpenSSL public key in PEM format.
+- openssl_publickey:
+    path: /etc/ssl/public/ansible.com.pem
+    privatekey_path: /etc/ssl/private/ansible.com.pem
+
+# Generate an OpenSSL public key in OpenSSH v2 format.
 - openssl_publickey:
     path: /etc/ssl/public/ansible.com.pem
     privatekey_path: /etc/ssl/private/ansible.com.pem
+    format: OpenSSH
 
 # Force regenerate an OpenSSL public key if it already exists
 - openssl_publickey:
@@ -81,6 +94,11 @@
     returned: changed or success
     type: string
     sample: /etc/ssl/private/ansible.com.pem
+format:
+    description: The format of the public key (PEM, OpenSSH, ...)
+    returned: changed or success
+    type: string
+    sample: PEM
 filename:
     description: Path to the generated TLS/SSL public key file
     returned: changed or success
@@ -107,6 +125,8 @@
 
 try:
     from OpenSSL import crypto
+    from cryptography.hazmat.backends import default_backend
+    from cryptography.hazmat.primitives import serialization as crypto_serialization
 except ImportError:
     pyopenssl_found = False
 else:
@@ -125,6 +145,7 @@ class PublicKey(object):
     def __init__(self, module):
         self.state = module.params['state']
         self.force = module.params['force']
+        self.format = module.params['format']
         self.name = os.path.basename(module.params['path'])
         self.path = module.params['path']
         self.privatekey_path = module.params['privatekey_path']
@@ -133,15 +154,25 @@ def __init__(self, module):
         self.fingerprint = {}
         self.check_mode = module.check_mode
 
-
     def generate(self, module):
         """Generate the public key."""
 
         if not os.path.exists(self.path) or self.force:
             try:
                 privatekey_content = open(self.privatekey_path, 'r').read()
-                self.privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, privatekey_content)
-                publickey_content = crypto.dump_publickey(crypto.FILETYPE_PEM, self.privatekey)
+
+                if self.format == 'OpenSSH':
+                    key = crypto_serialization.load_pem_private_key(privatekey_content,
+                                                                    password=None,
+                                                                    backend=default_backend())
+                    publickey_content = key.public_key().public_bytes(
+                        crypto_serialization.Encoding.OpenSSH,
+                        crypto_serialization.PublicFormat.OpenSSH
+                    )
+                else:
+                    self.privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, privatekey_content)
+                    publickey_content = crypto.dump_publickey(crypto.FILETYPE_PEM, self.privatekey)
+
                 publickey_file = open(self.path, 'w')
                 publickey_file.write(publickey_content)
                 publickey_file.close()
@@ -175,6 +206,7 @@ def dump(self):
         result = {
             'privatekey': self.privatekey_path,
             'filename': self.path,
+            'format': self.format,
             'changed': self.changed,
             'fingerprint': self.fingerprint,
         }
@@ -190,6 +222,7 @@ def main():
             force=dict(default=False, type='bool'),
             path=dict(required=True, type='path'),
             privatekey_path=dict(type='path'),
+            format=dict(type='str', choices=['PEM', 'OpenSSH'], default='PEM'),
         ),
         supports_check_mode = True,
         add_file_common_args = True,


Test patch: