Merge branch 'nats' into develop

This commit is contained in:
wh1te909
2020-11-21 04:00:21 +00:00
8 changed files with 104 additions and 36 deletions

1
.gitignore vendored
View File

@@ -44,3 +44,4 @@ resource.syso
htmlcov/
docker-compose.dev.yml
docs/.vuepress/dist
nats-rmm.conf

View File

@@ -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

View File

@@ -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,

View File

@@ -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:

View 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()

View File

@@ -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

View File

@@ -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"])

View File

@@ -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;