def _initialize(self): """ Calculates all Wazuh installation metadata """ # info DB if possible try: wdb_conn = WazuhDBConnection() open_ssl = wdb_conn.execute("global sql SELECT value FROM info WHERE key = 'openssl_support'")[0]['value'] self.openssl_support = open_ssl except Exception: self.openssl_support = "N/A" # Ruleset version ruleset_version_file = os.path.join(self.path, 'ruleset', 'VERSION') try: with open(ruleset_version_file, 'r') as f: line_regex = re.compile(r'(^\w+)="(.+)"') for line in f: match = line_regex.match(line) if match and len(match.groups()) == 2: self.ruleset_version = match.group(2) except: raise WazuhInternalError(1005, extra_message=ruleset_version_file) # Timezone info try: self.tz_offset = strftime("%z") self.tz_name = strftime("%Z") except Exception: self.tz_offset = None self.tz_name = None return self.to_dict()
def clear(agent_list=None): """Clear the rootcheck database for a list of agents. Parameters ---------- agent_list : list List of agent ids. Returns ------- result : AffectedItemsWazuhResult JSON containing the affected agents. """ result = AffectedItemsWazuhResult( all_msg='Rootcheck database was cleared on returned agents', some_msg='Rootcheck database was not cleared on some agents', none_msg="No rootcheck database was cleared") wdb_conn = WazuhDBConnection() for agent_id in agent_list: if agent_id not in get_agents_info(): result.add_failed_item(id_=agent_id, error=WazuhResourceNotFound(1701)) else: try: wdb_conn.execute(f"agent {agent_id} rootcheck delete", delete=True) result.affected_items.append(agent_id) except WazuhError as e: result.add_failed_item(id_=agent_id, error=e) result.affected_items.sort(key=int) result.total_affected_items = len(result.affected_items) return result
def last_scan(agent_id): """Get the last rootcheck scan of an agent. :param agent_id: Agent ID. :return: Dictionary: end, start. """ Agent(agent_id).get_basic_information() wdb_conn = WazuhDBConnection() # end time result = wdb_conn.execute( f"agent {agent_id} sql SELECT max(date_last) FROM pm_event WHERE " "log = 'Ending rootcheck scan.'") time = list(result[0].values())[0] if result else None end = datetime.utcfromtimestamp(time).strftime( date_format) if time is not None else None # start time result = wdb_conn.execute( f"agent {agent_id} sql SELECT max(date_last) FROM pm_event " "WHERE log = 'Starting rootcheck scan.'") time = list(result[0].values())[0] if result else None start = datetime.utcfromtimestamp(time).strftime( date_format) if time is not None else None return { 'start': start, 'end': None if start is None else None if end is None or end < start else end }
def test_run_wdb_command(connect_mock): with patch('wazuh.core.wdb.WazuhDBConnection._send', side_effect=[['due', 'chunk1'], ['due', 'chunk2'], ['ok', 'chunk3'], ['due', 'chunk4']]): mywdb = WazuhDBConnection() result = mywdb.run_wdb_command("global sync-agent-info-get ") assert result == ['chunk1', 'chunk2', 'chunk3']
def test_run_wdb_command_ko(connect_mock): with patch('wazuh.core.wdb.WazuhDBConnection._send', side_effect=[['due', 'chunk1'], ['err', 'chunk2'], ['ok', 'chunk3'], ['due', 'chunk4']]): mywdb = WazuhDBConnection() with pytest.raises(exception.WazuhInternalError, match=".* 2007 .* chunk2"): mywdb.run_wdb_command("global sync-agent-info-get ")
def get_manager_name(): """This function read the manager name from global.db""" wdb_conn = WazuhDBConnection() manager_name = wdb_conn.execute( "global sql SELECT name FROM agent WHERE (id = 0)")[0]['name'] wdb_conn.close() return manager_name
def remove_bulk_agents(agent_ids_list: KeysView, logger): """ Removes files created by agents in worker nodes. This function doesn't remove agents from client.keys since the client.keys file is overwritten by the master node. :param agent_ids_list: List of agents ids to remove. :param logger: Logger to use :return: None. """ def remove_agent_file_type(agent_files: List[str]): """ Removes files if they exist :param agent_files: Path regexes of the files to remove :return: None """ for filetype in agent_files: filetype_glob = filetype.format(ossec_path=common.ossec_path, id='*', name='*', ip='*') filetype_agent = {filetype.format(ossec_path=common.ossec_path, id=a['id'], name=a['name'], ip=a['ip']) for a in agent_info} for agent_file in set(glob.iglob(filetype_glob)) & filetype_agent: logger.debug2("Removing {}".format(agent_file)) if os.path.isdir(agent_file): shutil.rmtree(agent_file) else: os.remove(agent_file) if not agent_ids_list: return # the function doesn't make sense if there is no agents to remove logger.info("Removing files from {} agents".format(len(agent_ids_list))) logger.debug("Agents to remove: {}".format(', '.join(agent_ids_list))) # Remove agents in group of 500 elements (so wazuh-db socket is not saturated) for agents_ids_sublist in itertools.zip_longest(*itertools.repeat(iter(agent_ids_list), 500), fillvalue='0'): agents_ids_sublist = list(filter(lambda x: x != '0', agents_ids_sublist)) # Get info from DB agent_info = Agent.get_agents_overview(q=",".join(["id={}".format(i) for i in agents_ids_sublist]), select=['ip', 'id', 'name'], limit=None)['items'] logger.debug2("Removing files from agents {}".format(', '.join(agents_ids_sublist))) files_to_remove = ['{ossec_path}/queue/rootcheck/({name}) {ip}->rootcheck', '{ossec_path}/queue/diff/{name}', '{ossec_path}/queue/agent-groups/{id}', '{ossec_path}/queue/rids/{id}', '{ossec_path}/var/db/agents/{name}-{id}.db'] remove_agent_file_type(files_to_remove) logger.debug2("Removing agent group assigments from database") # remove agent from groups wdb_conn = WazuhDBConnection() query_to_execute = 'global sql delete from belongs where {}'.format(' or '.join([ 'id_agent = {}'.format(agent_id) for agent_id in agents_ids_sublist ])) wdb_conn.run_wdb_command(query_to_execute) logger.info("Agent files removed")
async def sync_wazuh_db_info(self, task_id: bytes): """Iterate and update in the local wazuh-db the chunks of data received from a worker. Parameters ---------- task_id : bytes ID of the string where the JSON chunks are stored. Returns ------- result : bytes Worker's response after finishing the synchronization. """ logger = self.task_loggers['Agent-info sync'] logger.info(f"Starting") date_start_master = datetime.now() wdb_conn = WazuhDBConnection() result = {'updated_chunks': 0, 'error_messages': list()} try: # Chunks were stored under 'task_id' as an string. received_string = self.in_str[task_id].payload data = json.loads(received_string.decode()) except KeyError as e: await self.send_request(command=b'syn_m_a_err', data=f"error while trying to access string under task_id {str(e)}.".encode()) raise exception.WazuhClusterError(3035, extra_message=f"it should be under task_id {str(e)}, but it's empty.") except ValueError as e: await self.send_request(command=b'syn_m_a_err', data=f"error while trying to load JSON: {str(e)}".encode()) raise exception.WazuhClusterError(3036, extra_message=str(e)) # Update chunks in local wazuh-db before = time() for i, chunk in enumerate(data['chunks']): try: logger.debug2(f"Sending chunk {i+1}/{len(data['chunks'])} to wazuh-db: {chunk}") response = wdb_conn.send(f"{data['set_data_command']} {chunk}", raw=True) if response[0] != 'ok': result['error_messages'].append(response) logger.error(f"Response for chunk {i}/{len(data['chunks'])} was not 'ok': {response}") else: result['updated_chunks'] += 1 except Exception as e: result['error_messages'].append(str(e)) logger.debug(f"All chunks updated in wazuh-db in {(time() - before):3f}s.") # Send result to worker response = await self.send_request(command=b'syn_m_a_e', data=json.dumps(result).encode()) self.sync_agent_info_status.update({'date_start_master': date_start_master, 'date_end_master': datetime.now(), 'n_synced_chunks': result['updated_chunks']}) logger.info("Finished in {:.3f}s ({} chunks updated).".format((self.sync_agent_info_status['date_end_master'] - self.sync_agent_info_status['date_start_master']) .total_seconds(), result['updated_chunks'])) return response
def test_remove_agents_database(send_mock, connect_mock, content): """ Tests delete_agents_db method handle exceptions properly """ def recv_mock(size_to_receive): return bytes(len(content)) if size_to_receive == 4 else content with patch('socket.socket.recv', side_effect=recv_mock): mywdb = WazuhDBConnection() received = mywdb.delete_agents_db(['001', '002']) assert(isinstance(received, dict)) assert("agents" in received)
def test_wrong_character_encodings_wdb(send_mock, connect_mock): """ Tests receiving a text with a bad character encoding from wazuh db """ def recv_mock(size_to_receive): bad_string = b' {"bad": "\x96bad"}' return bytes(len(bad_string)) if size_to_receive == 4 else bad_string with patch('socket.socket.recv', side_effect=recv_mock): mywdb = WazuhDBConnection() received = mywdb._send("test") assert received == {"bad": "bad"}
def test_failed_send_private(send_mock, connect_mock): """ Tests an exception is properly raised when it's not possible to send a msg to the wdb socket """ def recv_mock(size_to_receive): error_string = b'err {"agents": {"001": "Error"}}' return bytes(len(error_string)) if size_to_receive == 4 else error_string with patch('socket.socket.recv', side_effect=recv_mock): mywdb = WazuhDBConnection() with pytest.raises(exception.WazuhException, match=".* 2003 .*"): mywdb._send('test_msg')
def test_null_values_are_removed(send_mock, connect_mock): """ Tests '(null)' values are removed from the resulting dictionary """ def recv_mock(size_to_receive): nulls_string = b' {"a": "a", "b": "(null)", "c": [1, 2, 3], "d": {"e": "(null)"}}' return bytes(len(nulls_string)) if size_to_receive == 4 else nulls_string with patch('socket.socket.recv', side_effect=recv_mock): mywdb = WazuhDBConnection() received = mywdb._send("test") assert received == {"a": "a", "c": [1, 2, 3], "d": {}}
def test_failed_connection(): """ Tests an exception is properly raised when it's not possible to connect to wdb """ # tests the socket path doesn't exists with patch('wazuh.core.common.wdb_socket_path', '/this/path/doesnt/exist'): with pytest.raises(exception.WazuhException, match=".* 2005 .*"): WazuhDBConnection() # tests an exception is properly raised when a connection error is raised with patch('socket.socket') as socket_patch: with pytest.raises(exception.WazuhException, match=".* 2005 .*"): socket_patch.return_value.connect.side_effect = ConnectionError WazuhDBConnection()
def clear(agent_list=None): """Clear the syscheck database for a list of agents. :param agent_list: List of agent ids :return: AffectedItemsWazuhResult. """ result = AffectedItemsWazuhResult(all_msg='Syscheck database was cleared on returned agents', some_msg='Syscheck database was not cleared on some agents', none_msg="No syscheck database was cleared") wdb_conn = WazuhDBConnection() for agent in agent_list: if agent not in get_agents_info(): result.add_failed_item(id_=agent, error=WazuhResourceNotFound(1701)) else: try: wdb_conn.execute("agent {} sql delete from fim_entry".format(agent), delete=True) # Update key fields which contains keys to value 000 wdb_conn.execute("agent {} sql update metadata set value = '000' " "where key like 'fim_db%'".format(agent), update=True) wdb_conn.execute("agent {} sql update metadata set value = '000' " "where key = 'syscheck-db-completed'".format(agent), update=True) result.affected_items.append(agent) except WazuhError as e: result.add_failed_item(id_=agent, error=e) result.affected_items.sort(key=int) result.total_affected_items = len(result.affected_items) return result
async def sync_agent_info(self): """Obtain information from agents reporting this worker and send it to the master. Asynchronous task that is started when the worker connects to the master. It starts an agent-info synchronization process every 'sync_agent_info' seconds. A list of JSON chunks with the information of all local agents is retrieved from local wazuh-db socket and sent to the master's wazuh-db. """ logger = self.task_loggers["Agent-info sync"] wdb_conn = WazuhDBConnection() synced = True agent_info = SyncWazuhdb(worker=self, logger=logger, cmd=b'sync_a_w_m', data_retriever=wdb_conn.run_wdb_command, get_data_command='global sync-agent-info-get ', set_data_command='global sync-agent-info-set') while True: try: if self.connected: logger.info("Starting.") start_time = time.time() synced = await agent_info.sync() if synced: self.agent_info_sync_status['date_start'] = start_time except Exception as e: logger.error(f"Error synchronizing agent info: {e}") await asyncio.sleep( self.cluster_items['intervals']['worker']['sync_agent_info' if synced else 'sync_agent_info_ko_retry'])
def _initialize(self): """ Calculates all Wazuh installation metadata """ # info DB if possible try: wdb_conn = WazuhDBConnection() open_ssl = wdb_conn.execute("global sql SELECT value FROM info WHERE key = 'openssl_support'")[0]['value'] self.openssl_support = open_ssl except Exception: self.openssl_support = "N/A" # Timezone info try: self.tz_offset = strftime("%z") self.tz_name = strftime("%Z") except Exception: self.tz_offset = None self.tz_name = None return self.to_dict()
def test_failed_execute(send_mock, connect_mock, error_query, error_type, expected_exception, delete, update): mywdb = WazuhDBConnection() if not error_type: with pytest.raises(exception.WazuhException, match=f'.* {expected_exception} .*'): mywdb.execute(error_query, delete=delete, update=update) else: with patch("wazuh.core.wdb.WazuhDBConnection._send", return_value=[{'total': 5}]): with patch("wazuh.core.wdb.range", side_effect=error_type): with pytest.raises(exception.WazuhException, match=f'.* {expected_exception} .*'): mywdb.execute(error_query, delete=delete, update=update)
def test_execute_pagination(socket_send_mock, connect_mock): mywdb = WazuhDBConnection() # Test pagination with patch("wazuh.core.wdb.WazuhDBConnection._send", side_effect=[[{'total': 5}], exception.WazuhInternalError(2009), ['ok', '{"total": 5}'], ['ok', '{"total": 5}']]): mywdb.execute("agent 000 sql select test from test offset 1 limit 500") # Test pagination error with patch("wazuh.core.wdb.WazuhDBConnection._send", side_effect=[[{'total': 5}], exception.WazuhInternalError(2009)]): with pytest.raises(exception.WazuhInternalError, match=".* 2009 .*"): mywdb.execute("agent 000 sql select test from test offset 1 limit 1")
def send_data_to_wdb(data, timeout): """Send chunks of data to Wazuh-db socket. Parameters ---------- data : dict Dict containing command and list of chunks to be sent to wazuh-db. timeout : int Seconds to wait before stopping the task. Returns ------- result : dict Dict containing number of updated chunks, error messages (if any) and time spent. """ result = { 'updated_chunks': 0, 'error_messages': { 'chunks': [], 'others': [] }, 'time_spent': 0 } wdb_conn = WazuhDBConnection() before = datetime.utcnow().timestamp() try: with utils.Timeout(timeout): for i, chunk in enumerate(data['chunks']): try: wdb_conn.send(f"{data['set_data_command']} {chunk}", raw=True) result['updated_chunks'] += 1 except TimeoutError: raise e except Exception as e: result['error_messages']['chunks'].append((i, str(e))) except TimeoutError: result['error_messages']['others'].append( 'Timeout while processing agent-info chunks.') except Exception as e: result['error_messages']['others'].append( f'Error while processing agent-info chunks: {e}') result['time_spent'] = datetime.utcnow().timestamp() - before wdb_conn.close() return result
async def sync_agent_info(self): """Obtain information from agents reporting this worker and send it to the master. Asynchronous task that is started when the worker connects to the master. It starts an agent-info synchronization process every self.cluster_items['intervals']['worker']['sync_files'] seconds. A list of JSON chunks with the information of all local agents is retrieved from local wazuh-db socket and directly sent to the master's wazuh-db socket through 'sendsync' protocol. """ agent_info_logger = self.task_loggers["Agent info"] wdb_conn = WazuhDBConnection() agent_info = RetrieveAndSendToMaster( worker=self, destination_daemon='wazuh-db', logger=agent_info_logger, msg_format='global sync-agent-info-set {payload}', cmd=b'sync_a_w_m', data_retriever=wdb_conn.run_wdb_command, max_retry_time_allowed=self.cluster_items['intervals']['worker'] ['sync_files']) while True: try: if self.connected: agent_info_logger.info("Starting agent-info sync process.") before = time.time() await agent_info.retrieve_and_send( 'global sync-agent-info-get ') agent_info_logger.debug2( "Time synchronizing agent statuses: {} s".format( time.time() - before)) except Exception as e: agent_info_logger.error( "Error synchronizing agent info: {}".format(e)) await asyncio.sleep( self.cluster_items['intervals']['worker']['sync_files'])
def clear(agent_list=None): """Clear the rootcheck database for a list of agents. Parameters ---------- agent_list : list List of agent ids. Returns ------- result : AffectedItemsWazuhResult JSON containing the affected agents. """ result = AffectedItemsWazuhResult(all_msg='Rootcheck database was cleared on returned agents', some_msg='Rootcheck database was not cleared on some agents', none_msg="No rootcheck database was cleared") wdb_conn = WazuhDBConnection() system_agents = get_agents_info() agent_list = set(agent_list) not_found_agents = agent_list - system_agents # Add non existent agents to failed_items [result.add_failed_item(id_=agent_id, error=WazuhResourceNotFound(1701)) for agent_id in not_found_agents] eligible_agents = agent_list - not_found_agents for agent_id in eligible_agents: try: rootcheck_delete_agent(agent_id, wdb_conn) result.affected_items.append(agent_id) except WazuhError as e: result.add_failed_item(id_=agent_id, error=e) result.affected_items.sort(key=int) result.total_affected_items = len(result.affected_items) return result
async def sync_agent_info(self): """ Asynchronous task that is started when the worker connects to the master. It starts an agent-info synchronization process every self.cluster_items['intervals']['worker']['sync_files'] seconds. :return: None """ agent_info_logger = self.task_loggers["Agent info"] wdb_conn = WazuhDBConnection() agent_info = RetrieveAndSendToMaster(worker=self, destination_daemon='wazuh-db', logger=agent_info_logger, msg_format='global sync-agent-info-set {payload}', cmd=b'sync_a_w_m', data_retriever=wdb_conn.run_wdb_command, max_retry_time_allowed= self.cluster_items['intervals']['worker']['sync_files']) while True: try: if self.connected: agent_info_logger.info("Starting agent-info sync process.") before = time.time() await agent_info.retrieve_and_send('global sync-agent-info-get ') agent_info_logger.debug2("Time synchronizing agent statuses: {} s".format(time.time() - before)) except Exception as e: agent_info_logger.error("Error synchronizing agent info: {}".format(e)) await asyncio.sleep(self.cluster_items['intervals']['worker']['sync_files'])
def clear(agent_list: list = None): """Clear the syscheck database of the specified agents. Parameters ---------- agent_list : str Agent ID. Returns ------- result : AffectedItemsWazuhResult Confirmation/Error message. """ result = AffectedItemsWazuhResult( all_msg='Syscheck database was cleared on returned agents', some_msg='Syscheck database was not cleared on some agents', none_msg="No syscheck database was cleared") system_agents = get_agents_info() not_found_agents = set(agent_list) - system_agents list( map( lambda ag: result.add_failed_item( id_=ag, error=WazuhResourceNotFound(1701)), not_found_agents)) wdb_conn = None rbac_filters = get_rbac_filters(system_resources=system_agents, permitted_resources=agent_list) db_query = WazuhDBQueryAgents(select=["id", "version"], **rbac_filters) data = db_query.run() for item in data['items']: agent_id = item['id'] agent_version = item.get( 'version', None) # If the value was NULL in the DB the key might not exist if agent_version is not None: if WazuhVersion(agent_version) < WazuhVersion('v3.12.0'): try: if wdb_conn is None: wdb_conn = WazuhDBConnection() syscheck_delete_agent(agent_id, wdb_conn) result.affected_items.append(agent_id) except WazuhError as e: result.add_failed_item(id_=agent_id, error=e) else: result.add_failed_item( id_=agent_id, error=WazuhError( 1760, extra_message="Agent version should be < v3.12.0.")) else: result.add_failed_item(id_=agent_id, error=WazuhError(1015)) if wdb_conn is not None: wdb_conn.close() result.affected_items.sort(key=int) result.total_affected_items = len(result.affected_items) return result
def syscheck_delete_agent(agent: str, wdb_conn: WazuhDBConnection) -> None: wdb_conn.execute(f"agent {agent} sql delete from fim_entry", delete=True)
def test_execute(send_mock, socket_send_mock, connect_mock): mywdb = WazuhDBConnection() mywdb.execute('agent 000 sql delete from test', delete=True) mywdb.execute("agent 000 sql update test set value = 'test' where key = 'test'", update=True) with patch("wazuh.core.wdb.WazuhDBConnection._send", return_value=[{'total':5}]): mywdb.execute("agent 000 sql select test from test offset 1 limit 1") mywdb.execute("agent 000 sql select test from test offset 1 limit 1", count=True) mywdb.execute("agent 000 sql select test from test offset 1 count")
def test_query_lower_private(send_mock, connect_mock): mywdb = WazuhDBConnection() with pytest.raises(exception.WazuhException, match=".* 2004 .*"): mywdb.execute("Agent sql select 'test'")
def rootcheck_delete_agent(agent: str, wdb_conn: WazuhDBConnection) -> None: wdb_conn.execute(f"agent {agent} rootcheck delete", delete=True)
def test_query_input_validation_private(send_mock, connect_mock, error_query): mywdb = WazuhDBConnection() with pytest.raises(exception.WazuhException, match=".* 2004 .*"): mywdb.execute(error_query)
def test_execute(send_mock, socket_send_mock, connect_mock): def send_mock(obj, msg, raw=False): return ['ok', '{"total": 5}'] if raw else [{"total": 5}] mywdb = WazuhDBConnection() mywdb.execute('agent 000 sql delete from test', delete=True) mywdb.execute("agent 000 sql update test set value = 'test' where key = 'test'", update=True) with patch("wazuh.core.wdb.WazuhDBConnection._send", new=send_mock): mywdb.execute("agent 000 sql select test from test offset 1 limit 1") mywdb.execute("agent 000 sql select test from test offset 1 limit 1", count=True) mywdb.execute("agent 000 sql select test from test offset 1 count")
def remove_bulk_agents(agent_ids_list: KeysView, logger): """ Removes files created by agents in worker nodes. This function doesn't remove agents from client.keys since the client.keys file is overwritten by the master node. :param agent_ids_list: List of agents ids to remove. :param logger: Logger to use :return: None. """ def remove_agent_file_type(agent_files: List[str]): """ Removes files if they exist :param agent_files: Path regexes of the files to remove :return: None """ for filetype in agent_files: filetype_glob = filetype.format(ossec_path=common.ossec_path, id='*', name='*', ip='*') filetype_agent = { filetype.format(ossec_path=common.ossec_path, id=a['id'], name=a['name'], ip=a['ip']) for a in agent_info } for agent_file in set( glob.iglob(filetype_glob)) & filetype_agent: logger.debug2("Removing {}".format(agent_file)) if os.path.isdir(agent_file): shutil.rmtree(agent_file) else: os.remove(agent_file) if not agent_ids_list: return # the function doesn't make sense if there is no agents to remove logger.info("Removing files from {} agents".format( len(agent_ids_list))) logger.debug("Agents to remove: {}".format(', '.join(agent_ids_list))) # the agents must be removed in groups of 997: 999 is the limit of SQL variables per query. Limit and offset are # always included in the SQL query, so that leaves 997 variables as limit. for agents_ids_sublist in itertools.zip_longest(*itertools.repeat( iter(agent_ids_list), 997), fillvalue='0'): agents_ids_sublist = list( filter(lambda x: x != '0', agents_ids_sublist)) # Get info from DB agent_info = Agent.get_agents_overview(q=",".join( ["id={}".format(i) for i in agents_ids_sublist]), select=['ip', 'id', 'name'], limit=None)['items'] logger.debug2("Removing files from agents {}".format( ', '.join(agents_ids_sublist))) files_to_remove = [ '{ossec_path}/queue/agent-info/{name}-{ip}', '{ossec_path}/queue/rootcheck/({name}) {ip}->rootcheck', '{ossec_path}/queue/diff/{name}', '{ossec_path}/queue/agent-groups/{id}', '{ossec_path}/queue/rids/{id}', '{ossec_path}/var/db/agents/{name}-{id}.db' ] remove_agent_file_type(files_to_remove) logger.debug2("Removing agent group assigments from database") # remove agent from groups db_global = glob.glob(common.database_path_global) if not db_global: raise WazuhInternalError(1600) conn = Connection(db_global[0]) agent_ids_db = { 'id_agent{}'.format(i): int(i) for i in agents_ids_sublist } conn.execute( 'delete from belongs where {}'.format(' or '.join([ 'id_agent = :{}'.format(i) for i in agent_ids_db.keys() ])), agent_ids_db) conn.commit() # Tell wazuhbd to delete agent database wdb_conn = WazuhDBConnection() wdb_conn.delete_agents_db(agents_ids_sublist) logger.info("Agent files removed")