Compare commits

...

21 Commits

Author SHA1 Message Date
wh1te909
d82f0cd757 Release 0.4.5 2021-02-01 20:57:53 +00:00
wh1te909
5f529e2af4 bump versions 2021-02-01 20:57:35 +00:00
wh1te909
beadd9e02b fix duplicate pending actions being created 2021-02-01 20:56:05 +00:00
wh1te909
72543789cb Release 0.4.4 2021-02-01 19:24:51 +00:00
wh1te909
5789439fa9 bump versions 2021-02-01 19:23:03 +00:00
wh1te909
f549126bcf update natsapi 2021-02-01 19:20:32 +00:00
wh1te909
7197548bad new pipelines vm 2021-01-31 02:42:10 +00:00
wh1te909
241fde783c add back pending actions for agent updates 2021-01-31 02:06:55 +00:00
wh1te909
2b872cd1f4 remove old views 2021-01-31 00:19:10 +00:00
wh1te909
a606fb4d1d add some deps to install for stripped down vps [skip ci] 2021-01-30 21:31:01 +00:00
wh1te909
9f9c6be38e update natsapi [skip ci] github.com/wh1te909/rmmagent@47b25c29362f0639ec606571f679df1f523e69a9 2021-01-30 06:42:20 +00:00
wh1te909
01ee524049 Release 0.4.3 2021-01-30 04:45:10 +00:00
wh1te909
af9cb65338 bump version 2021-01-30 04:44:41 +00:00
wh1te909
8aa11c580b move agents monitor task to go 2021-01-30 04:39:15 +00:00
wh1te909
ada627f444 forgot to enable natsapi during install 2021-01-30 04:28:27 +00:00
wh1te909
a7b6d338c3 update reqs 2021-01-30 02:06:56 +00:00
wh1te909
9f00538b97 fix tests 2021-01-29 23:38:59 +00:00
wh1te909
a085015282 increase timeout for security eventlogs 2021-01-29 23:34:16 +00:00
wh1te909
0b9c220fbb remove old task 2021-01-29 20:36:28 +00:00
wh1te909
0e3d04873d move wmi celery task to golang 2021-01-29 20:10:52 +00:00
wh1te909
b7578d939f add test for community script shell type 2021-01-29 09:37:34 +00:00
28 changed files with 435 additions and 664 deletions

View File

@@ -278,15 +278,11 @@ class TestUserAction(TacticalTestCase):
r = self.client.patch(url, data, format="json") r = self.client.patch(url, data, format="json")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
data = {"agent_dblclick_action": "editagent"} data = {
r = self.client.patch(url, data, format="json") "userui": True,
self.assertEqual(r.status_code, 200) "agent_dblclick_action": "editagent",
"default_agent_tbl_tab": "mixed",
data = {"agent_dblclick_action": "remotebg"} }
r = self.client.patch(url, data, format="json")
self.assertEqual(r.status_code, 200)
data = {"agent_dblclick_action": "takecontrol"}
r = self.client.patch(url, data, format="json") r = self.client.patch(url, data, format="json")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)

View File

@@ -16,52 +16,20 @@ from logs.models import PendingAction
logger.configure(**settings.LOG_CONFIG) logger.configure(**settings.LOG_CONFIG)
def _check_agent_service(pk: int) -> None:
agent = Agent.objects.get(pk=pk)
r = asyncio.run(agent.nats_cmd({"func": "ping"}, timeout=2))
# if the agent is respoding to pong from the rpc service but is not showing as online (handled by tacticalagent service)
# then tacticalagent service is hung. forcefully restart it
if r == "pong":
logger.info(
f"Detected crashed tacticalagent service on {agent.hostname} v{agent.version}, attempting recovery"
)
data = {"func": "recover", "payload": {"mode": "tacagent"}}
asyncio.run(agent.nats_cmd(data, wait=False))
def _check_in_full(pk: int) -> None:
agent = Agent.objects.get(pk=pk)
asyncio.run(agent.nats_cmd({"func": "checkinfull"}, wait=False))
@app.task
def check_in_task() -> None:
q = Agent.objects.only("pk", "version")
agents: List[int] = [
i.pk for i in q if pyver.parse(i.version) == pyver.parse("1.1.12")
]
chunks = (agents[i : i + 50] for i in range(0, len(agents), 50))
for chunk in chunks:
for pk in chunk:
_check_in_full(pk)
sleep(0.1)
rand = random.randint(3, 7)
sleep(rand)
@app.task
def monitor_agents_task() -> None:
q = Agent.objects.only("pk", "version", "last_seen", "overdue_time")
agents: List[int] = [i.pk for i in q if i.has_nats and i.status != "online"]
for agent in agents:
_check_agent_service(agent)
def agent_update(pk: int) -> str: def agent_update(pk: int) -> str:
agent = Agent.objects.get(pk=pk) agent = Agent.objects.get(pk=pk)
if pyver.parse(agent.version) <= pyver.parse("1.1.11"):
logger.warning(
f"{agent.hostname} v{agent.version} is running an unsupported version. Refusing to auto update."
)
return "not supported"
# skip if we can't determine the arch # skip if we can't determine the arch
if agent.arch is None: if agent.arch is None:
logger.warning(f"Unable to determine arch on {agent.hostname}. Skipping.") logger.warning(
f"Unable to determine arch on {agent.hostname}. Skipping agent update."
)
return "noarch" return "noarch"
# removed sqlite in 1.4.0 to get rid of cgo dependency # removed sqlite in 1.4.0 to get rid of cgo dependency
@@ -77,55 +45,38 @@ def agent_update(pk: int) -> str:
) )
url = f"https://github.com/wh1te909/rmmagent/releases/download/v1.3.0/{inno}" url = f"https://github.com/wh1te909/rmmagent/releases/download/v1.3.0/{inno}"
if agent.has_nats: if agent.pendingactions.filter(
if pyver.parse(agent.version) <= pyver.parse("1.1.11"): action_type="agentupdate", status="pending"
if agent.pendingactions.filter( ).exists():
action_type="agentupdate", status="pending" agent.pendingactions.filter(
).exists(): action_type="agentupdate", status="pending"
action = agent.pendingactions.filter( ).delete()
action_type="agentupdate", status="pending"
).last()
if pyver.parse(action.details["version"]) < pyver.parse(version):
action.delete()
else:
return "pending"
PendingAction.objects.create( PendingAction.objects.create(
agent=agent, agent=agent,
action_type="agentupdate", action_type="agentupdate",
details={ details={
"url": url, "url": url,
"version": version, "version": version,
"inno": inno, "inno": inno,
}, },
) )
else:
nats_data = {
"func": "agentupdate",
"payload": {
"url": url,
"version": version,
"inno": inno,
},
}
asyncio.run(agent.nats_cmd(nats_data, wait=False))
return "created" nats_data = {
else: "func": "agentupdate",
logger.warning( "payload": {
f"{agent.hostname} v{agent.version} is running an unsupported version. Refusing to update." "url": url,
) "version": version,
"inno": inno,
return "not supported" },
}
asyncio.run(agent.nats_cmd(nats_data, wait=False))
return "created"
@app.task @app.task
def send_agent_update_task(pks: List[int], version: str) -> None: def send_agent_update_task(pks: List[int]) -> None:
q = Agent.objects.filter(pk__in=pks) chunks = (pks[i : i + 30] for i in range(0, len(pks), 30))
agents: List[int] = [
i.pk for i in q if pyver.parse(i.version) < pyver.parse(version)
]
chunks = (agents[i : i + 30] for i in range(0, len(agents), 30))
for chunk in chunks: for chunk in chunks:
for pk in chunk: for pk in chunk:
agent_update(pk) agent_update(pk)
@@ -154,43 +105,6 @@ def auto_self_agent_update_task() -> None:
sleep(4) sleep(4)
@app.task
def get_wmi_task():
agents = Agent.objects.only("pk", "version", "last_seen", "overdue_time")
online = [
i
for i in agents
if pyver.parse(i.version) >= pyver.parse("1.2.0") and i.status == "online"
]
chunks = (online[i : i + 50] for i in range(0, len(online), 50))
for chunk in chunks:
for agent in chunk:
asyncio.run(agent.nats_cmd({"func": "wmi"}, wait=False))
sleep(0.1)
rand = random.randint(3, 7)
sleep(rand)
@app.task
def sync_sysinfo_task():
agents = Agent.objects.only("pk", "version", "last_seen", "overdue_time")
online = [
i
for i in agents
if pyver.parse(i.version) >= pyver.parse("1.1.3")
and pyver.parse(i.version) <= pyver.parse("1.1.12")
and i.status == "online"
]
chunks = (online[i : i + 50] for i in range(0, len(online), 50))
for chunk in chunks:
for agent in chunk:
asyncio.run(agent.nats_cmd({"func": "sync"}, wait=False))
sleep(0.1)
rand = random.randint(3, 7)
sleep(rand)
@app.task @app.task
def agent_outage_email_task(pk): def agent_outage_email_task(pk):
sleep(random.randint(1, 15)) sleep(random.randint(1, 15))

View File

@@ -4,16 +4,19 @@ from unittest.mock import patch
from model_bakery import baker from model_bakery import baker
from itertools import cycle from itertools import cycle
from typing import List
from packaging import version as pyver
from django.test import TestCase, override_settings
from django.conf import settings from django.conf import settings
from django.utils import timezone as djangotime
from logs.models import PendingAction from logs.models import PendingAction
from tacticalrmm.test import TacticalTestCase from tacticalrmm.test import TacticalTestCase
from .serializers import AgentSerializer from .serializers import AgentSerializer
from winupdate.serializers import WinUpdatePolicySerializer from winupdate.serializers import WinUpdatePolicySerializer
from .models import Agent from .models import Agent
from .tasks import auto_self_agent_update_task
from winupdate.models import WinUpdatePolicy from winupdate.models import WinUpdatePolicy
@@ -64,12 +67,34 @@ class TestAgentViews(TacticalTestCase):
@patch("agents.tasks.send_agent_update_task.delay") @patch("agents.tasks.send_agent_update_task.delay")
def test_update_agents(self, mock_task): def test_update_agents(self, mock_task):
url = "/agents/updateagents/" url = "/agents/updateagents/"
data = {"pks": [1, 2, 3, 5, 10], "version": "0.11.1"} baker.make_recipe(
"agents.agent",
operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
version=settings.LATEST_AGENT_VER,
_quantity=15,
)
baker.make_recipe(
"agents.agent",
operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
version="1.3.0",
_quantity=15,
)
pks: List[int] = list(
Agent.objects.only("pk", "version").values_list("pk", flat=True)
)
data = {"pks": pks}
expected: List[int] = [
i.pk
for i in Agent.objects.only("pk", "version")
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
]
r = self.client.post(url, data, format="json") r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
mock_task.assert_called_with(pks=data["pks"], version=data["version"]) mock_task.assert_called_with(pks=expected)
self.check_not_authenticated("post", url) self.check_not_authenticated("post", url)
@@ -162,18 +187,44 @@ class TestAgentViews(TacticalTestCase):
self.check_not_authenticated("get", url) self.check_not_authenticated("get", url)
@patch("agents.models.Agent.nats_cmd") @patch("agents.models.Agent.nats_cmd")
def test_get_event_log(self, mock_ret): def test_get_event_log(self, nats_cmd):
url = f"/agents/{self.agent.pk}/geteventlog/Application/30/" url = f"/agents/{self.agent.pk}/geteventlog/Application/22/"
with open( with open(
os.path.join(settings.BASE_DIR, "tacticalrmm/test_data/appeventlog.json") os.path.join(settings.BASE_DIR, "tacticalrmm/test_data/appeventlog.json")
) as f: ) as f:
mock_ret.return_value = json.load(f) nats_cmd.return_value = json.load(f)
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
nats_cmd.assert_called_with(
{
"func": "eventlog",
"timeout": 30,
"payload": {
"logname": "Application",
"days": str(22),
},
},
timeout=32,
)
mock_ret.return_value = "timeout" url = f"/agents/{self.agent.pk}/geteventlog/Security/6/"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
nats_cmd.assert_called_with(
{
"func": "eventlog",
"timeout": 180,
"payload": {
"logname": "Security",
"days": str(6),
},
},
timeout=182,
)
nats_cmd.return_value = "timeout"
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
@@ -736,26 +787,28 @@ class TestAgentTasks(TacticalTestCase):
agent_noarch = baker.make_recipe( agent_noarch = baker.make_recipe(
"agents.agent", "agents.agent",
operating_system="Error getting OS", operating_system="Error getting OS",
version="1.1.11", version=settings.LATEST_AGENT_VER,
) )
r = agent_update(agent_noarch.pk) r = agent_update(agent_noarch.pk)
self.assertEqual(r, "noarch") self.assertEqual(r, "noarch")
self.assertEqual(
PendingAction.objects.filter(
agent=agent_noarch, action_type="agentupdate"
).count(),
0,
)
agent64_111 = baker.make_recipe( agent_1111 = baker.make_recipe(
"agents.agent", "agents.agent",
operating_system="Windows 10 Pro, 64 bit (build 19041.450)", operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
version="1.1.11", version="1.1.11",
) )
r = agent_update(agent_1111.pk)
self.assertEqual(r, "not supported")
r = agent_update(agent64_111.pk) agent64_1112 = baker.make_recipe(
"agents.agent",
operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
version="1.1.12",
)
r = agent_update(agent64_1112.pk)
self.assertEqual(r, "created") self.assertEqual(r, "created")
action = PendingAction.objects.get(agent__pk=agent64_111.pk) action = PendingAction.objects.get(agent__pk=agent64_1112.pk)
self.assertEqual(action.action_type, "agentupdate") self.assertEqual(action.action_type, "agentupdate")
self.assertEqual(action.status, "pending") self.assertEqual(action.status, "pending")
self.assertEqual( self.assertEqual(
@@ -764,6 +817,17 @@ class TestAgentTasks(TacticalTestCase):
) )
self.assertEqual(action.details["inno"], "winagent-v1.3.0.exe") self.assertEqual(action.details["inno"], "winagent-v1.3.0.exe")
self.assertEqual(action.details["version"], "1.3.0") self.assertEqual(action.details["version"], "1.3.0")
nats_cmd.assert_called_with(
{
"func": "agentupdate",
"payload": {
"url": "https://github.com/wh1te909/rmmagent/releases/download/v1.3.0/winagent-v1.3.0.exe",
"version": "1.3.0",
"inno": "winagent-v1.3.0.exe",
},
},
wait=False,
)
agent_64_130 = baker.make_recipe( agent_64_130 = baker.make_recipe(
"agents.agent", "agents.agent",
@@ -784,128 +848,34 @@ class TestAgentTasks(TacticalTestCase):
}, },
wait=False, wait=False,
) )
action = PendingAction.objects.get(agent__pk=agent_64_130.pk)
self.assertEqual(action.action_type, "agentupdate")
self.assertEqual(action.status, "pending")
agent64_old = baker.make_recipe( @patch("agents.tasks.agent_update")
"agents.agent",
operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
version="1.2.1",
)
nats_cmd.return_value = "ok"
r = agent_update(agent64_old.pk)
self.assertEqual(r, "created")
nats_cmd.assert_called_with(
{
"func": "agentupdate",
"payload": {
"url": "https://github.com/wh1te909/rmmagent/releases/download/v1.3.0/winagent-v1.3.0.exe",
"version": "1.3.0",
"inno": "winagent-v1.3.0.exe",
},
},
wait=False,
)
""" @patch("agents.models.Agent.salt_api_async")
@patch("agents.tasks.sleep", return_value=None) @patch("agents.tasks.sleep", return_value=None)
def test_auto_self_agent_update_task(self, mock_sleep, salt_api_async): def test_auto_self_agent_update_task(self, mock_sleep, agent_update):
# test 64bit golang agent baker.make_recipe(
self.agent64 = baker.make_recipe(
"agents.agent", "agents.agent",
operating_system="Windows 10 Pro, 64 bit (build 19041.450)", operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
version="1.0.0", version=settings.LATEST_AGENT_VER,
_quantity=23,
) )
salt_api_async.return_value = True baker.make_recipe(
ret = auto_self_agent_update_task.s().apply()
salt_api_async.assert_called_with(
func="win_agent.do_agent_update_v2",
kwargs={
"inno": f"winagent-v{settings.LATEST_AGENT_VER}.exe",
"url": settings.DL_64,
},
)
self.assertEqual(ret.status, "SUCCESS")
self.agent64.delete()
salt_api_async.reset_mock()
# test 32bit golang agent
self.agent32 = baker.make_recipe(
"agents.agent",
operating_system="Windows 7 Professional, 32 bit (build 7601.24544)",
version="1.0.0",
)
salt_api_async.return_value = True
ret = auto_self_agent_update_task.s().apply()
salt_api_async.assert_called_with(
func="win_agent.do_agent_update_v2",
kwargs={
"inno": f"winagent-v{settings.LATEST_AGENT_VER}-x86.exe",
"url": settings.DL_32,
},
)
self.assertEqual(ret.status, "SUCCESS")
self.agent32.delete()
salt_api_async.reset_mock()
# test agent that has a null os field
self.agentNone = baker.make_recipe(
"agents.agent",
operating_system=None,
version="1.0.0",
)
ret = auto_self_agent_update_task.s().apply()
salt_api_async.assert_not_called()
self.agentNone.delete()
salt_api_async.reset_mock()
# test auto update disabled in global settings
self.agent64 = baker.make_recipe(
"agents.agent", "agents.agent",
operating_system="Windows 10 Pro, 64 bit (build 19041.450)", operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
version="1.0.0", version="1.3.0",
_quantity=33,
) )
self.coresettings.agent_auto_update = False self.coresettings.agent_auto_update = False
self.coresettings.save(update_fields=["agent_auto_update"]) self.coresettings.save(update_fields=["agent_auto_update"])
ret = auto_self_agent_update_task.s().apply()
salt_api_async.assert_not_called()
# reset core settings r = auto_self_agent_update_task.s().apply()
self.agent64.delete() self.assertEqual(agent_update.call_count, 0)
salt_api_async.reset_mock()
self.coresettings.agent_auto_update = True self.coresettings.agent_auto_update = True
self.coresettings.save(update_fields=["agent_auto_update"]) self.coresettings.save(update_fields=["agent_auto_update"])
# test 64bit python agent r = auto_self_agent_update_task.s().apply()
self.agent64py = baker.make_recipe( self.assertEqual(agent_update.call_count, 33)
"agents.agent",
operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
version="0.11.1",
)
salt_api_async.return_value = True
ret = auto_self_agent_update_task.s().apply()
salt_api_async.assert_called_with(
func="win_agent.do_agent_update_v2",
kwargs={
"inno": "winagent-v0.11.2.exe",
"url": OLD_64_PY_AGENT,
},
)
self.assertEqual(ret.status, "SUCCESS")
self.agent64py.delete()
salt_api_async.reset_mock()
# test 32bit python agent
self.agent32py = baker.make_recipe(
"agents.agent",
operating_system="Windows 7 Professional, 32 bit (build 7601.24544)",
version="0.11.1",
)
salt_api_async.return_value = True
ret = auto_self_agent_update_task.s().apply()
salt_api_async.assert_called_with(
func="win_agent.do_agent_update_v2",
kwargs={
"inno": "winagent-v0.11.2-x86.exe",
"url": OLD_32_PY_AGENT,
},
)
self.assertEqual(ret.status, "SUCCESS") """

View File

@@ -59,9 +59,13 @@ def get_agent_versions(request):
@api_view(["POST"]) @api_view(["POST"])
def update_agents(request): def update_agents(request):
pks = request.data["pks"] q = Agent.objects.filter(pk__in=request.data["pks"]).only("pk", "version")
version = request.data["version"] pks: List[int] = [
send_agent_update_task.delay(pks=pks, version=version) i.pk
for i in q
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
]
send_agent_update_task.delay(pks=pks)
return Response("ok") return Response("ok")
@@ -184,15 +188,16 @@ def get_event_log(request, pk, logtype, days):
agent = get_object_or_404(Agent, pk=pk) agent = get_object_or_404(Agent, pk=pk)
if not agent.has_nats: if not agent.has_nats:
return notify_error("Requires agent version 1.1.0 or greater") return notify_error("Requires agent version 1.1.0 or greater")
timeout = 180 if logtype == "Security" else 30
data = { data = {
"func": "eventlog", "func": "eventlog",
"timeout": 30, "timeout": timeout,
"payload": { "payload": {
"logname": logtype, "logname": logtype,
"days": str(days), "days": str(days),
}, },
} }
r = asyncio.run(agent.nats_cmd(data, timeout=32)) r = asyncio.run(agent.nats_cmd(data, timeout=timeout + 2))
if r == "timeout": if r == "timeout":
return notify_error("Unable to contact the agent") return notify_error("Unable to contact the agent")

View File

@@ -26,21 +26,6 @@ class TestAPIv3(TacticalTestCase):
self.check_not_authenticated("get", url) self.check_not_authenticated("get", url)
def test_get_mesh_info(self):
url = f"/api/v3/{self.agent.pk}/meshinfo/"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.check_not_authenticated("get", url)
def test_get_winupdater(self):
url = f"/api/v3/{self.agent.agent_id}/winupdater/"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.check_not_authenticated("get", url)
def test_sysinfo(self): def test_sysinfo(self):
# TODO replace this with golang wmi sample data # TODO replace this with golang wmi sample data
@@ -59,23 +44,6 @@ class TestAPIv3(TacticalTestCase):
self.check_not_authenticated("patch", url) self.check_not_authenticated("patch", url)
def test_hello_patch(self):
url = "/api/v3/hello/"
payload = {
"agent_id": self.agent.agent_id,
"logged_in_username": "None",
"disks": [],
}
r = self.client.patch(url, payload, format="json")
self.assertEqual(r.status_code, 200)
payload["logged_in_username"] = "Bob"
r = self.client.patch(url, payload, format="json")
self.assertEqual(r.status_code, 200)
self.check_not_authenticated("patch", url)
def test_checkrunner_interval(self): def test_checkrunner_interval(self):
url = f"/api/v3/{self.agent.agent_id}/checkinterval/" url = f"/api/v3/{self.agent.agent_id}/checkinterval/"
r = self.client.get(url, format="json") r = self.client.get(url, format="json")

View File

@@ -2,18 +2,13 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path("checkin/", views.CheckIn.as_view()),
path("hello/", views.Hello.as_view()),
path("checkrunner/", views.CheckRunner.as_view()), path("checkrunner/", views.CheckRunner.as_view()),
path("<str:agentid>/checkrunner/", views.CheckRunner.as_view()), path("<str:agentid>/checkrunner/", views.CheckRunner.as_view()),
path("<str:agentid>/checkinterval/", views.CheckRunnerInterval.as_view()), path("<str:agentid>/checkinterval/", views.CheckRunnerInterval.as_view()),
path("<int:pk>/<str:agentid>/taskrunner/", views.TaskRunner.as_view()), path("<int:pk>/<str:agentid>/taskrunner/", views.TaskRunner.as_view()),
path("<int:pk>/meshinfo/", views.MeshInfo.as_view()),
path("meshexe/", views.MeshExe.as_view()), path("meshexe/", views.MeshExe.as_view()),
path("sysinfo/", views.SysInfo.as_view()), path("sysinfo/", views.SysInfo.as_view()),
path("newagent/", views.NewAgent.as_view()), path("newagent/", views.NewAgent.as_view()),
path("winupdater/", views.WinUpdater.as_view()),
path("<str:agentid>/winupdater/", views.WinUpdater.as_view()),
path("software/", views.Software.as_view()), path("software/", views.Software.as_view()),
path("installer/", views.Installer.as_view()), path("installer/", views.Installer.as_view()),
] ]

View File

