Compare commits

...

141 Commits

Author SHA1 Message Date
wh1te909
5c74d1d021 Release 0.8.5 2021-10-10 22:24:30 +00:00
wh1te909
aff659b6b6 bump versions 2021-10-10 22:21:23 +00:00
wh1te909
58724d95fa no debian 11 for now 2021-10-10 21:21:51 +00:00
wh1te909
8d61fcd5c9 handle invalid tokens 2021-10-10 21:08:43 +00:00
wh1te909
3e1be53c36 update reqs 2021-10-08 06:18:15 +00:00
Dan
f3754588bd Merge pull request #743 from bc24fl/develop
Fixed sorting under Folder View in Script Manager
2021-10-07 22:42:33 -07:00
Dan
c4ffffeec8 Merge pull request #750 from silversword411/develop
docs and scripts updates
2021-10-07 22:35:24 -07:00
silversword411
5b69f6a358 scripts_wip adding dell command 2021-10-08 00:50:19 -04:00
silversword411
1af89a7447 Merge pull request #1 from silversword411/develop-wip
Develop wip
2021-10-08 00:47:37 -04:00
silversword411
90abd81035 docs Adding way to restrict admin access thru nginx 2021-10-08 00:45:20 -04:00
Dan
898824b13f Merge pull request #733 from isaacg123/patch-1
Minor cleanup tweaks
2021-10-07 21:29:56 -07:00
Dan
9d093aa7f8 Merge pull request #740 from bbrendon/patch-2
Update Win_Defender_QuickScan_Background.ps1
2021-10-07 21:28:26 -07:00
Irving
1770549f6c Fixed sorting under Folder View in Script Manager 2021-10-07 01:23:44 -04:00
silversword411
d21be77fd2 scripts_wip TRMM Agent Install script 2021-10-06 09:09:49 -04:00
silversword411
41a1c19877 docs adding cmd deploy script 2021-10-06 09:08:44 -04:00
bbrendon
9b6571ce68 Update Win_Defender_QuickScan_Background.ps1
I did some tests and don't see value in specifying a drive letter. `QuickScanAge` resets to 0 even without a drive letter.

