ec2: add support for creation and importing of real SSH keys (#2108)
* ec2: add support for creation and importing of real SSH keys * setup: lock PyYAML version to avoid incompatibilities
This commit is contained in:
parent
d8ff67197b
commit
fb2a76fd66
8 changed files with 179 additions and 37 deletions
|
|
@ -58,6 +58,14 @@ class InvalidKeyPairDuplicateError(EC2ClientError):
|
|||
.format(key))
|
||||
|
||||
|
||||
class InvalidKeyPairFormatError(EC2ClientError):
|
||||
|
||||
def __init__(self):
|
||||
super(InvalidKeyPairFormatError, self).__init__(
|
||||
"InvalidKeyPair.Format",
|
||||
"Key is not in valid OpenSSH public key format")
|
||||
|
||||
|
||||
class InvalidVPCIdError(EC2ClientError):
|
||||
|
||||
def __init__(self, vpc_id):
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType
|
|||
from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest
|
||||
from boto.ec2.launchspecification import LaunchSpecification
|
||||
|
||||
|
||||
from moto.compat import OrderedDict
|
||||
from moto.core import BaseBackend
|
||||
from moto.core.models import Model, BaseModel
|
||||
|
|
@ -43,6 +44,7 @@ from .exceptions import (
|
|||
InvalidInstanceIdError,
|
||||
InvalidInternetGatewayIdError,
|
||||
InvalidKeyPairDuplicateError,
|
||||
InvalidKeyPairFormatError,
|
||||
InvalidKeyPairNameError,
|
||||
InvalidNetworkAclIdError,
|
||||
InvalidNetworkAttachmentIdError,
|
||||
|
|
@ -120,6 +122,8 @@ from .utils import (
|
|||
random_customer_gateway_id,
|
||||
is_tag_filter,
|
||||
tag_filter_matches,
|
||||
rsa_public_key_parse,
|
||||
rsa_public_key_fingerprint
|
||||
)
|
||||
|
||||
INSTANCE_TYPES = json.load(
|
||||
|
|
@ -910,7 +914,14 @@ class KeyPairBackend(object):
|
|||
def import_key_pair(self, key_name, public_key_material):
|
||||
if key_name in self.keypairs:
|
||||
raise InvalidKeyPairDuplicateError(key_name)
|
||||
keypair = KeyPair(key_name, **random_key_pair())
|
||||
|
||||
try:
|
||||
rsa_public_key = rsa_public_key_parse(public_key_material)
|
||||
except ValueError:
|
||||
raise InvalidKeyPairFormatError()
|
||||
|
||||
fingerprint = rsa_public_key_fingerprint(rsa_public_key)
|
||||
keypair = KeyPair(key_name, material=public_key_material, fingerprint=fingerprint)
|
||||
self.keypairs[key_name] = keypair
|
||||
return keypair
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import fnmatch
|
||||
import random
|
||||
import re
|
||||
import six
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
import sshpubkeys.exceptions
|
||||
from sshpubkeys.keys import SSHKey
|
||||
|
||||
|
||||
EC2_RESOURCE_TO_PREFIX = {
|
||||
'customer-gateway': 'cgw',
|
||||
'dhcp-options': 'dopt',
|
||||
|
|
@ -453,23 +462,19 @@ def simple_aws_filter_to_re(filter_string):
|
|||
|
||||
|
||||
def random_key_pair():
|
||||
def random_hex():
|
||||
return chr(random.choice(list(range(48, 58)) + list(range(97, 102))))
|
||||
private_key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend())
|
||||
private_key_material = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption())
|
||||
public_key_fingerprint = rsa_public_key_fingerprint(private_key.public_key())
|
||||
|
||||
def random_fingerprint():
|
||||
return ':'.join([random_hex() + random_hex() for i in range(20)])
|
||||
|
||||
def random_material():
|
||||
return ''.join([
|
||||
chr(random.choice(list(range(65, 91)) + list(range(48, 58)) +
|
||||
list(range(97, 102))))
|
||||
for i in range(1000)
|
||||
])
|
||||
material = "---- BEGIN RSA PRIVATE KEY ----" + random_material() + \
|
||||
"-----END RSA PRIVATE KEY-----"
|
||||
return {
|
||||
'fingerprint': random_fingerprint(),
|
||||
'material': material
|
||||
'fingerprint': public_key_fingerprint,
|
||||
'material': private_key_material.decode('ascii')
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -535,3 +540,28 @@ def generate_instance_identity_document(instance):
|
|||
}
|
||||
|
||||
return document
|
||||
|
||||
|
||||
def rsa_public_key_parse(key_material):
|
||||
try:
|
||||
if not isinstance(key_material, six.binary_type):
|
||||
key_material = key_material.encode("ascii")
|
||||
|
||||
decoded_key = base64.b64decode(key_material).decode("ascii")
|
||||
public_key = SSHKey(decoded_key)
|
||||
except (sshpubkeys.exceptions.InvalidKeyException, UnicodeDecodeError):
|
||||
raise ValueError('bad key')
|
||||
|
||||
if not public_key.rsa:
|
||||
raise ValueError('bad key')
|
||||
|
||||
return public_key.rsa
|
||||
|
||||
|
||||
def rsa_public_key_fingerprint(rsa_public_key):
|
||||
key_data = rsa_public_key.public_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
fingerprint_hex = hashlib.md5(key_data).hexdigest()
|
||||
fingerprint = re.sub(r'([a-f0-9]{2})(?!$)', r'\1:', fingerprint_hex)
|
||||
return fingerprint
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue