# Copyright 2016 Cloudbase Solutions Srl # 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. """ CLI interface for nova policy rule commands. """ import functools import os import sys from oslo_config import cfg from nova.cmd import common as cmd_common import nova.conf from nova import config from nova import context as nova_context from nova.db import api as db from nova import exception from nova.i18n import _ from nova import policies from nova import version CONF = nova.conf.CONF cli_opts = [ cfg.ListOpt( 'os-roles', metavar='', default=os.environ.get('OS_ROLES'), help=_('Defaults to env[OS_ROLES].')), cfg.StrOpt( 'os-user-id', metavar='', default=os.environ.get('OS_USER_ID'), help=_('Defaults to env[OS_USER_ID].')), cfg.StrOpt( 'os-tenant-id', metavar='', default=os.environ.get('OS_TENANT_ID'), help=_('Defaults to env[OS_TENANT_ID].')), ] class PolicyCommands(object): """Commands for policy rules.""" _ACCEPTABLE_TARGETS = [ 'project_id', 'user_id', 'quota_class', 'availability_zone', 'instance_id'] @cmd_common.args('--api-name', dest='api_name', metavar='', help='Will return only passing policy rules containing ' 'the given API name.') @cmd_common.args('--target', nargs='+', dest='target', metavar='', help='Will return only passing policy rules for the ' 'given target. The available targets are %s. When ' '"instance_id" is used, the other targets will be ' 'overwritten.' % ','.join(_ACCEPTABLE_TARGETS)) def check(self, api_name=None, target=None): """Prints all passing policy rules for the given user. :param api_name: If None, all passing policy rules will be printed, otherwise, only passing policies that contain the given api_name in their names. :param target: The target against which the policy rule authorization will be tested. If None, the given user will be considered as the target. """ context = self._get_context() api_name = api_name or '' target = self._get_target(context, target) allowed_operations = self._filter_rules(context, api_name, target) if allowed_operations: print('\n'.join(allowed_operations)) return 0 else: print('No rules matched or allowed') return 1 def _get_context(self): return nova_context.RequestContext( roles=CONF.os_roles, user_id=CONF.os_user_id, project_id=CONF.os_tenant_id) def _get_target(self, context, target): """Processes and validates the CLI given target and adapts it for policy authorization. :returns: None if the given target is None, otherwise returns a proper authorization target. :raises nova.exception.InvalidAttribute: if a key in the given target is not an acceptable. :raises nova.exception.InstanceNotFound: if 'instance_id' is given, and there is no instance match the id. """ if not target: return None new_target = {} for t in target: key, value = t.split('=') if key not in self._ACCEPTABLE_TARGETS: raise exception.InvalidAttribute(attr=key) new_target[key] = value # if the target is an instance_id, return an instance instead. instance_id = new_target.get('instance_id') if instance_id: admin_ctxt = nova_context.get_admin_context() instance = db.instance_get_by_uuid(admin_ctxt, instance_id) new_target = {'user_id': instance['user_id'], 'project_id': instance['project_id']} return new_target def _filter_rules(self, context, api_name, target): all_rules = policies.list_rules() return [rule.name for rule in all_rules if api_name in rule.name and context.can(rule.name, target, fatal=False)] CATEGORIES = { 'policy': PolicyCommands, } add_command_parsers = functools.partial(cmd_common.add_command_parsers, categories=CATEGORIES) category_opt = cfg.SubCommandOpt('category', title='Command categories', help='Available categories', handler=add_command_parsers) def main(): """Parse options and call the appropriate class/method.""" CONF.register_cli_opts(cli_opts) CONF.register_cli_opt(category_opt) config.parse_args(sys.argv) if CONF.category.name == "version": print(version.version_string_with_package()) return 0 if CONF.category.name == "bash-completion": cmd_common.print_bash_completion(CATEGORIES) return 0 try: fn, fn_args, fn_kwargs = cmd_common.get_action_fn() ret = fn(*fn_args, **fn_kwargs) return ret except Exception as ex: print(_("error: %s") % ex) return 1