Skip to content
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

Ssh key management for softlayer #321

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
49acc51
First round of changes: Add Keypar object as in other drivers, add s…
Itxaka Jun 21, 2014
60b0a71
implement get_keypar and catch and raise Libclouderror if no keys are…
Jun 22, 2014
ff688a3
raise KeyPairDoesNotExistError instead of Libcloud generic eror
Jun 22, 2014
77a663f
added working create_key_pair and import_key_pair_from_string
Jun 22, 2014
c6ae1dc
add delete_key_pair method
Jun 22, 2014
b403777
check if _key_name_to_id returns and empty key and raise KeyPairDoesN…
Jun 22, 2014
cd2a511
make _key_name_to_id return the id instead of the whole key, make get…
Jun 22, 2014
a2db2d6
wrap the key_id on node cration around [] as we need to pass a list o…
Jun 22, 2014
c244601
move the import to the proper place, remove Crypto import, add pycryp…
Jun 22, 2014
20b0239
use Importerror to determine if we can use create_key_pair or not
Jun 22, 2014
66a2e0f
add tests for the new methods, all passing
Jun 22, 2014
605893b
Add ssh key management to SoftLayer driver. Adds methods for add/remo…
Itxaka Jun 21, 2014
d1954f5
Merge branch 'ssh_key_management_for_softlayer' of https://github.com…
Jun 22, 2014
fb6d574
Use the propers arg name
Jun 22, 2014
583b14b
assert that create_key_pair() raises a NotImplemented if pycrypto is …
Jun 22, 2014
6668136
Raise NotImplementedError instead of NotImplemented, fix setting cryp…
Jun 22, 2014
391149d
Fix lint stuff, flake8 now reports no issues
Jun 22, 2014
6099e09
Fix lint stuff
Jun 22, 2014
1c7ca6e
Fix lint stuff
Jun 22, 2014
2d96355
Use the base Keypair and store the key id in the extra field
Jun 22, 2014
76c8ad4
Moved private methods to the end of the file, change " to ', use name…
Aug 31, 2014
6b809b7
simplify pycryto import status
Aug 31, 2014
9167cc9
add newline at the end of the file
Aug 31, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
93 changes: 90 additions & 3 deletions libcloud/compute/drivers/softlayer.py
Expand Up @@ -17,13 +17,20 @@
"""

import time
crypto = False
try:
from Crypto.PublicKey import RSA
crypto = True
except ImportError:
pass

from libcloud.common.base import ConnectionUserAndKey
from libcloud.common.xmlrpc import XMLRPCResponse, XMLRPCConnection
from libcloud.common.types import InvalidCredsError, LibcloudError
from libcloud.compute.types import Provider, NodeState
from libcloud.compute.base import NodeDriver, Node, NodeLocation, NodeSize, \
NodeImage
NodeImage, KeyPair
from libcloud.compute.types import KeyPairDoesNotExistError

DEFAULT_DOMAIN = 'example.com'
DEFAULT_CPU_SIZE = 1
Expand Down Expand Up @@ -158,7 +165,6 @@ def request(self, service, method, *args, **kwargs):

args = ({'headers': headers}, ) + args
endpoint = '%s/%s' % (self.endpoint, service)

return super(SoftLayerConnection, self).request(method, *args,
**{'endpoint':
endpoint})
Expand Down Expand Up @@ -204,7 +210,7 @@ class SoftLayerNodeDriver(NodeDriver):
website = 'http://www.softlayer.com/'
type = Provider.SOFTLAYER

features = {'create_node': ['generates_password']}
features = {'create_node': ['generates_password', 'ssh_key']}

def _to_node(self, host):
try:
Expand Down Expand Up @@ -330,6 +336,8 @@ def create_node(self, **kwargs):
:type ex_datacenter: ``str``
:keyword ex_os: e.g. UBUNTU_LATEST
:type ex_os: ``str``
:keyword ex_keyname: The name of the key pair
:type ex_keyname: ``str``
"""
name = kwargs['name']
os = 'DEBIAN_LATEST'
Expand Down Expand Up @@ -402,6 +410,9 @@ def create_node(self, **kwargs):
if datacenter:
newCCI['datacenter'] = {'name': datacenter}

if 'ex_keyname' in kwargs:
newCCI['sshKeys'] = [self._key_name_to_id(kwargs['ex_keyname'])]

