1033 lines
33 KiB
Python
1033 lines
33 KiB
Python
import asyncio
|
|
import datetime as dt
|
|
import os
|
|
import random
|
|
import string
|
|
import time
|
|
|
|
from django.conf import settings
|
|
from django.db.models import Count, Exists, OuterRef, Prefetch, Q
|
|
from django.http import HttpResponse
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils import timezone as djangotime
|
|
from meshctrl.utils import get_login_token
|
|
from packaging import version as pyver
|
|
from rest_framework import serializers
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
from rest_framework.exceptions import PermissionDenied
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from core.utils import (
|
|
get_core_settings,
|
|
get_mesh_ws_url,
|
|
remove_mesh_agent,
|
|
token_is_valid,
|
|
)
|
|
from logs.models import AuditLog, DebugLog, PendingAction
|
|
from scripts.models import Script
|
|
from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task
|
|
from tacticalrmm.constants import (
|
|
AGENT_DEFER,
|
|
AGENT_TABLE_DEFER,
|
|
AGENT_STATUS_OFFLINE,
|
|
AGENT_STATUS_ONLINE,
|
|
AgentHistoryType,
|
|
AgentMonType,
|
|
AgentPlat,
|
|
CustomFieldModel,
|
|
DebugLogType,
|
|
EvtLogNames,
|
|
PAAction,
|
|
PAStatus,
|
|
)
|
|
from tacticalrmm.helpers import date_is_in_past, notify_error
|
|
from tacticalrmm.permissions import (
|
|
_has_perm_on_agent,
|
|
_has_perm_on_client,
|
|
_has_perm_on_site,
|
|
)
|
|
from tacticalrmm.utils import get_default_timezone, reload_nats
|
|
from winupdate.models import WinUpdate
|
|
from winupdate.serializers import WinUpdatePolicySerializer
|
|
from winupdate.tasks import bulk_check_for_updates_task, bulk_install_updates_task
|
|
|
|
from .models import Agent, AgentCustomField, AgentHistory, Note
|
|
from .permissions import (
|
|
AgentHistoryPerms,
|
|
AgentNotesPerms,
|
|
AgentPerms,
|
|
EvtLogPerms,
|
|
InstallAgentPerms,
|
|
ManageProcPerms,
|
|
MeshPerms,
|
|
PingAgentPerms,
|
|
RebootAgentPerms,
|
|
RecoverAgentPerms,
|
|
RunBulkPerms,
|
|
RunScriptPerms,
|
|
SendCMDPerms,
|
|
UpdateAgentPerms,
|
|
)
|
|
from .serializers import (
|
|
AgentCustomFieldSerializer,
|
|
AgentHistorySerializer,
|
|
AgentHostnameSerializer,
|
|
AgentNoteSerializer,
|
|
AgentSerializer,
|
|
AgentTableSerializer,
|
|
)
|
|
from .tasks import (
|
|
bulk_recover_agents_task,
|
|
run_script_email_results_task,
|
|
send_agent_update_task,
|
|
)
|
|
|
|
|
|
class GetAgents(APIView):
|
|
permission_classes = [IsAuthenticated, AgentPerms]
|
|
|
|
def get(self, request):
|
|
from checks.models import Check, CheckResult
|
|
|
|
monitoring_type_filter = Q()
|
|
client_site_filter = Q()
|
|
|
|
monitoring_type = request.query_params.get("monitoring_type", None)
|
|
if monitoring_type:
|
|
if monitoring_type in AgentMonType.values:
|
|
monitoring_type_filter = Q(monitoring_type=monitoring_type)
|
|
else:
|
|
return notify_error("monitoring type does not exist")
|
|
|
|
if "site" in request.query_params.keys():
|
|
client_site_filter = Q(site_id=request.query_params["site"])
|
|
elif "client" in request.query_params.keys():
|
|
client_site_filter = Q(site__client_id=request.query_params["client"])
|
|
|
|
# by default detail=true
|
|
if (
|
|
"detail" not in request.query_params.keys()
|
|
or "detail" in request.query_params.keys()
|
|
and request.query_params["detail"] == "true"
|
|
):
|
|
agents = (
|
|
Agent.objects.filter_by_role(request.user) # type: ignore
|
|
.filter(monitoring_type_filter)
|
|
.filter(client_site_filter)
|
|
.defer(*AGENT_TABLE_DEFER)
|
|
.select_related(
|
|
"site__server_policy",
|
|
"site__workstation_policy",
|
|
"site__client__server_policy",
|
|
"site__client__workstation_policy",
|
|
"policy",
|
|
"alert_template",
|
|
)
|
|
.prefetch_related(
|
|
Prefetch(
|
|
"agentchecks",
|
|
queryset=Check.objects.select_related("script"),
|
|
),
|
|
Prefetch(
|
|
"checkresults",
|
|
queryset=CheckResult.objects.select_related("assigned_check"),
|
|
),
|
|
)
|
|
.annotate(
|
|
pending_actions_count=Count(
|
|
"pendingactions",
|
|
filter=Q(pendingactions__status=PAStatus.PENDING),
|
|
)
|
|
)
|
|
.annotate(
|
|
has_patches_pending=Exists(
|
|
WinUpdate.objects.filter(
|
|
agent_id=OuterRef("pk"), action="approve", installed=False
|
|
)
|
|
)
|
|
)
|
|
)
|
|
serializer = AgentTableSerializer(agents, many=True)
|
|
|
|
# if detail=false
|
|
else:
|
|
agents = (
|
|
Agent.objects.filter_by_role(request.user) # type: ignore
|
|
.defer(*AGENT_DEFER)
|
|
.select_related("site__client")
|
|
.filter(monitoring_type_filter)
|
|
.filter(client_site_filter)
|
|
)
|
|
serializer = AgentHostnameSerializer(agents, many=True)
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class GetUpdateDeleteAgent(APIView):
|
|
permission_classes = [IsAuthenticated, AgentPerms]
|
|
|
|
class InputSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Agent
|
|
fields = [
|
|
"monitoring_type",
|
|
"description",
|
|
"overdue_email_alert",
|
|
"overdue_text_alert",
|
|
"overdue_dashboard_alert",
|
|
"offline_time",
|
|
"overdue_time",
|
|
"check_interval",
|
|
"time_zone",
|
|
"site",
|
|
]
|
|
|
|
# get agent details
|
|
def get(self, request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
return Response(AgentSerializer(agent).data)
|
|
|
|
# edit agent
|
|
def put(self, request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
|
|
s = self.InputSerializer(instance=agent, data=request.data, partial=True)
|
|
s.is_valid(raise_exception=True)
|
|
s.save()
|
|
|
|
if "winupdatepolicy" in request.data.keys():
|
|
policy = agent.winupdatepolicy.get() # type: ignore
|
|
p_serializer = WinUpdatePolicySerializer(
|
|
instance=policy, data=request.data["winupdatepolicy"][0]
|
|
)
|
|
p_serializer.is_valid(raise_exception=True)
|
|
p_serializer.save()
|
|
|
|
if "custom_fields" in request.data.keys():
|
|
|
|
for field in request.data["custom_fields"]:
|
|
|
|
custom_field = field
|
|
custom_field["agent"] = agent.pk
|
|
|
|
if AgentCustomField.objects.filter(
|
|
field=field["field"], agent=agent.pk
|
|
):
|
|
value = AgentCustomField.objects.get(
|
|
field=field["field"], agent=agent.pk
|
|
)
|
|
serializer = AgentCustomFieldSerializer(
|
|
instance=value, data=custom_field
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
else:
|
|
serializer = AgentCustomFieldSerializer(data=custom_field)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response("The agent was updated successfully")
|
|
|
|
# uninstall agent
|
|
def delete(self, request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
|
|
code = "foo"
|
|
if agent.plat == AgentPlat.LINUX:
|
|
with open(settings.LINUX_AGENT_SCRIPT, "r") as f:
|
|
code = f.read()
|
|
|
|
asyncio.run(agent.nats_cmd({"func": "uninstall", "code": code}, wait=False))
|
|
name = agent.hostname
|
|
mesh_id = agent.mesh_node_id
|
|
agent.delete()
|
|
reload_nats()
|
|
try:
|
|
uri = get_mesh_ws_url()
|
|
asyncio.run(remove_mesh_agent(uri, mesh_id))
|
|
except Exception as e:
|
|
DebugLog.error(
|
|
message=f"Unable to remove agent {name} from meshcentral database: {str(e)}",
|
|
log_type=DebugLogType.AGENT_ISSUES,
|
|
)
|
|
return Response(f"{name} will now be uninstalled.")
|
|
|
|
|
|
class AgentProcesses(APIView):
|
|
permission_classes = [IsAuthenticated, ManageProcPerms]
|
|
|
|
# list agent processes
|
|
def get(self, request, agent_id):
|
|
if getattr(settings, "DEMO", False):
|
|
from tacticalrmm.demo_views import demo_get_procs
|
|
|
|
return demo_get_procs()
|
|
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
r = asyncio.run(agent.nats_cmd(data={"func": "procs"}, timeout=5))
|
|
if r == "timeout" or r == "natsdown":
|
|
return notify_error("Unable to contact the agent")
|
|
return Response(r)
|
|
|
|
# kill agent process
|
|
def delete(self, request, agent_id, pid):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
r = asyncio.run(
|
|
agent.nats_cmd({"func": "killproc", "procpid": int(pid)}, timeout=15)
|
|
)
|
|
|
|
if r == "timeout" or r == "natsdown":
|
|
return notify_error("Unable to contact the agent")
|
|
elif r != "ok":
|
|
return notify_error(r)
|
|
|
|
return Response(f"Process with PID: {pid} was ended successfully")
|
|
|
|
|
|
class AgentMeshCentral(APIView):
|
|
permission_classes = [IsAuthenticated, MeshPerms]
|
|
|
|
# get mesh urls
|
|
def get(self, request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
core = get_core_settings()
|
|
|
|
if not core.mesh_disable_auto_login:
|
|
token = get_login_token(
|
|
key=core.mesh_token, user=f"user//{core.mesh_username}"
|
|
)
|
|
token_param = f"login={token}&"
|
|
else:
|
|
token_param = ""
|
|
|
|
control = f"{core.mesh_site}/?{token_param}gotonode={agent.mesh_node_id}&viewmode=11&hide=31"
|
|
terminal = f"{core.mesh_site}/?{token_param}gotonode={agent.mesh_node_id}&viewmode=12&hide=31"
|
|
file = f"{core.mesh_site}/?{token_param}gotonode={agent.mesh_node_id}&viewmode=13&hide=31"
|
|
|
|
AuditLog.audit_mesh_session(
|
|
username=request.user.username,
|
|
agent=agent,
|
|
debug_info={"ip": request._client_ip},
|
|
)
|
|
|
|
ret = {
|
|
"hostname": agent.hostname,
|
|
"control": control,
|
|
"terminal": terminal,
|
|
"file": file,
|
|
"status": agent.status,
|
|
"client": agent.client.name,
|
|
"site": agent.site.name,
|
|
}
|
|
return Response(ret)
|
|
|
|
# start mesh recovery
|
|
def post(self, request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
data = {"func": "recover", "payload": {"mode": "mesh"}}
|
|
r = asyncio.run(agent.nats_cmd(data, timeout=90))
|
|
if r != "ok":
|
|
return notify_error("Unable to contact the agent")
|
|
|
|
return Response(f"Repaired mesh agent on {agent.hostname}")
|
|
|
|
|
|
@api_view(["GET"])
|
|
@permission_classes([IsAuthenticated, AgentPerms])
|
|
def get_agent_versions(request):
|
|
agents = (
|
|
Agent.objects.defer(*AGENT_DEFER)
|
|
.filter_by_role(request.user) # type: ignore
|
|
.select_related("site__client")
|
|
)
|
|
return Response(
|
|
{
|
|
"versions": [settings.LATEST_AGENT_VER],
|
|
"agents": AgentHostnameSerializer(agents, many=True).data,
|
|
}
|
|
)
|
|
|
|
|
|
@api_view(["POST"])
|
|
@permission_classes([IsAuthenticated, UpdateAgentPerms])
|
|
def update_agents(request):
|
|
q = (
|
|
Agent.objects.filter_by_role(request.user) # type: ignore
|
|
.filter(agent_id__in=request.data["agent_ids"])
|
|
.only("agent_id", "version")
|
|
)
|
|
agent_ids: list[str] = [
|
|
i.agent_id
|
|
for i in q
|
|
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
|
|
]
|
|
|
|
token, _ = token_is_valid()
|
|
send_agent_update_task.delay(agent_ids=agent_ids, token=token, force=False)
|
|
return Response("ok")
|
|
|
|
|
|
@api_view(["GET"])
|
|
@permission_classes([IsAuthenticated, PingAgentPerms])
|
|
def ping(request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
status = AGENT_STATUS_OFFLINE
|
|
attempts = 0
|
|
while 1:
|
|
r = asyncio.run(agent.nats_cmd({"func": "ping"}, timeout=2))
|
|
if r == "pong":
|
|
status = AGENT_STATUS_ONLINE
|
|
break
|
|
else:
|
|
attempts += 1
|
|
time.sleep(0.5)
|
|
|
|
if attempts >= 3:
|
|
break
|
|
|
|
return Response({"name": agent.hostname, "status": status})
|
|
|
|
|
|
@api_view(["GET"])
|
|
@permission_classes([IsAuthenticated, EvtLogPerms])
|
|
def get_event_log(request, agent_id, logtype, days):
|
|
if getattr(settings, "DEMO", False):
|
|
from tacticalrmm.demo_views import demo_get_eventlog
|
|
|
|
return demo_get_eventlog()
|
|
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
timeout = 180 if logtype == EvtLogNames.SECURITY else 30
|
|
|
|
data = {
|
|
"func": "eventlog",
|
|
"timeout": timeout,
|
|
"payload": {
|
|
"logname": logtype,
|
|
"days": str(days),
|
|
},
|
|
}
|
|
r = asyncio.run(agent.nats_cmd(data, timeout=timeout + 2))
|
|
if r == "timeout" or r == "natsdown":
|
|
return notify_error("Unable to contact the agent")
|
|
|
|
return Response(r)
|
|
|
|
|
|
@api_view(["POST"])
|
|
@permission_classes([IsAuthenticated, SendCMDPerms])
|
|
def send_raw_cmd(request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
timeout = int(request.data["timeout"])
|
|
if request.data["shell"] == "custom" and request.data["custom_shell"]:
|
|
shell = request.data["custom_shell"]
|
|
else:
|
|
shell = request.data["shell"]
|
|
|
|
data = {
|
|
"func": "rawcmd",
|
|
"timeout": timeout,
|
|
"payload": {
|
|
"command": request.data["cmd"],
|
|
"shell": shell,
|
|
},
|
|
"run_as_user": request.data["run_as_user"],
|
|
}
|
|
|
|
hist = AgentHistory.objects.create(
|
|
agent=agent,
|
|
type=AgentHistoryType.CMD_RUN,
|
|
command=request.data["cmd"],
|
|
username=request.user.username[:50],
|
|
)
|
|
data["id"] = hist.pk
|
|
|
|
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,
|
|
agent=agent,
|
|
cmd=request.data["cmd"],
|
|
shell=shell,
|
|
debug_info={"ip": request._client_ip},
|
|
)
|
|
|
|
return Response(r)
|
|
|
|
|
|
class Reboot(APIView):
|
|
permission_classes = [IsAuthenticated, RebootAgentPerms]
|
|
# reboot now
|
|
def post(self, request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
r = asyncio.run(agent.nats_cmd({"func": "rebootnow"}, timeout=10))
|
|
if r != "ok":
|
|
return notify_error("Unable to contact the agent")
|
|
|
|
return Response("ok")
|
|
|
|
# reboot later
|
|
def patch(self, request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
if agent.is_posix:
|
|
return notify_error(f"Not currently implemented for {agent.plat}")
|
|
|
|
try:
|
|
obj = dt.datetime.strptime(request.data["datetime"], "%Y-%m-%dT%H:%M")
|
|
except Exception:
|
|
return notify_error("Invalid date")
|
|
|
|
if date_is_in_past(datetime_obj=obj, agent_tz=agent.timezone):
|
|
return notify_error("Date cannot be set in the past")
|
|
|
|
task_name = "TacticalRMM_SchedReboot_" + "".join(
|
|
random.choice(string.ascii_letters) for _ in range(10)
|
|
)
|
|
|
|
expire_date = obj + djangotime.timedelta(minutes=5)
|
|
|
|
nats_data = {
|
|
"func": "schedtask",
|
|
"schedtaskpayload": {
|
|
"type": "schedreboot",
|
|
"enabled": True,
|
|
"delete_expired_task_after": True,
|
|
"start_when_available": False,
|
|
"multiple_instances": 2,
|
|
"trigger": "runonce",
|
|
"name": task_name,
|
|
"start_year": int(dt.datetime.strftime(obj, "%Y")),
|
|
"start_month": int(dt.datetime.strftime(obj, "%-m")),
|
|
"start_day": int(dt.datetime.strftime(obj, "%-d")),
|
|
"start_hour": int(dt.datetime.strftime(obj, "%-H")),
|
|
"start_min": int(dt.datetime.strftime(obj, "%-M")),
|
|
"expire_year": int(expire_date.strftime("%Y")),
|
|
"expire_month": int(expire_date.strftime("%-m")),
|
|
"expire_day": int(expire_date.strftime("%-d")),
|
|
"expire_hour": int(expire_date.strftime("%-H")),
|
|
"expire_min": int(expire_date.strftime("%-M")),
|
|
},
|
|
}
|
|
|
|
r = asyncio.run(agent.nats_cmd(nats_data, timeout=10))
|
|
if r != "ok":
|
|
return notify_error(r)
|
|
|
|
details = {"taskname": task_name, "time": str(obj)}
|
|
PendingAction.objects.create(
|
|
agent=agent, action_type=PAAction.SCHED_REBOOT, details=details
|
|
)
|
|
nice_time = dt.datetime.strftime(obj, "%B %d, %Y at %I:%M %p")
|
|
return Response(
|
|
{"time": nice_time, "agent": agent.hostname, "task_name": task_name}
|
|
)
|
|
|
|
|
|
@api_view(["POST"])
|
|
@permission_classes([IsAuthenticated, InstallAgentPerms])
|
|
def install_agent(request):
|
|
from knox.models import AuthToken
|
|
|
|
from accounts.models import User
|
|
from agents.utils import get_agent_url
|
|
from core.utils import token_is_valid
|
|
|
|
client_id = request.data["client"]
|
|
site_id = request.data["site"]
|
|
version = settings.LATEST_AGENT_VER
|
|
goarch = request.data["goarch"]
|
|
plat = request.data["plat"]
|
|
|
|
if not _has_perm_on_site(request.user, site_id):
|
|
raise PermissionDenied()
|
|
|
|
codesign_token, is_valid = token_is_valid()
|
|
|
|
inno = f"tacticalagent-v{version}-{plat}-{goarch}.exe"
|
|
download_url = get_agent_url(goarch=goarch, plat=plat, token=codesign_token)
|
|
|
|
installer_user = User.objects.filter(is_installer_user=True).first()
|
|
|
|
_, token = AuthToken.objects.create(
|
|
user=installer_user, expiry=dt.timedelta(hours=request.data["expires"])
|
|
)
|
|
|
|
if request.data["installMethod"] == "exe":
|
|
from tacticalrmm.utils import generate_winagent_exe
|
|
|
|
return generate_winagent_exe(
|
|
client=client_id,
|
|
site=site_id,
|
|
agent_type=request.data["agenttype"],
|
|
rdp=request.data["rdp"],
|
|
ping=request.data["ping"],
|
|
power=request.data["power"],
|
|
goarch=goarch,
|
|
token=token,
|
|
api=request.data["api"],
|
|
file_name=request.data["fileName"],
|
|
)
|
|
|
|
elif request.data["installMethod"] == "bash":
|
|
# TODO
|
|
# linux agents are in beta for now, only available for sponsors for testing
|
|
# remove this after it's out of beta
|
|
|
|
if not is_valid:
|
|
return notify_error(
|
|
"Missing code signing token, or token is no longer valid. Please read the docs for more info."
|
|
)
|
|
|
|
from agents.utils import generate_linux_install
|
|
|
|
return generate_linux_install(
|
|
client=str(client_id),
|
|
site=str(site_id),
|
|
agent_type=request.data["agenttype"],
|
|
arch=goarch,
|
|
token=token,
|
|
api=request.data["api"],
|
|
download_url=download_url,
|
|
)
|
|
|
|
elif request.data["installMethod"] == "manual":
|
|
cmd = [
|
|
inno,
|
|
"/VERYSILENT",
|
|
"/SUPPRESSMSGBOXES",
|
|
"&&",
|
|
"ping",
|
|
"127.0.0.1",
|
|
"-n",
|
|
"5",
|
|
"&&",
|
|
r'"C:\Program Files\TacticalAgent\tacticalrmm.exe"',
|
|
"-m",
|
|
"install",
|
|
"--api",
|
|
request.data["api"],
|
|
"--client-id",
|
|
client_id,
|
|
"--site-id",
|
|
site_id,
|
|
"--agent-type",
|
|
request.data["agenttype"],
|
|
"--auth",
|
|
token,
|
|
]
|
|
|
|
if int(request.data["rdp"]):
|
|
cmd.append("--rdp")
|
|
if int(request.data["ping"]):
|
|
cmd.append("--ping")
|
|
if int(request.data["power"]):
|
|
cmd.append("--power")
|
|
|
|
resp = {
|
|
"cmd": " ".join(str(i) for i in cmd),
|
|
"url": download_url,
|
|
}
|
|
|
|
return Response(resp)
|
|
|
|
elif request.data["installMethod"] == "powershell":
|
|
|
|
ps = os.path.join(settings.BASE_DIR, "core/installer.ps1")
|
|
|
|
with open(ps, "r") as f:
|
|
text = f.read()
|
|
|
|
replace_dict = {
|
|
"innosetupchange": inno,
|
|
"clientchange": str(client_id),
|
|
"sitechange": str(site_id),
|
|
"apichange": request.data["api"],
|
|
"atypechange": request.data["agenttype"],
|
|
"powerchange": str(request.data["power"]),
|
|
"rdpchange": str(request.data["rdp"]),
|
|
"pingchange": str(request.data["ping"]),
|
|
"downloadchange": download_url,
|
|
"tokenchange": token,
|
|
}
|
|
|
|
for i, j in replace_dict.items():
|
|
text = text.replace(i, j)
|
|
|
|
file_name = "rmm-installer.ps1"
|
|
ps1 = os.path.join(settings.EXE_DIR, file_name)
|
|
|
|
if os.path.exists(ps1):
|
|
try:
|
|
os.remove(ps1)
|
|
except Exception as e:
|
|
DebugLog.error(message=str(e))
|
|
|
|
with open(ps1, "w") as f:
|
|
f.write(text)
|
|
|
|
if settings.DEBUG:
|
|
with open(ps1, "r") as f:
|
|
response = HttpResponse(f.read(), content_type="text/plain")
|
|
response["Content-Disposition"] = f"inline; filename={file_name}"
|
|
return response
|
|
else:
|
|
response = HttpResponse()
|
|
response["Content-Disposition"] = f"attachment; filename={file_name}"
|
|
response["X-Accel-Redirect"] = f"/private/exe/{file_name}"
|
|
return response
|
|
|
|
|
|
@api_view(["POST"])
|
|
@permission_classes([IsAuthenticated, RecoverAgentPerms])
|
|
def recover(request, agent_id: str) -> Response:
|
|
agent: Agent = get_object_or_404(
|
|
Agent.objects.defer(*AGENT_DEFER), agent_id=agent_id
|
|
)
|
|
mode = request.data["mode"]
|
|
|
|
if mode == "tacagent":
|
|
uri = get_mesh_ws_url()
|
|
agent.recover(mode, uri, wait=False)
|
|
return Response("Recovery will be attempted shortly")
|
|
|
|
elif mode == "mesh":
|
|
r, err = agent.recover(mode, "")
|
|
if err:
|
|
return notify_error(f"Unable to complete recovery: {r}")
|
|
|
|
return Response("Successfully completed recovery")
|
|
|
|
|
|
@api_view(["POST"])
|
|
@permission_classes([IsAuthenticated, RunScriptPerms])
|
|
def run_script(request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
script = get_object_or_404(Script, pk=request.data["script"])
|
|
output = request.data["output"]
|
|
args = request.data["args"]
|
|
run_as_user: bool = request.data["run_as_user"]
|
|
req_timeout = int(request.data["timeout"]) + 3
|
|
|
|
AuditLog.audit_script_run(
|
|
username=request.user.username,
|
|
agent=agent,
|
|
script=script.name,
|
|
debug_info={"ip": request._client_ip},
|
|
)
|
|
|
|
hist = AgentHistory.objects.create(
|
|
agent=agent,
|
|
type=AgentHistoryType.SCRIPT_RUN,
|
|
script=script,
|
|
username=request.user.username[:50],
|
|
)
|
|
history_pk = hist.pk
|
|
|
|
if output == "wait":
|
|
r = agent.run_script(
|
|
scriptpk=script.pk,
|
|
args=args,
|
|
timeout=req_timeout,
|
|
wait=True,
|
|
history_pk=history_pk,
|
|
run_as_user=run_as_user,
|
|
)
|
|
return Response(r)
|
|
|
|
elif output == "email":
|
|
emails = (
|
|
[] if request.data["emailMode"] == "default" else request.data["emails"]
|
|
)
|
|
run_script_email_results_task.delay(
|
|
agentpk=agent.pk,
|
|
scriptpk=script.pk,
|
|
nats_timeout=req_timeout,
|
|
emails=emails,
|
|
args=args,
|
|
run_as_user=run_as_user,
|
|
)
|
|
elif output == "collector":
|
|
from core.models import CustomField
|
|
|
|
r = agent.run_script(
|
|
scriptpk=script.pk,
|
|
args=args,
|
|
timeout=req_timeout,
|
|
wait=True,
|
|
history_pk=history_pk,
|
|
run_as_user=run_as_user,
|
|
)
|
|
|
|
custom_field = CustomField.objects.get(pk=request.data["custom_field"])
|
|
|
|
if custom_field.model == CustomFieldModel.AGENT:
|
|
field = custom_field.get_or_create_field_value(agent)
|
|
elif custom_field.model == CustomFieldModel.CLIENT:
|
|
field = custom_field.get_or_create_field_value(agent.client)
|
|
elif custom_field.model == CustomFieldModel.SITE:
|
|
field = custom_field.get_or_create_field_value(agent.site)
|
|
else:
|
|
return notify_error("Custom Field was invalid")
|
|
|
|
value = (
|
|
r.strip()
|
|
if request.data["save_all_output"]
|
|
else r.strip().split("\n")[-1].strip()
|
|
)
|
|
|
|
field.save_to_field(value)
|
|
return Response(r)
|
|
elif output == "note":
|
|
r = agent.run_script(
|
|
scriptpk=script.pk,
|
|
args=args,
|
|
timeout=req_timeout,
|
|
wait=True,
|
|
history_pk=history_pk,
|
|
run_as_user=run_as_user,
|
|
)
|
|
|
|
Note.objects.create(agent=agent, user=request.user, note=r)
|
|
return Response(r)
|
|
else:
|
|
agent.run_script(
|
|
scriptpk=script.pk,
|
|
args=args,
|
|
timeout=req_timeout,
|
|
history_pk=history_pk,
|
|
run_as_user=run_as_user,
|
|
)
|
|
|
|
return Response(f"{script.name} will now be run on {agent.hostname}")
|
|
|
|
|
|
class GetAddNotes(APIView):
|
|
permission_classes = [IsAuthenticated, AgentNotesPerms]
|
|
|
|
def get(self, request, agent_id=None):
|
|
if agent_id:
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
notes = Note.objects.filter(agent=agent)
|
|
else:
|
|
notes = Note.objects.filter_by_role(request.user) # type: ignore
|
|
|
|
return Response(AgentNoteSerializer(notes, many=True).data)
|
|
|
|
def post(self, request):
|
|
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
|
|
if not _has_perm_on_agent(request.user, agent.agent_id):
|
|
raise PermissionDenied()
|
|
|
|
if "note" not in request.data.keys():
|
|
return notify_error("Cannot add an empty note")
|
|
|
|
data = {
|
|
"note": request.data["note"],
|
|
"agent": agent.pk,
|
|
"user": request.user.pk,
|
|
}
|
|
|
|
serializer = AgentNoteSerializer(data=data)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
return Response("Note added!")
|
|
|
|
|
|
class GetEditDeleteNote(APIView):
|
|
permission_classes = [IsAuthenticated, AgentNotesPerms]
|
|
|
|
def get(self, request, pk):
|
|
note = get_object_or_404(Note, pk=pk)
|
|
|
|
if not _has_perm_on_agent(request.user, note.agent.agent_id):
|
|
raise PermissionDenied()
|
|
|
|
return Response(AgentNoteSerializer(note).data)
|
|
|
|
def put(self, request, pk):
|
|
note = get_object_or_404(Note, pk=pk)
|
|
|
|
if not _has_perm_on_agent(request.user, note.agent.agent_id):
|
|
raise PermissionDenied()
|
|
|
|
serializer = AgentNoteSerializer(instance=note, data=request.data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
return Response("Note edited!")
|
|
|
|
def delete(self, request, pk):
|
|
note = get_object_or_404(Note, pk=pk)
|
|
|
|
if not _has_perm_on_agent(request.user, note.agent.agent_id):
|
|
raise PermissionDenied()
|
|
|
|
note.delete()
|
|
return Response("Note was deleted!")
|
|
|
|
|
|
@api_view(["POST"])
|
|
@permission_classes([IsAuthenticated, RunBulkPerms])
|
|
def bulk(request):
|
|
if request.data["target"] == "agents" and not request.data["agents"]:
|
|
return notify_error("Must select at least 1 agent")
|
|
|
|
if request.data["target"] == "client":
|
|
if not _has_perm_on_client(request.user, request.data["client"]):
|
|
raise PermissionDenied()
|
|
q = Agent.objects.filter_by_role(request.user).filter( # type: ignore
|
|
site__client_id=request.data["client"]
|
|
)
|
|
|
|
elif request.data["target"] == "site":
|
|
if not _has_perm_on_site(request.user, request.data["site"]):
|
|
raise PermissionDenied()
|
|
q = Agent.objects.filter_by_role(request.user).filter( # type: ignore
|
|
site_id=request.data["site"]
|
|
)
|
|
|
|
elif request.data["target"] == "agents":
|
|
q = Agent.objects.filter_by_role(request.user).filter( # type: ignore
|
|
agent_id__in=request.data["agents"]
|
|
)
|
|
|
|
elif request.data["target"] == "all":
|
|
q = Agent.objects.filter_by_role(request.user).only("pk", "monitoring_type") # type: ignore
|
|
|
|
else:
|
|
return notify_error("Something went wrong")
|
|
|
|
if request.data["monType"] == "servers":
|
|
q = q.filter(monitoring_type=AgentMonType.SERVER)
|
|
elif request.data["monType"] == "workstations":
|
|
q = q.filter(monitoring_type=AgentMonType.WORKSTATION)
|
|
|
|
if request.data["osType"] == AgentPlat.WINDOWS:
|
|
q = q.filter(plat=AgentPlat.WINDOWS)
|
|
elif request.data["osType"] == AgentPlat.LINUX:
|
|
q = q.filter(plat=AgentPlat.LINUX)
|
|
|
|
agents: list[int] = [agent.pk for agent in q]
|
|
|
|
if not agents:
|
|
return notify_error("No agents where found meeting the selected criteria")
|
|
|
|
AuditLog.audit_bulk_action(
|
|
request.user,
|
|
request.data["mode"],
|
|
request.data,
|
|
debug_info={"ip": request._client_ip},
|
|
)
|
|
|
|
if request.data["mode"] == "command":
|
|
if request.data["shell"] == "custom" and request.data["custom_shell"]:
|
|
shell = request.data["custom_shell"]
|
|
else:
|
|
shell = request.data["shell"]
|
|
|
|
handle_bulk_command_task.delay(
|
|
agents,
|
|
request.data["cmd"],
|
|
shell,
|
|
request.data["timeout"],
|
|
request.user.username[:50],
|
|
request.data["run_as_user"],
|
|
)
|
|
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["script"])
|
|
handle_bulk_script_task.delay(
|
|
script.pk,
|
|
agents,
|
|
request.data["args"],
|
|
request.data["timeout"],
|
|
request.user.username[:50],
|
|
request.data["run_as_user"],
|
|
)
|
|
return Response(f"{script.name} will now be run on {len(agents)} agents")
|
|
|
|
elif request.data["mode"] == "patch":
|
|
|
|
if request.data["patchMode"] == "install":
|
|
bulk_install_updates_task.delay(agents)
|
|
return Response(
|
|
f"Pending updates will now be installed on {len(agents)} agents"
|
|
)
|
|
elif request.data["patchMode"] == "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")
|
|
|
|
|
|
@api_view(["POST"])
|
|
@permission_classes([IsAuthenticated, AgentPerms])
|
|
def agent_maintenance(request):
|
|
|
|
if request.data["type"] == "Client":
|
|
if not _has_perm_on_client(request.user, request.data["id"]):
|
|
raise PermissionDenied()
|
|
|
|
count = (
|
|
Agent.objects.filter_by_role(request.user) # type: ignore
|
|
.filter(site__client_id=request.data["id"])
|
|
.update(maintenance_mode=request.data["action"])
|
|
)
|
|
|
|
elif request.data["type"] == "Site":
|
|
if not _has_perm_on_site(request.user, request.data["id"]):
|
|
raise PermissionDenied()
|
|
|
|
count = (
|
|
Agent.objects.filter_by_role(request.user) # type: ignore
|
|
.filter(site_id=request.data["id"])
|
|
.update(maintenance_mode=request.data["action"])
|
|
)
|
|
|
|
else:
|
|
return notify_error("Invalid data")
|
|
|
|
if count:
|
|
action = "disabled" if not request.data["action"] else "enabled"
|
|
return Response(f"Maintenance mode has been {action} on {count} agents")
|
|
else:
|
|
return Response(
|
|
f"No agents have been put in maintenance mode. You might not have permissions to the resources."
|
|
)
|
|
|
|
|
|
@api_view(["GET"])
|
|
@permission_classes([IsAuthenticated, RecoverAgentPerms])
|
|
def bulk_agent_recovery(request):
|
|
bulk_recover_agents_task.delay()
|
|
return Response("Agents will now be recovered")
|
|
|
|
|
|
class WMI(APIView):
|
|
permission_classes = [IsAuthenticated, AgentPerms]
|
|
|
|
def post(self, request, agent_id):
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
r = asyncio.run(agent.nats_cmd({"func": "sysinfo"}, timeout=20))
|
|
if r != "ok":
|
|
return notify_error("Unable to contact the agent")
|
|
return Response("Agent WMI data refreshed successfully")
|
|
|
|
|
|
class AgentHistoryView(APIView):
|
|
permission_classes = [IsAuthenticated, AgentHistoryPerms]
|
|
|
|
def get(self, request, agent_id=None):
|
|
if agent_id:
|
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
|
history = AgentHistory.objects.filter(agent=agent)
|
|
else:
|
|
history = AgentHistory.objects.filter_by_role(request.user) # type: ignore
|
|
ctx = {"default_tz": get_default_timezone()}
|
|
return Response(AgentHistorySerializer(history, many=True, context=ctx).data)
|