diff --git a/anchor/audit/__init__.py b/anchor/audit/__init__.py index 5ae2756..da3117f 100644 --- a/anchor/audit/__init__.py +++ b/anchor/audit/__init__.py @@ -12,6 +12,7 @@ # under the License. import logging +import uuid from anchor import jsonloader @@ -27,6 +28,8 @@ logger = logging.getLogger(__name__) target = None notifier = None +ANCHOR_UUID_NS = uuid.UUID('0ff9c8c5-f57e-47aa-bd3d-5407eb907c74') + def _emit_event(event_type, payload): if not payload.is_valid(): @@ -45,13 +48,25 @@ def _event_defaults(result): } -def _user_resource(username): +def _user_resource(username, result): + if result: + res_id = uuid.uuid5(ANCHOR_UUID_NS, result.username) + user = result.username + else: + if username: + res_id = uuid.uuid5(ANCHOR_UUID_NS, username.encode('utf-8', + 'replace')) + user = username + else: + # Authentication was a failure, but there was no username + # provided either. This can happen with failed token authentication + # for example. + res_id = uuid.uuid4() + user = None return resource.Resource( - # TODO(stan): generate id from username if it's known - # this should also get username from keystone tokens to work correctly - id=identifier.generate_uuid(), + id=str(res_id), typeURI=cadftaxonomy.ACCOUNT_USER, - name=username) + name=user) def _auth_resource(ra_name): @@ -80,9 +95,10 @@ def _certificate_resource(fingerprint): def emit_auth_event(ra_name, username, result): - params = _event_defaults(result) + success = result is not None + params = _event_defaults(success) params['action'] = 'authenticate' - params['initiator'] = _user_resource(username) + params['initiator'] = _user_resource(username, result) auth_res = _auth_resource(ra_name) params['observer'] = auth_res params['target'] = auth_res @@ -92,7 +108,7 @@ def emit_auth_event(ra_name, username, result): def emit_signing_event(ra_name, username, result, fingerprint=None): params = _event_defaults(result) params['action'] = 'evaluate' - params['initiator'] = _user_resource(username) + params['initiator'] = _user_resource(username, result) params['observer'] = _policy_resource(ra_name) params['target'] = _certificate_resource(fingerprint) # add when pycadf merges event names diff --git a/anchor/auth/keystone.py b/anchor/auth/keystone.py index 0c21c42..342af0b 100644 --- a/anchor/auth/keystone.py +++ b/anchor/auth/keystone.py @@ -41,11 +41,15 @@ def login(_, token): try: res = req.json() - user = res['token']['user']['name'] + user = res['token']['user'] + user_name = user['name'] + user_id = user['id'] + project_id = res['token']['project']['id'] roles = [role['name'] for role in res['token']['roles']] except Exception: logger.exception("Keystone response was not in the expected format") return None - return results.AuthDetails(username=user, groups=roles) + return results.AuthDetails(username=user_name, groups=roles, + user_id=user_id, project_id=project_id) diff --git a/anchor/auth/results.py b/anchor/auth/results.py index cd3891b..eefecfd 100644 --- a/anchor/auth/results.py +++ b/anchor/auth/results.py @@ -13,7 +13,17 @@ from __future__ import absolute_import -import collections +class AuthDetails(object): + def __init__(self, username=None, groups=None, user_id=None, + project_id=None): + self.username = username + self.groups = groups or [] + self.user_id = user_id + self.project_id = project_id -AuthDetails = collections.namedtuple('AuthDetails', ['username', 'groups']) + def __eq__(self, other): + return (self.username == other.username and + self.groups == other.groups and + self.user_id == other.user_id and + self.project_id == other.project_id) diff --git a/anchor/controllers/__init__.py b/anchor/controllers/__init__.py index 695aaea..a216b09 100644 --- a/anchor/controllers/__init__.py +++ b/anchor/controllers/__init__.py @@ -53,10 +53,10 @@ class SignInstanceController(GenericInstanceController): pecan.request.POST.get('user'), pecan.request.POST.get('secret')) audit.emit_auth_event(ra_name, pecan.request.POST.get('user'), - True) + auth_result) except http_status.HTTPUnauthorized: audit.emit_auth_event(ra_name, pecan.request.POST.get('user'), - False) + None) raise try: @@ -68,10 +68,10 @@ class SignInstanceController(GenericInstanceController): cert, fingerprint = certificate_ops.dispatch_sign(ra_name, csr) audit.emit_signing_event(ra_name, pecan.request.POST.get('user'), - True, fingerprint=fingerprint) + auth_result, fingerprint=fingerprint) except Exception: audit.emit_signing_event(ra_name, pecan.request.POST.get('user'), - False) + auth_result) raise return cert diff --git a/tests/auth/test_keystone.py b/tests/auth/test_keystone.py index 133c450..f9b8aa0 100644 --- a/tests/auth/test_keystone.py +++ b/tests/auth/test_keystone.py @@ -104,8 +104,11 @@ class AuthKeystoneTests(unittest.TestCase): self.user = self.json_response['token']['user']['name'] self.roles = [role['name'] for role in self.json_response['token']['roles']] + self.user_id = self.json_response['token']['user']['id'] + self.project_id = self.json_response['token']['project']['id'] self.expected = results.AuthDetails( - username=self.user, groups=self.roles) + username=self.user, groups=self.roles, + user_id=self.user_id, project_id=self.project_id) self.keystone_url = self.data['auth'][ 'keystone']['url'] + '/v3/auth/tokens'