move some tasks to agent, agent removal improvements. requires agent v0.9.1

This commit is contained in:
wh1te909 2020-06-14 10:13:57 +00:00
parent 3d736db8d3
commit 0914e6b28e
10 changed files with 147 additions and 206 deletions

View File

@ -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)

View File

@ -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',
),
]

View File

@ -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(

View File

@ -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):

View File

@ -12,7 +12,7 @@ urlpatterns = [
path("<pk>/meshtabs/", views.meshcentral_tabs),
path("<pk>/takecontrol/", views.take_control),
path("poweraction/", views.power_action),
path("uninstallagent/", views.uninstall_agent),
path("uninstall/", views.uninstall),
path("editagent/", views.edit_agent),
path("<pk>/geteventlog/<logtype>/<days>/", views.get_event_log),
path("getagentversions/", views.get_agent_versions),
@ -21,4 +21,5 @@ urlpatterns = [
path("<pk>/<pid>/killproc/", views.kill_proc),
path("rebootlater/", views.reboot_later),
path("installagent/", views.install_agent),
path("<int:pk>/ping/", views.ping),
]

View File

@ -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"])

View File

@ -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),

View File

@ -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 = {

View File

@ -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;
}

View File

@ -183,11 +183,7 @@
</q-item>
<q-separator />
<q-item
clickable
v-close-popup
@click.stop.prevent="showPolicyAdd(props.row.id)"
>
<q-item clickable v-close-popup @click.stop.prevent="showPolicyAdd(props.row.id)">
<q-item-section side>
<q-icon size="xs" name="policy" />
</q-item-section>
@ -195,11 +191,7 @@
</q-item>
<q-separator />
<q-item
clickable
v-close-popup
@click.stop.prevent="removeAgent(props.row.id, props.row.hostname)"
>
<q-item clickable v-close-popup @click.stop.prevent="pingAgent(props.row.id)">
<q-item-section side>
<q-icon size="xs" name="delete" />
</q-item-section>
@ -326,11 +318,7 @@
<PendingActions />
<!-- add policy modal -->
<q-dialog v-model="showPolicyAddModal">
<PolicyAdd
@close="showPolicyAddModal = false"
type="agent"
:pk="policyAddPk"
/>
<PolicyAdd @close="showPolicyAddModal = false" type="agent" :pk="policyAddPk" />
</q-dialog>
</div>
</template>
@ -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 <code style="color:red">${name}</code> 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 <code style="color:red">${hostname}</code> 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) {