From 60467738edba9ea50578da38092ef7d6063495a4 Mon Sep 17 00:00:00 2001 From: Rene Moser <mail@renemoser.net> Date: Thu, 26 Mar 2015 19:52:36 +0100 Subject: [PATCH 1/6] cloudstack: add new module cloudstack_sshkey This module depends on ansible.module_utils.cloudstack. --- cloud/cloudstack/__init__.py | 0 cloud/cloudstack/cloudstack_sshkey.py | 210 ++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 cloud/cloudstack/__init__.py create mode 100644 cloud/cloudstack/cloudstack_sshkey.py diff --git a/cloud/cloudstack/__init__.py b/cloud/cloudstack/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py new file mode 100644 index 00000000000..414ded6c971 --- /dev/null +++ b/cloud/cloudstack/cloudstack_sshkey.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2015, René Moser <mail@renemoser.net> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: cloudstack_sshkey +short_description: Manages SSH keys on Apache CloudStack based clouds. +description: + - If no public key is provided, a new ssh private/public key pair will be + created and the private key will be returned. +version_added: '2.0' +author: René Moser +options: + name: + description: + - Name of public key. + required: true + default: null + aliases: [] + project: + description: + - Name of the project the public key to be registered in. + required: false + default: null + aliases: [] + state: + description: + - State of the public key. + required: false + default: 'present' + choices: [ 'present', 'absent' ] + aliases: [] + public_key: + description: + - String of the public key. + required: false + default: null + aliases: [] +''' + +EXAMPLES = ''' +--- +# create a new private / public key pair: +- local_action: cloudstack_sshkey name=linus@example.com + register: key +- debug: msg='private key is {{ key.private_key }}' + +# remove a public key by its name: +- local_action: cloudstack_sshkey name=linus@example.com state=absent + +# register your existing local public key: +- local_action: cloudstack_sshkey name=linus@example.com public_key='{{ lookup('file', '~/.ssh/id_rsa.pub') }}' +''' + +RETURN = ''' +--- +name: + description: Name of the SSH public key. + returned: success + type: string + sample: linus@example.com +fingerprint: + description: Fingerprint of the SSH public key. + returned: success + type: string + sample: "86:5e:a3:e8:bd:95:7b:07:7c:c2:5c:f7:ad:8b:09:28" +private_key: + description: Private key of generated SSH keypair. + returned: changed + type: string + sample: "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQCkeFYjI+4k8bWfIRMzp4pCzhlopNydbbwRu824P5ilD4ATWMUG\nvEtuCQ2Mp5k5Bma30CdYHgh2/SbxC5RxXSUKTUJtTKpoJUy8PAhb1nn9dnfkC2oU\naRVi9NRUgypTIZxMpgooHOxvAzWxbZCyh1W+91Ld3FNaGxTLqTgeevY84wIDAQAB\nAoGAcwQwgLyUwsNB1vmjWwE0QEmvHS4FlhZyahhi4hGfZvbzAxSWHIK7YUT1c8KU\n9XsThEIN8aJ3GvcoL3OAqNKRnoNb14neejVHkYRadhxqc0GVN6AUIyCqoEMpvhFI\nQrinM572ORzv5ffRjCTbvZcYlW+sqFKNo5e8pYIB8TigpFECQQDu7bg9vkvg8xPs\nkP1K+EH0vsR6vUfy+m3euXjnbJtiP7RoTkZk0JQMOmexgy1qQhISWT0e451wd62v\nJ7M0trl5AkEAsDivJnMIlCCCypwPN4tdNUYpe9dtidR1zLmb3SA7wXk5xMUgLZI9\ncWPjBCMt0KKShdDhQ+hjXAyKQLF7iAPuOwJABjdHCMwvmy2XwhrPjCjDRoPEBtFv\n0sFzJE08+QBZVogDwIbwy+SlRWArnHGmN9J6N+H8dhZD3U4vxZPJ1MBAOQJBAJxO\nCv1dt1Q76gbwmYa49LnWO+F+2cgRTVODpr5iYt5fOmBQQRRqzFkRMkFvOqn+KVzM\nQ6LKM6dn8BEl295vLhUCQQCVDWzoSk3GjL3sOjfAUTyAj8VAXM69llaptxWWySPM\nE9pA+8rYmHfohYFx7FD5/KWCO+sfmxTNB48X0uwyE8tO\n-----END RSA PRIVATE KEY-----\n" +''' + + +try: + from cs import CloudStack, CloudStackException, read_config + has_lib_cs = True +except ImportError: + has_lib_cs = False + +from ansible.module_utils.cloudstack import * + +class AnsibleCloudStackSshKey(AnsibleCloudStack): + + def __init__(self, module): + AnsibleCloudStack.__init__(self, module) + self.result = { + 'changed': False, + } + self.ssh_key = None + + + def register_ssh_key(self): + ssh_key = self.get_ssh_key() + if not ssh_key: + self.result['changed'] = True + args = {} + args['projectid'] = self.get_project_id() + args['name'] = self.module.params.get('name') + args['publickey'] = self.module.params.get('public_key') + if not self.module.check_mode: + ssh_key = self.cs.registerSSHKeyPair(**args) + return ssh_key + + + def create_ssh_key(self): + ssh_key = self.get_ssh_key() + if not ssh_key: + self.result['changed'] = True + args = {} + args['projectid'] = self.get_project_id() + args['name'] = self.module.params.get('name') + if not self.module.check_mode: + res = self.cs.createSSHKeyPair(**args) + ssh_key = res['keypair'] + return ssh_key + + + def remove_ssh_key(self): + ssh_key = self.get_ssh_key() + if ssh_key: + self.result['changed'] = True + args = {} + args['name'] = self.module.params.get('name') + if not self.module.check_mode: + res = self.cs.deleteSSHKeyPair(**args) + return ssh_key + + + def get_ssh_key(self): + if not self.ssh_key: + args = {} + args['projectid'] = self.get_project_id() + args['name'] = self.module.params.get('name') + + ssh_keys = self.cs.listSSHKeyPairs(**args) + if ssh_keys and 'sshkeypair' in ssh_keys: + self.ssh_key = ssh_keys['sshkeypair'][0] + return self.ssh_key + + + def get_result(self, ssh_key): + if ssh_key: + if 'fingerprint' in ssh_key: + self.result['fingerprint'] = ssh_key['fingerprint'] + + if 'name' in ssh_key: + self.result['name'] = ssh_key['name'] + + if 'privatekey' in ssh_key: + self.result['private_key'] = ssh_key['privatekey'] + return self.result + + +def main(): + module = AnsibleModule( + argument_spec = dict( + name = dict(required=True, default=None), + public_key = dict(default=None), + project = dict(default=None), + state = dict(choices=['present', 'absent'], default='present'), + api_key = dict(default=None), + api_secret = dict(default=None), + api_url = dict(default=None), + api_http_method = dict(default='get'), + ), + supports_check_mode=True + ) + + if not has_lib_cs: + module.fail_json(msg="python library cs required: pip install cs") + + try: + acs_sshkey = AnsibleCloudStackSshKey(module) + state = module.params.get('state') + if state in ['absent']: + ssh_key = acs_sshkey.remove_ssh_key() + else: + if module.params.get('public_key'): + ssh_key = acs_sshkey.register_ssh_key() + else: + ssh_key = acs_sshkey.create_ssh_key() + + result = acs_sshkey.get_result(ssh_key) + + except CloudStackException, e: + module.fail_json(msg='CloudStackException: %s' % str(e)) + + module.exit_json(**result) + +# import module snippets +from ansible.module_utils.basic import * +main() From 82e25447adeab1c7d464e64119b594c5386506f8 Mon Sep 17 00:00:00 2001 From: Rene Moser <mail@renemoser.net> Date: Sat, 28 Mar 2015 10:58:02 +0100 Subject: [PATCH 2/6] cloudstack_ssh: fix missing projectid if state=absent --- cloud/cloudstack/cloudstack_sshkey.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py index 414ded6c971..97d6a222f09 100644 --- a/cloud/cloudstack/cloudstack_sshkey.py +++ b/cloud/cloudstack/cloudstack_sshkey.py @@ -139,6 +139,7 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): self.result['changed'] = True args = {} args['name'] = self.module.params.get('name') + args['projectid'] = self.get_project_id() if not self.module.check_mode: res = self.cs.deleteSSHKeyPair(**args) return ssh_key From bf32de8d8f705692662108759c5d2077baf6ddba Mon Sep 17 00:00:00 2001 From: Rene Moser <mail@renemoser.net> Date: Sat, 28 Mar 2015 22:07:39 +0100 Subject: [PATCH 3/6] cloudstack_ssh: register_ssh_key() set public_key as param --- cloud/cloudstack/cloudstack_sshkey.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py index 97d6a222f09..589e2783913 100644 --- a/cloud/cloudstack/cloudstack_sshkey.py +++ b/cloud/cloudstack/cloudstack_sshkey.py @@ -107,14 +107,14 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): self.ssh_key = None - def register_ssh_key(self): + def register_ssh_key(self, public_key): ssh_key = self.get_ssh_key() if not ssh_key: self.result['changed'] = True args = {} args['projectid'] = self.get_project_id() args['name'] = self.module.params.get('name') - args['publickey'] = self.module.params.get('public_key') + args['publickey'] = public_key if not self.module.check_mode: ssh_key = self.cs.registerSSHKeyPair(**args) return ssh_key @@ -194,8 +194,9 @@ def main(): if state in ['absent']: ssh_key = acs_sshkey.remove_ssh_key() else: - if module.params.get('public_key'): - ssh_key = acs_sshkey.register_ssh_key() + public_key = module.params.get('public_key') + if public_key: + ssh_key = acs_sshkey.register_ssh_key(public_key) else: ssh_key = acs_sshkey.create_ssh_key() From a24d691419c41f0e64b95d3560237f3829340917 Mon Sep 17 00:00:00 2001 From: Rene Moser <mail@renemoser.net> Date: Sat, 28 Mar 2015 22:09:21 +0100 Subject: [PATCH 4/6] cloudstack_ssh: update description --- cloud/cloudstack/cloudstack_sshkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py index 589e2783913..7e803be02e5 100644 --- a/cloud/cloudstack/cloudstack_sshkey.py +++ b/cloud/cloudstack/cloudstack_sshkey.py @@ -23,8 +23,8 @@ DOCUMENTATION = ''' module: cloudstack_sshkey short_description: Manages SSH keys on Apache CloudStack based clouds. description: - - If no public key is provided, a new ssh private/public key pair will be - created and the private key will be returned. + - If no key was found and no public key was provided and a new SSH + private/public key pair will be created and the private key will be returned. version_added: '2.0' author: René Moser options: From c03baa7ec64a6ae6acbd4176c24d1e757b88c42b Mon Sep 17 00:00:00 2001 From: Rene Moser <mail@renemoser.net> Date: Sat, 28 Mar 2015 22:12:19 +0100 Subject: [PATCH 5/6] cloudstack_ssh: replace ssh public key if fingerprints do not match --- cloud/cloudstack/cloudstack_sshkey.py | 38 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py index 7e803be02e5..4f63a9d566b 100644 --- a/cloud/cloudstack/cloudstack_sshkey.py +++ b/cloud/cloudstack/cloudstack_sshkey.py @@ -95,6 +95,12 @@ try: except ImportError: has_lib_cs = False +try: + import sshpubkeys + has_lib_sshpubkeys = True +except ImportError: + has_lib_sshpubkeys = False + from ansible.module_utils.cloudstack import * class AnsibleCloudStackSshKey(AnsibleCloudStack): @@ -109,14 +115,30 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): def register_ssh_key(self, public_key): ssh_key = self.get_ssh_key() + + args = {} + args['projectid'] = self.get_project_id() + args['name'] = self.module.params.get('name') + + res = None if not ssh_key: self.result['changed'] = True - args = {} - args['projectid'] = self.get_project_id() - args['name'] = self.module.params.get('name') args['publickey'] = public_key if not self.module.check_mode: - ssh_key = self.cs.registerSSHKeyPair(**args) + res = self.cs.registerSSHKeyPair(**args) + + else: + fingerprint = self._get_ssh_fingerprint(public_key) + if ssh_key['fingerprint'] != fingerprint: + self.result['changed'] = True + if not self.module.check_mode: + self.cs.deleteSSHKeyPair(**args) + args['publickey'] = public_key + res = self.cs.registerSSHKeyPair(**args) + + if res and 'keypair' in res: + ssh_key = res['keypair'] + return ssh_key @@ -170,6 +192,11 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): return self.result + def _get_ssh_fingerprint(self, public_key): + key = sshpubkeys.SSHKey(public_key) + return key.hash() + + def main(): module = AnsibleModule( argument_spec = dict( @@ -188,6 +215,9 @@ def main(): if not has_lib_cs: module.fail_json(msg="python library cs required: pip install cs") + if not has_lib_sshpubkeys: + module.fail_json(msg="python library sshpubkeys required: pip install sshpubkeys") + try: acs_sshkey = AnsibleCloudStackSshKey(module) state = module.params.get('state') From 392feaea63f845f53c02f70ea4a7dd3e723f3ed9 Mon Sep 17 00:00:00 2001 From: Rene Moser <mail@renemoser.net> Date: Tue, 31 Mar 2015 11:55:39 +0200 Subject: [PATCH 6/6] cloudstack_sshkey: cleanup docs --- cloud/cloudstack/cloudstack_sshkey.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py index 4f63a9d566b..657e367fefe 100644 --- a/cloud/cloudstack/cloudstack_sshkey.py +++ b/cloud/cloudstack/cloudstack_sshkey.py @@ -32,27 +32,22 @@ options: description: - Name of public key. required: true - default: null - aliases: [] project: description: - Name of the project the public key to be registered in. required: false default: null - aliases: [] state: description: - State of the public key. required: false default: 'present' choices: [ 'present', 'absent' ] - aliases: [] public_key: description: - String of the public key. required: false default: null - aliases: [] ''' EXAMPLES = '''