Skip to content

Commit

Permalink
Create nova cert worker for x509 support
Browse files Browse the repository at this point in the history
 * Adds new worker for cert management
 * Makes decrypt use an rpc to the worker
 * Moves CA filesystem creation out of cloud.setup
 * Moves test for X509 into crypto
 * Adds test for encrypting and decrypting using cert
 * Cleans up extra code in cloudpipe
 * Fixes bug 918563
 * Prepares for a future patch that will fix bug 903345

Change-Id: I4693c50c8f432706f97395af39e736f49d60e719
  • Loading branch information
vishvananda committed Jan 24, 2012
1 parent 30a40db commit 0c5273c
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 113 deletions.
2 changes: 1 addition & 1 deletion bin/nova-all
Expand Up @@ -65,7 +65,7 @@ if __name__ == '__main__':
except (Exception, SystemExit):
logging.exception(_('Failed to load %s') % 'objectstore-wsgi')
for binary in ['nova-xvpvncproxy', 'nova-compute', 'nova-volume',
'nova-network', 'nova-scheduler', 'nova-vsa']:
'nova-network', 'nova-scheduler', 'nova-vsa', 'nova-cert']:
try:
servers.append(service.Service.create(binary=binary))
except (Exception, SystemExit):
Expand Down
47 changes: 47 additions & 0 deletions bin/nova-cert
@@ -0,0 +1,47 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 OpenStack, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Starter script for Nova Cert."""

import eventlet
eventlet.monkey_patch()

import os
import sys

# If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
sys.path.insert(0, POSSIBLE_TOPDIR)


from nova import flags
from nova import log as logging
from nova import service
from nova import utils

if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
utils.monkey_patch()
server = service.Service.create(binary='nova-cert')
service.serve(server)
service.wait()
25 changes: 0 additions & 25 deletions nova/api/ec2/cloud.py
Expand Up @@ -205,35 +205,10 @@ def __init__(self):
self.volume_api = volume.API()
self.compute_api = compute.API(network_api=self.network_api,
volume_api=self.volume_api)
self.setup()

def __str__(self):
return 'CloudController'

def setup(self):
""" Ensure the keychains and folders exist. """
# FIXME(ja): this should be moved to a nova-manage command,
# if not setup throw exceptions instead of running
# Create keys folder, if it doesn't exist
if not os.path.exists(FLAGS.keys_path):
os.makedirs(FLAGS.keys_path)
# Gen root CA, if we don't have one
root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file)
if not os.path.exists(root_ca_path):
genrootca_sh_path = os.path.join(os.path.dirname(__file__),
os.path.pardir,
os.path.pardir,
'CA',
'genrootca.sh')

start = os.getcwd()
if not os.path.exists(FLAGS.ca_path):
os.makedirs(FLAGS.ca_path)
os.chdir(FLAGS.ca_path)
# TODO(vish): Do this with M2Crypto instead
utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path)
os.chdir(start)

def _get_image_state(self, image):
# NOTE(vish): fallback status if image_state isn't set
state = image.get('status')
Expand Down
23 changes: 3 additions & 20 deletions nova/api/openstack/compute/contrib/cloudpipe.py
Expand Up @@ -60,28 +60,11 @@ def __init__(self):

def setup(self):
"""Ensure the keychains and folders exist."""
# TODO(todd): this was copyed from api.ec2.cloud
# FIXME(ja): this should be moved to a nova-manage command,
# if not setup throw exceptions instead of running
# Create keys folder, if it doesn't exist
# NOTE(vish): One of the drawbacks of doing this in the api is
# the keys will only be on the api node that launched
# the cloudpipe.
if not os.path.exists(FLAGS.keys_path):
os.makedirs(FLAGS.keys_path)
# Gen root CA, if we don't have one
root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file)
if not os.path.exists(root_ca_path):
genrootca_sh_path = os.path.join(os.path.dirname(__file__),
os.path.pardir,
os.path.pardir,
'CA',
'genrootca.sh')

start = os.getcwd()
if not os.path.exists(FLAGS.ca_path):
os.makedirs(FLAGS.ca_path)
os.chdir(FLAGS.ca_path)
# TODO(vish): Do this with M2Crypto instead
utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path)
os.chdir(start)

def _get_cloudpipe_for_project(self, context, project_id):
"""Get the cloudpipe instance for a project ID."""
Expand Down
15 changes: 15 additions & 0 deletions nova/cert/__init__.py
@@ -0,0 +1,15 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 OpenStack, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
67 changes: 67 additions & 0 deletions nova/cert/manager.py
@@ -0,0 +1,67 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 OpenStack, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Cert manager manages x509 certificates.
**Related Flags**
:cert_topic: What :mod:`rpc` topic to listen to (default: `cert`).
:cert_manager: The module name of a class derived from
:class:`manager.Manager` (default:
:class:`nova.cert.manager.Manager`).
"""

import base64

