From 73250a882d817d75df036ec97b1e4949718a425e Mon Sep 17 00:00:00 2001 From: dameyerdave Date: Thu, 28 Sep 2023 11:21:40 +0200 Subject: [PATCH] [SEC] enforces secure token to be transmitted to trigger an action --- .env.TEMPLATE | 8 ++++++- app/acint.py | 58 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/.env.TEMPLATE b/.env.TEMPLATE index a1713b7..94b67bb 100644 --- a/.env.TEMPLATE +++ b/.env.TEMPLATE @@ -2,4 +2,10 @@ ACINT_ALLOWED_ACTIONS= # The proxy path for the service, default: /acint -ACINT_PROXY_PATH= \ No newline at end of file +ACINT_PROXY_PATH= + +# The security token to be used to authenticate +ACINT_TOKEN= + +# The environment to use dev | prod, default: prod +ACINT_ENV= \ No newline at end of file diff --git a/app/acint.py b/app/acint.py index 668ec95..25d7d24 100755 --- a/app/acint.py +++ b/app/acint.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 -import cherrypy import os +import cherrypy +from uuid import uuid4 + DO_PATH = os.path.join(os.path.sep, "do") os.makedirs(DO_PATH, exist_ok=True) @@ -12,6 +14,24 @@ else: allowed_actions = os.environ.get("ACINT_ALLOWED_ACTIONS", ".deploy").split(",") proxy_path = os.environ.get("ACINT_PROXY_PATH", None) or "/" +secure_token = os.environ.get("ACINT_TOKEN", None) or uuid4().hex +environment = os.environ.get("ACINT_ENV", None) or "prod" + +env_config = { + "environments": { + "dev": { + "tools.log_headers.on": True, + }, + "prod": { + "engine.autoreload.on": False, + "checker.on": False, + "tools.log_headers.on": False, + "request.show_tracebacks": False, + "request.show_mismatched_params": False, + "log.screen": False, + }, + }, +} def handle_an_exception(): @@ -25,36 +45,46 @@ def handle_default(status=None, message=None, version=None, traceback=None): return f"{status}".encode("UTF-8") -@cherrypy.popargs("action") +@cherrypy.tools.register("before_finalize", priority=60) +def secureheaders(): + headers = cherrypy.response.headers + headers["X-Frame-Options"] = "DENY" + headers["X-XSS-Protection"] = "1; mode=block" + headers["Content-Security-Policy"] = "default-src 'self';" + + +@cherrypy.popargs("action", "") class AcInt(object): _cp_config = { - # handler for an unhandled exception "request.error_response": handle_an_exception, - # default handler for any other HTTP error "error_page.default": handle_default, } @cherrypy.expose - def index(self, action=None): - if action not in allowed_actions: + @cherrypy.tools.allow(methods=["POST"]) + @cherrypy.tools.json_in() + def index(self): + print("request", cherrypy.request.json) + data = cherrypy.request.json + if data["action"] not in allowed_actions: + raise cherrypy.HTTPError(403, "Forbidden") + if data["token"] != secure_token: raise cherrypy.HTTPError(403, "Forbidden") - os.close(os.open(os.path.join(DO_PATH, action), os.O_CREAT)) - return f"Triggerd action {action}." + + os.close(os.open(os.path.join(DO_PATH, data["action"]), os.O_CREAT)) + return f"Triggerd action {data['action']}." if __name__ == "__main__": print("Allowed actions:", allowed_actions) print("Proxy path:", proxy_path) + print("Token:", secure_token) + print("Environment:", environment) cherrypy.config.update( { "server.socket_host": "0.0.0.0", "server.socket_port": 80, - "engine.autoreload.on": False, - "checker.on": False, - "tools.log_headers.on": False, - "request.show_tracebacks": False, - "request.show_mismatched_params": False, - "log.screen": False, } ) + cherrypy.config.update(env_config["environments"][environment]) cherrypy.quickstart(AcInt(), proxy_path)