Files
tacticalrmm/api/tacticalrmm/apiv3/views.py
wh1te909 9f2f23fa96 blacked
2023-02-15 00:21:57 +00:00

578 lines
19 KiB
Python

import asyncio
from django.conf import settings
from django.db.models import Prefetch
from django.shortcuts import get_object_or_404
from django.utils import timezone as djangotime
from packaging import version as pyver
from rest_framework.authentication import TokenAuthentication
from rest_framework.authtoken.models import Token
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from accounts.models import User
from agents.models import Agent, AgentHistory
from agents.serializers import AgentHistorySerializer
from apiv3.utils import get_agent_config
from autotasks.models import AutomatedTask, TaskResult
from autotasks.serializers import TaskGOGetSerializer, TaskResultSerializer
from checks.constants import CHECK_DEFER, CHECK_RESULT_DEFER
from checks.models import Check, CheckResult
from checks.serializers import CheckRunnerGetSerializer
from core.utils import (
download_mesh_agent,
get_core_settings,
get_mesh_device_id,
get_mesh_ws_url,
get_meshagent_url,
)
from logs.models import DebugLog, PendingAction
from software.models import InstalledSoftware
from tacticalrmm.constants import (
AGENT_DEFER,
AgentMonType,
AgentPlat,
AuditActionType,
AuditObjType,
CheckStatus,
DebugLogType,
GoArch,
MeshAgentIdent,
PAStatus,
)
from tacticalrmm.helpers import notify_error
from tacticalrmm.utils import reload_nats
from winupdate.models import WinUpdate, WinUpdatePolicy
class CheckIn(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
# called once during tacticalagent windows service startup
def post(self, request):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=request.data["agent_id"]
)
if not agent.choco_installed:
asyncio.run(agent.nats_cmd({"func": "installchoco"}, wait=False))
asyncio.run(agent.nats_cmd({"func": "getwinupdates"}, wait=False))
return Response("ok")
class SyncMeshNodeID(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=request.data["agent_id"]
)
if agent.mesh_node_id != request.data["nodeid"]:
agent.mesh_node_id = request.data["nodeid"]
agent.save(update_fields=["mesh_node_id"])
return Response("ok")
class Choco(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=request.data["agent_id"]
)
agent.choco_installed = request.data["installed"]
agent.save(update_fields=["choco_installed"])
return Response("ok")
class WinUpdates(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def put(self, request):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=request.data["agent_id"]
)
needs_reboot: bool = request.data["needs_reboot"]
agent.needs_reboot = needs_reboot
agent.save(update_fields=["needs_reboot"])
reboot_policy: str = agent.get_patch_policy().reboot_after_install
reboot = False
if reboot_policy == "always":
reboot = True
elif needs_reboot and reboot_policy == "required":
reboot = True
if reboot:
asyncio.run(agent.nats_cmd({"func": "rebootnow"}, wait=False))
DebugLog.info(
agent=agent,
log_type=DebugLogType.WIN_UPDATES,
message=f"{agent.hostname} is rebooting after updates were installed.",
)
agent.delete_superseded_updates()
return Response("ok")
def patch(self, request):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=request.data["agent_id"]
)
u = agent.winupdates.filter(guid=request.data["guid"]).last() # type: ignore
if not u:
raise WinUpdate.DoesNotExist
success: bool = request.data["success"]
if success:
u.result = "success"
u.downloaded = True
u.installed = True
u.date_installed = djangotime.now()
u.save(
update_fields=[
"result",
"downloaded",
"installed",
"date_installed",
]
)
else:
u.result = "failed"
u.save(update_fields=["result"])
agent.delete_superseded_updates()
return Response("ok")
def post(self, request):
updates = request.data["wua_updates"]
if not updates:
return notify_error("Empty payload")
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=request.data["agent_id"]
)
for update in updates:
if agent.winupdates.filter(guid=update["guid"]).exists(): # type: ignore
u = agent.winupdates.filter(guid=update["guid"]).last() # type: ignore
u.downloaded = update["downloaded"]
u.installed = update["installed"]
u.save(update_fields=["downloaded", "installed"])
else:
try:
kb = "KB" + update["kb_article_ids"][0]
except:
continue
WinUpdate(
agent=agent,
guid=update["guid"],
kb=kb,
title=update["title"],
installed=update["installed"],
downloaded=update["downloaded"],
description=update["description"],
severity=update["severity"],
categories=update["categories"],
category_ids=update["category_ids"],
kb_article_ids=update["kb_article_ids"],
more_info_urls=update["more_info_urls"],
support_url=update["support_url"],
revision_number=update["revision_number"],
).save()
agent.delete_superseded_updates()
return Response("ok")
class SupersededWinUpdate(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=request.data["agent_id"]
)
updates = agent.winupdates.filter(guid=request.data["guid"]) # type: ignore
for u in updates:
u.delete()
return Response("ok")
class RunChecks(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, agentid):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER).prefetch_related(
Prefetch("agentchecks", queryset=Check.objects.select_related("script"))
),
agent_id=agentid,
)
checks = agent.get_checks_with_policies(exclude_overridden=True)
ret = {
"agent": agent.pk,
"check_interval": agent.check_interval,
"checks": CheckRunnerGetSerializer(
checks, context={"agent": agent}, many=True
).data,
}
return Response(ret)
class CheckRunner(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, agentid):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER).prefetch_related(
Prefetch("agentchecks", queryset=Check.objects.select_related("script"))
),
agent_id=agentid,
)
checks = agent.get_checks_with_policies(exclude_overridden=True)
run_list = [
check
for check in checks
# always run if check hasn't run yet
if not isinstance(check.check_result, CheckResult)
or not check.check_result.last_run
# see if the correct amount of seconds have passed
or (
check.check_result.last_run
< djangotime.now()
- djangotime.timedelta(
seconds=check.run_interval or agent.check_interval
)
)
]
ret = {
"agent": agent.pk,
"check_interval": agent.check_run_interval(),
"checks": CheckRunnerGetSerializer(
run_list, context={"agent": agent}, many=True
).data,
}
return Response(ret)
def patch(self, request):
if "agent_id" not in request.data.keys():
return notify_error("Agent upgrade required")
check = get_object_or_404(
Check.objects.defer(*CHECK_DEFER),
pk=request.data["id"],
)
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=request.data["agent_id"]
)
# get check result or create if doesn't exist
check_result, created = CheckResult.objects.defer(
*CHECK_RESULT_DEFER
).get_or_create(
assigned_check=check,
agent=agent,
)
if created:
check_result.save()
status = check_result.handle_check(request.data, check, agent)
if status == CheckStatus.FAILING and check.assignedtasks.exists():
for task in check.assignedtasks.all():
if task.enabled:
if task.policy:
task.run_win_task(agent)
else:
task.run_win_task()
return Response("ok")
class CheckRunnerInterval(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, agentid):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER).prefetch_related("agentchecks"),
agent_id=agentid,
)
return Response(
{"agent": agent.pk, "check_interval": agent.check_run_interval()}
)
class TaskRunner(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, pk, agentid):
agent = get_object_or_404(Agent.objects.defer(*AGENT_DEFER), agent_id=agentid)
task = get_object_or_404(AutomatedTask, pk=pk)
return Response(TaskGOGetSerializer(task, context={"agent": agent}).data)
def patch(self, request, pk, agentid):
from alerts.models import Alert
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER),
agent_id=agentid,
)
task = get_object_or_404(
AutomatedTask.objects.select_related("custom_field"), pk=pk
)
# get task result or create if doesn't exist
try:
task_result = (
TaskResult.objects.select_related("agent")
.defer("agent__services", "agent__wmi_detail")
.get(task=task, agent=agent)
)
serializer = TaskResultSerializer(
data=request.data, instance=task_result, partial=True
)
except TaskResult.DoesNotExist:
serializer = TaskResultSerializer(data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
task_result = serializer.save(last_run=djangotime.now())
AgentHistory.objects.create(
agent=agent,
type=AuditActionType.TASK_RUN,
command=task.name,
script_results=request.data,
)
# check if task is a collector and update the custom field
if task.custom_field:
if not task_result.stderr:
task_result.save_collector_results()
status = CheckStatus.PASSING
else:
status = CheckStatus.FAILING
else:
status = (
CheckStatus.FAILING if task_result.retcode != 0 else CheckStatus.PASSING
)
if task_result:
task_result.status = status
task_result.save(update_fields=["status"])
else:
task_result.status = status
task.save(update_fields=["status"])
if status == CheckStatus.PASSING:
if Alert.create_or_return_task_alert(task, agent=agent, skip_create=True):
Alert.handle_alert_resolve(task_result)
else:
Alert.handle_alert_failure(task_result)
return Response("ok")
class MeshExe(APIView):
"""Sends the mesh exe to the installer"""
def post(self, request):
match request.data:
case {"goarch": GoArch.AMD64, "plat": AgentPlat.WINDOWS}:
ident = MeshAgentIdent.WIN64
case {"goarch": GoArch.i386, "plat": AgentPlat.WINDOWS}:
ident = MeshAgentIdent.WIN32
case {"goarch": GoArch.AMD64, "plat": AgentPlat.DARWIN} | {
"goarch": GoArch.ARM64,
"plat": AgentPlat.DARWIN,
}:
ident = MeshAgentIdent.DARWIN_UNIVERSAL
case _:
return notify_error("Arch not supported")
core = get_core_settings()
try:
uri = get_mesh_ws_url()
mesh_device_id: str = asyncio.run(
get_mesh_device_id(uri, core.mesh_device_group)
)
except:
return notify_error("Unable to connect to mesh to get group id information")
dl_url = get_meshagent_url(
ident=ident,
plat=request.data["plat"],
mesh_site=core.mesh_site,
mesh_device_id=mesh_device_id,
)
try:
return download_mesh_agent(dl_url)
except:
return notify_error("Unable to download mesh agent exe")
class NewAgent(APIView):
def post(self, request):
from logs.models import AuditLog
""" Creates the agent """
if Agent.objects.filter(agent_id=request.data["agent_id"]).exists():
return notify_error(
"Agent already exists. Remove old agent first if trying to re-install"
)
agent = Agent(
agent_id=request.data["agent_id"],
hostname=request.data["hostname"],
site_id=int(request.data["site"]),
monitoring_type=request.data["monitoring_type"],
description=request.data["description"],
mesh_node_id=request.data["mesh_node_id"],
goarch=request.data["goarch"],
plat=request.data["plat"],
last_seen=djangotime.now(),
)
agent.save()
user = User.objects.create_user( # type: ignore
username=request.data["agent_id"],
agent=agent,
password=User.objects.make_random_password(60), # type: ignore
)
token = Token.objects.create(user=user)
if agent.monitoring_type == AgentMonType.WORKSTATION:
WinUpdatePolicy(agent=agent, run_time_days=[5, 6]).save()
else:
WinUpdatePolicy(agent=agent).save()
reload_nats()
# create agent install audit record
AuditLog.objects.create(
username=request.user,
agent=agent.hostname,
object_type=AuditObjType.AGENT,
action=AuditActionType.AGENT_INSTALL,
message=f"{request.user} installed new agent {agent.hostname}",
after_value=Agent.serialize(agent),
debug_info={"ip": request._client_ip},
)
ret = {"pk": agent.pk, "token": token.key}
return Response(ret)
class Software(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
sw = request.data["software"]
if not InstalledSoftware.objects.filter(agent=agent).exists():
InstalledSoftware(agent=agent, software=sw).save()
else:
s = agent.installedsoftware_set.first() # type: ignore
s.software = sw
s.save(update_fields=["software"])
return Response("ok")
class Installer(APIView):
def get(self, request):
# used to check if token is valid. will return 401 if not
return Response("ok")
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)
and "-dev" not in 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")
class ChocoResult(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def patch(self, request, pk):
action = get_object_or_404(PendingAction, pk=pk)
results: str = request.data["results"]
software_name = action.details["name"].lower()
success = [
"install",
"of",
software_name,
"was",
"successful",
"installed",
]
duplicate = [software_name, "already", "installed", "--force", "reinstall"]
installed = False
if all(x in results.lower() for x in success):
installed = True
elif all(x in results.lower() for x in duplicate):
installed = True
action.details["output"] = results
action.details["installed"] = installed
action.status = PAStatus.COMPLETED
action.save(update_fields=["details", "status"])
return Response("ok")
class AgentHistoryResult(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def patch(self, request, agentid, pk):
hist = get_object_or_404(
AgentHistory.objects.filter(agent__agent_id=agentid), pk=pk
)
s = AgentHistorySerializer(instance=hist, data=request.data, partial=True)
s.is_valid(raise_exception=True)
s.save()
return Response("ok")
class AgentConfig(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, agentid):
ret = get_agent_config()
return Response(ret._to_dict())