diff --git a/etc/policy.json b/etc/policy.json index 4a1482f86a..aaf209242e 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -1,3 +1,57 @@ { - "admin_required": [["role:admin"], ["is_admin:1"]] + "admin_required": [["role:admin"], ["is_admin:1"]], + + "identity:get_service": [["rule:admin_required"]], + "identity:list_services": [["rule:admin_required"]], + "identity:create_service": [["rule:admin_required"]], + "identity:update_service": [["rule:admin_required"]], + "identity:delete_service": [["rule:admin_required"]], + + "identity:get_endpoint": [["rule:admin_required"]], + "identity:list_endpoints": [["rule:admin_required"]], + "identity:create_endpoint": [["rule:admin_required"]], + "identity:update_endpoint": [["rule:admin_required"]], + "identity:delete_endpoint": [["rule:admin_required"]], + + "identity:get_domain": [["rule:admin_required"]], + "identity:list_domains": [["rule:admin_required"]], + "identity:create_domain": [["rule:admin_required"]], + "identity:update_domain": [["rule:admin_required"]], + "identity:delete_domain": [["rule:admin_required"]], + + "identity:get_project": [["rule:admin_required"]], + "identity:list_projects": [["rule:admin_required"]], + "identity:list_user_projects": [["rule:admin_required"], ["user_id:%(user_id)s"]], + "identity:create_project": [["rule:admin_required"]], + "identity:update_project": [["rule:admin_required"]], + "identity:delete_project": [["rule:admin_required"]], + + "identity:get_user": [["rule:admin_required"]], + "identity:list_users": [["rule:admin_required"]], + "identity:create_user": [["rule:admin_required"]], + "identity:update_user": [["rule:admin_required"]], + "identity:delete_user": [["rule:admin_required"]], + + "identity:get_credential": [["rule:admin_required"]], + "identity:list_credentials": [["rule:admin_required"]], + "identity:create_credential": [["rule:admin_required"]], + "identity:update_credential": [["rule:admin_required"]], + "identity:delete_credential": [["rule:admin_required"]], + + "identity:get_role": [["rule:admin_required"]], + "identity:list_roles": [["rule:admin_required"]], + "identity:create_role": [["rule:admin_required"]], + "identity:update_roles": [["rule:admin_required"]], + "identity:delete_roles": [["rule:admin_required"]], + + "identity:check_grant": [["rule:admin_required"]], + "identity:list_grants": [["rule:admin_required"]], + "identity:create_grant": [["rule:admin_required"]], + "identity:revoke_grant": [["rule:admin_required"]], + + "identity:get_policy": [["rule:admin_required"]], + "identity:list_policies": [["rule:admin_required"]], + "identity:create_policy": [["rule:admin_required"]], + "identity:update_policy": [["rule:admin_required"]], + "identity:delete_policy": [["rule:admin_required"]] } diff --git a/keystone/catalog/core.py b/keystone/catalog/core.py index 2c396b5d52..79b3df2ad2 100644 --- a/keystone/catalog/core.py +++ b/keystone/catalog/core.py @@ -278,46 +278,40 @@ class EndpointController(wsgi.Application): class ServiceControllerV3(controller.V3Controller): + @controller.protected def create_service(self, context, service): - self.assert_admin(context) - ref = self._assign_unique_id(self._normalize_dict(service)) self._require_attribute(ref, 'type') ref = self.catalog_api.create_service(context, ref['id'], ref) return {'service': ref} + @controller.protected def list_services(self, context): - self.assert_admin(context) - refs = self.catalog_api.list_services(context) refs = self._filter_by_attribute(context, refs, 'type') return {'services': self._paginate(context, refs)} + @controller.protected def get_service(self, context, service_id): - self.assert_admin(context) - ref = self.catalog_api.get_service(context, service_id) return {'service': ref} + @controller.protected def update_service(self, context, service_id, service): - self.assert_admin(context) - self._require_matching_id(service_id, service) ref = self.catalog_api.update_service(context, service_id, service) return {'service': ref} + @controller.protected def delete_service(self, context, service_id): - self.assert_admin(context) - return self.catalog_api.delete_service(context, service_id) class EndpointControllerV3(controller.V3Controller): + @controller.protected def create_endpoint(self, context, endpoint): - self.assert_admin(context) - ref = self._assign_unique_id(self._normalize_dict(endpoint)) self._require_attribute(ref, 'service_id') self._require_attribute(ref, 'interface') @@ -326,23 +320,20 @@ class EndpointControllerV3(controller.V3Controller): ref = self.catalog_api.create_endpoint(context, ref['id'], ref) return {'endpoint': ref} + @controller.protected def list_endpoints(self, context): - self.assert_admin(context) - refs = self.catalog_api.list_endpoints(context) refs = self._filter_by_attribute(context, refs, 'service_id') refs = self._filter_by_attribute(context, refs, 'interface') return {'endpoints': self._paginate(context, refs)} + @controller.protected def get_endpoint(self, context, endpoint_id): - self.assert_admin(context) - ref = self.catalog_api.get_endpoint(context, endpoint_id) return {'endpoint': ref} + @controller.protected def update_endpoint(self, context, endpoint_id, endpoint): - self.assert_admin(context) - self._require_matching_id(endpoint_id, endpoint) if 'service_id' in endpoint: @@ -351,6 +342,6 @@ class EndpointControllerV3(controller.V3Controller): ref = self.catalog_api.update_endpoint(context, endpoint_id, endpoint) return {'endpoint': ref} + @controller.protected def delete_endpoint(self, context, endpoint_id): - self.assert_admin(context) return self.catalog_api.delete_endpoint(context, endpoint_id) diff --git a/keystone/common/controller.py b/keystone/common/controller.py index cb8d90d8ca..5db57b617a 100644 --- a/keystone/common/controller.py +++ b/keystone/common/controller.py @@ -1,9 +1,60 @@ import uuid +import functools +from keystone.common import logging from keystone.common import wsgi from keystone import exception +LOG = logging.getLogger(__name__) + + +def protected(f): + """Wraps API calls with role based access controls (RBAC).""" + + @functools.wraps(f) + def wrapper(self, context, **kwargs): + if not context['is_admin']: + action = 'identity:%s' % f.__name__ + + LOG.debug('RBAC: Authorizing %s(%s)' % ( + action, + ', '.join(['%s=%s' % (k, kwargs[k]) for k in kwargs]))) + + try: + token_ref = self.token_api.get_token( + context=context, token_id=context['token_id']) + except exception.TokenNotFound: + LOG.warning('RBAC: Invalid token') + raise exception.Unauthorized() + + creds = token_ref['metadata'].copy() + + try: + creds['user_id'] = token_ref['user'].get('id') + except AttributeError: + LOG.warning('RBAC: Invalid user') + raise exception.Unauthorized() + + try: + creds['tenant_id'] = token_ref['tenant'].get('id') + except AttributeError: + LOG.debug('RBAC: Proceeding without tenant') + + # NOTE(vish): this is pretty inefficient + creds['roles'] = [self.identity_api.get_role(context, role)['name'] + for role in creds.get('roles', [])] + + self.policy_api.enforce(context, creds, action, kwargs) + + LOG.debug('RBAC: Authorization granted') + else: + LOG.warning('RBAC: Bypassing authorization') + + return f(self, context, **kwargs) + return wrapper + + class V3Controller(wsgi.Application): """Base controller class for Identity API v3.""" diff --git a/keystone/identity/core.py b/keystone/identity/core.py index 5bd2f4463e..107dcaa946 100644 --- a/keystone/identity/core.py +++ b/keystone/identity/core.py @@ -859,134 +859,116 @@ class RoleController(wsgi.Application): class DomainControllerV3(controller.V3Controller): + @controller.protected def create_domain(self, context, domain): - self.assert_admin(context) - ref = self._assign_unique_id(self._normalize_dict(domain)) ref = self.identity_api.create_domain(context, ref['id'], ref) return {'domain': ref} + @controller.protected def list_domains(self, context): - self.assert_admin(context) - refs = self.identity_api.list_domains(context) return {'domains': self._paginate(context, refs)} + @controller.protected def get_domain(self, context, domain_id): - self.assert_admin(context) - ref = self.identity_api.get_domain(context, domain_id) return {'domain': ref} + @controller.protected def update_domain(self, context, domain_id, domain): - self.assert_admin(context) - self._require_matching_id(domain_id, domain) ref = self.identity_api.update_domain(context, domain_id, domain) return {'domain': ref} + @controller.protected def delete_domain(self, context, domain_id): - self.assert_admin(context) return self.identity_api.delete_domain(context, domain_id) class ProjectControllerV3(controller.V3Controller): + @controller.protected def create_project(self, context, project): - self.assert_admin(context) - ref = self._assign_unique_id(self._normalize_dict(project)) ref = self.identity_api.create_project(context, ref['id'], ref) return {'project': ref} + @controller.protected def list_projects(self, context): - self.assert_admin(context) - refs = self.identity_api.list_projects(context) return {'projects': self._paginate(context, refs)} + @controller.protected def list_user_projects(self, context, user_id): - # FIXME(dolph): this should also be callable by user_id themselves - self.assert_admin(context) - refs = self.identity_api.list_user_projects(context, user_id) return {'projects': self._paginate(context, refs)} + @controller.protected def get_project(self, context, project_id): - self.assert_admin(context) - ref = self.identity_api.get_project(context, project_id) return {'project': ref} + @controller.protected def update_project(self, context, project_id, project): - self.assert_admin(context) - self._require_matching_id(project_id, project) ref = self.identity_api.update_project(context, project_id, project) return {'project': ref} + @controller.protected def delete_project(self, context, project_id): - self.assert_admin(context) return self.identity_api.delete_project(context, project_id) class UserControllerV3(controller.V3Controller): + @controller.protected def create_user(self, context, user): - self.assert_admin(context) - ref = self._assign_unique_id(self._normalize_dict(user)) ref = self.identity_api.create_user(context, ref['id'], ref) return {'user': ref} + @controller.protected def list_users(self, context): - self.assert_admin(context) - refs = self.identity_api.list_users(context) return {'users': self._paginate(context, refs)} + @controller.protected def get_user(self, context, user_id): - self.assert_admin(context) - ref = self.identity_api.get_user(context, user_id) return {'user': ref} + @controller.protected def update_user(self, context, user_id, user): - self.assert_admin(context) - self._require_matching_id(user_id, user) ref = self.identity_api.update_user(context, user_id, user) return {'user': ref} + @controller.protected def delete_user(self, context, user_id): - self.assert_admin(context) return self.identity_api.delete_user(context, user_id) class CredentialControllerV3(controller.V3Controller): + @controller.protected def create_credential(self, context, credential): - self.assert_admin(context) - ref = self._assign_unique_id(self._normalize_dict(credential)) ref = self.identity_api.create_credential(context, ref['id'], ref) return {'credential': ref} + @controller.protected def list_credentials(self, context): - self.assert_admin(context) - refs = self.identity_api.list_credentials(context) return {'credentials': self._paginate(context, refs)} + @controller.protected def get_credential(self, context, credential_id): - self.assert_admin(context) - ref = self.identity_api.get_credential(context, credential_id) return {'credential': ref} + @controller.protected def update_credential(self, context, credential_id, credential): - self.assert_admin(context) - self._require_matching_id(credential_id, credential) ref = self.identity_api.update_credential( @@ -995,41 +977,37 @@ class CredentialControllerV3(controller.V3Controller): credential) return {'credential': ref} + @controller.protected def delete_credential(self, context, credential_id): - self.assert_admin(context) return self.identity_api.delete_credential(context, credential_id) class RoleControllerV3(controller.V3Controller): + @controller.protected def create_role(self, context, role): - self.assert_admin(context) - ref = self._assign_unique_id(self._normalize_dict(role)) ref = self.identity_api.create_role(context, ref['id'], ref) return {'role': ref} + @controller.protected def list_roles(self, context): - self.assert_admin(context) - refs = self.identity_api.list_roles(context) return {'roles': self._paginate(context, refs)} + @controller.protected def get_role(self, context, role_id): - self.assert_admin(context) - ref = self.identity_api.get_role(context, role_id) return {'role': ref} + @controller.protected def update_role(self, context, role_id, role): - self.assert_admin(context) - self._require_matching_id(role_id, role) ref = self.identity_api.update_role(context, role_id, role) return {'role': ref} + @controller.protected def delete_role(self, context, role_id): - self.assert_admin(context) return self.identity_api.delete_role(context, role_id) def _require_domain_or_project(self, domain_id, project_id): @@ -1037,41 +1015,37 @@ class RoleControllerV3(controller.V3Controller): msg = 'Specify a domain or project, not both' raise exception.ValidationError(msg) + @controller.protected def create_grant(self, context, role_id, user_id, domain_id=None, project_id=None): """Grants a role to a user on either a domain or project.""" - self.assert_admin(context) - self._require_domain_or_project(domain_id, project_id) return self.identity_api.create_grant( context, role_id, user_id, domain_id, project_id) + @controller.protected def list_grants(self, context, user_id, domain_id=None, project_id=None): """Lists roles granted to a user on either a domain or project.""" - self.assert_admin(context) - self._require_domain_or_project(domain_id, project_id) return self.identity_api.list_grants( context, user_id, domain_id, project_id) + @controller.protected def check_grant(self, context, role_id, user_id, domain_id=None, project_id=None): """Checks if a role has been granted on either a domain or project.""" - self.assert_admin(context) - self._require_domain_or_project(domain_id, project_id) self.identity_api.get_grant( context, role_id, user_id, domain_id, project_id) + @controller.protected def revoke_grant(self, context, role_id, user_id, domain_id=None, project_id=None): """Revokes a role from a user on either a domain or project.""" - self.assert_admin(context) - self._require_domain_or_project(domain_id, project_id) self.identity_api.delete_grant( diff --git a/keystone/policy/core.py b/keystone/policy/core.py index 36b982c8be..2e5676fc2b 100644 --- a/keystone/policy/core.py +++ b/keystone/policy/core.py @@ -105,9 +105,8 @@ class Driver(object): class PolicyControllerV3(controller.V3Controller): + @controller.protected def create_policy(self, context, policy): - self.assert_admin(context) - ref = self._assign_unique_id(self._normalize_dict(policy)) self._require_attribute(ref, 'blob') self._require_attribute(ref, 'type') @@ -115,22 +114,22 @@ class PolicyControllerV3(controller.V3Controller): ref = self.policy_api.create_policy(context, ref['id'], ref) return {'policy': ref} + @controller.protected def list_policies(self, context): - self.assert_admin(context) refs = self.policy_api.list_policies(context) refs = self._filter_by_attribute(context, refs, 'type') return {'policies': self._paginate(context, refs)} + @controller.protected def get_policy(self, context, policy_id): - self.assert_admin(context) ref = self.policy_api.get_policy(context, policy_id) return {'policy': ref} + @controller.protected def update_policy(self, context, policy_id, policy): - self.assert_admin(context) ref = self.policy_api.update_policy(context, policy_id, policy) return {'policy': ref} + @controller.protected def delete_policy(self, context, policy_id): - self.assert_admin(context) return self.policy_api.delete_policy(context, policy_id)