Compare commits

...

24 Commits

Author SHA1 Message Date
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
wh1te909
a9a0df9699 fix tests 2021-09-09 16:26:06 +00: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
27 changed files with 227 additions and 174 deletions

View File

@@ -210,6 +210,7 @@ services:
APP_PORT: ${APP_PORT}
API_PORT: ${API_PORT}
API_PROTOCOL: ${API_PROTOCOL}
DEV: 1
networks:
dev:
ipv4_address: ${DOCKER_NGINX_IP}

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

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

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

@@ -212,8 +212,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,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

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

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

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

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

@@ -15,11 +15,11 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
AUTH_USER_MODEL = "accounts.User"
# latest release
TRMM_VERSION = "0.8.3"
TRMM_VERSION = "0.8.4"
# bump this version everytime vue code is changed
# to alert user they need to manually refresh their browser
APP_VER = "0.0.145"
APP_VER = "0.0.146"
# https://github.com/wh1te909/rmmagent
LATEST_AGENT_VER = "1.6.2"

View File

@@ -5,7 +5,7 @@ set -e
: "${WORKER_CONNECTIONS:=2048}"
: "${APP_PORT:=80}"
: "${API_PORT:=80}"
: "${API_PROTOCOL:=}" # blank for uwgsi
: "${DEV:=0}"
CERT_PRIV_PATH=${TACTICAL_DIR}/certs/privkey.pem
CERT_PUB_PATH=${TACTICAL_DIR}/certs/fullchain.pem
@@ -29,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 {
@@ -37,11 +65,7 @@ server {
server_name ${API_HOST};
location / {
#Using variable to disable start checks
set \$api ${API_PROTOCOL}tactical-backend:${API_PORT};
include uwsgi_params;
uwsgi_pass \$api;
${API_NGINX}
}
location /static/ {

View File

@@ -1,7 +1,7 @@
# Installation
## Minimum requirements
- A fresh linux VM running either Ubuntu 20.04 or Debian 10, with a minimum of 2GB RAM.<br/>
- A fresh linux VM running either Ubuntu 20.04 or Debian 10, with a minimum of 3GB RAM (4GB Recommended).<br/>
!!!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/>
@@ -135,4 +135,4 @@ Once logged in, you will be redirected to the initial setup page.<br/><br/>
Create your first client/site, choose the default timezone and then upload the mesh agent you just downloaded.
!!!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)
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)

View File

@@ -2,49 +2,43 @@
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, if possible, use a pre-production environment so potential disruptions in your own environment and the service that you provide to your clients can be avoided.
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.
**<span style="text-decoration:underline;">Use the contents included in this guide and apply the security settings detailed here at your own discretion.</span>**
!!!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
## 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
## Hardening NGINX settings
## GeoIP Integration in NGINX - Blocking Requests by Country Code
### 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 {
##
@@ -55,11 +49,9 @@ http {
```
The next settings will depend on the desired GeoIP blocking strategy. For “allow by default, deny by exception”, the config would be:
```
```conf
http {
##
@@ -80,13 +72,11 @@ http {
```
(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;
@@ -100,11 +90,9 @@ Finally, the following “if” statement needs to be placed in all the vhosts w
```
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”
### 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
@@ -114,28 +102,23 @@ Source:
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
@@ -156,20 +139,16 @@ Downloading [FROM]=> [REPO]/setup-ngxblocker [TO]=> /usr/local/sbin/setup
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
@@ -189,7 +168,6 @@ Downloading [FROM]=> [REPO]/setup-ngxblocker [TO]=> /usr/local/sbin/setup
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.
@@ -198,31 +176,28 @@ Include any public IP addresses that should be whitelisted from bot and referrer
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
## 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
### 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.
```
$ 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
```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
### 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.
@@ -230,140 +205,117 @@ To compile libmodsecurity:
Clone the GitHub repository:
```bash
git clone --depth 1 -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity
```
$ 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 ..
```
$ 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
### 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
```
$ 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
```
$ 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:
```
$ 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 ..
```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
### 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
### 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
```
$ 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
```
$ 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
## Enabling OWASP Core Rule Set
Clone OWASP CRS:
```bash
cd /etc/nginx/modsec
git clone https://github.com/coreruleset/coreruleset.git
```
$ 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
```
$ 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,\
@@ -373,31 +325,25 @@ SecAction \
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;
@@ -406,19 +352,15 @@ server {
…………………..
```
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 #########

View File

@@ -0,0 +1,34 @@
## 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
* 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 😉 Help us maximize keeping developer time and resources focused on new releases...not support goosechases.
Thank you and #KeepDeploying

View File

@@ -38,8 +38,11 @@ nav:
- "Connectwise Control / Screenconnect": 3rdparty_screenconnect.md
- "Grafana": 3rdparty_grafana.md
- "TeamViewer": 3rdparty_teamviewer.md
- Unsupported Scripts & Security:
- "Unsupported Guidelines": unsupported_guidelines.md
- "Unsupported Scripts": unsupported_scripts.md
- "Securing nginx": securing_nginx.md
- Tips n' Tricks: tipsntricks.md
- Securing NGINX: securing_nginx.md
- Contributing:
- "Contributing to Docs": contributing.md
- "Contributing to Community Scripts": contributing_community_scripts.md

View File

@@ -11,7 +11,7 @@ export async function fetchAgents() {
export async function fetchAgentHistory(pk) {
try {
const { data } = await axios.get(`${baseUrl}/history/${pk}`)
const { data } = await axios.get(`${baseUrl}/history/${pk}/`)
return data
} catch (e) { }
}

View File

@@ -112,7 +112,7 @@ export default {
const pk = this.selectedAgentPk;
this.loading = true;
this.$axios
.get(`/software/refresh/${pk}`)
.get(`/software/refresh/${pk}/`)
.then(r => {
this.$store.dispatch("loadInstalledSoftware", pk);
this.loading = false;

View File

@@ -8,8 +8,8 @@
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<div class="text-h6 q-pl-sm q-pt-sm">Filter</div>
<div class="row">
<q-btn v-if="agentpk" class="q-pa-sm" dense flat push @click="search" icon="refresh" />
<div class="q-pa-sm col-1" v-if="!agentpk">
<q-option-group v-model="filterType" :options="filterTypeOptions" color="primary" />
</div>
@@ -306,6 +306,15 @@ export default {
if (props.agentpk) {
agentFilter.value = [props.agentpk];
watch([userFilter, actionFilter, timeFilter], search);
watch(
() => props.agentpk,
(newValue, oldValue) => {
if (newValue) {
agentFilter.value = [props.agentpk];
search();
}
}
);
}
// vue component hooks

View File

@@ -8,6 +8,7 @@
</q-btn>
</q-bar>
<q-card-section class="row">
<q-btn v-if="agentpk" dense flat push @click="getDebugLog" icon="refresh" />
<tactical-dropdown
v-if="!agentpk"
class="col-2 q-pr-sm"
@@ -134,6 +135,15 @@ export default {
if (props.agentpk) {
agentFilter.value = props.agentpk;
watch(
() => props.agentpk,
(newValue, oldValue) => {
if (newValue) {
agentFilter.value = props.agentpk;
getDebugLog();
}
}
);
}
// watchers

View File

@@ -98,7 +98,7 @@ export default {
getAlertTemplates() {
this.$q.loading.show();
this.$axios
.get("/alerts/alerttemplates")
.get("/alerts/alerttemplates/")
.then(r => {
this.options = r.data.map(template => ({
label: template.name,

View File

@@ -492,7 +492,7 @@ export default {
},
getAlertTemplates() {
this.$axios
.get("alerts/alerttemplates")
.get("alerts/alerttemplates/")
.then(r => {
this.alertTemplateOptions = r.data.map(template => ({ label: template.name, value: template.id }));
})

View File

@@ -184,7 +184,7 @@ export default function () {
.catch(e => { })
},
loadInstalledSoftware(context, pk) {
axios.get(`/software/installed/${pk}`).then(r => {
axios.get(`/software/installed/${pk}/`).then(r => {
context.commit("SET_INSTALLED_SOFTWARE", r.data.software);
})
.catch(e => { });

View File

@@ -604,7 +604,7 @@ export default {
this.ws.onclose = e => {
try {
console.log(`Closed code: ${e.code}`);
if (e.code !== 1000 && e.code !== 1006 && e.code) {
if (e.code !== 1000 && e.code) {
console.log("Retrying websocket connection...");
setTimeout(() => {
this.setupWS();