Having a drive letter seems to also assume something that may not be true.
2021-10-04 12:29:41 -07:00
silversword411
88e98e4e35 docs adding HP link 2021-10-01 12:20:50 -04:00
silversword411
10c56ffbfa Fixing lenovo support link 2021-10-01 07:57:29 -04:00
silversword411
cb2c8d6f3c working on support questions 2021-10-01 07:22:08 -04:00
silversword411
ca62b850ce wip scripts adding 2021-10-01 07:17:04 -04:00
silversword411
5a75d4e140 docs troubleshooting tweaks 2021-10-01 07:15:45 -04:00
Dan
e0972b7c24 Merge pull request #737 from silversword411/develop
wip and docs updates
2021-09-30 12:17:46 -07:00
silversword411
0db497916d docs install tweaks 2021-09-30 00:32:53 -04:00
silversword411
23a0ad3c4e docs updating docker with LE info and improving headers 2021-09-29 22:41:11 -04:00
silversword411
2b4e1c4b67 docker add 2021-09-29 14:47:47 -04:00
silversword411
9b1b9244cf full chain cert docs 2021-09-29 13:56:33 -04:00
silversword411
ad570e9b16 wip and howitallworks tweaks 2021-09-28 14:30:34 -04:00
wh1te909
812ba6de62 add security policy closes #725 2021-09-27 05:55:51 +00:00
silversword411
8f97124adb docs troubleshooting tweak 2021-09-26 13:55:09 -04:00
wh1te909
28289838f9 change docs for uploading mesh agent 2021-09-25 23:47:15 +00:00
Dan
cca8a010c3 Merge pull request #736 from silversword411/develop
WIP works under the wire
2021-09-25 16:45:42 -07:00
wh1te909
91ab296692 update reqs 2021-09-25 23:23:03 +00:00
silversword411
ee6c9c4272 WIP works 2021-09-25 18:56:43 -04:00
isaacg123
21cd36fa92 Minor cleanup tweaks
https://evotec.xyz/the-curious-case-of-null-should-be-on-the-left-side-of-equality-comparisons-psscriptanalyzer/
2021-09-25 04:30:54 -05:00
sadnub
b1aafe3dbc Fix block_policy_inheritance not saving correctly when set in UI 2021-09-24 17:43:08 -04:00
Dan
5cd832de89 Merge pull request #711 from silversword411/develop
Docs tweaks
2021-09-24 13:48:28 -07:00
silversword411
24dd9d0518 WIP scripts adds and tweaks 2021-09-24 16:36:45 -04:00
sadnub
aab6ab810a formatting 2021-09-24 12:16:13 -04:00
sadnub
d1d6d5e71e fix sms message sending 2021-09-24 12:12:49 -04:00
sadnub
e67dd68522 formatting 2021-09-23 13:50:32 -04:00
sadnub
e25eae846d make two-factor prompt dialog persistent 2021-09-23 13:23:52 -04:00
sadnub
995eeaa455 remove console.log entries 2021-09-23 13:23:52 -04:00
sadnub
240c61b967 fix some audit log entries not typing to agent correctly 2021-09-23 13:23:52 -04:00
sadnub
2d8b0753b4 move upload mesh agent modal to core settings and rewrite to comp api 2021-09-23 13:23:52 -04:00
sadnub
44eab3de7f docker dev changes and nginx dev fix 2021-09-23 13:23:52 -04:00
sadnub
007be5bf95 Stop DatabaseError exception when update_fields hasn't been changed 2021-09-23 12:56:27 -04:00
silversword411
ee19c7c51f docs adding tidbits 2021-09-23 11:05:51 -04:00
sadnub
ce56afbdf9 Fix serialization error in view
Also moved policy processing to celery task
2021-09-23 11:02:45 -04:00
silversword411
51012695a1 docs adding mesh agent to trmm integration 2021-09-23 10:28:09 -04:00
silversword411
0eef2d2cc5 scripts fixing mt duplicati install script 2021-09-23 03:14:51 -04:00
silversword411
487f9f2815 docs adding install on synology docker. Thx ildrad 2021-09-21 12:48:49 -04:00
silversword411
d065adcd8e Adding contributing using browser to docs 2021-09-21 12:18:44 -04:00
silversword411
0d9a1dc5eb json args fix 2021-09-21 11:09:50 -04:00
silversword411
8f9ad15108 Preliminary release of script library parameters 2021-09-20 16:56:42 -04:00
silversword411
e538e9b843 requirements tweak 2021-09-20 12:45:30 -04:00
Dan
4a702b6813 Merge pull request #718 from Yamacore/develop
Fix nginx crash after netinstall
2021-09-17 19:24:24 -07:00
silversword411
1e6fd2c57a Docs updating automation policies 2021-09-17 11:46:25 -04:00
silversword411
600b959d89 Tweaking community scripts 2021-09-17 11:10:29 -04:00
Yamacore
b96de9eb13 Fix nginx crash after netinstall
debian 10 netinstall got "server_names_hash_bucket_size" option commented out this fixes it
2021-09-16 20:09:46 +02:00
silversword411
93be19b647 docs Adding network diagram from NiceGuyIT - NC 2021-09-15 13:53:39 -04:00
silversword411
74f45f6f1d docs faq tweaks 2021-09-13 18:33:23 -04:00
silversword411
54ba3d2888 docs tweaks 2021-09-13 16:25:55 -04:00
silversword411
65d5149f60 docs - More tweaks 2021-09-13 12:03:47 -04:00
silversword411
917ebb3771 docs - install tweaks 2021-09-13 11:47:24 -04:00
silversword411
7e66b1f545 refactoring server install doc, and other tweaks 2021-09-13 10:08:39 -04:00
Dan
05837dca35 Merge pull request #706 from subzdev/develop
add password plain text toggle
2021-09-12 14:56:02 -07:00
Dan
53be2ebe59 Merge pull request #698 from silversword411/develop
server troubleshooting script, docs cleanup, script library updates
2021-09-12 14:52:26 -07:00
silversword411
0341efcaea subzdev doc revamp 2021-09-11 12:08:34 -04:00
Bob
ec75210fd3 add password plain text toggle 2021-09-11 03:10:00 +00:00
silversword411
e6afe3e806 scripts Archiving old broken installer 2021-09-10 17:03:23 -04:00
silversword411
5aa46f068e scripts - duplicati updates 2021-09-10 16:58:29 -04:00
silversword411
a11a5b28bc cleanup docs 2021-09-10 15:23:12 -04:00
silversword411
907aa566ca docs again 2021-09-10 15:18:41 -04:00
silversword411
5c21f099a8 docs tweaks 2021-09-10 14:56:52 -04:00
silversword411
b91201ae3e docs Fix png 2021-09-10 14:05:22 -04:00
silversword411
56d7e19968 docs cleanup, adding new dev docs with subzdev 2021-09-10 13:46:13 -04:00
silversword411
cf91c6c90e troubleshooter adding waits 2021-09-10 07:31:50 -04:00
wh1te909
9011148adf Release 0.8.4 2021-09-09 19:14:11 +00:00
wh1te909
897d0590d2 bump version 2021-09-09 19:10:28 +00:00
wh1te909
33b33e8458 retry websocket on 1006 error 2021-09-09 19:07:00 +00:00
wh1te909
7758f5c187 add a file to ignore 2021-09-09 18:47:28 +00:00
silversword411
83d7a03ba4 adding cert checks 2021-09-09 12:56:16 -04:00
wh1te909
a9a0df9699 fix tests 2021-09-09 16:26:06 +00:00
silversword411
df44f8f5f8 Adding server troubleshooting script 2021-09-09 11:43:12 -04:00
wh1te909
216a9ed035 speed up some views 2021-09-09 06:50:30 +00:00
wh1te909
35d61b6a6c add missing trailing slashes fixes #43 2021-09-09 05:55:27 +00:00
wh1te909
5fb72cea53 add types to url 2021-09-09 05:54:34 +00:00
Dan
d54d021e9f Merge pull request #697 from silversword411/develop
Tweaks
2021-09-08 18:17:42 -07:00
silversword411
06e78311df Tweaks 2021-09-08 21:04:35 -04:00
Dan
df720f95ca Merge pull request #696 from silversword411/develop
Unsupported Officially...no we really mean it
2021-09-08 17:06:48 -07:00
Dan
00faff34d3 Merge pull request #695 from aaronstuder/patch-1
Update install_server.md
2021-09-08 17:06:35 -07:00
silversword411
2b5b3ea4f3 Unsupported Officially...no we really mean it 2021-09-08 18:38:40 -04:00
sadnub
95e608d0b4 fix agent saying that it was approving updates when it actually didn't 2021-09-08 17:37:02 -04:00
sadnub
1d55bf87dd fix audit and debug log not refreshing on agent change 2021-09-08 17:36:30 -04:00
aaronstuder
1220ce53eb Update install_server.md 2021-09-08 12:55:53 -04:00
sadnub
2006218f87 honor block_dashboard_login from the login 2fa verification view 2021-09-08 10:29:58 -04:00
sadnub
40f427a387 add trailing slash to missing urls. Potentially fixes #43 2021-09-08 10:28:54 -04:00
sadnub
445e95baed formatting 2021-09-08 10:27:42 -04:00
sadnub
67fbc9ad33 make installer user use the new block_dasboard_login property 2021-09-06 22:42:32 -04:00
sadnub
1253e9e465 formatting 2021-09-06 20:10:14 -04:00
sadnub
21069432e8 fix tests 2021-09-06 20:06:23 -04:00
sadnub
6facf6a324 fix nginx on docker dev 2021-09-06 12:54:37 -04:00
sadnub
7556197485 move policy processing on any agent changes to celery task 2021-09-06 11:47:07 -04:00
wh1te909
8dddd2d896 Release 0.8.3 2021-09-06 09:30:51 +00:00
wh1te909
f319c95c2b bump version 2021-09-06 09:10:00 +00:00
wh1te909
8e972b0907 add docs for api keys 2021-09-06 08:50:18 +00:00
sadnub
395e400215 fix docker build script 2021-09-05 23:52:33 -04:00
sadnub
3685e3111f fix docker prod spinup. Move api container to uwsgi 2021-09-05 23:49:10 -04:00
sadnub
7bb1c75dc6 add auditing to objects URLAction, KeyStore, CustomFields and also audit when url actions are run 2021-09-05 12:32:37 -04:00
sadnub
b20834929c formatting 2021-09-05 11:35:15 -04:00
sadnub
181891757e fix tasks with assigned checks being added to automation policy 2021-09-05 11:22:21 -04:00
wh1te909
b16feeae44 fix debug log 2021-09-05 08:45:41 +00:00
wh1te909
684e049f27 typo 2021-09-05 06:07:46 +00:00
wh1te909
8cebd901b2 update reqs 2021-09-05 01:40:25 +00:00
wh1te909
3c96beb8fb fix celery memory leak 2021-09-04 23:40:57 +00:00
Dan
8a46459cf9 Merge pull request #683 from silversword411/develop
wip script additions and docs updates
2021-09-04 15:46:31 -07:00
Dan
be5c3e9daa Merge pull request #673 from juaromu/docs-securing-nginx
Securing NGINX added to docs
2021-09-04 15:45:37 -07:00
wh1te909
e44453877c skip sw errors fixes #682 2021-09-04 22:23:35 +00:00
wh1te909
f772a4ec56 allow users to reset their own password/2fa fixes #686 2021-09-04 22:15:51 +00:00
wh1te909
44182ec683 fix render error if results are null 2021-09-03 06:29:27 +00:00
wh1te909
b9ab13fa53 hide status field under properly implemented 2021-09-03 06:28:27 +00:00
wh1te909
2ad6721c95 fix pipeline 2021-09-03 05:45:31 +00:00
wh1te909
b7d0604e62 first/last name optional 2021-09-03 05:35:54 +00:00
wh1te909
a7518b4b26 black 2021-09-03 05:34:44 +00:00
wh1te909
50613f5d3e add api auth in settings, removed from local_settings 2021-09-03 05:31:44 +00:00
sadnub
f814767703 add tests and some ui fixes 2021-09-02 23:52:26 -04:00
sadnub
4af86d6456 set alert template on new agents 2021-09-02 21:36:35 -04:00
sadnub
f0a4f00c2d fix properties and block user dashboard access if denied 2021-09-02 21:32:18 -04:00
sadnub
4321affddb allow for creating special tokens for api access and bypassing two factor auth 2021-09-02 21:10:23 -04:00
silversword411
926ed55b9b docs update - Authorized users 2021-09-02 11:28:05 -04:00
silversword411
2ebf308565 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-09-02 10:33:36 -04:00
silversword411
1c5e736dce wip script network scanner 2021-09-02 10:33:25 -04:00
silversword411
b591f9f5b7 MOAR wips 2021-09-02 08:39:03 -04:00
silversword411
9724882578 wip script for print check 2021-09-02 08:23:05 -04:00
silversword411
ddef2df101 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-09-02 08:11:21 -04:00
silversword411
8af69c4284 adding alternate ssl to unsupported docs 2021-09-02 07:55:33 -04:00
silversword411
6ebe1ab467 adding alternate ssl to unsupported docs 2021-09-02 07:39:44 -04:00
silversword411
24e4d9cf6d docs Making docker howto visible 2021-09-02 05:21:51 -04:00
silversword411
f35fa0aa58 Troubleshooting docs update 2021-09-01 18:50:18 -04:00
silversword411
403762d862 wip script additions 2021-08-31 22:45:53 -04:00
Juan J. Romero
6294530fa3 Securing NGINX added to docs 2021-08-31 15:45:47 +10:00
160 changed files with 5644 additions and 2367 deletions

