summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2010-12-17 00:45:35 +0000
committerVishvananda Ishaya <vishvananda@gmail.com>2010-12-17 00:45:35 +0000
commitc3ffac68e41c5a53f8e4267db1b2474b9e10812e (patch)
tree304ed37261fda6bbaa9d0efaa63468d4c1cfc314
parent9e28957c45c69bf11a414faeb16a068f10a6a73d (diff)
parent86f71493fa5a02762bc7c56308c85b9182913efb (diff)
merged in project-vpns to get flag changes
-rwxr-xr-xCA/geninter.sh26
-rwxr-xr-xCA/genrootca.sh1
-rwxr-xr-xCA/genvpn.sh36
-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-manage84
-rw-r--r--nova/api/__init__.py4
-rw-r--r--nova/api/cloudpipe/__init__.py69
-rw-r--r--nova/api/ec2/cloud.py16
-rw-r--r--nova/auth/manager.py76
-rwxr-xr-xnova/cloudpipe/bootscript.sh63
-rwxr-xr-xnova/cloudpipe/bootscript.template50
-rw-r--r--nova/cloudpipe/pipelib.py123
-rw-r--r--nova/crypto.py187
-rw-r--r--nova/db/api.py44
-rw-r--r--nova/db/sqlalchemy/api.py90
-rw-r--r--nova/db/sqlalchemy/models.py12
-rw-r--r--nova/flags.py17
-rw-r--r--nova/network/linux_net.py2
-rw-r--r--nova/tests/auth_unittest.py17
-rw-r--r--nova/utils.py51
22 files changed, 649 insertions, 322 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/genvpn.sh b/CA/genvpn.sh
new file mode 100755
index 000000000..7e7db185d
--- /dev/null
+++ b/CA/genvpn.sh
@@ -0,0 +1,36 @@
+#!/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
+NAME=$1
+SUBJ=$2
+
+mkdir -p projects/$NAME
+cd projects/$NAME
+
+# generate a server priv key
+openssl genrsa -out server.key 2048
+
+# generate a server CSR
+openssl req -new -key server.key -out server.csr -batch -subj "$SUBJ"
+
+novauid=`getent passwd nova | awk -F: '{print $3}'`
+if [ ! -z "${novauid}" ] && [ "`id -u`" != "${novauid}" ]; then
+ sudo chown -R nova:nogroup .
+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 0c1b621ed..599e02a7e 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -72,6 +72,7 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
gettext.install('nova', unicode=1)
from nova import context
+from nova import crypto
from nova import db
from nova import exception
from nova import flags
@@ -96,47 +97,43 @@ 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()
+ # NOTE(vish): This hits the database a lot. We could optimize
+ # by getting all networks in one query and all vpns
+ # in aother query, then doing lookups by project
+ 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,
-
- vpn = self._vpn_for(project.id)
+ ipport = "%s:%s" % (project.vpn_ip, project.vpn_port)
+ print "%-20s\t" % ipport,
+ ctxt = context.get_admin_context()
+ vpn = db.instance_get_project_vpn(ctxt, 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'],
+ address = None
+ state = 'down'
+ if vpn.get('fixed_ip', None):
+ address = vpn['fixed_ip']['address']
+ if project.vpn_ip and utils.vpn_ping(project.vpn_ip,
+ project.vpn_port):
+ state = 'up'
+ print address,
+ print vpn['host'],
+ print vpn['ec2_id'],
print vpn['state_description'],
- print net
-
+ print state
else:
print None
- 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()):
- if (instance['image_id'] == FLAGS.vpn_image_id
- and not instance['state_description'] in
- ['shutting_down', 'shutdown']
- and instance['project_id'] == project_id):
- return instance
-
def spawn(self):
"""Run all VPNs."""
for p in reversed(self.manager.get_projects()):
@@ -149,6 +146,21 @@ 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):
@@ -295,6 +307,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 80f9f2109..803470570 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -29,9 +29,7 @@ import routes
import webob.dec
from nova import flags
-from nova import utils
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
@@ -41,6 +39,7 @@ flags.DEFINE_string('osapi_subdomain', 'api',
'subdomain running the OpenStack API')
flags.DEFINE_string('ec2api_subdomain', 'ec2',
'subdomain running the EC2 API')
+
FLAGS = flags.FLAGS
@@ -80,7 +79,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/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index ebb13aedc..684e29ee1 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -196,15 +196,19 @@ class CloudController(object):
if FLAGS.region_list:
regions = []
for region in FLAGS.region_list:
- name, _sep, url = region.partition('=')
+ name, _sep, host = region.partition('=')
+ endpoint = '%s://%s:%s%s' % (FLAGS.ec2_prefix,
+ host,
+ FLAGS.cc_port,
+ FLAGS.ec2_suffix)
regions.append({'regionName': name,
- 'regionEndpoint': url})
+ 'regionEndpoint': endpoint})
else:
regions = [{'regionName': 'nova',
- 'regionEndpoint': FLAGS.ec2_url}]
- if region_name:
- regions = [r for r in regions if r['regionName'] in region_name]
- return {'regionInfo': regions}
+ 'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_prefix,
+ FLAGS.cc_host,
+ FLAGS.cc_port,
+ FLAGS.ec2_suffix)}]
def describe_snapshots(self,
context,
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 11c3bd6df..74da8e045 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -64,12 +64,9 @@ 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',
- '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('credential_rc_file', '%src',
+ 'Filename of rc in credentials zip, %s will be '
+ 'replaced by name of the region (nova by default)')
flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver',
'Driver that auth manager uses')
@@ -628,27 +625,37 @@ class AuthManager(object):
def get_key_pairs(context):
return db.key_pair_get_all_by_user(context.elevated(), context.user_id)
- 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())
@@ -659,10 +666,9 @@ class AuthManager(object):
port=vpn_port)
zippy.writestr(FLAGS.credential_vpn_file, config)
else:
- logging.warn("No vpn data for project %s" %
- pid)
+ logging.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()
@@ -670,38 +676,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 aacc50b17..dcc735c05 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -19,7 +19,6 @@
Wrappers around standard crypto data elements.
Includes root and intermediate CAs, SSH key_pairs and x509 certificates.
-
"""
import base64
@@ -34,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', '$state_path/keys',
'Where we keep our keys')
flags.DEFINE_string('ca_path', '$state_path/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:
@@ -92,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'
@@ -106,53 +134,134 @@ 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)
+ (serial, signed_csr) = sign_csr(csr, project_id)
+ fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % serial)
+ 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 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):
+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 = 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):
@@ -160,8 +269,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')
@@ -225,7 +333,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 8f9dc2443..339e0a3ae 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -130,6 +130,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.
@@ -304,6 +343,11 @@ def instance_get_floating_address(context, instance_id):
return IMPL.instance_get_floating_address(context, instance_id)
+def instance_get_project_vpn(context, project_id):
+ """Get a vpn instance by project or return None."""
+ return IMPL.instance_get_project_vpn(context, project_id)
+
+
def instance_get_by_internal_id(context, internal_id):
"""Get an instance by internal id."""
return IMPL.instance_get_by_internal_id(context, internal_id)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 55036d1d1..6ecd824e1 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -252,6 +252,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)
@@ -651,6 +729,18 @@ def instance_get_all_by_reservation(context, reservation_id):
all()
+@require_admin_context
+def instance_get_project_vpn(context, project_id):
+ session = get_session()
+ return session.query(models.Instance).\
+ options(joinedload_all('fixed_ip.floating_ips')).\
+ options(joinedload('security_groups')).\
+ filter_by(project_id=project_id).\
+ filter_by(image_id=FLAGS.vpn_image_id).\
+ filter_by(deleted=can_read_deleted(context)).\
+ first()
+
+
@require_context
def instance_get_by_internal_id(context, internal_id):
session = get_session()
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index fe0a9a921..d754db90d 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'
@@ -527,7 +537,7 @@ def register_models():
"""
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/flags.py b/nova/flags.py
index 87444565a..95bfd3773 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -29,6 +29,7 @@ import sys
import gflags
+from nova import utils
class FlagValues(gflags.FlagValues):
"""Extension of gflags.FlagValues that allows undefined and runtime flags.
@@ -211,7 +212,8 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake')
DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID')
DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key')
DEFINE_integer('s3_port', 3333, 's3 port')
-DEFINE_string('s3_host', '127.0.0.1', 's3 host')
+DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)')
+DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)')
DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
DEFINE_string('scheduler_topic', 'scheduler',
'the topic scheduler nodes listen on')
@@ -230,8 +232,11 @@ DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host')
DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval')
DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts')
DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
-DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud',
- 'Url to ec2 api server')
+DEFINE_string('ec2_prefix', 'http', 'prefix for ec2')
+DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server')
+DEFINE_string('cc_dmz', utils.get_my_ip(), 'internal ip of api server')
+DEFINE_integer('cc_port', 8773, 'cloud controller port')
+DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2')
DEFINE_string('default_image', 'ami-11111',
'default image to use, testing only')
@@ -242,10 +247,10 @@ DEFINE_string('default_ramdisk', 'ari-11111',
DEFINE_string('default_instance_type', 'm1.small',
'default instance type to use, testing only')
-DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server')
+DEFINE_string('vpn_image_id', 'ami-cloudpipe', 'AMI for cloudpipe vpn server')
DEFINE_string('vpn_key_suffix',
- '-key',
- 'Suffix to add to project name for vpn key')
+ '-vpn',
+ 'Suffix to add to project name for vpn key and secgroups')
DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger')
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 8aca1c80a..5dfe152d9 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -45,8 +45,6 @@ flags.DEFINE_string('vlan_interface', 'eth0',
'network device for vlans')
flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'),
'location of nova-dhcpbridge')
-flags.DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server')
-flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
flags.DEFINE_string('routing_source_ip', utils.get_my_ip(),
'Public IP of network host')
flags.DEFINE_bool('use_nova_chains', False,
diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py
index 4508d6721..78ce7a0a0 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()))
diff --git a/nova/utils.py b/nova/utils.py
index ea1f04ca7..082a42acf 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -21,13 +21,13 @@ System-level utilities and helper functions.
"""
import datetime
-import functools
import inspect
import logging
import os
import random
import subprocess
import socket
+import struct
import sys
from xml.sax import saxutils
@@ -35,11 +35,9 @@ from eventlet import event
from eventlet import greenthread
from nova import exception
-from nova import flags
from nova.exception import ProcessExecutionError
-FLAGS = flags.FLAGS
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
@@ -63,6 +61,51 @@ def import_object(import_str):
return cls()
+def vpn_ping(address, port, timeout=0.05, session_id=None):
+ """Sends a vpn negotiation packet and returns the server session.
+
+ Returns False on a failure. Basic packet structure is below.
+
+ Client packet (14 bytes)::
+ 0 1 8 9 13
+ +-+--------+-----+
+ |x| cli_id |?????|
+ +-+--------+-----+
+ x = packet identifier 0x38
+ cli_id = 64 bit identifier
+ ? = unknown, probably flags/padding
+
+ Server packet (26 bytes)::
+ 0 1 8 9 13 14 21 2225
+ +-+--------+-----+--------+----+
+ |x| srv_id |?????| cli_id |????|
+ +-+--------+-----+--------+----+
+ x = packet identifier 0x40
+ cli_id = 64 bit identifier
+ ? = unknown, probably flags/padding
+ bit 9 was 1 and the rest were 0 in testing
+ """
+ if session_id is None:
+ session_id = random.randint(0, 0xffffffffffffffff)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ data = struct.pack("!BQxxxxxx", 0x38, session_id)
+ sock.sendto(data, (address, port))
+ sock.settimeout(timeout)
+ try:
+ received = sock.recv(2048)
+ except socket.timeout:
+ return False
+ finally:
+ sock.close()
+ fmt = "!BQxxxxxQxxxx"
+ if len(received) != struct.calcsize(fmt):
+ print struct.calcsize(fmt)
+ return False
+ (identifier, server_sess, client_sess) = struct.unpack(fmt, received)
+ if identifier == 0x40 and client_sess == session_id:
+ return server_sess
+
+
def fetchfile(url, target):
logging.debug("Fetching %s" % url)
# c = pycurl.Curl()
@@ -151,8 +194,6 @@ def last_octet(address):
def get_my_ip():
"""Returns the actual ip of the local machine."""
- if getattr(FLAGS, 'fake_tests', None):
- return '127.0.0.1'
try:
csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
csock.connect(('8.8.8.8', 80))