summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xCA/geninter.sh26
-rwxr-xr-xCA/genrootca.sh1
-rw-r--r--CA/openssl.cnf.tmpl3
-rw-r--r--CA/projects/.gitignore (renamed from CA/INTER/.gitignore)0
-rw-r--r--CA/projects/.placeholder (renamed from CA/INTER/.placeholder)0
-rwxr-xr-xbin/nova-manage75
-rw-r--r--nova/api/__init__.py2
-rw-r--r--nova/api/cloudpipe/__init__.py69
-rw-r--r--nova/auth/manager.py73
-rwxr-xr-xnova/cloudpipe/bootscript.sh63
-rwxr-xr-xnova/cloudpipe/bootscript.template50
-rw-r--r--nova/cloudpipe/pipelib.py123
-rw-r--r--nova/crypto.py195
-rw-r--r--nova/db/api.py39
-rw-r--r--nova/db/sqlalchemy/api.py78
-rw-r--r--nova/db/sqlalchemy/models.py12
-rw-r--r--nova/tests/auth_unittest.py17
17 files changed, 532 insertions, 294 deletions
diff --git a/CA/geninter.sh b/CA/geninter.sh
index 7d6c280d5..1fbcc9e73 100755
--- a/CA/geninter.sh
+++ b/CA/geninter.sh
@@ -16,16 +16,24 @@
# License for the specific language governing permissions and limitations
# under the License.
-# ARG is the id of the user
-export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-intCA-$1"
-mkdir INTER/$1
-cd INTER/$1
+# $1 is the id of the project and $2 is the subject of the cert
+NAME=$1
+SUBJ=$2
+mkdir -p projects/$NAME
+cd projects/$NAME
cp ../../openssl.cnf.tmpl openssl.cnf
-sed -i -e s/%USERNAME%/$1/g openssl.cnf
+sed -i -e s/%USERNAME%/$NAME/g openssl.cnf
mkdir certs crl newcerts private
+openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
echo "10" > serial
touch index.txt
-openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
-openssl req -new -sha2 -key private/cakey.pem -out ../../reqs/inter$1.csr -batch -subj "$SUBJ"
-cd ../../
-openssl ca -extensions v3_ca -days 365 -out INTER/$1/cacert.pem -in reqs/inter$1.csr -config openssl.cnf -batch
+# NOTE(vish): Disabling intermediate ca's because we don't actually need them.
+# It makes more sense to have each project have its own root ca.
+# openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
+# openssl req -new -sha256 -key private/cakey.pem -out ../../reqs/inter$NAME.csr -batch -subj "$SUBJ"
+openssl ca -gencrl -config ./openssl.cnf -out crl.pem
+if [ "`id -u`" != "`grep nova /etc/passwd | cut -d':' -f3`" ]; then
+ sudo chown -R nova:nogroup .
+fi
+# cd ../../
+# openssl ca -extensions v3_ca -days 365 -out INTER/$NAME/cacert.pem -in reqs/inter$NAME.csr -config openssl.cnf -batch
diff --git a/CA/genrootca.sh b/CA/genrootca.sh
index 31976092e..8f2c3ee3f 100755
--- a/CA/genrootca.sh
+++ b/CA/genrootca.sh
@@ -25,4 +25,5 @@ else
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
touch index.txt
echo "10" > serial
+ openssl ca -gencrl -config ./openssl.cnf -out crl.pem
fi
diff --git a/CA/openssl.cnf.tmpl b/CA/openssl.cnf.tmpl
index 639b8e80a..dd81f1c2b 100644
--- a/CA/openssl.cnf.tmpl
+++ b/CA/openssl.cnf.tmpl
@@ -24,7 +24,6 @@ dir = .
[ ca ]
default_ca = CA_default
-unique_subject = no
[ CA_default ]
serial = $dir/serial
@@ -32,6 +31,8 @@ database = $dir/index.txt
new_certs_dir = $dir/newcerts
certificate = $dir/cacert.pem
private_key = $dir/private/cakey.pem
+unique_subject = no
+default_crl_days = 365
default_days = 365
default_md = md5
preserve = no
diff --git a/CA/INTER/.gitignore b/CA/projects/.gitignore
index 72e8ffc0d..72e8ffc0d 100644
--- a/CA/INTER/.gitignore
+++ b/CA/projects/.gitignore
diff --git a/CA/INTER/.placeholder b/CA/projects/.placeholder
index e69de29bb..e69de29bb 100644
--- a/CA/INTER/.placeholder
+++ b/CA/projects/.placeholder
diff --git a/bin/nova-manage b/bin/nova-manage
index 08b3da123..b788ee62d 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -69,6 +69,7 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
from nova import context
+from nova import crypto
from nova import db
from nova import exception
from nova import flags
@@ -93,32 +94,36 @@ class VpnCommands(object):
self.manager = manager.AuthManager()
self.pipe = pipelib.CloudPipe()
- def list(self):
- """Print a listing of the VPNs for all projects."""
+ def list(self, project=None):
+ """Print a listing of the VPN data for one or all projects.
+
+ args: [project=all]"""
print "%-12s\t" % 'project',
print "%-20s\t" % 'ip:port',
+ print "%-20s\t" % 'private_ip',
print "%s" % 'state'
- for project in self.manager.get_projects():
+ if project:
+ projects = [self.manager.get_project(project)]
+ else:
+ projects = self.manager.get_projects()
+ for project in projects:
print "%-12s\t" % project.name,
-
- try:
- s = "%s:%s" % (project.vpn_ip, project.vpn_port)
- except exception.NotFound:
- s = "None"
- print "%-20s\t" % s,
-
+ ipport = "%s:%s" % (project.vpn_ip, project.vpn_port)
+ print "%-20s\t" % ipport,
vpn = self._vpn_for(project.id)
if vpn:
- command = "ping -c1 -w1 %s > /dev/null; echo $?"
- out, _err = utils.execute(command % vpn['private_dns_name'],
- check_exit_code=False)
- if out.strip() == '0':
- net = 'up'
- else:
- net = 'down'
- print vpn['private_dns_name'],
- print vpn['node_name'],
- print vpn['instance_id'],
+ net = 'down'
+ address = None
+ if vpn.get('fixed_ip', None):
+ address = vpn['fixed_ip']['address']
+ command = "ping -c1 -w1 %s > /dev/null; echo $?"
+ out, _err = utils.execute(command % address,
+ check_exit_code=False)
+ if out.strip() == '0':
+ net = 'up'
+ print address,
+ print vpn['host'],
+ print vpn['ec2_id'],
print vpn['state_description'],
print net
@@ -127,11 +132,11 @@ class VpnCommands(object):
def _vpn_for(self, project_id):
"""Get the VPN instance for a project ID."""
- for instance in db.instance_get_all(context.get_admin_context()):
+ ctxt = context.get_admin_context()
+ for instance in db.instance_get_all_by_project(ctxt, project_id):
if (instance['image_id'] == FLAGS.vpn_image_id
and not instance['state_description'] in
- ['shutting_down', 'shutdown']
- and instance['project_id'] == project_id):
+ ['shutting_down', 'shutdown']):
return instance
def spawn(self):
@@ -146,6 +151,22 @@ class VpnCommands(object):
"""Start the VPN for a given project."""
self.pipe.launch_vpn_instance(project_id)
+ def change(self, project_id, ip, port):
+ """Change the ip and port for a vpn.
+
+ args: project, ip, port"""
+ project = self.manager.get_project(project_id)
+ if not project:
+ print 'No project %s' % (project_id)
+ return
+ admin = context.get_admin_context()
+ network_ref = db.project_get_network(admin, project_id)
+ db.network_update(admin,
+ network_ref['id'],
+ {'vpn_public_address': ip,
+ 'vpn_public_port': int(port)})
+
+
class ShellCommands(object):
def bpython(self):
@@ -292,6 +313,14 @@ class UserCommands(object):
is_admin = False
self.manager.modify_user(name, access_key, secret_key, is_admin)
+ def revoke(self, user_id, project_id=None):
+ """revoke certs for a user
+ arguments: user_id [project_id]"""
+ if project_id:
+ crypto.revoke_certs_by_user_and_project(user_id, project_id)
+ else:
+ crypto.revoke_certs_by_user(user_id)
+
class ProjectCommands(object):
"""Class for managing projects."""
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 707c1623e..176d571a8 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -25,7 +25,6 @@ import webob.dec
from nova import flags
from nova import wsgi
-from nova.api import cloudpipe
from nova.api import ec2
from nova.api import openstack
from nova.api.ec2 import metadatarequesthandler
@@ -74,7 +73,6 @@ class API(wsgi.Router):
mapper.connect('%s/{path_info:.*}' % s, controller=mrh,
conditions=ec2api_subdomain)
- mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API())
super(API, self).__init__(mapper)
@webob.dec.wsgify
diff --git a/nova/api/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py
deleted file mode 100644
index 6d40990a8..000000000
--- a/nova/api/cloudpipe/__init__.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# 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.
-
-"""
-REST API Request Handlers for CloudPipe
-"""
-
-import logging
-import urllib
-import webob
-import webob.dec
-import webob.exc
-
-from nova import crypto
-from nova import wsgi
-from nova.auth import manager
-from nova.api.ec2 import cloud
-
-
-_log = logging.getLogger("api")
-_log.setLevel(logging.DEBUG)
-
-
-class API(wsgi.Application):
-
- def __init__(self):
- self.controller = cloud.CloudController()
-
- @webob.dec.wsgify
- def __call__(self, req):
- if req.method == 'POST':
- return self.sign_csr(req)
- _log.debug("Cloudpipe path is %s" % req.path_info)
- if req.path_info.endswith("/getca/"):
- return self.send_root_ca(req)
- return webob.exc.HTTPNotFound()
-
- def get_project_id_from_ip(self, ip):
- # TODO(eday): This was removed with the ORM branch, fix!
- instance = self.controller.get_instance_by_ip(ip)
- return instance['project_id']
-
- def send_root_ca(self, req):
- _log.debug("Getting root ca")
- project_id = self.get_project_id_from_ip(req.remote_addr)
- res = webob.Response()
- res.headers["Content-Type"] = "text/plain"
- res.body = crypto.fetch_ca(project_id)
- return res
-
- def sign_csr(self, req):
- project_id = self.get_project_id_from_ip(req.remote_addr)
- cert = self.str_params['cert']
- return crypto.sign_csr(urllib.unquote(cert), project_id)
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 001a96875..c6d4b6e53 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -64,12 +64,8 @@ flags.DEFINE_string('credential_key_file', 'pk.pem',
'Filename of private key in credentials zip')
flags.DEFINE_string('credential_cert_file', 'cert.pem',
'Filename of certificate in credentials zip')
-flags.DEFINE_string('credential_rc_file', 'novarc',
+flags.DEFINE_string('credential_rc_file', '%src',
'Filename of rc in credentials zip')
-flags.DEFINE_string('credential_cert_subject',
- '/C=US/ST=California/L=MountainView/O=AnsoLabs/'
- 'OU=NovaDev/CN=%s-%s',
- 'Subject for certificate for users')
flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver',
'Driver that auth manager uses')
@@ -625,27 +621,37 @@ class AuthManager(object):
with self.driver() as drv:
drv.modify_user(uid, access_key, secret_key, admin)
- def get_credentials(self, user, project=None):
+ def get_credentials(self, user, project=None, use_dmz=True):
"""Get credential zip for user in project"""
if not isinstance(user, User):
user = self.get_user(user)
if project is None:
project = user.id
pid = Project.safe_id(project)
- rc = self.__generate_rc(user.access, user.secret, pid)
- private_key, signed_cert = self._generate_x509_cert(user.id, pid)
+ private_key, signed_cert = crypto.generate_x509_cert(user.id, pid)
tmpdir = tempfile.mkdtemp()
zf = os.path.join(tmpdir, "temp.zip")
zippy = zipfile.ZipFile(zf, 'w')
- zippy.writestr(FLAGS.credential_rc_file, rc)
+ if use_dmz and FLAGS.region_list:
+ regions = {}
+ for item in FLAGS.region_list:
+ region, _sep, region_host = item.partition("=")
+ regions[region] = region_host
+ else:
+ regions = {'nova': FLAGS.cc_host}
+ for region, host in regions.iteritems():
+ rc = self.__generate_rc(user.access,
+ user.secret,
+ pid,
+ use_dmz,
+ host)
+ zippy.writestr(FLAGS.credential_rc_file % region, rc)
+
zippy.writestr(FLAGS.credential_key_file, private_key)
zippy.writestr(FLAGS.credential_cert_file, signed_cert)
- try:
- (vpn_ip, vpn_port) = self.get_project_vpn_data(project)
- except exception.NotFound:
- vpn_ip = None
+ (vpn_ip, vpn_port) = self.get_project_vpn_data(project)
if vpn_ip:
configfile = open(FLAGS.vpn_client_template, "r")
s = string.Template(configfile.read())
@@ -656,10 +662,9 @@ class AuthManager(object):
port=vpn_port)
zippy.writestr(FLAGS.credential_vpn_file, config)
else:
- logging.warn("No vpn data for project %s" %
- pid)
+ LOG.warn("No vpn data for project %s", pid)
- zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id))
+ zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(pid))
zippy.close()
with open(zf, 'rb') as f:
read_buffer = f.read()
@@ -667,38 +672,38 @@ class AuthManager(object):
shutil.rmtree(tmpdir)
return read_buffer
- def get_environment_rc(self, user, project=None):
+ def get_environment_rc(self, user, project=None, use_dmz=True):
"""Get credential zip for user in project"""
if not isinstance(user, User):
user = self.get_user(user)
if project is None:
project = user.id
pid = Project.safe_id(project)
- return self.__generate_rc(user.access, user.secret, pid)
+ return self.__generate_rc(user.access, user.secret, pid, use_dmz)
@staticmethod
- def __generate_rc(access, secret, pid):
+ def __generate_rc(access, secret, pid, use_dmz=True, host=None):
"""Generate rc file for user"""
+ if use_dmz:
+ cc_host = FLAGS.cc_dmz
+ else:
+ cc_host = FLAGS.cc_host
+ # NOTE(vish): Always use the dmz since it is used from inside the
+ # instance
+ s3_host = FLAGS.s3_dmz
+ if host:
+ s3_host = host
+ cc_host = host
rc = open(FLAGS.credentials_template).read()
rc = rc % {'access': access,
'project': pid,
'secret': secret,
- 'ec2': FLAGS.ec2_url,
- 's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
+ 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix,
+ cc_host,
+ FLAGS.cc_port,
+ FLAGS.ec2_suffix),
+ 's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port),
'nova': FLAGS.ca_file,
'cert': FLAGS.credential_cert_file,
'key': FLAGS.credential_key_file}
return rc
-
- def _generate_x509_cert(self, uid, pid):
- """Generate x509 cert for user"""
- (private_key, csr) = crypto.generate_x509_cert(
- self.__cert_subject(uid))
- # TODO(joshua): This should be async call back to the cloud controller
- signed_cert = crypto.sign_csr(csr, pid)
- return (private_key, signed_cert)
-
- @staticmethod
- def __cert_subject(uid):
- """Helper to generate cert subject"""
- return FLAGS.credential_cert_subject % (uid, utils.isotime())
diff --git a/nova/cloudpipe/bootscript.sh b/nova/cloudpipe/bootscript.sh
deleted file mode 100755
index 30d9ad102..000000000
--- a/nova/cloudpipe/bootscript.sh
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/bin/bash
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# 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.
-
-# This gets zipped and run on the cloudpipe-managed OpenVPN server
-
-export SUPERVISOR="http://10.255.255.1:8773/cloudpipe"
-export VPN_IP=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{print $1}'`
-export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $1}'`
-export DHCP_MASK=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f4 | awk '{print $1}'`
-export GATEWAY=`netstat -r | grep default | cut -d' ' -f10`
-export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-vpn-$VPN_IP"
-
-DHCP_LOWER=`echo $BROADCAST | awk -F. '{print $1"."$2"."$3"." $4 - 10 }'`
-DHCP_UPPER=`echo $BROADCAST | awk -F. '{print $1"."$2"."$3"." $4 - 1 }'`
-
-# generate a server DH
-openssl dhparam -out /etc/openvpn/dh1024.pem 1024
-
-# generate a server priv key
-openssl genrsa -out /etc/openvpn/server.key 2048
-
-# generate a server CSR
-openssl req -new -key /etc/openvpn/server.key -out /etc/openvpn/server.csr -batch -subj "$SUBJ"
-
-# URLEncode the CSR
-CSRTEXT=`cat /etc/openvpn/server.csr`
-CSRTEXT=$(python -c "import urllib; print urllib.quote('''$CSRTEXT''')")
-
-# SIGN the csr and save as server.crt
-# CURL fetch to the supervisor, POSTing the CSR text, saving the result as the CRT file
-curl --fail $SUPERVISOR -d "cert=$CSRTEXT" > /etc/openvpn/server.crt
-curl --fail $SUPERVISOR/getca/ > /etc/openvpn/ca.crt
-
-# Customize the server.conf.template
-cd /etc/openvpn
-
-sed -e s/VPN_IP/$VPN_IP/g server.conf.template > server.conf
-sed -i -e s/DHCP_SUBNET/$DHCP_MASK/g server.conf
-sed -i -e s/DHCP_LOWER/$DHCP_LOWER/g server.conf
-sed -i -e s/DHCP_UPPER/$DHCP_UPPER/g server.conf
-sed -i -e s/max-clients\ 1/max-clients\ 10/g server.conf
-
-echo "\npush \"route 10.255.255.1 255.255.255.255 $GATEWAY\"\n" >> server.conf
-echo "\npush \"route 10.255.255.253 255.255.255.255 $GATEWAY\"\n" >> server.conf
-echo "\nduplicate-cn\n" >> server.conf
-
-/etc/init.d/openvpn start
diff --git a/nova/cloudpipe/bootscript.template b/nova/cloudpipe/bootscript.template
new file mode 100755
index 000000000..11578c134
--- /dev/null
+++ b/nova/cloudpipe/bootscript.template
@@ -0,0 +1,50 @@
+#!/bin/bash
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# 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.
+
+# This gets zipped and run on the cloudpipe-managed OpenVPN server
+
+export VPN_IP=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{print $$1}'`
+export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $$1}'`
+export DHCP_MASK=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f4 | awk '{print $$1}'`
+export GATEWAY=`netstat -r | grep default | cut -d' ' -f10`
+
+DHCP_LOWER=`echo $$BROADCAST | awk -F. '{print $$1"."$$2"."$$3"." $$4 - ${num_vpn} }'`
+DHCP_UPPER=`echo $$BROADCAST | awk -F. '{print $$1"."$$2"."$$3"." $$4 - 1 }'`
+
+# generate a server DH
+openssl dhparam -out /etc/openvpn/dh1024.pem 1024
+
+cp crl.pem /etc/openvpn/
+cp server.key /etc/openvpn/
+cp ca.crt /etc/openvpn/
+cp server.crt /etc/openvpn/
+# Customize the server.conf.template
+cd /etc/openvpn
+
+sed -e s/VPN_IP/$$VPN_IP/g server.conf.template > server.conf
+sed -i -e s/DHCP_SUBNET/$$DHCP_MASK/g server.conf
+sed -i -e s/DHCP_LOWER/$$DHCP_LOWER/g server.conf
+sed -i -e s/DHCP_UPPER/$$DHCP_UPPER/g server.conf
+sed -i -e s/max-clients\ 1/max-clients\ 10/g server.conf
+
+echo "push \"route ${dmz_net} ${dmz_mask} $$GATEWAY\"" >> server.conf
+echo "duplicate-cn" >> server.conf
+echo "crl-verify /etc/openvpn/crl.pem" >> server.conf
+
+/etc/init.d/openvpn start
diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py
index 3472201cd..147b9dd7b 100644
--- a/nova/cloudpipe/pipelib.py
+++ b/nova/cloudpipe/pipelib.py
@@ -22,13 +22,15 @@ an instance with it.
"""
-import base64
import logging
import os
+import string
import tempfile
import zipfile
from nova import context
+from nova import crypto
+from nova import db
from nova import exception
from nova import flags
from nova import utils
@@ -39,8 +41,17 @@ from nova.api.ec2 import cloud
FLAGS = flags.FLAGS
flags.DEFINE_string('boot_script_template',
- utils.abspath('cloudpipe/bootscript.sh'),
+ utils.abspath('cloudpipe/bootscript.template'),
'Template for script to run on cloudpipe instance boot')
+flags.DEFINE_string('dmz_net',
+ '10.0.0.0',
+ 'Network to push into openvpn config')
+flags.DEFINE_string('dmz_mask',
+ '255.255.255.0',
+ 'Netmask to push into openvpn config')
+
+
+LOG = logging.getLogger('nova-cloudpipe')
class CloudPipe(object):
@@ -48,64 +59,96 @@ class CloudPipe(object):
self.controller = cloud.CloudController()
self.manager = manager.AuthManager()
- def launch_vpn_instance(self, project_id):
- logging.debug("Launching VPN for %s" % (project_id))
- project = self.manager.get_project(project_id)
+ def get_encoded_zip(self, project_id):
# Make a payload.zip
tmpfolder = tempfile.mkdtemp()
filename = "payload.zip"
zippath = os.path.join(tmpfolder, filename)
z = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED)
-
- z.write(FLAGS.boot_script_template, 'autorun.sh')
+ shellfile = open(FLAGS.boot_script_template, "r")
+ s = string.Template(shellfile.read())
+ shellfile.close()
+ boot_script = s.substitute(cc_dmz=FLAGS.cc_dmz,
+ cc_port=FLAGS.cc_port,
+ dmz_net=FLAGS.dmz_net,
+ dmz_mask=FLAGS.dmz_mask,
+ num_vpn=FLAGS.cnt_vpn_clients)
+ # genvpn, sign csr
+ crypto.generate_vpn_files(project_id)
+ z.writestr('autorun.sh', boot_script)
+ crl = os.path.join(crypto.ca_folder(project_id), 'crl.pem')
+ z.write(crl, 'crl.pem')
+ server_key = os.path.join(crypto.ca_folder(project_id), 'server.key')
+ z.write(server_key, 'server.key')
+ ca_crt = os.path.join(crypto.ca_path(project_id))
+ z.write(ca_crt, 'ca.crt')
+ server_crt = os.path.join(crypto.ca_folder(project_id), 'server.crt')
+ z.write(server_crt, 'server.crt')
z.close()
-
- key_name = self.setup_key_pair(project.project_manager_id, project_id)
zippy = open(zippath, "r")
- context = context.RequestContext(user=project.project_manager,
- project=project)
-
- reservation = self.controller.run_instances(context,
- # Run instances expects encoded userdata, it is decoded in the
- # get_metadata_call. autorun.sh also decodes the zip file, hence
- # the double encoding.
- user_data=zippy.read().encode("base64").encode("base64"),
+ # NOTE(vish): run instances expects encoded userdata, it is decoded
+ # in the get_metadata_call. autorun.sh also decodes the zip file,
+ # hence the double encoding.
+ encoded = zippy.read().encode("base64").encode("base64")
+ zippy.close()
+ return encoded
+
+ def launch_vpn_instance(self, project_id):
+ LOG.debug("Launching VPN for %s" % (project_id))
+ project = self.manager.get_project(project_id)
+ ctxt = context.RequestContext(user=project.project_manager,
+ project=project)
+ key_name = self.setup_key_pair(ctxt)
+ group_name = self.setup_security_group(ctxt)
+
+ reservation = self.controller.run_instances(ctxt,
+ user_data=self.get_encoded_zip(project_id),
max_count=1,
min_count=1,
instance_type='m1.tiny',
image_id=FLAGS.vpn_image_id,
key_name=key_name,
- security_groups=["vpn-secgroup"])
- zippy.close()
+ security_group=[group_name])
+
+ def setup_security_group(self, context):
+ group_name = '%s%s' % (context.project.id, FLAGS.vpn_key_suffix)
+ if db.security_group_exists(context, context.project.id, group_name):
+ return group_name
+ group = {'user_id': context.user.id,
+ 'project_id': context.project.id,
+ 'name': group_name,
+ 'description': 'Group for vpn'}
+ group_ref = db.security_group_create(context, group)
+ rule = {'parent_group_id': group_ref['id'],
+ 'cidr': '0.0.0.0/0',
+ 'protocol': 'udp',
+ 'from_port': 1194,
+ 'to_port': 1194}
+ db.security_group_rule_create(context, rule)
+ rule = {'parent_group_id': group_ref['id'],
+ 'cidr': '0.0.0.0/0',
+ 'protocol': 'icmp',
+ 'from_port': -1,
+ 'to_port': -1}
+ db.security_group_rule_create(context, rule)
+ # NOTE(vish): No need to trigger the group since the instance
+ # has not been run yet.
+ return group_name
- def setup_key_pair(self, user_id, project_id):
- key_name = '%s%s' % (project_id, FLAGS.vpn_key_suffix)
+ def setup_key_pair(self, context):
+ key_name = '%s%s' % (context.project.id, FLAGS.vpn_key_suffix)
try:
- private_key, fingerprint = self.manager.generate_key_pair(user_id,
- key_name)
+ result = cloud._gen_key(context, context.user.id, key_name)
+ private_key = result['private_key']
try:
- key_dir = os.path.join(FLAGS.keys_path, user_id)
+ key_dir = os.path.join(FLAGS.keys_path, context.user.id)
if not os.path.exists(key_dir):
os.makedirs(key_dir)
- file_name = os.path.join(key_dir, '%s.pem' % key_name)
- with open(file_name, 'w') as f:
+ key_path = os.path.join(key_dir, '%s.pem' % key_name)
+ with open(key_path, 'w') as f:
f.write(private_key)
except:
pass
except exception.Duplicate:
pass
return key_name
-
- # def setup_secgroups(self, username):
- # conn = self.euca.connection_for(username)
- # try:
- # secgroup = conn.create_security_group("vpn-secgroup",
- # "vpn-secgroup")
- # secgroup.authorize(ip_protocol = "udp", from_port = "1194",
- # to_port = "1194", cidr_ip = "0.0.0.0/0")
- # secgroup.authorize(ip_protocol = "tcp", from_port = "80",
- # to_port = "80", cidr_ip = "0.0.0.0/0")
- # secgroup.authorize(ip_protocol = "tcp", from_port = "22",
- # to_port = "22", cidr_ip = "0.0.0.0/0")
- # except:
- # pass
diff --git a/nova/crypto.py b/nova/crypto.py
index 16b4f5e1f..9e29f0b8d 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -17,7 +17,7 @@
# under the License.
"""
-Wrappers around standard crypto, including root and intermediate CAs,
+Wrappers around standard crypto, including root and project CAs,
SSH key_pairs and x509 certificates.
"""
@@ -33,28 +33,57 @@ import utils
import M2Crypto
-from nova import exception
+from nova import context
+from nova import db
from nova import flags
FLAGS = flags.FLAGS
flags.DEFINE_string('ca_file', 'cacert.pem', 'Filename of root CA')
+flags.DEFINE_string('key_file',
+ os.path.join('private', 'cakey.pem'),
+ 'Filename of private key')
+flags.DEFINE_string('crl_file', 'crl.pem',
+ 'Filename of root Certificate Revokation List')
flags.DEFINE_string('keys_path', utils.abspath('../keys'),
'Where we keep our keys')
flags.DEFINE_string('ca_path', utils.abspath('../CA'),
'Where we keep our root CA')
-flags.DEFINE_boolean('use_intermediate_ca', False,
- 'Should we use intermediate CAs for each project?')
+flags.DEFINE_boolean('use_project_ca', False,
+ 'Should we use a CA for each project?')
+flags.DEFINE_string('user_cert_subject',
+ '/C=US/ST=California/L=MountainView/O=AnsoLabs/'
+ 'OU=NovaDev/CN=%s-%s-%s',
+ 'Subject for certificate for users, '
+ '%s for project, user, timestamp')
+flags.DEFINE_string('project_cert_subject',
+ '/C=US/ST=California/L=MountainView/O=AnsoLabs/'
+ 'OU=NovaDev/CN=project-ca-%s-%s',
+ 'Subject for certificate for projects, '
+ '%s for project, timestamp')
+flags.DEFINE_string('vpn_cert_subject',
+ '/C=US/ST=California/L=MountainView/O=AnsoLabs/'
+ 'OU=NovaDev/CN=project-vpn-%s-%s',
+ 'Subject for certificate for vpns, '
+ '%s for project, timestamp')
-def ca_path(project_id):
- if project_id:
- return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, project_id)
- return "%s/cacert.pem" % (FLAGS.ca_path)
+def ca_folder(project_id=None):
+ if FLAGS.use_project_ca and project_id:
+ return os.path.join(FLAGS.ca_path, 'projects', project_id)
+ return FLAGS.ca_path
+
+
+def ca_path(project_id=None):
+ return os.path.join(ca_folder(project_id), FLAGS.ca_file)
+
+
+def key_path(project_id=None):
+ return os.path.join(ca_folder(project_id), FLAGS.key_file)
def fetch_ca(project_id=None, chain=True):
- if not FLAGS.use_intermediate_ca:
+ if not FLAGS.use_project_ca:
project_id = None
buffer = ""
if project_id:
@@ -91,8 +120,8 @@ def generate_key_pair(bits=1024):
def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
- pub_key_buffer = M2Crypto.BIO.MemoryBuffer(ssl_public_key)
- rsa_key = M2Crypto.RSA.load_pub_key_bio(pub_key_buffer)
+ buf = M2Crypto.BIO.MemoryBuffer(ssl_public_key)
+ rsa_key = M2Crypto.RSA.load_pub_key_bio(buf)
e, n = rsa_key.pub()
key_type = 'ssh-rsa'
@@ -105,53 +134,137 @@ 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 generate_x509_cert(subject, bits=1024):
+def revoke_cert(project_id, file_name):
+ """Revoke a cert by file name"""
+ start = os.getcwd()
+ os.chdir(ca_folder(project_id))
+ # NOTE(vish): potential race condition here
+ utils.execute("openssl ca -config ./openssl.cnf -revoke '%s'" % file_name)
+ utils.execute("openssl ca -gencrl -config ./openssl.cnf -out '%s'" %
+ FLAGS.crl_file)
+ os.chdir(start)
+
+
+def revoke_certs_by_user(user_id):
+ """Revoke all user certs"""
+ admin = context.get_admin_context()
+ for cert in db.certificate_get_all_by_user(admin, user_id):
+ revoke_cert(cert['project_id'], cert['file_name'])
+
+
+def revoke_certs_by_project(project_id):
+ """Revoke all project certs"""
+ # NOTE(vish): This is somewhat useless because we can just shut down
+ # the vpn.
+ admin = context.get_admin_context()
+ for cert in db.certificate_get_all_by_project(admin, project_id):
+ revoke_cert(cert['project_id'], cert['file_name'])
+
+
+def revoke_certs_by_user_and_project(user_id, project_id):
+ """Revoke certs for user in project"""
+ admin = context.get_admin_context()
+ for cert in db.certificate_get_all_by_user(admin, user_id, project_id):
+ revoke_cert(cert['project_id'], cert['file_name'])
+
+
+def _project_cert_subject(project_id):
+ """Helper to generate user cert subject"""
+ return FLAGS.project_cert_subject % (project_id, utils.isotime())
+
+
+def _vpn_cert_subject(project_id):
+ """Helper to generate user cert subject"""
+ return FLAGS.vpn_cert_subject % (project_id, utils.isotime())
+
+
+def _user_cert_subject(user_id, project_id):
+ """Helper to generate user cert subject"""
+ return FLAGS.user_cert_subject % (project_id, user_id, utils.isotime())
+
+
+def generate_x509_cert(user_id, project_id, bits=1024):
+ """Generate and sign a cert for user in project"""
+ subject = _user_cert_subject(user_id, project_id)
tmpdir = tempfile.mkdtemp()
keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key'))
csrfile = os.path.join(tmpdir, 'temp.csr')
- logging.debug("openssl genrsa -out %s %s" % (keyfile, bits))
- utils.runthis("Generating private key: %s",
- "openssl genrsa -out %s %s" % (keyfile, bits))
- utils.runthis("Generating CSR: %s",
- "openssl req -new -key %s -out %s -batch -subj %s" %
+ utils.execute("openssl genrsa -out %s %s" % (keyfile, bits))
+ utils.execute("openssl req -new -key %s -out %s -batch -subj %s" %
(keyfile, csrfile, subject))
private_key = open(keyfile).read()
csr = open(csrfile).read()
shutil.rmtree(tmpdir)
- return (private_key, csr)
-
-
-def sign_csr(csr_text, intermediate=None):
- if not FLAGS.use_intermediate_ca:
- intermediate = None
- if not intermediate:
- return _sign_csr(csr_text, FLAGS.ca_path)
- user_ca = "%s/INTER/%s" % (FLAGS.ca_path, intermediate)
- if not os.path.exists(user_ca):
+ (serial, signed_csr) = sign_csr(csr, project_id)
+ strserial = "%X" % serial
+ if(len(strserial) % 2):
+ strserial = "0%s" % strserial
+ fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % strserial)
+ cert = {'user_id': user_id,
+ 'project_id': project_id,
+ 'file_name': fname}
+ db.certificate_create(context.get_admin_context(), cert)
+ return (private_key, signed_csr)
+
+
+def _ensure_project_folder(project_id):
+ if not os.path.exists(ca_path(project_id)):
start = os.getcwd()
- os.chdir(FLAGS.ca_path)
- utils.runthis("Generating intermediate CA: %s",
- "sh geninter.sh %s" % (intermediate))
+ os.chdir(ca_folder())
+ utils.execute("sh geninter.sh %s %s" %
+ (project_id, _project_cert_subject(project_id)))
os.chdir(start)
- return _sign_csr(csr_text, user_ca)
+
+
+def generate_vpn_files(project_id):
+ project_folder = ca_folder(project_id)
+ csr_fn = os.path.join(project_folder, "server.csr")
+ crt_fn = os.path.join(project_folder, "server.crt")
+
+ if os.path.exists(crt_fn):
+ return
+ _ensure_project_folder(project_id)
+ start = os.getcwd()
+ os.chdir(ca_folder())
+ # TODO(vish): the shell scripts could all be done in python
+ utils.execute("sh genvpn.sh %s %s" %
+ (project_id, _vpn_cert_subject(project_id)))
+ with open(csr_fn, "r") as csrfile:
+ csr_text = csrfile.read()
+ (serial, signed_csr) = sign_csr(csr_text, project_id)
+ with open(crt_fn, "w") as crtfile:
+ crtfile.write(signed_csr)
+ os.chdir(start)
+
+
+def sign_csr(csr_text, project_id=None):
+ if not FLAGS.use_project_ca:
+ project_id = None
+ if not project_id:
+ return _sign_csr(csr_text, ca_folder())
+ _ensure_project_folder(project_id)
+ project_folder = ca_folder(project_id)
+ return _sign_csr(csr_text, ca_folder(project_id))
def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp()
- csrfile = open("%s/inbound.csr" % (tmpfolder), "w")
+ inbound = os.path.join(tmpfolder, "inbound.csr")
+ outbound = os.path.join(tmpfolder, "outbound.csr")
+ csrfile = open(inbound, "w")
csrfile.write(csr_text)
csrfile.close()
- logging.debug("Flags path: %s" % ca_folder)
+ logging.debug("Flags path: %s", ca_folder)
start = os.getcwd()
# Change working dir to CA
os.chdir(ca_folder)
- utils.runthis("Signing cert: %s",
- "openssl ca -batch -out %s/outbound.crt "
- "-config ./openssl.cnf -infiles %s/inbound.csr" %
- (tmpfolder, tmpfolder))
+ utils.execute("openssl ca -batch -out %s -config "
+ "./openssl.cnf -infiles %s" % (outbound, inbound))
+ out, _err = utils.execute("openssl x509 -in %s -serial -noout" % outbound)
+ serial = int(out.rpartition("=")[2])
os.chdir(start)
- with open("%s/outbound.crt" % (tmpfolder), "r") as crtfile:
- return crtfile.read()
+ with open(outbound, "r") as crtfile:
+ return (serial, crtfile.read())
def mkreq(bits, subject="foo", ca=0):
@@ -159,8 +272,7 @@ def mkreq(bits, subject="foo", ca=0):
req = M2Crypto.X509.Request()
rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
pk.assign_rsa(rsa)
- # Should not be freed here
- rsa = None
+ rsa = None # should not be freed here
req.set_pubkey(pk)
req.set_subject(subject)
req.sign(pk, 'sha512')
@@ -224,7 +336,6 @@ def mkcacert(subject='nova', years=1):
# IN THE SOFTWARE.
# http://code.google.com/p/boto
-
def compute_md5(fp):
"""
@type fp: file
diff --git a/nova/db/api.py b/nova/db/api.py
index 80563c452..aa77dd2ea 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -117,6 +117,45 @@ def service_update(context, service_id, values):
###################
+def certificate_create(context, values):
+ """Create a certificate from the values dictionary."""
+ return IMPL.certificate_create(context, values)
+
+
+def certificate_destroy(context, certificate_id):
+ """Destroy the certificate or raise if it does not exist."""
+ return IMPL.certificate_destroy(context, certificate_id)
+
+
+def certificate_get_all_by_project(context, project_id):
+ """Get all certificates for a project."""
+ return IMPL.certificate_get_all_by_project(context, project_id)
+
+
+def certificate_get_all_by_user(context, user_id):
+ """Get all certificates for a user."""
+ return IMPL.certificate_get_all_by_user(context, user_id)
+
+
+def certificate_get_all_by_user_and_project(context, user_id, project_id):
+ """Get all certificates for a user and project."""
+ return IMPL.certificate_get_all_by_user_and_project(context,
+ user_id,
+ project_id)
+
+
+def certificate_update(context, certificate_id, values):
+ """Set the given properties on an certificate and update it.
+
+ Raises NotFound if service does not exist.
+
+ """
+ return IMPL.service_update(context, certificate_id, values)
+
+
+###################
+
+
def floating_ip_allocate_address(context, host, project_id):
"""Allocate free floating ip and return the address.
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index db4d9f68f..9ae12f178 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -253,6 +253,84 @@ def service_update(context, service_id, values):
###################
+@require_admin_context
+def certificate_get(context, certificate_id, session=None):
+ if not session:
+ session = get_session()
+
+ result = session.query(models.Certificate).\
+ filter_by(id=certificate_id).\
+ filter_by(deleted=can_read_deleted(context)).\
+ first()
+
+ if not result:
+ raise exception.NotFound('No certificate for id %s' % certificate_id)
+
+ return result
+
+
+@require_admin_context
+def certificate_create(context, values):
+ certificate_ref = models.Certificate()
+ for (key, value) in values.iteritems():
+ certificate_ref[key] = value
+ certificate_ref.save()
+ return certificate_ref
+
+
+@require_admin_context
+def certificate_destroy(context, certificate_id):
+ session = get_session()
+ with session.begin():
+ certificate_ref = certificate_get(context,
+ certificate_id,
+ session=session)
+ certificate_ref.delete(session=session)
+
+
+@require_admin_context
+def certificate_get_all_by_project(context, project_id):
+ session = get_session()
+ return session.query(models.Certificate).\
+ filter_by(project_id=project_id).\
+ filter_by(deleted=False).\
+ all()
+
+
+@require_admin_context
+def certificate_get_all_by_user(context, user_id):
+ session = get_session()
+ return session.query(models.Certificate).\
+ filter_by(user_id=user_id).\
+ filter_by(deleted=False).\
+ all()
+
+
+@require_admin_context
+def certificate_get_all_by_user_and_project(_context, user_id, project_id):
+ session = get_session()
+ return session.query(models.Certificate).\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ filter_by(deleted=False).\
+ all()
+
+
+@require_admin_context
+def certificate_update(context, certificate_id, values):
+ session = get_session()
+ with session.begin():
+ certificate_ref = certificate_get(context,
+ certificate_id,
+ session=session)
+ for (key, value) in values.iteritems():
+ certificate_ref[key] = value
+ certificate_ref.save(session=session)
+
+
+###################
+
+
@require_context
def floating_ip_allocate_address(context, host, project_id):
authorize_project_context(context, project_id)
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 1111b5cbd..434f88e5e 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -151,6 +151,16 @@ class Service(BASE, NovaBase):
disabled = Column(Boolean, default=False)
+class Certificate(BASE, NovaBase):
+ """Represents a an x509 certificate"""
+ __tablename__ = 'certificates'
+ id = Column(Integer, primary_key=True)
+
+ user_id = Column(String(255))
+ project_id = Column(String(255))
+ file_name = Column(String(255))
+
+
class Instance(BASE, NovaBase):
"""Represents a guest vm"""
__tablename__ = 'instances'
@@ -521,7 +531,7 @@ def register_models():
"""Register Models and create metadata"""
from sqlalchemy import create_engine
models = (Service, Instance, Volume, ExportDevice, IscsiTarget, FixedIp,
- FloatingIp, Network, SecurityGroup,
+ FloatingIp, Network, SecurityGroup, Certificate,
SecurityGroupIngressRule, SecurityGroupInstanceAssociation,
AuthToken, User, Project) # , Image, Host
engine = create_engine(FLAGS.sql_connection, echo=False)
diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py
index fe891beee..0d2082bdb 100644
--- a/nova/tests/auth_unittest.py
+++ b/nova/tests/auth_unittest.py
@@ -208,17 +208,13 @@ class AuthManagerTestCase(object):
# so it probably belongs in crypto_unittest
# but I'm leaving it where I found it.
with user_and_project_generator(self.manager) as (user, project):
- # NOTE(todd): Should mention why we must setup controller first
- # (somebody please clue me in)
- cloud_controller = cloud.CloudController()
- cloud_controller.setup()
- _key, cert_str = self.manager._generate_x509_cert('test1',
- 'testproj')
+ # NOTE(vish): Setup runs genroot.sh if it hasn't been run
+ cloud.CloudController().setup()
+ _key, cert_str = crypto.generate_x509_cert(user.id, project.id)
logging.debug(cert_str)
- # Need to verify that it's signed by the right intermediate CA
- full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
- int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
+ full_chain = crypto.fetch_ca(project_id=project.id, chain=True)
+ int_cert = crypto.fetch_ca(project_id=project.id, chain=False)
cloud_cert = crypto.fetch_ca()
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
signed_cert = X509.load_cert_string(cert_str)
@@ -227,7 +223,8 @@ class AuthManagerTestCase(object):
cloud_cert = X509.load_cert_string(cloud_cert)
self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
self.assertTrue(signed_cert.verify(int_cert.get_pubkey()))
- if not FLAGS.use_intermediate_ca:
+
+ if not FLAGS.use_project_ca:
self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
else:
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))