nova/nova/cloudpipe/pipelib.py

181 lines
6.8 KiB
Python

# 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.
"""
CloudPipe - Build a user-data payload zip file, and launch
an instance with it.
"""
import os
import string
import zipfile
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import fileutils
from nova import compute
from nova.compute import flavors
from nova import crypto
from nova import db
from nova import exception
from nova.i18n import _
from nova import paths
from nova import utils
cloudpipe_opts = [
cfg.StrOpt('vpn_image_id',
default='0',
help='Image ID used when starting up a cloudpipe vpn server'),
cfg.StrOpt('vpn_flavor',
default='m1.tiny',
help=_('Flavor for vpn instances')),
cfg.StrOpt('boot_script_template',
default=paths.basedir_def('nova/cloudpipe/bootscript.template'),
help=_('Template for cloudpipe instance boot script')),
cfg.StrOpt('dmz_net',
default='10.0.0.0',
help=_('Network to push into openvpn config')),
cfg.StrOpt('dmz_mask',
default='255.255.255.0',
help=_('Netmask to push into openvpn config')),
cfg.StrOpt('vpn_key_suffix',
default='-vpn',
help='Suffix to add to project name for vpn key and secgroups'),
]
CONF = cfg.CONF
CONF.register_opts(cloudpipe_opts)
CONF.import_opt('keys_path', 'nova.crypto')
LOG = logging.getLogger(__name__)
def is_vpn_image(image_id):
return image_id == CONF.vpn_image_id
def _load_boot_script():
shellfile = open(CONF.boot_script_template, "r")
try:
s = string.Template(shellfile.read())
finally:
shellfile.close()
CONF.import_opt('ec2_dmz_host', 'nova.api.ec2.cloud')
CONF.import_opt('ec2_port', 'nova.api.ec2.cloud')
CONF.import_opt('cnt_vpn_clients', 'nova.network.manager')
return s.substitute(cc_dmz=CONF.ec2_dmz_host,
cc_port=CONF.ec2_port,
dmz_net=CONF.dmz_net,
dmz_mask=CONF.dmz_mask,
num_vpn=CONF.cnt_vpn_clients)
class CloudPipe(object):
def __init__(self, skip_policy_check=False):
self.compute_api = compute.API(skip_policy_check=skip_policy_check)
def get_encoded_zip(self, project_id):
# Make a payload.zip
with utils.tempdir() as tmpdir:
filename = "payload.zip"
zippath = os.path.join(tmpdir, filename)
z = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED)
boot_script = _load_boot_script()
# 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()
zippy = open(zippath, "r")
# 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, context):
LOG.debug("Launching VPN for %s", context.project_id)
key_name = self.setup_key_pair(context)
group_name = self.setup_security_group(context)
flavor = flavors.get_flavor_by_name(CONF.vpn_flavor)
instance_name = '%s%s' % (context.project_id, CONF.vpn_key_suffix)
user_data = self.get_encoded_zip(context.project_id)
return self.compute_api.create(context,
flavor,
CONF.vpn_image_id,
display_name=instance_name,
user_data=user_data,
key_name=key_name,
security_group=[group_name])
def setup_security_group(self, context):
group_name = '%s%s' % (context.project_id, CONF.vpn_key_suffix)
group = {'user_id': context.user_id,
'project_id': context.project_id,
'name': group_name,
'description': 'Group for vpn'}
try:
group_ref = db.security_group_create(context, group)
except exception.SecurityGroupExists:
return group_name
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, CONF.vpn_key_suffix)
try:
keypair_api = compute.api.KeypairAPI()
result, private_key = keypair_api.create_key_pair(context,
context.user_id,
key_name)
key_dir = os.path.join(CONF.keys_path, context.user_id)
fileutils.ensure_tree(key_dir)
key_path = os.path.join(key_dir, '%s.pem' % key_name)
with open(key_path, 'w') as f:
f.write(private_key)
except (exception.KeyPairExists, os.error, IOError):
pass
return key_name