diff --git a/config.yml.j2 b/config.yml.j2 index 8052c7bbc3517bf85cf076024562e93e36bf92d9..715f101bbbfb174c465f50f99088bc39b8255332 100644 --- a/config.yml.j2 +++ b/config.yml.j2 @@ -1,4 +1,4 @@ -# Global configuration parameters +# Configuration of SOCTools user management web GUI # Hostname of the SOCtools server soctoolsproxy: "{{soctoolsproxy}}" @@ -15,31 +15,31 @@ smtp: password: "{{smtp.password}}" # Path to the SOCtools CA certificate -ca_cert_file: "../secrets/CA/ca.crt" +ca_cert_file: "{{playbook_dir}}/secrets/CA/ca.crt" # Path to "easyrsa" executable and working directory -easyrsa_bin: "../roles/ca/files/easyrsa/easyrsa" -easyrsa_ca_dir: "../secrets/CA" +easyrsa_bin: "{{playbook_dir}}/roles/ca/files/easyrsa/easyrsa" +easyrsa_ca_dir: "{{playbook_dir}}/secrets/CA" # File to store tokens allowing users to download certificates -token_file: "../secrets/cert_access_tokens" +token_file: "{{playbook_dir}}/secrets/cert_access_tokens" # Credentials of the special user for account management # Cert and key should be in .pem format, unencrypted mgmt_user_name: "soctools-user-mgmt" -mgmt_user_cert_path: "../secrets/CA/issued/soctools-user-mgmt.crt" -mgmt_user_key_path: "../secrets/CA/private/soctools-user-mgmt.key" +mgmt_user_cert_path: "{{playbook_dir}}/secrets/CA/issued/soctools-user-mgmt.crt" +mgmt_user_key_path: "{{playbook_dir}}/secrets/CA/private/soctools-user-mgmt.key" user_mgmt_base_url: "https://{{soctoolsproxy}}:5443" keycloak_base_url: "https://{{soctoolsproxy}}:12443" keycloak_users_url: "https://{{soctoolsproxy}}:12443/auth/admin/realms/{{openid_realm}}/users" -keycloak_admin_password: "{{lookup('password', '{{playbook_dir}}/secrets/passwords/keycloak_admin')}}" +keycloak_admin_password_file: "{{playbook_dir}}/secrets/passwords/keycloak_admin" -misp_api_key: "{{lookup('password', '{{playbook_dir}}/secrets/tokens/misp')}}" +misp_api_key_file: "{{playbook_dir}}/secrets/tokens/misp" -thehive_api_key: "{{lookup('password', '{{playbook_dir}}/secrets/tokens/thehive_secret_key')}}" +thehive_api_key_file: "{{playbook_dir}}/secrets/tokens/thehive_secret_key" thehive_org_name: "{{org_name}}" -cortex_api_key: "{{lookup('password', '{{playbook_dir}}/secrets/tokens/cortex_secret_key')}}" +cortex_api_key_file: "{{playbook_dir}}/secrets/tokens/cortex_secret_key" cortex_org_name: "{{org_name}}" diff --git a/cortex.py b/cortex.py index 947721cbd01958ac794ec6c61115eb167a1dcd79..91bfa7573e7326962023187a9ca0f9a69c9ee9f0 100644 --- a/cortex.py +++ b/cortex.py @@ -132,10 +132,14 @@ def cortex_delete_user(login: str) -> None: # Auxiliary functions def _send_request(method:str, url:str, data:Optional[dict]=None): + try: + api_key = open(config['cortex_api_key_file'], 'r').read(100).strip() # read max 100 B, the key should never be so long + except IOError as e: + raise IOError(f"Can't load Cortex API key from file: {e}") return getattr(requests, method)( url, headers={ - "Authorization": "Bearer " + config['cortex_api_key'], + "Authorization": "Bearer " + api_key, }, verify=config['ca_cert_file'], json=data diff --git a/main.py b/main.py index 1cac4824b9d24ed8f8b1b30824cd1080c11b581e..772b4a6cd9befc874de79a25982ff6cbb5d5a430 100644 --- a/main.py +++ b/main.py @@ -27,8 +27,9 @@ try: # 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',): + 'keycloak_base_url', 'keycloak_users_url', 'keycloak_admin_password_file', + 'misp_api_key_file', 'thehive_api_key_file', 'thehive_org_name', + 'cortex_api_key_file', 'cortex_org_name',): 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 @@ -41,7 +42,7 @@ try: if not isinstance(config.get('smtp'), dict): 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')): + if not config['smtp'].get('host') or 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.') @@ -61,10 +62,10 @@ 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"keycloak_admin_password_file={config['keycloak_admin_password_file']}\n" + f"misp_api_key_file={config['misp_api_key_file']}\n" + f"thehive_api_key_file={config['thehive_api_key_file']}\n" + f"cortex_api_key_file={config['cortex_api_key_file']}\n" f"thehive_org_name={config['thehive_org_name']}\n") @@ -157,11 +158,16 @@ def kc_get_token() -> str: Return the token or raise KeycloakError """ + try: + passwd = open(config['keycloak_admin_password_file'], 'r').read(100).strip() # read max 100 B, the key should never be so long + except IOError as e: + raise IOError(f"Can't load Keycloak admin password from file: {e}") + url = config['keycloak_base_url'] + "/auth/realms/master/protocol/openid-connect/token" data = { "client_id": "admin-cli", "username": "admin", - "password": config['keycloak_admin_password'], + "password": passwd, "grant_type": "password" } try: @@ -303,14 +309,14 @@ 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": + if invalid_smtp is not None: 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() - except KeycloakError as e: + except (KeycloakError, IOError) as e: flash(f"ERROR: {e}", "error") users = [] # Mark "internal" users @@ -337,7 +343,7 @@ def main(): # Load MISP users try: misp_users = misp_get_users() - except MISPError as e: + except (MISPError, IOError) as e: flash(f"ERROR: {e}", "error") misp_users = [] # Mark "internal" users @@ -351,7 +357,7 @@ def main(): # Load The Hive users try: thehive_users = thehive_get_users() - except TheHiveError as e: + except (TheHiveError, IOError) as e: flash(f"ERROR: {e}", "error") thehive_users = [] # Mark "internal" users @@ -365,7 +371,7 @@ def main(): # Load Cortex users try: cortex_users = cortex_get_users() - except CortexError as e: + except (CortexError, IOError) as e: flash(f"ERROR: {e}", "error") cortex_users = [] # List of usernames only (for easier cross-check with Keycloak users) @@ -458,7 +464,7 @@ def edit_user(username: str): """Edit existing user. On GET show user details, on POST update user params with new values.""" try: user = kc_get_user_by_name(username) - except KeycloakError as e: + except (KeycloakError, IOError) as e: flash(f'ERROR: {e}', "error") return redirect_to_main_page() keycloak_id = user.kcid @@ -478,7 +484,7 @@ def edit_user(username: str): try: kc_update_user(new_user) flash(f'User "{new_user.username}" successfully updated in Keycloak.', "success") - except KeycloakError as e: + except (KeycloakError, IOError) as e: flash(f'Error when updating user in Keycloak: {e}', "error") # NiFi @@ -543,7 +549,7 @@ def delete_user(username: str): """Delete user given by username and redirect back to main page""" try: user_spec = kc_get_user_by_name(username) - except KeycloakError as e: + except (KeycloakError, IOError) as e: flash(f"Error: Can't get user info from KeyCloak: {e}", "error") return redirect_to_main_page() @@ -557,7 +563,7 @@ def delete_user(username: str): try: kc_delete_user(user_spec.kcid) flash(f'User "{user_spec.username}" successfully deleted from KeyCloak.', "success") - except KeycloakError as e: + except (KeycloakError, IOError) as e: flash(f'Error when deleting user from KeyCloak: {e}', "error") # NiFi @@ -575,7 +581,7 @@ def delete_user(username: str): flash(f'User "{user_spec.email}" successfully deleted from MISP.', "success") except MISPUserNotFoundError: flash(f'User "{user_spec.email}" was not found in MISP, nothing has changed.', "warning") - except MISPError as e: + except (MISPError, IOError) as e: flash(f'Error when deleting user from MISP: {e}', "error") # The Hive @@ -584,7 +590,7 @@ def delete_user(username: str): flash(f'User "{user_spec.email}" successfully deleted from The Hive.', "success") except TheHiveUserNotFoundError: flash(f'Error when deleting user from The Hive: User with email "{user_spec.email}" not found', "error") - except TheHiveError as e: + except (TheHiveError, IOError) as e: flash(f'Error when deleting user from The Hive: {e}', "error") # Cortex @@ -593,7 +599,7 @@ def delete_user(username: str): flash(f'User "{user_spec.email}" marked as "locked" in Cortex.', "success") except CortexUserNotFoundError: flash(f'Error when trying to mark user as "locked" in Cortex: User with email "{user_spec.email}" not found', "error") - except TheHiveError as e: + except (TheHiveError, IOError) as e: flash(f'Error when trying to mark user as "locked" in Cortex: {e}', "error") return redirect_to_main_page() diff --git a/misp.py b/misp.py index c5fe5af3c56626c45e206c2b5f2c61c22a77fdea..e9c0198f9dc0881da50e32b2a69bfee52306d464 100644 --- a/misp.py +++ b/misp.py @@ -131,10 +131,14 @@ def misp_delete_user(user_email: str) -> None: # Auxiliary functions def _send_request(method:str, url:str, data:Optional[dict]=None): + try: + api_key = open(config['misp_api_key_file'], 'r').read(100).strip() # read max 100 B, the key should never be so long + except IOError as e: + raise IOError(f"Can't load MISP API key from file: {e}") return getattr(requests, method)( url, headers={ - "Authorization": config['misp_api_key'], + "Authorization": api_key, "Accept": "application/json", }, verify=config['ca_cert_file'], diff --git a/thehive.py b/thehive.py index 2f32d2eb91d1ec3c1e5d7a4efab617c38b6d7cfe..9ffc4f1dcccd2715835a4780398eb3a949479457 100644 --- a/thehive.py +++ b/thehive.py @@ -135,10 +135,14 @@ def thehive_delete_user(login: str) -> None: # Auxiliary functions def _send_request(method:str, url:str, data:Optional[dict]=None): + try: + api_key = open(config['thehive_api_key_file'], 'r').read(100).strip() # read max 100 B, the key should never be so long + except IOError as e: + raise IOError(f"Can't load The Hive API key from file: {e}") return getattr(requests, method)( url, headers={ - "Authorization": "Bearer " + config['thehive_api_key'], + "Authorization": "Bearer " + api_key, }, verify=config['ca_cert_file'], json=data