Skip to content

Commit

Permalink
Create an internal key pair API.
Browse files Browse the repository at this point in the history
Creates an internal key pair API and update the EC2 and OS API's to
use it. This de-duplicates some of the code used to manage keypairs
across the APIs.

Fixes LP Bug #998059.

Change-Id: I10d58d7ce68cc2b993c72b6639f66c72def3bfbc
  • Loading branch information
dprince committed May 14, 2012
1 parent 2c7e0d1 commit ec0a65d
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 128 deletions.
94 changes: 35 additions & 59 deletions nova/api/ec2/cloud.py
Expand Up @@ -34,7 +34,6 @@
from nova import compute
from nova.compute import instance_types
from nova.compute import vm_states
from nova import crypto
from nova import db
from nova import exception
from nova import flags
Expand All @@ -61,33 +60,6 @@ def validate_ec2_id(val):
raise exception.InvalidInstanceIDMalformed(val)


def _gen_key(context, user_id, key_name):
"""Generate a key
This is a module level method because it is slow and we need to defer
it into a process pool."""
# NOTE(vish): generating key pair is slow so check for legal
# creation before creating key_pair
try:
db.key_pair_get(context, user_id, key_name)
raise exception.KeyPairExists(key_name=key_name)
except exception.NotFound:
pass

if quota.allowed_key_pairs(context, 1) < 1:
msg = _("Quota exceeded, too many key pairs.")
raise exception.EC2APIError(msg)

private_key, public_key, fingerprint = crypto.generate_key_pair()
key = {}
key['user_id'] = user_id
key['name'] = key_name
key['public_key'] = public_key
key['fingerprint'] = fingerprint
db.key_pair_create(context, key)
return {'private_key': private_key, 'fingerprint': fingerprint}


# EC2 API can return the following values as documented in the EC2 API
# http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/
# ApiReference-ItemType-InstanceStateType.html
Expand Down Expand Up @@ -217,6 +189,7 @@ def __init__(self):
self.volume_api = volume.API()
self.compute_api = compute.API(network_api=self.network_api,
volume_api=self.volume_api)
self.keypair_api = compute.api.KeypairAPI()
self.sgh = importutils.import_object(FLAGS.security_group_handler)

def __str__(self):
Expand Down Expand Up @@ -357,7 +330,7 @@ def delete_snapshot(self, context, snapshot_id, **kwargs):
return True

def describe_key_pairs(self, context, key_name=None, **kwargs):
key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
key_pairs = self.keypair_api.get_key_pairs(context, context.user_id)
if not key_name is None:
key_pairs = [x for x in key_pairs if x['name'] in key_name]

Expand All @@ -374,52 +347,55 @@ def describe_key_pairs(self, context, key_name=None, **kwargs):
return {'keySet': result}

def create_key_pair(self, context, key_name, **kwargs):
if not re.match('^[a-zA-Z0-9_\- ]+$', str(key_name)):
err = _("Value (%s) for KeyName is invalid."
" Content limited to Alphanumeric character, "
"spaces, dashes, and underscore.") % key_name
raise exception.EC2APIError(err)

if len(str(key_name)) > 255:
err = _("Value (%s) for Keyname is invalid."
" Length exceeds maximum of 255.") % key_name
raise exception.EC2APIError(err)

LOG.audit(_("Create key pair %s"), key_name, context=context)
data = _gen_key(context, context.user_id, key_name)

try:
keypair = self.keypair_api.create_key_pair(context,
context.user_id,
key_name)
except exception.KeypairLimitExceeded:
msg = _("Quota exceeded, too many key pairs.")
raise exception.EC2APIError(msg)
except exception.InvalidKeypair:
msg = _("Keypair data is invalid")
raise exception.EC2APIError(msg)
except exception.KeyPairExists:
msg = _("Key pair '%s' already exists.") % key_name
raise exception.KeyPairExists(msg)
return {'keyName': key_name,
'keyFingerprint': data['fingerprint'],
'keyMaterial': data['private_key']}
'keyFingerprint': keypair['fingerprint'],
'keyMaterial': keypair['private_key']}
# TODO(vish): when context is no longer an object, pass it here

def import_key_pair(self, context, key_name, public_key_material,
**kwargs):
LOG.audit(_("Import key %s"), key_name, context=context)
try:
db.key_pair_get(context, context.user_id, key_name)
raise exception.KeyPairExists(key_name=key_name)
except exception.NotFound:
pass

if quota.allowed_key_pairs(context, 1) < 1:
public_key = base64.b64decode(public_key_material)

