From 0914e6b28edb9f971a8328c8c1089c7d22479664 Mon Sep 17 00:00:00 2001 From: wh1te909 Date: Sun, 14 Jun 2020 10:13:57 +0000 Subject: [PATCH] move some tasks to agent, agent removal improvements. requires agent v0.9.1 --- _modules/meshfix.py | 18 ---- .../migrations/0006_auto_20200614_0850.py | 21 ++++ api/tacticalrmm/agents/models.py | 34 ------ api/tacticalrmm/agents/tasks.py | 59 ++++++---- api/tacticalrmm/agents/urls.py | 3 +- api/tacticalrmm/agents/views.py | 51 ++++----- api/tacticalrmm/api/urls.py | 1 - api/tacticalrmm/api/views.py | 59 +--------- web/src/boot/axios.js | 6 +- web/src/components/AgentTable.vue | 101 ++++++++++-------- 10 files changed, 147 insertions(+), 206 deletions(-) delete mode 100644 _modules/meshfix.py create mode 100644 api/tacticalrmm/agents/migrations/0006_auto_20200614_0850.py diff --git a/_modules/meshfix.py b/_modules/meshfix.py deleted file mode 100644 index 047b6b71..00000000 --- a/_modules/meshfix.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import absolute_import -import psutil - - -def fixmesh(): - mesh = [ - p.info - for p in psutil.process_iter(attrs=["pid", "name"]) - if "meshagent" in p.info["name"].lower() - ] - if mesh: - try: - mesh_pid = mesh[0]["pid"] - x = psutil.Process(mesh_pid) - except Exception as e: - pass - else: - return x.cpu_percent(5) diff --git a/api/tacticalrmm/agents/migrations/0006_auto_20200614_0850.py b/api/tacticalrmm/agents/migrations/0006_auto_20200614_0850.py new file mode 100644 index 00000000..f6f6b9c9 --- /dev/null +++ b/api/tacticalrmm/agents/migrations/0006_auto_20200614_0850.py @@ -0,0 +1,21 @@ +# Generated by Django 3.0.7 on 2020-06-14 08:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('agents', '0005_agent_policy'), + ] + + operations = [ + migrations.RemoveField( + model_name='agent', + name='uninstall_inprogress', + ), + migrations.RemoveField( + model_name='agent', + name='uninstall_pending', + ), + ] diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index 0107eb4b..b3f66802 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -44,8 +44,6 @@ class Agent(models.Model): overdue_email_alert = models.BooleanField(default=False) overdue_text_alert = models.BooleanField(default=False) overdue_time = models.PositiveIntegerField(default=30) - uninstall_pending = models.BooleanField(default=False) - uninstall_inprogress = models.BooleanField(default=False) check_interval = models.PositiveIntegerField(default=120) needs_reboot = models.BooleanField(default=False) managed_by_wsus = models.BooleanField(default=False) @@ -195,7 +193,6 @@ class Agent(models.Model): except: return [{"model": "unknown", "size": "unknown", "interfaceType": "unknown"}] - def generate_checks_from_policies(self): # Clear agent checks managed by policy self.agentchecks.filter(managed_by_policy=True).delete() @@ -368,37 +365,6 @@ class Agent(models.Model): else: return {"ret": True, "msg": salt_resp, "success": False} - def create_fix_salt_task(self): - # https://github.com/wh1te909/winagent/commit/64bc96c131dbdb568e8552c85ed970d06af055df - r = self.salt_api_cmd( - hostname=self.salt_id, - timeout=30, - func="task.create_task", - arg=[ - f"name=TacticalRMM_fixsalt", - "force=True", - "action_type=Execute", - 'cmd="C:\\Program Files\\TacticalAgent\\tacticalrmm.exe"', - 'arguments="-m fixsalt"', - "trigger_type=Daily", - "start_time='10:30'", - "repeat_interval='1 hour'", - "ac_only=False", - "stop_if_on_batteries=False", - ], - ) - - try: - data = r.json() - except: - return False - else: - ret = data["return"][0][self.salt_id] - if isinstance(ret, bool) and ret: - return True - else: - return False - class AgentOutage(models.Model): agent = models.ForeignKey( diff --git a/api/tacticalrmm/agents/tasks.py b/api/tacticalrmm/agents/tasks.py index bf71c403..7a95fd79 100644 --- a/api/tacticalrmm/agents/tasks.py +++ b/api/tacticalrmm/agents/tasks.py @@ -2,6 +2,7 @@ import os import subprocess from loguru import logger from time import sleep +import requests from django.conf import settings @@ -44,32 +45,28 @@ def sync_salt_modules_task(pk): @app.task -def uninstall_agent_task(pk, wait=True): - agent = Agent.objects.get(pk=pk) - salt_id = agent.salt_id - agent.uninstall_inprogress = True - agent.save(update_fields=["uninstall_inprogress"]) - logger.info(f"{agent.hostname} uninstall task is running") - - if wait: - logger.info(f"{agent.hostname} waiting 90 seconds before uninstalling") - sleep(90) # need to give salt time to startup on the minion - +def uninstall_agent_task(salt_id): attempts = 0 error = False while 1: - attempts += 1 - r = agent.salt_api_cmd( - hostname=salt_id, timeout=60, func="win_agent.uninstall_agent" - ) - if r.json()["return"][0][salt_id] != "ok": - if attempts >= 10: - error = True - break - else: - continue + try: + r = Agent.salt_api_cmd( + hostname=salt_id, timeout=10, func="win_agent.uninstall_agent" + ) + ret = r.json()["return"][0][salt_id] + except Exception: + attempts += 1 else: + if ret != "ok": + attempts += 1 + else: + attempts = 0 + + if attempts >= 10: + error = True + break + elif attempts == 0: break if error: @@ -77,7 +74,25 @@ def uninstall_agent_task(pk, wait=True): else: logger.info(f"{salt_id} was successfully uninstalled") - return "agent uninstall" + try: + r = requests.post( + f"http://{settings.SALT_HOST}:8123/run", + json=[ + { + "client": "wheel", + "fun": "key.delete", + "match": salt_id, + "username": settings.SALT_USERNAME, + "password": settings.SALT_PASSWORD, + "eauth": "pam", + } + ], + timeout=30, + ) + except Exception: + logger.error(f"{salt_id} unable to remove salt-key") + + return "ok" def service_action(hostname, action, service): diff --git a/api/tacticalrmm/agents/urls.py b/api/tacticalrmm/agents/urls.py index ceb1e40f..e0935d7a 100644 --- a/api/tacticalrmm/agents/urls.py +++ b/api/tacticalrmm/agents/urls.py @@ -12,7 +12,7 @@ urlpatterns = [ path("/meshtabs/", views.meshcentral_tabs), path("/takecontrol/", views.take_control), path("poweraction/", views.power_action), - path("uninstallagent/", views.uninstall_agent), + path("uninstall/", views.uninstall), path("editagent/", views.edit_agent), path("/geteventlog///", views.get_event_log), path("getagentversions/", views.get_agent_versions), @@ -21,4 +21,5 @@ urlpatterns = [ path("//killproc/", views.kill_proc), path("rebootlater/", views.reboot_later), path("installagent/", views.install_agent), + path("/ping/", views.ping), ] diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index a99f66af..1c6367b9 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -21,6 +21,7 @@ from rest_framework.authentication import BasicAuthentication, TokenAuthenticati from .models import Agent from winupdate.models import WinUpdatePolicy from clients.models import Client, Site +from accounts.models import User from .serializers import AgentSerializer, AgentHostnameSerializer, AgentTableSerializer from winupdate.serializers import WinUpdatePolicySerializer @@ -65,40 +66,32 @@ def update_agents(request): return Response("ok") -@api_view(["DELETE"]) -def uninstall_agent(request): - pk = request.data["pk"] +@api_view() +def ping(request, pk): agent = get_object_or_404(Agent, pk=pk) - try: - resp = agent.salt_api_cmd(hostname=agent.salt_id, timeout=30, func="test.ping") + r = agent.salt_api_cmd(hostname=agent.salt_id, timeout=5, func="test.ping") except Exception: - agent.uninstall_pending = True - agent.save(update_fields=["uninstall_pending"]) - logger.warning( - f"{agent.hostname} is offline. It will be removed on next check-in" - ) - return Response( - {"error": "Agent offline. It will be removed on next check-in"}, - status=status.HTTP_400_BAD_REQUEST, - ) + return Response({"name": agent.hostname, "status": "offline"}) - data = resp.json() - if not data["return"][0][agent.salt_id]: - agent.uninstall_pending = True - agent.save(update_fields=["uninstall_pending"]) - return Response( - {"error": "Agent offline. It will be removed on next check-in"}, - status=status.HTTP_400_BAD_REQUEST, - ) + data = r.json()["return"][0][agent.salt_id] + if isinstance(data, bool) and data: + return Response({"name": agent.hostname, "status": "online"}) + else: + return Response({"name": agent.hostname, "status": "offline"}) - logger.info( - f"{agent.hostname} has been marked for removal and will be uninstalled shortly" - ) - uninstall_agent_task.delay(pk, wait=False) - agent.uninstall_pending = True - agent.save(update_fields=["uninstall_pending"]) - return Response("ok") + +@api_view(["DELETE"]) +def uninstall(request): + agent = get_object_or_404(Agent, pk=request.data["pk"]) + user = get_object_or_404(User, username=agent.agent_id) + salt_id = agent.salt_id + name = agent.hostname + user.delete() + agent.delete() + + uninstall_agent_task.delay(salt_id) + return Response(f"{name} will now be uninstalled.") @api_view(["PATCH"]) diff --git a/api/tacticalrmm/api/urls.py b/api/tacticalrmm/api/urls.py index e8c12166..2948fc56 100644 --- a/api/tacticalrmm/api/urls.py +++ b/api/tacticalrmm/api/urls.py @@ -7,7 +7,6 @@ urlpatterns = [ path("add/", views.add), path("token/", views.create_auth_token), path("acceptsaltkey/", views.accept_salt_key), - path("deleteagent/", views.delete_agent), path("getmeshexe/", views.get_mesh_exe), path("triggerpatchscan/", views.trigger_patch_scan), path("firstinstall/", views.on_agent_first_install), diff --git a/api/tacticalrmm/api/views.py b/api/tacticalrmm/api/views.py index f87964f6..26758a5c 100644 --- a/api/tacticalrmm/api/views.py +++ b/api/tacticalrmm/api/views.py @@ -29,7 +29,6 @@ from autotasks.models import AutomatedTask from winupdate.models import WinUpdate, WinUpdatePolicy from agents.tasks import ( - uninstall_agent_task, sync_salt_modules_task, get_wmi_detail_task, agent_recovery_email_task, @@ -144,45 +143,6 @@ def accept_salt_key(request): return Response(status=status.HTTP_400_BAD_REQUEST) -@api_view(["POST"]) -@authentication_classes((TokenAuthentication,)) -@permission_classes((IsAuthenticated,)) -def delete_agent(request): - try: - user = User.objects.get(username=request.data["agent_id"]) - agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) - saltid = agent.salt_id - user.delete() - agent.delete() - except Exception as e: - logger.error(e) - return Response( - {"error": "something went wrong"}, status=status.HTTP_400_BAD_REQUEST - ) - else: - err = "Error removing agent from salt master. Please manually remove." - try: - resp = requests.post( - "http://" + settings.SALT_HOST + ":8123/run", - json=[ - { - "client": "wheel", - "fun": "key.delete", - "match": saltid, - "username": settings.SALT_USERNAME, - "password": settings.SALT_PASSWORD, - "eauth": "pam", - } - ], - timeout=30, - ) - except requests.exceptions.Timeout: - return Response(err, status=status.HTTP_400_BAD_REQUEST) - except requests.exceptions.ConnectionError: - return Response(err, status=status.HTTP_410_GONE) - return Response("ok") - - @api_view(["POST"]) def add(request): data = request.data @@ -254,14 +214,6 @@ def on_agent_first_install(request): if not data["return"][0][agent.salt_id]: return Response("err", status=status.HTTP_400_BAD_REQUEST) - create_salt_fix = agent.create_fix_salt_task() - if not create_salt_fix: - return Response("err", status=status.HTTP_400_BAD_REQUEST) - - sleep(1) - agent.salt_api_async( - hostname=agent.salt_id, func="task.run", arg=["name='TacticalRMM_fixsalt'"], - ) get_wmi_detail_task.delay(agent.pk) get_installed_software.delay(agent.pk) check_for_updates_task.delay(agent.pk, wait=True) @@ -274,13 +226,6 @@ def on_agent_first_install(request): def hello(request): agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) - if agent.uninstall_pending: - if agent.uninstall_inprogress: - return Response("uninstallip") - else: - uninstall_agent_task.delay(agent.pk) - return Response("ok") - serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True) serializer.is_valid(raise_exception=True) serializer.save(last_seen=djangotime.now()) @@ -311,10 +256,10 @@ class CheckRunner(APIView): def get(self, request, pk): agent = get_object_or_404(Agent, pk=pk) - + if agent.policies_pending: agent.generate_checks_from_policies() - + checks = Check.objects.filter(agent__pk=pk, overriden_by_policy=False) ret = { diff --git a/web/src/boot/axios.js b/web/src/boot/axios.js index 8f10a591..9dce3d20 100644 --- a/web/src/boot/axios.js +++ b/web/src/boot/axios.js @@ -1,6 +1,10 @@ +import Vue from 'vue'; import axios from 'axios'; export default function ({ router, store }) { + + Vue.prototype.$axios = axios; + axios.defaults.baseURL = process.env.NODE_ENV === "production" ? process.env.PROD_API @@ -33,7 +37,5 @@ export default function ({ router, store }) { return Promise.reject(error); } ); - - //Vue.prototype.$axios = axios; } diff --git a/web/src/components/AgentTable.vue b/web/src/components/AgentTable.vue index 5acf918b..f0ae9e43 100644 --- a/web/src/components/AgentTable.vue +++ b/web/src/components/AgentTable.vue @@ -183,11 +183,7 @@ - + @@ -195,11 +191,7 @@ - + @@ -326,11 +318,7 @@ - + @@ -341,14 +329,14 @@ import mixins from "@/mixins/mixins"; import EditAgent from "@/components/modals/agents/EditAgent"; import RebootLater from "@/components/modals/agents/RebootLater"; import PendingActions from "@/components/modals/logs/PendingActions"; -import PolicyAdd from "@/components/automation/modals/PolicyAdd" +import PolicyAdd from "@/components/automation/modals/PolicyAdd"; export default { name: "AgentTable", props: ["frame", "columns", "tab", "filter", "userName"], - components: { - EditAgent, - RebootLater, + components: { + EditAgent, + RebootLater, PendingActions, PolicyAdd }, @@ -407,34 +395,63 @@ export default { .then(r => this.notifySuccess(`Checks will now be re-run on ${r.data}`)) .catch(e => this.notifyError("Something went wrong")); }, - removeAgent(pk, hostname) { + removeAgent(pk, name) { this.$q .dialog({ - title: "Are you sure?", - message: `Delete agent ${hostname}`, + title: `Please type ${name} to confirm deletion.`, + prompt: { + model: "", + type: "text", + isValid: val => val === name + }, cancel: true, - persistent: true + ok: { label: "Uninstall", color: "negative" }, + persistent: true, + html: true }) - .onOk(() => { - this.$q - .dialog({ - title: `Please type ${hostname} to confirm`, - prompt: { model: "", type: "text" }, - cancel: true, - persistent: true, - html: true + .onOk(val => { + const data = { pk: pk }; + this.$axios + .delete("/agents/uninstall/", { data: data }) + .then(r => { + this.notifySuccess(r.data); + setTimeout(() => { + location.reload(); + }, 2000); }) - .onOk(hostnameConfirm => { - if (hostnameConfirm !== hostname) { - this.notifyError("ERROR: Please type the correct hostname"); - } else { - const data = { pk: pk }; - axios - .delete("/agents/uninstallagent/", { data: data }) - .then(r => this.notifySuccess(`${hostname} will now be uninstalled!`)) - .catch(e => this.notifyInfo(e.response.data.error)); - } - }); + .catch(() => this.notifyError("Something went wrong")); + }); + }, + pingAgent(pk) { + this.$q.loading.show(); + this.$axios + .get(`/agents/${pk}/ping/`) + .then(r => { + this.$q.loading.hide(); + if (r.data.status === "offline") { + this.$q + .dialog({ + title: "Agent offline", + message: `${r.data.name} cannot be contacted. + Would you like to continue with the uninstall? + If so, the agent will need to be manually uninstalled from the computer.`, + cancel: { label: "No", color: "negative" }, + ok: { label: "Yes", color: "positive" }, + persistent: true + }) + .onOk(() => this.removeAgent(pk, r.data.name)) + .onCancel(() => { + return; + }); + } else if (r.data.status === "online") { + this.removeAgent(pk, r.data.name); + } else { + this.notifyError("Something went wrong"); + } + }) + .catch(e => { + this.$q.loading.hide(); + this.notifyError("Something went wrong"); }); }, rebootNow(pk, hostname) {