@@ -36,187 +36,6 @@ from tacticalrmm.utils import notify_error, reload_nats, filter_software, Softwa
logger.configure(**settings.LOG_CONFIG) logger.configure(**settings.LOG_CONFIG)
class CheckIn(APIView):
"""
The agent's checkin endpoint
patch: called every 45 to 110 seconds, handles agent updates and recovery
put: called every 5 to 10 minutes, handles basic system info
post: called once on windows service startup
"""
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def patch(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
agent.version = request.data["version"]
agent.last_seen = djangotime.now()
agent.save(update_fields=["version", "last_seen"])
if agent.agentoutages.exists() and agent.agentoutages.last().is_active:
last_outage = agent.agentoutages.last()
last_outage.recovery_time = djangotime.now()
last_outage.save(update_fields=["recovery_time"])
if agent.overdue_email_alert:
agent_recovery_email_task.delay(pk=last_outage.pk)
if agent.overdue_text_alert:
agent_recovery_sms_task.delay(pk=last_outage.pk)
recovery = agent.recoveryactions.filter(last_run=None).last()
if recovery is not None:
recovery.last_run = djangotime.now()
recovery.save(update_fields=["last_run"])
return Response(recovery.send())
# handle agent update
if agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).exists():
update = agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).last()
update.status = "completed"
update.save(update_fields=["status"])
return Response(update.details)
# get any pending actions
if agent.pendingactions.filter(status="pending").exists():
agent.handle_pending_actions()
return Response("ok")
def put(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
if "disks" in request.data.keys():
disks = request.data["disks"]
new = []
# python agent
if isinstance(disks, dict):
for k, v in disks.items():
new.append(v)
else:
# golang agent
for disk in disks:
tmp = {}
for k, v in disk.items():
tmp["device"] = disk["device"]
tmp["fstype"] = disk["fstype"]
tmp["total"] = bytes2human(disk["total"])
tmp["used"] = bytes2human(disk["used"])
tmp["free"] = bytes2human(disk["free"])
tmp["percent"] = int(disk["percent"])
new.append(tmp)
serializer.save(disks=new)
return Response("ok")
if "logged_in_username" in request.data.keys():
if request.data["logged_in_username"] != "None":
serializer.save(last_logged_in_user=request.data["logged_in_username"])
return Response("ok")
serializer.save()
return Response("ok")
def post(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save(last_seen=djangotime.now())
return Response("ok")
class Hello(APIView):
#### DEPRECATED, for agents <= 1.1.9 ####
"""
The agent's checkin endpoint
patch: called every 30 to 120 seconds
post: called on agent windows service startup
"""
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def patch(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
disks = request.data["disks"]
new = []
# python agent
if isinstance(disks, dict):
for k, v in disks.items():
new.append(v)
else:
# golang agent
for disk in disks:
tmp = {}
for k, v in disk.items():
tmp["device"] = disk["device"]
tmp["fstype"] = disk["fstype"]
tmp["total"] = bytes2human(disk["total"])
tmp["used"] = bytes2human(disk["used"])
tmp["free"] = bytes2human(disk["free"])
tmp["percent"] = int(disk["percent"])
new.append(tmp)
if request.data["logged_in_username"] == "None":
serializer.save(last_seen=djangotime.now(), disks=new)
else:
serializer.save(
last_seen=djangotime.now(),
disks=new,
last_logged_in_user=request.data["logged_in_username"],
)
if agent.agentoutages.exists() and agent.agentoutages.last().is_active:
last_outage = agent.agentoutages.last()
last_outage.recovery_time = djangotime.now()
last_outage.save(update_fields=["recovery_time"])
if agent.overdue_email_alert:
agent_recovery_email_task.delay(pk=last_outage.pk)
if agent.overdue_text_alert:
agent_recovery_sms_task.delay(pk=last_outage.pk)
recovery = agent.recoveryactions.filter(last_run=None).last()
if recovery is not None:
recovery.last_run = djangotime.now()
recovery.save(update_fields=["last_run"])
return Response(recovery.send())
# handle agent update
if agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).exists():
update = agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).last()
update.status = "completed"
update.save(update_fields=["status"])
return Response(update.details)
# get any pending actions
if agent.pendingactions.filter(status="pending").exists():
agent.handle_pending_actions()
return Response("ok")
def post(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save(last_seen=djangotime.now())
return Response("ok")
class CheckRunner(APIView): class CheckRunner(APIView):
""" """
For the windows golang agent For the windows golang agent
@@ -292,76 +111,6 @@ class TaskRunner(APIView):
return Response("ok") return Response("ok")
class WinUpdater(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, agentid):
agent = get_object_or_404(Agent, agent_id=agentid)
agent.delete_superseded_updates()
patches = agent.winupdates.filter(action="approve").exclude(installed=True)
return Response(ApprovedUpdateSerializer(patches, many=True).data)
# agent sends patch results as it's installing them
def patch(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
kb = request.data["kb"]
results = request.data["results"]
update = agent.winupdates.get(kb=kb)
if results == "error" or results == "failed":
update.result = results
update.save(update_fields=["result"])
elif results == "success":
update.result = "success"
update.downloaded = True
update.installed = True
update.date_installed = djangotime.now()
update.save(
update_fields=[
"result",
"downloaded",
"installed",
"date_installed",
]
)
elif results == "alreadyinstalled":
update.result = "success"
update.downloaded = True
update.installed = True
update.save(update_fields=["result", "downloaded", "installed"])
agent.delete_superseded_updates()
return Response("ok")
# agent calls this after it's finished installing all patches
def post(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
reboot_policy = agent.get_patch_policy().reboot_after_install
reboot = False
if reboot_policy == "always":
reboot = True
if request.data["reboot"]:
if reboot_policy == "required":
reboot = True
elif reboot_policy == "never":
agent.needs_reboot = True
agent.save(update_fields=["needs_reboot"])
if reboot:
if agent.has_nats:
asyncio.run(agent.nats_cmd({"func": "rebootnow"}, wait=False))
logger.info(
f"{agent.hostname} is rebooting after updates were installed."
)
agent.delete_superseded_updates()
return Response("ok")
class SysInfo(APIView): class SysInfo(APIView):
authentication_classes = [TokenAuthentication] authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@@ -377,29 +126,6 @@ class SysInfo(APIView):
return Response("ok") return Response("ok")
class MeshInfo(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, pk):
agent = get_object_or_404(Agent, pk=pk)
return Response(agent.mesh_node_id)
def patch(self, request, pk):
agent = get_object_or_404(Agent, pk=pk)
if "nodeidhex" in request.data:
# agent <= 1.1.0
nodeid = request.data["nodeidhex"]
else:
# agent >= 1.1.1
nodeid = request.data["nodeid"]
agent.mesh_node_id = nodeid
agent.save(update_fields=["mesh_node_id"])
return Response("ok")
class MeshExe(APIView): class MeshExe(APIView):
""" Sends the mesh exe to the installer """ """ Sends the mesh exe to the installer """

View File

@@ -0,0 +1,59 @@
from model_bakery import baker
from tacticalrmm.test import TacticalTestCase
from django.conf import settings
class TestNatsAPIViews(TacticalTestCase):
def setUp(self):
self.authenticate()
self.setup_coresettings()
def test_nats_wmi(self):
url = "/natsapi/wmi/"
baker.make_recipe("agents.online_agent", version="1.2.0", _quantity=14)
baker.make_recipe(
"agents.online_agent", version=settings.LATEST_AGENT_VER, _quantity=3
)
baker.make_recipe(
"agents.overdue_agent", version=settings.LATEST_AGENT_VER, _quantity=5
)
baker.make_recipe("agents.online_agent", version="1.1.12", _quantity=7)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(r.json()["agent_ids"]), 17)
def test_natscheckin_patch(self):
from logs.models import PendingAction
url = "/natsapi/checkin/"
agent_updated = baker.make_recipe("agents.agent", version="1.3.0")
PendingAction.objects.create(
agent=agent_updated,
action_type="agentupdate",
details={
"url": agent_updated.winagent_dl,
"version": agent_updated.version,
"inno": agent_updated.win_inno_exe,
},
)
action = agent_updated.pendingactions.filter(action_type="agentupdate").first()
self.assertEqual(action.status, "pending")
# test agent failed to update and still on same version
payload = {
"func": "hello",
"agent_id": agent_updated.agent_id,
"version": "1.3.0",
}
r = self.client.patch(url, payload, format="json")
self.assertEqual(r.status_code, 200)
action = agent_updated.pendingactions.filter(action_type="agentupdate").first()
self.assertEqual(action.status, "pending")
# test agent successful update
payload["version"] = settings.LATEST_AGENT_VER
r = self.client.patch(url, payload, format="json")
self.assertEqual(r.status_code, 200)
action = agent_updated.pendingactions.filter(action_type="agentupdate").first()
self.assertEqual(action.status, "completed")
action.delete()

View File

@@ -7,4 +7,7 @@ urlpatterns = [
path("syncmesh/", views.SyncMeshNodeID.as_view()), path("syncmesh/", views.SyncMeshNodeID.as_view()),
path("winupdates/", views.NatsWinUpdates.as_view()), path("winupdates/", views.NatsWinUpdates.as_view()),
path("choco/", views.NatsChoco.as_view()), path("choco/", views.NatsChoco.as_view()),
path("wmi/", views.NatsWMI.as_view()),
path("offline/", views.OfflineAgents.as_view()),
path("logcrash/", views.LogCrash.as_view()),
] ]

View File

@@ -2,6 +2,8 @@ import asyncio
import time import time
from django.utils import timezone as djangotime from django.utils import timezone as djangotime
from loguru import logger from loguru import logger
from packaging import version as pyver
from typing import List
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
@@ -43,11 +45,25 @@ class NatsCheckIn(APIView):
permission_classes = [] permission_classes = []
def patch(self, request): def patch(self, request):
updated = False
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
if pyver.parse(request.data["version"]) > pyver.parse(agent.version):
updated = True
agent.version = request.data["version"] agent.version = request.data["version"]
agent.last_seen = djangotime.now() agent.last_seen = djangotime.now()
agent.save(update_fields=["version", "last_seen"]) agent.save(update_fields=["version", "last_seen"])
# change agent update pending status to completed if agent has just updated
if (
updated
and agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).exists()
):
agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).update(status="completed")
if agent.agentoutages.exists() and agent.agentoutages.last().is_active: if agent.agentoutages.exists() and agent.agentoutages.last().is_active:
last_outage = agent.agentoutages.last() last_outage = agent.agentoutages.last()
last_outage.recovery_time = djangotime.now() last_outage.recovery_time = djangotime.now()
@@ -237,3 +253,48 @@ class NatsWinUpdates(APIView):
agent.delete_superseded_updates() agent.delete_superseded_updates()
return Response("ok") return Response("ok")
class NatsWMI(APIView):
authentication_classes = []
permission_classes = []
def get(self, request):
agents = Agent.objects.only(
"pk", "agent_id", "version", "last_seen", "overdue_time"
)
online: List[str] = [
i.agent_id
for i in agents
if pyver.parse(i.version) >= pyver.parse("1.2.0") and i.status == "online"
]
return Response({"agent_ids": online})
class OfflineAgents(APIView):
authentication_classes = []
permission_classes = []
def get(self, request):
agents = Agent.objects.only(
"pk", "agent_id", "version", "last_seen", "overdue_time"
)
offline: List[str] = [
i.agent_id for i in agents if i.has_nats and i.status != "online"
]
return Response({"agent_ids": offline})
class LogCrash(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agentid"])
logger.info(
f"Detected crashed tacticalagent service on {agent.hostname} v{agent.version}, attempting recovery"
)
agent.last_seen = djangotime.now()
agent.save(update_fields=["last_seen"])
return Response("ok")

View File

@@ -1,4 +1,4 @@
amqp==5.0.2 amqp==5.0.5
asgiref==3.3.1 asgiref==3.3.1
asyncio-nats-client==0.11.4 asyncio-nats-client==0.11.4
billiard==3.6.3.0 billiard==3.6.3.0
@@ -9,7 +9,7 @@ chardet==4.0.0
cryptography==3.3.1 cryptography==3.3.1
decorator==4.4.2 decorator==4.4.2
Django==3.1.5 Django==3.1.5
django-cors-headers==3.6.0 django-cors-headers==3.7.0
django-rest-knox==4.1.0 django-rest-knox==4.1.0
djangorestframework==3.12.2 djangorestframework==3.12.2
future==0.18.2 future==0.18.2
@@ -21,7 +21,7 @@ packaging==20.8
psycopg2-binary==2.8.6 psycopg2-binary==2.8.6
pycparser==2.20 pycparser==2.20
pycryptodome==3.9.9 pycryptodome==3.9.9
pyotp==2.4.1 pyotp==2.5.0
pyparsing==2.4.7 pyparsing==2.4.7
pytz==2020.5 pytz==2020.5
qrcode==6.1 qrcode==6.1
@@ -29,8 +29,8 @@ redis==3.5.3
requests==2.25.1 requests==2.25.1
six==1.15.0 six==1.15.0
sqlparse==0.4.1 sqlparse==0.4.1
twilio==6.51.0 twilio==6.51.1
urllib3==1.26.2 urllib3==1.26.3
uWSGI==2.0.19.1 uWSGI==2.0.19.1
validators==0.18.2 validators==0.18.2
vine==5.0.0 vine==5.0.0

View File

@@ -195,15 +195,21 @@ class TestScriptViews(TacticalTestCase):
info = json.load(f) info = json.load(f)
for script in info: for script in info:
self.assertTrue( fn: str = script["filename"]
os.path.exists(os.path.join(scripts_dir, script["filename"])) self.assertTrue(os.path.exists(os.path.join(scripts_dir, fn)))
)
self.assertTrue(script["filename"]) self.assertTrue(script["filename"])
self.assertTrue(script["name"]) self.assertTrue(script["name"])
self.assertTrue(script["description"]) self.assertTrue(script["description"])
self.assertTrue(script["shell"]) self.assertTrue(script["shell"])
self.assertIn(script["shell"], valid_shells) self.assertIn(script["shell"], valid_shells)
if fn.endswith(".ps1"):
self.assertEqual(script["shell"], "powershell")
elif fn.endswith(".bat"):
self.assertEqual(script["shell"], "cmd")
elif fn.endswith(".py"):
self.assertEqual(script["shell"], "python")
def test_load_community_scripts(self): def test_load_community_scripts(self):
with open( with open(
os.path.join(settings.BASE_DIR, "scripts/community_scripts.json") os.path.join(settings.BASE_DIR, "scripts/community_scripts.json")

View File

@@ -29,26 +29,10 @@ app.conf.beat_schedule = {
"task": "winupdate.tasks.check_agent_update_schedule_task", "task": "winupdate.tasks.check_agent_update_schedule_task",
"schedule": crontab(minute=5, hour="*"), "schedule": crontab(minute=5, hour="*"),
}, },
"agents-checkinfull": {
"task": "agents.tasks.check_in_task",
"schedule": crontab(minute="*/24"),
},
"agent-auto-update": { "agent-auto-update": {
"task": "agents.tasks.auto_self_agent_update_task", "task": "agents.tasks.auto_self_agent_update_task",
"schedule": crontab(minute=35, hour="*"), "schedule": crontab(minute=35, hour="*"),
}, },
"agents-sync": {
"task": "agents.tasks.sync_sysinfo_task",
"schedule": crontab(minute=55, hour="*"),
},
"get-wmi": {
"task": "agents.tasks.get_wmi_task",
"schedule": crontab(minute="*/18"),
},
"check-agentservice": {
"task": "agents.tasks.monitor_agents_task",
"schedule": crontab(minute="*/15"),
},
"remove-salt": { "remove-salt": {
"task": "agents.tasks.remove_salt_task", "task": "agents.tasks.remove_salt_task",
"schedule": crontab(minute=14, hour="*/2"), "schedule": crontab(minute=14, hour="*/2"),

View File

@@ -15,19 +15,19 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
AUTH_USER_MODEL = "accounts.User" AUTH_USER_MODEL = "accounts.User"
# latest release # latest release
TRMM_VERSION = "0.4.2" TRMM_VERSION = "0.4.5"
# bump this version everytime vue code is changed # bump this version everytime vue code is changed
# to alert user they need to manually refresh their browser # to alert user they need to manually refresh their browser
APP_VER = "0.0.109" APP_VER = "0.0.110"
# https://github.com/wh1te909/rmmagent # https://github.com/wh1te909/rmmagent
LATEST_AGENT_VER = "1.4.1" LATEST_AGENT_VER = "1.4.2"
MESH_VER = "0.7.54" MESH_VER = "0.7.54"
# for the update script, bump when need to recreate venv or npm install # for the update script, bump when need to recreate venv or npm install
PIP_VER = "7" PIP_VER = "8"
NPM_VER = "7" NPM_VER = "7"
DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe" DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe"

View File

@@ -7,8 +7,8 @@ jobs:
displayName: "Setup" displayName: "Setup"
strategy: strategy:
matrix: matrix:
Ubuntu20: Debian10:
AGENT_NAME: "rmm-ubu20" AGENT_NAME: "azpipelines-deb10"
pool: pool:
name: linux-vms name: linux-vms
@@ -23,11 +23,11 @@ jobs:
rm -rf /myagent/_work/1/s/api/env rm -rf /myagent/_work/1/s/api/env
cd /myagent/_work/1/s/api cd /myagent/_work/1/s/api
python3 -m venv env python3.8 -m venv env
source env/bin/activate source env/bin/activate
cd /myagent/_work/1/s/api/tacticalrmm cd /myagent/_work/1/s/api/tacticalrmm
pip install --no-cache-dir --upgrade pip pip install --no-cache-dir --upgrade pip
pip install --no-cache-dir setuptools==51.1.2 wheel==0.36.2 pip install --no-cache-dir setuptools==52.0.0 wheel==0.36.2
pip install --no-cache-dir -r requirements.txt -r requirements-test.txt -r requirements-dev.txt pip install --no-cache-dir -r requirements.txt -r requirements-test.txt -r requirements-dev.txt
displayName: "Install Python Dependencies" displayName: "Install Python Dependencies"

8
go.mod
View File

@@ -3,14 +3,10 @@ module github.com/wh1te909/tacticalrmm
go 1.15 go 1.15
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-resty/resty/v2 v2.4.0 github.com/go-resty/resty/v2 v2.4.0
github.com/josephspurrier/goversioninfo v1.2.0 github.com/josephspurrier/goversioninfo v1.2.0
github.com/kr/pretty v0.1.0 // indirect
github.com/nats-io/nats.go v1.10.1-0.20210107160453-a133396829fc github.com/nats-io/nats.go v1.10.1-0.20210107160453-a133396829fc
github.com/ugorji/go/codec v1.2.2 github.com/ugorji/go/codec v1.2.3
github.com/wh1te909/rmmagent v1.2.2-0.20210121224121-abcecefe6da5 github.com/wh1te909/rmmagent v1.4.2
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
) )

53
go.sum
View File

@@ -5,12 +5,11 @@ github.com/capnspacehook/taskmaster v0.0.0-20201022195506-c2d8b114cec0/go.mod h1
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elastic/go-sysinfo v1.4.0/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= github.com/elastic/go-sysinfo v1.5.0/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
github.com/go-resty/resty/v2 v2.4.0 h1:s6TItTLejEI+2mn98oijC5w/Rk2YU+OA6x0mnZN6r6k= github.com/go-resty/resty/v2 v2.4.0 h1:s6TItTLejEI+2mn98oijC5w/Rk2YU+OA6x0mnZN6r6k=
github.com/go-resty/resty/v2 v2.4.0/go.mod h1:B88+xCTEwvfD94NOuE6GS1wMlnoKNY8eEiNizfNwOwA= github.com/go-resty/resty/v2 v2.4.0/go.mod h1:B88+xCTEwvfD94NOuE6GS1wMlnoKNY8eEiNizfNwOwA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -37,7 +36,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA= github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA=
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
@@ -68,14 +66,15 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/rickb777/date v1.14.2/go.mod h1:swmf05C+hN+m8/Xh7gEq3uB6QJDNc5pQBWojKdHetOs= github.com/rickb777/date v1.14.2/go.mod h1:swmf05C+hN+m8/Xh7gEq3uB6QJDNc5pQBWojKdHetOs=
github.com/rickb777/date v1.14.3/go.mod h1:mes+vf4wqTD6l4zgZh4Z5TQkrLA57dpuzEGVeTk/XSc= github.com/rickb777/date v1.15.3/go.mod h1:+spwdRnUrpqbYLOmRM6y8FbQMXwpNwHrNcWuOUipge4=
github.com/rickb777/plural v1.2.2/go.mod h1:xyHbelv4YvJE51gjMnHvk+U2e9zIysg6lTnSQK8XUYA= github.com/rickb777/plural v1.2.2/go.mod h1:xyHbelv4YvJE51gjMnHvk+U2e9zIysg6lTnSQK8XUYA=
github.com/rickb777/plural v1.3.0/go.mod h1:xyHbelv4YvJE51gjMnHvk+U2e9zIysg6lTnSQK8XUYA=
github.com/shirou/gopsutil/v3 v3.20.12/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4= github.com/shirou/gopsutil/v3 v3.20.12/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -83,32 +82,13 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tc-hib/goversioninfo v0.0.0-20200813185747-90ffbaa484a7/go.mod h1:NaPIGx19A2KXQEoek0x88NbM0lNgRooZS0xmrETzcjI= github.com/ugorji/go v1.2.3 h1:WbFSXLxDFKVN69Sk8t+XHGzVCD7R8UoAATR8NqZgTbk=
github.com/tc-hib/rsrc v0.9.1/go.mod h1:JGDB/TLOdMTvEEvjv3yetUTFnjXWYLbZDDeH4BTXG/8= github.com/ugorji/go v1.2.3/go.mod h1:5l8GZ8hZvmL4uMdy+mhCO1LjswGRYco9Q3HfuisB21A=
github.com/tc-hib/rsrc v0.9.2/go.mod h1:vUZqBwu0vX+ueZH/D5wEvihBZfON5BrWCg6Orbfq7A4= github.com/ugorji/go/codec v1.2.3 h1:/mVYEV+Jo3IZKeA5gBngN0AvNnQltEDkR+eQikkWQu0=
github.com/ugorji/go v1.2.0/go.mod h1:1ny++pKMXhLWrwWV5Nf+CbOuZJhMoaFD+0GMFfd8fEc= github.com/ugorji/go/codec v1.2.3/go.mod h1:5FxzDJIgeiWJZslYHPj+LS1dq1ZBQVelZFnjsFGI/Uc=
github.com/ugorji/go v1.2.2 h1:60ZHIOcsJlo3bJm9CbTVu7OSqT2mxaEmyQbK2NwCkn0=
github.com/ugorji/go v1.2.2/go.mod h1:bitgyERdV7L7Db/Z5gfd5v2NQMNhhiFiZwpgMw2SP7k=
github.com/ugorji/go/codec v1.2.0/go.mod h1:dXvG35r7zTX6QImXOSFhGMmKtX+wJ7VTWzGvYQGIjBs=
github.com/ugorji/go/codec v1.2.2 h1:08Gah8d+dXj4cZNUHhtuD/S4PXD5WpVbj5B8/ClELAQ=
github.com/ugorji/go/codec v1.2.2/go.mod h1:OM8g7OAy52uYl3Yk+RE/3AS1nXFn1Wh4PPLtupCxbuU=
github.com/wh1te909/go-win64api v0.0.0-20201021040544-8fba2a0fc3d0/go.mod h1:cfD5/vNQFm5PD5Q32YYYBJ6VIs9etzp8CJ9dinUcpUA= github.com/wh1te909/go-win64api v0.0.0-20201021040544-8fba2a0fc3d0/go.mod h1:cfD5/vNQFm5PD5Q32YYYBJ6VIs9etzp8CJ9dinUcpUA=
github.com/wh1te909/rmmagent v1.1.13-0.20210111092134-7c83da579caa h1:ZV7qIUJ5M3HDFLi3bun6a2A5+g9DoThbLWI7egBYYkQ= github.com/wh1te909/rmmagent v1.4.2 h1:noG/ELSue3d6UF7o0gp1ty0DpGbfrVz0VG6NEQm9kGM=
github.com/wh1te909/rmmagent v1.1.13-0.20210111092134-7c83da579caa/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0= github.com/wh1te909/rmmagent v1.4.2/go.mod h1:mcI27szhAGjAQzhX8eCsluE5670kUAyi3tJVJa6LNTo=
github.com/wh1te909/rmmagent v1.1.13-0.20210111205455-e6620a17aebe h1:xsutMbsAJL2xTvE119BVyK4RdBWx1IBvC7azoEpioEE=
github.com/wh1te909/rmmagent v1.1.13-0.20210111205455-e6620a17aebe/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0=
github.com/wh1te909/rmmagent v1.1.13-0.20210112033642-9b310c2c7f53 h1:Q47sibbW09BWaQoPZQTzblGd+rnNIc3W8W/jOYbMe10=
github.com/wh1te909/rmmagent v1.1.13-0.20210112033642-9b310c2c7f53/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0=
github.com/wh1te909/rmmagent v1.2.0 h1:dM/juD7k6Oa0lEKsvbNPgjc1wVC6uQtNzQoIqVuuxSQ=
github.com/wh1te909/rmmagent v1.2.0/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0=
github.com/wh1te909/rmmagent v1.2.2-0.20210118235958-bd6606570a6f h1:lhcD2yJauZ8TyYCxYvSv/CPnUhiTrxwydPTESfPkyuc=
github.com/wh1te909/rmmagent v1.2.2-0.20210118235958-bd6606570a6f/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0=
github.com/wh1te909/rmmagent v1.2.2-0.20210119030741-08ec2f919198 h1:lPxk5AEr/2y8txGtvbQgW0rofZ7RFaJBYmS8rLIxoVQ=
github.com/wh1te909/rmmagent v1.2.2-0.20210119030741-08ec2f919198/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0=
github.com/wh1te909/rmmagent v1.2.2-0.20210119225811-d3b8795ce1d7 h1:ctMUmZtlI2dH1WCndTFPOueWgYd18n+onYsnMKT/lns=
github.com/wh1te909/rmmagent v1.2.2-0.20210119225811-d3b8795ce1d7/go.mod h1:TG09pCLQZcN5jyrokVty3eHImponjh5nMmifru9RPeY=
github.com/wh1te909/rmmagent v1.2.2-0.20210121224121-abcecefe6da5 h1:md2uqZE2Too7mRvWCvA7vDpdpFP1bMEKWAfrIa0ARiA=
github.com/wh1te909/rmmagent v1.2.2-0.20210121224121-abcecefe6da5/go.mod h1:TG09pCLQZcN5jyrokVty3eHImponjh5nMmifru9RPeY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -117,11 +97,10 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNm
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
@@ -133,6 +112,7 @@ golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -141,9 +121,9 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200622182413-4b0db7f3f76b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622182413-4b0db7f3f76b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba h1:xmhUJGQGbxlod18iJGqVEp9cHIPLl7QiX2aA3to708s=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210122235752-a8b976e07c7b h1:HSSdksA3iHk8fuZz7C7+A6tDgtIRF+7FSXu5TgK09I8=
golang.org/x/sys v0.0.0-20210122235752-a8b976e07c7b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -169,6 +149,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=

View File

@@ -1,9 +1,9 @@
#!/bin/bash #!/bin/bash
SCRIPT_VERSION="34" SCRIPT_VERSION="36"
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh' SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh'
sudo apt install -y curl wget sudo apt install -y curl wget dirmngr gnupg lsb-release
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
@@ -375,7 +375,7 @@ python3 -m venv env
source /rmm/api/env/bin/activate source /rmm/api/env/bin/activate
cd /rmm/api/tacticalrmm cd /rmm/api/tacticalrmm
pip install --no-cache-dir --upgrade pip pip install --no-cache-dir --upgrade pip
pip install --no-cache-dir setuptools==51.1.2 wheel==0.36.2 pip install --no-cache-dir setuptools==52.0.0 wheel==0.36.2
pip install --no-cache-dir -r /rmm/api/tacticalrmm/requirements.txt pip install --no-cache-dir -r /rmm/api/tacticalrmm/requirements.txt
python manage.py migrate python manage.py migrate
python manage.py collectstatic --no-input python manage.py collectstatic --no-input
@@ -788,6 +788,7 @@ sleep 5
MESHEXE=$(node node_modules/meshcentral/meshctrl.js --url wss://${meshdomain}:443 --loginuser ${meshusername} --loginpass ${MESHPASSWD} GenerateInviteLink --group TacticalRMM --hours 8) MESHEXE=$(node node_modules/meshcentral/meshctrl.js --url wss://${meshdomain}:443 --loginuser ${meshusername} --loginpass ${MESHPASSWD} GenerateInviteLink --group TacticalRMM --hours 8)
sudo systemctl enable nats.service sudo systemctl enable nats.service
sudo systemctl enable natsapi.service
cd /rmm/api/tacticalrmm cd /rmm/api/tacticalrmm
source /rmm/api/env/bin/activate source /rmm/api/env/bin/activate
python manage.py initial_db_setup python manage.py initial_db_setup

View File

@@ -9,7 +9,7 @@ import (
"github.com/wh1te909/tacticalrmm/natsapi" "github.com/wh1te909/tacticalrmm/natsapi"
) )
var version = "1.0.2" var version = "1.0.5"
func main() { func main() {
ver := flag.Bool("version", false, "Prints version") ver := flag.Bool("version", false, "Prints version")

View File

@@ -75,6 +75,9 @@ func Listen(apihost, natshost, version string, debug bool) {
log.Fatalln(err) log.Fatalln(err)
} }
go getWMI(rClient, nc)
go monitorAgents(rClient, nc)
nc.Subscribe("*", func(msg *nats.Msg) { nc.Subscribe("*", func(msg *nats.Msg) {
var mh codec.MsgpackHandle var mh codec.MsgpackHandle
mh.RawToString = true mh.RawToString = true

Binary file not shown.

69
natsapi/tasks.go Normal file
View File

@@ -0,0 +1,69 @@
package api
import (
"time"
"github.com/go-resty/resty/v2"
nats "github.com/nats-io/nats.go"
"github.com/ugorji/go/codec"
)
func monitorAgents(c *resty.Client, nc *nats.Conn) {
var payload, recPayload []byte
var mh codec.MsgpackHandle
mh.RawToString = true
ret := codec.NewEncoderBytes(&payload, new(codec.MsgpackHandle))
ret.Encode(map[string]string{"func": "ping"})
rec := codec.NewEncoderBytes(&recPayload, new(codec.MsgpackHandle))
rec.Encode(Recovery{
Func: "recover",
Data: map[string]string{"mode": "tacagent"},
})
tick := time.NewTicker(10 * time.Minute)
for range tick.C {
agentids, _ := c.R().SetResult(&AgentIDS{}).Get("/offline/")
ids := agentids.Result().(*AgentIDS).IDs
var resp string
for _, id := range ids {
out, err := nc.Request(id, payload, 2*time.Second)
if err != nil {
continue
}
dec := codec.NewDecoderBytes(out.Data, &mh)
if err := dec.Decode(&resp); err == nil {
// if the agent is respoding to pong from the rpc service but is not showing as online (handled by tacticalagent service)
// then tacticalagent service is hung. forcefully restart it
if resp == "pong" {
nc.Publish(id, recPayload)
p := map[string]string{"agentid": id}
c.R().SetBody(p).Post("/logcrash/")
}
}
}
}
}
func getWMI(c *resty.Client, nc *nats.Conn) {
var payload []byte
var mh codec.MsgpackHandle
mh.RawToString = true
ret := codec.NewEncoderBytes(&payload, new(codec.MsgpackHandle))
ret.Encode(map[string]string{"func": "wmi"})
tick := time.NewTicker(18 * time.Minute)
for range tick.C {
agentids, _ := c.R().SetResult(&AgentIDS{}).Get("/wmi/")
ids := agentids.Result().(*AgentIDS).IDs
chunks := makeChunks(ids, 40)
for _, id := range chunks {
for _, chunk := range id {
nc.Publish(chunk, payload)
time.Sleep(time.Duration(randRange(50, 400)) * time.Millisecond)
}
time.Sleep(15 * time.Second)
}
}
}

View File

@@ -4,3 +4,12 @@ type NatsInfo struct {
User string `json:"user"` User string `json:"user"`
Password string `json:"password"` Password string `json:"password"`
} }
type AgentIDS struct {
IDs []string `json:"agent_ids"`
}
type Recovery struct {
Func string `json:"func"`
Data map[string]string `json:"payload"`
}

23
natsapi/utils.go Normal file
View File

@@ -0,0 +1,23 @@
package api
import (
"math/rand"
"time"
)
func makeChunks(ids []string, chunkSize int) [][]string {
var chunks [][]string
for i := 0; i < len(ids); i += chunkSize {
end := i + chunkSize
if end > len(ids) {
end = len(ids)
}
chunks = append(chunks, ids[i:end])
}
return chunks
}
func randRange(min, max int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(max-min) + min
}

View File

@@ -7,7 +7,7 @@ pgpw="hunter2"
##################################################### #####################################################
SCRIPT_VERSION="13" SCRIPT_VERSION="14"
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh' SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh'
sudo apt install -y curl wget sudo apt install -y curl wget
@@ -246,7 +246,7 @@ python3 -m venv env
source /rmm/api/env/bin/activate source /rmm/api/env/bin/activate
cd /rmm/api/tacticalrmm cd /rmm/api/tacticalrmm
pip install --no-cache-dir --upgrade pip pip install --no-cache-dir --upgrade pip
pip install --no-cache-dir setuptools==51.1.2 wheel==0.36.2 pip install --no-cache-dir setuptools==52.0.0 wheel==0.36.2
pip install --no-cache-dir -r /rmm/api/tacticalrmm/requirements.txt pip install --no-cache-dir -r /rmm/api/tacticalrmm/requirements.txt
python manage.py collectstatic --no-input python manage.py collectstatic --no-input
python manage.py reload_nats python manage.py reload_nats

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
SCRIPT_VERSION="104" SCRIPT_VERSION="105"
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/update.sh' SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/update.sh'
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py' LATEST_SETTINGS_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
@@ -165,6 +165,9 @@ printf >&2 "${GREEN}Stopping ${i} service...${NC}\n"
sudo systemctl stop ${i} sudo systemctl stop ${i}
done done
# forgot to add this in install script. catch any installs that don't have it enabled and enable it
sudo systemctl enable natsapi.service
CHECK_NGINX_WORKER_CONN=$(grep "worker_connections 2048" /etc/nginx/nginx.conf) CHECK_NGINX_WORKER_CONN=$(grep "worker_connections 2048" /etc/nginx/nginx.conf)
if ! [[ $CHECK_NGINX_WORKER_CONN ]]; then if ! [[ $CHECK_NGINX_WORKER_CONN ]]; then
printf >&2 "${GREEN}Changing nginx worker connections to 2048${NC}\n" printf >&2 "${GREEN}Changing nginx worker connections to 2048${NC}\n"
@@ -242,7 +245,7 @@ if [[ "${CURRENT_PIP_VER}" != "${LATEST_PIP_VER}" ]]; then
source /rmm/api/env/bin/activate source /rmm/api/env/bin/activate
cd /rmm/api/tacticalrmm cd /rmm/api/tacticalrmm
pip install --no-cache-dir --upgrade pip pip install --no-cache-dir --upgrade pip
pip install --no-cache-dir setuptools==51.1.2 wheel==0.36.2 pip install --no-cache-dir setuptools==52.0.0 wheel==0.36.2
pip install --no-cache-dir -r requirements.txt pip install --no-cache-dir -r requirements.txt
else else
source /rmm/api/env/bin/activate source /rmm/api/env/bin/activate

View File

@@ -38,7 +38,6 @@
</template> </template>
<script> <script>
import axios from "axios";
import mixins from "@/mixins/mixins"; import mixins from "@/mixins/mixins";
export default { export default {
name: "UpdateAgents", name: "UpdateAgents",
@@ -58,7 +57,7 @@ export default {
}, },
getVersions() { getVersions() {
this.$q.loading.show(); this.$q.loading.show();
axios this.$axios
.get("/agents/getagentversions/") .get("/agents/getagentversions/")
.then(r => { .then(r => {
this.versions = r.data.versions; this.versions = r.data.versions;
@@ -72,8 +71,8 @@ export default {
}); });
}, },
update() { update() {
const data = { version: this.version, pks: this.group }; const data = { pks: this.group };
axios this.$axios
.post("/agents/updateagents/", data) .post("/agents/updateagents/", data)
.then(r => { .then(r => {
this.$emit("close"); this.$emit("close");