try:
keypair = self.keypair_api.import_key_pair(context,
context.user_id,
key_name,
public_key)
except exception.KeypairLimitExceeded:
msg = _("Quota exceeded, too many key pairs.")
raise exception.EC2APIError(msg)
except exception.InvalidKeypair:
msg = _("Keypair data is invalid")
raise exception.EC2APIError(msg)
except exception.KeyPairExists:
msg = _("Key pair '%s' already exists.") % key_name
raise exception.EC2APIError(msg)

public_key = base64.b64decode(public_key_material)
fingerprint = crypto.generate_fingerprint(public_key)
key = {}
key['user_id'] = context.user_id
key['name'] = key_name
key['public_key'] = public_key
key['fingerprint'] = fingerprint
db.key_pair_create(context, key)
return {'keyName': key_name,
'keyFingerprint': fingerprint}
'keyFingerprint': keypair['fingerprint']}

def delete_key_pair(self, context, key_name, **kwargs):
LOG.audit(_("Delete key pair %s"), key_name, context=context)
try:
db.key_pair_destroy(context, context.user_id, key_name)
self.keypair_api.delete_key_pair(context, context.user_id,
key_name)
except exception.NotFound:
# aws returns true even if the key doesn't exist
pass
Expand Down
82 changes: 23 additions & 59 deletions nova/api/openstack/compute/contrib/keypairs.py
Expand Up @@ -17,18 +17,14 @@

""" Keypair management extension"""

import string

import webob
import webob.exc

from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
from nova.api.openstack import extensions
from nova import crypto
from nova import db
from nova.compute import api as compute_api
from nova import exception
from nova import quota


authorize = extensions.extension_authorizer('compute', 'keypairs')
Expand All @@ -50,26 +46,10 @@ def construct(self):


class KeypairController(object):
""" Keypair API controller for the OpenStack API """

# TODO(ja): both this file and nova.api.ec2.cloud.py have similar logic.
# move the common keypair logic to nova.compute.API?

def _gen_key(self):
"""
Generate a key
"""
private_key, public_key, fingerprint = crypto.generate_key_pair()
return {'private_key': private_key,
'public_key': public_key,
'fingerprint': fingerprint}

def _validate_keypair_name(self, value):
safechars = "_-" + string.digits + string.ascii_letters
clean_value = "".join(x for x in value if x in safechars)
if clean_value != value:
msg = _("Keypair name contains unsafe characters")
raise webob.exc.HTTPBadRequest(explanation=msg)
""" Keypair API controller for the OpenStack API """
def __init__(self):
self.api = compute_api.KeypairAPI()

@wsgi.serializers(xml=KeypairTemplate)
def create(self, req, body):
Expand All @@ -90,45 +70,29 @@ def create(self, req, body):
authorize(context)
params = body['keypair']
name = params['name']
self._validate_keypair_name(name)

if not 0 < len(name) < 256:
msg = _('Keypair name must be between 1 and 255 characters long')
raise webob.exc.HTTPBadRequest(explanation=msg)
# NOTE(ja): generation is slow, so shortcut invalid name exception
try:
db.key_pair_get(context, context.user_id, name)
msg = _("Key pair '%s' already exists.") % name
raise webob.exc.HTTPConflict(explanation=msg)
except exception.NotFound:
pass
if 'public_key' in params:
keypair = self.api.import_key_pair(context,
context.user_id, name,
params['public_key'])
else:
keypair = self.api.create_key_pair(context, context.user_id,
name)

keypair = {'user_id': context.user_id,
'name': name}
return {'keypair': keypair}

if quota.allowed_key_pairs(context, 1) < 1:
except exception.KeypairLimitExceeded:
msg = _("Quota exceeded, too many key pairs.")
raise webob.exc.HTTPRequestEntityTooLarge(
explanation=msg,
headers={'Retry-After': 0})
# import if public_key is sent
if 'public_key' in params:
try:
fingerprint = crypto.generate_fingerprint(params['public_key'])
except exception.InvalidKeypair:
msg = _("Keypair data is invalid")
raise webob.exc.HTTPBadRequest(explanation=msg)

keypair['public_key'] = params['public_key']
keypair['fingerprint'] = fingerprint
else:
generated_key = self._gen_key()
keypair['private_key'] = generated_key['private_key']
keypair['public_key'] = generated_key['public_key']
keypair['fingerprint'] = generated_key['fingerprint']

db.key_pair_create(context, keypair)
return {'keypair': keypair}
explanation=msg,
headers={'Retry-After': 0})
except exception.InvalidKeypair:
msg = _("Keypair data is invalid")
raise webob.exc.HTTPBadRequest(explanation=msg)
except exception.KeyPairExists:
msg = _("Key pair '%s' already exists.") % name
raise webob.exc.HTTPConflict(explanation=msg)