View File

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

View File

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

@@ -48,3 +48,4 @@ nats-rmm.conf
.mypy_cache
docs/site/
reset_db.sh
run_go_cmd.py

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()),
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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),
),
]

View File

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

View File

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

View File

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

View 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),
),
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -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",),
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,4 +6,4 @@ See <https://github.com/dinger1986/TRMM-Grafana>
![Example1](images/3rdparty_grafana_ex1.png)
![Example1](images/3rdparty_grafana_ex2.png)
![Example1](images/3rdparty_grafana_ex2.png)

View File

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

View File

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

View File

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

View File

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

View 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.
![RemoteSSH](images/Remote_SSH_connection.png)
### 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...

View 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>
![ForkIt](images/vscode-forkit.png)
### 2. Make Edits
Make some changes
![Edit](images/contribute_browser_make_changes.png)
![Edit](images/contribute_browser_make_changes2.png)
### 3. Request your changes to be pulled into the primary repo (Pull Request)
![Changes you've made need integration with master repo](images/trmm_contribute-notice.png)
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
![Sync Fork](images/trmm_need_sync_local_fork.png)
#### 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

View File

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

View File

@@ -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>
![ForkIt](images/vscode-forkit.png)
@@ -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)
![Changes you've made need integration with master repo](images/trmm_contribute-notice.png)
@@ -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/>

View File

@@ -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:
![reset2fa](images/reset2fa.png)
<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.
![AV Option1](images/faq_av_option1.png)
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.
![AV Sandbox1](images/faq_av_sandbox3.png)
![AV Sandbox1](images/faq_av_sandbox4.png)
![AV Sandbox1](images/faq_av_sandbox4.png)

View 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"
```

View File

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

View File

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

View File

@@ -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;">
&nbsp;<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>
&nbsp;<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>
&nbsp;<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.

View File

@@ -1,14 +1,10 @@
# How It All Works
INSERT WIREFRAME GRAPHICS HERE USING SOMETHING LIKE <https://www.yworks.com/yed-live/>
![Network Design](images/TacticalRMM-Network.png)
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:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -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/>
![siteagentinstall](images/siteagentinstall.png)
#### 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**.
![managedeployments](images/managedeployments.png)
!!!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)

View File

@@ -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:
![arecords](images/arecords.png)
## 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`
![txtrecord](images/txtrecord.png)
![dnstxt](images/dnstxt.png)
#### 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:
![meshagentdl](images/meshagentdl.png)
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.

View File

@@ -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:
![arecords](images/arecords.png)
#### 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
![questions](images/install_questions.png)
#### 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`
![txtrecord](images/txtrecord.png)
@@ -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:
![meshagentdl](images/meshagentdl.png)
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.

View File

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

View File

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

View File

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

View File

@@ -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
View 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 countrys 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.0s 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: Its 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 (toplevel) 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 were 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 toplevel 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:
![alt_text](images/owasp_burp.png "image_tooltip")
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
View 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.

View File

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

View 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?

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

View File

@@ -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
![Terminal](images/tipsntricks_meshcontrol.png)
### 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
![Features](images/mesh_features.png)
![Features](images/mesh_userconsent.png)

View File

@@ -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 isnt desirable as it logs extremely sensitive information which is why this isnt 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.

View 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

View File

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

View 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)
![syno ports](images/synology_docker_ports.jpg)
## 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
![syno reverse](images/synology_docker_reverse.jpg)
For the entries related to the mesh, add some custom headers and adjust the proxy timeout connection
![syno reverse detail](images/synology_docker_reverse_details1.jpg)
![syno reverse detail](images/synology_docker_reverse_details2.jpg)
## 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
```

View File

@@ -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**
![autoagentupdate](images/autoagentupdate.png)
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/>
![manualagentupdate](images/manualagentupdate.png)
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}

Some files were not shown because too many files have changed in this diff Show More