def test_basic_built(tmp_custom_config, config_changes_dict): # noqa F811 reset_config(tmp_custom_config.config_file_path) config_path = tmp_custom_config.config_file_path.with_suffix(".yaml") for section in config_changes_dict["replace"]: for option in config_changes_dict["replace"][section]: if section == "executor": if "ex1" not in configuration[Sections.AGENT][Sections.EXECUTORS]: configuration[Sections.AGENT][Sections.EXECUTORS]["ex1"] = {} configuration[Sections.AGENT][Sections.EXECUTORS]["ex1"][option] = config_changes_dict["replace"][ section ][option] continue elif section not in configuration: configuration[section] = {} configuration[section][option] = config_changes_dict["replace"][section][option] for section in config_changes_dict["remove"]: if "section" in config_changes_dict["remove"][section]: if section in configuration: configuration.pop(section) else: for option in config_changes_dict["remove"][section]: if ( section == "executor" and "ex1" in configuration[Sections.AGENT][Sections.EXECUTORS] and option in configuration[Sections.AGENT][Sections.EXECUTORS]["ex1"] ): configuration[Sections.AGENT][Sections.EXECUTORS]["ex1"].pop(option) elif section in configuration and option in configuration[section]: configuration[section].pop(option) save_config(config_path) if "expected_exception" in config_changes_dict: with pytest.raises(config_changes_dict["expected_exception"]): Dispatcher(None, config_path) else: Dispatcher(None, config_path)
def run(self): end = False def_value, choices = get_default_value_and_choices("Q", ["A", "E", "Q"]) while not end: value = click.prompt("Do you want to edit the [A]gent or the [E]xecutors? Do you want to [Q]uit?", type=click.Choice(choices=choices, case_sensitive=False), default=def_value) if value.upper() == "A": process_agent() elif value.upper() == "E": self.process_executors() else: process_choice_errors(value) try: if Sections.AGENT in config.instance.sections(): self.save_executors() config.control_config() end = True else: print(f"{Bcolors.FAIL}Add agent configuration{Bcolors.ENDC}") except ValueError as e: print(f"{Bcolors.FAIL}{e}{Bcolors.ENDC}") config.save_config(self.config_filepath)
async def register(self): if self.agent_token is None: registration_token = self.agent_token = config.get( Sections.TOKENS, "registration") assert registration_token is not None, "The registration token is mandatory" token_registration_url = api_url( self.host, self.api_port, postfix=f"/_api/v2/ws/{self.workspace}/agent_registration/") logger.info(f"token_registration_url: {token_registration_url}") try: token_response = await self.session.post( token_registration_url, json={ 'token': registration_token, 'name': self.agent_name }) assert token_response.status == 201 token = await token_response.json() self.agent_token = token["token"] config.set(Sections.TOKENS, "agent", self.agent_token) save_config(self.config_path) except ClientResponseError as e: if e.status == 404: logger.info( f'404 HTTP ERROR received: Workspace "{self.workspace}" not found' ) return else: logger.info(f"Unexpected error: {e}") raise e self.websocket_token = await self.reset_websocket_token()
async def register(self): if self.agent_token is None: registration_token = self.agent_token = config.get(Sections.TOKENS, "registration") assert registration_token is not None, "The registration token is mandatory" token_registration_url = api_url(self.host, self.api_port, postfix=f"/_api/v2/ws/{self.workspace}/agent_registration/", secure=self.api_ssl_enabled) logger.info(f"token_registration_url: {token_registration_url}") try: token_response = await self.session.post(token_registration_url, json={'token': registration_token, 'name': self.agent_name}, **self.api_kwargs ) token = await token_response.json() self.agent_token = token["token"] config.set(Sections.TOKENS, "agent", self.agent_token) save_config(self.config_path) except ClientResponseError as e: if e.status == 404: logger.error(f'404 HTTP ERROR received: Workspace "{self.workspace}" not found') elif e.status == 401: logger.error("Invalid registration token, please reset and retry. If the error persist, you should " "try to edit the registration token with the wizard command `faraday-dispatcher " "config-wizard`") else: logger.info(f"Unexpected error: {e}") logger.debug(msg="Exception raised", exc_info=e) return try: self.websocket_token = await self.reset_websocket_token() logger.info("Registered successfully") except ClientResponseError as e: error_msg = "Invalid agent token, please reset and retry. If the error persist, you should remove " \ f"the agent token with the wizard command `faraday-dispatcher " \ f"config-wizard`" logger.error(error_msg) self.agent_token = None logger.debug(msg="Exception raised", exc_info=e) return
async def run(self): end = False ignore_changes = False def_value, choices = get_default_value_and_choices( "Q", ["A", "E", "Q"]) while not end: value = click.prompt( "Do you want to edit the [A]gent or the [E]xecutors? Do you " "want to [Q]uit?", type=click.Choice(choices=choices, case_sensitive=False), default=def_value, ) if value.upper() == "A": process_agent() elif value.upper() == "E": await self.process_executors() else: process_choice_errors(value) try: if Sections.AGENT in config.instance: click.echo( self.status_report(sections=config.instance)) config.control_config() end = True else: if confirm_prompt( click.style( "File configuration not saved. Are you sure?", fg="yellow")): click.echo( self.status_report( sections=config.instance.sections())) end = True ignore_changes = True else: end = False except ValueError as e: click.secho(f"{e}", fg="red") if not ignore_changes: config.save_config(self.config_filepath)
def save(self): save_config(self.config_file_path)
async def register(self, registration_token=None): if not await self.check_connection(): exit(1) if self.agent_token is None: try: control_registration_token("token", registration_token) except ValueError as ex: print(f"{Bcolors.FAIL}{ex.args[0]}") logger.error(ex.args[0]) exit(1) token_registration_url = api_url( self.host, self.api_port, postfix="/_api/v3/agent_registration", secure=self.api_ssl_enabled, ) logger.info(f"token_registration_url: {token_registration_url}") try: token_response = await self.session.post( token_registration_url, json={ "token": registration_token, "name": self.agent_name, "workspaces": self.workspaces, }, **self.api_kwargs, ) token = await token_response.json() self.agent_token = token["token"] if Sections.TOKENS not in config.instance: config.instance[Sections.TOKENS] = {} config.instance[Sections.TOKENS]["agent"] = self.agent_token save_config(self.config_path) except ClientResponseError as e: if e.status == 404: logger.error("404 HTTP ERROR received: Can't connect to the server") elif e.status == 401: logger.error( "Invalid registration token, please reset and retry. " "If the error persist, you should try to edit the " "registration token with the wizard command " "`faraday-dispatcher config-wizard`\nHint: If the faraday " "version is not the expected this could fail, check " "https://github.com/infobyte/faraday_agent_dispatcher/blob/master/RELEASE.md" ) else: logger.info(f"Unexpected error: {e}") logger.debug(msg="Exception raised", exc_info=e) exit(1) except ClientConnectorError as e: logger.debug(msg="Connection con error failed", exc_info=e) logger.error("Can connect to server") try: self.websocket_token = await self.reset_websocket_token() logger.info("Registered successfully") except ClientResponseError as e: if e.status == 402: error_msg = "Unauthorized. Is your license expired or invalid?" else: error_msg = ( "Invalid agent token, please reset and retry. If " "the error persist, you should remove the agent " "token with the wizard command `faraday-dispatcher " "config-wizard`" ) logger.error(error_msg) self.agent_token = None logger.debug(msg="Exception raised", exc_info=e) exit(1)
def test_execute_agent(): session = Session() # Initial set up res = session.post(api_url(HOST, API_PORT, postfix='/_api/login'), json={ 'email': USER, 'password': PASS }) assert res.status_code == 200, res.text session_res = session.get(api_url(HOST, API_PORT, postfix='/_api/session')) res = session.post(api_url(HOST, API_PORT, postfix='/_api/v2/ws/'), json={'name': WORKSPACE}) assert res.status_code == 201, res.text res = session.get(api_url(HOST, API_PORT, postfix='/_api/v2/agent_token/')) assert res.status_code == 200, res.text token = res.json()['token'] # Config set up config.set(Sections.TOKENS, "registration", token) config.remove_option(Sections.TOKENS, "agent") config.set(Sections.SERVER, "workspace", WORKSPACE) config.set(Sections.AGENT, "agent_name", AGENT_NAME) config.set(Sections.AGENT, "executors", EXECUTOR_NAME) path_to_basic_executor = (Path(__file__).parent.parent.parent / 'data' / 'basic_executor.py') executor_section = Sections.EXECUTOR_DATA.format(EXECUTOR_NAME) params_section = Sections.EXECUTOR_PARAMS.format(EXECUTOR_NAME) for section in [executor_section, params_section]: if section not in config: config.add_section(section) config.set(Sections.EXECUTOR_DATA.format(EXECUTOR_NAME), "cmd", f"python {path_to_basic_executor}") config.set(params_section, "out", "True") [ config.set(params_section, param, "False") for param in ["count", "spare", "spaced_before", "spaced_middle", "err", "fails"] ] save_config(CONFIG_DIR) # Init dispatcher! command = [ 'faraday-dispatcher', f'--config-file={CONFIG_DIR}', f'--logdir={LOGGER_DIR}' ] p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(2) # If fails check time # Checking dispatcher connection res = session.get( api_url(HOST, API_PORT, postfix=f'/_api/v2/ws/{WORKSPACE}/agents/')) assert res.status_code == 200, res.text res_data = res.json() assert len(res_data) == 1, p.communicate(timeout=0.1) agent = res_data[0] agent_id = agent["id"] if agent_ok_status_keys_set != set(agent.keys()): print( "Keys set from agent endpoint differ from expected ones, checking if its a superset" ) assert agent_ok_status_keys_set.issubset(set(agent.keys())) for key in agent_ok_status_dict: assert agent[key] == agent_ok_status_dict[key], [ agent, agent_ok_status_dict ] # Run executor! res = session.post(api_url( HOST, API_PORT, postfix=f'/_api/v2/ws/{WORKSPACE}/agents/{agent["id"]}/run/'), json={ 'csrf_token': session_res.json()['csrf_token'], 'executorData': { "agent_id": agent_id, "executor": EXECUTOR_NAME, "args": { "out": "json" } } }) assert res.status_code == 200, res.text time.sleep(2) # If fails check time # Test results res = session.get( api_url(HOST, API_PORT, postfix=f'/_api/v2/ws/{WORKSPACE}/hosts')) host_dict = res.json() assert host_dict["total_rows"] == 1, (res.text, host_dict) host = host_dict["rows"][0]["value"] for key in host_data: if key == "hostnames": assert set(host[key]) == set(host_data[key]) else: assert host[key] == host_data[key] assert host["vulns"] == 1 res = session.get( api_url(HOST, API_PORT, postfix=f'/_api/v2/ws/{WORKSPACE}/vulns')) vuln_dict = res.json() assert vuln_dict["count"] == 1 vuln = vuln_dict["vulnerabilities"][0]["value"] for key in vuln_data: if key == 'impact': for k_key in vuln['impact']: if k_key in vuln_data['impact']: assert vuln['impact'][k_key] == vuln_data['impact'][k_key] else: assert not vuln['impact'][k_key] else: assert vuln[key] == vuln_data[key] assert vuln["target"] == host_data['ip']
def test_execute_agent(): session = Session() # Initial set up res = session.post( api_url(HOST, API_PORT, postfix="/_api/login"), json={ "email": USER, "password": PASS }, ) assert res.status_code == 200, res.text # session_res = session.get(api_url(HOST, API_PORT, postfix="/_api/session")) res = session.post(api_url(HOST, API_PORT, postfix="/_api/v2/ws/"), json={"name": WORKSPACE}) assert res.status_code == 201, res.text res = session.get(api_url(HOST, API_PORT, postfix="/_api/v2/agent_token/")) assert res.status_code == 200, res.text token = res.json()["token"] # Config set up config.set(Sections.TOKENS, "registration", token) config.remove_option(Sections.TOKENS, "agent") config.set(Sections.SERVER, "workspaces", WORKSPACE) config.set(Sections.SERVER, "ssl", SSL) config.set(Sections.AGENT, "agent_name", AGENT_NAME) config.set(Sections.AGENT, "executors", EXECUTOR_NAME) path_to_basic_executor = Path( __file__).parent.parent.parent / "data" / "basic_executor.py" executor_section = Sections.EXECUTOR_DATA.format(EXECUTOR_NAME) params_section = Sections.EXECUTOR_PARAMS.format(EXECUTOR_NAME) for section in [executor_section, params_section]: if section not in config: config.add_section(section) config.set( Sections.EXECUTOR_DATA.format(EXECUTOR_NAME), "cmd", f"python {path_to_basic_executor}", ) config.set(params_section, "out", "True") [ config.set(params_section, param, "False") for param in [ "count", "spare", "spaced_before", "spaced_middle", "err", "fails", ] ] with tempfile.TemporaryDirectory() as tempdirfile: config_pathfile = Path(tempdirfile) save_config(config_pathfile) # Init dispatcher! command = [ "faraday-dispatcher", "run", f"--config-file={config_pathfile}", f"--logdir={LOGGER_DIR}", ] p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(2) # If fails check time # Checking dispatcher connection res = session.get( api_url(HOST, API_PORT, postfix=f"/_api/v2/ws/{WORKSPACE}/agents/")) assert res.status_code == 200, res.text res_data = res.json() assert len(res_data) == 1, p.communicate(timeout=0.1) agent = res_data[0] agent_id = agent["id"] if agent_ok_status_keys_set != set(agent.keys()): print( "Keys set from agent endpoint differ from expected ones, checking if its a superset" ) print(f"agent_ok_status_keys_set= {agent_ok_status_keys_set}") print(f"agent.keys() = {agent.keys()}") assert agent_ok_status_keys_set.issubset(set(agent.keys())) for key in agent_ok_status_dict: assert agent[key] == agent_ok_status_dict[key], [ agent, agent_ok_status_dict, ] # Run executor! res = session.post( api_url( HOST, API_PORT, postfix=f'/_api/v2/ws/{WORKSPACE}/agents/{agent["id"]}/run/', ), json={ # "csrf_token": session_res.json()["csrf_token"], "executorData": { "agent_id": agent_id, "executor": EXECUTOR_NAME, "args": { "out": "json" }, }, }, ) assert res.status_code == 200, res.text command_id = res.json()["command_id"] # Command ID should be in progress! res = session.get( api_url( HOST, API_PORT, postfix=f"/_api/v2/ws/{WORKSPACE}/commands/{command_id}/", ), ) assert res.status_code == 200, res.text command_check_response = res.json() assert command_check_response["import_source"] == "agent" assert command_check_response["creator"] is None assert command_check_response["duration"] == "In progress" time.sleep(2) # If fails check time # Command ID should not be in progress! res = session.get( api_url( HOST, API_PORT, postfix=f"/_api/v2/ws/{WORKSPACE}/commands/{command_id}/", ), ) assert res.status_code == 200, res.text command_check_response = res.json() assert command_check_response["duration"] != "In progress" # Test results res = session.get( api_url(HOST, API_PORT, postfix=f"/_api/v2/ws/{WORKSPACE}/hosts")) host_dict = res.json() assert host_dict["count"] == 1, (res.text, host_dict) host = host_dict["rows"][0]["value"] for key in host_data: if key == "hostnames": assert set(host[key]) == set(host_data[key]) else: assert host[key] == host_data[key] assert host["vulns"] == 1 res = session.get( api_url(HOST, API_PORT, postfix=f"/_api/v2/ws/{WORKSPACE}/vulns")) vuln_dict = res.json() assert vuln_dict["count"] == 1 vuln = vuln_dict["vulnerabilities"][0]["value"] for key in vuln_data: if key == "impact": for k_key in vuln["impact"]: if k_key in vuln_data["impact"]: assert vuln["impact"][k_key] == vuln_data["impact"][ k_key] else: assert not vuln["impact"][k_key] else: assert vuln[key] == vuln_data[key] assert vuln["target"] == host_data["ip"]