diff --git a/certificates.py b/certificates.py index 8b23e40bdefa416e574e71744c90196c31bb2280..d4b042f07c106daa6528e6e86f0860ee5f608d79 100644 --- a/certificates.py +++ b/certificates.py @@ -239,6 +239,10 @@ def send_token(user: 'UserAccount') -> Tuple[bool, Optional[str]]: # Print URL to console (for debugging or when email sending doesn't work) print(f"Certificate access URL for '{user.username}': {access_url}") + if not config['smtp']: + # SMTP not configured, show URL to the admin (as part of error message) instead + return False, f"Can't send email to the user. URL to download the certificate: {access_url}" + # Send the token via email name = f"{user.firstname} {user.lastname}".strip() or user.username try: diff --git a/main.py b/main.py index 9a5f2644c64793767f179e8b35c1fd8c9f309659..1cac4824b9d24ed8f8b1b30824cd1080c11b581e 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import os.path import sys from datetime import datetime, timezone from typing import List, Dict, Optional, Union, Tuple @@ -17,44 +18,54 @@ from config import config # module serving as a global namespace for configurati # Load and check configuration - paths, URLs, api keys, etc. invalid_configuration_error = None +invalid_smtp = None try: config.update(yaml.safe_load(open("config.yml", "r"))) errors = [] + smtp_errors = [] # Check presence of all mandatory configuration parameters for attr in ('soctoolsproxy', 'ca_cert_file', 'easyrsa_bin', 'easyrsa_ca_dir', 'token_file', 'mgmt_user_name', 'mgmt_user_cert_path', 'mgmt_user_key_path', 'user_mgmt_base_url', 'keycloak_base_url', 'keycloak_users_url', 'keycloak_admin_password', 'misp_api_key', 'thehive_api_key', 'thehive_org_name', 'cortex_api_key', 'cortex_org_name',): - if not config[attr]: + if attr not in config or not config[attr]: errors.append(f'Missing mandatory parameter "{attr}".') + config[attr] = None # to avoid attr-access errors in some imported modules # Check soctoolsproxy - if not re.match('[a-zA-Z0-9.:-]+', config['soctoolsproxy']): + if not re.fullmatch('[a-zA-Z0-9.:-]+', config['soctoolsproxy'] or 'a'): errors.append("'soctoolsproxy' is not a valid hostname or IP address.") # Check smtp params, set defaults if not isinstance(config.get('smtp'), dict): - errors.append('Missing mandatory parameter "smtp" or it is not dict.') - if not re.match('[a-zA-Z0-9.:-]+', config['smtp'].get('host')): - errors.append('Missing mandatory parameter "smtp.host" or is not a valid hostname or IP address.') - if not config['smtp'].get('sender'): - errors.append('Missing mandatory parameter "smtp.sender".') - if not config['smtp'].get('port'): - config['smtp']['port'] = 465 + smtp_errors.append('Parameter "smtp" is missing or it is not a dict.') + else: + if not re.match('[a-zA-Z0-9.:-]+', config['smtp'].get('host')): + smtp_errors.append('Parameter "smtp.host" is missing or it is not a valid hostname or IP address.') + if not config['smtp'].get('sender'): + smtp_errors.append('Parameter "smtp.sender" is missing.') + if not config['smtp'].get('port'): + config['smtp']['port'] = 465 if errors: - invalid_configuration_error = "Configuration error(s):\n" + "\n".join(errors) + invalid_configuration_error = "Configuration error(s):\n" + "\n".join(errors + smtp_errors) + if smtp_errors: + invalid_smtp = f"WARNING: SMTP not properly configured, it is not possible to send emails ({' '.join(smtp_errors)})" + config['smtp'] = None except Exception as e: invalid_configuration_error = f"Can't load configuration: {e}" -print(f"Config loaded:\nsoctoolsproxy={config['soctoolsproxy']}\nkeycloak_base_url={config['keycloak_base_url']}\n" - f"keycloak_admin_password={config['keycloak_admin_password'][:3]}...{config['keycloak_admin_password'][-4:]}\n" - f"misp_api_key={config['misp_api_key'][:3]}...{config['misp_api_key'][-4:]}\n" - f"thehive_api_key={config['thehive_api_key'][:3]}...{config['thehive_api_key'][-4:]}\n" - f"cortex_api_key={config['cortex_api_key'][:3]}...{config['cortex_api_key'][-4:]}\n" - f"thehive_org_name={config['thehive_org_name']}\n") +if invalid_configuration_error: + print("ERROR: " + invalid_configuration_error) +else: + print(f"Config loaded:\nsoctoolsproxy={config['soctoolsproxy']}\nkeycloak_base_url={config['keycloak_base_url']}\n" + f"keycloak_admin_password={config['keycloak_admin_password'][:3]}...{config['keycloak_admin_password'][-4:]}\n" + f"misp_api_key={config['misp_api_key'][:3]}...{config['misp_api_key'][-4:]}\n" + f"thehive_api_key={config['thehive_api_key'][:3]}...{config['thehive_api_key'][-4:]}\n" + f"cortex_api_key={config['cortex_api_key'][:3]}...{config['cortex_api_key'][-4:]}\n" + f"thehive_org_name={config['thehive_org_name']}\n") import certificates @@ -69,10 +80,12 @@ app.secret_key = "ASDF1234 - CHANGE ME!" # TODO: set dynamically to something ra # If there is config error, report it instead of trying to render a page @app.before_request def config_check(): + # in case of fatal config error, return error 500 with an error message if invalid_configuration_error is not None: return make_response(f"500 Internal Server Error\n{'='*25}\n\n{invalid_configuration_error}\n\n" - "Fix the configuration file and restart the user-mgmt-ui service " - "('systemctl restart user-mgmt-ui')", 500, {'Content-Type': 'text/plain'}) + f"Fix the configuration file ('{os.path.abspath('config.yml')}') and restart" + " the user-mgmt-ui service ('systemctl restart user-mgmt-ui')", + 500, {'Content-Type': 'text/plain'}) def redirect_to_main_page(): @@ -289,6 +302,11 @@ class AddUserForm(FlaskForm): @app.route("/") def main(): + # In case of invalid SMTP configuration, show warning message + if invalid_smtp is not None and request.endpoint != "export_certificate": + flash(invalid_smtp + f" To fix it, edit the configuration file ('{os.path.abspath('config.yml')}') and restart" + " the user-mgmt-ui service ('systemctl restart user-mgmt-ui').", "warning") + # Load existing users from Keycloak try: users = kc_get_users() @@ -642,15 +660,13 @@ def send_token(username: str): # TODO: -# - revoke and delete certificate when user is deleted -# - send tokens via email # - authentication/authorization to this GUI -@app.route("/test_cert/<func>") -def test_cert_endpoint(func): - # run any function from "certificates" module - result = str(getattr(certificates, func)(**request.args)) - return make_response(result) +# @app.route("/test_cert/<func>") +# def test_cert_endpoint(func): +# # run any function from "certificates" module +# result = str(getattr(certificates, func)(**request.args)) +# return make_response(result) # When the script is run directly, run the application on a local development server. # Optionally pass two parameters, 'host' (IP to listen on) and 'port',