Merge branch 'nats' into develop
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -44,3 +44,4 @@ resource.syso
|
||||
htmlcov/
|
||||
docker-compose.dev.yml
|
||||
docs/.vuepress/dist
|
||||
nats-rmm.conf
|
||||
|
||||
@@ -7,6 +7,7 @@ from Crypto.Random import get_random_bytes
|
||||
from Crypto.Hash import SHA3_384
|
||||
from Crypto.Util.Padding import pad
|
||||
import validators
|
||||
import msgpack
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
@@ -14,6 +15,8 @@ from collections import Counter
|
||||
from loguru import logger
|
||||
from packaging import version as pyver
|
||||
from distutils.version import LooseVersion
|
||||
from nats.aio.client import Client as NATS
|
||||
from nats.aio.errors import ErrTimeout
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
@@ -433,6 +436,30 @@ class Agent(BaseAuditModel):
|
||||
except Exception:
|
||||
return "err"
|
||||
|
||||
async def nats_cmd(self, data, timeout=30):
|
||||
nc = NATS()
|
||||
options = {
|
||||
"servers": f"tls://{settings.ALLOWED_HOSTS[0]}:4222",
|
||||
"user": "tacticalrmm",
|
||||
"password": settings.SECRET_KEY,
|
||||
"connect_timeout": 3,
|
||||
"max_reconnect_attempts": 2,
|
||||
}
|
||||
try:
|
||||
await nc.connect(**options)
|
||||
except:
|
||||
return "natsdown"
|
||||
|
||||
try:
|
||||
msg = await nc.request(self.agent_id, msgpack.dumps(data), timeout=timeout)
|
||||
except ErrTimeout:
|
||||
ret = "timeout"
|
||||
else:
|
||||
ret = msgpack.loads(msg.data)
|
||||
|
||||
await nc.close()
|
||||
return ret
|
||||
|
||||
def salt_api_cmd(self, **kwargs):
|
||||
|
||||
# salt should always timeout first before the requests' timeout
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
from loguru import logger
|
||||
import os
|
||||
import subprocess
|
||||
@@ -153,12 +154,9 @@ def agent_detail(request, pk):
|
||||
@api_view()
|
||||
def get_processes(request, pk):
|
||||
agent = get_object_or_404(Agent, pk=pk)
|
||||
r = agent.salt_api_cmd(timeout=20, func="win_agent.get_procs")
|
||||
|
||||
r = asyncio.run(agent.nats_cmd(data={"func": "procs"}, timeout=5))
|
||||
if r == "timeout":
|
||||
return notify_error("Unable to contact the agent")
|
||||
elif r == "error":
|
||||
return notify_error("Something went wrong")
|
||||
|
||||
return Response(r)
|
||||
|
||||
@@ -182,16 +180,19 @@ def kill_proc(request, pk, pid):
|
||||
@api_view()
|
||||
def get_event_log(request, pk, logtype, days):
|
||||
agent = get_object_or_404(Agent, pk=pk)
|
||||
r = agent.salt_api_cmd(
|
||||
timeout=30,
|
||||
func="win_agent.get_eventlog",
|
||||
arg=[logtype, int(days)],
|
||||
)
|
||||
|
||||
if r == "timeout" or r == "error":
|
||||
data = {
|
||||
"func": "eventlog",
|
||||
"timeout": 30,
|
||||
"payload": {
|
||||
"logname": logtype,
|
||||
"days": str(days),
|
||||
},
|
||||
}
|
||||
r = asyncio.run(agent.nats_cmd(data, timeout=32))
|
||||
if r == "timeout":
|
||||
return notify_error("Unable to contact the agent")
|
||||
|
||||
return Response(json.loads(zlib.decompress(base64.b64decode(r["wineventlog"]))))
|
||||
return Response(r)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
@@ -216,21 +217,19 @@ def power_action(request):
|
||||
@api_view(["POST"])
|
||||
def send_raw_cmd(request):
|
||||
agent = get_object_or_404(Agent, pk=request.data["pk"])
|
||||
|
||||
r = agent.salt_api_cmd(
|
||||
timeout=request.data["timeout"],
|
||||
func="cmd.run",
|
||||
kwargs={
|
||||
"cmd": request.data["cmd"],
|
||||
timeout = int(request.data["timeout"])
|
||||
data = {
|
||||
"func": "rawcmd",
|
||||
"timeout": timeout,
|
||||
"payload": {
|
||||
"command": request.data["cmd"],
|
||||
"shell": request.data["shell"],
|
||||
"timeout": request.data["timeout"],
|
||||
},
|
||||
)
|
||||
}
|
||||
r = asyncio.run(agent.nats_cmd(data, timeout=timeout + 2))
|
||||
|
||||
if r == "timeout":
|
||||
return notify_error("Unable to contact the agent")
|
||||
elif r == "error" or not r:
|
||||
return notify_error("Something went wrong")
|
||||
|
||||
AuditLog.audit_raw_command(
|
||||
username=request.user.username,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import base64
|
||||
import string
|
||||
import os
|
||||
import json
|
||||
import pytz
|
||||
import zlib
|
||||
from statistics import mean
|
||||
|
||||
from django.db import models
|
||||
@@ -336,8 +334,7 @@ class Check(BaseAuditModel):
|
||||
eventID = self.event_id
|
||||
source = self.event_source
|
||||
message = self.event_message
|
||||
|
||||
r = json.loads(zlib.decompress(base64.b64decode(data["log"])))
|
||||
r = data["log"]
|
||||
|
||||
for i in r:
|
||||
if i["eventType"] == eventType:
|
||||
|
||||
9
api/tacticalrmm/core/management/commands/reload_nats.py
Normal file
9
api/tacticalrmm/core/management/commands/reload_nats.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from tacticalrmm.utils import reload_nats
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Reload Nats"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
reload_nats()
|
||||
@@ -1,5 +1,6 @@
|
||||
amqp==2.6.1
|
||||
asgiref==3.3.0
|
||||
asyncio-nats-client==0.11.4
|
||||
billiard==3.6.3.0
|
||||
celery==4.4.6
|
||||
certifi==2020.11.8
|
||||
@@ -28,6 +29,7 @@ redis==3.5.3
|
||||
requests==2.24.0
|
||||
six==1.15.0
|
||||
sqlparse==0.4.1
|
||||
tldextract==3.0.2
|
||||
twilio==6.47.0
|
||||
urllib3==1.25.11
|
||||
uWSGI==2.0.19.1
|
||||
|
||||
@@ -1,4 +1,36 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tldextract
|
||||
|
||||
from django.conf import settings
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from agents.models import Agent
|
||||
|
||||
notify_error = lambda msg: Response(msg, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
def reload_nats():
|
||||
users = [{"user": "tacticalrmm", "password": settings.SECRET_KEY}]
|
||||
agents = Agent.objects.prefetch_related("user").only("pk", "agent_id")
|
||||
for agent in agents:
|
||||
users.append({"user": agent.agent_id, "password": agent.user.auth_token.key})
|
||||
|
||||
tld = tldextract.extract(settings.ALLOWED_HOSTS[0])
|
||||
domain = tld.domain + "." + tld.suffix
|
||||
config = {
|
||||
"tls": {
|
||||
"cert_file": f"/etc/letsencrypt/live/{domain}/fullchain.pem",
|
||||
"key_file": f"/etc/letsencrypt/live/{domain}/privkey.pem",
|
||||
},
|
||||
"authorization": {"users": users},
|
||||
"max_payload": 2048576005,
|
||||
}
|
||||
|
||||
conf = os.path.join(settings.BASE_DIR, "nats-rmm.conf")
|
||||
with open(conf, "w") as f:
|
||||
json.dump(config, f)
|
||||
|
||||
subprocess.run(["/usr/local/bin/nats-server", "-signal", "reload"])
|
||||
|
||||
@@ -35,8 +35,10 @@
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<q-td>{{ props.row.name }}</q-td>
|
||||
<q-td>{{ Math.ceil(props.row.cpu_percent) }}%</q-td>
|
||||
<q-td>{{ convert(props.row.memory_percent) }} MB</q-td>
|
||||
<!-- <q-td>{{ Math.ceil(props.row.cpu_percent) }}%</q-td>
|
||||
<q-td>{{ convert(props.row.memory_percent) }} MB</q-td> -->
|
||||
<q-td>{{ props.row.cpu_percent }}%</q-td>
|
||||
<q-td>{{ props.row.memory_percent }}</q-td>
|
||||
<q-td>{{ props.row.username }}</q-td>
|
||||
<q-td>{{ props.row.pid }}</q-td>
|
||||
<q-td>{{ props.row.status }}</q-td>
|
||||
@@ -47,7 +49,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import mixins from "@/mixins/mixins";
|
||||
|
||||
export default {
|
||||
@@ -116,7 +117,7 @@ export default {
|
||||
getProcesses() {
|
||||
this.procs = [];
|
||||
this.$q.loading.show({ message: "Loading Processes..." });
|
||||
axios
|
||||
this.$axios
|
||||
.get(`/agents/${this.pk}/getprocs/`)
|
||||
.then(r => {
|
||||
this.procs = r.data;
|
||||
@@ -129,15 +130,15 @@ export default {
|
||||
},
|
||||
refreshProcs() {
|
||||
this.polling = setInterval(() => {
|
||||
axios
|
||||
this.$axios
|
||||
.get(`/agents/${this.pk}/getprocs/`)
|
||||
.then(r => (this.procs = r.data))
|
||||
.catch(() => this.notifyError("Unable to contact the agent"));
|
||||
}, 10000);
|
||||
.catch(() => console.log("Unable to contact the agent"));
|
||||
}, 2 * 1000);
|
||||
},
|
||||
killProc(pid, name) {
|
||||
this.$q.loading.show({ message: `Attempting to kill process ${name}` });
|
||||
axios
|
||||
this.$axios
|
||||
.get(`/agents/${this.pk}/${pid}/killproc/`)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
@@ -154,11 +155,11 @@ export default {
|
||||
},
|
||||
startPoll() {
|
||||
this.poll = true;
|
||||
axios.get(`/agents/${this.pk}/getprocs/`).then(r => (this.procs = r.data));
|
||||
this.$axios.get(`/agents/${this.pk}/getprocs/`).then(r => (this.procs = r.data));
|
||||
this.refreshProcs();
|
||||
},
|
||||
getAgent() {
|
||||
axios.get(`/agents/${this.pk}/agentdetail/`).then(r => (this.mem = r.data.total_ram));
|
||||
this.$axios.get(`/agents/${this.pk}/agentdetail/`).then(r => (this.mem = r.data.total_ram));
|
||||
},
|
||||
convert(percent) {
|
||||
const mb = this.mem * 1024;
|
||||
|
||||
Reference in New Issue
Block a user