def upload_xml(xml_file, path): """ Upload XML files (rules, decoders and ossec.conf) :param xml_file: content of the XML file :param path: Destination of the new XML file :return: Confirmation message """ # Path of temporary files for parsing xml input tmp_file_path = '{}/tmp/api_tmp_file_{}_{}.xml'.format( common.ossec_path, time.time(), random.randint(0, 1000)) try: with open(tmp_file_path, 'w') as tmp_file: final_xml = prettify_xml(xml_file) tmp_file.write(final_xml) chmod(tmp_file_path, 0o660) except IOError: raise WazuhInternalError(1005) # Move temporary file to group folder try: new_conf_path = join(common.ossec_path, path) safe_move(tmp_file_path, new_conf_path, permissions=0o660) except Error: raise WazuhInternalError(1016) return WazuhResult({'message': 'File was successfully updated'})
def upload_list(list_file, path): """ Updates CDB lists :param list_file: content of the list :param path: Destination of the new list file :return: Confirmation message. """ # path of temporary file tmp_file_path = '{}/tmp/api_tmp_file_{}_{}.txt'.format(common.ossec_path, time.time(), random.randint(0, 1000)) try: # create temporary file with open(tmp_file_path, 'w') as tmp_file: # write json in tmp_file_path for element in list_file.splitlines(): # skip empty lines if not element: continue tmp_file.write(element.strip() + '\n') chmod(tmp_file_path, 0o640) except IOError: raise WazuhInternalError(1005) # validate CDB list if not validate_cdb_list(tmp_file_path): raise WazuhError(1800) # move temporary file to group folder try: new_conf_path = join(common.ossec_path, path) safe_move(tmp_file_path, new_conf_path, permissions=0o660) except Error: raise WazuhInternalError(1016) return WazuhResult({'message': 'File updated successfully'})
def upload_xml(xml_file, path): """ Upload XML files (rules and decoders) :param xml_file: content of the XML file :param path: Destination of the new XML file :return: Confirmation message """ # -- characters are not allowed in XML comments xml_file = replace_in_comments(xml_file, '--', '%wildcard%') # path of temporary files for parsing xml input tmp_file_path = '{}/tmp/api_tmp_file_{}_{}.xml'.format(common.ossec_path, time.time(), random.randint(0, 1000)) # create temporary file for parsing xml input try: with open(tmp_file_path, 'w') as tmp_file: # beauty xml file xml = parseString('<root>' + xml_file + '</root>') # remove first line (XML specification: <? xmlversion="1.0" ?>), <root> and </root> tags, and empty lines indent = ' ' # indent parameter for toprettyxml function pretty_xml = '\n'.join(filter(lambda x: x.strip(), xml.toprettyxml(indent=indent).split('\n')[2:-2])) + '\n' # revert xml.dom replacings # (https://github.com/python/cpython/blob/8e0418688906206fe59bd26344320c0fc026849e/Lib/xml/dom/minidom.py#L305) pretty_xml = pretty_xml.replace("&", "&").replace("<", "<").replace(""", "\"", ) \ .replace(">", ">").replace(''', "'") # delete two first spaces of each line final_xml = re.sub(fr'^{indent}', '', pretty_xml, flags=re.MULTILINE) final_xml = replace_in_comments(final_xml, '%wildcard%', '--') tmp_file.write(final_xml) chmod(tmp_file_path, 0o660) except IOError: raise WazuhInternalError(1005) except ExpatError: raise WazuhError(1113) try: # check xml format try: load_wazuh_xml(tmp_file_path) except Exception as e: raise WazuhError(1113, str(e)) # move temporary file to group folder try: new_conf_path = join(common.ossec_path, path) safe_move(tmp_file_path, new_conf_path, permissions=0o660) except Error: raise WazuhInternalError(1016) return WazuhResult({'message': 'File updated successfully'}) except Exception as e: # remove created temporary file if an exception happens remove(tmp_file_path) raise e
async def test_get_system_nodes(): """Verify that get_system_nodes function returns the name of all cluster nodes.""" with patch('wazuh.core.cluster.local_client.LocalClient.execute', side_effect=async_local_client): expected_result = [{'items': [{'name': 'master'}]}] for expected in expected_result: with patch('wazuh.core.cluster.control.get_nodes', return_value=expected): result = await control.get_system_nodes() assert result == [expected['items'][0]['name']] expected_exception = WazuhInternalError(3012) with patch('wazuh.core.cluster.control.get_nodes', side_effect=WazuhInternalError(3012)): result = await control.get_system_nodes() assert result == WazuhError(3013)
def update_api_conf(new_config): """Update the API.yaml file. Parameters ---------- new_config : dict Dictionary with the new configuration. """ if new_config: 'remote_commands' in new_config.keys() and new_config.pop( 'remote_commands') try: with open(common.api_config_path, 'r') as f: # Avoid changing the "remote_commands" option through the Framework previous_config = yaml.safe_load(f) if previous_config and 'remote_commands' in previous_config.keys( ): new_config['remote_commands'] = previous_config[ 'remote_commands'] with open(common.api_config_path, 'w+') as f: yaml.dump(new_config, f) except IOError: raise WazuhInternalError(1005) else: raise WazuhError(1105)
async def get_system_nodes(): try: lc = local_client.LocalClient() result = await get_nodes(lc) return [node['name'] for node in result['items']] except Exception: raise WazuhInternalError(3012)
def test_DistributedAPI_local_request_errors(): """Check the behaviour when the local_request function raised an error.""" with patch( 'wazuh.core.cluster.dapi.dapi.DistributedAPI.execute_local_request', new=AsyncMock(side_effect=WazuhInternalError(1001))): dapi_kwargs = {'f': agent.get_agents_summary_status, 'logger': logger} raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=1001) dapi_kwargs['debug'] = True dapi = DistributedAPI(f=agent.get_agents_summary_status, logger=logger, debug=True) try: raise_if_exc(loop.run_until_complete(dapi.distribute_function())) except WazuhInternalError as e: assert e.code == 1001 with patch( 'wazuh.core.cluster.dapi.dapi.DistributedAPI.execute_local_request', new=AsyncMock(side_effect=KeyError('Testing'))): dapi_kwargs = {'f': agent.get_agents_summary_status, 'logger': logger} raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=1000) # Specify KeyError dapi = DistributedAPI(f=agent.get_agents_summary_status, logger=logger, debug=True) try: raise_if_exc(loop.run_until_complete(dapi.distribute_function())) except KeyError as e: assert 'KeyError' in repr(e)
def test_DistributedAPI_local_request(mock_local_request): """Test `local_request` method from class DistributedAPI and check the behaviour when an error raise.""" dapi_kwargs = {'f': manager.status, 'logger': logger} raise_if_exc_routine(dapi_kwargs=dapi_kwargs) dapi_kwargs = { 'f': cluster.get_nodes_info, 'logger': logger, 'local_client_arg': 'lc' } raise_if_exc_routine(dapi_kwargs=dapi_kwargs) dapi_kwargs['is_async'] = True raise_if_exc_routine(dapi_kwargs=dapi_kwargs) with patch('asyncio.wait_for', new=AsyncMock(side_effect=TimeoutError('Testing'))): dapi = DistributedAPI(f=manager.status, logger=logger) try: raise_if_exc(loop.run_until_complete(dapi.distribute_function())) except ProblemException as e: assert e.ext['dapi_errors'][list(e.ext['dapi_errors'].keys())[0]]['error'] == \ 'Timeout executing API request' with patch('asyncio.wait_for', new=AsyncMock(side_effect=WazuhError(1001))): dapi_kwargs = {'f': manager.status, 'logger': logger} raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=1001) dapi_kwargs['debug'] = True raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=1001) with patch('asyncio.wait_for', new=AsyncMock(side_effect=WazuhInternalError(1001))): dapi_kwargs = {'f': manager.status, 'logger': logger} raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=1001) dapi = DistributedAPI(f=manager.status, logger=logger, debug=True) try: raise_if_exc(loop.run_until_complete(dapi.distribute_function())) except WazuhInternalError as e: assert e.code == 1001 with patch('asyncio.wait_for', new=AsyncMock(side_effect=KeyError('Testing'))): dapi_kwargs = {'f': manager.status, 'logger': logger} raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=1000) dapi = DistributedAPI(f=manager.status, logger=logger, debug=True) try: raise_if_exc(loop.run_until_complete(dapi.distribute_function())) except Exception as e: assert type(e) == KeyError
def validate_xml(path): """ Validates a XML file :param path: Relative path of file from origin :return: True if XML is OK, False otherwise """ full_path = join(common.ossec_path, path) try: with open(full_path) as f: parseString('<root>' + f.read() + '</root>') except IOError: raise WazuhInternalError(1005) except ExpatError: return False return True
def update_api_conf(new_config): """Update the API.yaml file. Parameters ---------- new_config : dict Dictionary with the new configuration. """ if new_config: try: with open(common.api_config_path, 'w+') as f: yaml.dump(new_config, f) except IOError: raise WazuhInternalError(1005) else: raise WazuhError(1105)
def generate_secret(): """Generate secret file to keep safe or load existing secret.""" try: if not os.path.exists(_secret_file_path): jwt_secret = token_urlsafe(512) with open(_secret_file_path, mode='x') as secret_file: secret_file.write(jwt_secret) try: chown(_secret_file_path, 'ossec', 'ossec') except PermissionError: pass os.chmod(_secret_file_path, 0o640) else: with open(_secret_file_path, mode='r') as secret_file: jwt_secret = secret_file.readline() except IOError: raise WazuhInternalError(6003) return jwt_secret
def validate_cdb_list(path): """ Validates a CDB list :param path: Relative path of file from origin :return: True if CDB list is OK, False otherwise """ full_path = join(common.ossec_path, path) regex_cdb = re.compile(r'^[^:]+:[^:]*$') try: with open(full_path) as f: for line in f: # skip empty lines if not line.strip(): continue if not re.match(regex_cdb, line): return False except IOError: raise WazuhInternalError(1005) return True
def update_security_conf(new_config): """Update dict and write it in the configuration file. Parameters ---------- new_config : dict Dictionary with the new configuration. """ if new_config: try: with open(SECURITY_CONFIG_PATH, 'w+') as f: yaml.dump(new_config, f) except IOError: raise WazuhInternalError(1005) else: raise WazuhError(4021) if 'max_login_attempts' in new_config.keys(): middlewares.ip_stats = dict() middlewares.ip_block = set() if 'max_request_per_minute' in new_config.keys(): middlewares.request_counter = 0
def generate_keypair(): """Generate key files to keep safe or load existing public and private keys.""" try: if not os.path.exists(_private_key_path) or not os.path.exists( _public_key_path): private_key, public_key = change_keypair() try: os.chown(_private_key_path, wazuh_uid(), wazuh_gid()) os.chown(_public_key_path, wazuh_uid(), wazuh_gid()) except PermissionError: pass os.chmod(_private_key_path, 0o640) os.chmod(_public_key_path, 0o640) else: with open(_private_key_path, mode='r') as key_file: private_key = key_file.read() with open(_public_key_path, mode='r') as key_file: public_key = key_file.read() except IOError: raise WazuhInternalError(6003) return private_key, public_key
def update_api_conf(new_config): """Update dict and subdicts without overriding unspecified keys and write it in the API.yaml file. Parameters ---------- new_config : dict Dictionary with the new configuration. """ if new_config: for key in new_config: if key in configuration.api_conf: if isinstance(configuration.api_conf[key], dict) and isinstance(new_config[key], dict): configuration.api_conf[key].update(new_config[key]) else: configuration.api_conf[key] = new_config[key] try: with open(common.api_config_path, 'w+') as f: yaml.dump(configuration.api_conf, f) except IOError: raise WazuhInternalError(1005) else: raise WazuhError(1105)
def update_security_conf(new_config): """Update dict and write it in the configuration file. Parameters ---------- new_config : dict Dictionary with the new configuration. """ configuration.security_conf.update(new_config) need_revoke = False if new_config: for key in new_config: if key in configuration.security_conf.keys(): need_revoke = True try: with open(SECURITY_CONFIG_PATH, 'w+') as f: yaml.dump(configuration.security_conf, f) except IOError: raise WazuhInternalError(1005) else: raise WazuhError(4021) return need_revoke
def test_DistributedAPI_forward_request_errors(mock_client_execute, mock_get_solver_node, mock_check_cluster_status, mock_get_node): """Check the behaviour when the forward_request function raised an error""" # Test forward_request when it raises a JSONDecodeError dapi_kwargs = { 'f': agent.reconnect_agents, 'logger': logger, 'request_type': 'distributed_master' } raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=3036) @patch('wazuh.core.cluster.dapi.dapi.DistributedAPI.execute_local_request', new=AsyncMock(side_effect=WazuhInternalError(1001))) def test_DistributedAPI_logger(): """Test custom logger inside DistributedAPI class.""" new_logger = logging.getLogger('dapi_test') fh = logging.FileHandler('/tmp/dapi_test.log') fh.setLevel(logging.DEBUG) new_logger.addHandler(fh) dapi_kwargs = {'f': agent.get_agents_summary_status, 'logger': new_logger} raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=1001) @patch('wazuh.core.cluster.local_client.LocalClient.send_file', new=AsyncMock(return_value='{"Testing": 1}')) @patch('wazuh.core.cluster.local_client.LocalClient.execute', new=AsyncMock(return_value='{"Testing": 1}')) @patch('wazuh.core.cluster.dapi.dapi.DistributedAPI.get_solver_node',
def validate_ossec_conf(): """Check if Wazuh configuration is OK. Raises ------ WazuhInternalError(1014) If there is a socket communication error. WazuhInternalError(1013) If it is unable to connect to socket. WazuhInternalError(1901) If 'execq' socket cannot be created. WazuhInternalError(1904) If there is bad data received from 'execq'. Returns ------- str Status of the configuration. """ lock_file = open(wcom_lockfile, 'a+') fcntl.lockf(lock_file, fcntl.LOCK_EX) try: # Socket path wcom_socket_path = common.WCOM_SOCKET # Message for checking Wazuh configuration wcom_msg = common.CHECK_CONFIG_COMMAND # Connect to wcom socket if exists(wcom_socket_path): try: wcom_socket = WazuhSocket(wcom_socket_path) except WazuhException as e: extra_msg = f'Socket: WAZUH_PATH/queue/sockets/com. Error {e.message}' raise WazuhInternalError(1013, extra_message=extra_msg) else: raise WazuhInternalError(1901) # Send msg to wcom socket try: wcom_socket.send(wcom_msg.encode()) buffer = bytearray() datagram = wcom_socket.receive() buffer.extend(datagram) wcom_socket.close() except (socket.error, socket.timeout) as e: raise WazuhInternalError(1014, extra_message=str(e)) finally: wcom_socket.close() try: response = parse_execd_output(buffer.decode('utf-8').rstrip('\0')) except (KeyError, json.decoder.JSONDecodeError) as e: raise WazuhInternalError(1904, extra_message=str(e)) finally: fcntl.lockf(lock_file, fcntl.LOCK_UN) lock_file.close() return response
def validate_ossec_conf(): """Check if Wazuh configuration is OK. Returns ------- response : str Status of the configuration. """ lock_file = open(execq_lockfile, 'a+') fcntl.lockf(lock_file, fcntl.LOCK_EX) try: # Sockets path api_socket_relative_path = join('queue', 'alerts', 'execa') api_socket_path = join(common.ossec_path, api_socket_relative_path) execq_socket_path = common.EXECQ # Message for checking Wazuh configuration execq_msg = 'check-manager-configuration ' # Remove api_socket if exists try: remove(api_socket_path) except OSError as e: if exists(api_socket_path): extra_msg = f'Socket: WAZUH_PATH/{api_socket_relative_path}. Error: {e.strerror}' raise WazuhInternalError(1014, extra_message=extra_msg) # up API socket try: api_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) api_socket.bind(api_socket_path) # Timeout api_socket.settimeout(5) except OSError as e: extra_msg = f'Socket: WAZUH_PATH/{api_socket_relative_path}. Error: {e.strerror}' raise WazuhInternalError(1013, extra_message=extra_msg) # Connect to execq socket if exists(execq_socket_path): try: execq_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) execq_socket.connect(execq_socket_path) except OSError as e: extra_msg = f'Socket: WAZUH_PATH/queue/alerts/execq. Error {e.strerror}' raise WazuhInternalError(1013, extra_message=extra_msg) else: raise WazuhInternalError(1901) # Send msg to execq socket try: execq_socket.send(execq_msg.encode()) execq_socket.close() except socket.error as e: raise WazuhInternalError(1014, extra_message=str(e)) finally: execq_socket.close() # If api_socket receives a message, configuration is OK try: buffer = bytearray() # Receive data datagram = api_socket.recv(4096) buffer.extend(datagram) except socket.timeout as e: raise WazuhInternalError(1014, extra_message=str(e)) finally: api_socket.close() # Remove api_socket if exists(api_socket_path): remove(api_socket_path) try: response = parse_execd_output(buffer.decode('utf-8').rstrip('\0')) except (KeyError, json.decoder.JSONDecodeError) as e: raise WazuhInternalError(1904, extra_message=str(e)) finally: fcntl.lockf(lock_file, fcntl.LOCK_UN) lock_file.close() return response
json_response = json.dumps({ 'error': error_flag, 'message': error_msg }).encode() sock.return_value.receive.return_value = json_response result = validation() # Assert if error was returned assert isinstance(result, AffectedItemsWazuhResult), 'No expected result type' assert result.render()['data']['total_failed_items'] == error_flag @pytest.mark.parametrize( 'exception', [WazuhInternalError(1013), WazuhError(1013)]) @patch('wazuh.manager.validate_ossec_conf') def test_validation_ko(mock_validate, exception): mock_validate.side_effect = exception if isinstance(exception, WazuhInternalError): with pytest.raises(WazuhInternalError, match='.* 1013 .*'): validation() else: result = validation() assert not result.affected_items assert result.total_failed_items == 1 @patch('wazuh.core.configuration.get_active_configuration') def test_get_config(mock_act_conf):
def validate_ossec_conf(): """Check if Wazuh configuration is OK. Raises ------ WazuhInternalError(1014) If there is a socket communication error. WazuhInternalError(1013) If it is unable to connect to socket. WazuhInternalError(1901) If 'execq' socket cannot be created. WazuhInternalError(1904) If there is bad data received from 'execq'. Returns ------- str Status of the configuration. """ lock_file = open(execq_lockfile, 'a+') fcntl.lockf(lock_file, fcntl.LOCK_EX) try: # Sockets path api_socket_relative_path = join('queue', 'alerts', 'execa') api_socket_path = join(common.wazuh_path, api_socket_relative_path) execq_socket_path = common.EXECQ # Message for checking Wazuh configuration execq_msg = json.dumps( create_wazuh_socket_message(origin={'module': 'api/framework'}, command=common.CHECK_CONFIG_COMMAND, parameters={ "extra_args": [], "alert": {} })) # Remove api_socket if exists try: remove(api_socket_path) except OSError as e: if exists(api_socket_path): extra_msg = f'Socket: WAZUH_PATH/{api_socket_relative_path}. Error: {e.strerror}' raise WazuhInternalError(1014, extra_message=extra_msg) # up API socket try: api_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) api_socket.bind(api_socket_path) # Timeout api_socket.settimeout(10) except OSError as e: extra_msg = f'Socket: WAZUH_PATH/{api_socket_relative_path}. Error: {e.strerror}' raise WazuhInternalError(1013, extra_message=extra_msg) # Connect to execq socket if exists(execq_socket_path): try: execq_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) execq_socket.connect(execq_socket_path) except OSError as e: extra_msg = f'Socket: WAZUH_PATH/queue/alerts/execq. Error {e.strerror}' raise WazuhInternalError(1013, extra_message=extra_msg) else: raise WazuhInternalError(1901) # Send msg to execq socket try: execq_socket.send(execq_msg.encode()) execq_socket.close() except socket.error as e: raise WazuhInternalError(1014, extra_message=str(e)) finally: execq_socket.close() # If api_socket receives a message, configuration is OK try: buffer = bytearray() # Receive data datagram = api_socket.recv(4096) buffer.extend(datagram) except socket.timeout as e: raise WazuhInternalError(1014, extra_message=str(e)) finally: api_socket.close() # Remove api_socket if exists(api_socket_path): remove(api_socket_path) try: response = parse_execd_output(buffer.decode('utf-8').rstrip('\0')) except (KeyError, json.decoder.JSONDecodeError) as e: raise WazuhInternalError(1904, extra_message=str(e)) finally: fcntl.lockf(lock_file, fcntl.LOCK_UN) lock_file.close() return response
error_msg : str Error message to be mocked in the socket response. """ with patch('wazuh.core.manager.WazuhSocket') as sock: # Mock sock response json_response = json.dumps({'error': error_flag, 'message': error_msg}).encode() sock.return_value.receive.return_value = json_response result = validation() # Assert if error was returned assert isinstance(result, AffectedItemsWazuhResult), 'No expected result type' assert result.render()['data']['total_failed_items'] == error_flag @pytest.mark.parametrize('exception', [ WazuhInternalError(1013), WazuhError(1013) ]) @patch('wazuh.manager.validate_ossec_conf') def test_validation_ko(mock_validate, exception): mock_validate.side_effect = exception if isinstance(exception, WazuhInternalError): with pytest.raises(WazuhInternalError, match='.* 1013 .*'): validation() else: result = validation() assert not result.affected_items assert result.total_failed_items == 1