1
0
mirror of https://github.com/gryf/openstack.git synced 2025-12-17 11:30:24 +01:00

Publish keystone federation with athenz

This commit is contained in:
Arun S A G
2018-10-09 14:52:50 -07:00
parent 64de20915c
commit 425d83e3d2
10 changed files with 813 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
# Copyright 2018, Oath Inc
# Licensed under the terms of the Apache 2.0 license. See LICENSE file for terms.
import ast
import keystone.conf
import six
import sys
import uuid
from keystone.auth.plugins import base
from keystone.common import dependency
from keystone.common import driver_hints
from keystone import exception
from keystone.i18n import _
from oslo_log import log
from yahoo.contrib.ocata_openstack_yahoo_plugins.keystone.auth.plugins.athenz_token import AthenzToken # noqa
from yahoo.contrib.ocata_openstack_yahoo_plugins.keystone.auth.plugins.athenz_token import is_athenz_role_token # noqa
LOG = log.getLogger(__name__)
KEYSTONE_CONF = keystone.conf.CONF
METHOD_NAME = 'athenz_token'
class AthenzUserAuthInfo(object):
@classmethod
def create(cls, auth_payload, method_name):
user_auth_info = cls()
user_auth_info._validate_and_normalize_auth_data(auth_payload)
user_auth_info.METHOD_NAME = method_name
return user_auth_info
def __init__(self):
self.user_name = None
self.athenz_token = None
self.domain_id = None
self.domain_name = None
self.project_name = None
def _validate_and_normalize_auth_data(self, auth_payload):
if 'user' not in auth_payload:
raise exception.ValidationError(attribute='user',
target=self.METHOD_NAME)
user_info = auth_payload['user']
user_id = user_info.get('id')
self.user_name = user_info.get('name')
self.project_name = user_info.get('project_name')
if not user_id and not self.user_name:
raise exception.ValidationError(attribute='id or name',
target='user')
if not self.project_name:
raise exception.ValidationError(attribute='project name',
target='user')
if self.user_name:
if 'domain' not in user_info:
raise exception.ValidationError(attribute='domain',
target='user')
self.domain_id = user_info['domain'].get('id')
self.domain_name = user_info['domain'].get('name')
if not self.domain_id and not self.domain_name:
raise exception.ValidationError(attribute='domain id or name',
target='user')
if 'athenz_token' not in user_info:
LOG.error("athenz_token not found in user object: %s", user_info)
raise exception.ValidationError(attribute='athenz_token',
target='user')
athenz_token = ast.literal_eval(user_info['athenz_token'])['token']
self.athenz_token = athenz_token
@dependency.requires('identity_api', 'resource_api', 'role_api',
'assignment_api')
class AthenzAuthPlugin(base.AuthMethodHandler):
def generate_consistent_id(self, string):
"""Given string generate a consistent hash"""
return uuid.uuid3(uuid.NAMESPACE_OID, str(string)).hex
def _lookup_domain(self, domain_id):
try:
self.resource_api.assert_domain_enabled(domain_id)
except exception.DomainNotFound as e:
LOG.error("Domain not found: %s", domain_id)
LOG.warning(six.text_type(e))
raise exception.Unauthorized(e)
except AssertionError as e:
LOG.error("Domain is not enabled: %s", domain_id)
log.warning(six.text_type(e))
six.reraise(exception.Unauthorized, exception.Unauthorized(e),
sys.exc_info()[2])
def _create_project(self, request, tenant_ref):
try:
return self.resource_api.create_project(tenant_ref['id'],
tenant_ref,
request.audit_initiator)
except (exception.DomainNotFound, exception.ProjectNotFound) as e:
raise exception.ValidationError(e)
def _lookup_and_create_project(self, request, project_name, domain_id):
try:
return self.resource_api.get_project_by_name(project_name,
domain_id)
except exception.ProjectNotFound:
project_ref = {'id': self.generate_consistent_id(project_name),
'name': project_name,
'enabled': True,
'domain_id': domain_id,
'is_domain': False,
'parent_id': domain_id,
'description': 'Project created by athenz plugin',
}
return self._create_project(request, project_ref)
def _lookup_and_create_user(self, request, domain_id, uname):
try:
return self.identity_api.get_user_by_name(uname, domain_id)
except exception.UserNotFound:
user_ref = {'id': self.generate_consistent_id(uname),
'name': uname,
'enabled': True,
'domain_id': domain_id,
'description': 'User created by athenz plugin'
}
return self.identity_api.create_user(user_ref,
request.audit_initiator)
def _lookup_and_create_role(self, request, role_name, domain_id):
hints = driver_hints.Hints()
hints.add_filter("name", role_name, case_sensitive=True)
found_roles = self.role_api.list_roles(hints)
LOG.info("Found roles:", found_roles)
if not found_roles:
# Create the role
role_id = self.generate_consistent_id(role_name)
role_ref = {'id': role_id,
'name': role_name
}
return self.role_api.create_role(role_ref['id'],
role_ref,
initiator=request.audit_initiator)
elif len(found_roles) == 1:
return self.role_api.get_role(found_roles[0]['id'])
else:
raise exception.AmbiguityError(resource='role',
name=role_name)
def _create_project_and_assign_roles(self,
request,
atoken,
requested_project_name,
user_ref, domain_id):
""" If requested project has a role in athenz, then create the
requested project and role if necessary. Assign said role to user
on the created project
request: The request object
atoken: Athenz token object
requested_project_name: project_name from the request (OS_PROJECTNAME)
user_ref: User object
domain_id: ID of the domain
"""
created = False
for role, projects in atoken.projects.items():
# check requested_project_name is part of one of the roles
if requested_project_name.lower() in projects:
if not created:
project_ref = self._lookup_and_create_project(request,
requested_project_name, # noqa
domain_id)
created = True
role_ref = self._lookup_and_create_role(request,
role,
domain_id)
# Do grants
self.assignment_api.create_grant(role_ref['id'],
user_id=user_ref['id'],
project_id=project_ref['id'])
def authenticate(self, request, auth_payload):
"""Autenticate the athenz token
Validate the athenz token create projects/users if needed
"""
response_data = {}
user_info = AthenzUserAuthInfo.create(auth_payload, METHOD_NAME)
if not is_athenz_role_token(user_info.athenz_token):
LOG.error("Not a valid athenz role token")
raise exception.Unauthorized(_('Not a valid athenz role token'))
atoken = AthenzToken(user_info.athenz_token)
if atoken.validate(user_info.user_name) and atoken.user:
# We got a valid athenz token
LOG.debug("Athenz token is valid")
# Get domain ID from name if it is not part of the request
domain_id = user_info.domain_id
if not user_info.domain_id:
domain_ref = self.resource_api.get_domain_by_name(
user_info.domain_name)
domain_id = domain_ref['id']
# Assert domain isn't disabled
self._lookup_domain(domain_id)
# Create the user specified in athenz token if necessary
user_ref = self._lookup_and_create_user(request,
domain_id,
atoken.user)
# Create keystone project specified by the user in the request
# (user_info.project_name) after validating athenz role token has
# the same project name in its roles.
self._create_project_and_assign_roles(request,
atoken,
user_info.project_name,
user_ref,
domain_id)
response_data['user_id'] = user_ref['id']
response_data['athenz_token'] = user_info.athenz_token
return base.AuthHandlerResponse(status=True, response_body=None,
response_data=response_data)
msg = _('Invalid athenz token')
raise exception.Unauthorized(msg)