mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 01:19:11 +00:00 
			
		
		
		
	Add preliminary support for logging
Current logging mechanism makes use of Python's logging module. MySQL logging handler inserts log entries to MySQL server and another logging handler is used to stream events to web interface via nginx streaming push.
This commit is contained in:
		| @@ -35,7 +35,7 @@ class SessionResource(object): | ||||
|     @event_source | ||||
|     def on_get(self, req, resp): | ||||
|         return dict( | ||||
|             username=req.context.get("user")[0], | ||||
|             username=req.context.get("user"), | ||||
|             event_channel = config.PUSH_EVENT_SOURCE % config.PUSH_TOKEN, | ||||
|             autosign_subnets = config.AUTOSIGN_SUBNETS, | ||||
|             request_subnets = config.REQUEST_SUBNETS, | ||||
| @@ -78,6 +78,7 @@ def certidude_app(): | ||||
|     from .request import RequestListResource, RequestDetailResource | ||||
|     from .lease import LeaseResource | ||||
|     from .whois import WhoisResource | ||||
|     from .log import LogResource | ||||
|  | ||||
|     app = falcon.API() | ||||
|  | ||||
| @@ -89,10 +90,51 @@ def certidude_app(): | ||||
|     app.add_route("/api/signed/", SignedCertificateListResource()) | ||||
|     app.add_route("/api/request/{cn}/", RequestDetailResource()) | ||||
|     app.add_route("/api/request/", RequestListResource()) | ||||
|     app.add_route("/api/log/", LogResource()) | ||||
|     app.add_route("/api/", SessionResource()) | ||||
|  | ||||
|     # Gateway API calls, should this be moved to separate project? | ||||
|     app.add_route("/api/lease/", LeaseResource()) | ||||
|     app.add_route("/api/whois/", WhoisResource()) | ||||
|  | ||||
|     """ | ||||
|     Set up logging | ||||
|     """ | ||||
|  | ||||
|     from certidude import config | ||||
|     from certidude.mysqllog import MySQLLogHandler | ||||
|     from datetime import datetime | ||||
|     import logging | ||||
|     import socket | ||||
|     import json | ||||
|  | ||||
|  | ||||
|     class PushLogHandler(logging.Handler): | ||||
|         def emit(self, record): | ||||
|             from certidude.push import publish | ||||
|             print("EVENT HAPPENED:", record.created) | ||||
|             publish("log-entry", dict( | ||||
|                 created = datetime.fromtimestamp(record.created), | ||||
|                 message = record.msg % record.args, | ||||
|                 severity = record.levelname.lower())) | ||||
|  | ||||
|     sql_handler = MySQLLogHandler(config.DATABASE_POOL) | ||||
|     push_handler = PushLogHandler() | ||||
|  | ||||
|     for facility in "api", "cli": | ||||
|         logger = logging.getLogger(facility) | ||||
|         logger.setLevel(logging.DEBUG) | ||||
|         logger.addHandler(sql_handler) | ||||
|         logger.addHandler(push_handler) | ||||
|  | ||||
|  | ||||
|     logging.getLogger("cli").info("Started Certidude at %s", socket.getaddrinfo(socket.gethostname(), 0, flags=socket.AI_CANONNAME)[0][3]) | ||||
|  | ||||
|     import atexit | ||||
|  | ||||
|     def exit_handler(): | ||||
|         logging.getLogger("cli").info("Shutting down Certidude") | ||||
|  | ||||
|     atexit.register(exit_handler) | ||||
|  | ||||
|     return app | ||||
|   | ||||
							
								
								
									
										39
									
								
								certidude/api/log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								certidude/api/log.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
|  | ||||
| from certidude import config | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.decorators import serialize | ||||
|  | ||||
| class LogResource(object): | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_get(self, req, resp): | ||||
|         """ | ||||
|         Translate currently online client's IP-address to distinguished name | ||||
|         """ | ||||
|  | ||||
|         SQL_LOG_ENTRIES = """ | ||||
|             SELECT | ||||
|                 * | ||||
|             FROM | ||||
|                 log | ||||
|             ORDER BY created DESC | ||||
|         """ | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor(dictionary=True) | ||||
|         cursor.execute(SQL_LOG_ENTRIES) | ||||
|  | ||||
|         def g(): | ||||
|             for row in cursor: | ||||
|                 yield row | ||||
|             cursor.close() | ||||
|             conn.close() | ||||
|         return tuple(g()) | ||||
|  | ||||
| #        for acquired, released, identity in cursor: | ||||
| #            return { | ||||
| #                "acquired": datetime.utcfromtimestamp(acquired), | ||||
| #                "identity": parse_dn(bytes(identity)) | ||||
| #            } | ||||
| #        return None | ||||
|          | ||||
| @@ -1,6 +1,7 @@ | ||||
|  | ||||
| import click | ||||
| import falcon | ||||
| import logging | ||||
| import ipaddress | ||||
| import os | ||||
| from certidude import config, authority, helpers, push | ||||
| @@ -8,6 +9,8 @@ from certidude.auth import login_required, authorize_admin | ||||
| from certidude.decorators import serialize | ||||
| from certidude.wrappers import Request, Certificate | ||||
|  | ||||
| logger = logging.getLogger("api") | ||||
|  | ||||
| class RequestListResource(object): | ||||
|     @serialize | ||||
|     @authorize_admin | ||||
| @@ -27,13 +30,14 @@ class RequestListResource(object): | ||||
|                 if subnet.overlaps(remote_addr): | ||||
|                     break | ||||
|             else: | ||||
|                logger.warning("Attempted to submit signing request from non-whitelisted address %s", req.env["REMOTE_ADDR"]) | ||||
|                raise falcon.HTTPForbidden("Forbidden", "IP address %s not whitelisted" % remote_addr) | ||||
|  | ||||
|         if req.get_header("Content-Type") != "application/pkcs10": | ||||
|             raise falcon.HTTPUnsupportedMediaType( | ||||
|                 "This API call accepts only application/pkcs10 content type") | ||||
|  | ||||
|         body = req.stream.read(req.content_length) | ||||
|         body = req.stream.read(req.content_length).decode("ascii") | ||||
|         csr = Request(body) | ||||
|  | ||||
|         # Check if this request has been already signed and return corresponding certificte if it has been signed | ||||
| @@ -65,6 +69,7 @@ class RequestListResource(object): | ||||
|         try: | ||||
|             csr = authority.store_request(body) | ||||
|         except FileExistsError: | ||||
|             logger.warning("Rejected signing request with overlapping common name from %s", req.env["REMOTE_ADDR"]) | ||||
|             raise falcon.HTTPConflict( | ||||
|                 "CSR with such CN already exists", | ||||
|                 "Will not overwrite existing certificate signing request, explicitly delete CSR and try again") | ||||
| @@ -77,9 +82,11 @@ class RequestListResource(object): | ||||
|             click.echo("Redirecting to: %s"  % url) | ||||
|             resp.status = falcon.HTTP_SEE_OTHER | ||||
|             resp.set_header("Location", url) | ||||
|             logger.warning("Redirecting signing request from %s to %s", req.env["REMOTE_ADDR"], url) | ||||
|         else: | ||||
|             # Request was accepted, but not processed | ||||
|             resp.status = falcon.HTTP_202 | ||||
|             logger.info("Signing request from %s stored", req.env["REMOTE_ADDR"]) | ||||
|  | ||||
|  | ||||
| class RequestDetailResource(object): | ||||
| @@ -108,6 +115,7 @@ class RequestDetailResource(object): | ||||
|         resp.body = "Certificate successfully signed" | ||||
|         resp.status = falcon.HTTP_201 | ||||
|         resp.location = os.path.join(req.relative_uri, "..", "..", "signed", cn) | ||||
|         logger.info("Signing request %s signed by %s from %s", csr.common_name, req.context["user"], req.env["REMOTE_ADDR"]) | ||||
|  | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
| @@ -116,4 +124,5 @@ class RequestDetailResource(object): | ||||
|             authority.delete_request(cn) | ||||
|         except FileNotFoundError: | ||||
|             resp.body = "No certificate CN=%s found" % cn | ||||
|             logger.warning("User %s attempted to delete non-existant signing request %s from %s", req.context["user"], cn, req.env["REMOTE_ADDR"]) | ||||
|             raise falcon.HTTPNotFound() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user