from nova import crypto
from nova import flags
from nova import log as logging
from nova import manager

LOG = logging.getLogger('nova.cert.manager')
FLAGS = flags.FLAGS


class CertManager(manager.Manager):
def init_host(self):
crypto.ensure_ca_filesystem()

def revoke_certs_by_user(self, context, user_id):
"""Revoke all user certs."""
return crypto.revoke_certs_by_user(user_id)

def revoke_certs_by_project(self, context, project_id):
"""Revoke all project certs."""
return crypto.revoke_certs_by_project(project_id)

def revoke_certs_by_user_and_project(self, context, user_id, project_id):
"""Revoke certs for user in project."""
return crypto.revoke_certs_by_user_and_project(project_id)

def generate_x509_cert(self, context, user_id, project_id):
"""Generate and sign a cert for user in project"""
return crypto.generate_x509_cert(user_id, project_id)

def fetch_ca(self, context, project_id):
"""Get root ca for a project"""
return crypto.fetch_ca(project_id)

def fetch_crl(self, context, project_id):
"""Get crl for a project"""
return crypto.fetch_ca(project_id)

def decrypt_text(self, context, project_id, text):
"""Decrypt base64 encoded text using the projects private key."""
return crypto.decrypt_text(project_id, base64.b64decode(text))
44 changes: 44 additions & 0 deletions nova/crypto.py
Expand Up @@ -39,6 +39,7 @@

from nova import context
from nova import db
from nova import exception
from nova import flags
from nova import log as logging

Expand Down Expand Up @@ -85,13 +86,33 @@ def key_path(project_id=None):
return os.path.join(ca_folder(project_id), FLAGS.key_file)


def crl_path(project_id=None):
return os.path.join(ca_folder(project_id), FLAGS.crl_file)


def fetch_ca(project_id=None):
if not FLAGS.use_project_ca:
project_id = None
with open(ca_path(project_id), 'r') as cafile:
return cafile.read()


def ensure_ca_filesystem():
"""Ensure the CA filesystem exists."""
ca_dir = ca_folder()
if not os.path.exists(ca_path()):
genrootca_sh_path = os.path.join(os.path.dirname(__file__),
'CA',
'genrootca.sh')

start = os.getcwd()
if not os.path.exists(ca_dir):
os.makedirs(ca_dir)
os.chdir(ca_dir)
utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path)
os.chdir(start)


def _generate_fingerprint(public_key_file):
(out, err) = utils.execute('ssh-keygen', '-q', '-l', '-f', public_key_file)
fingerprint = out.split(' ')[1]
Expand Down Expand Up @@ -148,6 +169,29 @@ def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
return '%s %s %s@%s\n' % (key_type, b64_blob, name, suffix)


def fetch_crl(project_id):
"""Get crl file for project."""
if not FLAGS.use_project_ca:
project_id = None
with open(crl_path(project_id), 'r') as crlfile:
return crlfile.read()


def decrypt_text(project_id, text):
private_key = key_path(project_id)
if not os.path.exists(private_key):
raise exception.ProjectNotFound(project_id=project_id)
try:
dec, _err = utils.execute('openssl',
'rsautl',
'-decrypt',
'-inkey', '%s' % private_key,
process_input=text)
return dec
except exception.ProcessExecutionError:
raise exception.DecryptionFailure()


def revoke_cert(project_id, file_name):
"""Revoke a cert by file name."""
start = os.getcwd()
Expand Down
4 changes: 4 additions & 0 deletions nova/exception.py
Expand Up @@ -179,6 +179,10 @@ def __init__(self, message=None, **kwargs):
super(NovaException, self).__init__(message)


class DecryptionFailure(NovaException):
message = _("Failed to decrypt text")


class ImagePaginationFailed(NovaException):
message = _("Failed to paginate through images from image service")

Expand Down
3 changes: 3 additions & 0 deletions nova/flags.py
Expand Up @@ -275,6 +275,7 @@ def _get_my_ip():
DEFINE_integer('s3_port', 3333, 's3 port')
DEFINE_string('s3_host', '$my_ip', 's3 host (for infrastructure)')
DEFINE_string('s3_dmz', '$my_ip', 's3 dmz ip (for instances)')
DEFINE_string('cert_topic', 'cert', 'the topic cert nodes listen on')
DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
DEFINE_string('console_topic', 'console',
'the topic console proxy nodes listen on')
Expand Down Expand Up @@ -367,6 +368,8 @@ def _get_my_ip():
'Manager for compute')
DEFINE_string('console_manager', 'nova.console.manager.ConsoleProxyManager',
'Manager for console proxy')
DEFINE_string('cert_manager', 'nova.cert.manager.CertManager',
'Manager for cert')
DEFINE_string('instance_dns_manager',
'nova.network.dns_driver.DNSDriver',
'DNS Manager for instance IPs')
Expand Down

0 comments on commit 0c5273c

Please sign in to comment.