Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d82f0cd757 | ||
|
|
5f529e2af4 | ||
|
|
beadd9e02b | ||
|
|
72543789cb | ||
|
|
5789439fa9 | ||
|
|
f549126bcf | ||
|
|
7197548bad | ||
|
|
241fde783c | ||
|
|
2b872cd1f4 | ||
|
|
a606fb4d1d | ||
|
|
9f9c6be38e | ||
|
|
01ee524049 | ||
|
|
af9cb65338 | ||
|
|
8aa11c580b | ||
|
|
ada627f444 | ||
|
|
a7b6d338c3 | ||
|
|
9f00538b97 | ||
|
|
a085015282 | ||
|
|
0b9c220fbb | ||
|
|
0e3d04873d | ||
|
|
b7578d939f |
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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,18 +45,12 @@ 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 pyver.parse(agent.version) <= pyver.parse("1.1.11"):
|
|
||||||
if agent.pendingactions.filter(
|
if agent.pendingactions.filter(
|
||||||
action_type="agentupdate", status="pending"
|
action_type="agentupdate", status="pending"
|
||||||
).exists():
|
).exists():
|
||||||
action = agent.pendingactions.filter(
|
agent.pendingactions.filter(
|
||||||
action_type="agentupdate", status="pending"
|
action_type="agentupdate", status="pending"
|
||||||
).last()
|
).delete()
|
||||||
if pyver.parse(action.details["version"]) < pyver.parse(version):
|
|
||||||
action.delete()
|
|
||||||
else:
|
|
||||||
return "pending"
|
|
||||||
|
|
||||||
PendingAction.objects.create(
|
PendingAction.objects.create(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
@@ -99,7 +61,7 @@ def agent_update(pk: int) -> str:
|
|||||||
"inno": inno,
|
"inno": inno,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
nats_data = {
|
nats_data = {
|
||||||
"func": "agentupdate",
|
"func": "agentupdate",
|
||||||
"payload": {
|
"payload": {
|
||||||
@@ -109,23 +71,12 @@ def agent_update(pk: int) -> str:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
asyncio.run(agent.nats_cmd(nats_data, wait=False))
|
asyncio.run(agent.nats_cmd(nats_data, wait=False))
|
||||||
|
|
||||||
return "created"
|
return "created"
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"{agent.hostname} v{agent.version} is running an unsupported version. Refusing to update."
|
|
||||||
)
|
|
||||||
|
|
||||||
return "not supported"
|
|
||||||
|
|
||||||
|
|
||||||
@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))
|
||||||
|
|||||||
@@ -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") """
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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 """
|
||||||
|
|
||||||
|
|||||||
59
api/tacticalrmm/natsapi/tests.py
Normal file
59
api/tacticalrmm/natsapi/tests.py
Normal 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()
|
||||||
@@ -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()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
8
go.mod
@@ -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
53
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -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")
|
||||||
|
|||||||
@@ -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
69
natsapi/tasks.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
23
natsapi/utils.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user