res = self.connection.request(
'SoftLayer_Virtual_Guest', 'createObject', newCCI
).object
Expand All @@ -411,6 +422,82 @@ def create_node(self, **kwargs):

return self._to_node(raw_node)

def _to_key_pairs(self, elems):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor style thing - for consistency, please move "private" methods to the end of the class after all the "public" ones.

key_pairs = [self._to_key_pair(elem=elem) for elem in elems]
return key_pairs

def _to_key_pair(self, elem):
key_pair = KeyPair(name=elem['label'],
public_key=elem['key'],
fingerprint=elem['fingerprint'],
private_key=elem.get('private', None),
driver=self,
extra={'id': elem['id']})
return key_pair

def _key_name_to_id(self, key):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argument should probably be called name since it represents a key name and not a key object.

result = self.connection.request(
'SoftLayer_Account', 'getSshKeys'
).object
key_id = [x for x in result if x['label'] == key]
if len(key_id) == 0:
raise KeyPairDoesNotExistError(key, self)
else:
return int(key_id[0]['id'])

def list_key_pairs(self):
result = self.connection.request(
'SoftLayer_Account', 'getSshKeys'
).object
elems = [x for x in result]
key_pairs = self._to_key_pairs(elems=elems)
return key_pairs

def get_key_pair(self, name):
key_id = self._key_name_to_id(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing - for consistency and explicitness sake, please use keyword arguments (self._key_name_to_id(name=name)).

result = self.connection.request(
'SoftLayer_Security_Ssh_Key', 'getObject', id=key_id
).object
return self._to_key_pair(result)

# TODO: Check this with the libcloud guys,
# can we create new dependencies?
def create_key_pair(self, name):
if crypto is False:
raise NotImplementedError("create_key_pair needs"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor style thing - for consistency, please use single quotes around strings.

" the pycrypto library")
key = RSA.generate(2048)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably also allow user to specify key size and default to 4096 bits just to be on the safe side.

For example:

def create_key_pair(self, name, ex_size=4096)

new_key = {
'key': key.publickey().exportKey("OpenSSH"),
'label': name,
'notes': '',
}
result = self.connection.request(
'SoftLayer_Security_Ssh_Key', 'createObject', new_key
).object
result['private'] = key.exportKey("PEM")
return self._to_key_pair(result)

def import_key_pair_from_string(self, name, key_material):
new_key = {
'key': key_material,
'label': name,
'notes': '',
}
result = self.connection.request(
'SoftLayer_Security_Ssh_Key', 'createObject', new_key
).object

key_pair = self._to_key_pair(result)
return key_pair

def delete_key_pair(self, key_pair):
key = self._key_name_to_id(key_pair)
result = self.connection.request(
'SoftLayer_Security_Ssh_Key', 'deleteObject', id=key
).object
return result

def _to_image(self, img):
return NodeImage(
id=img['template']['operatingSystemReferenceCode'],
Expand Down
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<params>
<param>
<value>
<struct>
<member>
<name>id</name>
<value>
<int>1</int>
</value>
</member>
<member>
<name>key</name>
<value>
<string>ssh-key</string>
</value>
</member>
<member>
<name>label</name>
<value>
<string>test1</string>
</value>
</member>
<member>
<name>fingerprint</name>
<value>
<string>00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00</string>
</value>
</member>
</struct>
</value>
</param>
<param>
<value>
<struct>
<member>
<name>id</name>
<value>
<int>2</int>
</value>
</member>
<member>
<name>key</name>
<value>
<string>ssh-key</string>
</value>
</member>
<member>
<name>label</name>
<value>
<string>test2</string>
</value>
</member>
<member>
<name>fingerprint</name>
<value>
<string>00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00</string>
</value>
</member>
</struct>
</value>
</param>
</params>
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<params>
<param>
<value>
<struct>
<member>
<name>id</name>
<value>
<int>1</int>
</value>
</member>
<member>
<name>key</name>
<value>
<string>ssh-key</string>
</value>
</member>
<member>
<name>label</name>
<value>
<string>my-key-pair</string>
</value>
</member>
<member>
<name>label</name>
<value>
<string>my-key-pair</string>
</value>
</member>
<member>
<name>fingerprint</name>
<value>
<string>1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:6f</string>
</value>
</member>
</struct>
</value>
</param>
</params>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<params>
<param>
<value>
<struct>
<member>
<name>status</name>
<value>
<string>success</string>
</value>
</member>
</struct>
</value>
</param>
</params>
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<params>
<param>
<value>
<struct>
<member>
<name>id</name>
<value>
<int>1</int>
</value>
</member>
<member>
<name>key</name>
<value>
<string>ssh-key</string>
</value>
</member>
<member>
<name>label</name>
<value>
<string>test1</string>
</value>
</member>
<member>
<name>fingerprint</name>
<value>
<string>00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00</string>
</value>
</member>
</struct>
</value>
</param>
</params>
65 changes: 64 additions & 1 deletion libcloud/test/compute/test_softlayer.py
Expand Up @@ -16,6 +16,14 @@
import unittest
import sys

crypto = False
try:
import Crypto
crypto = True
Crypto
except ImportError:
pass

from libcloud.common.types import InvalidCredsError

from libcloud.utils.py3 import httplib
Expand All @@ -25,12 +33,15 @@
from libcloud.compute.drivers.softlayer import SoftLayerNodeDriver as SoftLayer
from libcloud.compute.drivers.softlayer import SoftLayerException, \
NODE_STATE_MAP
from libcloud.compute.types import NodeState
from libcloud.compute.types import NodeState, KeyPairDoesNotExistError

from libcloud.test import MockHttp # pylint: disable-msg=E0611
from libcloud.test.file_fixtures import ComputeFileFixtures
from libcloud.test.secrets import SOFTLAYER_PARAMS

null_fingerprint = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:' + \
'00:00:00:00:00'


class SoftLayerTests(unittest.TestCase):

Expand Down Expand Up @@ -119,6 +130,7 @@ def test_create_node_ex_options(self):
ex_cpus=2,
ex_ram=2048,
ex_disk=100,
ex_key='test1',
ex_bandwidth=10,
ex_local_disk=False,
ex_datacenter='Dal05',
Expand All @@ -132,6 +144,37 @@ def test_destroy_node(self):
node = self.driver.list_nodes()[0]
self.driver.destroy_node(node)

def test_list_keypairs(self):
keypairs = self.driver.list_key_pairs()
self.assertEqual(len(keypairs), 2)
self.assertEqual(keypairs[0].name, 'test1')
self.assertEqual(keypairs[0].fingerprint, null_fingerprint)

def test_get_key_pair(self):
key_pair = self.driver.get_key_pair(name='test1')
self.assertEqual(key_pair.name, 'test1')

def test_get_key_pair_does_not_exist(self):
self.assertRaises(KeyPairDoesNotExistError, self.driver.get_key_pair,
name='test-key-pair')

def test_create_key_pair(self):
if crypto:
key_pair = self.driver.create_key_pair(name='my-key-pair')
fingerprint = ('1f:51:ae:28:bf:89:e9:d8:1f:25:5d'
':37:2d:7d:b8:ca:9f:f5:f1:6f')

self.assertEqual(key_pair.name, 'my-key-pair')
self.assertEqual(key_pair.fingerprint, fingerprint)
self.assertTrue(key_pair.private_key is not None)
else:
self.assertRaises(NotImplementedError, self.driver.create_key_pair,
name='my-key-pair')

def test_delete_key_pair(self):
success = self.driver.delete_key_pair('test1')
self.assertTrue(success)


class SoftLayerMockHttp(MockHttp):
fixtures = ComputeFileFixtures('softlayer')
Expand Down Expand Up @@ -188,6 +231,26 @@ def _xmlrpc_v3_SoftLayer_Virtual_Guest_deleteObject(
body = self.fixtures.load('empty.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])

def _xmlrpc_v3_SoftLayer_Account_getSshKeys(
self, method, url, body, headers):
body = self.fixtures.load('v3__SoftLayer_Account_getSshKeys.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])

def _xmlrpc_v3_SoftLayer_Security_Ssh_Key_getObject(
self, method, url, body, headers):
body = self.fixtures.load('v3__SoftLayer_Security_Ssh_Key_getObject.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])

def _xmlrpc_v3_SoftLayer_Security_Ssh_Key_createObject(
self, method, url, body, headers):
body = self.fixtures.load('v3__SoftLayer_Security_Ssh_Key_createObject.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])

def _xmlrpc_v3_SoftLayer_Security_Ssh_Key_deleteObject(
self, method, url, body, headers):
body = self.fixtures.load('v3__SoftLayer_Security_Ssh_Key_deleteObject.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])


if __name__ == '__main__':
sys.exit(unittest.main())