def recover(request): agent = get_object_or_404(Agent, pk=request.data["pk"]) mode = request.data["mode"] # attempt a realtime recovery, otherwise fall back to old recovery method if mode == "tacagent" or mode == "mesh": data = {"func": "recover", "payload": {"mode": mode}} r = asyncio.run(agent.nats_cmd(data, timeout=10)) if r == "ok": return Response("Successfully completed recovery") if agent.recoveryactions.filter(last_run=None).exists(): # type: ignore return notify_error( "A recovery action is currently pending. Please wait for the next agent check-in." ) if mode == "command" and not request.data["cmd"]: return notify_error("Command is required") # if we've made it this far and realtime recovery didn't work, # tacagent service is the fallback recovery so we obv can't use that to recover itself if it's down if mode == "tacagent": return notify_error( "Requires RPC service to be functional. Please recover that first") # we should only get here if all other methods fail RecoveryAction( agent=agent, mode=mode, command=request.data["cmd"] if mode == "command" else None, ).save() return Response("Recovery will be attempted on the agent's next check-in")
def send_raw_cmd(request): agent = get_object_or_404(Agent, pk=request.data["pk"]) if not agent.has_nats: return notify_error("Requires agent version 1.1.0 or greater") timeout = int(request.data["timeout"]) data = { "func": "rawcmd", "timeout": timeout, "payload": { "command": request.data["cmd"], "shell": request.data["shell"], }, } r = asyncio.run(agent.nats_cmd(data, timeout=timeout + 2)) if r == "timeout": return notify_error("Unable to contact the agent") AuditLog.audit_raw_command( username=request.user.username, hostname=agent.hostname, cmd=request.data["cmd"], shell=request.data["shell"], ) return Response(r)
def install(request): agent = get_object_or_404(Agent, pk=request.data["pk"]) if pyver.parse(agent.version) < pyver.parse("1.4.8"): return notify_error("Requires agent v1.4.8") name = request.data["name"] action = PendingAction.objects.create( agent=agent, action_type="chocoinstall", details={ "name": name, "output": None, "installed": False }, ) nats_data = { "func": "installwithchoco", "choco_prog_name": name, "pending_action_pk": action.pk, } r = asyncio.run(agent.nats_cmd(nats_data, timeout=2)) if r != "ok": action.delete() return notify_error("Unable to contact the agent") return Response( f"{name} will be installed shortly on {agent.hostname}. Check the Pending Actions menu to see the status/output" )
def edit_service(request): data = request.data pk = data["pk"] service_name = data["sv_name"] edit_action = data["edit_action"] agent = get_object_or_404(Agent, pk=pk) if edit_action == "autodelay": kwargs = {"start_type": "auto", "start_delayed": True} elif edit_action == "auto": kwargs = {"start_type": "auto", "start_delayed": False} else: kwargs = {"start_type": edit_action} r = agent.salt_api_cmd( timeout=20, func="service.modify", arg=service_name, kwargs=kwargs, ) if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error" or not r: return notify_error("Something went wrong") return Response("ok")
def send_raw_cmd(request): agent = get_object_or_404(Agent, pk=request.data["pk"]) r = agent.salt_api_cmd( timeout=request.data["timeout"], func="cmd.run", kwargs={ "cmd": request.data["cmd"], "shell": request.data["shell"], "timeout": request.data["timeout"], }, ) if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error" or not r: return notify_error("Something went wrong") AuditLog.audit_raw_command( username=request.user.username, hostname=agent.hostname, cmd=request.data["cmd"], shell=request.data["shell"], ) logger.info(f"The command {request.data['cmd']} was sent on agent {agent.hostname}") return Response(r)
def refresh_installed(request, pk): agent = get_object_or_404(Agent, pk=pk) r = agent.salt_api_cmd( timeout=20, func="pkg.list_pkgs", kwargs={ "include_components": False, "include_updates": False }, ) if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error": return notify_error("Something went wrong") printable = set(string.printable) try: software = [{ "name": "".join(filter(lambda x: x in printable, k)), "version": "".join(filter(lambda x: x in printable, v)), } for k, v in r.items()] except Exception: return notify_error("Something went wrong") if not InstalledSoftware.objects.filter(agent=agent).exists(): InstalledSoftware(agent=agent, software=software).save() else: s = agent.installedsoftware_set.get() s.software = software s.save(update_fields=["software"]) return Response("ok")
def post(self, request): # accept the salt key agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) if agent.salt_id != request.data["saltid"]: return notify_error("Salt keys do not match") try: resp = requests.post( f"http://{settings.SALT_HOST}:8123/run", json=[{ "client": "wheel", "fun": "key.accept", "match": request.data["saltid"], "username": settings.SALT_USERNAME, "password": settings.SALT_PASSWORD, "eauth": "pam", }], timeout=30, ) except Exception: return notify_error("No communication between agent and salt-api") try: data = resp.json()["return"][0]["data"] minion = data["return"]["minions"][0] except Exception: return notify_error("Key error") if data["success"] and minion == request.data["saltid"]: return Response("Salt key was accepted") else: return notify_error("Not accepted")
def run_script(request): agent = get_object_or_404(Agent, pk=request.data["pk"]) script = get_object_or_404(Script, pk=request.data["scriptPK"]) output = request.data["output"] args = request.data["args"] req_timeout = int(request.data["timeout"]) + 3 AuditLog.audit_script_run( username=request.user.username, hostname=agent.hostname, script=script.name, ) if output == "wait": r = agent.salt_api_cmd( timeout=req_timeout, func="win_agent.run_script", kwargs={ "filepath": script.filepath, "filename": script.filename, "shell": script.shell, "timeout": request.data["timeout"], "args": args, }, ) if isinstance(r, dict): if r["stdout"]: return Response(r["stdout"]) elif r["stderr"]: return Response(r["stderr"]) else: try: r["retcode"] except KeyError: return notify_error("Something went wrong") return Response(f"Return code: {r['retcode']}") else: if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error": return notify_error("Something went wrong") else: return notify_error(str(r)) else: data = { "agentpk": agent.pk, "scriptpk": script.pk, "timeout": request.data["timeout"], "args": args, } run_script_bg_task.delay(data) return Response(f"{script.name} will now be run on {agent.hostname}")
def get(self, request, pk): agent = get_object_or_404(Agent, pk=pk) if pyver.parse(agent.version) < pyver.parse("1.1.2"): return notify_error("Requires agent version 1.1.2 or greater") r = asyncio.run(agent.nats_cmd({"func": "sysinfo"}, timeout=20)) if r != "ok": return notify_error("Unable to contact the agent") return Response("ok")
def restart_mesh(request, pk): agent = get_object_or_404(Agent, pk=pk) r = agent.salt_api_cmd(func="service.restart", arg="mesh agent", timeout=30) if r == "timeout" or r == "error": return notify_error("Unable to contact the agent") elif isinstance(r, bool) and r: return Response(f"Restarted Mesh Agent on {agent.hostname}") else: return notify_error(f"Failed to restart the Mesh Agent on {agent.hostname}")
def get_processes(request, pk): agent = get_object_or_404(Agent, pk=pk) if pyver.parse(agent.version) < pyver.parse("1.2.0"): return notify_error("Requires agent version 1.2.0 or greater") r = asyncio.run(agent.nats_cmd(data={"func": "procs"}, timeout=5)) if r == "timeout": return notify_error("Unable to contact the agent") return Response(r)
def service_detail(request, pk, svcname): agent = get_object_or_404(Agent, pk=pk) if not agent.has_nats: return notify_error("Requires agent version 1.1.0 or greater") data = {"func": "winsvcdetail", "payload": {"name": svcname}} r = asyncio.run(agent.nats_cmd(data, timeout=10)) if r == "timeout": return notify_error("Unable to contact the agent") return Response(r)
def get_processes(request, pk): agent = get_object_or_404(Agent, pk=pk) r = agent.salt_api_cmd(timeout=20, func="win_agent.get_procs") if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error": return notify_error("Something went wrong") return Response(r)
def post(self, request): agent = get_object_or_404(Agent, pk=request.data["pk"]) if not agent.has_nats: return notify_error("Requires agent version 1.1.0 or greater") r = asyncio.run(agent.nats_cmd({"func": "rebootnow"}, timeout=10)) if r != "ok": return notify_error("Unable to contact the agent") return Response("ok")
def service_detail(request, pk, svcname): agent = get_object_or_404(Agent, pk=pk) r = agent.salt_api_cmd(timeout=20, func="service.info", arg=svcname) if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error" or not r: return notify_error("Something went wrong") return Response(r)
def post(self, request): if "version" not in request.data: return notify_error("Invalid data") ver = request.data["version"] if pyver.parse(ver) < pyver.parse(settings.LATEST_AGENT_VER): return notify_error( f"Old installer detected (version {ver} ). Latest version is {settings.LATEST_AGENT_VER} Please generate a new installer from the RMM" ) return Response("ok")
def recover_mesh(request, pk): agent = get_object_or_404(Agent, pk=pk) if not agent.has_nats: return notify_error("Requires agent version 1.1.0 or greater") data = {"func": "recover", "payload": {"mode": "mesh"}} r = asyncio.run(agent.nats_cmd(data, timeout=45)) if r != "ok": return notify_error("Unable to contact the agent") return Response(f"Repaired mesh agent on {agent.hostname}")
def server_maintenance(request): from tacticalrmm.utils import reload_nats if "action" not in request.data: return notify_error("The data is incorrect") if request.data["action"] == "reload_nats": reload_nats() return Response("Nats configuration was reloaded successfully.") if request.data["action"] == "rm_orphaned_tasks": from agents.models import Agent from autotasks.tasks import remove_orphaned_win_tasks agents = Agent.objects.only("pk", "last_seen", "overdue_time", "offline_time") online = [i for i in agents if i.status == "online"] for agent in online: remove_orphaned_win_tasks.delay(agent.pk) return Response( "The task has been initiated. Check the Debug Log in the UI for progress." ) if request.data["action"] == "prune_db": from logs.models import AuditLog, PendingAction if "prune_tables" not in request.data: return notify_error("The data is incorrect.") tables = request.data["prune_tables"] records_count = 0 if "audit_logs" in tables: auditlogs = AuditLog.objects.filter(action="check_run") records_count += auditlogs.count() auditlogs.delete() if "pending_actions" in tables: pendingactions = PendingAction.objects.filter(status="completed") records_count += pendingactions.count() pendingactions.delete() if "alerts" in tables: from alerts.models import Alert alerts = Alert.objects.all() records_count += alerts.count() alerts.delete() return Response( f"{records_count} records were pruned from the database") return notify_error("The data is incorrect")
def get_refreshed_services(request, pk): agent = get_object_or_404(Agent, pk=pk) if not agent.has_nats: return notify_error("Requires agent version 1.1.0 or greater") r = asyncio.run(agent.nats_cmd(data={"func": "winservices"}, timeout=10)) if r == "timeout": return notify_error("Unable to contact the agent") agent.services = r agent.save(update_fields=["services"]) return Response(ServicesSerializer(agent).data)
def get_refreshed_services(request, pk): agent = get_object_or_404(Agent, pk=pk) r = agent.salt_api_cmd(timeout=15, func="win_agent.get_services") if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error" or not r: return notify_error("Something went wrong") agent.services = r agent.save(update_fields=["services"]) return Response(ServicesSerializer(agent).data)
def kill_proc(request, pk, pid): agent = get_object_or_404(Agent, pk=pk) r = asyncio.run( agent.nats_cmd({"func": "killproc", "procpid": int(pid)}, timeout=15) ) if r == "timeout": return notify_error("Unable to contact the agent") elif r != "ok": return notify_error(r) return Response("ok")
def kill_proc(request, pk, pid): agent = get_object_or_404(Agent, pk=pk) r = agent.salt_api_cmd(timeout=25, func="ps.kill_pid", arg=int(pid)) if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error": return notify_error("Something went wrong") if isinstance(r, bool) and not r: return notify_error("Unable to kill the process") return Response("ok")
def patch(self, request): # sync modules agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) r = agent.salt_api_cmd(timeout=45, func="saltutil.sync_modules") if r == "timeout" or r == "error": return notify_error("Failed to sync salt modules") if isinstance(r, list) and any("modules" in i for i in r): return Response("Successfully synced salt modules") elif isinstance(r, list) and not r: return Response("Modules are already in sync") else: return notify_error(f"Failed to sync salt modules: {str(r)}")
def delete(self, request, pk): site = get_object_or_404(Site, pk=pk) if site.client.sites.count() == 1: return notify_error(f"A client must have at least 1 site.") agent_count = Agent.objects.filter(site=site).count() if agent_count > 0: return notify_error( f"Cannot delete {site.name} while {agent_count} agents exist in it. Move the agents to another site first." ) site.delete() return Response(f"{site.name} was deleted!")
def run_checks(request, pk): agent = get_object_or_404(Agent, pk=pk) if pyver.parse(agent.version) >= pyver.parse("1.4.1"): r = asyncio.run(agent.nats_cmd({"func": "runchecks"}, timeout=15)) if r == "busy": return notify_error(f"Checks are already running on {agent.hostname}") elif r == "ok": return Response(f"Checks will now be re-run on {agent.hostname}") else: return notify_error("Unable to contact the agent") else: asyncio.run(agent.nats_cmd({"func": "runchecks"}, wait=False)) return Response(f"Checks will now be re-run on {agent.hostname}")
def patch(self, request, pk): check = get_object_or_404(Check, pk=pk) # remove fields that should not be changed when editing a check from the frontend if "check_alert" not in request.data.keys(): [request.data.pop(i) for i in check.non_editable_fields] # set event id to 0 if wildcard because it needs to be an integer field for db # will be ignored anyway by the agent when doing wildcard check if check.check_type == "eventlog": try: request.data["event_id_is_wildcard"] except KeyError: pass else: if request.data["event_id_is_wildcard"]: if check.agent.not_supported(version_added="0.10.2"): return notify_error({ "non_field_errors": "Wildcard is only available in agent 0.10.2 or greater" }) request.data["event_id"] = 0 elif check.check_type == "script": added = "0.11.0" try: request.data["script_args"] except KeyError: pass else: if request.data["script_args"] and check.agent.not_supported( version_added=added): return notify_error({ "non_field_errors": f"Script arguments only available in agent {added} or greater" }) serializer = CheckSerializer(instance=check, data=request.data, partial=True) serializer.is_valid(raise_exception=True) obj = serializer.save() # Update policy check fields if check.policy: update_policy_check_fields_task(checkpk=pk) return Response(f"{obj.readable_desc} was edited!")
def delete_site(request): client = get_object_or_404(Client, client=request.data["client"]) if client.sites.count() == 1: return notify_error(f"A client must have at least 1 site.") site = Site.objects.filter(client=client).filter(site=request.data["site"]).get() agents = Agent.objects.filter(client=client.client).filter(site=site.site) if agents.exists(): return notify_error( f"Cannot delete {site} while {agents.count()} agents exist in it. Move the agents to another site first." ) site.delete() return Response(f"{site} was deleted!")
def bulk(request): if request.data["target"] == "agents" and not request.data["agentPKs"]: return notify_error("Must select at least 1 agent") if request.data["target"] == "client": q = Agent.objects.filter(site__client_id=request.data["client"]) elif request.data["target"] == "site": q = Agent.objects.filter(site_id=request.data["site"]) elif request.data["target"] == "agents": q = Agent.objects.filter(pk__in=request.data["agentPKs"]) elif request.data["target"] == "all": q = Agent.objects.only("pk", "monitoring_type") else: return notify_error("Something went wrong") if request.data["monType"] == "servers": q = q.filter(monitoring_type="server") elif request.data["monType"] == "workstations": q = q.filter(monitoring_type="workstation") agents: list[int] = [agent.pk for agent in q] AuditLog.audit_bulk_action(request.user, request.data["mode"], request.data) if request.data["mode"] == "command": handle_bulk_command_task.delay(agents, request.data["cmd"], request.data["shell"], request.data["timeout"]) return Response(f"Command will now be run on {len(agents)} agents") elif request.data["mode"] == "script": script = get_object_or_404(Script, pk=request.data["scriptPK"]) handle_bulk_script_task.delay(script.pk, agents, request.data["args"], request.data["timeout"]) return Response( f"{script.name} will now be run on {len(agents)} agents") elif request.data["mode"] == "install": bulk_install_updates_task.delay(agents) return Response( f"Pending updates will now be installed on {len(agents)} agents") elif request.data["mode"] == "scan": bulk_check_for_updates_task.delay(agents) return Response( f"Patch status scan will now run on {len(agents)} agents") return notify_error("Something went wrong")
def service_action(request): data = request.data pk = data["pk"] service_name = data["sv_name"] service_action = data["sv_action"] agent = get_object_or_404(Agent, pk=pk) r = agent.salt_api_cmd( timeout=45, func=f"service.{service_action}", arg=service_name, ) if r == "timeout": return notify_error("Unable to contact the agent") elif r == "error" or not r: return notify_error("Something went wrong") return Response("ok")
def meshcentral(request, pk): agent = get_object_or_404(Agent, pk=pk) core = CoreSettings.objects.first() token = agent.get_login_token(key=core.mesh_token, user=f"user//{core.mesh_username}") if token == "err": return notify_error("Invalid mesh token") control = f"{core.mesh_site}/?login={token}&gotonode={agent.mesh_node_id}&viewmode=11&hide=31" terminal = f"{core.mesh_site}/?login={token}&gotonode={agent.mesh_node_id}&viewmode=12&hide=31" file = f"{core.mesh_site}/?login={token}&gotonode={agent.mesh_node_id}&viewmode=13&hide=31" AuditLog.audit_mesh_session(username=request.user.username, hostname=agent.hostname) ret = { "hostname": agent.hostname, "control": control, "terminal": terminal, "file": file, "status": agent.status, "client": agent.client.name, "site": agent.site.name, } return Response(ret)