Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c74d1d021 | ||
|
|
aff659b6b6 | ||
|
|
58724d95fa | ||
|
|
8d61fcd5c9 | ||
|
|
3e1be53c36 | ||
|
|
f3754588bd | ||
|
|
c4ffffeec8 | ||
|
|
5b69f6a358 | ||
|
|
1af89a7447 | ||
|
|
90abd81035 | ||
|
|
898824b13f | ||
|
|
9d093aa7f8 | ||
|
|
1770549f6c | ||
|
|
d21be77fd2 | ||
|
|
41a1c19877 | ||
|
|
9b6571ce68 | ||
|
|
88e98e4e35 | ||
|
|
10c56ffbfa | ||
|
|
cb2c8d6f3c | ||
|
|
ca62b850ce | ||
|
|
5a75d4e140 | ||
|
|
e0972b7c24 | ||
|
|
0db497916d | ||
|
|
23a0ad3c4e | ||
|
|
2b4e1c4b67 | ||
|
|
9b1b9244cf | ||
|
|
ad570e9b16 | ||
|
|
812ba6de62 | ||
|
|
8f97124adb | ||
|
|
28289838f9 | ||
|
|
cca8a010c3 | ||
|
|
91ab296692 | ||
|
|
ee6c9c4272 | ||
|
|
21cd36fa92 | ||
|
|
b1aafe3dbc | ||
|
|
5cd832de89 | ||
|
|
24dd9d0518 | ||
|
|
aab6ab810a | ||
|
|
d1d6d5e71e | ||
|
|
e67dd68522 | ||
|
|
e25eae846d | ||
|
|
995eeaa455 | ||
|
|
240c61b967 | ||
|
|
2d8b0753b4 | ||
|
|
44eab3de7f | ||
|
|
007be5bf95 | ||
|
|
ee19c7c51f | ||
|
|
ce56afbdf9 | ||
|
|
51012695a1 | ||
|
|
0eef2d2cc5 | ||
|
|
487f9f2815 | ||
|
|
d065adcd8e | ||
|
|
0d9a1dc5eb | ||
|
|
8f9ad15108 | ||
|
|
e538e9b843 | ||
|
|
4a702b6813 | ||
|
|
1e6fd2c57a | ||
|
|
600b959d89 | ||
|
|
b96de9eb13 | ||
|
|
93be19b647 | ||
|
|
74f45f6f1d | ||
|
|
54ba3d2888 | ||
|
|
65d5149f60 | ||
|
|
917ebb3771 | ||
|
|
7e66b1f545 | ||
|
|
05837dca35 | ||
|
|
53be2ebe59 | ||
|
|
0341efcaea | ||
|
|
ec75210fd3 | ||
|
|
e6afe3e806 | ||
|
|
5aa46f068e | ||
|
|
a11a5b28bc | ||
|
|
907aa566ca | ||
|
|
5c21f099a8 | ||
|
|
b91201ae3e | ||
|
|
56d7e19968 | ||
|
|
cf91c6c90e | ||
|
|
9011148adf | ||
|
|
897d0590d2 | ||
|
|
33b33e8458 | ||
|
|
7758f5c187 | ||
|
|
83d7a03ba4 | ||
|
|
a9a0df9699 | ||
|
|
df44f8f5f8 | ||
|
|
216a9ed035 | ||
|
|
35d61b6a6c | ||
|
|
5fb72cea53 | ||
|
|
d54d021e9f | ||
|
|
06e78311df | ||
|
|
df720f95ca | ||
|
|
00faff34d3 | ||
|
|
2b5b3ea4f3 | ||
|
|
95e608d0b4 | ||
|
|
1d55bf87dd | ||
|
|
1220ce53eb | ||
|
|
2006218f87 | ||
|
|
40f427a387 | ||
|
|
445e95baed | ||
|
|
67fbc9ad33 | ||
|
|
1253e9e465 | ||
|
|
21069432e8 | ||
|
|
6facf6a324 | ||
|
|
7556197485 | ||
|
|
8dddd2d896 | ||
|
|
f319c95c2b | ||
|
|
8e972b0907 | ||
|
|
395e400215 | ||
|
|
3685e3111f | ||
|
|
7bb1c75dc6 | ||
|
|
b20834929c | ||
|
|
181891757e | ||
|
|
b16feeae44 | ||
|
|
684e049f27 | ||
|
|
8cebd901b2 | ||
|
|
3c96beb8fb | ||
|
|
8a46459cf9 | ||
|
|
be5c3e9daa | ||
|
|
e44453877c | ||
|
|
f772a4ec56 | ||
|
|
44182ec683 | ||
|
|
b9ab13fa53 | ||
|
|
2ad6721c95 | ||
|
|
b7d0604e62 | ||
|
|
a7518b4b26 | ||
|
|
50613f5d3e | ||
|
|
f814767703 | ||
|
|
4af86d6456 | ||
|
|
f0a4f00c2d | ||
|
|
4321affddb | ||
|
|
926ed55b9b | ||
|
|
2ebf308565 | ||
|
|
1c5e736dce | ||
|
|
b591f9f5b7 | ||
|
|
9724882578 | ||
|
|
ddef2df101 | ||
|
|
8af69c4284 | ||
|
|
6ebe1ab467 | ||
|
|
24e4d9cf6d | ||
|
|
f35fa0aa58 | ||
|
|
403762d862 | ||
|
|
6294530fa3 |
@@ -209,6 +209,7 @@ services:
|
||||
CERT_PRIV_KEY: ${CERT_PRIV_KEY}
|
||||
APP_PORT: ${APP_PORT}
|
||||
API_PORT: ${API_PORT}
|
||||
DEV: 1
|
||||
networks:
|
||||
dev:
|
||||
ipv4_address: ${DOCKER_NGINX_IP}
|
||||
|
||||
@@ -78,24 +78,6 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DATETIME_FORMAT': '%b-%d-%Y - %H:%M',
|
||||
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'knox.auth.TokenAuthentication',
|
||||
),
|
||||
}
|
||||
|
||||
if not DEBUG:
|
||||
REST_FRAMEWORK.update({
|
||||
'DEFAULT_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
)
|
||||
})
|
||||
|
||||
MESH_USERNAME = '${MESH_USER}'
|
||||
MESH_SITE = 'https://${MESH_HOST}'
|
||||
MESH_TOKEN_KEY = '${MESH_TOKEN}'
|
||||
|
||||
1
.gitignore
vendored
@@ -48,3 +48,4 @@ nats-rmm.conf
|
||||
.mypy_cache
|
||||
docs/site/
|
||||
reset_db.sh
|
||||
run_go_cmd.py
|
||||
|
||||
@@ -15,4 +15,5 @@ class Command(BaseCommand):
|
||||
username=uuid.uuid4().hex,
|
||||
is_installer_user=True,
|
||||
password=User.objects.make_random_password(60), # type: ignore
|
||||
block_dashboard_login=True,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 3.2.6 on 2021-09-01 12:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0025_auto_20210721_0424'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='APIKey',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_by', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('created_time', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('modified_by', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('modified_time', models.DateTimeField(auto_now=True, null=True)),
|
||||
('name', models.CharField(max_length=25, unique=True)),
|
||||
('key', models.CharField(blank=True, max_length=48, unique=True)),
|
||||
('expiration', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='role',
|
||||
name='can_manage_api_keys',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 3.2.6 on 2021-09-03 00:54
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0026_auto_20210901_1247'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='apikey',
|
||||
name='user',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='api_key', to='accounts.user'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='block_dashboard_login',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
from django.db.models.fields import CharField, DateTimeField
|
||||
|
||||
from logs.models import BaseAuditModel
|
||||
|
||||
@@ -24,6 +25,7 @@ CLIENT_TREE_SORT_CHOICES = [
|
||||
|
||||
class User(AbstractUser, BaseAuditModel):
|
||||
is_active = models.BooleanField(default=True)
|
||||
block_dashboard_login = models.BooleanField(default=False)
|
||||
totp_key = models.CharField(max_length=50, null=True, blank=True)
|
||||
dark_mode = models.BooleanField(default=True)
|
||||
show_community_scripts = models.BooleanField(default=True)
|
||||
@@ -138,6 +140,9 @@ class Role(BaseAuditModel):
|
||||
can_manage_accounts = models.BooleanField(default=False)
|
||||
can_manage_roles = models.BooleanField(default=False)
|
||||
|
||||
# authentication
|
||||
can_manage_api_keys = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@@ -186,4 +191,22 @@ class Role(BaseAuditModel):
|
||||
"can_manage_winupdates",
|
||||
"can_manage_accounts",
|
||||
"can_manage_roles",
|
||||
"can_manage_api_keys",
|
||||
]
|
||||
|
||||
|
||||
class APIKey(BaseAuditModel):
|
||||
name = CharField(unique=True, max_length=25)
|
||||
key = CharField(unique=True, blank=True, max_length=48)
|
||||
expiration = DateTimeField(blank=True, null=True, default=None)
|
||||
user = models.ForeignKey(
|
||||
"accounts.User",
|
||||
related_name="api_key",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def serialize(apikey):
|
||||
from .serializers import APIKeyAuditSerializer
|
||||
|
||||
return APIKeyAuditSerializer(apikey).data
|
||||
|
||||
@@ -8,6 +8,21 @@ class AccountsPerms(permissions.BasePermission):
|
||||
if r.method == "GET":
|
||||
return True
|
||||
|
||||
# allow users to reset their own password/2fa see issue #686
|
||||
base_path = "/accounts/users/"
|
||||
paths = ["reset/", "reset_totp/"]
|
||||
|
||||
if r.path in [base_path + i for i in paths]:
|
||||
from accounts.models import User
|
||||
|
||||
try:
|
||||
user = User.objects.get(pk=r.data["id"])
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
if user == r.user:
|
||||
return True
|
||||
|
||||
return _has_perm(r, "can_manage_accounts")
|
||||
|
||||
|
||||
@@ -17,3 +32,9 @@ class RolesPerms(permissions.BasePermission):
|
||||
return True
|
||||
|
||||
return _has_perm(r, "can_manage_roles")
|
||||
|
||||
|
||||
class APIKeyPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
|
||||
return _has_perm(r, "can_manage_api_keys")
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import pyotp
|
||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||
from rest_framework.serializers import (
|
||||
ModelSerializer,
|
||||
SerializerMethodField,
|
||||
ReadOnlyField,
|
||||
)
|
||||
|
||||
from .models import User, Role
|
||||
from .models import APIKey, User, Role
|
||||
|
||||
|
||||
class UserUISerializer(ModelSerializer):
|
||||
@@ -17,6 +21,7 @@ class UserUISerializer(ModelSerializer):
|
||||
"client_tree_splitter",
|
||||
"loading_bar_color",
|
||||
"clear_search_when_switching",
|
||||
"block_dashboard_login",
|
||||
]
|
||||
|
||||
|
||||
@@ -33,6 +38,7 @@ class UserSerializer(ModelSerializer):
|
||||
"last_login",
|
||||
"last_login_ip",
|
||||
"role",
|
||||
"block_dashboard_login",
|
||||
]
|
||||
|
||||
|
||||
@@ -64,3 +70,24 @@ class RoleAuditSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class APIKeySerializer(ModelSerializer):
|
||||
|
||||
username = ReadOnlyField(source="user.username")
|
||||
|
||||
class Meta:
|
||||
model = APIKey
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class APIKeyAuditSerializer(ModelSerializer):
|
||||
username = ReadOnlyField(source="user.username")
|
||||
|
||||
class Meta:
|
||||
model = APIKey
|
||||
fields = [
|
||||
"name",
|
||||
"username",
|
||||
"expiration",
|
||||
]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import override_settings
|
||||
|
||||
from accounts.models import User
|
||||
from model_bakery import baker, seq
|
||||
from accounts.models import User, APIKey
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
from accounts.serializers import APIKeySerializer
|
||||
|
||||
|
||||
class TestAccounts(TacticalTestCase):
|
||||
def setUp(self):
|
||||
@@ -39,6 +41,12 @@ class TestAccounts(TacticalTestCase):
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data, "ok")
|
||||
|
||||
# test user set to block dashboard logins
|
||||
self.bob.block_dashboard_login = True
|
||||
self.bob.save()
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 400)
|
||||
|
||||
@patch("pyotp.TOTP.verify")
|
||||
def test_login_view(self, mock_verify):
|
||||
url = "/login/"
|
||||
@@ -288,6 +296,68 @@ class TestUserAction(TacticalTestCase):
|
||||
self.check_not_authenticated("patch", url)
|
||||
|
||||
|
||||
class TestAPIKeyViews(TacticalTestCase):
|
||||
def setUp(self):
|
||||
self.setup_coresettings()
|
||||
self.authenticate()
|
||||
|
||||
def test_get_api_keys(self):
|
||||
url = "/accounts/apikeys/"
|
||||
apikeys = baker.make("accounts.APIKey", key=seq("APIKEY"), _quantity=3)
|
||||
|
||||
serializer = APIKeySerializer(apikeys, many=True)
|
||||
resp = self.client.get(url, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(serializer.data, resp.data) # type: ignore
|
||||
|
||||
self.check_not_authenticated("get", url)
|
||||
|
||||
def test_add_api_keys(self):
|
||||
url = "/accounts/apikeys/"
|
||||
|
||||
user = baker.make("accounts.User")
|
||||
data = {"name": "Name", "user": user.id, "expiration": None}
|
||||
|
||||
resp = self.client.post(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTrue(APIKey.objects.filter(name="Name").exists())
|
||||
self.assertTrue(APIKey.objects.get(name="Name").key)
|
||||
|
||||
self.check_not_authenticated("post", url)
|
||||
|
||||
def test_modify_api_key(self):
|
||||
# test a call where api key doesn't exist
|
||||
resp = self.client.put("/accounts/apikeys/500/", format="json")
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
|
||||
apikey = baker.make("accounts.APIKey", name="Test")
|
||||
url = f"/accounts/apikeys/{apikey.pk}/" # type: ignore
|
||||
|
||||
data = {"name": "New Name"} # type: ignore
|
||||
|
||||
resp = self.client.put(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
apikey = APIKey.objects.get(pk=apikey.pk) # type: ignore
|
||||
self.assertEquals(apikey.name, "New Name")
|
||||
|
||||
self.check_not_authenticated("put", url)
|
||||
|
||||
def test_delete_api_key(self):
|
||||
# test a call where api key doesn't exist
|
||||
resp = self.client.delete("/accounts/apikeys/500/", format="json")
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
|
||||
# test delete api key
|
||||
apikey = baker.make("accounts.APIKey")
|
||||
url = f"/accounts/apikeys/{apikey.pk}/" # type: ignore
|
||||
resp = self.client.delete(url, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
self.assertFalse(APIKey.objects.filter(pk=apikey.pk).exists()) # type: ignore
|
||||
|
||||
self.check_not_authenticated("delete", url)
|
||||
|
||||
|
||||
class TestTOTPSetup(TacticalTestCase):
|
||||
def setUp(self):
|
||||
self.authenticate()
|
||||
@@ -313,3 +383,29 @@ class TestTOTPSetup(TacticalTestCase):
|
||||
r = self.client.post(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data, "totp token already set")
|
||||
|
||||
|
||||
class TestAPIAuthentication(TacticalTestCase):
|
||||
def setUp(self):
|
||||
# create User and associate to API Key
|
||||
self.user = User.objects.create(username="api_user", is_superuser=True)
|
||||
self.api_key = APIKey.objects.create(
|
||||
name="Test Token", key="123456", user=self.user
|
||||
)
|
||||
|
||||
self.client_setup()
|
||||
|
||||
def test_api_auth(self):
|
||||
url = "/clients/clients/"
|
||||
# auth should fail if no header set
|
||||
self.check_not_authenticated("get", url)
|
||||
|
||||
# invalid api key in header should return code 400
|
||||
self.client.credentials(HTTP_X_API_KEY="000000")
|
||||
r = self.client.get(url, format="json")
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
# valid api key in header should return code 200
|
||||
self.client.credentials(HTTP_X_API_KEY="123456")
|
||||
r = self.client.get(url, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
@@ -12,4 +12,6 @@ urlpatterns = [
|
||||
path("permslist/", views.PermsList.as_view()),
|
||||
path("roles/", views.GetAddRoles.as_view()),
|
||||
path("<int:pk>/role/", views.GetUpdateDeleteRole.as_view()),
|
||||
path("apikeys/", views.GetAddAPIKeys.as_view()),
|
||||
path("apikeys/<int:pk>/", views.GetUpdateDeleteAPIKey.as_view()),
|
||||
]
|
||||
|
||||
@@ -13,9 +13,10 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from tacticalrmm.utils import notify_error
|
||||
|
||||
from .models import Role, User
|
||||
from .permissions import AccountsPerms, RolesPerms
|
||||
from .models import APIKey, Role, User
|
||||
from .permissions import APIKeyPerms, AccountsPerms, RolesPerms
|
||||
from .serializers import (
|
||||
APIKeySerializer,
|
||||
RoleSerializer,
|
||||
TOTPSetupSerializer,
|
||||
UserSerializer,
|
||||
@@ -47,6 +48,9 @@ class CheckCreds(KnoxLoginView):
|
||||
|
||||
user = serializer.validated_data["user"]
|
||||
|
||||
if user.block_dashboard_login:
|
||||
return Response("bad credentials", status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# if totp token not set modify response to notify frontend
|
||||
if not user.totp_key:
|
||||
login(request, user)
|
||||
@@ -68,6 +72,9 @@ class LoginView(KnoxLoginView):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = serializer.validated_data["user"]
|
||||
|
||||
if user.block_dashboard_login:
|
||||
return Response("bad credentials", status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
token = request.data["twofactor"]
|
||||
totp = pyotp.TOTP(user.totp_key)
|
||||
|
||||
@@ -123,8 +130,10 @@ class GetAddUsers(APIView):
|
||||
f"ERROR: User {request.data['username']} already exists!"
|
||||
)
|
||||
|
||||
user.first_name = request.data["first_name"]
|
||||
user.last_name = request.data["last_name"]
|
||||
if "first_name" in request.data.keys():
|
||||
user.first_name = request.data["first_name"]
|
||||
if "last_name" in request.data.keys():
|
||||
user.last_name = request.data["last_name"]
|
||||
if "role" in request.data.keys() and isinstance(request.data["role"], int):
|
||||
role = get_object_or_404(Role, pk=request.data["role"])
|
||||
user.role = role
|
||||
@@ -252,3 +261,48 @@ class GetUpdateDeleteRole(APIView):
|
||||
role = get_object_or_404(Role, pk=pk)
|
||||
role.delete()
|
||||
return Response("ok")
|
||||
|
||||
|
||||
class GetAddAPIKeys(APIView):
|
||||
permission_classes = [IsAuthenticated, APIKeyPerms]
|
||||
|
||||
def get(self, request):
|
||||
apikeys = APIKey.objects.all()
|
||||
return Response(APIKeySerializer(apikeys, many=True).data)
|
||||
|
||||
def post(self, request):
|
||||
# generate a random API Key
|
||||
# https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits/23728630#23728630
|
||||
import random
|
||||
import string
|
||||
|
||||
request.data["key"] = "".join(
|
||||
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(32)
|
||||
)
|
||||
|
||||
serializer = APIKeySerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
obj = serializer.save()
|
||||
return Response("The API Key was added")
|
||||
|
||||
|
||||
class GetUpdateDeleteAPIKey(APIView):
|
||||
permission_classes = [IsAuthenticated, APIKeyPerms]
|
||||
|
||||
def put(self, request, pk):
|
||||
apikey = get_object_or_404(APIKey, pk=pk)
|
||||
|
||||
# remove API key is present in request data
|
||||
if "key" in request.data.keys():
|
||||
request.data.pop("key")
|
||||
|
||||
serializer = APIKeySerializer(instance=apikey, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response("The API Key was edited")
|
||||
|
||||
def delete(self, request, pk):
|
||||
apikey = get_object_or_404(APIKey, pk=pk)
|
||||
apikey.delete()
|
||||
return Response("The API Key was deleted")
|
||||
|
||||
@@ -87,6 +87,7 @@ class Agent(BaseAuditModel):
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
from automation.tasks import generate_agent_checks_task
|
||||
|
||||
# get old agent if exists
|
||||
old_agent = Agent.objects.get(pk=self.pk) if self.pk else None
|
||||
@@ -103,8 +104,11 @@ class Agent(BaseAuditModel):
|
||||
or (old_agent.monitoring_type != self.monitoring_type)
|
||||
or (old_agent.block_policy_inheritance != self.block_policy_inheritance)
|
||||
):
|
||||
self.generate_checks_from_policies()
|
||||
self.generate_tasks_from_policies()
|
||||
generate_agent_checks_task.delay(agents=[self.pk], create_tasks=True)
|
||||
|
||||
# calculate alert template for new agents
|
||||
if not old_agent:
|
||||
self.set_alert_template()
|
||||
|
||||
def __str__(self):
|
||||
return self.hostname
|
||||
@@ -413,11 +417,12 @@ class Agent(BaseAuditModel):
|
||||
update.action = "approve"
|
||||
update.save(update_fields=["action"])
|
||||
|
||||
DebugLog.info(
|
||||
agent=self,
|
||||
log_type="windows_updates",
|
||||
message=f"Approving windows updates on {self.hostname}",
|
||||
)
|
||||
if updates:
|
||||
DebugLog.info(
|
||||
agent=self,
|
||||
log_type="windows_updates",
|
||||
message=f"Approving windows updates on {self.hostname}",
|
||||
)
|
||||
|
||||
# returns agent policy merged with a client or site specific policy
|
||||
def get_patch_policy(self):
|
||||
|
||||
@@ -148,6 +148,7 @@ class AgentEditSerializer(serializers.ModelSerializer):
|
||||
fields = [
|
||||
"id",
|
||||
"hostname",
|
||||
"block_policy_inheritance",
|
||||
"client",
|
||||
"site",
|
||||
"monitoring_type",
|
||||
@@ -212,8 +213,8 @@ class AgentHistorySerializer(serializers.ModelSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
def get_time(self, history):
|
||||
timezone = get_default_timezone()
|
||||
return history.time.astimezone(timezone).strftime("%m %d %Y %H:%M:%S")
|
||||
tz = self.context["default_tz"]
|
||||
return history.time.astimezone(tz).strftime("%m %d %Y %H:%M:%S")
|
||||
|
||||
|
||||
class AgentAuditSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import asyncio
|
||||
import datetime as dt
|
||||
import random
|
||||
import urllib.parse
|
||||
from time import sleep
|
||||
from typing import Union
|
||||
|
||||
from alerts.models import Alert
|
||||
from core.models import CodeSignToken, CoreSettings
|
||||
from core.models import CoreSettings
|
||||
from django.conf import settings
|
||||
from django.utils import timezone as djangotime
|
||||
from logs.models import DebugLog, PendingAction
|
||||
@@ -16,10 +15,10 @@ from tacticalrmm.celery import app
|
||||
from tacticalrmm.utils import run_nats_api_cmd
|
||||
|
||||
from agents.models import Agent
|
||||
from agents.utils import get_winagent_url
|
||||
|
||||
|
||||
def agent_update(pk: int, codesigntoken: str = None, force: bool = False) -> str:
|
||||
from agents.utils import get_exegen_url
|
||||
def agent_update(pk: int, force: bool = False) -> str:
|
||||
|
||||
agent = Agent.objects.get(pk=pk)
|
||||
|
||||
@@ -37,13 +36,7 @@ def agent_update(pk: int, codesigntoken: str = None, force: bool = False) -> str
|
||||
|
||||
version = settings.LATEST_AGENT_VER
|
||||
inno = agent.win_inno_exe
|
||||
|
||||
if codesigntoken is not None and pyver.parse(version) >= pyver.parse("1.5.0"):
|
||||
base_url = get_exegen_url() + "/api/v1/winagents/?"
|
||||
params = {"version": version, "arch": agent.arch, "token": codesigntoken}
|
||||
url = base_url + urllib.parse.urlencode(params)
|
||||
else:
|
||||
url = agent.winagent_dl
|
||||
url = get_winagent_url(agent.arch)
|
||||
|
||||
if not force:
|
||||
if agent.pendingactions.filter(
|
||||
@@ -77,30 +70,20 @@ def agent_update(pk: int, codesigntoken: str = None, force: bool = False) -> str
|
||||
|
||||
@app.task
|
||||
def force_code_sign(pks: list[int]) -> None:
|
||||
try:
|
||||
token = CodeSignToken.objects.first().tokenv # type:ignore
|
||||
except:
|
||||
return
|
||||
|
||||
chunks = (pks[i : i + 50] for i in range(0, len(pks), 50))
|
||||
for chunk in chunks:
|
||||
for pk in chunk:
|
||||
agent_update(pk=pk, codesigntoken=token, force=True)
|
||||
agent_update(pk=pk, force=True)
|
||||
sleep(0.05)
|
||||
sleep(4)
|
||||
|
||||
|
||||
@app.task
|
||||
def send_agent_update_task(pks: list[int]) -> None:
|
||||
try:
|
||||
codesigntoken = CodeSignToken.objects.first().token # type:ignore
|
||||
except:
|
||||
codesigntoken = None
|
||||
|
||||
chunks = (pks[i : i + 30] for i in range(0, len(pks), 30))
|
||||
for chunk in chunks:
|
||||
for pk in chunk:
|
||||
agent_update(pk, codesigntoken)
|
||||
agent_update(pk)
|
||||
sleep(0.05)
|
||||
sleep(4)
|
||||
|
||||
@@ -111,11 +94,6 @@ def auto_self_agent_update_task() -> None:
|
||||
if not core.agent_auto_update: # type:ignore
|
||||
return
|
||||
|
||||
try:
|
||||
codesigntoken = CodeSignToken.objects.first().token # type:ignore
|
||||
except:
|
||||
codesigntoken = None
|
||||
|
||||
q = Agent.objects.only("pk", "version")
|
||||
pks: list[int] = [
|
||||
i.pk
|
||||
@@ -126,7 +104,7 @@ def auto_self_agent_update_task() -> None:
|
||||
chunks = (pks[i : i + 30] for i in range(0, len(pks), 30))
|
||||
for chunk in chunks:
|
||||
for pk in chunk:
|
||||
agent_update(pk, codesigntoken)
|
||||
agent_update(pk)
|
||||
sleep(0.05)
|
||||
sleep(4)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import pytz
|
||||
from django.utils import timezone as djangotime
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -966,7 +967,8 @@ class TestAgentViews(TacticalTestCase):
|
||||
|
||||
# test pulling data
|
||||
r = self.client.get(url, format="json")
|
||||
data = AgentHistorySerializer(history, many=True).data
|
||||
ctx = {"default_tz": pytz.timezone("America/Los_Angeles")}
|
||||
data = AgentHistorySerializer(history, many=True, context=ctx).data
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data, data) # type:ignore
|
||||
|
||||
@@ -1047,9 +1049,11 @@ class TestAgentTasks(TacticalTestCase):
|
||||
self.authenticate()
|
||||
self.setup_coresettings()
|
||||
|
||||
@patch("agents.utils.get_exegen_url")
|
||||
@patch("agents.utils.get_winagent_url")
|
||||
@patch("agents.models.Agent.nats_cmd")
|
||||
def test_agent_update(self, nats_cmd, get_exe):
|
||||
def test_agent_update(self, nats_cmd, get_url):
|
||||
get_url.return_value = "https://exe.tacticalrmm.io"
|
||||
|
||||
from agents.tasks import agent_update
|
||||
|
||||
agent_noarch = baker.make_recipe(
|
||||
@@ -1075,7 +1079,7 @@ class TestAgentTasks(TacticalTestCase):
|
||||
version="1.4.14",
|
||||
)
|
||||
|
||||
r = agent_update(agent64_nosign.pk, None)
|
||||
r = agent_update(agent64_nosign.pk)
|
||||
self.assertEqual(r, "created")
|
||||
action = PendingAction.objects.get(agent__pk=agent64_nosign.pk)
|
||||
self.assertEqual(action.action_type, "agentupdate")
|
||||
@@ -1101,7 +1105,7 @@ class TestAgentTasks(TacticalTestCase):
|
||||
)
|
||||
|
||||
# test __with__ code signing (64 bit)
|
||||
codesign = baker.make("core.CodeSignToken", token="testtoken123")
|
||||
""" codesign = baker.make("core.CodeSignToken", token="testtoken123")
|
||||
agent64_sign = baker.make_recipe(
|
||||
"agents.agent",
|
||||
operating_system="Windows 10 Pro, 64 bit (build 19041.450)",
|
||||
@@ -1151,7 +1155,7 @@ class TestAgentTasks(TacticalTestCase):
|
||||
)
|
||||
action = PendingAction.objects.get(agent__pk=agent32_sign.pk)
|
||||
self.assertEqual(action.action_type, "agentupdate")
|
||||
self.assertEqual(action.status, "pending")
|
||||
self.assertEqual(action.status, "pending") """
|
||||
|
||||
@patch("agents.tasks.agent_update")
|
||||
@patch("agents.tasks.sleep", return_value=None)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import random
|
||||
import urllib.parse
|
||||
|
||||
import requests
|
||||
|
||||
from django.conf import settings
|
||||
from core.models import CodeSignToken
|
||||
|
||||
|
||||
def get_exegen_url() -> str:
|
||||
@@ -20,18 +21,20 @@ def get_exegen_url() -> str:
|
||||
|
||||
|
||||
def get_winagent_url(arch: str) -> str:
|
||||
from core.models import CodeSignToken
|
||||
|
||||
dl_url = settings.DL_32 if arch == "32" else settings.DL_64
|
||||
|
||||
try:
|
||||
codetoken = CodeSignToken.objects.first().token
|
||||
base_url = get_exegen_url() + "/api/v1/winagents/?"
|
||||
params = {
|
||||
"version": settings.LATEST_AGENT_VER,
|
||||
"arch": arch,
|
||||
"token": codetoken,
|
||||
}
|
||||
dl_url = base_url + urllib.parse.urlencode(params)
|
||||
t: CodeSignToken = CodeSignToken.objects.first() # type: ignore
|
||||
if t.is_valid:
|
||||
base_url = get_exegen_url() + "/api/v1/winagents/?"
|
||||
params = {
|
||||
"version": settings.LATEST_AGENT_VER,
|
||||
"arch": arch,
|
||||
"token": t.token,
|
||||
}
|
||||
dl_url = base_url + urllib.parse.urlencode(params)
|
||||
except:
|
||||
dl_url = settings.DL_64 if arch == "64" else settings.DL_32
|
||||
pass
|
||||
|
||||
return dl_url
|
||||
|
||||
@@ -842,5 +842,5 @@ class AgentHistoryView(APIView):
|
||||
def get(self, request, pk):
|
||||
agent = get_object_or_404(Agent, pk=pk)
|
||||
history = AgentHistory.objects.filter(agent=agent)
|
||||
|
||||
return Response(AgentHistorySerializer(history, many=True).data)
|
||||
ctx = {"default_tz": get_default_timezone()}
|
||||
return Response(AgentHistorySerializer(history, many=True, context=ctx).data)
|
||||
|
||||
@@ -918,11 +918,13 @@ class TestPolicyTasks(TacticalTestCase):
|
||||
@patch("autotasks.models.AutomatedTask.create_task_on_agent")
|
||||
@patch("autotasks.models.AutomatedTask.delete_task_on_agent")
|
||||
def test_delete_policy_tasks(self, delete_task_on_agent, create_task):
|
||||
from .tasks import delete_policy_autotasks_task
|
||||
from .tasks import delete_policy_autotasks_task, generate_agent_checks_task
|
||||
|
||||
policy = baker.make("automation.Policy", active=True)
|
||||
tasks = baker.make("autotasks.AutomatedTask", policy=policy, _quantity=3)
|
||||
baker.make_recipe("agents.server_agent", policy=policy)
|
||||
agent = baker.make_recipe("agents.server_agent", policy=policy)
|
||||
|
||||
generate_agent_checks_task(agents=[agent.pk], create_tasks=True)
|
||||
|
||||
delete_policy_autotasks_task(task=tasks[0].id) # type: ignore
|
||||
|
||||
@@ -931,11 +933,13 @@ class TestPolicyTasks(TacticalTestCase):
|
||||
@patch("autotasks.models.AutomatedTask.create_task_on_agent")
|
||||
@patch("autotasks.models.AutomatedTask.run_win_task")
|
||||
def test_run_policy_task(self, run_win_task, create_task):
|
||||
from .tasks import run_win_policy_autotasks_task
|
||||
from .tasks import run_win_policy_autotasks_task, generate_agent_checks_task
|
||||
|
||||
policy = baker.make("automation.Policy", active=True)
|
||||
tasks = baker.make("autotasks.AutomatedTask", policy=policy, _quantity=3)
|
||||
baker.make_recipe("agents.server_agent", policy=policy)
|
||||
agent = baker.make_recipe("agents.server_agent", policy=policy)
|
||||
|
||||
generate_agent_checks_task(agents=[agent.pk], create_tasks=True)
|
||||
|
||||
run_win_policy_autotasks_task(task=tasks[0].id) # type: ignore
|
||||
|
||||
@@ -944,7 +948,10 @@ class TestPolicyTasks(TacticalTestCase):
|
||||
@patch("autotasks.models.AutomatedTask.create_task_on_agent")
|
||||
@patch("autotasks.models.AutomatedTask.modify_task_on_agent")
|
||||
def test_update_policy_tasks(self, modify_task_on_agent, create_task):
|
||||
from .tasks import update_policy_autotasks_fields_task
|
||||
from .tasks import (
|
||||
update_policy_autotasks_fields_task,
|
||||
generate_agent_checks_task,
|
||||
)
|
||||
|
||||
# setup data
|
||||
policy = baker.make("automation.Policy", active=True)
|
||||
@@ -956,6 +963,8 @@ class TestPolicyTasks(TacticalTestCase):
|
||||
)
|
||||
agent = baker.make_recipe("agents.server_agent", policy=policy)
|
||||
|
||||
generate_agent_checks_task(agents=[agent.pk], create_tasks=True)
|
||||
|
||||
tasks[0].enabled = False # type: ignore
|
||||
tasks[0].save() # type: ignore
|
||||
|
||||
@@ -995,6 +1004,8 @@ class TestPolicyTasks(TacticalTestCase):
|
||||
|
||||
@patch("autotasks.models.AutomatedTask.create_task_on_agent")
|
||||
def test_policy_exclusions(self, create_task):
|
||||
from .tasks import generate_agent_checks_task
|
||||
|
||||
# setup data
|
||||
policy = baker.make("automation.Policy", active=True)
|
||||
baker.make_recipe("checks.memory_check", policy=policy)
|
||||
@@ -1003,6 +1014,8 @@ class TestPolicyTasks(TacticalTestCase):
|
||||
"agents.agent", policy=policy, monitoring_type="server"
|
||||
)
|
||||
|
||||
generate_agent_checks_task(agents=[agent.pk], create_tasks=True)
|
||||
|
||||
# make sure related agents on policy returns correctly
|
||||
self.assertEqual(policy.related_agents().count(), 1) # type: ignore
|
||||
self.assertEqual(agent.agentchecks.count(), 1) # type: ignore
|
||||
|
||||
@@ -197,6 +197,14 @@ class AutomatedTask(BaseAuditModel):
|
||||
|
||||
def create_policy_task(self, agent=None, policy=None, assigned_check=None):
|
||||
|
||||
# added to allow new policy tasks to be assigned to check only when the agent check exists already
|
||||
if (
|
||||
self.assigned_check
|
||||
and agent
|
||||
and agent.agentchecks.filter(parent_check=self.assigned_check.id).exists()
|
||||
):
|
||||
assigned_check = agent.agentchecks.get(parent_check=self.assigned_check.id)
|
||||
|
||||
# if policy is present, then this task is being copied to another policy
|
||||
# if agent is present, then this task is being created on an agent from a policy
|
||||
# exit if neither are set or if both are set
|
||||
|
||||
@@ -457,7 +457,7 @@ class Check(BaseAuditModel):
|
||||
|
||||
elif self.status == "passing":
|
||||
self.fail_count = 0
|
||||
self.save(update_fields=["status", "fail_count", "alert_severity"])
|
||||
self.save()
|
||||
if Alert.objects.filter(assigned_check=self, resolved=False).exists():
|
||||
Alert.handle_alert_resolve(self)
|
||||
|
||||
|
||||
@@ -137,11 +137,11 @@ class GetUpdateDeleteCheck(APIView):
|
||||
|
||||
# Re-evaluate agent checks is policy was enforced
|
||||
if check.policy.enforced:
|
||||
generate_agent_checks_task.delay(policy=check.policy)
|
||||
generate_agent_checks_task.delay(policy=check.policy.pk)
|
||||
|
||||
# Agent check deleted
|
||||
elif check.agent:
|
||||
check.agent.generate_checks_from_policies()
|
||||
generate_agent_checks_task.delay(agents=[check.agent.pk])
|
||||
|
||||
return Response(f"{check.readable_desc} was deleted!")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.core.management.base import BaseCommand
|
||||
|
||||
from logs.models import PendingAction
|
||||
from scripts.models import Script
|
||||
from accounts.models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -13,3 +14,9 @@ class Command(BaseCommand):
|
||||
|
||||
# load community scripts into the db
|
||||
Script.load_community_scripts()
|
||||
|
||||
# make sure installer user is set to block_dashboard_logins
|
||||
if User.objects.filter(is_installer_user=True).exists():
|
||||
for user in User.objects.filter(is_installer_user=True):
|
||||
user.block_dashboard_login = True
|
||||
user.save()
|
||||
|
||||
73
api/tacticalrmm/core/migrations/0027_auto_20210905_1606.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Generated by Django 3.2.6 on 2021-09-05 16:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0026_coresettings_audit_log_prune_days'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customfield',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customfield',
|
||||
name='created_time',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customfield',
|
||||
name='modified_by',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customfield',
|
||||
name='modified_time',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='globalkvstore',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='globalkvstore',
|
||||
name='created_time',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='globalkvstore',
|
||||
name='modified_by',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='globalkvstore',
|
||||
name='modified_time',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='urlaction',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='urlaction',
|
||||
name='created_time',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='urlaction',
|
||||
name='modified_by',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='urlaction',
|
||||
name='modified_time',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
import requests
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
from django.db.models.enums import Choices
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
@@ -8,6 +8,7 @@ from django.contrib.postgres.fields import ArrayField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from twilio.rest import Client as TwClient
|
||||
from twilio.base.exceptions import TwilioRestException
|
||||
|
||||
from logs.models import BaseAuditModel, DebugLog, LOG_LEVEL_CHOICES
|
||||
|
||||
@@ -195,22 +196,29 @@ class CoreSettings(BaseAuditModel):
|
||||
else:
|
||||
return True
|
||||
|
||||
def send_sms(self, body, alert_template=None):
|
||||
if not alert_template or not self.sms_is_configured:
|
||||
return
|
||||
def send_sms(self, body, alert_template=None, test=False):
|
||||
if not alert_template and not self.sms_is_configured:
|
||||
return "Sms alerting is not setup correctly."
|
||||
|
||||
# override email recipients if alert_template is passed and is set
|
||||
if alert_template and alert_template.text_recipients:
|
||||
text_recipients = alert_template.email_recipients
|
||||
text_recipients = alert_template.text_recipients
|
||||
else:
|
||||
text_recipients = self.sms_alert_recipients
|
||||
|
||||
if not text_recipients:
|
||||
return "No sms recipients found"
|
||||
|
||||
tw_client = TwClient(self.twilio_account_sid, self.twilio_auth_token)
|
||||
for num in text_recipients:
|
||||
try:
|
||||
tw_client.messages.create(body=body, to=num, from_=self.twilio_number)
|
||||
except Exception as e:
|
||||
except TwilioRestException as e:
|
||||
DebugLog.error(message=f"SMS failed to send: {e}")
|
||||
if test:
|
||||
return str(e)
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def serialize(core):
|
||||
@@ -232,7 +240,7 @@ FIELD_TYPE_CHOICES = (
|
||||
MODEL_CHOICES = (("client", "Client"), ("site", "Site"), ("agent", "Agent"))
|
||||
|
||||
|
||||
class CustomField(models.Model):
|
||||
class CustomField(BaseAuditModel):
|
||||
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
model = models.CharField(max_length=25, choices=MODEL_CHOICES)
|
||||
@@ -261,6 +269,12 @@ class CustomField(models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def serialize(field):
|
||||
from .serializers import CustomFieldSerializer
|
||||
|
||||
return CustomFieldSerializer(field).data
|
||||
|
||||
@property
|
||||
def default_value(self):
|
||||
if self.type == "multiple":
|
||||
@@ -300,26 +314,63 @@ class CodeSignToken(models.Model):
|
||||
|
||||
super(CodeSignToken, self).save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
if not self.token:
|
||||
return False
|
||||
|
||||
errors = []
|
||||
for url in settings.EXE_GEN_URLS:
|
||||
try:
|
||||
r = requests.post(
|
||||
f"{url}/api/v1/checktoken",
|
||||
json={"token": self.token},
|
||||
headers={"Content-type": "application/json"},
|
||||
timeout=15,
|
||||
)
|
||||
except Exception as e:
|
||||
errors.append(str(e))
|
||||
else:
|
||||
errors = []
|
||||
break
|
||||
|
||||
if errors:
|
||||
return False
|
||||
|
||||
return r.status_code == 200
|
||||
|
||||
def __str__(self):
|
||||
return "Code signing token"
|
||||
|
||||
|
||||
class GlobalKVStore(models.Model):
|
||||
class GlobalKVStore(BaseAuditModel):
|
||||
name = models.CharField(max_length=25)
|
||||
value = models.TextField()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def serialize(store):
|
||||
from .serializers import KeyStoreSerializer
|
||||
|
||||
OPEN_ACTIONS = (("window", "New Window"), ("tab", "New Tab"))
|
||||
return KeyStoreSerializer(store).data
|
||||
|
||||
|
||||
class URLAction(models.Model):
|
||||
class URLAction(BaseAuditModel):
|
||||
name = models.CharField(max_length=25)
|
||||
desc = models.CharField(max_length=100, null=True, blank=True)
|
||||
pattern = models.TextField()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def serialize(action):
|
||||
from .serializers import URLActionSerializer
|
||||
|
||||
return URLActionSerializer(action).data
|
||||
|
||||
|
||||
RUN_ON_CHOICES = (
|
||||
("client", "Client"),
|
||||
|
||||
@@ -58,7 +58,9 @@ def core_maintenance_tasks():
|
||||
def cache_db_fields_task():
|
||||
from agents.models import Agent
|
||||
|
||||
for agent in Agent.objects.all():
|
||||
for agent in Agent.objects.prefetch_related("winupdates", "pendingactions").only(
|
||||
"pending_actions_count", "has_patches_pending", "pk"
|
||||
):
|
||||
agent.pending_actions_count = agent.pendingactions.filter(
|
||||
status="pending"
|
||||
).count()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import os
|
||||
import pprint
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import get_object_or_404
|
||||
from logs.models import AuditLog
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.exceptions import ParseError
|
||||
@@ -12,7 +12,6 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from agents.permissions import MeshPerms
|
||||
from tacticalrmm.utils import notify_error
|
||||
|
||||
from .models import CodeSignToken, CoreSettings, CustomField, GlobalKVStore, URLAction
|
||||
@@ -32,7 +31,7 @@ from .serializers import (
|
||||
|
||||
|
||||
class UploadMeshAgent(APIView):
|
||||
permission_classes = [IsAuthenticated, MeshPerms]
|
||||
permission_classes = [IsAuthenticated, EditCoreSettingsPerms]
|
||||
parser_class = (FileUploadParser,)
|
||||
|
||||
def put(self, request, format=None):
|
||||
@@ -48,7 +47,9 @@ class UploadMeshAgent(APIView):
|
||||
for chunk in f.chunks():
|
||||
j.write(chunk)
|
||||
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
return Response(
|
||||
"Mesh Agent uploaded successfully", status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
|
||||
@api_view()
|
||||
@@ -369,12 +370,18 @@ class RunURLAction(APIView):
|
||||
|
||||
url_pattern = re.sub("\\{\\{" + string + "\\}\\}", str(value), url_pattern)
|
||||
|
||||
AuditLog.audit_url_action(
|
||||
username=request.user.username,
|
||||
urlaction=action,
|
||||
instance=instance,
|
||||
debug_info={"ip": request._client_ip},
|
||||
)
|
||||
|
||||
return Response(requote_uri(url_pattern))
|
||||
|
||||
|
||||
class TwilioSMSTest(APIView):
|
||||
def get(self, request):
|
||||
from twilio.rest import Client as TwClient
|
||||
|
||||
core = CoreSettings.objects.first()
|
||||
if not core.sms_is_configured:
|
||||
@@ -382,14 +389,9 @@ class TwilioSMSTest(APIView):
|
||||
"All fields are required, including at least 1 recipient"
|
||||
)
|
||||
|
||||
try:
|
||||
tw_client = TwClient(core.twilio_account_sid, core.twilio_auth_token)
|
||||
tw_client.messages.create(
|
||||
body="TacticalRMM Test SMS",
|
||||
to=core.sms_alert_recipients[0],
|
||||
from_=core.twilio_number,
|
||||
)
|
||||
except Exception as e:
|
||||
return notify_error(pprint.pformat(e))
|
||||
r = core.send_sms("TacticalRMM Test SMS", test=True)
|
||||
|
||||
return Response("SMS Test OK!")
|
||||
if not isinstance(r, bool) and isinstance(r, str):
|
||||
return notify_error(r)
|
||||
|
||||
return Response("SMS Test sent successfully!")
|
||||
|
||||
23
api/tacticalrmm/logs/migrations/0018_auto_20210905_1606.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.6 on 2021-09-05 16:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('logs', '0017_auto_20210731_1707'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='auditlog',
|
||||
name='action',
|
||||
field=models.CharField(choices=[('login', 'User Login'), ('failed_login', 'Failed User Login'), ('delete', 'Delete Object'), ('modify', 'Modify Object'), ('add', 'Add Object'), ('view', 'View Object'), ('check_run', 'Check Run'), ('task_run', 'Task Run'), ('agent_install', 'Agent Install'), ('remote_session', 'Remote Session'), ('execute_script', 'Execute Script'), ('execute_command', 'Execute Command'), ('bulk_action', 'Bulk Action'), ('url_action', 'URL Action')], max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='auditlog',
|
||||
name='object_type',
|
||||
field=models.CharField(choices=[('user', 'User'), ('script', 'Script'), ('agent', 'Agent'), ('policy', 'Policy'), ('winupdatepolicy', 'Patch Policy'), ('client', 'Client'), ('site', 'Site'), ('check', 'Check'), ('automatedtask', 'Automated Task'), ('coresettings', 'Core Settings'), ('bulk', 'Bulk'), ('alerttemplate', 'Alert Template'), ('role', 'Role'), ('urlaction', 'URL Action'), ('keystore', 'Global Key Store'), ('customfield', 'Custom Field')], max_length=100),
|
||||
),
|
||||
]
|
||||
@@ -36,6 +36,7 @@ AUDIT_ACTION_TYPE_CHOICES = [
|
||||
("execute_script", "Execute Script"),
|
||||
("execute_command", "Execute Command"),
|
||||
("bulk_action", "Bulk Action"),
|
||||
("url_action", "URL Action"),
|
||||
]
|
||||
|
||||
AUDIT_OBJECT_TYPE_CHOICES = [
|
||||
@@ -52,6 +53,9 @@ AUDIT_OBJECT_TYPE_CHOICES = [
|
||||
("bulk", "Bulk"),
|
||||
("alerttemplate", "Alert Template"),
|
||||
("role", "Role"),
|
||||
("urlaction", "URL Action"),
|
||||
("keystore", "Global Key Store"),
|
||||
("customfield", "Custom Field"),
|
||||
]
|
||||
|
||||
STATUS_CHOICES = [
|
||||
@@ -102,6 +106,7 @@ class AuditLog(models.Model):
|
||||
AuditLog.objects.create(
|
||||
username=username,
|
||||
agent=agent.hostname,
|
||||
agent_id=agent.id,
|
||||
object_type="agent",
|
||||
action="execute_command",
|
||||
message=f"{username} issued {shell} command on {agent.hostname}.",
|
||||
@@ -116,6 +121,7 @@ class AuditLog(models.Model):
|
||||
AuditLog.objects.create(
|
||||
username=username,
|
||||
object_type=object_type,
|
||||
agent=before["hostname"] if object_type == "agent" else None,
|
||||
agent_id=before["id"] if object_type == "agent" else None,
|
||||
action="modify",
|
||||
message=f"{username} modified {object_type} {name}",
|
||||
@@ -129,7 +135,8 @@ class AuditLog(models.Model):
|
||||
AuditLog.objects.create(
|
||||
username=username,
|
||||
object_type=object_type,
|
||||
agent=after["id"] if object_type == "agent" else None,
|
||||
agent=after["hostname"] if object_type == "agent" else None,
|
||||
agent_id=after["id"] if object_type == "agent" else None,
|
||||
action="add",
|
||||
message=f"{username} added {object_type} {name}",
|
||||
after_value=after,
|
||||
@@ -141,7 +148,7 @@ class AuditLog(models.Model):
|
||||
AuditLog.objects.create(
|
||||
username=username,
|
||||
object_type=object_type,
|
||||
agent=before["id"] if object_type == "agent" else None,
|
||||
agent=before["hostname"] if object_type == "agent" else None,
|
||||
action="delete",
|
||||
message=f"{username} deleted {object_type} {name}",
|
||||
before_value=before,
|
||||
@@ -190,6 +197,21 @@ class AuditLog(models.Model):
|
||||
debug_info=debug_info,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def audit_url_action(username, urlaction, instance, debug_info={}):
|
||||
|
||||
name = instance.hostname if hasattr(instance, "hostname") else instance.name
|
||||
classname = type(instance).__name__
|
||||
AuditLog.objects.create(
|
||||
username=username,
|
||||
agent=instance.hostname if classname == "Agent" else None,
|
||||
agent_id=instance.id if classname == "Agent" else None,
|
||||
object_type=classname.lower(),
|
||||
action="url_action",
|
||||
message=f"{username} ran url action: {urlaction.pattern} on {classname}: {name}",
|
||||
debug_info=debug_info,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def audit_bulk_action(username, action, affected, debug_info={}):
|
||||
from agents.models import Agent
|
||||
@@ -271,22 +293,30 @@ class DebugLog(models.Model):
|
||||
log_type="system_issues",
|
||||
):
|
||||
if get_debug_level() in ["info"]:
|
||||
cls(log_level="info", agent=agent, log_type=log_type, message=message)
|
||||
cls.objects.create(
|
||||
log_level="info", agent=agent, log_type=log_type, message=message
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def warning(cls, message, agent=None, log_type="system_issues"):
|
||||
if get_debug_level() in ["info", "warning"]:
|
||||
cls(log_level="warning", agent=agent, log_type=log_type, message=message)
|
||||
cls.objects.create(
|
||||
log_level="warning", agent=agent, log_type=log_type, message=message
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def error(cls, message, agent=None, log_type="system_issues"):
|
||||
if get_debug_level() in ["info", "warning", "error"]:
|
||||
cls(log_level="error", agent=agent, log_type=log_type, message=message)
|
||||
cls.objects.create(
|
||||
log_level="error", agent=agent, log_type=log_type, message=message
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def critical(cls, message, agent=None, log_type="system_issues"):
|
||||
if get_debug_level() in ["info", "warning", "error", "critical"]:
|
||||
cls(log_level="critical", agent=agent, log_type=log_type, message=message)
|
||||
cls.objects.create(
|
||||
log_level="critical", agent=agent, log_type=log_type, message=message
|
||||
)
|
||||
|
||||
|
||||
class PendingAction(models.Model):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from tacticalrmm.utils import get_default_timezone
|
||||
|
||||
from .models import AuditLog, DebugLog, PendingAction
|
||||
|
||||
@@ -14,8 +13,8 @@ class AuditLogSerializer(serializers.ModelSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
def get_entry_time(self, log):
|
||||
timezone = get_default_timezone()
|
||||
return log.entry_time.astimezone(timezone).strftime("%m %d %Y %H:%M:%S")
|
||||
tz = self.context["default_tz"]
|
||||
return log.entry_time.astimezone(tz).strftime("%m %d %Y %H:%M:%S")
|
||||
|
||||
|
||||
class PendingActionSerializer(serializers.ModelSerializer):
|
||||
@@ -40,5 +39,5 @@ class DebugLogSerializer(serializers.ModelSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
def get_entry_time(self, log):
|
||||
timezone = get_default_timezone()
|
||||
return log.entry_time.astimezone(timezone).strftime("%m %d %Y %H:%M:%S")
|
||||
tz = self.context["default_tz"]
|
||||
return log.entry_time.astimezone(tz).strftime("%m %d %Y %H:%M:%S")
|
||||
|
||||
@@ -13,7 +13,7 @@ from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from tacticalrmm.utils import notify_error
|
||||
from tacticalrmm.utils import notify_error, get_default_timezone
|
||||
|
||||
from .models import AuditLog, PendingAction, DebugLog
|
||||
from .permissions import AuditLogPerms, DebugLogPerms, ManagePendingActionPerms
|
||||
@@ -79,11 +79,12 @@ class GetAuditLogs(APIView):
|
||||
).order_by(order_by)
|
||||
|
||||
paginator = Paginator(audit_logs, pagination["rowsPerPage"])
|
||||
ctx = {"default_tz": get_default_timezone()}
|
||||
|
||||
return Response(
|
||||
{
|
||||
"audit_logs": AuditLogSerializer(
|
||||
paginator.get_page(pagination["page"]), many=True
|
||||
paginator.get_page(pagination["page"]), many=True, context=ctx
|
||||
).data,
|
||||
"total": paginator.count,
|
||||
}
|
||||
@@ -138,7 +139,6 @@ class GetDebugLog(APIView):
|
||||
permission_classes = [IsAuthenticated, DebugLogPerms]
|
||||
|
||||
def patch(self, request):
|
||||
|
||||
agentFilter = Q()
|
||||
logTypeFilter = Q()
|
||||
logLevelFilter = Q()
|
||||
@@ -153,9 +153,12 @@ class GetDebugLog(APIView):
|
||||
agentFilter = Q(agent=request.data["agentFilter"])
|
||||
|
||||
debug_logs = (
|
||||
DebugLog.objects.filter(logLevelFilter)
|
||||
DebugLog.objects.prefetch_related("agent")
|
||||
.filter(logLevelFilter)
|
||||
.filter(agentFilter)
|
||||
.filter(logTypeFilter)
|
||||
)
|
||||
|
||||
return Response(DebugLogSerializer(debug_logs, many=True).data)
|
||||
ctx = {"default_tz": get_default_timezone()}
|
||||
ret = DebugLogSerializer(debug_logs, many=True, context=ctx).data
|
||||
return Response(ret)
|
||||
|
||||
@@ -4,13 +4,13 @@ celery==5.1.2
|
||||
certifi==2021.5.30
|
||||
cffi==1.14.6
|
||||
channels==3.0.4
|
||||
channels_redis==3.3.0
|
||||
channels_redis==3.3.1
|
||||
chardet==4.0.0
|
||||
cryptography==3.4.8
|
||||
daphne==3.0.2
|
||||
Django==3.2.6
|
||||
django-cors-headers==3.8.0
|
||||
django-ipware==3.0.2
|
||||
Django==3.2.8
|
||||
django-cors-headers==3.10.0
|
||||
django-ipware==4.0.0
|
||||
django-rest-knox==4.1.0
|
||||
djangorestframework==3.12.4
|
||||
future==0.18.2
|
||||
@@ -19,19 +19,19 @@ msgpack==1.0.2
|
||||
packaging==21.0
|
||||
psycopg2-binary==2.9.1
|
||||
pycparser==2.20
|
||||
pycryptodome==3.10.1
|
||||
pycryptodome==3.10.4
|
||||
pyotp==2.6.0
|
||||
pyparsing==2.4.7
|
||||
pytz==2021.1
|
||||
pytz==2021.3
|
||||
qrcode==6.1
|
||||
redis==3.5.3
|
||||
requests==2.26.0
|
||||
six==1.16.0
|
||||
sqlparse==0.4.1
|
||||
twilio==6.63.1
|
||||
urllib3==1.26.6
|
||||
uWSGI==2.0.19.1
|
||||
sqlparse==0.4.2
|
||||
twilio==7.1.0
|
||||
urllib3==1.26.7
|
||||
uWSGI==2.0.20
|
||||
validators==0.18.2
|
||||
vine==5.0.0
|
||||
websockets==9.1
|
||||
zipp==3.5.0
|
||||
zipp==3.6.0
|
||||
|
||||
@@ -30,18 +30,27 @@
|
||||
"default_timeout": "300"
|
||||
},
|
||||
{
|
||||
"guid": "2ee134d5-76aa-4160-b334-a1efbc62079f",
|
||||
"filename": "Win_Install_Duplicati.ps1",
|
||||
"submittedBy": "https://github.com/Omnicef",
|
||||
"name": "Duplicati - Install",
|
||||
"description": "This script installs Duplicati 2.0.5.1 as a service.",
|
||||
"shell": "powershell",
|
||||
"guid": "7b1d90a1-3eda-48ab-9c49-20e714c9e82a",
|
||||
"filename": "Win_Duplicati_Install.bat",
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"name": "Duplicati - Install 2.0.6.100 to work with Community Check Status",
|
||||
"description": "This script installs Duplicati 2.0.6.100 as a service and creates status files to be used with commuity check",
|
||||
"shell": "cmd",
|
||||
"category": "TRMM (Win):3rd Party Software",
|
||||
"default_timeout": "300"
|
||||
},
|
||||
{
|
||||
"guid": "7c14beb4-d1c3-41aa-8e70-92a267d6e080",
|
||||
"filename": "Win_Duplicati_Status.ps1",
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"name": "Duplicati - Check Status",
|
||||
"description": "Checks Duplicati Backup is running properly over the last 24 hours",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):3rd Party Software>Monitoring"
|
||||
},
|
||||
{
|
||||
"guid": "81cc5bcb-01bf-4b0c-89b9-0ac0f3fe0c04",
|
||||
"filename": "Win_Reset_Windows_Update.ps1",
|
||||
"filename": "Win_Windows_Update_Reset.ps1",
|
||||
"submittedBy": "https://github.com/Omnicef",
|
||||
"name": "Windows Update - Reset",
|
||||
"description": "This script will reset all of the Windows Updates components to DEFAULT SETTINGS.",
|
||||
@@ -91,17 +100,23 @@
|
||||
"guid": "9d34f482-1f0c-4b2f-b65f-a9cf3c13ef5f",
|
||||
"filename": "Win_TRMM_Rename_Installed_App.ps1",
|
||||
"submittedBy": "https://github.com/bradhawkins85",
|
||||
"name": "TacticalRMM Agent Rename",
|
||||
"name": "TacticalRMM - Agent Rename",
|
||||
"description": "Updates the DisplayName registry entry for the Tactical RMM windows agent to your desired name. This script takes 1 required argument: the name you wish to set.",
|
||||
"args": [
|
||||
"<string>"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):TacticalRMM Related"
|
||||
},
|
||||
{
|
||||
"guid": "525ae965-1dcf-4c17-92b3-5da3cf6819f5",
|
||||
"filename": "Win_Bitlocker_Encrypted_Drive_c.ps1",
|
||||
"submittedBy": "https://github.com/ThatsNASt",
|
||||
"name": "Bitlocker - Check C Drive for Status",
|
||||
"description": "Runs a check on drive C for Bitlocker status.",
|
||||
"filename": "Win_Bitlocker_Drive_Check_Status.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Bitlocker - Check Drive for Status",
|
||||
"description": "Runs a check on drive for Bitlocker status. Returns 0 if Bitlocker is not enabled, 1 if Bitlocker is enabled",
|
||||
"args": [
|
||||
"[Drive <string>]"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Storage"
|
||||
},
|
||||
@@ -235,15 +250,6 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Hardware"
|
||||
},
|
||||
{
|
||||
"guid": "7c14beb4-d1c3-41aa-8e70-92a267d6e080",
|
||||
"filename": "Win_Duplicati_Status.ps1",
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"name": "Duplicati - Check Status",
|
||||
"description": "Checks Duplicati Backup is running properly over the last 24 hours",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):3rd Party Software"
|
||||
},
|
||||
{
|
||||
"guid": "907652a5-9ec1-4759-9871-a7743f805ff2",
|
||||
"filename": "Win_Software_Uninstall.ps1",
|
||||
@@ -317,7 +323,7 @@
|
||||
},
|
||||
{
|
||||
"guid": "a821975c-60df-4d58-8990-6cf8a55b4ee0",
|
||||
"filename": "Win_Sync_Time.bat",
|
||||
"filename": "Win_Time_Sync.bat",
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"name": "ADDC - Sync DC Time",
|
||||
"description": "Syncs time with domain controller",
|
||||
@@ -425,6 +431,11 @@
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Chocolatey - Install, Uninstall and Upgrade Software",
|
||||
"description": "This script installs, uninstalls and updates software using Chocolatey with logic to slow tasks to minimize hitting community limits. Mode install/uninstall/upgrade Hosts x",
|
||||
"args": [
|
||||
"-$PackageName <string>",
|
||||
"[-Hosts <string>]",
|
||||
"[-mode {(install) | update | uninstall}]"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):3rd Party Software>Chocolatey",
|
||||
"default_timeout": "600"
|
||||
@@ -478,17 +489,23 @@
|
||||
"guid": "08ca81f2-f044-4dfc-ad47-090b19b19d76",
|
||||
"filename": "Win_User_Logged_in_with_Temp_Profile.ps1",
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"name": "User Logged in with temp profile check",
|
||||
"name": "User Check - See if user logged in with temp profile",
|
||||
"description": "Check if users are logged in with a temp profile",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other"
|
||||
},
|
||||
{
|
||||
"guid": "5d905886-9eb1-4129-8b81-a013f842eb24",
|
||||
"filename": "Win_Rename_Computer.ps1",
|
||||
"filename": "Win_Computer_Rename.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Rename Computer",
|
||||
"description": "Rename computer. First parameter will be new PC name. 2nd parameter if yes will auto-reboot machine",
|
||||
"args": [
|
||||
"-NewName <string>",
|
||||
"[-Username <string>]",
|
||||
"[-Password <string>]",
|
||||
"[-Restart]"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other",
|
||||
"default_timeout": 30
|
||||
@@ -499,6 +516,9 @@
|
||||
"submittedBy": "https://github.com/tremor021",
|
||||
"name": "Power - Restart or Shutdown PC",
|
||||
"description": "Restart PC. Add parameter: shutdown if you want to shutdown computer",
|
||||
"args": [
|
||||
"[shutdown]"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Updates"
|
||||
},
|
||||
@@ -523,7 +543,7 @@
|
||||
"-url {{client.ScreenConnectInstaller}}",
|
||||
"-clientname {{client.name}}",
|
||||
"-sitename {{site.name}}",
|
||||
"-action install"
|
||||
"-action {(install) | uninstall | start | stop}"
|
||||
],
|
||||
"default_timeout": "90",
|
||||
"shell": "powershell",
|
||||
@@ -573,7 +593,7 @@
|
||||
"guid": "7c0c7e37-60ff-462f-9c34-b5cd4c4796a7",
|
||||
"filename": "Win_Wifi_SSID_and_Password_Retrieval.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Network Wireless - Retrieve Saved passwords",
|
||||
"name": "Network Wireless - Retrieve Saved WiFi passwords",
|
||||
"description": "Returns all saved wifi passwords stored on the computer",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Network",
|
||||
@@ -624,7 +644,7 @@
|
||||
"filename": "Win_Network_TCP_Reset_Stack.bat",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Network - Reset tcp using netsh",
|
||||
"description": "resets tcp stack using netsh",
|
||||
"description": "Resets TCP stack using netsh",
|
||||
"shell": "cmd",
|
||||
"category": "TRMM (Win):Network",
|
||||
"default_timeout": "120"
|
||||
@@ -633,7 +653,7 @@
|
||||
"guid": "6ce5682a-49db-4c0b-9417-609cf905ac43",
|
||||
"filename": "Win_Win10_Change_Key_and_Activate.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Product Key in Win10 Change and Activate",
|
||||
"name": "Product Key in Win10 - Change and Activate",
|
||||
"description": "Insert new product key and Activate. Requires 1 parameter the product key you want to use",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other",
|
||||
@@ -653,7 +673,7 @@
|
||||
"guid": "83f6c6ea-6120-4fd3-bec8-d3abc505dcdf",
|
||||
"filename": "Win_TRMM_Start_Menu_Delete_Shortcut.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "TacticalRMM Delete Start Menu Shortcut for App",
|
||||
"name": "TacticalRMM - Delete Start Menu Shortcut for App",
|
||||
"description": "Delete its application shortcut that's installed in the start menu by default",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):TacticalRMM Related",
|
||||
@@ -735,19 +755,26 @@
|
||||
"guid": "6a52f495-d43e-40f4-91a9-bbe4f578e6d1",
|
||||
"filename": "Win_User_Create.ps1",
|
||||
"submittedBy": "https://github.com/brodur",
|
||||
"name": "Create Local User",
|
||||
"name": "User - Create Local",
|
||||
"description": "Create a local user. Parameters are: username, password and optional: description, fullname, group (adds to Users if not specified)",
|
||||
"args": [
|
||||
"-username <string>",
|
||||
"-password <string>",
|
||||
"[-description <string>]",
|
||||
"[-fullname <string>]",
|
||||
"[-group <string>]"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other"
|
||||
"category": "TRMM (Win):User Management"
|
||||
},
|
||||
{
|
||||
"guid": "57997ec7-b293-4fd5-9f90-a25426d0eb90",
|
||||
"filename": "Win_Users_List.ps1",
|
||||
"submittedBy": "https://github.com/tremor021",
|
||||
"name": "Get Computer Users",
|
||||
"name": "Users - List Local Users and Enabled/Disabled Status",
|
||||
"description": "Get list of computer users and show which one is enabled",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other"
|
||||
"category": "TRMM (Win):User Management"
|
||||
},
|
||||
{
|
||||
"guid": "77da9c87-5a7a-4ba1-bdde-3eeb3b01d62d",
|
||||
|
||||
@@ -14,7 +14,15 @@ class Command(BaseCommand):
|
||||
|
||||
agents = Agent.objects.all()
|
||||
for agent in agents:
|
||||
sw = agent.installedsoftware_set.first().software
|
||||
try:
|
||||
sw = agent.installedsoftware_set.first().software
|
||||
except:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"Agent {agent.hostname} missing software list. Try manually refreshing it from the web UI from the software tab."
|
||||
)
|
||||
)
|
||||
continue
|
||||
for i in sw:
|
||||
if search in i["name"].lower():
|
||||
self.stdout.write(
|
||||
|
||||
@@ -5,6 +5,6 @@ from . import views
|
||||
urlpatterns = [
|
||||
path("chocos/", views.chocos),
|
||||
path("install/", views.install),
|
||||
path("installed/<pk>/", views.get_installed),
|
||||
path("refresh/<pk>/", views.refresh_installed),
|
||||
path("installed/<int:pk>/", views.get_installed),
|
||||
path("refresh/<int:pk>/", views.refresh_installed),
|
||||
]
|
||||
|
||||
64
api/tacticalrmm/tacticalrmm/auth.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from django.utils import timezone as djangotime
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.authentication import BaseAuthentication, HTTP_HEADER_ENCODING
|
||||
|
||||
from accounts.models import APIKey
|
||||
|
||||
|
||||
def get_authorization_header(request):
|
||||
"""
|
||||
Return request's 'Authorization:' header, as a bytestring.
|
||||
|
||||
Hide some test client ickyness where the header can be unicode.
|
||||
"""
|
||||
auth = request.META.get("HTTP_X_API_KEY", b"")
|
||||
if isinstance(auth, str):
|
||||
# Work around django test client oddness
|
||||
auth = auth.encode(HTTP_HEADER_ENCODING)
|
||||
return auth
|
||||
|
||||
|
||||
class APIAuthentication(BaseAuthentication):
|
||||
"""
|
||||
Simple token based authentication for stateless api access.
|
||||
|
||||
Clients should authenticate by passing the token key in the "X-API-KEY"
|
||||
HTTP header. For example:
|
||||
|
||||
X-API-KEY: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
|
||||
"""
|
||||
|
||||
def get_model(self):
|
||||
return APIKey
|
||||
|
||||
def authenticate(self, request):
|
||||
auth = get_authorization_header(request)
|
||||
|
||||
if not auth:
|
||||
return None
|
||||
|
||||
try:
|
||||
apikey = auth.decode()
|
||||
except UnicodeError:
|
||||
msg = _(
|
||||
"Invalid token header. Token string should not contain invalid characters."
|
||||
)
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
||||
return self.authenticate_credentials(apikey)
|
||||
|
||||
def authenticate_credentials(self, key):
|
||||
try:
|
||||
apikey = APIKey.objects.select_related("user").get(key=key)
|
||||
except APIKey.DoesNotExist:
|
||||
raise exceptions.AuthenticationFailed(_("Invalid token."))
|
||||
|
||||
if not apikey.user.is_active:
|
||||
raise exceptions.AuthenticationFailed(_("User inactive or deleted."))
|
||||
|
||||
# check if token is expired
|
||||
if apikey.expiration and apikey.expiration < djangotime.now():
|
||||
raise exceptions.AuthenticationFailed(_("The token as expired."))
|
||||
|
||||
return (apikey.user, apikey.key)
|
||||
@@ -1,43 +0,0 @@
|
||||
SECRET_KEY = 'changeme'
|
||||
|
||||
ALLOWED_HOSTS = ['api.example.com']
|
||||
|
||||
ADMIN_URL = "somerandomstring/"
|
||||
|
||||
CORS_ORIGIN_WHITELIST = ["https://rmm.example.com",]
|
||||
|
||||
DEBUG = False
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'tacticalrmm',
|
||||
'USER': 'tacticalrmm',
|
||||
'PASSWORD': 'changeme',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '5432',
|
||||
}
|
||||
}
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DATETIME_FORMAT': "%b-%d-%Y - %H:%M",
|
||||
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'knox.auth.TokenAuthentication',
|
||||
),
|
||||
}
|
||||
|
||||
if not DEBUG:
|
||||
REST_FRAMEWORK.update({
|
||||
'DEFAULT_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
)
|
||||
})
|
||||
|
||||
MESH_USERNAME = "changeme"
|
||||
MESH_SITE = "https://mesh.example.com"
|
||||
MESH_TOKEN_KEY = "changeme"
|
||||
REDIS_HOST = "localhost"
|
||||
@@ -1,3 +1,7 @@
|
||||
from rest_framework import permissions
|
||||
from tacticalrmm.auth import APIAuthentication
|
||||
|
||||
|
||||
def _has_perm(request, perm):
|
||||
if request.user.is_superuser or (
|
||||
request.user.role and getattr(request.user.role, "is_superuser")
|
||||
|
||||
@@ -15,24 +15,24 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
|
||||
AUTH_USER_MODEL = "accounts.User"
|
||||
|
||||
# latest release
|
||||
TRMM_VERSION = "0.8.2"
|
||||
TRMM_VERSION = "0.8.5"
|
||||
|
||||
# bump this version everytime vue code is changed
|
||||
# to alert user they need to manually refresh their browser
|
||||
APP_VER = "0.0.144"
|
||||
APP_VER = "0.0.147"
|
||||
|
||||
# https://github.com/wh1te909/rmmagent
|
||||
LATEST_AGENT_VER = "1.6.1"
|
||||
LATEST_AGENT_VER = "1.6.2"
|
||||
|
||||
MESH_VER = "0.9.16"
|
||||
|
||||
NATS_SERVER_VER = "2.3.3"
|
||||
|
||||
# for the update script, bump when need to recreate venv or npm install
|
||||
PIP_VER = "21"
|
||||
NPM_VER = "21"
|
||||
PIP_VER = "22"
|
||||
NPM_VER = "23"
|
||||
|
||||
SETUPTOOLS_VER = "57.4.0"
|
||||
SETUPTOOLS_VER = "58.2.0"
|
||||
WHEEL_VER = "0.37.0"
|
||||
|
||||
DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe"
|
||||
@@ -58,6 +58,21 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
"DATETIME_FORMAT": "%b-%d-%Y - %H:%M",
|
||||
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"knox.auth.TokenAuthentication",
|
||||
"tacticalrmm.auth.APIAuthentication",
|
||||
),
|
||||
}
|
||||
|
||||
if not "AZPIPELINE" in os.environ:
|
||||
if not DEBUG: # type: ignore
|
||||
REST_FRAMEWORK.update(
|
||||
{"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",)}
|
||||
)
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
@@ -207,7 +222,10 @@ if "AZPIPELINE" in os.environ:
|
||||
REST_FRAMEWORK = {
|
||||
"DATETIME_FORMAT": "%b-%d-%Y - %H:%M",
|
||||
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": ("knox.auth.TokenAuthentication",),
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"knox.auth.TokenAuthentication",
|
||||
"tacticalrmm.auth.APIAuthentication",
|
||||
),
|
||||
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import string
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.parse
|
||||
from typing import Optional, Union
|
||||
|
||||
import pytz
|
||||
@@ -50,7 +49,7 @@ def generate_winagent_exe(
|
||||
file_name: str,
|
||||
) -> Union[Response, FileResponse]:
|
||||
|
||||
from agents.utils import get_exegen_url
|
||||
from agents.utils import get_winagent_url
|
||||
|
||||
inno = (
|
||||
f"winagent-v{settings.LATEST_AGENT_VER}.exe"
|
||||
@@ -58,18 +57,12 @@ def generate_winagent_exe(
|
||||
else f"winagent-v{settings.LATEST_AGENT_VER}-x86.exe"
|
||||
)
|
||||
|
||||
dl_url = get_winagent_url(arch)
|
||||
|
||||
try:
|
||||
codetoken = CodeSignToken.objects.first().token # type:ignore
|
||||
base_url = get_exegen_url() + "/api/v1/winagents/?"
|
||||
params = {
|
||||
"version": settings.LATEST_AGENT_VER,
|
||||
"arch": arch,
|
||||
"token": codetoken,
|
||||
}
|
||||
dl_url = base_url + urllib.parse.urlencode(params)
|
||||
except:
|
||||
codetoken = ""
|
||||
dl_url = settings.DL_64 if arch == "64" else settings.DL_32
|
||||
|
||||
data = {
|
||||
"client": client,
|
||||
|
||||
@@ -5,6 +5,7 @@ set -e
|
||||
: "${WORKER_CONNECTIONS:=2048}"
|
||||
: "${APP_PORT:=80}"
|
||||
: "${API_PORT:=80}"
|
||||
: "${DEV:=0}"
|
||||
|
||||
CERT_PRIV_PATH=${TACTICAL_DIR}/certs/privkey.pem
|
||||
CERT_PUB_PATH=${TACTICAL_DIR}/certs/fullchain.pem
|
||||
@@ -28,6 +29,34 @@ fi
|
||||
|
||||
/bin/bash -c "sed -i 's/worker_connections.*/worker_connections ${WORKER_CONNECTIONS};/g' /etc/nginx/nginx.conf"
|
||||
|
||||
|
||||
if [[ $DEV -eq 1 ]]; then
|
||||
API_NGINX="
|
||||
#Using variable to disable start checks
|
||||
set \$api http://tactical-backend:${API_PORT};
|
||||
proxy_pass \$api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_cache_bypass \$http_upgrade;
|
||||
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection \"upgrade\";
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_set_header X-Forwarded-Host \$host;
|
||||
proxy_set_header X-Forwarded-Port \$server_port;
|
||||
"
|
||||
else
|
||||
API_NGINX="
|
||||
#Using variable to disable start checks
|
||||
set \$api tactical-backend:${API_PORT};
|
||||
|
||||
include uwsgi_params;
|
||||
uwsgi_pass \$api;
|
||||
"
|
||||
fi
|
||||
|
||||
nginx_config="$(cat << EOF
|
||||
# backend config
|
||||
server {
|
||||
@@ -36,21 +65,7 @@ server {
|
||||
server_name ${API_HOST};
|
||||
|
||||
location / {
|
||||
#Using variable to disable start checks
|
||||
set \$api http://tactical-backend:${API_PORT};
|
||||
|
||||
proxy_pass \$api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_cache_bypass \$http_upgrade;
|
||||
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_set_header X-Forwarded-Host \$host;
|
||||
proxy_set_header X-Forwarded-Port \$server_port;
|
||||
${API_NGINX}
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
|
||||
@@ -18,8 +18,7 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends gcc libc6-dev && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
pip install --upgrade pip && \
|
||||
pip install --no-cache-dir setuptools wheel gunicorn && \
|
||||
sed -i '/uWSGI/d' ${TACTICAL_TMP_DIR}/api/requirements.txt && \
|
||||
pip install --no-cache-dir setuptools wheel && \
|
||||
pip install --no-cache-dir -r ${TACTICAL_TMP_DIR}/api/requirements.txt
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ if [ "$1" = 'tactical-init' ]; then
|
||||
|
||||
mkdir -p ${TACTICAL_DIR}/tmp
|
||||
mkdir -p ${TACTICAL_DIR}/api/tacticalrmm/private/exe
|
||||
mkdir -p ${TACTICAL_DIR}/api/tacticalrmm/logs
|
||||
mkdir -p ${TACTICAL_DIR}/api/tacticalrmm/private/log
|
||||
touch ${TACTICAL_DIR}/api/tacticalrmm/private/log/django_debug.log
|
||||
|
||||
until (echo > /dev/tcp/"${POSTGRES_HOST}"/"${POSTGRES_PORT}") &> /dev/null; do
|
||||
echo "waiting for postgresql container to be ready..."
|
||||
@@ -87,24 +88,6 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DATETIME_FORMAT': '%b-%d-%Y - %H:%M',
|
||||
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'knox.auth.TokenAuthentication',
|
||||
),
|
||||
}
|
||||
|
||||
if not DEBUG:
|
||||
REST_FRAMEWORK.update({
|
||||
'DEFAULT_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
)
|
||||
})
|
||||
|
||||
MESH_USERNAME = '${MESH_USER}'
|
||||
MESH_SITE = 'https://${MESH_HOST}'
|
||||
MESH_TOKEN_KEY = '${MESH_TOKEN}'
|
||||
@@ -116,6 +99,28 @@ EOF
|
||||
|
||||
echo "${localvars}" > ${TACTICAL_DIR}/api/tacticalrmm/local_settings.py
|
||||
|
||||
|
||||
uwsgiconf="$(cat << EOF
|
||||
[uwsgi]
|
||||
chdir = /opt/tactical/api
|
||||
module = tacticalrmm.wsgi
|
||||
home = /opt/venv
|
||||
master = true
|
||||
processes = 8
|
||||
threads = 2
|
||||
enable-threads = true
|
||||
socket = 0.0.0.0:80
|
||||
chmod-socket = 660
|
||||
buffer-size = 65535
|
||||
vacuum = true
|
||||
die-on-term = true
|
||||
max-requests = 2000
|
||||
EOF
|
||||
)"
|
||||
|
||||
echo "${uwsgiconf}" > ${TACTICAL_DIR}/api/uwsgi.ini
|
||||
|
||||
|
||||
# run migrations and init scripts
|
||||
python manage.py migrate --no-input
|
||||
python manage.py collectstatic --no-input
|
||||
@@ -141,22 +146,7 @@ fi
|
||||
if [ "$1" = 'tactical-backend' ]; then
|
||||
check_tactical_ready
|
||||
|
||||
# Prepare log files and start outputting logs to stdout
|
||||
mkdir -p ${TACTICAL_DIR}/api/tacticalrmm/logs
|
||||
touch ${TACTICAL_DIR}/api/tacticalrmm/logs/gunicorn.log
|
||||
touch ${TACTICAL_DIR}/api/tacticalrmm/logs/gunicorn-access.log
|
||||
tail -n 0 -f ${TACTICAL_DIR}/api/tacticalrmm/logs/gunicorn*.log &
|
||||
|
||||
export DJANGO_SETTINGS_MODULE=tacticalrmm.settings
|
||||
|
||||
exec gunicorn tacticalrmm.wsgi:application \
|
||||
--name tactical-backend \
|
||||
--bind 0.0.0.0:80 \
|
||||
--workers 5 \
|
||||
--log-level=info \
|
||||
--log-file=${TACTICAL_DIR}/api/tacticalrmm/logs/gunicorn.log \
|
||||
--access-logfile=${TACTICAL_DIR}/api/tacticalrmm/logs/gunicorn-access.log \
|
||||
|
||||
uwsgi ${TACTICAL_DIR}/api/uwsgi.ini
|
||||
fi
|
||||
|
||||
if [ "$1" = 'tactical-celery' ]; then
|
||||
@@ -170,7 +160,7 @@ if [ "$1" = 'tactical-celerybeat' ]; then
|
||||
celery -A tacticalrmm beat -l info
|
||||
fi
|
||||
|
||||
# backend container
|
||||
# websocket container
|
||||
if [ "$1" = 'tactical-websockets' ]; then
|
||||
check_tactical_ready
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
# tactical tactical-frontend tactical-nats tactical-nginx
|
||||
# tactical tactical-frontend tactical-nats tactical-nginx tactical-meshcentral
|
||||
DOCKER_IMAGES="tactical tactical-frontend tactical-nats tactical-nginx tactical-meshcentral"
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -31,4 +31,4 @@ Paste download link into the `bdurl` when you right click your target clients na
|
||||
|
||||
Right click the Agent you want to deploy to and **Run Script**. Select **BitDefender GravityZone Install** and set timeout for 1800 seconds.
|
||||
|
||||
**Install time will vary based on internet speed and other AV removal by BitDefender BEST deployment**
|
||||
**Install time will vary based on internet speed and other AV removal by BitDefender BEST deployment**
|
||||
|
||||
@@ -6,4 +6,4 @@ See <https://github.com/dinger1986/TRMM-Grafana>
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# Backing up the RMM
|
||||
|
||||
!!!note
|
||||
This is only applicable for the standard install, not Docker installs.
|
||||
|
||||
A backup script is provided for quick and easy way to backup all settings into one file to move to another server.
|
||||
|
||||
Download the backup script:
|
||||
|
||||
```bash
|
||||
wget -N https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/backup.sh
|
||||
```
|
||||
@@ -23,4 +27,3 @@ chmod +x backup.sh
|
||||
The backup tar file will be saved in `/rmmbackups` with the following format:
|
||||
|
||||
`rmm-backup-CURRENTDATETIME.tar`
|
||||
|
||||
|
||||
@@ -12,11 +12,10 @@ Please allow up to 24 hours for a response
|
||||
|
||||
You will then be sent a code signing auth token, which you should enter into Tactical's web UI from *Settings > Code Signing*
|
||||
|
||||
|
||||
## How does it work?
|
||||
|
||||
Everytime you generate an agent or an agent does a self-update, your self-hosted instance sends a request to Tactical's code signing servers with your auth token.
|
||||
|
||||
If the token is valid, the server sends you back a code signed agent. If not, it sends you back the un-signed agent.
|
||||
|
||||
If you think your auth token has been compromised or stolen then please email support or contact wh1te909 on discord to get a new token / invalidate the old one.
|
||||
If you think your auth token has been compromised or stolen then please email support or contact wh1te909 on discord to get a new token / invalidate the old one.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
### Contributing to the docs
|
||||
## Contributing to the docs
|
||||
|
||||
Docs are built with [MKDocs for Material](https://squidfunk.github.io/mkdocs-material/)
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# Community Scripts
|
||||
|
||||
## Script Library Naming Conventions
|
||||
|
||||
### File names
|
||||
### File names
|
||||
|
||||
Under `/scripts` the file name should generally follow this format:
|
||||
|
||||
```
|
||||
```text
|
||||
(Platform)_(Category or Function)_(What It Does).xxx
|
||||
```
|
||||
|
||||
@@ -13,7 +15,7 @@ Under `/scripts` the file name should generally follow this format:
|
||||
|
||||
Platform for now are:
|
||||
|
||||
```
|
||||
```text
|
||||
Win
|
||||
OSX
|
||||
Linux
|
||||
@@ -21,10 +23,9 @@ iOS
|
||||
Android
|
||||
```
|
||||
|
||||
|
||||
Good filename examples include:
|
||||
|
||||
```
|
||||
```text
|
||||
Win_Azure_Mars_Cloud_Backup_Status.ps1
|
||||
Win_AzureAD_Check_Connection_Status.ps1
|
||||
Win_Network_DHCP_Set.bat
|
||||
@@ -44,7 +45,7 @@ Script Manager
|
||||
|
||||
- Folder View (Grouped by Categories)
|
||||
|
||||
Run or Add script
|
||||
Run or Add script
|
||||
|
||||
- Running scripts manually or adding tasks (or adding in Automation Manager)
|
||||
|
||||
@@ -53,7 +54,7 @@ Run or Add script
|
||||
|
||||
Make sure your Name roughly follows the order of file naming as above
|
||||
|
||||
```
|
||||
```text
|
||||
Category or Function - What It Does
|
||||
```
|
||||
|
||||
@@ -67,12 +68,13 @@ Category or Function - What It Does
|
||||
|
||||
### Good Habits
|
||||
|
||||
- Try and make them fully self-contained.
|
||||
- Try and make them fully self-contained.
|
||||
|
||||
- If they pull data from elsewhere, create comment notes at the top with references for others to audit/validate
|
||||
|
||||
- Good folder locations to use for standardized things:
|
||||
```
|
||||
|
||||
```text
|
||||
c:\ProgramData\TacticalRMM\
|
||||
c:\ProgramData\TacticalRMM\scripts
|
||||
c:\ProgramData\TacticalRMM\toolbox
|
||||
@@ -81,9 +83,10 @@ c:\ProgramData\TacticalRMM\temp
|
||||
c:\ProgramData\TacticalRMM\
|
||||
```
|
||||
|
||||
- Command Parameters are good. Optional command parameters for extra functions are better.
|
||||
- Command Parameters are good. Optional command parameters for extra functions are better.
|
||||
|
||||
- Add standardized Comment headers to scripts (include the first 2, more if appropriate):
|
||||
|
||||
```powershell
|
||||
<#
|
||||
.Synopsis
|
||||
@@ -118,9 +121,12 @@ c:\ProgramData\TacticalRMM\
|
||||
|
||||
- Doesn't play well with other community scripts (reused names etc.)
|
||||
|
||||
|
||||
*****
|
||||
|
||||
## Script Parameters
|
||||
|
||||
|
||||
|
||||
## Useful Reference Script Examples
|
||||
|
||||
RunAsUser (since Tactical RMM runs as system)
|
||||
@@ -136,14 +142,13 @@ Optional Command Parameters and testing for errors
|
||||
|
||||
## Volunteers Needed
|
||||
|
||||
If you want to contribute back to the project there are a lot of scripts that need some TLC (Tender Loving Care) please paruse thru them here: [https://github.com/wh1te909/tacticalrmm/tree/develop/scripts_wip](https://github.com/wh1te909/tacticalrmm/tree/develop/scripts_wip)
|
||||
If you want to contribute back to the project there are a lot of scripts that need some TLC (Tender Loving Care) please paruse thru them in The Script WIP (Work In Progress): [https://github.com/wh1te909/tacticalrmm/tree/develop/scripts_wip](https://github.com/wh1te909/tacticalrmm/tree/develop/scripts_wip)
|
||||
|
||||
Discuss/ask questions in the Discord group [here](https://discord.com/channels/736478043522072608/744281869499105290)
|
||||
|
||||
What you can add is:
|
||||
|
||||
- Add standardized Comment headers per above
|
||||
- Parameterize scripts where appropriate
|
||||
- Add $ExitCode and error conditions as appropriate
|
||||
- Contact @silversword in Discord if you need help doing Github additions/edits/adding to the community Library and have questions about [Script Library Naming Conventions](#script-library-naming-conventions)
|
||||
|
||||
- Add standardized Comment headers per above
|
||||
- Parameterize scripts where appropriate
|
||||
- Add $ExitCode and error conditions as appropriate
|
||||
- Contact @silversword in Discord if you need help doing Github additions/edits/adding to the community Library and have questions about [Script Library Naming Conventions](#script-library-naming-conventions)
|
||||
|
||||
115
docs/docs/contributing_using_a_remote_server.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Contributing Using a Remote Server
|
||||
|
||||
The below instructions are for a non-production server that has Tactical RMM installed and configured with a real domain. You can then use your own GitHub to push changes to and then submit a PR request to the TRMM `develop` branch (<https://github.com/wh1te909/tacticalrmm>).
|
||||
|
||||
!!!warning
|
||||
Do not attempt development of this kind on your production server.
|
||||
|
||||
## Install Tacticall RMM
|
||||
|
||||
### 1. Traditional install
|
||||
|
||||
This guide assumes you have done a [Traditional Install](install_server.md).
|
||||
|
||||
### 2. Install VSCode and Extensions
|
||||
Download VSCode [here](https://code.visualstudio.com/download)
|
||||
|
||||
Download the Remote SSH Development Pack [here](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)
|
||||
|
||||
## Configure the Remote Development Server
|
||||
### 1. Connect
|
||||
|
||||
The remote development server should already have Tactical RMM installed via the traditional install method.
|
||||
|
||||
After the extension pack is installed in VSCode you will have a new button at the bottom-left of VSCode. You can select it and add your remote SSH host information.
|
||||
|
||||

|
||||
|
||||
### 2. Configure
|
||||
|
||||
Configuring a remote server for development work is necessary so that as you make changes to the code base it will automatically refresh and you can see the changes. It may be necessary to do a full browser refresh if changing styles.
|
||||
|
||||
Disable RMM and Daphne services
|
||||
|
||||
```bash
|
||||
sudo systemctl disable --now rmm.service && sudo systemctl disable --now daphne.service
|
||||
```
|
||||
|
||||
Open /rmm/web/.env and make it look like the following
|
||||
|
||||
```bash
|
||||
DEV_URL = "http://api.domain.com:8000"
|
||||
APP_URL = "http://rmm.domain.com:8080"
|
||||
```
|
||||
|
||||
Open /rmm/api/tacticalrmm/tacticalrmm/local_settings.py
|
||||
|
||||
```bash
|
||||
change DEBUG = True
|
||||
```
|
||||
Remove
|
||||
```bash
|
||||
CORS_ORIGIN_WHITELIST list
|
||||
```
|
||||
Add
|
||||
```bash
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
```
|
||||
|
||||
Add the following to the ALLOWED HOSTS
|
||||
```bash
|
||||
rmm.doamin.com
|
||||
```
|
||||
cd /rmm/api/tacticalrmm/
|
||||
|
||||
```bash
|
||||
source ../env/bin/activate
|
||||
```
|
||||
|
||||
Install requirements
|
||||
|
||||
```bash
|
||||
pip install -r requirements-dev.txt -r requirements-test.txt
|
||||
```
|
||||
|
||||
Start Django backend
|
||||
|
||||
```bash
|
||||
python manage.py runserver 0:8000
|
||||
```
|
||||
|
||||
Open a new terminal and compile quasar frontend
|
||||
|
||||
```bash
|
||||
cd /rmm/web
|
||||
npm install
|
||||
npm install -g @quasar/cli
|
||||
quasar dev
|
||||
```
|
||||
|
||||
!!!info If you receive a CORS error when trying to log into your server via localhost or IP, try the following
|
||||
```bash
|
||||
rm -rf node_modules .quasar
|
||||
npm install
|
||||
quasar dev
|
||||
```
|
||||
You should now have a localhost and IP based URL to view that has a live reload feature.
|
||||
|
||||
## Configure GitHub with VSCode
|
||||
!!!info Make sure you are submitting Pull Requests to the develop branch.
|
||||
Follow this guide for a good introduction to GitHub: <https://www.digitalocean.com/community/tutorials/how-to-create-a-pull-request-on-github>
|
||||
|
||||
Make sure u are on develop branch
|
||||
```bash
|
||||
git checkout develop
|
||||
```
|
||||
git remote -v should look like the following
|
||||
```bash
|
||||
origin https://github.com/yourusername/tacticalrmm.git (fetch)
|
||||
origin https://github.com/yourusername/tacticalrmm.git (push)
|
||||
upstream https://github.com/wh1te909/tacticalrmm.git (fetch)
|
||||
upstream https://github.com/wh1te909/tacticalrmm.git (push)
|
||||
```
|
||||
You will commit the change to your GitHub and from within GitHub you can then submit a PR to the develop branch of wh1te909 Tactical RMM.
|
||||
|
||||
More to come...
|
||||
51
docs/docs/contributing_using_browser.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Contributing Using Web Browser
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1. Fork Project in Github
|
||||
|
||||
This is making a duplicate of the code under your Github that you can edit
|
||||
|
||||
<https://github.com/wh1te909/tacticalrmm>
|
||||
|
||||

|
||||
|
||||
### 2. Make Edits
|
||||
|
||||
Make some changes
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 3. Request your changes to be pulled into the primary repo (Pull Request)
|
||||
|
||||

|
||||
|
||||
This is taking your changes and requesting they be integrated into the Tactical RMM develop branch.
|
||||
|
||||
#### 3a. Check the status of your PR
|
||||
|
||||
Look at a summary of the changes you've requested, monitor for them to be accepted, or commented on.
|
||||
|
||||
<https://github.com/wh1te909/tacticalrmm/pulls>
|
||||
|
||||
Once they're accepted you can either:
|
||||
* Delete your fork
|
||||
* Sync your local fork
|
||||
|
||||
#### 4. Sync your fork
|
||||
|
||||
<https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork>
|
||||
|
||||
Bring changes from original repo to your fork so you're current with changes made in original Github repo
|
||||
|
||||

|
||||
|
||||
#### 5. Lather, Rinse, Repeat
|
||||
|
||||
Goto Step 2. and contribute some more
|
||||
|
||||
## Notes
|
||||
|
||||
After your changes are accepted, they won't be live in Tactical RMM until there is a new [release](https://github.com/wh1te909/tacticalrmm/releases). #BePatient
|
||||
@@ -1,13 +1,12 @@
|
||||
|
||||
# Contributing using Docker
|
||||
|
||||
## Install WSL2
|
||||
|
||||
https://docs.microsoft.com/en-us/windows/wsl/install-win10
|
||||
|
||||
<https://docs.microsoft.com/en-us/windows/wsl/install-win10>
|
||||
|
||||
## Install Docker Desktop
|
||||
|
||||
https://www.docker.com/products/docker-desktop
|
||||
<https://www.docker.com/products/docker-desktop>
|
||||
|
||||
### Configure Docker
|
||||
|
||||
@@ -40,19 +39,19 @@ This is better
|
||||
|
||||
Under .devcontainer duplicate
|
||||
|
||||
```
|
||||
```text
|
||||
.env.example
|
||||
```
|
||||
|
||||
as
|
||||
as
|
||||
|
||||
```
|
||||
```text
|
||||
.env
|
||||
```
|
||||
|
||||
Customize to your tastes (it doesn't need to be internet configured, just add records in your `hosts` file) eg
|
||||
|
||||
```
|
||||
```conf
|
||||
127.0.0.1 rmm.example.com
|
||||
127.0.0.1 api.example.com
|
||||
127.0.0.1 mesh.example.com
|
||||
@@ -64,12 +63,12 @@ Right-click `docker-compose.yml` and choose `Compose Up`
|
||||
|
||||
Wait, it'll take a while as docker downloads all the modules and gets running.
|
||||
|
||||
## Develop!
|
||||
## Develop
|
||||
|
||||
You're operational!
|
||||
|
||||
!!!note
|
||||
Self-signed certs are in your dev environment. Navigate to https://api.example.com and https://rmm.example.com and accept the self signed certs to get rid of errors.
|
||||
Self-signed certs are in your dev environment. Navigate to <https://api.example.com> and <https://rmm.example.com> and accept the self signed certs to get rid of errors.
|
||||
|
||||
### View mkdocks live edits in browser
|
||||
|
||||
@@ -82,4 +81,3 @@ Open: [http://rmm.example.com:8005/](http://rmm.example.com:8005/)
|
||||
### View django administration
|
||||
|
||||
Open: [http://rmm.example.com:8000/admin/](http://rmm.example.com:8000/admin/)
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# Contributing Using VSCode
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1. Install vscode
|
||||
[https://code.visualstudio.com/download](https://code.visualstudio.com/download)
|
||||
|
||||
<https://code.visualstudio.com/download>
|
||||
|
||||
### 2. Fork Project in Github
|
||||
|
||||
This is making a duplicate of the code under your Github that you can edit
|
||||
|
||||
[https://github.com/wh1te909/tacticalrmm](https://github.com/wh1te909/tacticalrmm)
|
||||
<https://github.com/wh1te909/tacticalrmm>
|
||||
|
||||

|
||||
|
||||
@@ -28,37 +30,36 @@ Remote - SSH
|
||||
|
||||
### 4. Open Terminal
|
||||
|
||||
[https://code.visualstudio.com/docs/editor/integrated-terminal](https://code.visualstudio.com/docs/editor/integrated-terminal)
|
||||
<https://code.visualstudio.com/docs/editor/integrated-terminal>
|
||||
|
||||
```
|
||||
```text
|
||||
Ctrl+`
|
||||
```
|
||||
|
||||
### 5. Configure a remote for your fork (in vscode)
|
||||
|
||||
[https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork)
|
||||
<https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork>
|
||||
|
||||
Configure your local fork and tell it where the original code repo is so you can compare and merge updates later when official repo is updated
|
||||
|
||||
Check repos
|
||||
|
||||
```
|
||||
```bash
|
||||
git remote -v
|
||||
```
|
||||
|
||||
Add upstream repo
|
||||
|
||||
```
|
||||
```bash
|
||||
git remote add upstream https://github.com/wh1te909/tacticalrmm
|
||||
```
|
||||
|
||||
Confirm changes
|
||||
|
||||
```
|
||||
```bash
|
||||
git remote -v
|
||||
```
|
||||
|
||||
|
||||
### 6. Contribute code
|
||||
|
||||
Make changes to something.
|
||||
@@ -69,7 +70,6 @@ Make changes to something.
|
||||
|
||||
Open browser and look at your repo (It should reflect your commit)
|
||||
|
||||
|
||||
#### 6a. Request your changes to be pulled into the primary repo (Pull Request)
|
||||
|
||||

|
||||
@@ -78,7 +78,7 @@ In browser create pull request
|
||||
|
||||
### 7. Sync your local fork
|
||||
|
||||
[https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork)
|
||||
<https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork>
|
||||
|
||||
Bring changes from original repo to your local vscode copy so you're current with changes made in original Github repo
|
||||
|
||||
@@ -86,15 +86,16 @@ Bring changes from original repo to your local vscode copy so you're current wit
|
||||
|
||||
In VSCode open TERMINAL
|
||||
|
||||
```
|
||||
```text
|
||||
Ctrl+`
|
||||
```
|
||||
|
||||
Tell git to pull from the GitHub upstream repo all new changes into your local directory
|
||||
|
||||
```
|
||||
```bash
|
||||
git pull --rebase upstream develop
|
||||
```
|
||||
|
||||
#### 7a. Push your local updated copy to your Github fork
|
||||
|
||||
Then you're `push`ing that updated local repo to your online Github fork
|
||||
@@ -106,6 +107,7 @@ Then you're `push`ing that updated local repo to your online Github fork
|
||||
Check your Github fork in browser, should be up to date now with original. Repeat 6 or 7 as necessary
|
||||
|
||||
*****
|
||||
|
||||
## Reference
|
||||
|
||||
### Customizing the Admin Web Interface
|
||||
@@ -114,6 +116,4 @@ Created using quasar, it's all your .vue files in `web/src/components/modals/age
|
||||
|
||||
Learn stuff here
|
||||
|
||||
https://quasar.dev/
|
||||
|
||||
|
||||
<https://quasar.dev/>
|
||||
|
||||
@@ -1,35 +1,54 @@
|
||||
# FAQ
|
||||
|
||||
## Is it possible to use XXX with Tactical RMM
|
||||
|
||||
While it _may be possible_ to use XXX, we have not configured it and therefore it is [Unsupported](../unsupported_guidelines). We cannot help you configure XXX as it pertains to **your environment**.
|
||||
|
||||
## Is it possible to use XXX proxy server with Tactical RMM
|
||||
|
||||
If you wish to stray from the [easy install](../install_server/#option-1-easy-install) of a standard install in a VPS, you need to have the knowledge on how to troubleshoot your own custom environment.
|
||||
|
||||
The most common reasons you're running a proxy is:
|
||||
|
||||
1. Because you only have a single public IP and you already have something on Port 443. **Workaround**: Get another public IP from your ISP
|
||||
2. Because you want to monitor traffic for security reasons: You're a [Networking Wizard](../unsupported_guidelines).
|
||||
|
||||
There are some [implementations](../unsupported_scripts) that others have done, but be aware it is [Unsupported](../unsupported_guidelines) and if you're requesting help in Discord please let us know in advance.
|
||||
|
||||
## How do I do X feature in the web UI?
|
||||
|
||||
#### How do I do X feature in the web UI?
|
||||
Alot of features in the web UI are hidden behind right-click menus; almost everything has a right click menu so if you don't see something, try right clicking on it.
|
||||
#### Where are the Linux / Mac agents?
|
||||
|
||||
## Where are the Linux / Mac agents?
|
||||
|
||||
Linux / Mac agents are currently under development.
|
||||
|
||||
#### Can I run Tactical RMM locally behind NAT without exposing anything to the internet?
|
||||
## Can I run Tactical RMM locally behind NAT without exposing anything to the internet?
|
||||
|
||||
Yes, you will just need to setup local DNS for the 3 subdomains, either by editing host files on all your agents or through a local DNS server.
|
||||
#### I am locked out of the web UI. How do I reset my password?
|
||||
|
||||
## I am locked out of the web UI. How do I reset my password?
|
||||
|
||||
SSH into your server and run:
|
||||
|
||||
```bash
|
||||
/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py reset_password <username>
|
||||
```
|
||||
|
||||
<br/>
|
||||
## How do I reset password or 2 factor token?
|
||||
|
||||
#### How do I reset password or 2 factor token?
|
||||
From the web UI, click **Settings > User Administration** and then right-click on a user:<br/><br/>
|
||||
From the web UI, click **Settings > User Administration** and then right-click on a user:
|
||||

|
||||
<br/><br/>
|
||||
Or from the command line:<br/>
|
||||
|
||||
Or from the command line:
|
||||
|
||||
```bash
|
||||
/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py reset_2fa <username>
|
||||
```
|
||||
|
||||
Then simply log out of the web UI and next time the user logs in they will be redirected to the 2FA setup page which will present a barcode to be scanned with the Authenticator app.
|
||||
|
||||
<br/>
|
||||
|
||||
#### How do I recover my MeshCentral login credentials?
|
||||
## How do I recover my MeshCentral login credentials?
|
||||
|
||||
From Tactical's web UI: *Settings > Global Settings > MeshCentral*
|
||||
|
||||
@@ -42,21 +61,21 @@ node node_modules/meshcentral --resetaccount <username> --pass <newpassword>
|
||||
sudo systemctl start meshcentral
|
||||
```
|
||||
|
||||
#### Help! I've been hacked there are weird agents appearing in my Tactical RMM
|
||||
## Help! I've been hacked there are weird agents appearing in my Tactical RMM
|
||||
|
||||
No, you haven't.
|
||||
No, you haven't.
|
||||
|
||||
1. Your installer was scanned by an antivirus.
|
||||
1. Your installer was scanned by an antivirus.
|
||||
|
||||
2. It didn't recognize the exe.
|
||||
2. It didn't recognize the exe.
|
||||
|
||||
3. You have the option enabled to submit unknown applications for analysis.
|
||||
3. You have the option enabled to submit unknown applications for analysis.
|
||||
|
||||

|
||||
|
||||
4. They ran it against their virtualization testing cluster.
|
||||
4. They ran it against their virtualization testing cluster.
|
||||
|
||||
5. You allow anyone to connect to your rmm server (you should look into techniques to hide your server from the internet).
|
||||
5. You allow anyone to connect to your rmm server (you should look into techniques to hide your server from the internet).
|
||||
|
||||
6. Here are some examples of what that looks like.
|
||||
|
||||
@@ -66,4 +85,4 @@ No, you haven't.
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
25
docs/docs/functions/api.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# API Access
|
||||
|
||||
*Version added: v0.8.3*
|
||||
|
||||
API Keys can be created to access any of TacticalRMM's api endpoints, which will bypass 2fa authentication
|
||||
|
||||
When creating the key you'll need to choose a user, which will reflect what permissions the key has based on the user's role.
|
||||
|
||||
Navigate to Settings > Global Settings > API Keys to generate a key
|
||||
|
||||
Headers:
|
||||
|
||||
```json
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"X-API-KEY": "J57BXCFDA2WBCXH0XTELBR5KAI69CNCZ"
|
||||
}
|
||||
```
|
||||
|
||||
Example curl request:
|
||||
|
||||
```bash
|
||||
curl https://api.example.com/clients/clients/ -H "X-API-KEY: Y57BXCFAA9WBCXH0XTEL6R5KAK69CNCZ"
|
||||
```
|
||||
|
||||
@@ -7,7 +7,8 @@ Automation policies in Tactical RMM allow for mass deployment of Checks, Automat
|
||||
- Site
|
||||
- Agent
|
||||
|
||||
## Adding Automation Policies
|
||||
You can also see a list of Relations that show what policy is applied to what Clients | Sites | Agents
|
||||
## Creating Automation Policies
|
||||
|
||||
In the dashboard, navigate to **Settings > Automation Manager**. Use the **Add** button to create a blank Automation Policy. The options available are:
|
||||
|
||||
@@ -18,5 +19,18 @@ In the dashboard, navigate to **Settings > Automation Manager**. Use the **Add**
|
||||
|
||||
## Policy Inheritance
|
||||
|
||||
They get applied in this order:
|
||||
|
||||
1. Global Settings
|
||||
2. Client
|
||||
3. Site
|
||||
4. Agent
|
||||
|
||||
and at each level you can Block policy inheritance from the level above using checkboxes in the appropriate screens.
|
||||
|
||||
## Adding Windows Patch Management Policy
|
||||
|
||||
Under the Automation Manager you can create a Patch Policy and control what patches are applied, when, and if the computer is rebooted after.
|
||||
|
||||
!!!note
|
||||
Most "regular" Windows patches are listed in the "Other" category.
|
||||
|
||||
@@ -27,5 +27,13 @@ https://www.dell.com/support/home/en-us/product-support/servicetag/{{agent.Seria
|
||||
Lenovo Support Page
|
||||
|
||||
```
|
||||
https://www.dell.com/support/home/en-us/product-support/servicetag/{{agent.SerialNumber}}/overview
|
||||
```
|
||||
https://pcsupport.lenovo.com/us/en/products/{{agent.SerialNumber}}
|
||||
```
|
||||
|
||||
HP Support Page
|
||||
|
||||
It gives an errors because the product model doesn't match the serial number. If you figure out a better link please let us know! :)
|
||||
|
||||
```
|
||||
https://support.hp.com/us-en/product/hp-pro-3500-microtower-pc/5270849/model/5270850?serialnumber={{agent.SerialNumber}}
|
||||
```
|
||||
|
||||
@@ -45,9 +45,24 @@ In the **Agent Table**, you can right-click on an agent and select **Run Script*
|
||||
|
||||
There is also an option on the agent context menu called **Run Favorited Script**. This will essentially Fire and Forget the script with default args and timeout.
|
||||
|
||||
### Script Arguments
|
||||
|
||||
The `Script Arguments` field should be pre-filled with information for any script that can accept or requires parameters.
|
||||
|
||||
<p style="background-color:#1e1e1e;">
|
||||
<span style=color:#d4d4d4><</span><span style="color:#358cd6">Required Parameter Name</span><span style=color:#d4d4d4>></span> <span style=color:#d4d4d4><</span><span style="color:#358cd6">string</span><span style=color:#d4d4d4>></span><br>
|
||||
<span style="color:#ffd70a">[</span><span style=color:#d4d4d4>-<</span><span style="color:#358cd6">Optional Parameter Name</span><span style=color:#d4d4d4>></span> <span style=color:#d4d4d4><</span><span style="color:#358cd6">string</span><span style=color:#d4d4d4>></span><span style="color:#ffd70a">]</span><br>
|
||||
<span style="color:#ffd70a">[</span><span style=color:#d4d4d4>-<</span><span style="color:#358cd6">string</span><span style=color:#d4d4d4>></span> <span style="color:#c586b6">{</span><span style=color:#87cefa>(</span><span style=color:#d4d4d4><</span><span style="color:#358cd6">default string if not specified</span><span style=color:#d4d4d4>></span><span style=color:#87cefa>)</span> <span style=color:#d4d4d4>|</span> <span style=color:#d4d4d4><</span><span style="color:#358cd6">string2</span><span style=color:#d4d4d4>></span> <span style=color:#d4d4d4>|</span> <span style=color:#d4d4d4><</span><span style="color:#358cd6">string3</span><span style=color:#d4d4d4>></span><span style="color:#c586b6">}</span><span style="color:#ffd70a">]</span></p>
|
||||
|
||||
Where `[]` indicates an optional parameter
|
||||
|
||||
and `{}` indicates a parameter with several preconfigured parameter
|
||||
|
||||
and `()` indicates a default parameter if none is specified
|
||||
|
||||
### Bulk Run on agents
|
||||
|
||||
Tactical RMM offers a way to run a script on multiple agents at once. Browse to **Tools > Bulk Script** and select the target for the script to run.
|
||||
There is also an option on the agent context menu called **Run Favorited Script**.
|
||||
|
||||
### Automated Tasks
|
||||
|
||||
@@ -134,4 +149,4 @@ When editing a script, you can add template tags to the script body that contain
|
||||
!!!info
|
||||
Everything between {{}} is CaSe sEnSiTive
|
||||
|
||||
The template tags will only be visible when Editing the script. When downloading or viewing the script code the template tags will be replaced with the script snippet code.
|
||||
The template tags will only be visible when Editing the script. When downloading or viewing the script code the template tags will be replaced with the script snippet code.
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
# How It All Works
|
||||
|
||||
INSERT WIREFRAME GRAPHICS HERE USING SOMETHING LIKE <https://www.yworks.com/yed-live/>
|
||||

|
||||
|
||||
1) how nats-django-admin web interface work
|
||||
1. Agent installer steps
|
||||
|
||||
2) Agent installer steps
|
||||
|
||||
3) Agent communication process with server (what ports to which services etc)
|
||||
|
||||
4) Agent checks/tasks and how they work on the workstation/interact with server
|
||||
2. Agent checks/tasks and how they work on the workstation/interact with server
|
||||
|
||||
## Server
|
||||
|
||||
@@ -140,6 +136,16 @@ Files create `c:\Windows\temp\Tacticalxxxx\` folder for install (and log files)
|
||||
|
||||
***
|
||||
|
||||
### Agent Recovery
|
||||
|
||||
#### Mesh Agent Recovery
|
||||
|
||||
Tactical Agent just runs `mesh_agent.exe -something` to get the mesh agent id and saves it to the django database.
|
||||
|
||||
#### Tactical RPC Recovery
|
||||
|
||||
#### Tactical Agent Recovery
|
||||
|
||||
### Windows Update Management
|
||||
|
||||
Tactical RMM Agent sets:
|
||||
|
||||
|
Before Width: | Height: | Size: 57 KiB |
BIN
docs/docs/images/Remote_SSH_connection.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
1
docs/docs/images/TacticalRMM-Network.drawio
Normal file
BIN
docs/docs/images/TacticalRMM-Network.png
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
docs/docs/images/contribute_browser_make_changes.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/docs/images/contribute_browser_make_changes2.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/docs/images/mesh_features.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
docs/docs/images/mesh_userconsent.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
BIN
docs/docs/images/owasp_burp.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/docs/images/synology_docker_ports.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
docs/docs/images/synology_docker_reverse.jpg
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
docs/docs/images/synology_docker_reverse_details1.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
docs/docs/images/synology_docker_reverse_details2.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
@@ -10,74 +10,101 @@
|
||||
`C:\Windows\Temp\winagent-v*.exe`<br/>
|
||||
`C:\Windows\Temp\trmm\*`<br/>
|
||||
`C:\temp\tacticalrmm*.exe`<br/>
|
||||
|
||||
|
||||
## Dynamically generated executable
|
||||
|
||||
The generated exe is simply a wrapper around the Manual install method, using a single exe/command without the need to pass any command line flags to the installer.
|
||||
All it does is download the generic installer from the agent's github [release page](https://github.com/wh1te909/rmmagent/releases) and call it using predefined command line args that you choose from the web UI.
|
||||
It "bakes" the command line args into the executable.
|
||||
|
||||
#### Dynamically generated executable
|
||||
From the UI, click **Agents > Install Agent**
|
||||
|
||||
You can also **right click on a site > Install Agent**. This will automatically fill in the client/site dropdown for you.
|
||||
|
||||
The generated exe is simply a wrapper around the Manual install method, using a single exe/command without the need to pass any command line flags to the installer.<br/><br/>
|
||||
All it does is download the generic installer from the agent's github [release page](https://github.com/wh1te909/rmmagent/releases) and call it using predefined command line args that you choose from the web UI.<br/><br/>
|
||||
It "bakes" the command line args into the executable.<br/><br/>
|
||||
From the UI, click **Agents > Install Agent**<br/>
|
||||
You can also **right click on a site > Install Agent**. This will automatically fill in the client/site dropdown for you.<br/><br/>
|
||||

|
||||
|
||||
#### Powershell
|
||||
## Powershell
|
||||
|
||||
The powershell method is very similar to the generated exe in that it simply downloads the installer from github and calls the exe for you.
|
||||
|
||||
#### Manual
|
||||
The manual installation method requires you to first download the generic installer and call it using command line args.<br/><br/>
|
||||
This is useful for scripting the installation using Group Policy or some other batch deployment method.<br/>
|
||||
## Manual
|
||||
|
||||
The manual installation method requires you to first download the generic installer and call it using command line args.
|
||||
This is useful for scripting the installation using Group Policy or some other batch deployment method.
|
||||
|
||||
!!!tip
|
||||
You can reuse the installer for any of the deployment methods, you don't need to constantly create a new installer for each new agent.<br/>
|
||||
The installer will be valid for however long you specify the token expiry time when generating an agent.
|
||||
|
||||
<br/>
|
||||
#### Using a deployment link
|
||||
## Using a deployment link
|
||||
|
||||
Creating a deployment link is the recommended way to deploy agents.<br/><br/>
|
||||
The main benefit of this method is that the exectuable is generated only whenever the deployment download link is accessed, whereas with the other methods it's generated right away and the agent's version hardcoded into the exe.<br/><br/>
|
||||
Using a deployment link will allow you to not worry about installing using an older version of an agent, which will fail to install if you have updated your RMM to a version that is not compatible with an older installer you might have lying around.<br/><br/>
|
||||
Creating a deployment link is the recommended way to deploy agents.
|
||||
The main benefit of this method is that the exectuable is generated only whenever the deployment download link is accessed, whereas with the other methods it's generated right away and the agent's version hardcoded into the exe.
|
||||
Using a deployment link will allow you to not worry about installing using an older version of an agent, which will fail to install if you have updated your RMM to a version that is not compatible with an older installer you might have lying around.
|
||||
|
||||
To create a deployment, from the web UI click **Agents > Manage Deployments**.<br/><br/>
|
||||
To create a deployment, from the web UI click **Agents > Manage Deployments**.
|
||||

|
||||
|
||||
|
||||
!!!tip
|
||||
Create a client/site named "Default" and create a deployment for it with a very long expiry to have a generic installer that can be deployed anytime at any client/site.<br/><br/>
|
||||
Create a client/site named "Default" and create a deployment for it with a very long expiry to have a generic installer that can be deployed anytime at any client/site.
|
||||
You can then move the agent into the correct client/site from the web UI after it's been installed.
|
||||
|
||||
Copy/paste the download link from the deployment into your browser. It will take a few seconds to dynamically generate the executable and then your browser will automatically download the exe.
|
||||
|
||||
|
||||
#### Optional installer args
|
||||
## Optional installer args
|
||||
|
||||
The following optional arguments can be passed to any of the installation method executables:
|
||||
|
||||
```
|
||||
```text
|
||||
-log debug
|
||||
```
|
||||
|
||||
Will print very verbose logging during agent install. Useful for troubleshooting agent install.
|
||||
|
||||
```
|
||||
```text
|
||||
-silent
|
||||
```
|
||||
|
||||
This will not popup any message boxes during install, either any error messages or the "Installation was successfull" message box that pops up at the end of a successfull install.
|
||||
|
||||
```
|
||||
```text
|
||||
-proxy "http://proxyserver:port"
|
||||
```
|
||||
|
||||
Use a http proxy
|
||||
|
||||
```
|
||||
```text
|
||||
-meshdir "C:\Program Files\Your Company Name\Mesh Agent"
|
||||
```
|
||||
|
||||
Specify the full path to the directory containing `MeshAgent.exe` if using custom agent branding for your MeshCentral instance.
|
||||
|
||||
```
|
||||
```text
|
||||
-nomesh
|
||||
```
|
||||
Do not install meshcentral agent during tactical agent install. Note: take control, remote terminal and file browser will not work.
|
||||
|
||||
Do not install meshcentral agent during tactical agent install. Note: take control, remote terminal and file browser will not work.
|
||||
|
||||
## Scripting Agent Installation
|
||||
|
||||
If you want to deploy the TRMM agent using AD, intune, mesh, teamviewer, Group Policy GPO etc this is a sample CMD script for deploying Tactical.
|
||||
|
||||
!!!note
|
||||
You will need to replace `deployment url` with your custom deployment URL
|
||||
|
||||
```bat
|
||||
if not exist C:\TEMP\TRMM md C:\TEMP\TRMM
|
||||
powershell Set-ExecutionPolicy -ExecutionPolicy Unrestricted
|
||||
powershell Add-MpPreference -ExclusionPath C:\TEMP\TRMM
|
||||
powershell Add-MpPreference -ExclusionPath "C:\Program Files\TacticalAgent\*"
|
||||
powershell Add-MpPreference -ExclusionPath C:\Windows\Temp\winagent-v*.exe
|
||||
powershell Add-MpPreference -ExclusionPath "C:\Program Files\Mesh Agent\*"
|
||||
powershell Add-MpPreference -ExclusionPath C:\Windows\Temp\TRMM\*
|
||||
cd c:\temp\trmm
|
||||
powershell Invoke-WebRequest "deployment url" -Outfile tactical.exe
|
||||
"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS
|
||||
start tactical.exe
|
||||
powershell Remove-MpPreference -ExclusionPath C:\TEMP\TRMM
|
||||
```
|
||||
|
||||
There is also a full powershell version [here](https://wh1te909.github.io/tacticalrmm/3rdparty_screenconnect/#install-tactical-rmm-via-screeconnect-commands-window)
|
||||
|
||||
@@ -1,30 +1,66 @@
|
||||
# Docker Setup
|
||||
|
||||
- Install docker and docker-compose
|
||||
- Obtain valid wildcard certificate for your domain. If certificates are not provided, a self-signed certificate will be generated and most agent functions won't work. See below on how to generate a free Let's Encrypt!
|
||||
## 1. Install Docker
|
||||
|
||||
## Generate certificates with certbot
|
||||
Install Certbot
|
||||
Install docker
|
||||
|
||||
```
|
||||
### 2. Create the A records
|
||||
|
||||
We'll be using `example.com` as our domain for this example.
|
||||
|
||||
!!!info
|
||||
The RMM uses 3 different sites. The Vue frontend e.g. `rmm.example.com` which is where you'll be accesing your RMM from the browser, the REST backend e.g. `api.example.com` and Meshcentral e.g. `mesh.example.com`
|
||||
|
||||
1. Get the public IP of your server with `curl https://icanhazip.tacticalrmm.io`
|
||||
2. Open the DNS manager of wherever the domain you purchased is hosted.
|
||||
3. Create 3 A records: `rmm`, `api` and `mesh` and point them to the public IP of your server:
|
||||
|
||||

|
||||
|
||||
## 3. Acquire Let's Encrypt Wildcard certs with certbot
|
||||
|
||||
!!!warning
|
||||
If the Let's Encrypt wildcard certificates are not provided, a self-signed certificate will be generated and most agent functions won't work.
|
||||
|
||||
### A. Install Certbot
|
||||
|
||||
```bash
|
||||
sudo apt-get install certbot
|
||||
```
|
||||
|
||||
Generate the wildcard certificate. Add the DNS entry for domain validation. Replace `example.com` with your root doamin
|
||||
### B. Generate the wildcard Let's Encrypt certificates
|
||||
|
||||
```
|
||||
We're using the [DNS-01 challenge method](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)
|
||||
#### a. Deploy the TXT record in your DNS manager
|
||||
|
||||
!!!warning
|
||||
TXT records can take anywhere from 1 minute to a few hours to propogate depending on your DNS provider.<br/>
|
||||
You should verify the TXT record has been deployed first before pressing Enter.<br/>
|
||||
A quick way to check is with the following command:<br/> `dig -t txt _acme-challenge.example.com`<br/>
|
||||
or test using: <https://viewdns.info/dnsrecord/> Enter: `_acme-challenge.example.com`
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### b. Request Let's Encrypt Wildcard cert
|
||||
|
||||
```bash
|
||||
sudo certbot certonly --manual -d *.example.com --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns
|
||||
```
|
||||
|
||||
## Configure DNS and firewall
|
||||
!!!note
|
||||
Replace `example.com` with your root domain
|
||||
|
||||
## 4. Configure DNS and firewall
|
||||
|
||||
You will need to add DNS entries so that the three subdomains resolve to the IP of the docker host. There is a reverse proxy running that will route the hostnames to the correct container. On the host, you will need to ensure the firewall is open on tcp ports 80, 443 and 4222.
|
||||
|
||||
## Setting up the environment
|
||||
## 5. Setting up the environment
|
||||
|
||||
Get the docker-compose and .env.example file on the host you which to install on
|
||||
|
||||
```
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/docker/docker-compose.yml
|
||||
wget https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/docker/.env.example
|
||||
mv .env.example .env
|
||||
@@ -32,9 +68,9 @@ mv .env.example .env
|
||||
|
||||
Change the values in .env to match your environment.
|
||||
|
||||
If you are supplying certificates through Let's Encrypt or another source, see the section below about base64 encoding the certificate files.
|
||||
When supplying certificates through Let's Encrypt, see the section below about base64 encoding the certificate files.
|
||||
|
||||
## Base64 encoding certificates to pass as env variables
|
||||
### A. Base64 encoding certificates to pass as env variables
|
||||
|
||||
Use the below command to add the the correct values to the .env.
|
||||
|
||||
@@ -48,25 +84,39 @@ public key
|
||||
private key
|
||||
`/etc/letsencrypt/live/${rootdomain}/privkey.pem`
|
||||
|
||||
```
|
||||
```bash
|
||||
echo "CERT_PUB_KEY=$(sudo base64 -w 0 /path/to/pub/key)" >> .env
|
||||
echo "CERT_PRIV_KEY=$(sudo base64 -w 0 /path/to/priv/key)" >> .env
|
||||
```
|
||||
|
||||
## Starting the environment
|
||||
## 6. Starting the environment
|
||||
|
||||
Run the below command to start the environment.
|
||||
|
||||
```
|
||||
```bash
|
||||
sudo docker-compose up -d
|
||||
```
|
||||
|
||||
Removing the -d will start the containers in the foreground and is useful for debugging.
|
||||
|
||||
## Get MeshCentral EXE download link
|
||||
## 7. Get MeshCentral EXE download link
|
||||
|
||||
Run the below command to get the download link for the mesh central exe. This needs to be uploaded on first successful signin.
|
||||
|
||||
```
|
||||
```bash
|
||||
sudo docker-compose exec tactical-backend python manage.py get_mesh_exe_url
|
||||
```
|
||||
```
|
||||
|
||||
Download the mesh agent:
|
||||
|
||||

|
||||
|
||||
Navigate to `https://rmm.example.com` and login with the username/password you created during install.
|
||||
|
||||
Once logged in, you will be redirected to the initial setup page.
|
||||
|
||||
Create your first client/site, choose the default timezone and then upload the mesh agent you just downloaded.
|
||||
|
||||
## Note about Backups
|
||||
|
||||
The backup script **does not** work with docker. To backup your install use [standard docker backup/restore](https://docs.docker.com/desktop/backup-and-restore/) processes.
|
||||
|
||||
@@ -1,41 +1,62 @@
|
||||
# Installation
|
||||
|
||||
## Minimum requirements
|
||||
- A fresh linux VM running either Ubuntu 20.04 or Debian 10, with a minimum of 2GB RAM.<br/>
|
||||
## General Information
|
||||
|
||||
### Minimum requirements
|
||||
|
||||
#### Hardware / OS
|
||||
|
||||
A fresh linux VM running either Ubuntu 20.04 LTS or Debian 10 with 3GB RAM
|
||||
|
||||
!!!warning
|
||||
The provided install script assumes a fresh server with no software installed on it. Attempting to run it on an existing server with other services **will** break things and the install will fail.<br/><br/>
|
||||
The provided install script assumes a fresh server with no software installed on it. Attempting to run it on an existing server with other services **will** break things and the install will fail.
|
||||
|
||||
!!!note
|
||||
The install script has been tested on the following public cloud providers: DigitalOcean, Linode, Vultr, BuyVM (highly recommended), Hetzner, AWS, Google Cloud and Azure, as well as behind NAT on Hyper-V, Proxmox and ESXi.
|
||||
|
||||
- A real (internet resolvable) domain is needed to generate a Let's Encrypt wildcard cert. <br/>If you cannot afford to purchase a domain ($12 a year) then you can get one for free at [freenom.com](https://www.freenom.com/)
|
||||
- example.local is __NOT__ a real domain. No you [don't have to expose your server](faq.md#can-i-run-tactical-rmm-locally-behind-nat-without-exposing-anything-to-the-internet) to the internet<br/><br/>
|
||||
#### Network Requirements
|
||||
|
||||
- A TOTP based authenticator app. Some popular ones are Google Authenticator, Authy and Microsoft Authenticator.<br/><br/>
|
||||
- A real (internet resolvable) domain is needed to generate a Let's Encrypt wildcard cert. _If you cannot afford to purchase a domain ($12 a year) then you can get one for free at [freenom.com](https://www.freenom.com/)_
|
||||
- example.local is __NOT__ a real domain. No you [don't have to expose your server](faq.md#can-i-run-tactical-rmm-locally-behind-nat-without-exposing-anything-to-the-internet) to the internet
|
||||
- A TOTP based authenticator app. Some popular ones are Google Authenticator, Authy and Microsoft Authenticator.
|
||||
|
||||
## Install
|
||||
#### Update Recommendations
|
||||
|
||||
!!!info
|
||||
It is recommended that you keep your server updated regularly (monthly). SSL wildcard certs will expire every 3 months and need manual updating as well. <br/><br/>
|
||||
!!!note
|
||||
We highly recommend staying current with updates (at least every 3 months when you update your SSL certs is a good minimum) while Tactical RMM is still working towards its 1.0 release.<br/><br/>
|
||||
Until we reach production release, there may be architectural changes that may be made to Tactical RMM and only a regular patching schedule is supported by developers.
|
||||
|
||||
#### Run updates and setup the linux user
|
||||
SSH into the server as **root**.<br/><br/>
|
||||
Download and run the prereqs and latest updates<br/>
|
||||
## Option 1: Easy Install on a VPS
|
||||
|
||||
Install on a VPS: DigitalOcean, Linode, Vultr, BuyVM (highly recommended), Hetzner, AWS, Google Cloud and Azure to name a few
|
||||
|
||||
Use something that meets [minimum specs](install_server.md#hardware-os)
|
||||
|
||||
### Run updates and setup the linux user
|
||||
|
||||
SSH into the server as **root**.
|
||||
|
||||
Download and run the prereqs and latest updates
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt install -y wget curl sudo
|
||||
apt -y upgrade
|
||||
```
|
||||
If a new kernel is installed, then reboot the server with the `reboot` command<br/><br/>
|
||||
Create a linux user named `tactical` to run the rmm and add it to the sudoers group.<br/>
|
||||
|
||||
If a new kernel is installed, then reboot the server with the `reboot` command
|
||||
|
||||
Create a linux user named `tactical` to run the rmm and add it to the sudoers group.
|
||||
|
||||
**For Ubuntu**:
|
||||
|
||||
```bash
|
||||
adduser tactical
|
||||
usermod -a -G sudo tactical
|
||||
```
|
||||
|
||||
**For Debian**:
|
||||
|
||||
```bash
|
||||
useradd -m -s /bin/bash tactical
|
||||
usermod -a -G sudo tactical
|
||||
@@ -44,7 +65,7 @@ usermod -a -G sudo tactical
|
||||
!!!tip
|
||||
[Enable passwordless sudo to make your life easier](https://linuxconfig.org/configure-sudo-without-password-on-ubuntu-20-04-focal-fossa-linux)
|
||||
|
||||
#### Setup the firewall (optional but highly recommended)
|
||||
### Setup the firewall (optional but highly recommended)
|
||||
|
||||
!!!info
|
||||
Skip this step if your VM is __not__ publicly exposed to the world e.g. running behind NAT. You should setup the firewall rules in your router instead (ports 22, 443 and 4222 TCP).
|
||||
@@ -59,44 +80,47 @@ ufw allow proto tcp from any to any port 4222
|
||||
!!!info
|
||||
SSH (port 22 tcp) is only required for you to remotely login and do basic linux server administration for your rmm. It is not needed for any agent communication.<br/>
|
||||
Allow ssh from everywhere (__not__ recommended)
|
||||
|
||||
```bash
|
||||
ufw allow ssh
|
||||
```
|
||||
|
||||
Allow ssh from only allowed IP's (__highly__ recommended)
|
||||
|
||||
```bash
|
||||
ufw allow proto tcp from X.X.X.X to any port 22
|
||||
ufw allow proto tcp from X.X.X.X to any port 22
|
||||
```
|
||||
|
||||
Enable and activate the firewall
|
||||
```
|
||||
|
||||
```bash
|
||||
ufw enable && ufw reload
|
||||
```
|
||||
|
||||
#### Create the A records
|
||||
### Create the A records
|
||||
|
||||
We'll be using `example.com` as our domain for this example.
|
||||
|
||||
!!!info
|
||||
The RMM uses 3 different sites. The Vue frontend e.g. `rmm.example.com` which is where you'll be accesing your RMM from the browser, the REST backend e.g. `api.example.com` and Meshcentral e.g. `mesh.example.com`
|
||||
|
||||
|
||||
Get the public IP of your server with `curl https://icanhazip.tacticalrmm.io`<br/>
|
||||
Open the DNS manager of wherever the domain you purchased is hosted.<br/>
|
||||
Create 3 A records: `rmm`, `api` and `mesh` and point them to the public IP of your server:
|
||||
1. Get the public IP of your server with `curl https://icanhazip.tacticalrmm.io`
|
||||
2. Open the DNS manager of wherever the domain you purchased is hosted.
|
||||
3. Create 3 A records: `rmm`, `api` and `mesh` and point them to the public IP of your server:
|
||||
|
||||

|
||||
|
||||
|
||||
#### Run the install script
|
||||
### Run the install script
|
||||
|
||||
Switch to the `tactical` user
|
||||
|
||||
```bash
|
||||
su - tactical
|
||||
```
|
||||
|
||||
Download and run the install script
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh
|
||||
chmod +x install.sh
|
||||
@@ -107,13 +131,13 @@ Answer the initial questions when prompted. Replace `example.com` with your doma
|
||||
|
||||

|
||||
|
||||
|
||||
#### Deploy the TXT record in your DNS manager:
|
||||
### Deploy the TXT record in your DNS manager for Lets Encrypt wildcard certs
|
||||
|
||||
!!!warning
|
||||
TXT records can take anywhere from 1 minute to a few hours to propogate depending on your DNS provider.<br/>
|
||||
You should verify the TXT record has been deployed first before pressing Enter.<br/>
|
||||
A quick way to check is with the following command:<br/> `dig -t txt _acme-challenge.example.com`
|
||||
A quick way to check is with the following command:<br/> `dig -t txt _acme-challenge.example.com`<br/>
|
||||
or test using: <https://viewdns.info/dnsrecord/> Enter: `_acme-challenge.example.com`
|
||||
|
||||

|
||||
|
||||
@@ -125,14 +149,74 @@ Create a login for the RMM web UI:
|
||||
|
||||
A bunch of URLS / usernames / passwords will be printed out at the end of the install script. **Save these somewhere safe.** [Recover them if you didn't](faq.md#how-do-i-recover-my-meshcentral-login-credentials)
|
||||
|
||||
### Upload mesh agents
|
||||
|
||||
Copy the url for the meshagent exe (`https://mesh.example.com/agentinvite?c=......`), paste it in your browser and download the mesh agent:
|
||||
|
||||

|
||||
|
||||
Navigate to `https://rmm.example.com` and login with the username/password you created during install.<br/><br/>
|
||||
Once logged in, you will be redirected to the initial setup page.<br/><br/>
|
||||
Navigate to `https://rmm.example.com` and login with the username/password you created during install.
|
||||
|
||||
Once logged in, you will be redirected to the initial setup page.
|
||||
|
||||
Create your first client/site, choose the default timezone and then upload the mesh agent you just downloaded.
|
||||
|
||||
### You're Done
|
||||
|
||||
[Update Regularly](install_server.md#update-regularly)
|
||||
|
||||
## Option 2: Install behind NAT Router
|
||||
|
||||
Install in your local network using: Dedicated hardware, Hyper-V, Proxmox or ESXi. All been tested and work fine.
|
||||
|
||||
Do everything from [Option 1: Easy Install](install_server.md#run-updates-and-setup-the-linux-user)
|
||||
|
||||
### If you only have agents on the private network/subnet
|
||||
|
||||
Make sure your local DNS server (or agents hosts file) have your Tactical RMM server IP addresses for the 3 domain names: `rmm`, `api` and `mesh`
|
||||
|
||||
### Agents exist outside the private network/subnet - Setup Port Forwarding
|
||||
|
||||
If you have agents outside your local network: Make sure the public DNS servers have A records for the 3 Tactical RMM server domain names: `rmm`, `api` and `mesh`
|
||||
|
||||
Login to your router/NAT device.
|
||||
|
||||
1. Set your TRMM server as a static IP (Use a DHCP reservation is usually safer)
|
||||
2. Create 2 port forwarding rules. `TCP Port 443` and `TCP Port 4222` to your TRMM servers private IP address.
|
||||
|
||||
!!!note
|
||||
Though it is an unsupported configuration, if you are using HAProxy or wish to configure fail2ban this might be of use to you [Unsupported Configuration Notes](unsupported_scripts.md)
|
||||
<https://portforward.com/> can help with Port Forwarding setup
|
||||
|
||||
### You're Done
|
||||
|
||||
[Update Regularly](install_server.md#update-regularly)
|
||||
|
||||
## Option 3: Installs by Network Wizards
|
||||
|
||||
Use the scripts above.
|
||||
|
||||
### Requirements
|
||||
|
||||
1. TLD domain name which is internet resolvable (this is for a LetsEncrypt DNS wildcard request during the install script [validated by DNS txt record](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)).
|
||||
- Test using: <https://viewdns.info/dnsrecord/> or <https://dnschecker.org/>. Enter: `_acme-challenge.example.com` as `TXT`
|
||||
2. Agents need to be able to connect to your server via DNS lookup (hosts file, local DNS, smoke signals etc.).
|
||||
- Test from agent: `ping rmm.example.com`. Should result in the IP of your Tactical RMM server
|
||||
- Test from agent: `ping api.example.com`. Should result in the IP of your Tactical RMM server
|
||||
- Test from agent: `ping mesh.example.com`. Should result in the IP of your Tactical RMM server
|
||||
|
||||
!!!note
|
||||
Did you notice #2 doesn't need to be something publicly available?
|
||||
|
||||
That's it. You're a wizard, you know how to satisfy these 2 items.
|
||||
|
||||
You'll probably enjoy browsing thru the [Unsupported section](unsupported_guidelines.md) of the docs.
|
||||
|
||||
## Update Regularly
|
||||
|
||||
We've said it before, we'll say it again.
|
||||
|
||||
- We recommend regular updates.
|
||||
|
||||
- Every 3 months.
|
||||
|
||||
- Do it when you update your SSL certs.
|
||||
|
||||
@@ -1,74 +1,88 @@
|
||||
# Management Commands
|
||||
|
||||
To run any of the management commands you must first activate the python virtual env:
|
||||
|
||||
```bash
|
||||
cd /rmm/api/tacticalrmm
|
||||
source ../env/bin/activate
|
||||
```
|
||||
|
||||
#### Reset a user's password
|
||||
## Reset a user's password
|
||||
|
||||
```bash
|
||||
python manage.py reset_password <username>
|
||||
```
|
||||
|
||||
#### Reset a user's 2fa token
|
||||
## Reset a user's 2fa token
|
||||
|
||||
```bash
|
||||
python manage.py reset_2fa <username>
|
||||
```
|
||||
|
||||
#### Find all agents that have X software installed
|
||||
## Find all agents that have X software installed
|
||||
|
||||
```bash
|
||||
python manage.py find_software "adobe"
|
||||
```
|
||||
|
||||
#### Show outdated online agents
|
||||
## Show outdated online agents
|
||||
|
||||
```bash
|
||||
python manage.py show_outdated_agents
|
||||
```
|
||||
|
||||
#### Log out all active web sessions
|
||||
## Log out all active web sessions
|
||||
|
||||
```bash
|
||||
python manage.py delete_tokens
|
||||
```
|
||||
|
||||
#### Check for orphaned tasks on all agents and remove them
|
||||
## Check for orphaned tasks on all agents and remove them
|
||||
|
||||
```bash
|
||||
python manage.py remove_orphaned_tasks
|
||||
```
|
||||
|
||||
#### Create a MeshCentral agent invite link
|
||||
## Create a MeshCentral agent invite link
|
||||
|
||||
```bash
|
||||
python manage.py get_mesh_exe_url
|
||||
```
|
||||
|
||||
#### Bulk update agent offline/overdue time
|
||||
## Bulk update agent offline/overdue time
|
||||
|
||||
Change offline time on all agents to 5 minutes
|
||||
|
||||
```bash
|
||||
python manage.py bulk_change_checkin --offline --all 5
|
||||
```
|
||||
|
||||
Change offline time on all agents in site named *Example Site* to 2 minutes
|
||||
|
||||
```bash
|
||||
python manage.py bulk_change_checkin --offline --site "Example Site" 2
|
||||
```
|
||||
|
||||
Change offline time on all agents in client named *Example Client* to 12 minutes
|
||||
|
||||
```bash
|
||||
python manage.py bulk_change_checkin --offline --client "Example Client" 12
|
||||
```
|
||||
|
||||
Change overdue time on all agents to 10 minutes
|
||||
|
||||
```bash
|
||||
python manage.py bulk_change_checkin --overdue --all 10
|
||||
```
|
||||
|
||||
Change overdue time on all agents in site named *Example Site* to 4 minutes
|
||||
|
||||
```bash
|
||||
python manage.py bulk_change_checkin --overdue --site "Example Site" 4
|
||||
```
|
||||
|
||||
Change overdue time on all agents in client named *Example Client* to 14 minutes
|
||||
|
||||
```bash
|
||||
python manage.py bulk_change_checkin --overdue --client "Example Client" 14
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MeshCentral Integration
|
||||
|
||||
#### Overview
|
||||
## Overview
|
||||
|
||||
Tactical RMM integrates with [MeshCentral](https://github.com/Ylianst/MeshCentral) for the following 3 functions:
|
||||
|
||||
@@ -16,7 +16,7 @@ They do not even have to run on the same box, however when you install Tactical
|
||||
|
||||
It is highly recommended to use the MeshCentral instance that Tactical installs, since it allows the developers more control over it and to ensure things don't break.
|
||||
|
||||
#### How does it work
|
||||
## How does it work
|
||||
|
||||
MeshCentral has an embedding feature that allows integration into existing products.
|
||||
|
||||
@@ -25,4 +25,3 @@ See *Section 14 - Embedding MeshCentral* in the [MeshCentral User Guide](https:/
|
||||
The Tactical RMM Agent keeps track of your Mesh Agents, and periodically interacts with them to synchronize the mesh agent's unique ID with the tactical rmm database.
|
||||
|
||||
When you do a take control / terminal / file browser on an agent using the Tactical UI, behind the scenes, Tactical generates a login token for meshcentral's website and then "wraps" MeshCentral's UI in an iframe for that specific agent only, using it's unique ID to know what agent to render in the iframe.
|
||||
|
||||
|
||||
@@ -7,19 +7,22 @@
|
||||
The restore script will always restore to the latest available RMM version on github.
|
||||
|
||||
Make sure you update your old RMM to the latest version using the `update.sh` script and then run a fresh backup to use with this restore script.
|
||||
#### Prepare the new server
|
||||
|
||||
## Prepare the new server
|
||||
|
||||
Create the same exact linux user account as you did when you installed the original server.
|
||||
|
||||
Add it to the sudoers group and setup the firewall.
|
||||
|
||||
Refer to the [installation instructions](install_server.md) for steps on how to do all of the above.
|
||||
|
||||
#### Change DNS A records
|
||||
## Change DNS A records
|
||||
|
||||
Open the DNS manager of wherever your domain is hosted.
|
||||
|
||||
Change the 3 A records `rmm`, `api` and `mesh` and point them to the public IP of your new server.
|
||||
|
||||
#### Run the restore script
|
||||
## Run the restore script
|
||||
|
||||
Copy the backup tar file you created during [backup](backup.md) to the new server.
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ See below for the available options.
|
||||
- **{{agent.public_ip}}** - Public IP address of agent
|
||||
- **{{agent.agent_id}}** - agent ID in database
|
||||
- **{{agent.last_seen}}** - Date and Time Agent last seen
|
||||
- **{{agent.used_ram}}** - Used RAM on agent. Returns an integer - example: *16*
|
||||
- **{{agent.total_ram}}** - Total RAM on agent. Returns an integer - example: *16*
|
||||
- **{{agent.used_ram}}** - Used RAM on agent. Returns an integer - example: *16*
|
||||
- **{{agent.total_ram}}** - Total RAM on agent. Returns an integer - example: *16*
|
||||
- **{{agent.boot_time}}** - Uptime of agent. Returns unix timestamp. example: *1619439603.0*
|
||||
- **{{agent.logged_in_username}}** - Username of logged in user
|
||||
- **{{agent.last_logged_in_user}}** - Username of last logged in user
|
||||
@@ -34,7 +34,7 @@ See below for the available options.
|
||||
- **{{agent.check_interval}}** - Returns check interval time setting for agent in TRMM
|
||||
- **{{agent.needs_reboot}}** - Returns true if reboot is pending on agent
|
||||
- **{{agent.choco_installed}}** - Returns true if Chocolatey is installed
|
||||
- **{{agent.patches_last_installed}}** - The date that patches were last installed by Tactical RMM.
|
||||
- **{{agent.patches_last_installed}}** - The date that patches were last installed by Tactical RMM.
|
||||
- **{{agent.needs_reboot}}** - Returns true if the agent needs a reboot
|
||||
- **{{agent.time_zone}}** - Returns timezone configured on agent
|
||||
- **{{agent.maintenance_mode}}** - Returns true if agent is in maintenance mode
|
||||
@@ -42,16 +42,18 @@ See below for the available options.
|
||||
- **{{agent.alert_template}** - Returns true if agent has block policy inheritance
|
||||
|
||||
## Client
|
||||
|
||||
- **{{client.name}}** - Returns name of client
|
||||
|
||||
## Site
|
||||
|
||||
- **{{site.name}}** - Returns name of Site
|
||||
|
||||
## Alert
|
||||
|
||||
!!!info
|
||||
Only available in failure and resolve actions on alert templates!
|
||||
|
||||
|
||||
- **{{alert.alert_time}}** - Time of the alert
|
||||
- **{{alert.message}}** - Alert message
|
||||
- **{{alert.severity}}** - Severity of the alert *info, warning, or error*
|
||||
|
||||
406
docs/docs/securing_nginx.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# DISCLAIMER
|
||||
|
||||
All the settings covered in this document have been tested against Tactical RMM v0.7.2 and v0.8.0.
|
||||
|
||||
Before applying these settings in production, use a pre-production environment so potential disruptions in your own environment and the service that you provide to your clients can be avoided.
|
||||
|
||||
!!!warning
|
||||
**<span style="text-decoration:underline;">Use the contents included in this guide and apply the security settings detailed here at your own discretion.</span>**
|
||||
|
||||
## Intro
|
||||
|
||||
This section is structured in three main subsections:
|
||||
|
||||
* Enabling GeoIP in NGINX config with the purpose of filtering (blocking) web requests based on the country’s source IP.
|
||||
* Enabling anti “bad” bots/referrers in HTTP requests to the NGINX server.
|
||||
* Compiling and enabling ModSec + OWASP CRS in NGINX server.
|
||||
|
||||
Each section can be enabled independently.
|
||||
|
||||
## Hardening NGINX settings
|
||||
|
||||
### GeoIP Integration in NGINX - Blocking Requests by Country Code
|
||||
|
||||
Install required packages and NGINX module for GeoIP:
|
||||
|
||||
```bash
|
||||
# apt-get install geoip-database libgeoip1 libnginx-mod-http-geoip
|
||||
```
|
||||
|
||||
Verify that the GeoIP database files have been placed in the right location:
|
||||
|
||||
```bash
|
||||
# ls -lrt /usr/share/GeoIP/
|
||||
total 10004
|
||||
-rw-r--r-- 1 root root 8138841 Jan 24 2020 GeoIPv6.dat
|
||||
-rw-r--r-- 1 root root 2099217 Jan 24 2020 GeoIP.dat
|
||||
```
|
||||
|
||||
Edit NGINX config file (“/etc/nginx/nginx.conf”) and add the following config under the “http {“ block:
|
||||
|
||||
```conf
|
||||
http {
|
||||
|
||||
##
|
||||
# Basic Settings
|
||||
##
|
||||
# Load GeoIP Database
|
||||
geoip_country /usr/share/GeoIP/GeoIP.dat;
|
||||
|
||||
```
|
||||
|
||||
The next settings will depend on the desired GeoIP blocking strategy. For “allow by default, deny by exception”, the config would be:
|
||||
|
||||
```conf
|
||||
http {
|
||||
|
||||
##
|
||||
# Basic Settings
|
||||
##
|
||||
# Load GeoIP Database
|
||||
geoip_country /usr/share/GeoIP/GeoIP.dat;
|
||||
# map the list of denied countries
|
||||
map $geoip_country_code $allowed_country {
|
||||
default yes;
|
||||
# BLOCKED_COUNTRY_1
|
||||
COUNTRY_CODE_1 no;
|
||||
# BLOCKED_COUNTRY_2
|
||||
COUNTRY_CODE_2 no;
|
||||
# BLOCKED_COUNTRY_3
|
||||
COUNTRY_CODE_3 no;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
(The macro can be modified to achieve the “deny by default, allow by exception” approach).
|
||||
|
||||
Finally, the following “if” statement needs to be placed in all the vhosts where the GeoIP blocking should take effect, under the “location” section:
|
||||
|
||||
```conf
|
||||
location / {
|
||||
root /var/www/rmm/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
# block the country
|
||||
if ($allowed_country = no) {
|
||||
return 444;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The HTTP Status = 444 is a good choice for NGINX not “wasting” too many resources in sending back the 4xx code to the client being blocked by GeoIP.
|
||||
|
||||
### Blocking “bad bots” and “bad referrers”
|
||||
|
||||
Nginx Bad Bot and User-Agent Blocker, Spam Referrer Blocker, Anti DDOS, Bad IP Blocker and Wordpress Theme Detector Blocker
|
||||
|
||||
Source:
|
||||
|
||||
[https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker](https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker)
|
||||
|
||||
Download “install-ngxblocker” to your /usr/local/sbin/directory and make the script executable.
|
||||
|
||||
```bash
|
||||
sudo wget https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/install-ngxblocker -O /usr/local/sbin/install-ngxblocker
|
||||
sudo chmod +x /usr/local/sbin/install-ngxblocker
|
||||
```
|
||||
|
||||
**<span style="text-decoration:underline;">(OPTIONAL)</span>**Now run the ”install-ngxblocker” script in **DRY-MODE** which will show you what changes it will make and what files it will download for you. This is only a DRY-RUN so no changes are being made yet.
|
||||
|
||||
The install-ngxblocker downloads all required files including the setup and update scripts.
|
||||
|
||||
```bash
|
||||
cd /usr/local/sbin
|
||||
sudo ./install-ngxblocker
|
||||
```
|
||||
|
||||
This will show you output as follows of the changes that will be made (NOTE: this is only a **DRY-RUN** no changes have been made)
|
||||
|
||||
```log
|
||||
Checking url: https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/include_filelist.txt
|
||||
** Dry Run ** | not updating files | run as 'install-ngxblocker -x' to install files.
|
||||
Creating directory: /etc/nginx/bots.d
|
||||
REPO = https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master
|
||||
Downloading [FROM]=> [REPO]/conf.d/globalblacklist.conf [TO]=> /etc/nginx/conf.d/globalblacklist.conf
|
||||
Downloading [FROM]=> [REPO]/conf.d/botblocker-nginx-settings.conf [TO]=> /etc/nginx/conf.d/botblocker-nginx-settings.conf
|
||||
REPO = https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master
|
||||
Downloading [FROM]=> [REPO]/bots.d/blockbots.conf [TO]=> /etc/nginx/bots.d/blockbots.conf
|
||||
Downloading [FROM]=> [REPO]/bots.d/ddos.conf [TO]=> /etc/nginx/bots.d/ddos.conf
|
||||
Downloading [FROM]=> [REPO]/bots.d/whitelist-ips.conf [TO]=> /etc/nginx/bots.d/whitelist-ips.conf
|
||||
Downloading [FROM]=> [REPO]/bots.d/whitelist-domains.conf [TO]=> /etc/nginx/bots.d/whitelist-domains.conf
|
||||
Downloading [FROM]=> [REPO]/bots.d/blacklist-user-agents.conf [TO]=> /etc/nginx/bots.d/blacklist-user-agents.conf
|
||||
Downloading [FROM]=> [REPO]/bots.d/blacklist-ips.conf [TO]=> /etc/nginx/bots.d/blacklist-ips.conf
|
||||
Downloading [FROM]=> [REPO]/bots.d/bad-referrer-words.conf [TO]=> /etc/nginx/bots.d/bad-referrer-words.conf
|
||||
Downloading [FROM]=> [REPO]/bots.d/custom-bad-referrers.conf [TO]=> /etc/nginx/bots.d/custom-bad-referrers.conf
|
||||
REPO = https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master
|
||||
Downloading [FROM]=> [REPO]/setup-ngxblocker [TO]=> /usr/local/sbin/setup-ngxblocker
|
||||
Downloading [FROM]=> [REPO]/update-ngxblocker [TO]=> /usr/local/sbin/update-ngxblocker
|
||||
```
|
||||
|
||||
Now run the install script with the -x parameter to download all the necessary files from the repository:
|
||||
|
||||
```bash
|
||||
cd /usr/local/sbin/
|
||||
sudo ./install-ngxblocker -x
|
||||
```
|
||||
|
||||
This will give you the following output:
|
||||
|
||||
```log
|
||||
Checking url: https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/include_filelist.txt
|
||||
Creating directory: /etc/nginx/bots.d
|
||||
REPO = https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master
|
||||
Downloading [FROM]=> [REPO]/conf.d/globalblacklist.conf [TO]=> /etc/nginx/conf.d/globalblacklist.conf...OK
|
||||
Downloading [FROM]=> [REPO]/conf.d/botblocker-nginx-settings.conf [TO]=> /etc/nginx/conf.d/botblocker-nginx-settings.conf...OK
|
||||
REPO = https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master
|
||||
Downloading [FROM]=> [REPO]/bots.d/blockbots.conf [TO]=> /etc/nginx/bots.d/blockbots.conf...OK
|
||||
Downloading [FROM]=> [REPO]/bots.d/ddos.conf [TO]=> /etc/nginx/bots.d/ddos.conf...OK
|
||||
Downloading [FROM]=> [REPO]/bots.d/whitelist-ips.conf [TO]=> /etc/nginx/bots.d/whitelist-ips.conf...OK
|
||||
Downloading [FROM]=> [REPO]/bots.d/whitelist-domains.conf [TO]=> /etc/nginx/bots.d/whitelist-domains.conf...OK
|
||||
Downloading [FROM]=> [REPO]/bots.d/blacklist-user-agents.conf [TO]=> /etc/nginx/bots.d/blacklist-user-agents.conf...OK
|
||||
Downloading [FROM]=> [REPO]/bots.d/blacklist-ips.conf [TO]=> /etc/nginx/bots.d/blacklist-ips.conf...OK
|
||||
Downloading [FROM]=> [REPO]/bots.d/bad-referrer-words.conf [TO]=> /etc/nginx/bots.d/bad-referrer-words.conf...OK
|
||||
Downloading [FROM]=> [REPO]/bots.d/custom-bad-referrers.conf [TO]=> /etc/nginx/bots.d/custom-bad-referrers.conf...OK
|
||||
REPO = https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master
|
||||
Downloading [FROM]=> [REPO]/setup-ngxblocker [TO]=> /usr/local/sbin/setup-ngxblocker...OK
|
||||
Downloading [FROM]=> [REPO]/update-ngxblocker [TO]=> /usr/local/sbin/update-ngxblocker...OK
|
||||
```
|
||||
|
||||
All the required files have now been downloaded to the correct folders on Nginx for you direct from the repository.
|
||||
|
||||
**<span style="text-decoration:underline;">NOTE:</span>** The setup and update scripts can be used, however in this guide the config is done manually. For script execution, refer to the Github page linked above.
|
||||
|
||||
Include any public IP addresses that should be whitelisted from bot and referrer analysis/blocking by editing the file “/etc/nginx/bots.d/whitelist-ips.conf”.
|
||||
|
||||
Finally, edit every vhost file (“/etc/nginx/sites-enabled/frontend.conf”, “/etc/nginx/sites-enabled/rmm.conf” and “/etc/nginx/sites-enabled/meshcentral.conf”) and place the following include statements under the “server” block:
|
||||
|
||||
```conf
|
||||
server {
|
||||
listen 443 ssl;
|
||||
include /etc/nginx/bots.d/ddos.conf;
|
||||
include /etc/nginx/bots.d/blockbots.conf;
|
||||
```
|
||||
|
||||
## Enabling ModSec in NGINX
|
||||
|
||||
All steps in this section taken from the NGINX blog post “Compiling and Installing ModSecurity for NGINX Open Source”:
|
||||
|
||||
[https://www.nginx.com/blog/compiling-and-installing-modsecurity-for-open-source-nginx/](https://www.nginx.com/blog/compiling-and-installing-modsecurity-for-open-source-nginx/)
|
||||
|
||||
### Install Prerequisite Packages
|
||||
|
||||
The first step is to install the packages required to complete the remaining steps in this tutorial. Run the following command, which is appropriate for a freshly installed Ubuntu/Debian system. The required packages might be different for RHEL/CentOS/Oracle Linux.
|
||||
|
||||
```bash
|
||||
apt-get install -y apt-utils autoconf automake build-essential git libcurl4-openssl-dev libgeoip-dev liblmdb-dev libpcre++-dev libtool libxml2-dev libyajl-dev pkgconf wget zlib1g-dev
|
||||
```
|
||||
|
||||
### Download and Compile the ModSecurity 3.0 Source Code
|
||||
|
||||
With the required prerequisite packages installed, the next step is to compile ModSecurity as an NGINX dynamic module. In ModSecurity 3.0’s new modular architecture, libmodsecurity is the core component which includes all rules and functionality. The second main component in the architecture is a connector that links libmodsecurity to the web server it is running with. There are separate connectors for NGINX, Apache HTTP Server, and IIS. We cover the NGINX connector in the next section.
|
||||
|
||||
To compile libmodsecurity:
|
||||
|
||||
Clone the GitHub repository:
|
||||
|
||||
```bash
|
||||
git clone --depth 1 -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity
|
||||
```
|
||||
|
||||
Change to the ModSecurity directory and compile the source code:
|
||||
|
||||
```bash
|
||||
cd ModSecurity
|
||||
git submodule init
|
||||
git submodule update
|
||||
./build.sh
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
cd ..
|
||||
```
|
||||
|
||||
The compilation takes about 15 minutes, depending on the processing power of your system.
|
||||
|
||||
Note: It’s safe to ignore messages like the following during the build process. Even when they appear, the compilation completes and creates a working object.
|
||||
|
||||
```log
|
||||
fatal: No names found, cannot describe anything.
|
||||
```
|
||||
|
||||
### Download the NGINX Connector for ModSecurity and Compile It as a Dynamic Module
|
||||
|
||||
Compile the ModSecurity connector for NGINX as a dynamic module for NGINX.
|
||||
|
||||
Clone the GitHub repository:
|
||||
|
||||
```bash
|
||||
git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git
|
||||
```
|
||||
|
||||
Determine which version of NGINX is running on the host where the ModSecurity module will be loaded:
|
||||
|
||||
```bash
|
||||
$ nginx -v
|
||||
nginx version: nginx/1.18.0 (Ubuntu)
|
||||
```
|
||||
|
||||
Download the source code corresponding to the installed version of NGINX (the complete sources are required even though only the dynamic module is being compiled):
|
||||
|
||||
```bash
|
||||
wget http://nginx.org/download/nginx-1.18.0.tar.gz
|
||||
tar zxvf nginx-1.18.0.tar.gz
|
||||
```
|
||||
|
||||
Compile the dynamic module and copy it to the standard directory for modules:
|
||||
|
||||
```bash
|
||||
cd nginx-1.18.0
|
||||
./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
|
||||
make modules
|
||||
cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules
|
||||
cp objs/ngx_http_modsecurity_module.so /usr/share/nginx/modules/
|
||||
cd ..
|
||||
```
|
||||
|
||||
### Load the NGINX ModSecurity Connector Dynamic Module
|
||||
|
||||
Add the following load_module directive to the main (top‑level) context in /etc/nginx/nginx.conf. It instructs NGINX to load the ModSecurity dynamic module when it processes the configuration:
|
||||
|
||||
```conf
|
||||
load_module modules/ngx_http_modsecurity_module.so;
|
||||
```
|
||||
|
||||
### Configure and Enable ModSecurity
|
||||
|
||||
The final step is to enable and test ModSecurity.
|
||||
|
||||
Set up the appropriate ModSecurity configuration file. Here we’re using the recommended ModSecurity configuration provided by TrustWave Spiderlabs, the corporate sponsors of ModSecurity.
|
||||
|
||||
```bash
|
||||
mkdir /etc/nginx/modsec
|
||||
wget -P /etc/nginx/modsec/ https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
|
||||
mv /etc/nginx/modsec/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf
|
||||
```
|
||||
|
||||
To guarantee that ModSecurity can find the unicode.mapping file (distributed in the top‑level ModSecurity directory of the GitHub repo), copy it to /etc/nginx/modsec.
|
||||
|
||||
```bash
|
||||
cp ModSecurity/unicode.mapping /etc/nginx/modsec
|
||||
```
|
||||
|
||||
Change the SecRuleEngine directive in the configuration to change from the default “detection only” mode to actively dropping malicious traffic.
|
||||
|
||||
```conf
|
||||
#SecRuleEngine DetectionOnly
|
||||
SecRuleEngine On
|
||||
```
|
||||
|
||||
## Enabling OWASP Core Rule Set
|
||||
|
||||
Clone OWASP CRS:
|
||||
|
||||
```bash
|
||||
cd /etc/nginx/modsec
|
||||
git clone https://github.com/coreruleset/coreruleset.git
|
||||
```
|
||||
|
||||
Create CRS setup config file:
|
||||
|
||||
```bash
|
||||
cp /etc/nginx/modsec/coreruleset/crs-setup.conf.example /etc/nginx/modsec/coreruleset/crs-setup.conf
|
||||
```
|
||||
|
||||
Edit config file and enable a paranoia level of 2 (comment out section below and modify the paranoia level from 1 - default to 2):
|
||||
|
||||
```conf
|
||||
SecAction \
|
||||
"id:900000,\
|
||||
phase:1,\
|
||||
nolog,\
|
||||
pass,\
|
||||
t:none,\
|
||||
setvar:tx.paranoia_level=2"
|
||||
```
|
||||
|
||||
A Paranoia level of 2 is a good combination of security rules to load by the ModSec engine while keeping low the number of false positives.
|
||||
|
||||
The OWASP CRS team carried out some tests using BURP against ModSec + OWASP CRS:
|
||||
|
||||

|
||||
|
||||
Create ModSecurity base config file (“/etc/nginx/modsec/modsec-base-cfg.conf”) and include the following lines (the order is important)`:`
|
||||
|
||||
```conf
|
||||
Include /etc/nginx/modsec/modsecurity.conf
|
||||
Include /etc/nginx/modsec/coreruleset/crs-setup.conf
|
||||
Include /etc/nginx/modsec/coreruleset/rules/*.conf
|
||||
```
|
||||
|
||||
Enable ModSec in all NGINX enabled sites:
|
||||
|
||||
“/etc/nginx/sites-enabled/frontend.conf”, “/etc/nginx/sites-enabled/rmm.conf” and “/etc/nginx/sites-enabled/meshcentral.conf”:
|
||||
|
||||
```conf
|
||||
server {
|
||||
modsecurity on;
|
||||
modsecurity_rules_file /etc/nginx/modsec/modsec-base-cfg.conf;
|
||||
|
||||
…………………..
|
||||
…………………..
|
||||
```
|
||||
|
||||
Tactical RMM custom rules:
|
||||
|
||||
* Access to the admin UI (front-end): We apply the “deny by default, allow by exception” principle, whereby only a set of predefined public IPs should be allowed to access the UI
|
||||
* API and Meshcentral: RMM agents and RMM UI (as referrer while an admin session is active) make web calls that get blocked by the OWASP CRS, specifically PUT, POST and PATCH methods. These three methods can be “whitelisted” when the requested URI matches legitimate requests.
|
||||
* Connection to Meshcentral during Tactical agent install.
|
||||
|
||||
Create a .conf file under “/etc/nginx/modsec/coreruleset/rules” named “RMM-RULES.conf”, for example, with the following content:
|
||||
|
||||
```conf
|
||||
#ADMIN UI/FRONTEND ACCESS - DENY BY DEFAULT, ALLOW BY EXCEPTION
|
||||
SecRule SERVER_NAME "rmm.yourdomain.com" "id:1001,phase:1,nolog,msg:'Remote IP Not allowed',deny,chain"
|
||||
### ALLOWED PUBLIC IP 1 #########
|
||||
SecRule REMOTE_ADDR "!@eq IP1" chain
|
||||
### ALLOWED PUBLIC IP 2 #########
|
||||
SecRule REMOTE_ADDR "!@eq IP2" "t:none"
|
||||
|
||||
#API AND MESHCENTRAL - WHITELIST PUT, PATCH AND POST METHODS BY REQUESTED URI
|
||||
SecRule REQUEST_URI "@beginsWith /api/v3/checkin" "id:1002,phase:1,t:none,nolog,allow,chain"
|
||||
SecRule REQUEST_METHOD "PUT|PATCH" "t:none"
|
||||
SecRule REQUEST_URI "@beginsWith /api/v3/checkrunner" "chain,id:'1003',phase:1,t:none,nolog,allow"
|
||||
SecRule REQUEST_METHOD "PATCH" "t:none"
|
||||
SecRule REQUEST_URI "@beginsWith /alerts/alerts" "chain,id:'1004',phase:1,t:none,nolog,allow"
|
||||
SecRule REQUEST_METHOD "PATCH" "t:none"
|
||||
SecRule REQUEST_URI "@beginsWith /agents/listagents" "chain,id:'1005',phase:1,t:none,nolog,allow"
|
||||
SecRule REQUEST_METHOD "PATCH" "t:none"
|
||||
SecRule REQUEST_URI "@beginsWith /api/v3/sysinfo" "chain,id:'1006',phase:1,t:none,nolog,allow"
|
||||
SecRule REQUEST_METHOD "PATCH" "t:none"
|
||||
SecRule REQUEST_URI "@beginsWith /api/v3/winupdates" "chain,id:'1007',phase:1,t:none,nolog,allow"
|
||||
SecRule REQUEST_METHOD "POST"
|
||||
|
||||
##REQUIRED FOR MANAGEMENT ACTIONS FROM ADMIN/FRONT-END UI. WHITELIST BY REFERRER's URL
|
||||
SecRule REQUEST_HEADERS:REFERER "https://rmm.yourdomain.com/" "id:1008,phase:1,nolog,ctl:ruleRemoveById=920170,allow"
|
||||
|
||||
#REQUIRED FOR NEW CLIENTS TO CONNECT TO MESH SERVICE WHILE INSTALLING THE AGENT
|
||||
SecRule REQUEST_URI "@beginsWith /api/v3/meshexe" "id:1009,phase:1,nolog,ctl:ruleRemoveById=920170,allow"
|
||||
|
||||
### NOTE ON RULE ID = 920170 (WHITELISTED IN CASES ABOVE FOR TACTICAL RMM) ###
|
||||
# Do not accept GET or HEAD requests with bodies
|
||||
# HTTP standard allows GET requests to have a body but this
|
||||
# feature is not used in real life. Attackers could try to force
|
||||
# a request body on an unsuspecting web applications.
|
||||
#
|
||||
# -=[ Rule Logic ]=-
|
||||
# This is a chained rule that first checks the Request Method. If it is a
|
||||
# GET or HEAD method, then it checks for the existence of a Content-Length
|
||||
# header. If the header exists and its payload is either not a 0 digit or not
|
||||
# empty, then it will match.
|
||||
#
|
||||
# -=[ References ]=-
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
||||
###
|
||||
```
|
||||
7
docs/docs/security.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Security
|
||||
|
||||
If you think that you have found a security vulnerability in Tactical RMM, please disclose it to us via our security e-mail address at **security@amidaware.com**
|
||||
|
||||
Please do not make vulnerabilities public without notifying us and giving us at least 3 days to respond.
|
||||
|
||||
If you are going to write about Tactical RMM's security, please get in touch, so we can make sure that all claims are correct.
|
||||
@@ -13,4 +13,3 @@ We are always looking for feedback and ways to improve Tactical RMM to better ad
|
||||
[Sponsor with Github](https://github.com/wh1te909)
|
||||
|
||||
[Sponsor with Ko-fi](https://ko-fi.com/tacticalrmm)
|
||||
|
||||
|
||||
9
docs/docs/support_templates/Initial questions.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
1. Standard/Docker
|
||||
2. VPS/onprem
|
||||
3. Are you using a proxy?
|
||||
4. What version of Ubuntu/Debian, is it a Desktop or Server
|
||||
5. Specs of machine including hard drive spec is it ssd or mechanical?
|
||||
6. have you looked at the troubleshooting on github?
|
||||
7. are you using a real domain
|
||||
8. did letsencrypt finalise and work
|
||||
9. are you using the standard ssl certs or something else?
|
||||
19
docs/docs/support_templates/Initial questionsv2.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Note: If you don't want to share any specific info publicly on discord you can DM me that data
|
||||
1. Install type? (Standard/Docker)
|
||||
If standard install did you deviate IN ANY WAY from these instructions? https://wh1te909.github.io/tacticalrmm/install_server/
|
||||
If docker install did you deviate IN ANY WAY from these instructions? https://wh1te909.github.io/tacticalrmm/install_docker/
|
||||
2. Where is the server? (VPS/onprem)
|
||||
3. New install, or established? Rough age of TRMM server (days/weeks/months)?
|
||||
|
||||
Server Install Specific questions:
|
||||
4. What version of Ubuntu/Debian, is it a Desktop or Server
|
||||
5. Are you using a real domain
|
||||
6. Did letsencrypt finalise and work
|
||||
7. Have you looked at the troubleshooting steps on github? https://wh1te909.github.io/tacticalrmm/troubleshooting/
|
||||
8. What kind of ssl certs? Let's Encrypt, or purchased (you're not trying to make self-signed work right?)
|
||||
9. Check Expiry date of your certificates in the browser (at https://rmm.example.com )
|
||||
|
||||
Network Troubleshooting
|
||||
10. Are you using a proxy?
|
||||
11. Are you a wizard? See https://wh1te909.github.io/tacticalrmm/unsupported_guidelines/
|
||||
If so, what's in the network between agent and server?
|
||||
12
docs/docs/tidbits.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Misc info
|
||||
|
||||
## Run Intervals for Checks
|
||||
|
||||
You can modify at several locations/levels:
|
||||
|
||||
* **Settings Menu > Automation Manager > Checks tab >** Edit check
|
||||
* Agent Level: **Edit Agent > Run checks every**
|
||||
* Edit Check under agent > Run this check every (seconds)
|
||||
|
||||
!!!note
|
||||
The interval under check will override agent check if set
|
||||
@@ -8,7 +8,11 @@ At the top right of your web administration interface, click your Username > pre
|
||||
|
||||
*****
|
||||
|
||||
## Mesh
|
||||
## MeshCentral
|
||||
|
||||
Tactical RMM is actually 2 products: An RMM service with agent, and a secondary [MeshCentral](https://github.com/Ylianst/MeshCentral) install that handles the `Take Control` and `Remote Background` stuff.
|
||||
|
||||
### Adjust Settings
|
||||
|
||||
Right-click the connect button in *Remote Background | Terminal* for shell options
|
||||
|
||||
@@ -17,3 +21,19 @@ Right-click the connect button in *Remote Background | Terminal* for shell optio
|
||||
Right-click the connect button in *Take Control* for connect options
|
||||
|
||||

|
||||
|
||||
### Enable Remote Control options
|
||||
|
||||
!!!note
|
||||
These settings are independant of Tactical RMM. Enable features (like auto remove inactive devices) with caution
|
||||
|
||||
1. Remote background a machine then go to mesh.yourdomain.com
|
||||
2. Click on My Account
|
||||
3. Click on the device group you want to enable notifications or accept connection etc on (probably TacticalRMM)
|
||||
4. Next to User Consent click edit (the wee pencil)
|
||||
5. Tick whatever boxes you want in there (Features: Sync server device name to hostname, Automatically remove inactive devices, Notify/Prompt for Consent/Connection Toolbar settings)
|
||||
6. Click ok
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -1,6 +1,43 @@
|
||||
# Troubleshooting
|
||||
|
||||
#### "Bad credentials" error when trying to login to the Web UI
|
||||
## Server Troubleshooting Script
|
||||
|
||||
If you've asked for help in [#support](https://discord.com/channels/736478043522072608/744282073870630912) please run this, and send a screenshot at the top of the thread created for troubleshooting your issue.
|
||||
|
||||
Blur your domains if you desire privacy.
|
||||
|
||||
```bash
|
||||
wget -N https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/troubleshoot_server.sh
|
||||
chmod +x troubleshoot_server.sh
|
||||
./troubleshoot_server.sh
|
||||
```
|
||||
|
||||
## Make sure DNS (name resolution) was setup properly
|
||||
|
||||
### From the agent
|
||||
|
||||
Open command prompt
|
||||
|
||||
```cmd
|
||||
ping rmm.example.com
|
||||
ping api.example.com
|
||||
ping mesh.example.com
|
||||
```
|
||||
|
||||
The IP address for all 3 should reflect your Tactical RMM server
|
||||
|
||||
## Problems after new server install
|
||||
|
||||
In the very unlikely event you have issues after install please wipe the box and install again (following all the steps including downloading the install script _but not running it yet_) use the following command which will log the install progress and if you continue to have issues will assist with support of the installation.
|
||||
|
||||
```bash
|
||||
bash -x install.sh 2>&1 | tee install.log
|
||||
```
|
||||
|
||||
!!!note
|
||||
Logging of installs isn’t desirable as it logs extremely sensitive information which is why this isn’t done by default! **Do not** post the raw log publicly only provide it if requested and then by dm only. Authorized users in Discord are: @BurningTimes#1938 @sadnub#6992 @dinger1986#1734 @silversword#9652
|
||||
|
||||
## "Bad credentials" error when trying to login to the Web UI
|
||||
|
||||
If you are sure you are using the correct credentials and still getting a "bad credentials" error, open your browser's dev tools (ctrl + shift + j on chrome) and check the Console tab to see the real error.
|
||||
|
||||
@@ -10,11 +47,9 @@ If you see an error about SSL or certificate expired, then your Let's Encrypt ce
|
||||
|
||||
Refer to the Let's Encrypt cert renewal instructions [here](update_server.md#keeping-your-lets-encrypt-certificate-up-to-date)
|
||||
|
||||
<br/>
|
||||
## Agents not installing or updating
|
||||
|
||||
#### Agents not updating
|
||||
|
||||
The most common problem we've seen of agents not updating is due to Antivirus blocking the updater executable.
|
||||
The most common problem we've seen of agents not installing or updating is due to Antivirus blocking the updater executable.
|
||||
|
||||
Windows Defender will 100% of the time block the updater from running unless an exclusion is set.
|
||||
|
||||
@@ -26,11 +61,9 @@ Since Tactical RMM is still in alpha and the developers makes breaking changes p
|
||||
|
||||
If you have agents that are relatively old, you will need to uninstall them manually and reinstall using the latest version.
|
||||
|
||||
<br/>
|
||||
## Agents not checking in or showing up / General agent issues
|
||||
|
||||
#### Agents not checking in or showing up / General agent issues
|
||||
|
||||
First, reload NATS from tactical's web UI:<br />
|
||||
First, reload NATS from tactical's web UI:<br>
|
||||
*Tools > Server Maintenance > Reload Nats Configuration*
|
||||
|
||||
Open CMD as admin on the problem computer and stop the agent services:
|
||||
@@ -41,11 +74,13 @@ net stop tacticalrpc
|
||||
```
|
||||
|
||||
Run the tacticalagent service manually with debug logging:
|
||||
|
||||
```cmd
|
||||
"C:\Program Files\TacticalAgent\tacticalrmm.exe" -m winagentsvc -log debug -logto stdout
|
||||
```
|
||||
|
||||
Run the tacticalrpc service manually with debug logging:
|
||||
|
||||
```cmd
|
||||
"C:\Program Files\TacticalAgent\tacticalrmm.exe" -m rpc -log debug -logto stdout
|
||||
```
|
||||
@@ -56,9 +91,11 @@ Please then copy/paste the logs and post them either in our [Discord support cha
|
||||
|
||||
If all else fails, simply uninstall the agent either from control panel or silently with `"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT` and then reinstall the agent.
|
||||
|
||||
#### All other errors
|
||||
## All other errors
|
||||
|
||||
First, run the [update script](update_server.md#updating-to-the-latest-rmm-version) with the `--force` flag. <br/>This will fix permissions and reinstall python/node packages that might have gotten corrupted.
|
||||
First, run the [update script](update_server.md#updating-to-the-latest-rmm-version) with the `--force` flag.
|
||||
|
||||
This will fix permissions and reinstall python/node packages that might have gotten corrupted.
|
||||
|
||||
```bash
|
||||
./update.sh --force
|
||||
@@ -84,12 +121,13 @@ sudo systemctl status redis
|
||||
```
|
||||
|
||||
Read through the log files in the following folders and check for errors:
|
||||
|
||||
```bash
|
||||
/rmm/api/tacticalrmm/tacticalrmm/private/log
|
||||
/var/log/celery
|
||||
```
|
||||
|
||||
#### Using Cloudflare DNS
|
||||
## Using Cloudflare DNS
|
||||
|
||||
- rmm.example.com can be proxied.
|
||||
|
||||
@@ -97,7 +135,7 @@ Read through the log files in the following folders and check for errors:
|
||||
|
||||
- mesh.example.com can be proxied with the caveat that Mesh checks the cert presented to the agent is the same one on the server. I.e. no MITM. You'll need to copy Cloudflare's edge cert to your server if you want to proxy this domain.
|
||||
|
||||
#### Testing Network Connectivity between agent and server
|
||||
## Testing Network Connectivity between agent and server
|
||||
|
||||
Use powershell, make sure you can connect to 443 and 4222 from agent to server:
|
||||
|
||||
@@ -113,4 +151,13 @@ Test-NetConnection -ComputerName api.example.com -Port 443
|
||||
Test-NetConnection -ComputerName rmm.example.com -Port 443
|
||||
```
|
||||
|
||||
Are you trying to use a proxy to share your single public IP with multiple services on 443? This is complicated and [unsupported by Tactical RMM](unsupported_scripts.md), test your setup.
|
||||
Are you trying to use a proxy to share your single public IP with multiple services on 443? This is complicated and [unsupported by Tactical RMM](unsupported_scripts.md), test your setup.
|
||||
|
||||
## Mesh Agent x86 x64 integration with TRMM
|
||||
|
||||
1. Log into Mesh (you can right-click any agent, choose remote control or Remote Background)
|
||||
2. Goto your mesh interface (eg `https://mesh.domain.com`)
|
||||
3. Find your TacticalRMM group
|
||||
4. Click the add link
|
||||
5. Download both agents
|
||||
6. In Tactical RMM, go **Settings > Global Settings > MeshCentral > Upload Mesh Agents** upload them both into the appropriate places.
|
||||
|
||||
41
docs/docs/unsupported_guidelines.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Unsupported Guidelines
|
||||
|
||||
## General Information
|
||||
|
||||
Tactical RMM is designed to be secure by default.
|
||||
|
||||
You **CAN** **_expose_** it to the internet, and start deploying agents.
|
||||
|
||||
You **CAN** **_not expose_** it to the internet, and start deploying agents.
|
||||
|
||||
### Period
|
||||
|
||||
!!!info
|
||||
BIG PERIOD **.** <--- See, it's really really big 🙂
|
||||
|
||||
## That said
|
||||
|
||||
There are those that wish to add layers to their security onion. For the benefit of others following in their footsteps, we have added here for your convenience additional information on a range of subjects and technologies that have been graciously donated to us by the community at large.
|
||||
|
||||
Please be aware that those providing help and assistance in the Discord [#support](https://discord.com/channels/736478043522072608/744282073870630912) channel will generally assume that you are **not** one of these wizards of networking magic.
|
||||
|
||||
Should you employ any one or several of these unsupported technologies:
|
||||
|
||||
* Proxies
|
||||
* Firewalls
|
||||
* GeoIP filters
|
||||
* fail2ban filters
|
||||
* alternate methods of SSL cert management
|
||||
* IDSs
|
||||
* IPSs
|
||||
* SDNs
|
||||
* Did anything other than follow the installation instructions exactly
|
||||
* and any/all other magical ABC thru XYZ technologies
|
||||
|
||||
Please let us know **BEFORE** we start troubleshooting and looking for software bugs that you are...in fact...a 🧙...and using something non-standard 😉
|
||||
|
||||
These are "unsupported" because then we are troubleshooting **your** environment, not Tactical RMM. You need to have knowledge about how things work if you're going to stray from the [easy path](../install_server/#option-1-easy-install) of the standard install.
|
||||
|
||||
Help us maximize keeping developer time and resources focused on new releases...not support goosechases.
|
||||
|
||||
Thank you and #KeepDeploying
|
||||
@@ -1,11 +1,24 @@
|
||||
# Unsupported Reference scripts
|
||||
# Unsupported Reference Scripts
|
||||
|
||||
!!!note
|
||||
These are not supported scripts/configurations by Tactical RMM, but it's provided here for your reference.
|
||||
!!!note
|
||||
These are not supported scripts/configurations by Tactical RMM, but it's provided here for your reference.
|
||||
|
||||
## General Notes on Proxies and Tactical RMM
|
||||
|
||||
### Port 443
|
||||
|
||||
Make sure websockets option is enabled.
|
||||
|
||||
All 3 URL's will need to be configured: `rmm`, `api`, `mesh`
|
||||
|
||||
For `mesh` see the Section 10. TLS Offloading of the [MeshCentral 2 User Guide](https://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2UserGuide.pdf)
|
||||
|
||||
### Port 4222
|
||||
|
||||
Is NATS (<https://nats.io>). You'll need a TCP forwarder as NATS only talks TCP not HTTP.
|
||||
|
||||
## HAProxy
|
||||
|
||||
|
||||
Check/Change the mesh central config.json, some of the values may be set already, CertUrl must be changed to point to the HAProxy server.
|
||||
|
||||
### Meshcentral Adjustment
|
||||
@@ -20,7 +33,7 @@ nano /meshcentral/meshcentral-data/config.json
|
||||
|
||||
Insert this (modify `HAProxyIP` to your network)
|
||||
|
||||
```
|
||||
```conf
|
||||
{
|
||||
"settings": {
|
||||
"Port": 4430,
|
||||
@@ -45,9 +58,9 @@ service meshcentral restart
|
||||
### HAProxy Config
|
||||
|
||||
The order of use_backend is important `Tactical-Mesh-WebSocket_ipvANY` must be before `Tactical-Mesh_ipvANY`
|
||||
The values of `timeout connect`, `timeout server`, `timeout tunnel` in `Tactical-Mesh-WebSocket` have been configured to maintain a stable agent connection, however you may need to adjust these values to suit your environment.
|
||||
The values of `timeout connect`, `timeout server`, `timeout tunnel` in `Tactical-Mesh-WebSocket` have been configured to maintain a stable agent connection, however you may need to adjust these values to suit your environment.
|
||||
|
||||
```
|
||||
```conf
|
||||
frontend HTTPS-merged
|
||||
bind 0.0.0.0:443 name 0.0.0.0:443 ssl crt-list /var/etc/haproxy/HTTPS.crt_list #ADJUST THIS TO YOUR OWN SSL CERTIFICATES
|
||||
mode http
|
||||
@@ -131,8 +144,7 @@ sudo apt install -y fail2ban
|
||||
|
||||
### Set Tactical fail2ban filter conf File
|
||||
|
||||
|
||||
```
|
||||
```bash
|
||||
tacticalfail2banfilter="$(cat << EOF
|
||||
[Definition]
|
||||
failregex = ^<HOST>.*400.17.*$
|
||||
@@ -144,7 +156,7 @@ sudo echo "${tacticalfail2banfilter}" > /etc/fail2ban/filter.d/tacticalrmm.conf
|
||||
|
||||
### Set Tactical fail2ban jail conf File
|
||||
|
||||
```
|
||||
```bash
|
||||
tacticalfail2banjail="$(cat << EOF
|
||||
[tacticalrmm]
|
||||
enabled = true
|
||||
@@ -164,4 +176,503 @@ sudo echo "${tacticalfail2banjail}" > /etc/fail2ban/jail.d/tacticalrmm.local
|
||||
|
||||
```bash
|
||||
sudo systemctl restart fail2ban
|
||||
```
|
||||
```
|
||||
|
||||
## Using purchased SSL certs instead of LetsEncrypt wildcards
|
||||
|
||||
Credit to [@dinger1986](https://github.com/dinger1986)
|
||||
|
||||
How to change certs used by Tactical RMM to purchased ones (this can be a wildcard cert).
|
||||
|
||||
You need to add the certificate private key and public keys to the following files:
|
||||
|
||||
`/etc/nginx/sites-available/rmm.conf`
|
||||
|
||||
`/etc/nginx/sites-available/meshcentral.conf`
|
||||
|
||||
`/etc/nginx/sites-available/frontend.conf`
|
||||
|
||||
`/rmm/api/tacticalrmm/tacticalrmm/local_settings.py`
|
||||
|
||||
1. create a new folder for certs and allow tactical user permissions (assumed to be tactical)
|
||||
|
||||
sudo mkdir /certs
|
||||
sudo chown -R tactical:tactical /certs"
|
||||
|
||||
2. Now move your certs into that folder.
|
||||
|
||||
3. Open the api file and add the api certificate or if its a wildcard the directory should be `/certs/yourdomain.com/`
|
||||
|
||||
sudo nano /etc/nginx/sites-available/rmm.conf
|
||||
|
||||
replace
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
|
||||
|
||||
with
|
||||
|
||||
ssl_certificate /certs/api.yourdomain.com/fullchain.pem;
|
||||
ssl_certificate_key /certs/api.yourdomain.com/privkey.pem;
|
||||
|
||||
4. Repeat the process for
|
||||
|
||||
/etc/nginx/sites-available/meshcentral.conf
|
||||
/etc/nginx/sites-available/frontend.conf
|
||||
|
||||
but change api. to: mesh. and rmm. respectively.
|
||||
|
||||
5. Add the following to the last lines of `/rmm/api/tacticalrmm/tacticalrmm/local_settings.py`
|
||||
|
||||
nano /rmm/api/tacticalrmm/tacticalrmm/local_settings.py
|
||||
|
||||
add
|
||||
|
||||
CERT_FILE = "/certs/api.yourdomain.com/fullchain.pem"
|
||||
KEY_FILE = "/certs/api.yourdomain.com/privkey.pem"
|
||||
|
||||
|
||||
6. Regenerate Nats Conf
|
||||
|
||||
cd /rmm/api/tacticalrmm
|
||||
source ../env/bin/activate
|
||||
python manage.py reload_nats
|
||||
|
||||
7. Restart services
|
||||
|
||||
sudo systemctl restart rmm celery celerybeat nginx nats natsapi
|
||||
|
||||
## Use certbot to do acme challenge over http
|
||||
|
||||
The standard SSL cert process in Tactical uses a [DNS challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) that requires dns txt files to be updated with every run.
|
||||
|
||||
The below script uses [http challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge) on the 3 separate ssl certs, one for each subdomain: rmm, api, mesh. They still have the same 3 month expiry. Restart the Tactical RMM server about every 2.5 months (80 days) for auto-renewed certs to become active.
|
||||
|
||||
!!!note
|
||||
Your Tactical RMM server will need to have TCP Port: 80 exposed to the internet
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
###Set colours same as Tactical RMM install and Update
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
### Ubuntu 20.04 Check
|
||||
|
||||
UBU20=$(grep 20.04 "/etc/"*"release")
|
||||
if ! [[ $UBU20 ]]; then
|
||||
echo -ne "\033[0;31mThis script will only work on Ubuntu 20.04\e[0m\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cls() {
|
||||
printf "\033c"
|
||||
}
|
||||
|
||||
print_green() {
|
||||
printf >&2 "${GREEN}%0.s-${NC}" {1..80}
|
||||
printf >&2 "\n"
|
||||
printf >&2 "${GREEN}${1}${NC}\n"
|
||||
printf >&2 "${GREEN}%0.s-${NC}" {1..80}
|
||||
printf >&2 "\n"
|
||||
}
|
||||
|
||||
cls
|
||||
|
||||
### Set variables for domains
|
||||
|
||||
while [[ $rmmdomain != *[.]*[.]* ]]
|
||||
do
|
||||
echo -ne "${YELLOW}Enter the subdomain used for the backend (e.g. api.example.com)${NC}: "
|
||||
read rmmdomain
|
||||
done
|
||||
|
||||
while [[ $frontenddomain != *[.]*[.]* ]]
|
||||
do
|
||||
echo -ne "${YELLOW}Enter the subdomain used for the frontend (e.g. rmm.example.com)${NC}: "
|
||||
read frontenddomain
|
||||
done
|
||||
|
||||
while [[ $meshdomain != *[.]*[.]* ]]
|
||||
do
|
||||
echo -ne "${YELLOW}Enter the subdomain used for meshcentral (e.g. mesh.example.com)${NC}: "
|
||||
read meshdomain
|
||||
done
|
||||
|
||||
echo -ne "${YELLOW}Enter the current root domain (e.g. example.com or example.co.uk)${NC}: "
|
||||
read rootdomain
|
||||
|
||||
|
||||
### Setup Certificate Variables
|
||||
CERT_PRIV_KEY=/etc/letsencrypt/live/${rootdomain}/privkey.pem
|
||||
CERT_PUB_KEY=/etc/letsencrypt/live/${rootdomain}/fullchain.pem
|
||||
|
||||
### Make Letsencrypt directories
|
||||
|
||||
sudo mkdir /var/www/letsencrypt
|
||||
sudo mkdir /var/www/letsencrypt/.mesh
|
||||
sudo mkdir /var/www/letsencrypt/.rmm
|
||||
sudo mkdir /var/www/letsencrypt/.api
|
||||
|
||||
### Remove config files for nginx
|
||||
|
||||
sudo rm /etc/nginx/sites-available/rmm.conf
|
||||
sudo rm /etc/nginx/sites-available/meshcentral.conf
|
||||
sudo rm /etc/nginx/sites-available/frontend.conf
|
||||
sudo rm /etc/nginx/sites-enabled/rmm.conf
|
||||
sudo rm /etc/nginx/sites-enabled/meshcentral.conf
|
||||
sudo rm /etc/nginx/sites-enabled/frontend.conf
|
||||
|
||||
### Setup tactical nginx config files for letsencrypt
|
||||
|
||||
nginxrmm="$(cat << EOF
|
||||
server_tokens off;
|
||||
upstream tacticalrmm {
|
||||
server unix:////rmm/api/tacticalrmm/tacticalrmm.sock;
|
||||
}
|
||||
map \$http_user_agent \$ignore_ua {
|
||||
"~python-requests.*" 0;
|
||||
"~go-resty.*" 0;
|
||||
default 1;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${rmmdomain};
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt/.api/;}
|
||||
location / {
|
||||
return 301 https://\$server_name\$request_uri;}
|
||||
}
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name ${rmmdomain};
|
||||
client_max_body_size 300M;
|
||||
access_log /rmm/api/tacticalrmm/tacticalrmm/private/log/access.log;
|
||||
error_log /rmm/api/tacticalrmm/tacticalrmm/private/log/error.log;
|
||||
ssl_certificate ${CERT_PUB_KEY};
|
||||
ssl_certificate_key ${CERT_PRIV_KEY};
|
||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
|
||||
|
||||
location /static/ {
|
||||
root /rmm/api/tacticalrmm;
|
||||
}
|
||||
location /private/ {
|
||||
internal;
|
||||
add_header "Access-Control-Allow-Origin" "https://${frontenddomain}";
|
||||
alias /rmm/api/tacticalrmm/tacticalrmm/private/;
|
||||
}
|
||||
location ~ ^/ws/ {
|
||||
proxy_pass http://unix:/rmm/daphne.sock;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
}
|
||||
location /saltscripts/ {
|
||||
internal;
|
||||
add_header "Access-Control-Allow-Origin" "https://${frontenddomain}";
|
||||
alias /srv/salt/scripts/userdefined/;
|
||||
}
|
||||
location /builtin/ {
|
||||
internal;
|
||||
add_header "Access-Control-Allow-Origin" "https://${frontenddomain}";
|
||||
alias /srv/salt/scripts/;
|
||||
}
|
||||
location ~ ^/(natsapi) {
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
uwsgi_pass tacticalrmm;
|
||||
include /etc/nginx/uwsgi_params;
|
||||
uwsgi_read_timeout 500s;
|
||||
uwsgi_ignore_client_abort on;
|
||||
}
|
||||
location / {
|
||||
uwsgi_pass tacticalrmm;
|
||||
include /etc/nginx/uwsgi_params;
|
||||
uwsgi_read_timeout 9999s;
|
||||
uwsgi_ignore_client_abort on;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)"
|
||||
echo "${nginxrmm}" | sudo tee /etc/nginx/sites-available/rmm.conf > /dev/null
|
||||
|
||||
|
||||
nginxmesh="$(cat << EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${meshdomain};
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt/.mesh/;}
|
||||
location / {
|
||||
return 301 https://\$server_name\$request_uri;}
|
||||
}
|
||||
server {
|
||||
listen 443 ssl;
|
||||
proxy_send_timeout 330s;
|
||||
proxy_read_timeout 330s;
|
||||
server_name ${meshdomain};
|
||||
ssl_certificate ${CERT_PUB_KEY};
|
||||
ssl_certificate_key ${CERT_PRIV_KEY};
|
||||
ssl_session_cache shared:WEBSSL:10m;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:4430/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header X-Forwarded-Host \$host:\$server_port;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)"
|
||||
echo "${nginxmesh}" | sudo tee /etc/nginx/sites-available/meshcentral.conf > /dev/null
|
||||
|
||||
|
||||
|
||||
nginxfrontend="$(cat << EOF
|
||||
server {
|
||||
server_name ${frontenddomain};
|
||||
charset utf-8;
|
||||
location / {
|
||||
root /var/www/rmm/dist;
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
}
|
||||
error_log /var/log/nginx/frontend-error.log;
|
||||
access_log /var/log/nginx/frontend-access.log;
|
||||
listen 443 ssl;
|
||||
ssl_certificate ${CERT_PUB_KEY};
|
||||
ssl_certificate_key ${CERT_PRIV_KEY};
|
||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${frontenddomain};
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt/.rmm/;}
|
||||
location / {
|
||||
return 301 https://\$host\$request_uri;}
|
||||
}
|
||||
EOF
|
||||
)"
|
||||
echo "${nginxfrontend}" | sudo tee /etc/nginx/sites-available/frontend.conf > /dev/null
|
||||
|
||||
### Relink nginx config files
|
||||
|
||||
sudo ln -s /etc/nginx/sites-available/rmm.conf /etc/nginx/sites-enabled/rmm.conf
|
||||
sudo ln -s /etc/nginx/sites-available/meshcentral.conf /etc/nginx/sites-enabled/meshcentral.conf
|
||||
sudo ln -s /etc/nginx/sites-available/frontend.conf /etc/nginx/sites-enabled/frontend.conf
|
||||
|
||||
### Restart nginx
|
||||
|
||||
sudo systemctl restart nginx
|
||||
|
||||
|
||||
### Get letsencrypt Certs
|
||||
|
||||
sudo letsencrypt certonly --webroot -w /var/www/letsencrypt/.mesh/ -d ${meshdomain}
|
||||
sudo letsencrypt certonly --webroot -w /var/www/letsencrypt/.rmm/ -d ${frontenddomain}
|
||||
sudo letsencrypt certonly --webroot -w /var/www/letsencrypt/.api/ -d ${rmmdomain}
|
||||
|
||||
### Ensure letsencrypt Permissions are correct
|
||||
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
||||
sudo chmod 775 -R /etc/letsencrypt
|
||||
|
||||
### Set variables for new certs
|
||||
|
||||
CERT_PRIV_KEY_API=/etc/letsencrypt/live/${rmmdomain}/privkey.pem
|
||||
CERT_PUB_KEY_API=/etc/letsencrypt/live/${rmmdomain}/fullchain.pem
|
||||
CERT_PRIV_KEY_RMM=/etc/letsencrypt/live/${frontenddomain}/privkey.pem
|
||||
CERT_PUB_KEY_RMM=/etc/letsencrypt/live/${frontenddomain}/fullchain.pem
|
||||
CERT_PRIV_KEY_MESH=/etc/letsencrypt/live/${meshdomain}/privkey.pem
|
||||
CERT_PUB_KEY_MESH=/etc/letsencrypt/live/${meshdomain}/fullchain.pem
|
||||
|
||||
### Replace certs in files
|
||||
|
||||
rmmlocalsettings="$(cat << EOF
|
||||
CERT_FILE = "${CERT_PUB_KEY_API}"
|
||||
KEY_FILE = "${CERT_PRIV_KEY_API}"
|
||||
EOF
|
||||
)"
|
||||
echo "${rmmlocalsettings}" | tee --append /rmm/api/tacticalrmm/tacticalrmm/local_settings.py > /dev/null
|
||||
|
||||
sudo sed -i "s|${CERT_PRIV_KEY}|${CERT_PRIV_KEY_API}|g" /etc/nginx/sites-available/rmm.conf
|
||||
sudo sed -i "s|${CERT_PUB_KEY}|${CERT_PUB_KEY_API}|g" /etc/nginx/sites-available/rmm.conf
|
||||
sudo sed -i "s|${CERT_PRIV_KEY}|${CERT_PRIV_KEY_MESH}|g" /etc/nginx/sites-available/meshcentral.conf
|
||||
sudo sed -i "s|${CERT_PUB_KEY}|${CERT_PUB_KEY_MESH}|g" /etc/nginx/sites-available/meshcentral.conf
|
||||
sudo sed -i "s|${CERT_PRIV_KEY}|${CERT_PRIV_KEY_RMM}|g" /etc/nginx/sites-available/frontend.conf
|
||||
sudo sed -i "s|${CERT_PUB_KEY}|${CERT_PUB_KEY_RMM}|g" /etc/nginx/sites-available/frontend.conf
|
||||
|
||||
### Remove Wildcard Cert
|
||||
|
||||
rm -r /etc/letsencrypt/live/${rootdomain}/
|
||||
rm -r /etc/letsencrypt/archive/${rootdomain}/
|
||||
rm /etc/letsencrypt/renewal/${rootdomain}.conf
|
||||
|
||||
|
||||
### Regenerate Nats Conf
|
||||
cd /rmm/api/tacticalrmm
|
||||
source ../env/bin/activate
|
||||
python manage.py reload_nats
|
||||
|
||||
### Restart services
|
||||
|
||||
for i in rmm celery celerybeat nginx nats natsapi
|
||||
do
|
||||
printf >&2 "${GREEN}Restarting ${i} service...${NC}\n"
|
||||
sudo systemctl restart ${i}
|
||||
done
|
||||
|
||||
|
||||
###Renew certs can be done by sudo letsencrypt renew (this should automatically be in /etc/cron.d/certbot)
|
||||
```
|
||||
|
||||
### Using your own certs with Docker
|
||||
|
||||
Let's Encrypt is the only officially supported method of obtaining wildcard certificates. Publicly signed certificates should work but have not been fully tested.
|
||||
|
||||
If you are providing your own publicly signed certificates, ensure you download the **full chain** (combined CA/Root + Intermediary) certificate in pem format. If certificates are not provided, a self-signed certificate will be generated and most agent functions won't work.
|
||||
|
||||
## Restricting Access to rmm.yourdomain.com
|
||||
|
||||
### Using DNS
|
||||
|
||||
1. Create a file allowed-domain.list which contains the DNS names you want to grant access to your rmm:
|
||||
|
||||
Edit `/etc/nginx/allowed-domain.list` and add
|
||||
|
||||
nom1.dyndns.tv
|
||||
nom2.dyndns.tv
|
||||
|
||||
2. Create a bash script domain-resolver.sh which do the DNS lookups for you:
|
||||
|
||||
Edit `/etc/nginx/domain-resolver.sh`
|
||||
|
||||
#!/usr/bin/env bash
|
||||
filename="$1"
|
||||
while read -r line
|
||||
do
|
||||
ddns_record="$line"
|
||||
if [[ ! -z $ddns_record ]]; then
|
||||
resolved_ip=getent ahosts $line | awk '{ print $1 ; exit }'
|
||||
if [[ ! -z $resolved_ip ]]; then
|
||||
echo "allow $resolved_ip;# from $ddns_record"
|
||||
fi
|
||||
fi
|
||||
done < "$filename"
|
||||
|
||||
3. Give the right permission to this script `chmod +x /etc/nginx/domain-resolver.sh`
|
||||
|
||||
4. Add a cron job which produces a valid nginx configuration and restarts nginx:
|
||||
|
||||
`/etc/cron.hourly/domain-resolver`
|
||||
|
||||
#!/usr/bin/env bash
|
||||
/etc/nginx/domain-resolver.sh /etc/nginx/allowed-domain.list > /etc/nginx//allowed-ips-from-domains.conf
|
||||
service nginx reload > /dev/null 2>&1
|
||||
|
||||
This can be a hourly, daily or monthly job or you can have it run at a specific time.
|
||||
|
||||
5. Give the right permission to this script chmod +x /etc/cron.hourly/domain-resolver
|
||||
|
||||
6. When run it will give something like this
|
||||
|
||||
Edit `/etc/nginx//allowed-ips-from-domains.conf`
|
||||
|
||||
allow xxx.xxx.xxx.xxx;# from maison.nom1.dyndns.tv
|
||||
allow xxx.xxx.xxx.xxx;# from maison.nom2.dyndns.tv
|
||||
|
||||
7. Update your nginx configuration to take this output into account:
|
||||
|
||||
Edit `/etc/nginx/sites-enabled/frontend.conf`
|
||||
|
||||
server {
|
||||
server_name rmm.example.com;
|
||||
charset utf-8;
|
||||
location / {
|
||||
root /var/www/rmm/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
}
|
||||
error_log /var/log/nginx/frontend-error.log;
|
||||
access_log /var/log/nginx/frontend-access.log;
|
||||
include /etc/nginx/allowed-ips-from-domains.conf;
|
||||
deny all;
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = rmm.example.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name rmm.example.com;
|
||||
return 404;
|
||||
}
|
||||
|
||||
### Using a fixed IP
|
||||
|
||||
1. Create a file containg the fixed IP address (where xxx.xxx.xxx.xxx must be replaced by your real IP address)
|
||||
|
||||
Edit `/etc/nginx//allowed-ips.conf`
|
||||
# Private IP address
|
||||
allow 192.168.0.0/16;
|
||||
allow 172.16.0.0/12;
|
||||
allow 10.0.0.0/8;
|
||||
# Public fixed IP address
|
||||
allow xxx.xxx.xxx.xxx
|
||||
|
||||
2. Update your nginx configuration to take this output into account:
|
||||
|
||||
Edit `/etc/nginx/sites-enabled/frontend.conf`
|
||||
|
||||
server {
|
||||
server_name rmm.example.com;
|
||||
charset utf-8;
|
||||
location / {
|
||||
root /var/www/rmm/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
}
|
||||
error_log /var/log/nginx/frontend-error.log;
|
||||
access_log /var/log/nginx/frontend-access.log;
|
||||
include /etc/nginx/allowed-ips;
|
||||
deny all;
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = rmm.example.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name rmm.example.com;
|
||||
return 404;
|
||||
}
|
||||
|
||||
38
docs/docs/unsupported_synology_docker_install.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Installing on Synology NAS using docker install
|
||||
|
||||
## Docker Setup
|
||||
|
||||
While a docker install is supported, trying to help someone get it working on their own Synology NAS is not. But here's how you do it!
|
||||
|
||||
- Follow the [standard docker install](./install_docker.md) documentation.
|
||||
- Once the `docker-compose` file is downloaded, edit it and modify the ports used by the nginx server to custom ports (`13180` and `13443` in the example below)
|
||||
|
||||

|
||||
|
||||
## Setup the reverse proxy
|
||||
|
||||
Go to **Login Portal > Advanced > Reverse Proxy** in the Control Panel
|
||||
|
||||
Create 2 entries for each tactical DNS entries, one for the HTTP port & one for the HTTPS
|
||||
|
||||

|
||||
|
||||
For the entries related to the mesh, add some custom headers and adjust the proxy timeout connection
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Bonus: SSL Certificate
|
||||
|
||||
In regards to the certificate, I followed this [tutorial](https://www.nas-forum.com/forum/topic/68046-tuto-certificat-lets-encrypt-avec-acmesh-api-ovh-en-docker-dsm67-update-180621) (in french but still clear after translation) to automatically update it and manually updating it on the NAS and in TRMM
|
||||
|
||||
```bash
|
||||
docker exec Acme sh -c "acme.sh --issue --keylength 4096 -d '*.mydomain.com' --dns dns_provider"
|
||||
sed -i '/CERT_PUB_KEY/d' /path/to/tactical/.env
|
||||
sed -i '/CERT_PRIV_KEY/d' /path/to/tactical/.env
|
||||
echo "CERT_PUB_KEY=$(sudo base64 -w 0 /volume1/docker/acme/\*.mydomain.com/fullchain.cer)" >> /path/to/tactical/.env
|
||||
echo "CERT_PRIV_KEY=$(sudo base64 -w 0 /volume1/docker/acme/\*.mydomain.com/*.whitesnew.com.key)" >> /path/to/tactical/.env
|
||||
docker exec Acme sh -c "acme.sh --deploy -d '*.mydomain.com' --deploy-hook synology_provider"
|
||||
docker-compose -f /path/to/tactical/docker-compose.yml restart
|
||||
```
|
||||
@@ -5,35 +5,41 @@
|
||||
For example, currently RMM version 0.4.17 is compatible with agent version 1.4.6 and lower.<br/><br/>
|
||||
You should never attempt to manually update an agent to a newer version without first making sure your RMM is on the latest version.
|
||||
|
||||
#### Updating from the Web UI
|
||||
Agents will automatically self update themselves if you have auto self update enabled in **Settings > Global Settings**<br/><br/>
|
||||
## Updating from the Web UI
|
||||
|
||||
Agents will automatically self update themselves if you have auto self update enabled in **Settings > Global Settings**
|
||||
|
||||

|
||||
|
||||
There is a background job that runs every hour, at 35 minutes past the hour and sends any online agents an update command if it detects they are on an older version.<br/><br/>
|
||||
There is a background job that runs every hour, at 35 minutes past the hour and sends any online agents an update command if it detects they are on an older version.
|
||||
|
||||
You can also trigger this background job to run on demand by clicking **Agents > Update Agents** in the web UI:
|
||||
|
||||
You can also trigger this background job to run on demand by clicking **Agents > Update Agents** in the web UI:<br/><br/>
|
||||

|
||||
|
||||
You can individually choose which agents to update, or simply Select All.<br/><br/>
|
||||
The RMM will automatically skip any agents that don't need updating.<br/><br/>
|
||||
You can trigger this manual agent update anytime you want. It is safe to spam, and won't run if an agent update task is already running.<br/><br/>
|
||||
It will also make sure agents update to the correct version, in case they are an older version that cannot be directly upgraded to the latest version.<br/><br/>
|
||||
For example, agents older than version 1.3.0 must first be updated to 1.3.0 before they can go any further.<br/>
|
||||
You can individually choose which agents to update, or simply Select All.
|
||||
|
||||
<br/>
|
||||
The RMM will automatically skip any agents that don't need updating.
|
||||
|
||||
#### Manually updating from the command line on the agent
|
||||
You can trigger this manual agent update anytime you want. It is safe to spam, and won't run if an agent update task is already running.
|
||||
|
||||
You should never need to do this but might be needed to troubleshoot agents that are not updating automatically.<br/>
|
||||
It will also make sure agents update to the correct version, in case they are an older version that cannot be directly upgraded to the latest version.
|
||||
|
||||
Download the `winagent-vX.X.X.exe` executable from the [github releases page](https://github.com/wh1te909/rmmagent/releases) and place it somewhere on the filesystem.<br/>
|
||||
For example, agents older than version 1.3.0 must first be updated to 1.3.0 before they can go any further.
|
||||
|
||||
## Manually updating from the command line on the agent
|
||||
|
||||
You should never need to do this but might be needed to troubleshoot agents that are not updating automatically.
|
||||
|
||||
Download the `winagent-vX.X.X.exe` executable from the [github releases page](https://github.com/wh1te909/rmmagent/releases) and place it somewhere on the filesystem.
|
||||
|
||||
Open CMD as admin and call the exe like so:
|
||||
|
||||
```
|
||||
```cmd
|
||||
C:\Windows\Temp>winagent-vX.X.X.exe /VERYSILENT /LOG=agentupdate.txt
|
||||
```
|
||||
|
||||
This command will return immediately since it spawns a background process to run the update.<br/>
|
||||
The agent will take around 30 seconds to fully update.<br/><br/>
|
||||
You can check the `agentupdate.txt` log file that is created for troubleshooting.<br/><br/>
|
||||
This command will return immediately since it spawns a background process to run the update.
|
||||
The agent will take around 30 seconds to fully update.
|
||||
|
||||
You can check the `agentupdate.txt` log file that is created for troubleshooting.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Updating the RMM (Docker)
|
||||
|
||||
#### Updating to the latest RMM version
|
||||
## Updating to the latest RMM version
|
||||
|
||||
Tactical RMM updates the docker images on every release and should be available within a few minutes
|
||||
|
||||
SSH into your server as a root user and run the below commands:<br/>
|
||||
SSH into your server as a root user and run the below commands:
|
||||
|
||||
```bash
|
||||
cd [dir/with/compose/file]
|
||||
mv docker-compose.yml docker-compose.yml.old
|
||||
@@ -14,7 +15,7 @@ sudo docker-compose down
|
||||
sudo docker-compose up -d --remove-orphans
|
||||
```
|
||||
|
||||
#### Keeping your Let's Encrypt certificate up to date
|
||||
## Keeping your Let's Encrypt certificate up to date
|
||||
|
||||
To renew your Let's Encrypt wildcard cert, run the following command, replacing `example.com` with your domain and `admin@example.com` with your email:
|
||||
|
||||
@@ -29,7 +30,7 @@ echo "CERT_PUB_KEY=$(sudo base64 -w 0 /etc/letsencrypt/live/${rootdomain}/fullch
|
||||
echo "CERT_PRIV_KEY=$(sudo base64 -w 0 /etc/letsencrypt/live/${rootdomain}/privkey.pem)" >> .env
|
||||
```
|
||||
|
||||
!!!warning
|
||||
!!!warning
|
||||
You must remove the old and any duplicate entries for CERT_PUB_KEY and CERT_PRIV_KEY in the .env file
|
||||
|
||||
Now run `sudo docker-compose restart` and the new certificate will be in effect
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
# Updating the RMM
|
||||
|
||||
#### Keeping your linux server up to date
|
||||
## Keeping your linux server up to date
|
||||
|
||||
You should periodically run `sudo apt update` and `sudo apt -y upgrade` to keep your server up to date.
|
||||
|
||||
Other than this, you should avoid making any changes to your server and let the `update.sh` script handle everything else for you.
|
||||
#### Updating to the latest RMM version
|
||||
|
||||
## Updating to the latest RMM version
|
||||
|
||||
!!!danger
|
||||
Do __not__ attempt to manually edit the update script or any configuration files unless specifically told to by one of the developers.<br/><br/>
|
||||
Since this software is completely self hosted and we have no access to your server, we have to assume you have not made any config changes to any of the files or services on your server, and the update script will assume this.<br/><br/>
|
||||
You should also **never** attempt to automate running the update script via cron.<br/><br/>
|
||||
The update script will update itself if needed to the latest version when you run it, and them prompt you to run it again.<br/><br/>
|
||||
Do __not__ attempt to manually edit the update script or any configuration files unless specifically told to by one of the developers.
|
||||
|
||||
Since this software is completely self hosted and we have no access to your server, we have to assume you have not made any config changes to any of the files or services on your server, and the update script will assume this.
|
||||
|
||||
You should also **never** attempt to automate running the update script via cron.
|
||||
|
||||
The update script will update itself if needed to the latest version when you run it, and then prompt you to run it again.
|
||||
|
||||
Sometimes, manual intervention will be required during an update in the form of yes/no prompts, so attempting to automate this will ignore these prompts and cause your installation to break.
|
||||
|
||||
SSH into your server as the linux user you created during install.<br/><br/>
|
||||
__Never__ run any update scripts or commands as the `root` user.<br/>This will mess up permissions and break your installation.<br/><br/>
|
||||
Download the update script and run it:<br/>
|
||||
SSH into your server as the linux user you created during install.
|
||||
|
||||
!!!danger
|
||||
__Never__ run any update scripts or commands as the `root` user.
|
||||
|
||||
This will mess up permissions and break your installation.
|
||||
|
||||
Download the update script and run it:
|
||||
|
||||
```bash
|
||||
wget -N https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/update.sh
|
||||
@@ -24,19 +34,17 @@ chmod +x update.sh
|
||||
./update.sh
|
||||
```
|
||||
|
||||
<br/>
|
||||
If you are already on the latest version, the update script will notify you of this and return immediately.
|
||||
|
||||
If you are already on the latest version, the update script will notify you of this and return immediately.<br/><br/>
|
||||
You can pass the optional `--force` flag to the update script to forcefully run through an update, which will bypass the check for latest version.<br/>
|
||||
You can pass the optional `--force` flag to the update script to forcefully run through an update, which will bypass the check for latest version.
|
||||
|
||||
```bash
|
||||
./update.sh --force
|
||||
```
|
||||
|
||||
This is usefull for a botched update that might have not completed fully.<br/><br/>
|
||||
The update script will also fix any permissions that might have gotten messed up during a botched update, or if you accidentally ran the update script as the `root` user.
|
||||
This is usefull for a botched update that might have not completed fully.
|
||||
|
||||
<br/>
|
||||
The update script will also fix any permissions that might have gotten messed up during a botched update, or if you accidentally ran the update script as the `root` user.
|
||||
|
||||
!!!warning
|
||||
Do __not__ attempt to manually update MeshCentral to a newer version.
|
||||
@@ -45,7 +53,7 @@ The update script will also fix any permissions that might have gotten messed up
|
||||
|
||||
The developers will test MeshCentral and make sure integration does not break before bumping the mesh version.
|
||||
|
||||
#### Keeping your Let's Encrypt certificate up to date
|
||||
## Keeping your Let's Encrypt certificate up to date
|
||||
|
||||
!!!info
|
||||
Currently, the update script does not automatically renew your Let's Encrypt wildcard certificate, which expires every 3 months, since this is non-trivial to automate using the DNS TXT record method.
|
||||
@@ -64,7 +72,7 @@ After this you have renewed the cert, simply run the `update.sh` script, passing
|
||||
./update.sh --force
|
||||
```
|
||||
|
||||
#### Keep an eye on your disk space
|
||||
## Keep an eye on your disk space
|
||||
|
||||
If you're running low, shrink you database
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@ nav:
|
||||
- "Updating Agents": update_agents.md
|
||||
- Functionality:
|
||||
- "Alerting": functions/alerting.md
|
||||
- "API Access": functions/api.md
|
||||
- "Automated Tasks": functions/automated_tasks.md
|
||||
- "Custom Fields": functions/custom_fields.md
|
||||
- "Database Maintenance": functions/database_maintenance.md
|
||||
- "Django Admin": functions/django_admin.md
|
||||
- "Global Keystore": functions/keystore.md
|
||||
- "Maintenance Mode": functions/maintenance_mode.md
|
||||
@@ -24,7 +26,6 @@ nav:
|
||||
- "URL Actions": functions/url_actions.md
|
||||
- "User Interface Preferences": functions/user_ui.md
|
||||
- "Examples": functions/examples.md
|
||||
- "Database Maintenace": functions/database_maintenance.md
|
||||
- Backup: backup.md
|
||||
- Restore: restore.md
|
||||
- Troubleshooting: troubleshooting.md
|
||||
@@ -32,16 +33,24 @@ nav:
|
||||
- Management Commands: management_cmds.md
|
||||
- MeshCentral Integration: mesh_integration.md
|
||||
- 3rd Party Integrations:
|
||||
- "AnyDesk": 3rdparty_anydesk.md
|
||||
- "BitDefender GravityZone": 3rdparty_bitdefender_gravityzone.md
|
||||
- "Connectwise Control / Screenconnect": 3rdparty_screenconnect.md
|
||||
- "Grafana": 3rdparty_grafana.md
|
||||
- "AnyDesk": 3rdparty_anydesk.md
|
||||
- "Connectwise Control / Screenconnect": 3rdparty_screenconnect.md
|
||||
- "TeamViewer": 3rdparty_teamviewer.md
|
||||
- "BitDefender GravityZone": 3rdparty_bitdefender_gravityzone.md
|
||||
- Unsupported Extras:
|
||||
- "Unsupported Guidelines": unsupported_guidelines.md
|
||||
- "Unsupported Scripts": unsupported_scripts.md
|
||||
- "Securing nginx": securing_nginx.md
|
||||
- "Installing in Synology docker": unsupported_synology_docker_install.md
|
||||
- Tips n' Tricks: tipsntricks.md
|
||||
- Contributing:
|
||||
- "Contributing to Docs": contributing.md
|
||||
- "Contributing using VSCode": contributing_using_vscode.md
|
||||
- "Contributing to Community Scripts": contributing_community_scripts.md
|
||||
- "Contributing using VSCode": contributing_using_vscode.md
|
||||
- "Contributing using Docker": contributing_using_docker.md
|
||||
- "Contributing using a Remote Server": contributing_using_a_remote_server.md
|
||||
- Security: security.md
|
||||
- License: license.md
|
||||
site_description: "A remote monitoring and management tool"
|
||||
site_author: "wh1te909"
|
||||
|
||||
25
install.sh
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="52"
|
||||
SCRIPT_VERSION="54"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh'
|
||||
|
||||
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
||||
@@ -40,11 +40,11 @@ fi
|
||||
|
||||
|
||||
# determine system
|
||||
if ([ "$osname" = "ubuntu" ] && [ "$fullrelno" = "20.04" ]) || ([ "$osname" = "debian" ] && [ $relno -ge 10 ]); then
|
||||
if ([ "$osname" = "ubuntu" ] && [ "$fullrelno" = "20.04" ]) || ([ "$osname" = "debian" ] && [ $relno -eq 10 ]); then
|
||||
echo $fullrel
|
||||
else
|
||||
echo $fullrel
|
||||
echo -ne "${RED}Only Ubuntu release 20.04 and Debian 10 and later, are supported\n"
|
||||
echo -ne "${RED}Only Ubuntu release 20.04 and Debian 10 are supported\n"
|
||||
echo -ne "Your system does not appear to be supported${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
@@ -169,6 +169,7 @@ print_green 'Installing Nginx'
|
||||
sudo apt install -y nginx
|
||||
sudo systemctl stop nginx
|
||||
sudo sed -i 's/worker_connections.*/worker_connections 2048;/g' /etc/nginx/nginx.conf
|
||||
sudo sed -i 's/# server_names_hash_bucket_size.*/server_names_hash_bucket_size 64;/g' /etc/nginx/nginx.conf
|
||||
|
||||
print_green 'Installing NodeJS'
|
||||
|
||||
@@ -324,24 +325,6 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DATETIME_FORMAT': "%b-%d-%Y - %H:%M",
|
||||
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'knox.auth.TokenAuthentication',
|
||||
),
|
||||
}
|
||||
|
||||
if not DEBUG:
|
||||
REST_FRAMEWORK.update({
|
||||
'DEFAULT_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
)
|
||||
})
|
||||
|
||||
MESH_USERNAME = "${meshusername}"
|
||||
MESH_SITE = "https://${meshdomain}"
|
||||
REDIS_HOST = "localhost"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="30"
|
||||
SCRIPT_VERSION="31"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh'
|
||||
|
||||
sudo apt update
|
||||
@@ -39,11 +39,11 @@ if [ ! "$osname" = "ubuntu" ] && [ ! "$osname" = "debian" ]; then
|
||||
fi
|
||||
|
||||
# determine system
|
||||
if ([ "$osname" = "ubuntu" ] && [ "$fullrelno" = "20.04" ]) || ([ "$osname" = "debian" ] && [ $relno -ge 10 ]); then
|
||||
if ([ "$osname" = "ubuntu" ] && [ "$fullrelno" = "20.04" ]) || ([ "$osname" = "debian" ] && [ $relno -eq 10 ]); then
|
||||
echo $fullrel
|
||||
else
|
||||
echo $fullrel
|
||||
echo -ne "${RED}Only Ubuntu release 20.04 and Debian 10 and later, are supported\n"
|
||||
echo -ne "${RED}Only Ubuntu release 20.04 and Debian 10 are supported\n"
|
||||
echo -ne "Your system does not appear to be supported${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
$log if provided will output verbose logs with timestamps. This can be used to determine how long the installer took.
|
||||
|
||||
TacticalRMM: Need to add Custom Fields to the Client or Site and invoke them in the Script Arguments; example shown.
|
||||
Name the url "bdurl" in the client custom field.
|
||||
Name the url "bdurl" in the client custom field.
|
||||
-url {{client.bdurl}
|
||||
|
||||
SuperOps.ai: Add url and exe run time variables.
|
||||
@@ -45,24 +45,23 @@ if ($log) {
|
||||
Write-Output ""
|
||||
}
|
||||
|
||||
|
||||
if (($exe -ne $null) -and ($exe.Length -gt 0)) {
|
||||
if (($null -ne $exe) -and ($exe.Length -gt 0)) {
|
||||
Write-Output "$(Get-Timestamp) The -exe parameter is deprecated (not needed)"
|
||||
}
|
||||
|
||||
if (($url -eq $null) -or ($url.Length -eq 0)) {
|
||||
if (($null -eq $url) -or ($url.Length -eq 0)) {
|
||||
Write-Output "$(Get-Timestamp) Url parameter is not specified"
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
$exe = [uri]::UnescapeDataString($([uri]$url).segments[-1])
|
||||
if ($exe -eq $null) {
|
||||
if ($null -eq $exe) {
|
||||
Write-Output "$(Get-Timestamp) Exe could not be extracted from the URL"
|
||||
Write-Output "$(Get-Timestamp) Make sure the URL is not modified from the original URL"
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
#Check if software is installed. If folder is present, terminate script
|
||||
# Check if software is installed. If key is present, terminate script
|
||||
if ($log) {
|
||||
Write-Output "$(Get-Timestamp) Checking if Bitdefender is installed..."
|
||||
}
|
||||
@@ -126,7 +125,6 @@ if ($log) {
|
||||
Write-Output "$(Get-Timestamp) Cleaning up temp file..."
|
||||
}
|
||||
|
||||
|
||||
# Cleanup
|
||||
if (Test-Path -PathType Leaf -Path $tmpExe) {
|
||||
Remove-Item $tmpExe
|
||||
|
||||
31
scripts/Win_Bitlocker_Drive_Check_Status.ps1
Normal file
@@ -0,0 +1,31 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Checks drive to see if bitlocker is enabled
|
||||
.DESCRIPTION
|
||||
Assumes c, but you can specify a drive if you want.
|
||||
.PARAMETER Drive
|
||||
Optional: Specify drive letter if you want to check a drive other than c
|
||||
.EXAMPLE
|
||||
Drive d
|
||||
.NOTES
|
||||
9/20/2021 v1 Initial release by @silversword411 with the help of @Ruben
|
||||
#>
|
||||
|
||||
param (
|
||||
[string] $Drive = "c"
|
||||
)
|
||||
|
||||
|
||||
if ((Get-BitLockerVolume -MountPoint $Drive).ProtectionStatus -eq 'On') {
|
||||
do {
|
||||
$EncryptionPercentage = (Get-BitLockerVolume -MountPoint $Drive).EncryptionPercentage
|
||||
Write-Output "BitLocker Encryption Percentage: $EncryptionPercentage"
|
||||
Start-Sleep -Seconds 5
|
||||
} until ($EncryptionPercentage -match 100)
|
||||
Write-Output "Bitlocker is enabled and Encryption completed"
|
||||
Exit 1
|
||||
}
|
||||
else {
|
||||
Write-Output "BitLocker is not turned on for this volume!"
|
||||
Exit 0
|
||||
}
|
||||