def delete(self, req, id):
"""
Expand All @@ -137,7 +101,7 @@ def delete(self, req, id):
context = req.environ['nova.context']
authorize(context)
try:
db.key_pair_destroy(context, context.user_id, id)
self.api.delete_key_pair(context, context.user_id, id)
except exception.KeypairNotFound:
raise webob.exc.HTTPNotFound()
return webob.Response(status_int=202)
Expand All @@ -149,7 +113,7 @@ def index(self, req):
"""
context = req.environ['nova.context']
authorize(context)
key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
key_pairs = self.api.get_key_pairs(context, context.user_id)
rval = []
for key_pair in key_pairs:
rval.append({'keypair': {
Expand Down
83 changes: 83 additions & 0 deletions nova/compute/api.py
Expand Up @@ -3,6 +3,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Piston Cloud Computing, Inc.
# Copyright 2012 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
Expand All @@ -23,13 +24,15 @@
import functools
import re
import time
import string

from nova import block_device
from nova.compute import aggregate_states
from nova.compute import instance_types
from nova.compute import power_state
from nova.compute import task_states
from nova.compute import vm_states
from nova import crypto
from nova.db import base
from nova import exception
from nova import flags
Expand Down Expand Up @@ -1908,3 +1911,83 @@ def _get_aggregate_info(self, context, aggregate):
result["metadata"] = metadata
result["hosts"] = hosts
return result


class KeypairAPI(base.Base):
"""Sub-set of the Compute Manager API for managing key pairs."""
def __init__(self, **kwargs):
super(KeypairAPI, self).__init__(**kwargs)

def _validate_keypair_name(self, context, user_id, key_name):
safechars = "_- " + string.digits + string.ascii_letters
clean_value = "".join(x for x in key_name if x in safechars)
if clean_value != key_name:
msg = _("Keypair name contains unsafe characters")
raise exception.InvalidKeypair(explanation=msg)

if not 0 < len(key_name) < 256:
msg = _('Keypair name must be between 1 and 255 characters long')
raise exception.InvalidKeypair(explanation=msg)

# NOTE: check for existing keypairs of same name
try:
self.db.key_pair_get(context, user_id, key_name)
msg = _("Key pair '%s' already exists.") % key_name
raise exception.KeyPairExists(explanation=msg)
except exception.NotFound:
pass

def import_key_pair(self, context, user_id, key_name, public_key):
"""Import a key pair using an existing public key."""
self._validate_keypair_name(context, user_id, key_name)

if quota.allowed_key_pairs(context, 1) < 1:
raise exception.KeypairLimitExceeded()

try:
fingerprint = crypto.generate_fingerprint(public_key)
except exception.InvalidKeypair:
msg = _("Keypair data is invalid")
raise exception.InvalidKeypair(explanation=msg)

keypair = {'user_id': user_id,
'name': key_name,
'fingerprint': fingerprint,
'public_key': public_key}

self.db.key_pair_create(context, keypair)
return keypair

def create_key_pair(self, context, user_id, key_name):
"""Create a new key pair."""
self._validate_keypair_name(context, user_id, key_name)

if quota.allowed_key_pairs(context, 1) < 1:
raise exception.KeypairLimitExceeded()

private_key, public_key, fingerprint = crypto.generate_key_pair()

keypair = {'user_id': user_id,
'name': key_name,
'fingerprint': fingerprint,
'public_key': public_key,
'private_key': private_key}
self.db.key_pair_create(context, keypair)

return keypair

def delete_key_pair(self, context, user_id, key_name):
"""Delete a keypair by name."""
self.db.key_pair_destroy(context, user_id, key_name)

def get_key_pairs(self, context, user_id):
"""List key pairs."""
key_pairs = self.db.key_pair_get_all_by_user(context, user_id)
rval = []
for key_pair in key_pairs:
rval.append({
'name': key_pair['name'],
'public_key': key_pair['public_key'],
'fingerprint': key_pair['fingerprint'],
})
return rval
4 changes: 4 additions & 0 deletions nova/exception.py
Expand Up @@ -993,6 +993,10 @@ class OnsetFileContentLimitExceeded(QuotaError):
message = _("Personality file content too long")


class KeypairLimitExceeded(QuotaError):
message = _("Maximum number of key pairs exceeded")


class AggregateError(NovaException):
message = _("Aggregate %(aggregate_id)s: action '%(action)s' "
"caused an error: %(reason)s.")
Expand Down

0 comments on commit ec0a65d

Please sign in to comment.