ソースを参照

Added keypair import functionality and refactored

Closes: https://github.com/gvlproject/cloudbridge/issues/49
Nuwan Goonasekera 8 年 前
コミット
732835e7da

+ 10 - 0
cloudbridge/cloud/base/resources.py

@@ -594,6 +594,7 @@ class BaseKeyPair(BaseCloudResource, KeyPair):
     def __init__(self, provider, key_pair):
         super(BaseKeyPair, self).__init__(provider)
         self._key_pair = key_pair
+        self._private_material = None
 
     def __eq__(self, other):
         return (isinstance(other, KeyPair) and
@@ -615,6 +616,15 @@ class BaseKeyPair(BaseCloudResource, KeyPair):
         """
         return self._key_pair.name
 
+    @property
+    def material(self):
+        return self._private_material
+
+    @material.setter
+    # pylint:disable=arguments-differ
+    def material(self, value):
+        self._private_material = value
+
     def delete(self):
         """
         Delete this KeyPair.

+ 8 - 1
cloudbridge/cloud/interfaces/services.py

@@ -1175,13 +1175,20 @@ class KeyPairService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def create(self, name):
+    def create(self, name, public_key_material=None):
         """
         Create a new key pair or raise an exception if one already exists.
+        If the public_key_material is provided, the material will be imported
+        to create the new keypair. Otherwise, a new public and private key
+        pair will be generated.
 
         :type name: str
         :param name: The name of the key pair to be created.
 
+        :type public_key_material: str
+        :param public_key_material: The key-pair material to import in OpenSSH
+                                    format.
+
         :rtype: ``object`` of :class:`.KeyPair`
         :return:  A keypair instance or ``None``.
         """

+ 0 - 8
cloudbridge/cloud/providers/aws/resources.py

@@ -546,14 +546,6 @@ class AWSKeyPair(BaseKeyPair):
     def __init__(self, provider, key_pair):
         super(AWSKeyPair, self).__init__(provider, key_pair)
 
-    @property
-    def material(self):
-        # boto3 object will only have this field if the value is not empty
-        if hasattr(self._key_pair, 'key_material'):
-            return self._key_pair.key_material
-        else:
-            return None
-
 
 class AWSVMFirewall(BaseVMFirewall):
 

+ 9 - 2
cloudbridge/cloud/providers/aws/services.py

@@ -4,6 +4,7 @@ import string
 
 from botocore.exceptions import ClientError
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
@@ -95,10 +96,16 @@ class AWSKeyPairService(BaseKeyPairService):
         return self.svc.find(filter_name='key-name', filter_value=name,
                              limit=limit, marker=marker)
 
-    def create(self, name):
+    def create(self, name, public_key_material=None):
         log.debug("Creating Key Pair Service %s", name)
         AWSKeyPair.assert_valid_resource_name(name)
-        return self.svc.create('create_key_pair', KeyName=name)
+        private_key = None
+        if not public_key_material:
+            public_key_material, private_key = cb_helpers.generate_key_pair()
+        kp = self.svc.create('import_key_pair', KeyName=name,
+                             PublicKeyMaterial=public_key_material)
+        kp.material = private_key
+        return kp
 
 
 class AWSVMFirewallService(BaseVMFirewallService):

+ 0 - 1
cloudbridge/cloud/providers/azure/azure_client.py

@@ -467,7 +467,6 @@ class AzureClient(object):
                                        public_ip_name).wait()
 
     def create_public_key(self, entity):
-
         return self.table_service. \
             insert_or_replace_entity(self.public_key_storage_table_name,
                                      entity)

+ 0 - 29
cloudbridge/cloud/providers/azure/helpers.py

