def get_permissions(user_id=None, auth_context=None): with AuthenticationManager() as auth: if auth.user_auth_context(user_id): # Add dummy rbac_policies for developing here if auth_context: return WazuhResult( optimize_resources(auth_context=auth_context)) return WazuhResult(optimize_resources(user_id=user_id))
def test_results_WazuhResult__merge_str(dikt, priority, get_wazuh_affected_item): """Test method `_merge_str` from `WazuhResult`. Parameters ---------- dikt : dict Dict with basic information for the class declaration. priority : list Used to set the WazuhResult priority. """ wazuh_result = WazuhResult(deepcopy(dikt), str_priority=priority) assert isinstance(wazuh_result, WazuhResult) item2 = wazuh_result.dikt['data']['items'][1]['item2'] merge_result = wazuh_result._merge_str(item2, 'KO') assert merge_result == priority[0] if priority else '{}|{}'.format(item2, 'KO')
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 manager_restart(): """ Restart Wazuh manager. :return: Confirmation message. """ lock_file = open(execq_lockfile, 'a+') fcntl.lockf(lock_file, fcntl.LOCK_EX) try: # execq socket path socket_path = common.EXECQ # msg for restarting Wazuh manager msg = 'restart-wazuh ' # initialize socket if exists(socket_path): try: conn = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) conn.connect(socket_path) except socket.error: raise WazuhInternalError(1902) else: raise WazuhInternalError(1901) try: conn.send(msg.encode()) conn.close() except socket.error as e: raise WazuhInternalError(1014, extra_message=str(e)) finally: fcntl.lockf(lock_file, fcntl.LOCK_UN) lock_file.close() read_config.cache_clear() return WazuhResult({'message': 'Restart request sent'})
def test_decode_token(mock_raise_if_exc, mock_distribute_function, mock_dapi, mock_generate_secret, mock_decode): mock_decode.return_value = payload mock_raise_if_exc.side_effect = [WazuhResult({'valid': True}), WazuhResult(security_conf)] result = authentication.decode_token('test_token') assert result == payload # Check all functions are called with expected params calls = [call(f=ANY, f_kwargs={'username': payload['sub'], 'token_iat_time': payload['iat']}, request_type='local_master', is_async=False, wait_for_complete=True, logger=ANY), call(f=ANY, request_type='local_master', is_async=False, wait_for_complete=True, logger=ANY)] mock_dapi.assert_has_calls(calls) mock_generate_secret.assert_called_once() mock_decode.assert_called_once_with('test_token', 'test_secret_token', algorithms=['HS256']) assert mock_distribute_function.call_count == 2 assert mock_raise_if_exc.call_count == 2
def get_agents_summary_status(agent_list=None): """Counts the number of agents by status. :param agent_list: List of agents ID's. :return: WazuhResult. """ summary = { 'active': 0, 'disconnected': 0, 'never_connected': 0, 'pending': 0, 'total': 0 } if len(agent_list) != 0: rbac_filters = get_rbac_filters(system_resources=get_agents_info(), permitted_resources=agent_list) db_query = WazuhDBQueryAgents(limit=None, select=['status'], **rbac_filters) data = db_query.run() for agent in data['items']: summary[agent['status']] += 1 summary['total'] += 1 return WazuhResult({'data': summary})
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 revoke_tokens(): """ Revoke all tokens """ change_secret() with TokenManager() as tm: tm.delete_all_rules() return WazuhResult({'msg': 'Tokens revoked successfully'})
def weekly(): """ Returns the weekly averages. :return: A dictionary for each week day. """ response = {} # 0..6 => Sunday..Saturday for i in range(7): hours = [] interactions = 0 for j in range(25): try: wfile = open(common.stats_path + '/weekly-average/' + str(i) + '/' + str(j)) data = wfile.read() if j == 24: interactions = int(data) else: hours.append(int(data)) wfile.close() except IOError: if i < 24: hours.append(0) response[DAYS[i]] = {'hours': hours, 'interactions': interactions} return WazuhResult(response)
def create_group(group_id): """Creates a group. :param group_id: Group ID. :return: Confirmation message. """ # Input Validation of group_id if not InputValidator().group(group_id): raise WazuhError(1722) group_path = path.join(common.shared_path, group_id) if group_id.lower() == "default" or path.exists(group_path): raise WazuhError(1711, extra_message=group_id) # Create group in /etc/shared group_def_path = path.join(common.shared_path, 'agent-template.conf') try: mkdir_with_mode(group_path) copyfile(group_def_path, path.join(group_path, 'agent.conf')) chown_r(group_path, common.wazuh_uid(), common.wazuh_gid()) chmod_r(group_path, 0o660) chmod(group_path, 0o770) msg = f"Group '{group_id}' created." except Exception as e: raise WazuhInternalError(1005, extra_message=str(e)) return WazuhResult({'message': msg})
async def run_as_login(request, user: str, raw: bool = False) -> web.Response: """User/password authentication to get an access token. This method should be called to get an API token using an authorization context body. This token will expire at some time. # noqa: E501 Parameters ---------- request : connexion.request user : str Name of the user who wants to be authenticated. raw : bool, optional Respond in raw format. Default `False` Returns ------- web.Response Raw or JSON response with the generated access token. """ f_kwargs = {'user_id': user, 'auth_context': await request.json()} dapi = DistributedAPI(f=preprocessor.get_permissions, f_kwargs=remove_nones_to_dict(f_kwargs), request_type='local_master', is_async=False, logger=logger ) data = raise_if_exc(await dapi.distribute_function()) token = None try: token = generate_token(user_id=user, data=data.dikt, run_as=True) except WazuhException as e: raise_if_exc(e) return web.Response(text=token, content_type='text/plain', status=200) if raw \ else web.json_response(data=WazuhResult({'data': TokenResponseModel(token=token)}), status=200, dumps=dumps)
def get_agents_summary_status(agent_list=None): """Count the number of agents by status. Parameters ---------- agent_list : list[str] List of agents ID's Returns ------- WazuhResult """ summary = { 'active': 0, 'disconnected': 0, 'never_connected': 0, 'pending': 0, 'total': 0 } if agent_list: rbac_filters = get_rbac_filters(system_resources=get_agents_info(), permitted_resources=agent_list) # We don't consider agent 000 in order to get the summary db_query = WazuhDBQueryAgents(limit=None, select=['status'], query="id!=000", **rbac_filters) data = db_query.run() for agent in data['items']: summary[agent['status']] += 1 summary['total'] += 1 return WazuhResult({'data': summary})
async def get_user_me_policies(request, pretty=False, wait_for_complete=False): """Return processed RBAC policies and rbac_mode for the current user. Parameters ---------- request : connexion.request pretty : bool, optional Show results in human-readable format wait_for_complete : bool, optional Disable timeout response Returns ------- Users information """ data = WazuhResult({ 'data': request['token_info']['rbac_policies'], 'message': "Current user processed policies information was returned" }) return web.json_response(data=data, status=200, dumps=prettify if pretty else dumps)
def revoke_current_user_tokens(): """Revoke all current user's tokens""" with TokenManager() as tm: with AuthenticationManager() as am: tm.add_user_roles_rules(users={am.get_user(common.current_user.get())['id']}) return WazuhResult({'message': f'User {common.current_user.get()} was successfully logged out'})
def hourly(): """ Returns the hourly averages. :return: Dictionary: averages and interactions. """ averages = [] interactions = 0 # What's the 24 for? for i in range(25): try: hfile = open(common.stats_path + '/hourly-average/' + str(i)) data = hfile.read() if i == 24: interactions = int(data) else: averages.append(int(data)) hfile.close() except IOError: if i < 24: averages.append(0) return WazuhResult({'averages': averages, 'interactions': interactions})
def get_file(path, validate=False): """Returns the content of a file. :param path: Relative path of file from origin :param validate: Whether to validate file content or not :return: WazuhResult """ full_path = join(common.ossec_path, path[0]) # check if file exists if not exists(full_path): raise WazuhError(1906) # validate CDB lists files if validate and re.match(r'^etc/lists', path[0]) and not validate_cdb_list(path[0]): raise WazuhError(1800, {'path': path[0]}) # validate XML files if validate and not validate_xml(path[0]): raise WazuhError(1113) try: with open(full_path) as f: output = f.read() except IOError: raise WazuhInternalError(1005) return WazuhResult({'contents': output})
def custom_hook(dct): if 'key' in dct: return HTTPSModel.from_dict(dct) elif 'error' in dct: return WazuhResult.decode_json({'result': dct, 'str_priority': 'v2'}) else: return dct
def get_status_json(): """ Returns the cluster status :return: Dictionary with the cluster status. """ return WazuhResult({'data': get_cluster_status()})
def add_agent(name=None, agent_id=None, key=None, ip='any', force_time=-1, use_only_authd=False): """Adds a new Wazuh agent. :param name: name of the new agent. :param agent_id: id of the new agent. :param ip: IP of the new agent. It can be an IP, IP/NET or ANY. :param key: key of the new agent. :param force_time: Remove old agent with same IP if disconnected since <force_time> seconds. :param use_only_authd: Force the use of authd when adding and removing agents. :return: Agent ID and Agent key. """ # Check length of agent name if len(name) > 128: raise WazuhError(1738) new_agent = Agent(name=name, ip=ip, id=agent_id, key=key, force=force_time, use_only_authd=use_only_authd) return WazuhResult({'data': {'id': new_agent.id, 'key': new_agent.key}})
def get_full_overview() -> WazuhResult: """Get information about agents. Returns ------- Dictionary with information about agents """ # We don't consider agent 000 in order to get the summary q = "id!=000" # Get information from different methods of Agent class stats_distinct_node = get_distinct_agents(fields=['node_name'], q=q).affected_items groups = get_agent_groups().affected_items stats_distinct_os = get_distinct_agents(fields=['os.name', 'os.platform', 'os.version'], q=q).affected_items stats_version = get_distinct_agents(fields=['version'], q=q).affected_items agent_summary_status = get_agents_summary_status() summary = agent_summary_status['data'] if 'data' in agent_summary_status else dict() try: last_registered_agent = [get_agents(limit=1, sort={'fields': ['dateAdd'], 'order': 'desc'}, q=q).affected_items[0]] except IndexError: # an IndexError could happen if there are not registered agents last_registered_agent = [] # combine results in an unique dictionary result = {'nodes': stats_distinct_node, 'groups': groups, 'agent_os': stats_distinct_os, 'agent_status': summary, 'agent_version': stats_version, 'last_registered_agent': last_registered_agent} return WazuhResult({'data': result})
def test_decode_token(mock_raise_if_exc, mock_submit, mock_distribute_function, mock_dapi, mock_generate_secret, mock_decode): mock_decode.return_value = deepcopy(original_payload) mock_raise_if_exc.side_effect = [ WazuhResult({ 'valid': True, 'policies': { 'value': 'test' } }), WazuhResult(security_conf) ] result = authentication.decode_token('test_token') assert result == decoded_payload # Check all functions are called with expected params calls = [ call(f=ANY, f_kwargs={ 'username': original_payload['sub'], 'token_nbf_time': original_payload['nbf'], 'run_as': False, 'roles': tuple(original_payload['rbac_roles']), 'origin_node_type': 'master' }, request_type='local_master', is_async=False, wait_for_complete=False, logger=ANY), call(f=ANY, request_type='local_master', is_async=False, wait_for_complete=False, logger=ANY) ] mock_dapi.assert_has_calls(calls) mock_generate_secret.assert_called_once() mock_decode.assert_called_once_with('test_token', 'test_secret_token', algorithms=['HS256'], audience='Wazuh API REST') assert mock_distribute_function.call_count == 2 assert mock_raise_if_exc.call_count == 2
def revoke_current_user_tokens(): """Revoke all current user's tokens""" with AuthenticationManager() as am: invalid_users_tokens( users=[am.get_user(common.current_user.get())['id']]) return WazuhResult({ 'message': f'User {common.current_user.get()} was successfully logged out' })
def test_get_status_json(): """Verify that get_status_json returns the default status information.""" result = cluster.get_status_json() expected = WazuhResult({ 'data': { "enabled": "no" if default_config['disabled'] else "yes", "running": "no" } }) assert result == expected
def get_rbac_resources(resource: str = None): """Get the RBAC resources from the catalog Parameters ---------- resource : str Show the information of the specified resource. Ex: agent:id Returns ------- dict RBAC resources """ if not resource: return WazuhResult({'data': load_spec()['x-rbac-catalog']['resources']}) else: if resource not in load_spec()['x-rbac-catalog']['resources'].keys(): raise WazuhError(4019) return WazuhResult({'data': {resource: load_spec()['x-rbac-catalog']['resources'][resource]}})
def totals(date): """ Returns the totals file. :param date: date object with the date value of the stats :return: Array of dictionaries. Each dictionary represents an hour. """ stat_filename = "" try: stat_filename = os.path.join( common.stats_path, "totals", str(date.year), MONTHS[date.month - 1], f"ossec-totals-{date.strftime('%d')}.log") stats = open(stat_filename, 'r') except IOError: raise WazuhError(1308, extra_message=stat_filename) response = [] alerts = [] for line in stats: data = line.split('-') if len(data) == 4: sigid = int(data[1]) level = int(data[2]) times = int(data[3]) alert = {'sigid': sigid, 'level': level, 'times': times} alerts.append(alert) else: data = line.split('--') if len(data) != 5: if len(data) in (0, 1): continue else: raise WazuhInternalError(1309) hour = int(data[0]) total_alerts = int(data[1]) events = int(data[2]) syscheck = int(data[3]) firewall = int(data[4]) response.append({ 'hour': hour, 'alerts': alerts, 'totalAlerts': total_alerts, 'events': events, 'syscheck': syscheck, 'firewall': firewall }) alerts = [] return WazuhResult({'data': response})
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
def manager_restart() -> WazuhResult: """Restart Wazuh manager. Send JSON message with the 'restart-wazuh' command to common.EXECQ socket. Raises ------ WazuhInternalError(1901) If the socket path doesn't exist. WazuhInternalError(1902) If there is a socket connection error. WazuhInternalError(1014) If there is a socket communication error. Returns ------- WazuhResult Confirmation message. """ lock_file = open(execq_lockfile, 'a+') fcntl.lockf(lock_file, fcntl.LOCK_EX) try: # execq socket path socket_path = common.EXECQ # json msg for restarting Wazuh manager msg = json.dumps( create_wazuh_socket_message( origin={'module': common.origin_module.get()}, command=common.RESTART_WAZUH_COMMAND, parameters={ 'extra_args': [], 'alert': {} })) # initialize socket if exists(socket_path): try: conn = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) conn.connect(socket_path) except socket.error: raise WazuhInternalError(1902) else: raise WazuhInternalError(1901) try: conn.send(msg.encode()) conn.close() except socket.error as e: raise WazuhInternalError(1014, extra_message=str(e)) finally: fcntl.lockf(lock_file, fcntl.LOCK_UN) lock_file.close() read_config.cache_clear() return WazuhResult({'message': 'Restart request sent'})
def get_wazuh_result(): return WazuhResult(dct={ "data": { "items": [{ "item1": "data1" }, { "item2": "OK" }], "message": "Everything ok" } }, str_priority=['KO', 'OK'])
def test_decode_token_ko(mock_generate_secret, mock_raise_if_exc, mock_submit, mock_distribute_function): """Assert exceptions are handled as expected inside decode_token()""" with pytest.raises(Unauthorized): authentication.decode_token(token='test_token') with patch('api.authentication.jwt.decode') as mock_decode: with patch('api.authentication.generate_secret', return_value='test_secret_token'): with patch('wazuh.core.cluster.dapi.dapi.DistributedAPI.__init__', return_value=None): with patch('wazuh.core.cluster.dapi.dapi.DistributedAPI.distribute_function'): with patch('api.authentication.raise_if_exc') as mock_raise_if_exc: mock_decode.return_value = deepcopy(original_payload) with pytest.raises(Unauthorized): mock_raise_if_exc.side_effect = [WazuhResult({'valid': False})] authentication.decode_token(token='test_token') with pytest.raises(Unauthorized): mock_raise_if_exc.side_effect = [WazuhResult({'valid': True, 'policies': {'value': 'test'}}), WazuhResult({'auth_token_exp_timeout': 3600, 'rbac_mode': 'white'})] authentication.decode_token(token='test_token')
def upload_group_file(group_list=None, file_data=None, file_name='agent.conf'): """Updates a group file. :param group_list: List of Group names. :param file_data: Relative path of temporary file to upload. :param file_name: File name to update. :return: Confirmation message. """ # We access unique group_id from list, this may change if and when we decide to add option to update files for # a list of groups group_id = group_list[0] return WazuhResult({'message': configuration.upload_group_file(group_id, file_data, file_name=file_name)})