Per-project vpns, certificates, and revocation
This commit is contained in:
parent
671b712a5a
commit
f127d85d77
@ -16,16 +16,24 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
# ARG is the id of the user
|
# $1 is the id of the project and $2 is the subject of the cert
|
||||||
export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-intCA-$1"
|
NAME=$1
|
||||||
mkdir INTER/$1
|
SUBJ=$2
|
||||||
cd INTER/$1
|
mkdir -p projects/$NAME
|
||||||
|
cd projects/$NAME
|
||||||
cp ../../openssl.cnf.tmpl openssl.cnf
|
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
|
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
|
echo "10" > serial
|
||||||
touch index.txt
|
touch index.txt
|
||||||
openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
|
# NOTE(vish): Disabling intermediate ca's because we don't actually need them.
|
||||||
openssl req -new -sha2 -key private/cakey.pem -out ../../reqs/inter$1.csr -batch -subj "$SUBJ"
|
# It makes more sense to have each project have its own root ca.
|
||||||
cd ../../
|
# openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
|
||||||
openssl ca -extensions v3_ca -days 365 -out INTER/$1/cacert.pem -in reqs/inter$1.csr -config openssl.cnf -batch
|
# 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
|
||||||
|
@ -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
|
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
|
||||||
touch index.txt
|
touch index.txt
|
||||||
echo "10" > serial
|
echo "10" > serial
|
||||||
|
openssl ca -gencrl -config ./openssl.cnf -out crl.pem
|
||||||
fi
|
fi
|
||||||
|
@ -24,7 +24,6 @@ dir = .
|
|||||||
|
|
||||||
[ ca ]
|
[ ca ]
|
||||||
default_ca = CA_default
|
default_ca = CA_default
|
||||||
unique_subject = no
|
|
||||||
|
|
||||||
[ CA_default ]
|
[ CA_default ]
|
||||||
serial = $dir/serial
|
serial = $dir/serial
|
||||||
@ -32,6 +31,8 @@ database = $dir/index.txt
|
|||||||
new_certs_dir = $dir/newcerts
|
new_certs_dir = $dir/newcerts
|
||||||
certificate = $dir/cacert.pem
|
certificate = $dir/cacert.pem
|
||||||
private_key = $dir/private/cakey.pem
|
private_key = $dir/private/cakey.pem
|
||||||
|
unique_subject = no
|
||||||
|
default_crl_days = 365
|
||||||
default_days = 365
|
default_days = 365
|
||||||
default_md = md5
|
default_md = md5
|
||||||
preserve = no
|
preserve = no
|
||||||
|
@ -69,6 +69,7 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
|
|||||||
sys.path.insert(0, possible_topdir)
|
sys.path.insert(0, possible_topdir)
|
||||||
|
|
||||||
from nova import context
|
from nova import context
|
||||||
|
from nova import crypto
|
||||||
from nova import db
|
from nova import db
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
@ -93,32 +94,36 @@ class VpnCommands(object):
|
|||||||
self.manager = manager.AuthManager()
|
self.manager = manager.AuthManager()
|
||||||
self.pipe = pipelib.CloudPipe()
|
self.pipe = pipelib.CloudPipe()
|
||||||
|
|
||||||
def list(self):
|
def list(self, project=None):
|
||||||
"""Print a listing of the VPNs for all projects."""
|
"""Print a listing of the VPN data for one or all projects.
|
||||||
|
|
||||||
|
args: [project=all]"""
|
||||||
print "%-12s\t" % 'project',
|
print "%-12s\t" % 'project',
|
||||||
print "%-20s\t" % 'ip:port',
|
print "%-20s\t" % 'ip:port',
|
||||||
|
print "%-20s\t" % 'private_ip',
|
||||||
print "%s" % 'state'
|
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,
|
print "%-12s\t" % project.name,
|
||||||
|
ipport = "%s:%s" % (project.vpn_ip, project.vpn_port)
|
||||||
try:
|
print "%-20s\t" % ipport,
|
||||||
s = "%s:%s" % (project.vpn_ip, project.vpn_port)
|
|
||||||
except exception.NotFound:
|
|
||||||
s = "None"
|
|
||||||
print "%-20s\t" % s,
|
|
||||||
|
|
||||||
vpn = self._vpn_for(project.id)
|
vpn = self._vpn_for(project.id)
|
||||||
if vpn:
|
if vpn:
|
||||||
|
net = 'down'
|
||||||
|
address = None
|
||||||
|
if vpn.get('fixed_ip', None):
|
||||||
|
address = vpn['fixed_ip']['address']
|
||||||
command = "ping -c1 -w1 %s > /dev/null; echo $?"
|
command = "ping -c1 -w1 %s > /dev/null; echo $?"
|
||||||
out, _err = utils.execute(command % vpn['private_dns_name'],
|
out, _err = utils.execute(command % address,
|
||||||
check_exit_code=False)
|
check_exit_code=False)
|
||||||
if out.strip() == '0':
|
if out.strip() == '0':
|
||||||
net = 'up'
|
net = 'up'
|
||||||
else:
|
print address,
|
||||||
net = 'down'
|
print vpn['host'],
|
||||||
print vpn['private_dns_name'],
|
print vpn['ec2_id'],
|
||||||
print vpn['node_name'],
|
|
||||||
print vpn['instance_id'],
|
|
||||||
print vpn['state_description'],
|
print vpn['state_description'],
|
||||||
print net
|
print net
|
||||||
|
|
||||||
@ -127,11 +132,11 @@ class VpnCommands(object):
|
|||||||
|
|
||||||
def _vpn_for(self, project_id):
|
def _vpn_for(self, project_id):
|
||||||
"""Get the VPN instance for a 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
|
if (instance['image_id'] == FLAGS.vpn_image_id
|
||||||
and not instance['state_description'] in
|
and not instance['state_description'] in
|
||||||
['shutting_down', 'shutdown']
|
['shutting_down', 'shutdown']):
|
||||||
and instance['project_id'] == project_id):
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def spawn(self):
|
def spawn(self):
|
||||||
@ -146,6 +151,22 @@ class VpnCommands(object):
|
|||||||
"""Start the VPN for a given project."""
|
"""Start the VPN for a given project."""
|
||||||
self.pipe.launch_vpn_instance(project_id)
|
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):
|
class ShellCommands(object):
|
||||||
def bpython(self):
|
def bpython(self):
|
||||||
@ -292,6 +313,14 @@ class UserCommands(object):
|
|||||||
is_admin = False
|
is_admin = False
|
||||||
self.manager.modify_user(name, access_key, secret_key, is_admin)
|
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 ProjectCommands(object):
|
||||||
"""Class for managing projects."""
|
"""Class for managing projects."""
|
||||||
|
@ -25,7 +25,6 @@ import webob.dec
|
|||||||
|
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import wsgi
|
from nova import wsgi
|
||||||
from nova.api import cloudpipe
|
|
||||||
from nova.api import ec2
|
from nova.api import ec2
|
||||||
from nova.api import openstack
|
from nova.api import openstack
|
||||||
from nova.api.ec2 import metadatarequesthandler
|
from nova.api.ec2 import metadatarequesthandler
|
||||||
@ -74,7 +73,6 @@ class API(wsgi.Router):
|
|||||||
mapper.connect('%s/{path_info:.*}' % s, controller=mrh,
|
mapper.connect('%s/{path_info:.*}' % s, controller=mrh,
|
||||||
conditions=ec2api_subdomain)
|
conditions=ec2api_subdomain)
|
||||||
|
|
||||||
mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API())
|
|
||||||
super(API, self).__init__(mapper)
|
super(API, self).__init__(mapper)
|
||||||
|
|
||||||
@webob.dec.wsgify
|
@webob.dec.wsgify
|
||||||
|
@ -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)
|
|
@ -64,12 +64,8 @@ flags.DEFINE_string('credential_key_file', 'pk.pem',
|
|||||||
'Filename of private key in credentials zip')
|
'Filename of private key in credentials zip')
|
||||||
flags.DEFINE_string('credential_cert_file', 'cert.pem',
|
flags.DEFINE_string('credential_cert_file', 'cert.pem',
|
||||||
'Filename of certificate in credentials zip')
|
'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')
|
'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',
|
flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver',
|
||||||
'Driver that auth manager uses')
|
'Driver that auth manager uses')
|
||||||
|
|
||||||
@ -625,27 +621,37 @@ class AuthManager(object):
|
|||||||
with self.driver() as drv:
|
with self.driver() as drv:
|
||||||
drv.modify_user(uid, access_key, secret_key, admin)
|
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"""
|
"""Get credential zip for user in project"""
|
||||||
if not isinstance(user, User):
|
if not isinstance(user, User):
|
||||||
user = self.get_user(user)
|
user = self.get_user(user)
|
||||||
if project is None:
|
if project is None:
|
||||||
project = user.id
|
project = user.id
|
||||||
pid = Project.safe_id(project)
|
pid = Project.safe_id(project)
|
||||||
rc = self.__generate_rc(user.access, user.secret, pid)
|
private_key, signed_cert = crypto.generate_x509_cert(user.id, pid)
|
||||||
private_key, signed_cert = self._generate_x509_cert(user.id, pid)
|
|
||||||
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
tmpdir = tempfile.mkdtemp()
|
||||||
zf = os.path.join(tmpdir, "temp.zip")
|
zf = os.path.join(tmpdir, "temp.zip")
|
||||||
zippy = zipfile.ZipFile(zf, 'w')
|
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_key_file, private_key)
|
||||||
zippy.writestr(FLAGS.credential_cert_file, signed_cert)
|
zippy.writestr(FLAGS.credential_cert_file, signed_cert)
|
||||||
|
|
||||||
try:
|
|
||||||
(vpn_ip, vpn_port) = self.get_project_vpn_data(project)
|
(vpn_ip, vpn_port) = self.get_project_vpn_data(project)
|
||||||
except exception.NotFound:
|
|
||||||
vpn_ip = None
|
|
||||||
if vpn_ip:
|
if vpn_ip:
|
||||||
configfile = open(FLAGS.vpn_client_template, "r")
|
configfile = open(FLAGS.vpn_client_template, "r")
|
||||||
s = string.Template(configfile.read())
|
s = string.Template(configfile.read())
|
||||||
@ -656,10 +662,9 @@ class AuthManager(object):
|
|||||||
port=vpn_port)
|
port=vpn_port)
|
||||||
zippy.writestr(FLAGS.credential_vpn_file, config)
|
zippy.writestr(FLAGS.credential_vpn_file, config)
|
||||||
else:
|
else:
|
||||||
logging.warn("No vpn data for project %s" %
|
LOG.warn("No vpn data for project %s", pid)
|
||||||
pid)
|
|
||||||
|
|
||||||
zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id))
|
zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(pid))
|
||||||
zippy.close()
|
zippy.close()
|
||||||
with open(zf, 'rb') as f:
|
with open(zf, 'rb') as f:
|
||||||
read_buffer = f.read()
|
read_buffer = f.read()
|
||||||
@ -667,38 +672,38 @@ class AuthManager(object):
|
|||||||
shutil.rmtree(tmpdir)
|
shutil.rmtree(tmpdir)
|
||||||
return read_buffer
|
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"""
|
"""Get credential zip for user in project"""
|
||||||
if not isinstance(user, User):
|
if not isinstance(user, User):
|
||||||
user = self.get_user(user)
|
user = self.get_user(user)
|
||||||
if project is None:
|
if project is None:
|
||||||
project = user.id
|
project = user.id
|
||||||
pid = Project.safe_id(project)
|
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
|
@staticmethod
|
||||||
def __generate_rc(access, secret, pid):
|
def __generate_rc(access, secret, pid, use_dmz=True, host=None):
|
||||||
"""Generate rc file for user"""
|
"""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 = open(FLAGS.credentials_template).read()
|
||||||
rc = rc % {'access': access,
|
rc = rc % {'access': access,
|
||||||
'project': pid,
|
'project': pid,
|
||||||
'secret': secret,
|
'secret': secret,
|
||||||
'ec2': FLAGS.ec2_url,
|
'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix,
|
||||||
's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
|
cc_host,
|
||||||
|
FLAGS.cc_port,
|
||||||
|
FLAGS.ec2_suffix),
|
||||||
|
's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port),
|
||||||
'nova': FLAGS.ca_file,
|
'nova': FLAGS.ca_file,
|
||||||
'cert': FLAGS.credential_cert_file,
|
'cert': FLAGS.credential_cert_file,
|
||||||
'key': FLAGS.credential_key_file}
|
'key': FLAGS.credential_key_file}
|
||||||
return rc
|
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())
|
|
||||||
|
@ -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
|
|
50
nova/cloudpipe/bootscript.template
Executable file
50
nova/cloudpipe/bootscript.template
Executable file
@ -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
|
@ -22,13 +22,15 @@ an instance with it.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import string
|
||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from nova import context
|
from nova import context
|
||||||
|
from nova import crypto
|
||||||
|
from nova import db
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import utils
|
from nova import utils
|
||||||
@ -39,8 +41,17 @@ from nova.api.ec2 import cloud
|
|||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
flags.DEFINE_string('boot_script_template',
|
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')
|
'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):
|
class CloudPipe(object):
|
||||||
@ -48,64 +59,96 @@ class CloudPipe(object):
|
|||||||
self.controller = cloud.CloudController()
|
self.controller = cloud.CloudController()
|
||||||
self.manager = manager.AuthManager()
|
self.manager = manager.AuthManager()
|
||||||
|
|
||||||
def launch_vpn_instance(self, project_id):
|
def get_encoded_zip(self, project_id):
|
||||||
logging.debug("Launching VPN for %s" % (project_id))
|
|
||||||
project = self.manager.get_project(project_id)
|
|
||||||
# Make a payload.zip
|
# Make a payload.zip
|
||||||
tmpfolder = tempfile.mkdtemp()
|
tmpfolder = tempfile.mkdtemp()
|
||||||
filename = "payload.zip"
|
filename = "payload.zip"
|
||||||
zippath = os.path.join(tmpfolder, filename)
|
zippath = os.path.join(tmpfolder, filename)
|
||||||
z = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED)
|
z = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED)
|
||||||
|
shellfile = open(FLAGS.boot_script_template, "r")
|
||||||
z.write(FLAGS.boot_script_template, 'autorun.sh')
|
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()
|
z.close()
|
||||||
|
|
||||||
key_name = self.setup_key_pair(project.project_manager_id, project_id)
|
|
||||||
zippy = open(zippath, "r")
|
zippy = open(zippath, "r")
|
||||||
context = context.RequestContext(user=project.project_manager,
|
# NOTE(vish): run instances expects encoded userdata, it is decoded
|
||||||
project=project)
|
# 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
|
||||||
|
|
||||||
reservation = self.controller.run_instances(context,
|
def launch_vpn_instance(self, project_id):
|
||||||
# Run instances expects encoded userdata, it is decoded in the
|
LOG.debug("Launching VPN for %s" % (project_id))
|
||||||
# get_metadata_call. autorun.sh also decodes the zip file, hence
|
project = self.manager.get_project(project_id)
|
||||||
# the double encoding.
|
ctxt = context.RequestContext(user=project.project_manager,
|
||||||
user_data=zippy.read().encode("base64").encode("base64"),
|
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,
|
max_count=1,
|
||||||
min_count=1,
|
min_count=1,
|
||||||
instance_type='m1.tiny',
|
instance_type='m1.tiny',
|
||||||
image_id=FLAGS.vpn_image_id,
|
image_id=FLAGS.vpn_image_id,
|
||||||
key_name=key_name,
|
key_name=key_name,
|
||||||
security_groups=["vpn-secgroup"])
|
security_group=[group_name])
|
||||||
zippy.close()
|
|
||||||
|
|
||||||
def setup_key_pair(self, user_id, project_id):
|
def setup_security_group(self, context):
|
||||||
key_name = '%s%s' % (project_id, FLAGS.vpn_key_suffix)
|
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, context):
|
||||||
|
key_name = '%s%s' % (context.project.id, FLAGS.vpn_key_suffix)
|
||||||
try:
|
try:
|
||||||
private_key, fingerprint = self.manager.generate_key_pair(user_id,
|
result = cloud._gen_key(context, context.user.id, key_name)
|
||||||
key_name)
|
private_key = result['private_key']
|
||||||
try:
|
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):
|
if not os.path.exists(key_dir):
|
||||||
os.makedirs(key_dir)
|
os.makedirs(key_dir)
|
||||||
file_name = os.path.join(key_dir, '%s.pem' % key_name)
|
key_path = os.path.join(key_dir, '%s.pem' % key_name)
|
||||||
with open(file_name, 'w') as f:
|
with open(key_path, 'w') as f:
|
||||||
f.write(private_key)
|
f.write(private_key)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
except exception.Duplicate:
|
except exception.Duplicate:
|
||||||
pass
|
pass
|
||||||
return key_name
|
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
|
|
||||||
|
191
nova/crypto.py
191
nova/crypto.py
@ -17,7 +17,7 @@
|
|||||||
# under the License.
|
# 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.
|
SSH key_pairs and x509 certificates.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -33,28 +33,57 @@ import utils
|
|||||||
|
|
||||||
import M2Crypto
|
import M2Crypto
|
||||||
|
|
||||||
from nova import exception
|
from nova import context
|
||||||
|
from nova import db
|
||||||
from nova import flags
|
from nova import flags
|
||||||
|
|
||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
flags.DEFINE_string('ca_file', 'cacert.pem', 'Filename of root CA')
|
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'),
|
flags.DEFINE_string('keys_path', utils.abspath('../keys'),
|
||||||
'Where we keep our keys')
|
'Where we keep our keys')
|
||||||
flags.DEFINE_string('ca_path', utils.abspath('../CA'),
|
flags.DEFINE_string('ca_path', utils.abspath('../CA'),
|
||||||
'Where we keep our root CA')
|
'Where we keep our root CA')
|
||||||
flags.DEFINE_boolean('use_intermediate_ca', False,
|
flags.DEFINE_boolean('use_project_ca', False,
|
||||||
'Should we use intermediate CAs for each project?')
|
'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):
|
def ca_folder(project_id=None):
|
||||||
if project_id:
|
if FLAGS.use_project_ca and project_id:
|
||||||
return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, project_id)
|
return os.path.join(FLAGS.ca_path, 'projects', project_id)
|
||||||
return "%s/cacert.pem" % (FLAGS.ca_path)
|
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):
|
def fetch_ca(project_id=None, chain=True):
|
||||||
if not FLAGS.use_intermediate_ca:
|
if not FLAGS.use_project_ca:
|
||||||
project_id = None
|
project_id = None
|
||||||
buffer = ""
|
buffer = ""
|
||||||
if project_id:
|
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'):
|
def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
|
||||||
pub_key_buffer = M2Crypto.BIO.MemoryBuffer(ssl_public_key)
|
buf = M2Crypto.BIO.MemoryBuffer(ssl_public_key)
|
||||||
rsa_key = M2Crypto.RSA.load_pub_key_bio(pub_key_buffer)
|
rsa_key = M2Crypto.RSA.load_pub_key_bio(buf)
|
||||||
e, n = rsa_key.pub()
|
e, n = rsa_key.pub()
|
||||||
|
|
||||||
key_type = 'ssh-rsa'
|
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)
|
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()
|
tmpdir = tempfile.mkdtemp()
|
||||||
keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key'))
|
keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key'))
|
||||||
csrfile = os.path.join(tmpdir, 'temp.csr')
|
csrfile = os.path.join(tmpdir, 'temp.csr')
|
||||||
logging.debug("openssl genrsa -out %s %s" % (keyfile, bits))
|
utils.execute("openssl genrsa -out %s %s" % (keyfile, bits))
|
||||||
utils.runthis("Generating private key: %s",
|
utils.execute("openssl req -new -key %s -out %s -batch -subj %s" %
|
||||||
"openssl genrsa -out %s %s" % (keyfile, bits))
|
|
||||||
utils.runthis("Generating CSR: %s",
|
|
||||||
"openssl req -new -key %s -out %s -batch -subj %s" %
|
|
||||||
(keyfile, csrfile, subject))
|
(keyfile, csrfile, subject))
|
||||||
private_key = open(keyfile).read()
|
private_key = open(keyfile).read()
|
||||||
csr = open(csrfile).read()
|
csr = open(csrfile).read()
|
||||||
shutil.rmtree(tmpdir)
|
shutil.rmtree(tmpdir)
|
||||||
return (private_key, csr)
|
(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 sign_csr(csr_text, intermediate=None):
|
def _ensure_project_folder(project_id):
|
||||||
if not FLAGS.use_intermediate_ca:
|
if not os.path.exists(ca_path(project_id)):
|
||||||
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):
|
|
||||||
start = os.getcwd()
|
start = os.getcwd()
|
||||||
os.chdir(FLAGS.ca_path)
|
os.chdir(ca_folder())
|
||||||
utils.runthis("Generating intermediate CA: %s",
|
utils.execute("sh geninter.sh %s %s" %
|
||||||
"sh geninter.sh %s" % (intermediate))
|
(project_id, _project_cert_subject(project_id)))
|
||||||
os.chdir(start)
|
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):
|
def _sign_csr(csr_text, ca_folder):
|
||||||
tmpfolder = tempfile.mkdtemp()
|
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.write(csr_text)
|
||||||
csrfile.close()
|
csrfile.close()
|
||||||
logging.debug("Flags path: %s" % ca_folder)
|
logging.debug("Flags path: %s", ca_folder)
|
||||||
start = os.getcwd()
|
start = os.getcwd()
|
||||||
# Change working dir to CA
|
# Change working dir to CA
|
||||||
os.chdir(ca_folder)
|
os.chdir(ca_folder)
|
||||||
utils.runthis("Signing cert: %s",
|
utils.execute("openssl ca -batch -out %s -config "
|
||||||
"openssl ca -batch -out %s/outbound.crt "
|
"./openssl.cnf -infiles %s" % (outbound, inbound))
|
||||||
"-config ./openssl.cnf -infiles %s/inbound.csr" %
|
out, _err = utils.execute("openssl x509 -in %s -serial -noout" % outbound)
|
||||||
(tmpfolder, tmpfolder))
|
serial = int(out.rpartition("=")[2])
|
||||||
os.chdir(start)
|
os.chdir(start)
|
||||||
with open("%s/outbound.crt" % (tmpfolder), "r") as crtfile:
|
with open(outbound, "r") as crtfile:
|
||||||
return crtfile.read()
|
return (serial, crtfile.read())
|
||||||
|
|
||||||
|
|
||||||
def mkreq(bits, subject="foo", ca=0):
|
def mkreq(bits, subject="foo", ca=0):
|
||||||
@ -159,8 +272,7 @@ def mkreq(bits, subject="foo", ca=0):
|
|||||||
req = M2Crypto.X509.Request()
|
req = M2Crypto.X509.Request()
|
||||||
rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
|
rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
|
||||||
pk.assign_rsa(rsa)
|
pk.assign_rsa(rsa)
|
||||||
# Should not be freed here
|
rsa = None # should not be freed here
|
||||||
rsa = None
|
|
||||||
req.set_pubkey(pk)
|
req.set_pubkey(pk)
|
||||||
req.set_subject(subject)
|
req.set_subject(subject)
|
||||||
req.sign(pk, 'sha512')
|
req.sign(pk, 'sha512')
|
||||||
@ -224,7 +336,6 @@ def mkcacert(subject='nova', years=1):
|
|||||||
# IN THE SOFTWARE.
|
# IN THE SOFTWARE.
|
||||||
# http://code.google.com/p/boto
|
# http://code.google.com/p/boto
|
||||||
|
|
||||||
|
|
||||||
def compute_md5(fp):
|
def compute_md5(fp):
|
||||||
"""
|
"""
|
||||||
@type fp: file
|
@type fp: file
|
||||||
|
@ -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):
|
def floating_ip_allocate_address(context, host, project_id):
|
||||||
"""Allocate free floating ip and return the address.
|
"""Allocate free floating ip and return the address.
|
||||||
|
|
||||||
|
@ -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
|
@require_context
|
||||||
def floating_ip_allocate_address(context, host, project_id):
|
def floating_ip_allocate_address(context, host, project_id):
|
||||||
authorize_project_context(context, project_id)
|
authorize_project_context(context, project_id)
|
||||||
|
@ -151,6 +151,16 @@ class Service(BASE, NovaBase):
|
|||||||
disabled = Column(Boolean, default=False)
|
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):
|
class Instance(BASE, NovaBase):
|
||||||
"""Represents a guest vm"""
|
"""Represents a guest vm"""
|
||||||
__tablename__ = 'instances'
|
__tablename__ = 'instances'
|
||||||
@ -521,7 +531,7 @@ def register_models():
|
|||||||
"""Register Models and create metadata"""
|
"""Register Models and create metadata"""
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
models = (Service, Instance, Volume, ExportDevice, IscsiTarget, FixedIp,
|
models = (Service, Instance, Volume, ExportDevice, IscsiTarget, FixedIp,
|
||||||
FloatingIp, Network, SecurityGroup,
|
FloatingIp, Network, SecurityGroup, Certificate,
|
||||||
SecurityGroupIngressRule, SecurityGroupInstanceAssociation,
|
SecurityGroupIngressRule, SecurityGroupInstanceAssociation,
|
||||||
AuthToken, User, Project) # , Image, Host
|
AuthToken, User, Project) # , Image, Host
|
||||||
engine = create_engine(FLAGS.sql_connection, echo=False)
|
engine = create_engine(FLAGS.sql_connection, echo=False)
|
||||||
|
@ -208,17 +208,13 @@ class AuthManagerTestCase(object):
|
|||||||
# so it probably belongs in crypto_unittest
|
# so it probably belongs in crypto_unittest
|
||||||
# but I'm leaving it where I found it.
|
# but I'm leaving it where I found it.
|
||||||
with user_and_project_generator(self.manager) as (user, project):
|
with user_and_project_generator(self.manager) as (user, project):
|
||||||
# NOTE(todd): Should mention why we must setup controller first
|
# NOTE(vish): Setup runs genroot.sh if it hasn't been run
|
||||||
# (somebody please clue me in)
|
cloud.CloudController().setup()
|
||||||
cloud_controller = cloud.CloudController()
|
_key, cert_str = crypto.generate_x509_cert(user.id, project.id)
|
||||||
cloud_controller.setup()
|
|
||||||
_key, cert_str = self.manager._generate_x509_cert('test1',
|
|
||||||
'testproj')
|
|
||||||
logging.debug(cert_str)
|
logging.debug(cert_str)
|
||||||
|
|
||||||
# Need to verify that it's signed by the right intermediate CA
|
full_chain = crypto.fetch_ca(project_id=project.id, chain=True)
|
||||||
full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
|
int_cert = crypto.fetch_ca(project_id=project.id, chain=False)
|
||||||
int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
|
|
||||||
cloud_cert = crypto.fetch_ca()
|
cloud_cert = crypto.fetch_ca()
|
||||||
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
|
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
|
||||||
signed_cert = X509.load_cert_string(cert_str)
|
signed_cert = X509.load_cert_string(cert_str)
|
||||||
@ -227,7 +223,8 @@ class AuthManagerTestCase(object):
|
|||||||
cloud_cert = X509.load_cert_string(cloud_cert)
|
cloud_cert = X509.load_cert_string(cloud_cert)
|
||||||
self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
|
self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
|
||||||
self.assertTrue(signed_cert.verify(int_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()))
|
self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
|
||||||
else:
|
else:
|
||||||
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
|
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
|
||||||
|
Loading…
Reference in New Issue
Block a user