@@ -1,8 +1,3 @@
-from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import serialization
-from cryptography.hazmat.primitives.asymmetric import rsa
-
-
 def filter_by_tag(list_items, filters):
     """
     This function filter items on the tags
@@ -41,27 +36,3 @@ def parse_url(template_url, original_url):
             resource_param.update({key[1:-1]: value})
 
     return resource_param
-
-
-def gen_key_pair():
-    """
-    This method generates the public and private key pair.
-    The public key format is OpenSSH and private key format is PEM container
-    :return:
-    """
-
-    private_key = rsa.generate_private_key(backend=default_backend(),
-                                           public_exponent=65537,
-                                           key_size=2048)
-
-    public_key_str = private_key.public_key(). \
-        public_bytes(serialization.Encoding.OpenSSH,
-                     serialization.PublicFormat.OpenSSH).decode('utf-8')
-
-    private_key_str = private_key. \
-        private_bytes(encoding=serialization.Encoding.PEM,
-                      format=serialization.PrivateFormat.TraditionalOpenSSL,
-                      encryption_algorithm=serialization.NoEncryption()
-                      ).decode('utf-8')
-
-    return (private_key_str, public_key_str)

+ 0 - 16
cloudbridge/cloud/providers/azure/resources.py

@@ -1595,7 +1595,6 @@ class AzureKeyPair(BaseKeyPair):
 
     def __init__(self, provider, key_pair):
         super(AzureKeyPair, self).__init__(provider, key_pair)
-        self._material = None
 
     @property
     def id(self):
@@ -1605,21 +1604,6 @@ class AzureKeyPair(BaseKeyPair):
     def name(self):
         return self._key_pair.Name
 
-    @property
-    def material(self):
-        """
-        Unencrypted private key.
-
-        :rtype: str
-        :return: Unencrypted private key or ``None`` if not available.
-
-        """
-        return self._material
-
-    @material.setter
-    def material(self, value):
-        self._material = value
-
     def delete(self):
         try:
             self._provider.azure_client.\

+ 7 - 7
cloudbridge/cloud/providers/azure/services.py

@@ -4,6 +4,7 @@ import uuid
 
 from azure.common import AzureException
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList, \
     ServerPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService, \
@@ -160,7 +161,7 @@ class AzureKeyPairService(BaseKeyPairService):
                                      [key_pair] if key_pair else [],
                                      limit, marker)
 
-    def create(self, name):
+    def create(self, name, public_key_material=None):
         AzureKeyPair.assert_valid_resource_name(name)
 
         key_pair = self.get(name)
@@ -169,21 +170,20 @@ class AzureKeyPairService(BaseKeyPairService):
             raise Exception(
                 'Keypair already exists with name {0}'.format(name))
 
-        private_key_str, public_key_str = azure_helpers.gen_key_pair()
+        private_key = None
+        if not public_key_material:
+            public_key_material, private_key = cb_helpers.generate_key_pair()
 
         entity = {
                   'PartitionKey': AzureKeyPairService.PARTITION_KEY,
                   'RowKey': str(uuid.uuid4()),
                   'Name': name,
-                  'Key': public_key_str
+                  'Key': public_key_material
                  }
 
         self.provider.azure_client.create_public_key(entity)
-
         key_pair = self.get(name)
-
-        key_pair.material = private_key_str
-
+        key_pair.material = private_key
         return key_pair
 
 

+ 0 - 11
cloudbridge/cloud/providers/openstack/resources.py

@@ -1015,17 +1015,6 @@ class OpenStackKeyPair(BaseKeyPair):
     def __init__(self, provider, key_pair):
         super(OpenStackKeyPair, self).__init__(provider, key_pair)
 
-    @property
-    def material(self):
-        """
-        Unencrypted private key.
-
-        :rtype: str
-        :return: Unencrypted private key or ``None`` if not available.
-
-        """
-        return getattr(self._key_pair, 'private_key', None)
-
 
 class OpenStackVMFirewall(BaseVMFirewall):
 

+ 9 - 2
cloudbridge/cloud/providers/openstack/services.py

@@ -7,6 +7,7 @@ import re
 
 from cinderclient.exceptions import NotFound as CinderNotFound
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseLaunchConfig
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService
@@ -165,7 +166,7 @@ class OpenStackKeyPairService(BaseKeyPairService):
         return ClientPagedResultList(self.provider, results,
                                      limit=limit, marker=marker)
 
-    def create(self, name):
+    def create(self, name, public_key_material=None):
         """
         Create a new key pair or raise an exception if one already exists.
 
@@ -178,9 +179,15 @@ class OpenStackKeyPairService(BaseKeyPairService):
         log.debug("Creating a new key pair with the name: %s", name)
         OpenStackKeyPair.assert_valid_resource_name(name)
 
-        kp = self.provider.nova.keypairs.create(name)
+        private_key = None
+        if not public_key_material:
+            public_key_material, private_key = cb_helpers.generate_key_pair()
+        kp = self.provider.nova.keypairs.create(name,
+                                                public_key=public_key_material)
+
         if kp:
             return OpenStackKeyPair(self.provider, kp)
+            kp.material = private_key
         log.debug("Key Pair with the name %s already exists", name)
         return None
 

+ 12 - 0
test/test_security_service.py

@@ -3,6 +3,7 @@ from test import helpers
 from test.helpers import ProviderTestBase
 from test.helpers import standard_interface_tests as sit
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
@@ -42,6 +43,17 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
             self.assertIsNone(kp.material,
                               "Keypair material should now be empty")
 
+    @helpers.skipIfNoService(['security.key_pairs'])
+    def test_import_key_pair(self):
+        name = 'cb_kpimport-{0}'.format(helpers.get_uuid())
+
+        public_key, _ = cb_helpers.generate_key_pair()
+        kp = self.provider.security.key_pairs.create(
+            name=name, public_key=public_key)
+        with helpers.cleanup_action(lambda: kp.delete()):
+            self.assertIsNone(kp.material, "Private KeyPair material should"
+                              " be none when key is imported.")
+
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_crud_vm_firewall(self):
         name = 'cb_crudfw-{0}'.format(helpers.get_uuid())