######### # Copyright (c) 2014 GigaSpaces Technologies Ltd. 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. import os import errno from getpass import getuser from cloudify import ctx from cloudify.decorators import operation from cloudify.exceptions import NonRecoverableError from openstack_plugin_common import ( with_nova_client, validate_resource, use_external_resource, transform_resource_name, is_external_resource, is_external_resource_not_conditionally_created, delete_runtime_properties, get_resource_id, delete_resource_and_runtime_properties, OPENSTACK_ID_PROPERTY, OPENSTACK_TYPE_PROPERTY, OPENSTACK_NAME_PROPERTY, COMMON_RUNTIME_PROPERTIES_KEYS ) RUNTIME_PROPERTIES_KEYS = COMMON_RUNTIME_PROPERTIES_KEYS KEYPAIR_OPENSTACK_TYPE = 'keypair' PRIVATE_KEY_PATH_PROP = 'private_key_path' @operation @with_nova_client def create(nova_client, args, **kwargs): private_key_path = _get_private_key_path() pk_exists = _check_private_key_exists(private_key_path) if use_external_resource(ctx, nova_client, KEYPAIR_OPENSTACK_TYPE): if not pk_exists: delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) raise NonRecoverableError( 'Failed to use external keypair (node {0}): the public key {1}' ' is available on Openstack, but the private key could not be ' 'found at {2}'.format(ctx.node.id, ctx.node.properties['resource_id'], private_key_path)) return if pk_exists: raise NonRecoverableError( "Can't create keypair - private key path already exists: {0}" .format(private_key_path)) keypair = { 'name': get_resource_id(ctx, KEYPAIR_OPENSTACK_TYPE), } keypair.update(ctx.node.properties['keypair'], **args) transform_resource_name(ctx, keypair) keypair = nova_client.keypairs.create(keypair['name'], keypair.get('public_key')) ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] = keypair.id ctx.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY] = \ KEYPAIR_OPENSTACK_TYPE ctx.instance.runtime_properties[OPENSTACK_NAME_PROPERTY] = keypair.name try: # write private key file _mkdir_p(os.path.dirname(private_key_path)) with open(private_key_path, 'w') as f: f.write(keypair.private_key) os.chmod(private_key_path, 0600) except Exception: _delete_private_key_file() delete_resource_and_runtime_properties(ctx, nova_client, RUNTIME_PROPERTIES_KEYS) raise @operation @with_nova_client def delete(nova_client, **kwargs): if not is_external_resource(ctx): ctx.logger.info('deleting keypair') _delete_private_key_file() nova_client.keypairs.delete( ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY]) else: ctx.logger.info('not deleting keypair since an external keypair is ' 'being used') delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) @operation @with_nova_client def creation_validation(nova_client, **kwargs): def validate_private_key_permissions(private_key_path): ctx.logger.debug('checking whether private key file {0} has the ' 'correct permissions'.format(private_key_path)) if not os.access(private_key_path, os.R_OK): err = 'private key file {0} is not readable'\ .format(private_key_path) ctx.logger.error('VALIDATION ERROR: ' + err) raise NonRecoverableError(err) ctx.logger.debug('OK: private key file {0} has the correct ' 'permissions'.format(private_key_path)) def validate_path_owner(path): ctx.logger.debug('checking whether directory {0} is owned by the ' 'current user'.format(path)) from pwd import getpwnam, getpwuid user = getuser() owner = getpwuid(os.stat(path).st_uid).pw_name current_user_id = str(getpwnam(user).pw_uid) owner_id = str(os.stat(path).st_uid) if not current_user_id == owner_id: err = '{0} is not owned by the current user (it is owned by {1})'\ .format(path, owner) ctx.logger.warning('VALIDATION WARNING: {0}'.format(err)) return ctx.logger.debug('OK: {0} is owned by the current user'.format(path)) validate_resource(ctx, nova_client, KEYPAIR_OPENSTACK_TYPE) private_key_path = _get_private_key_path() pk_exists = _check_private_key_exists(private_key_path) if is_external_resource_not_conditionally_created(ctx): if pk_exists: if os.name == 'posix': validate_private_key_permissions(private_key_path) validate_path_owner(private_key_path) else: err = "can't use external keypair: the public key {0} is " \ "available on Openstack, but the private key could not be " \ "found at {1}".format(ctx.node.properties['resource_id'], private_key_path) ctx.logger.error('VALIDATION ERROR: {0}'.format(err)) raise NonRecoverableError(err) else: if pk_exists: err = 'private key path already exists: {0}'.format( private_key_path) ctx.logger.error('VALIDATION ERROR: {0}'.format(err)) raise NonRecoverableError(err) else: err = 'private key directory {0} is not writable' while private_key_path: if os.path.isdir(private_key_path): if not os.access(private_key_path, os.W_OK | os.X_OK): raise NonRecoverableError(err.format(private_key_path)) else: break private_key_path, _ = os.path.split(private_key_path) ctx.logger.debug('OK: keypair configuration is valid') def _get_private_key_path(): return os.path.expanduser(ctx.node.properties[PRIVATE_KEY_PATH_PROP]) def _delete_private_key_file(): private_key_path = _get_private_key_path() ctx.logger.debug('deleting private key file at {0}'.format( private_key_path)) try: os.remove(private_key_path) except OSError as e: if e.errno == errno.ENOENT: # file was already deleted somehow pass raise def _check_private_key_exists(private_key_path): return os.path.isfile(private_key_path) def _mkdir_p(path): if path and not os.path.isdir(path): os.makedirs(path)