diff --git a/anchor/audit/__init__.py b/anchor/audit/__init__.py new file mode 100644 index 0000000..275b1ab --- /dev/null +++ b/anchor/audit/__init__.py @@ -0,0 +1,91 @@ +# +# 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 logging + +from pycadf import cadftaxonomy +from pycadf import event +from pycadf import identifier +from pycadf import resource + + +logger = logging.getLogger(__name__) + + +def _emit_event(ev): + # no actual implementation yet + if not ev.is_valid(): + logger.error("created invalid audit event: %s", ev) + + +def _event_defaults(result): + # eventType, id, eventTime are filled in automatically by pyCADF + return { + 'outcome': (cadftaxonomy.OUTCOME_SUCCESS if result else + cadftaxonomy.OUTCOME_FAILURE), + } + + +def _user_resource(username): + 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(), + typeURI=cadftaxonomy.ACCOUNT_USER, + name=username) + + +def _auth_resource(ra_name): + return resource.Resource( + id='anchor://authentication', + typeURI=cadftaxonomy.SERVICE_SECURITY, + domain=ra_name) + + +def _policy_resource(ra_name): + return resource.Resource( + id='anchor://certificates/policy', + typeURI=cadftaxonomy.SECURITY_POLICY, + domain=ra_name) + + +def _certificate_resource(fingerprint): + if fingerprint is None: + res_id = identifier.generate_uuid() + else: + res_id = "certificate:%s" % (fingerprint,) + return resource.Resource( + id=res_id, + typeURI=cadftaxonomy.SECURITY_KEY, + ) + + +def emit_auth_event(ra_name, username, result): + params = _event_defaults(result) + params['action'] = 'authenticate' + params['initiator'] = _user_resource(username) + auth_res = _auth_resource(ra_name) + params['observer'] = auth_res + params['target'] = auth_res + _emit_event(event.Event(**params)) + + +def emit_signing_event(ra_name, username, result, fingerprint=None): + params = _event_defaults(result) + params['action'] = 'evaluate' + params['initiator'] = _user_resource(username) + params['observer'] = _policy_resource(ra_name) + params['target'] = _certificate_resource(fingerprint) + # add when pycadf merges event names + # params['name'] = "certificate signing" + _emit_event(event.Event(**params)) diff --git a/anchor/certificate_ops.py b/anchor/certificate_ops.py index a33a2da..a229267 100644 --- a/anchor/certificate_ops.py +++ b/anchor/certificate_ops.py @@ -123,8 +123,8 @@ def dispatch_sign(ra_name, csr): logger.exception("Failed to sign the certificate") pecan.abort(500, "certificate signing error") + fingerprint = certificate_fingerprint(cert_pem, 'sha256') if ca_conf.get('output_path') is not None: - fingerprint = certificate_fingerprint(cert_pem, 'sha256') path = os.path.join( ca_conf['output_path'], '%s.crt' % fingerprint) @@ -134,7 +134,7 @@ def dispatch_sign(ra_name, csr): with open(path, "w") as f: f.write(cert_pem) - return cert_pem + return cert_pem, fingerprint def _run_fixup(name, body, args): diff --git a/anchor/controllers/__init__.py b/anchor/controllers/__init__.py index 17da827..695aaea 100644 --- a/anchor/controllers/__init__.py +++ b/anchor/controllers/__init__.py @@ -15,7 +15,9 @@ import logging import pecan from pecan import rest +from webob import exc as http_status +from anchor import audit from anchor import auth from anchor import certificate_ops from anchor import jsonloader @@ -46,15 +48,32 @@ class SignInstanceController(GenericInstanceController): logger.debug("processing signing request in registration authority %s", ra_name) - auth_result = auth.validate(ra_name, - pecan.request.POST.get('user'), - pecan.request.POST.get('secret')) - csr = certificate_ops.parse_csr(pecan.request.POST.get('csr'), - pecan.request.POST.get('encoding')) - certificate_ops.validate_csr(ra_name, auth_result, csr, pecan.request) - csr = certificate_ops.fixup_csr(ra_name, csr, pecan.request) + try: + auth_result = auth.validate(ra_name, + pecan.request.POST.get('user'), + pecan.request.POST.get('secret')) + audit.emit_auth_event(ra_name, pecan.request.POST.get('user'), + True) + except http_status.HTTPUnauthorized: + audit.emit_auth_event(ra_name, pecan.request.POST.get('user'), + False) + raise - return certificate_ops.dispatch_sign(ra_name, csr) + try: + csr = certificate_ops.parse_csr(pecan.request.POST.get('csr'), + pecan.request.POST.get('encoding')) + certificate_ops.validate_csr(ra_name, auth_result, csr, + pecan.request) + csr = certificate_ops.fixup_csr(ra_name, csr, pecan.request) + + cert, fingerprint = certificate_ops.dispatch_sign(ra_name, csr) + audit.emit_signing_event(ra_name, pecan.request.POST.get('user'), + True, fingerprint=fingerprint) + except Exception: + audit.emit_signing_event(ra_name, pecan.request.POST.get('user'), + False) + raise + return cert class CAInstanceController(GenericInstanceController): diff --git a/requirements.txt b/requirements.txt index 224d45d..955554f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ netaddr!=0.7.16,>=0.7.12 ldap3>=0.9.8.2 # LGPLv3 requests!=2.8.0,>=2.5.2 stevedore>=1.5.0 # Apache-2.0 +pycadf>=1.1.0