Compare commits

...

129 Commits

Author SHA1 Message Date
wh1te909
00c5f1365a bump versions 2021-12-20 06:48:02 +00:00
wh1te909
f7d317328a update reqs 2021-12-20 06:48:02 +00:00
wh1te909
3ccd705225 bump backup script version 2021-12-20 06:48:02 +00:00
diskraider
9e439fffaa Change timeout method
The current timeout results in an error "ERROR: Input redirection is not supported, exiting the process immediately.".

Reusing the ping tool to act as a timeout resolves this error because the batch script is not producing a user interruptable timeout but will still produce a 4-5 second timeout.
2021-12-20 06:48:02 +00:00
wh1te909
859dc170e7 update uninstall params 2021-12-20 06:48:02 +00:00
silversword411
1932d8fad9 docs - backup and silent uninstall tweaks 2021-12-20 06:48:02 +00:00
wh1te909
0c814ae436 reduce ram reqs 2021-12-20 06:48:02 +00:00
sadnub
89313d8a37 make post_update_tasks run on init container start 2021-12-20 06:48:02 +00:00
silversword411
2b85722222 docs - mesh download multiple 2021-12-20 06:48:02 +00:00
David Randall
57e5b0188c Fixes #872: backup.sh does not have EOL
Add EOL to backup.sh so CRON doesn't fail.
2021-12-20 06:48:02 +00:00
silversword411
2d7c830e70 docs code signing emphasis 2021-12-20 06:48:02 +00:00
silversword411
ccaa1790a9 docs - sys req info 2021-12-20 06:48:02 +00:00
silversword411
f6531d905e docs cron backups 2021-12-20 06:48:02 +00:00
silversword411
64a31879d3 docs - video of updating server 2021-12-20 06:48:02 +00:00
silversword411
0c6a4b1ed2 script - tweak AUOptions revert 2021-12-20 06:48:02 +00:00
silversword411
67801f39fe docs - 2rd party Screenconnect AIO 2021-12-20 06:48:02 +00:00
silversword411
892a0d67bf docs updating install agent script 2021-12-20 06:48:02 +00:00
silversword411
9fc0b7d5cc script_wip 2021-12-20 06:48:02 +00:00
bc24fl
22a614ef54 Added Printer Restart Jobs Community Script 2021-12-20 06:48:02 +00:00
silversword411
cd257b8e4d docs faq log4j 2021-12-20 06:48:02 +00:00
silversword
fa1ee2ca14 docs - updating index 2021-12-20 06:48:02 +00:00
wh1te909
34ea1adde6 sorting fixes #857 2021-12-20 06:48:02 +00:00
wh1te909
41cf8abb1f update reqs 2021-12-20 06:48:02 +00:00
silversword411
c0ffec1a4c docs - howitallworks nats server service 2021-12-20 06:48:02 +00:00
bc24fl
65779b8eaf Added Sophos Endpoint Install Community Script 2021-12-20 06:48:02 +00:00
bc24fl
c47bdb2d56 Added Sophos Endpoint Install Community Script 2021-12-20 06:48:02 +00:00
Michael Maertzdorf
d47ae642e7 Create SECURITY.md 2021-12-20 06:48:02 +00:00
Michael Maertzdorf
39c4609cc6 Create devskim-analysis.yml 2021-12-20 06:48:02 +00:00
dependabot[bot]
3ebba02a10 Bump django from 3.2.9 to 3.2.10 in /api/tacticalrmm
Bumps [django](https://github.com/django/django) from 3.2.9 to 3.2.10.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.9...3.2.10)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 06:48:02 +00:00
Michael Maertzdorf
4dc7a96e79 Create codeql-analysis.yml 2021-12-20 06:48:02 +00:00
silversword411
5a49a29110 docs - nginx proxy info 2021-12-20 06:48:02 +00:00
sadnub
1e2a56c5e9 Release 0.10.4 2021-12-10 21:59:35 -05:00
sadnub
8011773af4 bump versions 2021-12-10 19:12:45 -05:00
sadnub
ddc69c692e formatting 2021-12-10 19:09:52 -05:00
sadnub
df925c9744 fix script tests 2021-12-10 19:04:20 -05:00
sadnub
1726341aad remove script hashing since it was erroring out on some characters 2021-12-10 18:51:32 -05:00
sadnub
63b1ccc7a7 fix deleted community scripts not being removed from database 2021-12-10 18:50:51 -05:00
Dan
e80397c857 Merge pull request #847 from silversword411/develop
Community scripts - adding software install report, parameters to task scheduler, bluescreen report
2021-12-09 09:25:03 -08:00
silversword411
81aa7ca1a4 community scripts - user enable/disable 2021-12-09 01:44:40 -05:00
silversword411
f0f7695890 community script - Windows Update revert to MS Auto managed 2021-12-09 01:16:49 -05:00
silversword411
e7e8ce2f7a community script - adding chocolately list installed 2021-12-09 01:09:47 -05:00
silversword411
ba37a3f18d script library - task scheduler adding parameters 2021-12-09 01:00:41 -05:00
silversword411
60b11a7a5d community scripts - new user monitor add parameters 2021-12-09 00:56:13 -05:00
silversword411
29461c20a7 script library - Bluescreen Report 2021-12-09 00:50:43 -05:00
silversword411
2ff1f34543 Community scripts - adding software install report 2021-12-09 00:40:37 -05:00
wh1te909
b75d7f970f use getattr with a default for optional settings 2021-12-09 00:08:49 +00:00
wh1te909
204681f097 fix openfile limit with 1k+ agents 2021-12-09 00:06:33 +00:00
wh1te909
e239fe95a4 remove old checks from update script 2021-12-08 18:37:44 +00:00
Dan
0a101f061a Merge pull request #844 from silversword411/develop
adding docs tips n trick and fixing PR #833
2021-12-07 20:03:07 -08:00
silversword411
f112a17afa Fixing community scripts and docs from PR #833 2021-12-07 22:29:37 -05:00
silversword411
54658a66d2 docs - Adding tips n tricks 2021-12-07 22:12:44 -05:00
sadnub
6b8f5a76e4 Merge pull request #833 from r3die/develop
Splashtop 3rd party integration docs and script
2021-12-07 19:47:52 -05:00
Dan
623a5d338d Merge pull request #842 from silversword411/develop
Adding Repo to Help menu
2021-12-06 20:30:38 -08:00
silversword411
9c5565cfd5 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-12-06 22:50:12 -05:00
silversword411
722f2efaee Adding Github repo to Help menu 2021-12-06 22:49:45 -05:00
Dan
4928264204 Merge pull request #841 from silversword411/develop
docs update: mgmt commands
2021-12-04 01:14:20 -08:00
silversword411
12d62ddc2a docs - adding mgmt commands for docker 2021-12-03 11:13:44 -05:00
wh1te909
da54e97217 Release 0.10.3 2021-12-02 08:19:38 +00:00
wh1te909
9c0993dac8 bump version 2021-12-02 07:50:52 +00:00
wh1te909
175486b7c4 fix bug where reboot_required field was not being updated when agent didn't have a patch policy and setting was set to 'inherit' 2021-12-02 01:39:41 +00:00
wh1te909
4760a287f6 update docs 2021-12-01 19:53:37 +00:00
wh1te909
0237b48c87 update reqs 2021-12-01 19:37:53 +00:00
Dan
95c9f22e6c Merge pull request #837 from silversword411/develop
docs tweak
2021-12-01 09:31:22 -08:00
silversword411
9b001219d5 docs tweak 2021-12-01 12:27:52 -05:00
Dan
6ff15efc7b Merge pull request #835 from silversword411/develop
docs outbound firewall rules
2021-11-30 16:07:56 -08:00
silversword411
6fe1dccc7e docs outbound firewall rules 2021-11-30 18:15:49 -05:00
sadnub
1c80f6f3fa Don't allow script arg variable assignment to callable attributes. Fixes #726 2021-11-29 22:18:05 -05:00
sadnub
54d3177fdd also don't include callable attributes with variable substitutions on alert scripts 2021-11-29 22:15:07 -05:00
r3die
a24ad245d2 splashtop 4rd party integration docs and script
splashtop 4rd party integration docs and script
2021-11-29 15:20:29 -08:00
wh1te909
f38cfdcadf fix test script 2021-11-29 18:51:18 +00:00
Dan
92e4ad8ccd Merge pull request #830 from silversword411/develop
docs updates
2021-11-29 09:20:14 -08:00
silversword411
3f3ab088d2 docs - adding bulk delete 2021-11-29 09:47:38 -05:00
sadnub
2c2cbaa175 formatting 2021-11-28 21:08:41 -05:00
sadnub
911b6bf863 fix sorting process cpu percentage. Fixes #831 2021-11-28 21:07:09 -05:00
sadnub
31462cab64 fix tests and also check for the correct script hash 2021-11-28 20:59:21 -05:00
silversword411
1ee35da62d docs updates 2021-11-28 15:31:37 -05:00
sadnub
edf4815595 make script file encoding consistent. utf-8 2021-11-28 13:23:47 -05:00
sadnub
06ccee5d18 add script hash field and calculate hash on script changes. Also removed storing scripts in DB as base64 strings. Should fix #634 2021-11-28 13:23:10 -05:00
sadnub
d5ad85725f fix duplicate package in dev requirements 2021-11-27 23:26:44 -05:00
sadnub
4d5bddb413 rework script form and add syntax field 2021-11-27 22:59:18 -05:00
Dan
2f4da7c381 Merge pull request #829 from ssteeltm/develop
Update unsupported_scripts.md
2021-11-26 16:11:50 -08:00
Dan
8b845fce03 Merge pull request #826 from NiceGuyIT/docs-howitallworks-services
Document server services and configuration
2021-11-26 16:11:24 -08:00
Dan
9fd15c38a9 Merge pull request #825 from silversword411/develop
scripts and docs
2021-11-26 16:11:00 -08:00
silversword411
ec1573d01f Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-11-26 18:05:02 -05:00
silversword411
92ec1cc9e7 docs - add howitallworks to index 2021-11-26 18:05:00 -05:00
Hugo Sampaio
8b2f9665ce Update unsupported_scripts.md
Added info about how I run rmm behind Apache Proxy
( discord Hugo )
2021-11-26 17:25:42 -03:00
silversword411
cb388a5a78 scripts - adding demo server scripts 2021-11-26 14:35:33 -05:00
David Randall
7f4389ae08 Docs: Server services
Document the server services and configuration.
2021-11-25 16:59:19 -05:00
silversword411
76d71beaa2 script_wip addition 2021-11-25 15:06:15 -05:00
silversword411
31bb9c2197 docs - tips and tricks add mesh connection logs 2021-11-25 08:59:21 -05:00
wh1te909
6a2cd5c45a reduce celery memory usage and optimize a query 2021-11-25 06:20:06 +00:00
Dan
520632514b Merge pull request #823 from silversword411/develop
docs - video embed #1 and getting started started
2021-11-24 15:25:46 -08:00
silversword411
f998b28d0b docs - numbering fix 2021-11-24 17:34:43 -05:00
silversword411
1a6587e9e6 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-11-24 17:13:09 -05:00
silversword411
9b4b729d19 undo vscode spellcheck 2021-11-24 17:12:58 -05:00
silversword411
e80345295e script library - adding security audit 2021-11-24 14:06:44 -05:00
silversword411
026c259a2e added vscode spellcheck, shouldn't go public 2021-11-24 11:32:04 -05:00
silversword411
63474c2269 community scripts - adding syntax to defender enable 2021-11-24 11:30:03 -05:00
silversword411
faa1a9312f scripts - adding parameter check 2021-11-24 11:25:15 -05:00
silversword411
23fa0726d5 docs - v1 of getting started guide 2021-11-24 10:10:46 -05:00
silversword411
22210eaf7d Merge branch 'wh1te909:develop' into develop 2021-11-23 23:40:47 -05:00
silversword411
dcd8bee676 docs - video embed 2021-11-23 23:40:29 -05:00
silversword411
06f0fa8f0e Revert "docs - video embed testing"
This reverts commit 6d0f9e2cd5.
2021-11-23 23:37:54 -05:00
silversword411
6d0f9e2cd5 docs - video embed testing 2021-11-23 23:33:25 -05:00
sadnub
732afdb65d move custom fields to tab in edit agent modal 2021-11-23 21:35:11 -05:00
sadnub
1a9e8742f7 remove the need to type agent name to delete agents in dashboard 2021-11-23 21:35:11 -05:00
sadnub
b8eda37339 Fix setting alert template when policy assignment changes 2021-11-23 21:35:11 -05:00
sadnub
5107db6169 add drf_spectacular to dev requirements 2021-11-23 21:35:11 -05:00
wh1te909
2c8f207454 add mgmt command to bulk delete agents 2021-11-22 20:26:10 +00:00
wh1te909
489bc9c3b3 optimize some queries 2021-11-22 17:24:48 +00:00
wh1te909
514713e883 don't log swagger 2021-11-22 17:23:38 +00:00
wh1te909
17cc0cd09c forgot to check core settings fixes #816 2021-11-22 17:18:58 +00:00
Dan
4475df1295 Merge pull request #815 from tremor021/develop
Update Defender script
2021-11-22 09:06:04 -08:00
Dan
fdad267cfd Merge pull request #814 from silversword411/develop
docs updates
2021-11-22 09:05:19 -08:00
silversword411
3684fc80f0 docs - spellchecking 2021-11-22 08:07:49 -05:00
silversword411
e97a5fef94 script library - adding syntax to tooltip helper 2021-11-22 00:14:19 -05:00
silversword411
de2972631f docs - tips about running scripts syntax 2021-11-21 23:02:47 -05:00
tremor021
e5b8fd67c8 Update Defender script 2021-11-22 02:14:11 +01:00
silversword411
5fade89e2d docs - fixing install and restore docs to eliminate confusion 2021-11-21 15:18:18 -05:00
wh1te909
2eefedadb3 Release 0.10.2 2021-11-21 02:24:29 +00:00
wh1te909
e63d7a0b8a bump version 2021-11-21 02:24:07 +00:00
wh1te909
2a1b1849fa fix nats-api not working in docker 2021-11-21 02:02:29 +00:00
wh1te909
0461cb7f19 update docs 2021-11-20 22:21:02 +00:00
Dan
0932e0be03 Merge pull request #811 from silversword411/develop
docs updates
2021-11-20 14:17:11 -08:00
silversword411
4638ac9474 docs - reiterating no root and backup 2021-11-20 12:59:42 -05:00
silversword411
d8d7255029 docs - filter tips 2021-11-20 12:50:10 -05:00
wh1te909
fa05276c3f black 2021-11-19 20:00:22 +00:00
silversword411
e50a5d51d8 docs - troubleshooting enhancements 2021-11-19 14:14:12 -05:00
sadnub
c03ba78587 make swagger views optional 2021-11-19 13:58:38 -05:00
96 changed files with 3208 additions and 1386 deletions

View File

@@ -35,3 +35,4 @@ Pygments
mypy
pysnooper
isort
drf_spectacular

70
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ develop ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
schedule:
- cron: '19 14 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go', 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

34
.github/workflows/devskim-analysis.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: DevSkim
on:
push:
branches: [ develop ]
pull_request:
branches: [ develop ]
schedule:
- cron: '19 5 * * 0'
jobs:
lint:
name: DevSkim
runs-on: ubuntu-20.04
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run DevSkim scanner
uses: microsoft/DevSkim-Action@v1
- name: Upload DevSkim scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: devskim-results.sarif

19
SECURITY.md Normal file
View File

@@ -0,0 +1,19 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 0.10.4 | :white_check_mark: |
| < 0.10.4| :x: |
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc.

View File

@@ -0,0 +1,81 @@
import asyncio
from django.core.management.base import BaseCommand
from django.utils import timezone as djangotime
from packaging import version as pyver
from agents.models import Agent
from tacticalrmm.utils import AGENT_DEFER, reload_nats
class Command(BaseCommand):
help = "Delete old agents"
def add_arguments(self, parser):
parser.add_argument(
"--days",
type=int,
help="Delete agents that have not checked in for this many days",
)
parser.add_argument(
"--agentver",
type=str,
help="Delete agents that equal to or less than this version",
)
parser.add_argument(
"--delete",
action="store_true",
help="This will delete agents",
)
def handle(self, *args, **kwargs):
days = kwargs["days"]
agentver = kwargs["agentver"]
delete = kwargs["delete"]
if not days and not agentver:
self.stdout.write(
self.style.ERROR("Must have at least one parameter: days or agentver")
)
return
q = Agent.objects.defer(*AGENT_DEFER)
agents = []
if days:
overdue = djangotime.now() - djangotime.timedelta(days=days)
agents = [i for i in q if i.last_seen < overdue]
if agentver:
agents = [i for i in q if pyver.parse(i.version) <= pyver.parse(agentver)]
if not agents:
self.stdout.write(self.style.ERROR("No agents matched"))
return
deleted_count = 0
for agent in agents:
s = f"{agent.hostname} | Version {agent.version} | Last Seen {agent.last_seen} | {agent.client} > {agent.site}"
if delete:
s = "Deleting " + s
self.stdout.write(self.style.SUCCESS(s))
asyncio.run(agent.nats_cmd({"func": "uninstall"}, wait=False))
try:
agent.delete()
except Exception as e:
err = f"Failed to delete agent {agent.hostname}: {str(e)}"
self.stdout.write(self.style.ERROR(err))
else:
deleted_count += 1
else:
self.stdout.write(self.style.WARNING(s))
if delete:
reload_nats()
self.stdout.write(self.style.SUCCESS(f"Deleted {deleted_count} agents"))
else:
self.stdout.write(
self.style.SUCCESS(
"The above agents would be deleted. Run again with --delete to actually delete them."
)
)

View File

@@ -3,6 +3,7 @@ from django.core.management.base import BaseCommand
from packaging import version as pyver
from agents.models import Agent
from core.models import CoreSettings
from agents.tasks import send_agent_update_task
from tacticalrmm.utils import AGENT_DEFER
@@ -11,6 +12,10 @@ class Command(BaseCommand):
help = "Triggers an agent update task to run"
def handle(self, *args, **kwargs):
core = CoreSettings.objects.first()
if not core.agent_auto_update: # type: ignore
return
q = Agent.objects.defer(*AGENT_DEFER).exclude(version=settings.LATEST_AGENT_VER)
agent_ids: list[str] = [
i.agent_id

View File

@@ -98,7 +98,7 @@ class Agent(BaseAuditModel):
# check if new agent has been created
# or check if policy have changed on agent
# or if site has changed on agent and if so generate-policies
# or if site has changed on agent and if so generate policies
# or if agent was changed from server or workstation
if (
not old_agent
@@ -109,10 +109,6 @@ class Agent(BaseAuditModel):
):
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

View File

@@ -15,6 +15,7 @@ from tacticalrmm.celery import app
from agents.models import Agent
from agents.utils import get_winagent_url
from tacticalrmm.utils import AGENT_DEFER
def agent_update(agent_id: str, force: bool = False) -> str:
@@ -311,9 +312,7 @@ def prune_agent_history(older_than_days: int) -> str:
@app.task
def handle_agents_task() -> None:
q = Agent.objects.prefetch_related("pendingactions", "autotasks").only(
"pk", "agent_id", "version", "last_seen", "overdue_time", "offline_time"
)
q = Agent.objects.defer(*AGENT_DEFER)
agents = [
i
for i in q

View File

@@ -456,7 +456,8 @@ class Alert(models.Model):
if match:
name = match.group(1)
if hasattr(self, name):
# check if attr exists and isn't a function
if hasattr(self, name) and not callable(getattr(self, name)):
value = f"'{getattr(self, name)}'"
else:
continue

View File

@@ -121,18 +121,18 @@ class WinUpdates(APIView):
def put(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
needs_reboot: bool = request.data["needs_reboot"]
agent.needs_reboot = needs_reboot
agent.save(update_fields=["needs_reboot"])
reboot_policy: str = agent.get_patch_policy().reboot_after_install
reboot = False
if reboot_policy == "always":
reboot = True
if request.data["needs_reboot"]:
if reboot_policy == "required":
reboot = True
elif reboot_policy == "never":
agent.needs_reboot = True
agent.save(update_fields=["needs_reboot"])
elif needs_reboot and reboot_policy == "required":
reboot = True
if reboot:
asyncio.run(agent.nats_cmd({"func": "rebootnow"}, wait=False))

View File

@@ -54,6 +54,8 @@ def generate_agent_checks_task(
if create_tasks:
agent.generate_tasks_from_policies()
agent.set_alert_template()
return "ok"

View File

@@ -1,3 +1,4 @@
import base64
from django.core.management.base import BaseCommand
from logs.models import PendingAction
@@ -20,3 +21,15 @@ class Command(BaseCommand):
for user in User.objects.filter(is_installer_user=True):
user.block_dashboard_login = True
user.save()
# convert script base64 field to text field
user_scripts = Script.objects.exclude(script_type="builtin").filter(
script_body=""
)
for script in user_scripts:
# decode base64 string
script.script_body = base64.b64decode(
script.code_base64.encode("ascii", "ignore")
).decode("ascii", "ignore")
# script.hash_script_body() # also saves script
script.save(update_fields=["script_body"])

View File

@@ -9,6 +9,7 @@ from alerts.tasks import prune_resolved_alerts
from core.models import CoreSettings
from logs.tasks import prune_debug_log, prune_audit_log
from tacticalrmm.celery import app
from tacticalrmm.utils import AGENT_DEFER
@app.task
@@ -58,9 +59,7 @@ def core_maintenance_tasks():
def cache_db_fields_task():
from agents.models import Agent
for agent in Agent.objects.prefetch_related("winupdates", "pendingactions").only(
"pending_actions_count", "has_patches_pending", "pk"
):
for agent in Agent.objects.defer(*AGENT_DEFER):
agent.pending_actions_count = agent.pendingactions.filter(
status="pending"
).count()

View File

@@ -98,7 +98,7 @@ def dashboard_info(request):
"client_tree_splitter": request.user.client_tree_splitter,
"loading_bar_color": request.user.loading_bar_color,
"clear_search_when_switching": request.user.clear_search_when_switching,
"hosted": hasattr(settings, "HOSTED") and settings.HOSTED,
"hosted": getattr(settings, "HOSTED", False),
}
)

View File

@@ -9,7 +9,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.exceptions import PermissionDenied
from tacticalrmm.utils import notify_error, get_default_timezone
from tacticalrmm.utils import notify_error, get_default_timezone, AGENT_DEFER
from tacticalrmm.permissions import _audit_log_filter, _has_perm_on_agent
from .models import AuditLog, PendingAction, DebugLog
@@ -93,10 +93,16 @@ class PendingActions(APIView):
def get(self, request, agent_id=None):
if agent_id:
agent = get_object_or_404(Agent, agent_id=agent_id)
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=agent_id
)
actions = PendingAction.objects.filter(agent=agent)
else:
actions = PendingAction.objects.filter_by_role(request.user)
actions = (
PendingAction.objects.select_related("agent")
.defer("agent__services", "agent__wmi_detail")
.filter_by_role(request.user) # type: ignore
)
return Response(PendingActionSerializer(actions, many=True).data)

View File

@@ -8,4 +8,3 @@ Pygments
isort
mypy
types-pytz
types-pytz

View File

@@ -1,34 +1,34 @@
asgiref==3.4.1
asyncio-nats-client==0.11.4
asyncio-nats-client==0.11.5
celery==5.2.1
certifi==2021.10.8
cffi==1.15.0
channels==3.0.4
channels_redis==3.3.1
chardet==4.0.0
cryptography==35.0.0
cryptography==36.0.1
daphne==3.0.2
Django==3.2.9
django-cors-headers==3.10.0
django-ipware==4.0.0
Django==3.2.10
django-cors-headers==3.10.1
django-ipware==4.0.2
django-rest-knox==4.1.0
djangorestframework==3.12.4
djangorestframework==3.13.1
future==0.18.2
loguru==0.5.3
msgpack==1.0.2
msgpack==1.0.3
packaging==21.3
psycopg2-binary==2.9.2
pycparser==2.21
pycryptodome==3.11.0
pycryptodome==3.12.0
pyotp==2.6.0
pyparsing==2.4.7
pyparsing==3.0.6
pytz==2021.3
qrcode==6.1
redis==3.5.3
redis==4.0.2
requests==2.26.0
six==1.16.0
sqlparse==0.4.2
twilio==7.3.1
twilio==7.4.0
urllib3==1.26.7
uWSGI==2.0.20
validators==0.18.2

View File

@@ -9,6 +9,16 @@
"category": "TRMM (Win):Browsers",
"default_timeout": "300"
},
{
"guid": "720edbb7-8faf-4a77-9283-29935e8880d0",
"filename": "Win_Printer_ClearandRestart.bat",
"submittedBy": "https://github.com/wh1te909",
"name": "Printers - Clear all print jobs",
"description": "This script will stop the spooler, delete all pending print jobs and restart the spooler",
"shell": "cmd",
"category": "TRMM (Win):Printing",
"default_timeout": "300"
},
{
"guid": "3ff6a386-11d1-4f9d-8cca-1b0563bb6443",
"filename": "Win_Google_Chrome_Clear_Cache.ps1",
@@ -19,6 +29,16 @@
"category": "TRMM (Win):Browsers",
"default_timeout": "300"
},
{
"guid": "d3c74105-d1e5-40d8-94ff-b4d6b216fe0f",
"filename": "Win_Chocolatey_List_Installed.bat",
"submittedBy": "https://github.com/silversword411",
"name": "Chocolatey - List Installed apps",
"description": "Lists apps locally installed by chocolatey",
"shell": "cmd",
"category": "TRMM (Win):3rd Party Software>Chocolatey",
"default_timeout": "90"
},
{
"guid": "be1de837-f677-4ac5-aa0c-37a0fc9991fc",
"filename": "Win_Install_Adobe_Reader.ps1",
@@ -48,6 +68,16 @@
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software>Monitoring"
},
{
"guid": "5a60c13b-1882-4a92-bdfb-6dd1f6a11dd14",
"filename": "Win_Windows_Update_RevertToDefault.ps1",
"submittedBy": "https://github.com/silversword411",
"name": "Windows Update - Re-enable Microsoft managed Windows Update",
"description": "TRMM agent will set registry key to disable Windows Auto Updates. This will re-enable Windows standard update settings",
"shell": "powershell",
"category": "TRMM (Win):Updates",
"default_timeout": "90"
},
{
"guid": "81cc5bcb-01bf-4b0c-89b9-0ac0f3fe0c04",
"filename": "Win_Windows_Update_Reset.ps1",
@@ -63,7 +93,7 @@
"filename": "Win_Start_Cleanup.ps1",
"submittedBy": "https://github.com/Omnicef",
"name": "Disk - Cleanup C: drive",
"description": "Cleans the C: drive's Window Temperary files, Windows SoftwareDistribution folder, the local users Temperary folder, IIS logs (if applicable) and empties the recycling bin. All deleted files will go into a log transcript in $env:TEMP. By default this script leaves files that are newer than 7 days old however this variable can be edited.",
"description": "Cleans the C: drive's Window Temporary files, Windows SoftwareDistribution folder, the local users Temperary folder, IIS logs (if applicable) and empties the recycling bin. All deleted files will go into a log transcript in $env:TEMP. By default this script leaves files that are newer than 7 days old however this variable can be edited.",
"shell": "powershell",
"category": "TRMM (Win):Maintenance",
"default_timeout": "25000"
@@ -143,6 +173,15 @@
"shell": "powershell",
"category": "TRMM (Win):Storage"
},
{
"guid": "11be7136-0416-47b4-a6dd-9776fa857dca",
"filename": "Win_Storage_CheckPools.ps1",
"submittedBy": "https://github.com/wh1te909",
"name": "Storage Pools - Check Health",
"description": "Checks all storage pools for health, returns error 1 if unhealthy",
"shell": "powershell",
"category": "TRMM (Win):Monitoring"
},
{
"guid": "cfa14c28-4dfc-4d4e-95ee-a380652e058d",
"filename": "Win_Bios_Check.ps1",
@@ -184,19 +223,31 @@
"filename": "Win_Screenconnect_GetGUID.ps1",
"submittedBy": "https://github.com/silversword411",
"name": "Screenconnect - Get GUID for client",
"description": "Returns Screenconnect GUID for client - Use with Custom Fields for later use. ",
"description": "Returns Screenconnect GUID for client - Use with Custom Fields for later use.",
"args": [
"{{client.ScreenConnectService}}"
],
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
{
"guid": "bbe5645f-c8d8-4d86-bddd-c8dbea45c974",
"filename": "Win_Splashtop_Get_ID.ps1",
"submittedBy": "https://github.com/r3die",
"name": "Splashtop - Get SUUID for client",
"description": "Returns Splashtop SUUID for client - Use with Custom Fields for later use.",
"args": [
"{{agent.SplashtopSUUID}}"
],
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
{
"guid": "9cfdfe8f-82bf-4081-a59f-576d694f4649",
"filename": "Win_Teamviewer_Get_ID.ps1",
"submittedBy": "https://github.com/silversword411",
"name": "TeamViewer - Get ClientID for client",
"description": "Returns Teamviwer ClientID for client - Use with Custom Fields for later use. ",
"description": "Returns Teamviwer ClientID for client - Use with Custom Fields for later use.",
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
@@ -205,7 +256,7 @@
"filename": "Win_AnyDesk_Get_Anynet_ID.ps1",
"submittedBy": "https://github.com/meuchels",
"name": "AnyDesk - Get AnyNetID for client",
"description": "Returns AnyNetID for client - Use with Custom Fields for later use. ",
"description": "Returns AnyNetID for client - Use with Custom Fields for later use.",
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
@@ -256,12 +307,24 @@
"shell": "powershell",
"category": "TRMM (Win):Hardware"
},
{
"guid": "4ace28ee-98f7-4931-9ac9-0adaf1a757ed",
"filename": "Win_Software_Install_Report.ps1",
"submittedBy": "https://github.com/silversword",
"name": "Software Install - Reports new installs",
"description": "This will check for software install events in the application Event Viewer log. If a number is provided as a command parameter it will search that number of days back.",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Monitoring",
"default_timeout": "90"
},
{
"guid": "907652a5-9ec1-4759-9871-a7743f805ff2",
"filename": "Win_Software_Uninstall.ps1",
"submittedBy": "https://github.com/subzdev",
"name": "Software Uninstaller - list, find, and uninstall most software",
"description": "Allows listing, finding and uninstalling most software on Windows. There will be a best effort to uninstall silently if the silent uninstall string is not provided.",
"syntax": "-list <string>\n[-u <uninstall string>]\n[-u quiet <uninstall string>]",
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software",
"default_timeout": "600"
@@ -272,6 +335,7 @@
"submittedBy": "https://github.com/jhtechIL/",
"name": "BitDefender Gravity Zone Install",
"description": "Installs BitDefender Gravity Zone, requires client custom field setup. See script comments for details",
"syntax": "[-log]",
"args": [
"-url {{client.bdurl}}",
"-exe {{client.bdexe}}"
@@ -280,10 +344,37 @@
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software"
},
{
"guid": "bfd61545-839b-45da-8b3d-75ffc4d43272",
"filename": "Win_Sophos_EndpointProtection_Install.ps1",
"submittedBy": "https://github.com/bc24fl/",
"name": "Sophos Endpoint Protection Install",
"description": "Installs Sophos Endpoint Protection via the Sophos API. Products include Antivirus, InterceptX, MDR, Device Encryption. The script requires API credentials, Custom Fields, and Arguments passed to script. See script comments for details",
"args": [
"-ClientId {{client.SophosClientId}}",
"-ClientSecret {{client.SophosClientSecret}}",
"-TenantName {{client.SophosTenantName}}",
"-Products antivirus,intercept"
],
"default_timeout": "3600",
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software"
},
{
"guid": "a9d2a6c0-8afa-4d69-8faf-f83b49c11702",
"filename": "Win_Printer_Restart_Jobs.ps1",
"submittedBy": "https://github.com/bc24fl/",
"name": "Printers - Restarts stuck printer jobs.",
"description": "Cycles through each printer and restarts any jobs that are stuck with error status.",
"shell": "powershell",
"category": "TRMM (Win):Printing",
"default_timeout": "90"
},
{
"guid": "da51111c-aff6-4d87-9d76-0608e1f67fe5",
"filename": "Win_Defender_Enable.ps1",
"submittedBy": "https://github.com/dinger1986",
"syntax": "[-NoControlledFolders]",
"name": "Defender - Enable",
"description": "Enables Windows Defender and sets preferences",
"shell": "powershell",
@@ -374,6 +465,7 @@
"submittedBy": "https://github.com/dinger1986",
"name": "Defender - Status Report",
"description": "This will check for Malware and Antispyware within the last 24 hours and display, otherwise will report as Healthy. Command Parameter: (number) if provided will check that number of days back in the log.",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Security>Antivirus"
},
@@ -409,6 +501,7 @@
"filename": "Win_Display_Message_To_User.ps1",
"submittedBy": "https://github.com/bradhawkins85",
"name": "Message Popup To User",
"syntax": "<string>",
"description": "Displays a popup message to the currently logged on user",
"shell": "powershell",
"category": "TRMM (Win):Other"
@@ -418,6 +511,7 @@
"filename": "Win_Antivirus_Verify.ps1",
"submittedBy": "https://github.com/beejayzed",
"name": "Antivirus - Verify Status",
"syntax": "[-antivirusName <string>]",
"description": "Verify and display status for all installed Antiviruses",
"shell": "powershell",
"category": "TRMM (Win):Security>Antivirus"
@@ -462,10 +556,11 @@
},
{
"guid": "71090fc4-faa6-460b-adb0-95d7863544e1",
"filename": "Win_Check_Events_for_Bluescreens.ps1",
"submittedBy": "https://github.com/dinger1986",
"filename": "Win_Bluescreen_Report.ps1",
"submittedBy": "https://github.com/bbrendon",
"name": "Event Viewer - Bluescreen Notification",
"description": "Event Viewer Monitor - Notify Bluescreen events on your system",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Monitoring"
},
@@ -474,7 +569,8 @@
"filename": "Win_Local_User_Created_Monitor.ps1",
"submittedBy": "https://github.com/dinger1986",
"name": "Event Viewer - New User Notification",
"description": "Event Viewer Monitor - Notify when new Local user is created",
"description": "Event Viewer Monitor - Notify when new Local user is created. If parameter provided will search back that number of days",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Monitoring"
},
@@ -484,6 +580,7 @@
"submittedBy": "https://github.com/dinger1986",
"name": "Event Viewer - Task Scheduler New Item Notification",
"description": "Event Viewer Monitor - Notify when new Task Scheduler item is created",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Monitoring"
},
@@ -692,6 +789,15 @@
"shell": "powershell",
"category": "TRMM (Win):Security"
},
{
"guid": "43a3206d-f1cb-44ef-8405-aae4d33a0bad",
"filename": "Win_Security_Audit.ps1",
"submittedBy": "theinterwebs",
"name": "Windows Security - Security Audit",
"description": "Runs an Audit on many components of windows to check for security issues",
"shell": "powershell",
"category": "TRMM (Win):Security"
},
{
"guid": "7ea6a11a-05c0-4151-b5c1-cb8af029299f",
"filename": "Win_AzureAD_Check_Connection_Status.ps1",
@@ -756,6 +862,16 @@
"shell": "powershell",
"category": "TRMM (Win):User Management"
},
{
"guid": "6e27d5341-88fa-4c2f-9c91-c3aeb1740e85",
"filename": "Win_User_EnableDisable.ps1",
"submittedBy": "https://github.com/silversword411",
"name": "User - Enable or disable a user",
"description": "Used to enable or disable local user",
"syntax": "-Name <string>\n-Enabled { yes | no }",
"shell": "powershell",
"category": "TRMM (Win):User Management"
},
{
"guid": "57997ec7-b293-4fd5-9f90-a25426d0eb90",
"filename": "Win_Users_List.ps1",
@@ -799,6 +915,7 @@
"submittedBy": "https://github.com/tremor021",
"name": "EXAMPLE File Copying using powershell",
"description": "Reference Script: Will need manual tweaking, for copying files/folders from paths/websites to local",
"syntax": "-source <string>\n-destination <string>\n[-recursive {True | False}]",
"shell": "powershell",
"category": "TRMM (Win):Misc>Reference",
"default_timeout": "1"
@@ -818,6 +935,7 @@
"filename": "Win_AD_Join_Computer.ps1",
"submittedBy": "https://github.com/rfost52",
"name": "AD - Join Computer to Domain",
"syntax": "-domain <string>\n-password <string>\n-UserAccount ADMINaccount\n[-OUPath <OU=testOU,DC=test,DC=local>]",
"description": "Join computer to a domain in Active Directory",
"shell": "powershell",
"category": "TRMM (Win):Active Directory",
@@ -828,6 +946,7 @@
"filename": "Win_Collect_System_Report_And_Email.ps1",
"submittedBy": "https://github.com/rfost52",
"name": "Collect System Report and Email",
"syntax": "-agentname <string>\n-file <string enter file name with the extension .HTM or .HTML>\n-fromaddress <string>\n-toaddress <string>\n-smtpserver <string>\n-password <string>\n-port <int 587 is the standard port for sending mail over TLS>",
"description": "Generates a system report in HTML format, then emails it",
"shell": "powershell",
"category": "TRMM (Win):Other",

View File

@@ -0,0 +1,28 @@
# Generated by Django 3.2.9 on 2021-11-28 16:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scripts', '0014_alter_script_filename'),
]
operations = [
migrations.AddField(
model_name='script',
name='script_body',
field=models.TextField(blank=True, default=''),
),
migrations.AddField(
model_name='script',
name='script_hash',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AlterField(
model_name='script',
name='code_base64',
field=models.TextField(blank=True, default=''),
),
]

View File

@@ -1,5 +1,6 @@
import base64
import re
import hmac
import hashlib
from typing import List
from django.contrib.postgres.fields import ArrayField
@@ -40,7 +41,9 @@ class Script(BaseAuditModel):
syntax = TextField(null=True, blank=True)
favorite = models.BooleanField(default=False)
category = models.CharField(max_length=100, null=True, blank=True)
code_base64 = models.TextField(null=True, blank=True, default="")
script_body = models.TextField(blank=True, default="")
script_hash = models.CharField(max_length=100, null=True, blank=True)
code_base64 = models.TextField(blank=True, default="") # deprecated
default_timeout = models.PositiveIntegerField(default=90)
def __str__(self):
@@ -48,12 +51,7 @@ class Script(BaseAuditModel):
@property
def code_no_snippets(self):
if self.code_base64:
return base64.b64decode(self.code_base64.encode("ascii", "ignore")).decode(
"ascii", "ignore"
)
else:
return ""
return self.script_body if self.script_body else ""
@property
def code(self):
@@ -78,6 +76,15 @@ class Script(BaseAuditModel):
else:
return code
def hash_script_body(self):
from django.conf import settings
msg = self.code.encode()
self.script_hash = hmac.new(
settings.SECRET_KEY.encode(), msg, hashlib.sha256
).hexdigest()
self.save()
@classmethod
def load_community_scripts(cls):
import json
@@ -100,6 +107,9 @@ class Script(BaseAuditModel):
) as f:
info = json.load(f)
# used to remove scripts from DB that are removed from the json file and file system
community_scripts_processed = [] # list of script guids
for script in info:
if os.path.exists(os.path.join(scripts_dir, script["filename"])):
s = cls.objects.filter(script_type="builtin", guid=script["guid"])
@@ -118,91 +128,34 @@ class Script(BaseAuditModel):
syntax = script["syntax"] if "syntax" in script.keys() else ""
# if community script exists update it
if s.exists():
i = s.first()
i.name = script["name"] # type: ignore
i.description = script["description"] # type: ignore
i.category = category # type: ignore
i.shell = script["shell"] # type: ignore
i.default_timeout = default_timeout # type: ignore
i.args = args # type: ignore
i.syntax = syntax # type: ignore
i.filename = script["filename"] # type: ignore
i: Script = s.get()
i.name = script["name"]
i.description = script["description"]
i.category = category
i.shell = script["shell"]
i.default_timeout = default_timeout
i.args = args
i.syntax = syntax
i.filename = script["filename"]
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
script_bytes = (
f.read().decode("utf-8").encode("ascii", "ignore")
)
i.code_base64 = base64.b64encode(script_bytes).decode("ascii") # type: ignore
i.script_body = f.read().decode("utf-8")
# i.hash_script_body()
i.save()
i.save( # type: ignore
update_fields=[
"name",
"description",
"category",
"default_timeout",
"code_base64",
"shell",
"args",
"filename",
"syntax",
]
)
# check if script was added without a guid
elif cls.objects.filter(
script_type="builtin", name=script["name"]
).exists():
s = cls.objects.get(script_type="builtin", name=script["name"])
if not s.guid:
print(f"Updating GUID for: {script['name']}")
s.guid = script["guid"]
s.name = script["name"]
s.description = script["description"]
s.category = category
s.shell = script["shell"]
s.default_timeout = default_timeout
s.args = args
s.filename = script["filename"]
s.syntax = syntax
with open(
os.path.join(scripts_dir, script["filename"]), "rb"
) as f:
script_bytes = (
f.read().decode("utf-8").encode("ascii", "ignore")
)
s.code_base64 = base64.b64encode(script_bytes).decode(
"ascii"
)
s.save(
update_fields=[
"guid",
"name",
"description",
"category",
"default_timeout",
"code_base64",
"shell",
"args",
"filename",
"syntax",
]
)
community_scripts_processed.append(i.guid)
# doesn't exist in database so create it
else:
print(f"Adding new community script: {script['name']}")
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
script_bytes = (
f.read().decode("utf-8").encode("ascii", "ignore")
)
code_base64 = base64.b64encode(script_bytes).decode("ascii")
script_body = f.read().decode("utf-8")
cls(
code_base64=code_base64,
new_script: Script = cls(
script_body=script_body,
guid=script["guid"],
name=script["name"],
description=script["description"],
@@ -213,10 +166,22 @@ class Script(BaseAuditModel):
args=args,
filename=script["filename"],
syntax=syntax,
).save()
)
# new_script.hash_script_body() # also saves script
new_script.save()
# delete community scripts that had their name changed
cls.objects.filter(script_type="builtin", guid=None).delete()
community_scripts_processed.append(new_script.guid)
# check for community scripts that were deleted from json and scripts folder
count, _ = (
Script.objects.filter(script_type="builtin")
.exclude(guid__in=community_scripts_processed)
.delete()
)
if count:
print(
f"Removing {count} community scripts that was removed from source repo"
)
@staticmethod
def serialize(script):

View File

@@ -22,6 +22,8 @@ class ScriptTableSerializer(ModelSerializer):
class ScriptSerializer(ModelSerializer):
script_hash = ReadOnlyField()
class Meta:
model = Script
fields = [
@@ -32,7 +34,8 @@ class ScriptSerializer(ModelSerializer):
"args",
"category",
"favorite",
"code_base64",
"script_body",
"script_hash",
"default_timeout",
"syntax",
"filename",
@@ -41,10 +44,11 @@ class ScriptSerializer(ModelSerializer):
class ScriptCheckSerializer(ModelSerializer):
code = ReadOnlyField()
script_hash = ReadOnlyField
class Meta:
model = Script
fields = ["code", "shell"]
fields = ["code", "shell", "script_hash"]
class ScriptSnippetSerializer(ModelSerializer):

View File

@@ -1,8 +1,12 @@
import json
import os
import hmac
import hashlib
from pathlib import Path
from unittest.mock import patch
from django.test import override_settings
from django.conf import settings
from model_bakery import baker
from tacticalrmm.test import TacticalTestCase
@@ -31,6 +35,7 @@ class TestScriptViews(TacticalTestCase):
self.check_not_authenticated("get", url)
@override_settings(SECRET_KEY="Test Secret Key")
def test_add_script(self):
url = f"/scripts/"
@@ -39,7 +44,7 @@ class TestScriptViews(TacticalTestCase):
"description": "Description",
"shell": "powershell",
"category": "New",
"code_base64": "VGVzdA==", # Test
"script_body": "Test Script",
"default_timeout": 99,
"args": ["hello", "world", r"{{agent.public_ip}}"],
"favorite": False,
@@ -48,11 +53,18 @@ class TestScriptViews(TacticalTestCase):
# test without file upload
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 200)
self.assertTrue(Script.objects.filter(name="Name").exists())
self.assertEqual(Script.objects.get(name="Name").code, "Test")
new_script = Script.objects.filter(name="Name").get()
self.assertTrue(new_script)
# correct_hash = hmac.new(
# settings.SECRET_KEY.encode(), data["script_body"].encode(), hashlib.sha256
# ).hexdigest()
# self.assertEqual(new_script.script_hash, correct_hash)
self.check_not_authenticated("post", url)
@override_settings(SECRET_KEY="Test Secret Key")
def test_modify_script(self):
# test a call where script doesn't exist
resp = self.client.put("/scripts/500/", format="json")
@@ -66,7 +78,7 @@ class TestScriptViews(TacticalTestCase):
"name": script.name,
"description": "Description Change",
"shell": script.shell,
"code_base64": "VGVzdA==", # Test
"script_body": "Test Script Body", # Test
"default_timeout": 13344556,
}
@@ -75,14 +87,17 @@ class TestScriptViews(TacticalTestCase):
self.assertEqual(resp.status_code, 200)
script = Script.objects.get(pk=script.pk)
self.assertEquals(script.description, "Description Change")
self.assertEquals(script.code, "Test")
# correct_hash = hmac.new(
# settings.SECRET_KEY.encode(), data["script_body"].encode(), hashlib.sha256
# ).hexdigest()
# self.assertEqual(script.script_hash, correct_hash)
# test edit a builtin script
data = {
"name": "New Name",
"description": "New Desc",
"code_base64": "VGVzdA==",
"script_body": "aasdfdsf",
} # Test
builtin_script = baker.make_recipe("scripts.script", script_type="builtin")
@@ -94,7 +109,7 @@ class TestScriptViews(TacticalTestCase):
"description": "Description Change",
"shell": script.shell,
"favorite": True,
"code_base64": "VGVzdA==", # Test
"script_body": "Test Script Body", # Test
"default_timeout": 54345,
}
# test marking a builtin script as favorite
@@ -166,29 +181,33 @@ class TestScriptViews(TacticalTestCase):
# test powershell file
script = baker.make(
"scripts.Script", code_base64="VGVzdA==", shell="powershell"
"scripts.Script", script_body="Test Script Body", shell="powershell"
)
url = f"/scripts/{script.pk}/download/" # type: ignore
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, {"filename": f"{script.name}.ps1", "code": "Test"}) # type: ignore
self.assertEqual(resp.data, {"filename": f"{script.name}.ps1", "code": "Test Script Body"}) # type: ignore
# test batch file
script = baker.make("scripts.Script", code_base64="VGVzdA==", shell="cmd")
script = baker.make(
"scripts.Script", script_body="Test Script Body", shell="cmd"
)
url = f"/scripts/{script.pk}/download/" # type: ignore
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, {"filename": f"{script.name}.bat", "code": "Test"}) # type: ignore
self.assertEqual(resp.data, {"filename": f"{script.name}.bat", "code": "Test Script Body"}) # type: ignore
# test python file
script = baker.make("scripts.Script", code_base64="VGVzdA==", shell="python")
script = baker.make(
"scripts.Script", script_body="Test Script Body", shell="python"
)
url = f"/scripts/{script.pk}/download/" # type: ignore
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, {"filename": f"{script.name}.py", "code": "Test"}) # type: ignore
self.assertEqual(resp.data, {"filename": f"{script.name}.py", "code": "Test Script Body"}) # type: ignore
self.check_not_authenticated("get", url)

View File

@@ -1,4 +1,3 @@
import base64
import asyncio
from django.shortcuts import get_object_or_404
@@ -37,6 +36,8 @@ class GetAddScripts(APIView):
serializer.is_valid(raise_exception=True)
obj = serializer.save()
# obj.hash_script_body()
return Response(f"{obj.name} was added!")
@@ -64,6 +65,8 @@ class GetUpdateDeleteScript(APIView):
serializer.is_valid(raise_exception=True)
obj = serializer.save()
# obj.hash_script_body()
return Response(f"{obj.name} was edited!")
def delete(self, request, pk):

View File

@@ -20,8 +20,9 @@ app.accept_content = ["application/json"] # type: ignore
app.result_serializer = "json" # type: ignore
app.task_serializer = "json" # type: ignore
app.conf.task_track_started = True
app.autodiscover_tasks()
app.conf.worker_proc_alive_timeout = 30
app.conf.worker_max_tasks_per_child = 2
app.autodiscover_tasks()
app.conf.beat_schedule = {
"auto-approve-win-updates": {

View File

@@ -21,6 +21,7 @@ EXCLUDE_PATHS = (
f"/{settings.ADMIN_URL}",
"/logout",
"/agents/installer",
"/api/schema",
)

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.10.1"
TRMM_VERSION = "0.10.5"
# bump this version everytime vue code is changed
# to alert user they need to manually refresh their browser
APP_VER = "0.0.152"
APP_VER = "0.0.155"
# https://github.com/wh1te909/rmmagent
LATEST_AGENT_VER = "1.7.0"
LATEST_AGENT_VER = "1.7.2"
MESH_VER = "0.9.51"
MESH_VER = "0.9.61"
NATS_SERVER_VER = "2.3.3"
# for the update script, bump when need to recreate venv or npm install
PIP_VER = "24"
NPM_VER = "25"
PIP_VER = "25"
NPM_VER = "27"
SETUPTOOLS_VER = "58.5.3"
SETUPTOOLS_VER = "59.6.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"

View File

@@ -1,7 +1,6 @@
from django.conf import settings
from django.urls import include, path, register_converter
from knox import views as knox_views
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from accounts.views import CheckCreds, LoginView
from core.consumers import DashInfo
@@ -38,19 +37,25 @@ urlpatterns = [
path("scripts/", include("scripts.urls")),
path("alerts/", include("alerts.urls")),
path("accounts/", include("accounts.urls")),
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path(
"api/schema/swagger-ui/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
]
if hasattr(settings, "ADMIN_ENABLED") and settings.ADMIN_ENABLED:
if getattr(settings, "ADMIN_ENABLED", False):
from django.contrib import admin
urlpatterns += (path(settings.ADMIN_URL, admin.site.urls),)
if getattr(settings, "SWAGGER_ENABLED", False):
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns += (
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path(
"api/schema/swagger-ui/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
)
ws_urlpatterns = [
path("ws/dashinfo/", DashInfo.as_asgi()), # type: ignore
]

View File

@@ -299,7 +299,8 @@ def replace_db_values(
if not obj:
return ""
if hasattr(obj, temp[1]):
# check if attr exists and isn't a function
if hasattr(obj, temp[1]) and not callable(getattr(obj, temp[1])):
value = f"'{getattr(obj, temp[1])}'" if quotes else getattr(obj, temp[1])
elif CustomField.objects.filter(model=model, name=temp[1]).exists():

View File

@@ -1,6 +1,6 @@
#!/bin/bash
SCRIPT_VERSION="16"
SCRIPT_VERSION="17"
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/backup.sh'
GREEN='\033[0;32m'
@@ -89,4 +89,4 @@ tar -cf /rmmbackups/rmm-backup-${dt_now}.tar -C ${tmp_dir} .
rm -rf ${tmp_dir}
echo -ne "${GREEN}Backup saved to /rmmbackups/rmm-backup-${dt_now}.tar${NC}\n"
echo -ne "${GREEN}Backup saved to /rmmbackups/rmm-backup-${dt_now}.tar${NC}\n"

View File

@@ -131,6 +131,7 @@ EOF
python manage.py reload_nats
python manage.py create_natsapi_conf
python manage.py create_installer_user
python manage.py post_update_tasks
# create super user
echo "from accounts.models import User; User.objects.create_superuser('${TRMM_USER}', 'admin@example.com', '${TRMM_PASS}') if not User.objects.filter(username='${TRMM_USER}').exists() else 0;" | python manage.py shell
@@ -168,4 +169,4 @@ if [ "$1" = 'tactical-websockets' ]; then
export DJANGO_SETTINGS_MODULE=tacticalrmm.settings
daphne tacticalrmm.asgi:application --port 8383 -b 0.0.0.0
fi
fi

View File

@@ -8,17 +8,16 @@ networks:
driver: default
config:
- subnet: 172.20.0.0/24
api-db:
redis:
mesh-db:
api-db: null
redis: null
mesh-db: null # docker managed persistent volumes
# docker managed persistent volumes
volumes:
tactical_data:
postgres_data:
mongo_data:
mesh_data:
redis_data:
tactical_data: null
postgres_data: null
mongo_data: null
mesh_data: null
redis_data: null
services:
# postgres database for api service
@@ -41,7 +40,7 @@ services:
image: redis:6.0-alpine
command: redis-server --appendonly yes
restart: always
volumes:
volumes:
- redis_data:/data
networks:
- redis
@@ -51,7 +50,7 @@ services:
container_name: trmm-init
image: ${IMAGE_REPO}tactical:${VERSION}
restart: on-failure
command: ["tactical-init"]
command: [ "tactical-init" ]
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASS: ${POSTGRES_PASS}
@@ -63,13 +62,13 @@ services:
TRMM_PASS: ${TRMM_PASS}
depends_on:
- tactical-postgres
- tactical-meshcentral
- tactical-meshcentral
networks:
- api-db
- proxy
volumes:
- tactical_data:/opt/tactical
# nats
tactical-nats:
container_name: trmm-nats
@@ -82,6 +81,7 @@ services:
volumes:
- tactical_data:/opt/tactical
networks:
api-db: null
proxy:
aliases:
- ${API_HOST}
@@ -91,7 +91,7 @@ services:
container_name: trmm-meshcentral
image: ${IMAGE_REPO}tactical-meshcentral:${VERSION}
restart: always
environment:
environment:
MESH_HOST: ${MESH_HOST}
MESH_USER: ${MESH_USER}
MESH_PASS: ${MESH_PASS}
@@ -102,7 +102,7 @@ services:
proxy:
aliases:
- ${MESH_HOST}
mesh-db:
mesh-db: null
volumes:
- tactical_data:/opt/tactical
- mesh_data:/home/node/app/meshcentral-data
@@ -137,7 +137,7 @@ services:
tactical-backend:
container_name: trmm-backend
image: ${IMAGE_REPO}tactical:${VERSION}
command: ["tactical-backend"]
command: [ "tactical-backend" ]
restart: always
networks:
- proxy
@@ -152,7 +152,7 @@ services:
tactical-websockets:
container_name: trmm-websockets
image: ${IMAGE_REPO}tactical:${VERSION}
command: ["tactical-websockets"]
command: [ "tactical-websockets" ]
restart: always
networks:
- proxy
@@ -188,7 +188,7 @@ services:
tactical-celery:
container_name: trmm-celery
image: ${IMAGE_REPO}tactical:${VERSION}
command: ["tactical-celery"]
command: [ "tactical-celery" ]
restart: always
networks:
- redis
@@ -204,7 +204,7 @@ services:
tactical-celerybeat:
container_name: trmm-celerybeat
image: ${IMAGE_REPO}tactical:${VERSION}
command: ["tactical-celerybeat"]
command: [ "tactical-celerybeat" ]
restart: always
networks:
- proxy

View File

@@ -61,6 +61,12 @@ It should ask you to sign into your Connectwise Control server if you are not al
*****
## Install Screenconnect via Tactical
Use the [Screenconnect AIO script](https://github.com/wh1te909/tacticalrmm/blob/develop/scripts/Win_ScreenConnectAIO.ps1)
![AIO](images/3rdparty_sc_aio.png)
## Install Tactical RMM via Screeconnect commands window
1. Create a Deplopment under **Agents > Manage Deployments**

View File

@@ -0,0 +1,42 @@
# Splashtop
## Splashtop Integration
From the UI go to **Settings > Global Settings > CUSTOM FIELDS > Agents**
Add Custom Field</br>
**Target** = `Agent`</br>
**Name** = `SplashtopSUUID`</br>
**Field Type** = `Text`</br>
![Service Name](images/3rdparty_splashtop1.png)
While in Global Settings go to **URL ACTIONS**
Add a URL Action</br>
**Name** = `Splashtop`</br>
**Description** = `Connect to a Splashtop client`</br>
**URL Pattern** =
```html
st-business://com.splashtop.business?account=&uuid={{agent.SplashtopSUUID}}&sessiontype=remote
```
Navigate to an agent with Splashtop running (or apply using **Settings > Automation Manager**).</br>
Go to Tasks.</br>
Add Task</br>
**Select Script** = `Splashtop - Get SUUID for client` (this is a builtin script from script library)</br>
**Descriptive name of task** = `Obtain Splashtop SUUID from device registry.`</br>
**Collector Task** = `CHECKED`</br>
**Custom Field to update** = `SplashtopSUUID`</br>
![Service Name](images/3rdparty_splashtop2.png)
Click **Next**</br>
Check **Manual**</br>
Click **Add Task**
Right click on the newly created task and click **Run Task Now**.
Give it a second to execute then right click the agent that you are working with and go to **Run URL Action > Splashtop**

View File

@@ -27,3 +27,21 @@ chmod +x backup.sh
The backup tar file will be saved in `/rmmbackups` with the following format:
`rmm-backup-CURRENTDATETIME.tar`
# Schedule to run daily via cron
Make a symlink in `/etc/cron.d` (daily cron jobs) with these contents `00 18 * * * tactical /rmm/backup.sh` to run at 6pm daily.
```bash
echo -e "\n" >> /rmm/backup.sh
sudo ln -s /rmm/backup.sh /etc/cron.daily/
```
!!!warning
Currently the backup script doesn't have any pruning functions so the folder will grow forever without periodic cleanup
# Video Walkthru
<div class="video-wrapper">
<iframe width="320" height="180" src="https://www.youtube.com/embed/rC0NgYJUf_8" frameborder="0" allowfullscreen></iframe>
</div>

View File

@@ -4,7 +4,7 @@
Tactical RMM agents are now [code signed](https://comodosslstore.com/resources/what-is-microsoft-authenticode-code-signing-certificate/)!
To get access to code signed agents, you must be a [Github Sponsor](https://github.com/sponsors/wh1te909) with a minumum monthly donation of $50.00
To get access to code signed agents, you must be a [Github Sponsor](https://github.com/sponsors/wh1te909) with a minumum **monthly** donation of $50.00. If you signup for the $50, and then downgrade your auth token _**will be**_ invalidated and stop working.
Once you have become a sponsor, please email **support@amidaware.com** with your Github username (and Discord username if you're on our [Discord](https://discord.gg/upGTkWp))

View File

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

View File

@@ -78,6 +78,12 @@ mkdocs is Exposed on Port: 8005
Open: [http://rmm.example.com:8005/](http://rmm.example.com:8005/)
!!!note
If you add new mkdocs extensions you might need to:<br>
- docker-compose down.<br>
- Then delete the `/api/tacticalrmm/env/` folder.<br>
- Then docker-compose up and it will download/rebuild new extensions
### View django administration
Open: [http://rmm.example.com:8000/admin/](http://rmm.example.com:8000/admin/)

View File

@@ -1,5 +1,9 @@
# FAQ
## Is Tactical RMM vulnerable to Log4j
No
## 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**.

View File

@@ -0,0 +1,25 @@
# TLRD Version
## At Install
Setup Email Alerts
Setup SMS Alerts
Setup Server Preferences
General
Time Zone
Clear faults on agents that haven't checked in after (days)
Setup Automation Manager
Default Profile for workstations
## Every 75 days
OS updates
reboot
Backup
TRMM Update
## Biannually

View File

@@ -1,10 +1,12 @@
# How It All Works
![Network Design](images/TacticalRMM-Network.png)
[![Network Design](images/TacticalRMM-Network.png)](images/TacticalRMM-Network.png)
1. Agent installer steps
Still need graphics for
2. Agent checks/tasks and how they work on the workstation/interact with server
1. Agent installer steps
2. Agent checks/tasks and how they work on the workstation/interact with server
## Server
@@ -15,41 +17,308 @@ Has a postgres database located here:
!!!description
A web interface for the postgres database
### Services
All Tactical RMM dependencies are listed [here](https://github.com/wh1te909/tacticalrmm/blob/develop/api/tacticalrmm/requirements.txt)
nginx
### System Services
!!!description
Web server that handles https traffic
This lists the system services used by the server.
Log located at `/var/log/nginx`
#### nginx web server
```bash
tail /var/log/nginx
```
Nginx is the web server for the `rmm`, `api`, and `mesh` domains. All sites redirect port 80 (HTTP) to port 443 (HTTPS).
### Dependencies from [here](https://github.com/wh1te909/tacticalrmm/blob/develop/api/tacticalrmm/requirements.txt)
!!! warning
[nats](https://nats.io/)
nginx does not serve the NATS service on port 4222.
How communication between client and server bride NAT (Network Address Translation)
???+ abstract "nginx configuration (a.k.a. sites available)"
[celery](https://github.com/celery/celery)
- [nginx configuration docs](https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/)
!!!description
Used to schedule tasks to be sent to Agent
=== ":material-web: `rmm.example.com`"
This serves the frontend website that you intereact with.
- Config: `/etc/nginx/sites-enabled/frontend.conf`
- root: `/var/www/rmm/dist`
- Access log: `/var/log/nginx/frontend-access.log`
- Error log: `/var/log/nginx/frontend-error.log`
- TLS certificate: `/etc/letsencrypt/live/example.com/fullchain.pem`
=== ":material-web: `api.example.com`"
This serves the TRMM API for the frontend and agents.
- Config: `/etc/nginx/sites-enabled/rmm.conf`
- roots:
- `/rmm/api/tacticalrmm/static/`
- `/rmm/api/tacticalrmm/tacticalrmm/private/`
- Upstreams:
- `unix://rmm/api/tacticalrmm/tacticalrmm.sock`
- `unix://rmm/daphne.sock`
- Access log: `/rmm/api/tacticalrmm/tacticalrmm/private/log/access.log`
- Error log: `/rmm/api/tacticalrmm/tacticalrmm/private/log/error.log`
- TLS certificate: `/etc/letsencrypt/live/example.com/fullchain.pem`
=== ":material-web: `mesh.example.com`"
This serves MeshCentral for remote access.
- Config: `/etc/nginx/sites-enabled/meshcentral.conf`
- Upstream: `http://127.0.0.1:4430/`
- Access log: `/var/log/nginx/access.log` (uses deafult)
- Error log: `/var/log/nginx/error.log` (uses deafult)
- TLS certificate: `/etc/letsencrypt/live/example.com/fullchain.pem`
=== ":material-web: default"
This is the default site installed with nginx. This listens on port 80 only.
- Config: `/etc/nginx/sites-enabled/default`
- root: `/var/www/rmm/dist`
- Access log: `/var/log/nginx/access.log` (uses deafult)
- Error log: `/var/log/nginx/error.log` (uses deafult)
???+ note "systemd config"
=== ":material-console-line: status commands"
- Status: `systemctl status --full nginx.service`
- Stop: `systemctl stop nginx.service`
- Start: `systemctl start nginx.service`
- Restart: `systemctl restart nginx.service`
- Restart: `systemctl reload nginx.service` reloads the config without restarting
- Test config: `nginx -t`
- Listening process: `ss -tulnp | grep nginx`
=== ":material-ubuntu: standard"
- Service: `nginx.service`
- Address: `0.0.0.0`
- Port: 443
- Exec: `/usr/sbin/nginx -g 'daemon on; master_process on;'`
- Version: 1.18.0
=== ":material-docker: docker"
TBD - To Be Documented
#### Tactical RMM (Django uWSGI) service
Built on the Django framework, the Tactical RMM service is the heart of system by serving the API for the frontend and agents.
???+ note "systemd config"
- [uWSGI docs](https://uwsgi-docs.readthedocs.io/en/latest/index.html)
=== ":material-console-line: status commands"
- Status: `systemctl status --full rmm.service`
- Stop: `systemctl stop rmm.service`
- Start: `systemctl start rmm.service`
- Restart: `systemctl restart rmm.service`
- journalctl:
- "tail" the logs: `journalctl --identifier uwsgi --follow`
- View the logs: `journalctl --identifier uwsgi --since "30 minutes ago" | less`
=== ":material-ubuntu: standard"
- Service: `rmm.service`
- Socket: `/rmm/api/tacticalrmm/tacticalrmm.sock`
- uWSGI config: `/rmm/api/tacticalrmm/app.ini`
- Log: None
- Journal identifier: `uwsgi`
- Version: 2.0.18
=== ":material-docker: docker"
TBD - To Be Documented
#### Daphne: Django channels daemon
[Daphne](https://github.com/django/daphne) is the official ASGI HTTP/WebSocket server maintained by the [Channels project](https://channels.readthedocs.io/en/stable/index.html).
???+ note "systemd config"
- Django [Channels configuration docs](https://channels.readthedocs.io/en/stable/topics/channel_layers.html)
=== ":material-console-line: status commands"
- Status: `systemctl status --full daphne.service`
- Stop: `systemctl stop daphne.service`
- Start: `systemctl start daphne.service`
- Restart: `systemctl restart daphne.service`
- journalctl (this provides only system start/stop logs, not the actual logs):
- "tail" the logs: `journalctl --identifier daphne --follow`
- View the logs: `journalctl --identifier daphne --since "30 minutes ago" | less`
=== ":material-ubuntu: standard"
- Service: `daphne.service`
- Socket: `/rmm/daphne.sock`
- Exec: `/rmm/api/env/bin/daphne -u /rmm/daphne.sock tacticalrmm.asgi:application`
- Config: `/rmm/api/tacticalrmm/tacticalrmm/local_settings.py`
- Log: `/rmm/api/tacticalrmm/tacticalrmm/private/log/debug.log`
=== ":material-docker: docker"
TBD - To Be Documented
#### NATS server service
[NATS](https://nats.io/) is a messaging bus for "live" communication between the agent and server. NATS provides the framework for the server to push commands to the agent and receive information back.
???+ note "systemd config"
- [NATS server configuration docs](https://docs.nats.io/running-a-nats-service/configuration)
=== ":material-console-line: status commands"
- Status: `systemctl status --full nats.service`
- Stop: `systemctl stop nats.service`
- Start: `systemctl start nats.service`
- Restart: `systemctl restart nats.service`
- Restart: `systemctl reload nats.service` reloads the config without restarting
- journalctl:
- "tail" the logs: `journalctl --identifier nats-server --follow`
- View the logs: `journalctl --identifier nats-server --since "30 minutes ago" | less`
- Listening process: `ss -tulnp | grep nats-server`
=== ":material-ubuntu: standard"
- Service: `nats.service`
- Address: `0.0.0.0`
- Port: `4222`
- Exec: `/usr/local/bin/nats-server --config /rmm/api/tacticalrmm/nats-rmm.conf`
- Config: `/rmm/api/tacticalrmm/nats-rmm.conf`
- TLS: `/etc/letsencrypt/live/example.com/fullchain.pem`
- Log: None
- Version: v2.3.3
=== ":material-docker: docker"
- Get into bash in your docker with: `sudo docker-compose exec tactical-nats /bin/bash`
- Log: `nats-api -log debug`
#### NATS API service
The NATS API service appears to bridge the connection between the NATS server and database, allowing the agent to save (i.e. push) information in the database.
???+ note "systemd config"
=== ":material-console-line: status commands"
- Status: `systemctl status --full nats-api.service`
- Stop: `systemctl stop nats-api.service`
- Start: `systemctl start nats-api.service`
- Restart: `systemctl restart nats-api.service`
- journalctl: This application does not appear to log anything.
=== ":material-ubuntu: standard"
- Service: `nats-api.service`
- Exec: `/usr/local/bin/nats-api --config /rmm/api/tacticalrmm/nats-api.conf`
- Config: `/rmm/api/tacticalrmm/nats-api.conf`
- TLS: `/etc/letsencrypt/live/example.com/fullchain.pem`
- Log: None
=== ":material-docker: docker"
TBD - To Be Documented
#### Celery service
[Celery](https://github.com/celery/celery) is a task queue focused on real-time processing and is responsible for scheduling tasks to be sent to agents.
Log located at `/var/log/celery`
```bash
tail /var/log/celery
```
???+ note "systemd config"
[Django](https://www.djangoproject.com/)
- [Celery docs](https://docs.celeryproject.org/en/stable/index.html)
- [Celery configuration docs](https://docs.celeryproject.org/en/stable/userguide/configuration.html)
!!!description
Framework to integrate the server to interact with browser
=== ":material-console-line: status commands"
- Status: `systemctl status --full celery.service`
- Stop: `systemctl stop celery.service`
- Start: `systemctl start celery.service`
- Restart: `systemctl restart celery.service`
- journalctl: Celery executes `sh` causing the systemd identifier to be `sh`, thus mixing the `celery` and `celerybeat` logs together.
- "tail" the logs: `journalctl --identifier sh --follow`
- View the logs: `journalctl --identifier sh --since "30 minutes ago" | less`
- Tail logs: `tail -F /var/log/celery/w*-*.log`
=== ":material-ubuntu: standard"
- Service: `celery.service`
- Exec: `/bin/sh -c '${CELERY_BIN} -A $CELERY_APP multi start $CELERYD_NODES --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel="${CELERYD_LOG_LEVEL}" $CELERYD_OPTS'`
- Config: `/etc/conf.d/celery.conf`
- Log: `/var/log/celery/w*-*.log`
=== ":material-docker: docker"
TBD - To Be Documented
#### Celery Beat service
[celery beat](https://github.com/celery/django-celery-beat) is a scheduler; It kicks off tasks at regular intervals, that are then executed by available worker nodes in the cluster.
???+ note "systemd config"
- [Celery beat docs](https://docs.celeryproject.org/en/stable/userguide/periodic-tasks.html)
=== ":material-console-line: status commands"
- Status: `systemctl status --full celerybeat.service`
- Stop: `systemctl stop celerybeat.service`
- Start: `systemctl start celerybeat.service`
- Restart: `systemctl restart celerybeat.service`
- journalctl: Celery executes `sh` causing the systemd identifier to be `sh`, thus mixing the `celery` and `celerybeat` logs together.
- "tail" the logs: `journalctl --identifier sh --follow`
- View the logs: `journalctl --identifier sh --since "30 minutes ago" | less`
- Tail logs: `tail -F /var/log/celery/beat.log`
=== ":material-ubuntu: standard"
- Service: `celerybeat.service`
- Exec: `/bin/sh -c '${CELERY_BIN} -A ${CELERY_APP} beat --pidfile=${CELERYBEAT_PID_FILE} --logfile=${CELERYBEAT_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'`
- Config: `/etc/conf.d/celery.conf`
- Log: `/var/log/celery/beat.log`
=== ":material-docker: docker"
TBD - To Be Documented
#### MeshCentral
[MeshCentral](https://github.com/Ylianst/MeshCentral) is used for: "Take Control" (connecting to machine for remote access), and 2 screens of the "Remote Background" (Terminal, and File Browser).
???+ note "meshcentral"
- [MeshCentral docs](https://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2UserGuide.pdf)
=== ":material-console-line: status commands"
- Status: `systemctl status --full meshcentral`
- Stop: `systemctl stop meshcentral`
- Start: `systemctl start meshcentral`
- Restart: `systemctl restart meshcentral`
=== ":material-remote-desktop: Debugging"
- Open either "Take Control" or "Remote Background" to get mesh login token
- Open https://mesh.example.com to open native mesh admin interface
- Left-side "My Server" > Choose "Console" > type `agentstats`
- To view detailed logging goto "Trace" > click Tracing button and choose categories
### Other Dependencies
[Django](https://www.djangoproject.com/) - Framework to integrate the server to interact with browser.
<details>
<summary>Django dependencies</summary>
```text
future==0.18.2
loguru==0.5.3
msgpack==1.0.2
@@ -60,33 +329,58 @@ pycryptodome==3.10.1
pyotp==2.6.0
pyparsing==2.4.7
pytz==2021.1
```
</details>
[qrcode](https://pypi.org/project/qrcode/)
[qrcode](https://pypi.org/project/qrcode/) - Creating QR codes for 2FA.
!!!description
For creating QR codes for 2FA
<details>
<summary>qrcode dependencies</summary>
```text
redis==3.5.3
requests==2.25.1
six==1.16.0
sqlparse==0.4.1
```
</details>
[twilio](https://www.twilio.com/)
[twilio](https://www.twilio.com/) - Python SMS notification integration.
!!!description
Python SMS notification integration
<details>
<summary>twilio dependencies</summary>
```text
urllib3==1.26.5
uWSGI==2.0.19.1
validators==0.18.2
vine==5.0.0
websockets==9.1
zipp==3.4.1
```
</details>
## Windows Agent
Found in `%programfiles%\TacticalAgent`
### Outbound Firewall Rules
If you have strict firewall rules these are the only outbound rules from the agent needed for all functionality:
1. All agents have to be able to connect outbound to TRMM server on the 3 domain names on ports: 443 (agent and mesh) and 4222 (nats for checks/tasks/data)
2. The agent uses `https://icanhazip.tacticalrmm.io/` to get public IP info. If this site is down for whatever reason, the agent will fallback to `https://icanhazip.com` and then `https://ifconfig.co/ip`
#### Unsigned Agents
Unsigned agents require access to: `https://github.com/wh1te909/rmmagent/releases/*`
#### Signed Agents
Signed agents will require: `https://exe.tacticalrmm.io/` and `https://exe2.tacticalrmm.io/` for downloading/updating agents
### Services
3 services exist on all clients
@@ -101,7 +395,7 @@ Found in `%programfiles%\TacticalAgent`
![TacticalAgentServices](images/trmm_services.png)
![TacticalAgentTaskManager](images/trmm_services__taskmanager_agent.png)
The [MeshCentral](https://meshcentral.com/) system which is accessible from <https://mesh.example.com> and is used
The [MeshCentral](https://meshcentral.com/) system which is accessible from `https://mesh.example.com` and is used
* It runs 2 goroutines
* one is the checkrunner which runs all the checks and then just sleeps until it's time to run more checks

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -93,18 +93,45 @@ If you want to deploy the TRMM agent using AD, intune, mesh, teamviewer, Group P
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
@echo off
REM Setup deployment URL
set "DeploymentURL="
set "Name="
for /f "usebackq tokens=* delims=" %%# in (
`wmic service where "name like 'tacticalagent'" get Name /Format:Value`
) do (
for /f "tokens=* delims=" %%g in ("%%#") do set "%%g"
)
if not defined Name (
echo Tactical RMM not found, installing now.
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 "%DeploymentURL%" -Outfile tactical.exe
REM"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT
tactical.exe
powershell Remove-MpPreference -ExclusionPath C:\TEMP\TRMM
rem exit /b 1
) else (
echo Tactical RMM already installed Exiting
Exit 0
)
```
There is also a full powershell version [here](https://wh1te909.github.io/tacticalrmm/3rdparty_screenconnect/#install-tactical-rmm-via-screeconnect-commands-window)
## Script for full agent uninstall
You can always use this to silently uninstall agent on workstations
```cmd
"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT
```

View File

@@ -2,7 +2,7 @@
There's pluses and minuses to each install type. Be aware that:
- There is no migration script, once you've installed with one type there is no "conversion". You'll be installing a new server and migrating agents if you decide to go another way.
- There is no migration script, once you've installed with one type there is no "conversion". You'll be installing a new server and migrating agents manually if you decide to go another way.
## Traditional Install
@@ -13,5 +13,5 @@ There's pluses and minuses to each install type. Be aware that:
- Docker is more complicated in concept: has volumes and images
- If you're running multiple apps it uses less resources in the long run because you only have one OS base files underlying many Containers/Apps
- Backup/restore is by via Docker methods only
- Backup/restore is via Docker methods only
- Docker has container replication/mirroring options for redundancy/multiple servers

View File

@@ -9,7 +9,7 @@ Install docker
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`
The RMM uses 3 different sites. The Vue frontend e.g. `rmm.example.com` which is where you'll be accessing 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.
@@ -34,7 +34,7 @@ We're using the [DNS-01 challenge method](https://letsencrypt.org/docs/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/>
TXT records can take anywhere from 1 minute to a few hours to propagate 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`

View File

@@ -6,7 +6,7 @@
#### Hardware / OS
A fresh linux VM running either Ubuntu 20.04 LTS or Debian 10 with 3GB RAM
A fresh linux VM running either Ubuntu 20.04 LTS or Debian 10/11 with 2GB 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.
@@ -14,6 +14,10 @@ A fresh linux VM running either Ubuntu 20.04 LTS or Debian 10 with 3GB RAM
!!!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.
!!!note
CPU: 1 core is fine for < 200 agents with limited checks/tasks.<br><br>
Disk space and speed are dependent on your use case. Of course faster is better SSD/NVMe. Space is dependent on how long you're keeping historical data, and how many checks/script runs and their output size. 50GB should be fine for < 12months of history on < 200 agents with < 30 checks/tasks run at reasonable time intervals.
#### Network Requirements
- 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/)_
@@ -32,7 +36,7 @@ Install on a VPS: DigitalOcean, Linode, Vultr, BuyVM (highly recommended), Hetzn
Use something that meets [minimum specs](install_server.md#hardware-os)
### Run updates and setup the linux user
### Run Updates on OS
SSH into the server as **root**.
@@ -46,6 +50,8 @@ apt -y upgrade
If a new kernel is installed, then reboot the server with the `reboot` command
### Create a linux user
Create a linux user named `tactical` to run the rmm and add it to the sudoers group.
**For Ubuntu**:
@@ -63,7 +69,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)
[Enable passwordless sudo to make your life easier in the future](https://linuxconfig.org/configure-sudo-without-password-on-ubuntu-20-04-focal-fossa-linux)
### Setup the firewall (optional but highly recommended)
@@ -98,12 +104,15 @@ Enable and activate the firewall
ufw enable && ufw reload
```
!!!note
You will never login to the server again as `root` again unless something has gone horribly wrong, and you're working with the developers.
### 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`
The RMM uses 3 different sites. The Vue frontend e.g. `rmm.example.com` which is where you'll be accessing 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.
@@ -134,7 +143,7 @@ Answer the initial questions when prompted. Replace `example.com` with your doma
### 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/>
TXT records can take anywhere from 1 minute to a few hours to propagate 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`
@@ -181,7 +190,7 @@ If you have agents outside your local network: Make sure the public DNS servers
Login to your router/NAT device.
1. Set your TRMM server as a static IP (Use a DHCP reservation is usually safer)
1. Set your TRMM server as a static IP (Using 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

View File

@@ -7,6 +7,32 @@ cd /rmm/api/tacticalrmm
source ../env/bin/activate
```
or docker version:
```bash
docker exec -it trmm-backend /bin/bash
/opt/venv/bin/python /opt/tactical/api/manage.py shell
```
!!!tip
The Dev Docker version it would be `docker exec -it trmm-api-dev env/bin/python manage.py shell`
## Bulk Delete old agents by last checkin date or agent version
Test to see what will happen
```bash
python manage.py bulk_delete_agents --days 60
python manage.py bulk_delete_agents --agentver 1.5.0
```
Do the delete
```bash
python manage.py bulk_delete_agents --days 60 --delete
python manage.py bulk_delete_agents --agentver 1.5.0 --delete
```
## Reset a user's password
```bash
@@ -25,6 +51,13 @@ python manage.py reset_2fa <username>
python manage.py find_software "adobe"
```
## Set specific Windows update to not install
```bash
from winupdate.models import WinUpdate
WinUpdate.objects.filter(kb="KB5007186").update(action="ignore", date_installed=None)
```
## Show outdated online agents
```bash

View File

@@ -8,13 +8,79 @@
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
## Install the new server
Create the same exact linux user account as you did when you installed the original server.
### Run Updates on OS
Add it to the sudoers group and setup the firewall.
SSH into the server as **root**.
Refer to the [installation instructions](install_server.md) for steps on how to do all of the above.
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
### Create a linux user
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
```
!!!tip
[Enable passwordless sudo to make your life easier in the future](https://linuxconfig.org/configure-sudo-without-password-on-ubuntu-20-04-focal-fossa-linux)
### 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).
```bash
ufw default deny incoming
ufw default allow outgoing
ufw allow https
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
```
!!!note
You will never login to the server again as `root` again unless something has gone horribly wrong, and you're working with the developers.
## Change DNS A records
@@ -24,16 +90,16 @@ Change the 3 A records `rmm`, `api` and `mesh` and point them to the public IP o
## Run the restore script
Copy the backup tar file you created during [backup](backup.md) to the new server.
1. Make sure you're logged in with the non-root user (eg `tactical`)
Download the restore script.
2. Copy the backup tar file you created during [backup](backup.md) to the new server.
```bash
wget https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh
chmod +x restore.sh
```
3. Download the restore script.
Call the restore script, passing it the backup file as the first argument:
wget https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh
chmod +x restore.sh
4. Call the restore script, passing it the backup file as the first argument:
```bash
./restore.sh rmm-backup-XXXXXXXXX.tar

View File

@@ -1,5 +1,11 @@
# Tips and Tricks
## Server Monitoring
Monitor Network usage: <https://humdi.net/vnstat/>
Realtime Everything Usage: (_only run when needed because it uses a lot of resources_): <https://learn.netdata.cloud/docs/agent/packaging/installer/methods/kickstart>
## Customize User Interface
At the top right of your web administration interface, click your Username > preferences. Set default tab: Servers|Workstations|Mixed
@@ -8,10 +14,19 @@ At the top right of your web administration interface, click your Username > pre
*****
## Use the filters in the agent list
![User Preferences](images/tipsntricks_filters.png)
*****
## 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.
Want to download multiple files?
> ZIP zip's the currently selected file(s) and saves it in the current directory. Then you can download the ZIP. It doesn't download and ZIP on the fly.
### Adjust Settings
Right-click the connect button in *Remote Background | Terminal* for shell options
@@ -25,7 +40,7 @@ Right-click the connect button in *Take Control* for connect options
### Enable Remote Control options
!!!note
These settings are independant of Tactical RMM. Enable features (like auto remove inactive devices) with caution
These settings are independent of Tactical RMM. Enable features (like auto remove inactive devices) with caution
1. Remote background a machine then go to mesh.EXAMPLE.COM
2. Click on My Account
@@ -34,6 +49,20 @@ Right-click the connect button in *Take Control* for connect options
![Features](images/mesh_userconsent.png)
5. You can also change features by ticking whatever boxes you want in there (Features: Sync server device name to hostname, Automatically remove inactive devices, Notify/Prompt for Consent/Connection Toolbar settings)<br>
![Features](images/mesh_features.png)
6. Ok your way out
### Agent online/offline logs
In mesh from the agent | General Tab
![online](images/mesh_agent_onlineoffline.png)
## Scripts
### When Running Scripts
Use the (i) at the end of the script name to:
- Hover: see script parameter syntax help
- Left Click: Opens the script source in Github
![Script Parameter Syntax](images/tipsntricks_script_syntaxhelp.png)

View File

@@ -63,9 +63,44 @@ If you have agents that are relatively old, you will need to uninstall them manu
## Agents not checking in or showing up / General agent issues
These are nats problems. Try quickfix first:
### from Admin Web Interface
First, reload NATS from tactical's web UI:<br>
*Tools > Server Maintenance > Reload Nats Configuration*
If that doesn't work, check each part starting with the server:
### Server SSH login
Reload NATS:
```bash
/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py reload_nats
sudo systemctl restart nats
```
Look at nats service errors (make sure it's running)
```bash
sudo systemctl status nats
```
If nats isn't running see detailed reason why it isn't:
```bash
sudo systemctl stop nats
nats-server -DVV -c /rmm/api/tacticalrmm/nats-rmm.conf
```
Fix the problem, then restart nats.
```
sudo systemctl restart nats
```
### From Agent Install
Open CMD as admin on the problem computer and stop the agent services:
```cmd

View File

@@ -869,3 +869,71 @@ Limit access to Tactical RMM's administration panel in nginx to specific locatio
server_name rmm.example.com;
return 404;
}
## Apache Proxy
howto - proxy on apache
### TRMM SERVER
edit file /etc/nginx/sites-available/rmm.conf
add the lines from 'real_ip' module inside server tag:
set_real_ip_from 192.168.0.200; #IP Address of your apache proxy
real_ip_header X-Forwarded-For;
restart nginx
systemctl restart nginx
### APACHE
enable ssl proxy, rewriteEngine.
set proxy to preserve host.
set upgrade rule to websocket.
set proxypass rules redirecting to rmm location
on your apache ssl config
example:
<VirtualHost *:443>
ServerName rmm.blablabla.com.br:443
ServerAlias mesh.blablabla.com.br:443 api.blablabla.com.br:443
SSLEngine on
SSLCertificateFile "C:/Apache24/conf/ssl-rmm.blablabla.com.br/_.blablabla.com.br-chain.pem"
SSLCertificateKeyFile "C:/Apache24/conf/ssl-rmm.blablabla.com.br/_.blablabla.com.br-key.pem"
SSLProxyEngine on
RewriteEngine On
ProxyPreserveHost On
# When Upgrade:websocket header is present, redirect to ws
# Using NC flag (case-insensitive) as some browsers will pass Websocket
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^/(.*) wss://192.168.0.212/$1 [P,L]
ProxyPass "/" "https://192.168.0..212/" retry=3
ProxyPassReverse "/" "https://192.168.0.212/" retry=3
BrowserMatch "MSIE [2-5]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
</VirtualHost>
### Updating certificate:
Im my case, auto DNS Challenge from apache, so every time we get new cert files, it must be copied inside rmm too.
just overwrite default location:
/etc/letsencrypt/archive/blablablabla
or change certs location on nginx conf to whatever you want.
## nginx Proxy
Having mesh connection issues?
See <https://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2UserGuide.pdf> page 30.

View File

@@ -2,6 +2,9 @@
## Updating to the latest RMM version
!!!question
You have a [backup](https://docs.docker.com/desktop/backup-and-restore/) right?
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:
@@ -23,7 +26,7 @@ To renew your Let's Encrypt wildcard cert, run the following command, replacing
sudo certbot certonly --manual -d *.example.com --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns -m admin@example.com --no-eff-email
```
Verify the domain with the TXT record. Once issued, run the below commands to base64 encode the certificates and add then to the .env file
Verify the domain with the TXT record. Once issued, run the below commands to base64 encode the certificates and add them to the .env file
```bash
echo "CERT_PUB_KEY=$(sudo base64 -w 0 /etc/letsencrypt/live/${rootdomain}/fullchain.pem)" >> .env

View File

@@ -19,13 +19,16 @@ Other than this, you should avoid making any changes to your server and let the
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.
SSH into your server as the linux user you created during install (eg `tactical`).
!!!danger
__Never__ run any update scripts or commands as the `root` user.
This will mess up permissions and break your installation.
!!!question
You have a [backup](backup.md) right?
Download the update script and run it:
```bash
@@ -42,7 +45,7 @@ You can pass the optional `--force` flag to the update script to forcefully run
./update.sh --force
```
This is usefull for a botched update that might have not completed fully.
This is useful for a botched update that might have not completed fully.
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.
@@ -64,7 +67,7 @@ To renew your Let's Encrypt wildcard cert, run the following command, replacing
sudo certbot certonly --manual -d *.example.com --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns -m admin@example.com --no-eff-email
```
Same instructions as during install for [verifying the TXT record](install_server.md#deploy-the-txt-record-in-your-dns-manager) has propogated before hitting Enter.
Same instructions as during install for [verifying the TXT record](install_server.md#deploy-the-txt-record-in-your-dns-manager) has propagated before hitting Enter.
After this you have renewed the cert, simply run the `update.sh` script, passing it the `--force` flag.
@@ -84,3 +87,9 @@ If you're running low, shrink you database
sudo -u postgres psql -d tacticalrmm -c "vacuum full logs_auditlog"
sudo -u postgres psql -d tacticalrmm -c "vacuum full logs_auditlog"
```
## Video Walkthru
<div class="video-wrapper">
<iframe width="320" height="180" src="https://www.youtube.com/embed/ElUfQgesYs0" frameborder="0" allowfullscreen></iframe>
</div>

View File

@@ -14,6 +14,7 @@ nav:
- "Agent Installation": install_agent.md
- "Updating Agents": update_agents.md
- Functionality:
- "How it all Works": howitallworks.md
- "Alerting": functions/alerting.md
- "API Access": functions/api.md
- "Automated Tasks": functions/automated_tasks.md
@@ -39,11 +40,12 @@ nav:
- "Grafana": 3rdparty_grafana.md
- "AnyDesk": 3rdparty_anydesk.md
- "Connectwise Control / Screenconnect": 3rdparty_screenconnect.md
- "Splashtop": 3rdparty_splashtop.md
- "TeamViewer": 3rdparty_teamviewer.md
- "BitDefender GravityZone": 3rdparty_bitdefender_gravityzone.md
- Unsupported Extras:
- "Unsupported Guidelines": unsupported_guidelines.md
- "Unsupported Scripts": unsupported_scripts.md
- "Unsupported Configs": unsupported_scripts.md
- "Securing nginx": securing_nginx.md
- "Installing in Synology docker": unsupported_synology_docker_install.md
- Tips n' Tricks: tipsntricks.md
@@ -74,16 +76,25 @@ theme:
palette:
primary: "white"
accent: "indigo"
features:
extra_css:
- stylesheets/extra.css
extra:
social:
- icon: fontawesome/brands/github
link: "https://github.com/wh1te909/tacticalrmm"
markdown_extensions:
- pymdownx.inlinehilite
- admonition
- pymdownx.details
- codehilite:
guess_lang: false
- toc:
permalink: true
permalink: true
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true

View File

@@ -1,6 +1,6 @@
#!/bin/bash
SCRIPT_VERSION="56"
SCRIPT_VERSION="57"
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh'
sudo apt install -y curl wget dirmngr gnupg lsb-release
@@ -454,6 +454,7 @@ User=${USER}
Group=www-data
Restart=always
RestartSec=5s
LimitNOFILE=1000000
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,37 @@
<#
.Synopsis
Bluescreen - Reports bluescreens
.DESCRIPTION
This will check for Bluescreen events on your system. If parameter provided, goes back that number of days
.EXAMPLE
365
.NOTES
v1 bbrendon 2/2021
v1.1 silversword updating with parameters 11/2021
#>
$param1 = $args[0]
$ErrorActionPreference = 'silentlycontinue'
if ($Args.Count -eq 0) {
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
}
else {
$TimeSpan = (Get-Date) - (New-TimeSpan -Day $param1)
}
if (Get-WinEvent -FilterHashtable @{LogName = 'application'; ID = '1001'; ProviderName = 'Windows Error Reporting'; Level = 4; Data = 'BlueScreen'; StartTime = $TimeSpan }) {
Write-Output "There has been bluescreen events detected on your system"
Get-WinEvent -FilterHashtable @{LogName = 'application'; ID = '1001'; ProviderName = 'Windows Error Reporting'; Level = 4; Data = 'BlueScreen'; StartTime = $TimeSpan }
exit 1
}
{
else
Write-Output "No bluescreen events detected in the past 24 hours."
exit 0
}
Exit $LASTEXITCODE

View File

@@ -1,20 +0,0 @@
# This will check for Bluescreen events on your system
$ErrorActionPreference= 'silentlycontinue'
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
if (Get-WinEvent -FilterHashtable @{LogName='application';ID='1001';ProviderName='Windows Error Reporting';Level=4;Data='BlueScreen';StartTime=$TimeSpan})
{
Write-Output "There has been bluescreen events detected on your system"
Get-WinEvent -FilterHashtable @{LogName='application';ID='1001';ProviderName='Windows Error Reporting';Level=4;Data='BlueScreen';StartTime=$TimeSpan}
exit 1
}
{
else
Write-Output "No bluescreen events detected in the past 24 hours."
exit 0
}
Exit $LASTEXITCODE

View File

@@ -0,0 +1,3 @@
rem List apps installed by Chocolatey
choco list --localonly

View File

@@ -1,3 +1,21 @@
<#
.SYNOPSIS
Enables Windows Defender and sets preferences to lock Defender down
.DESCRIPTION
Windows Defender in its default configuration does basic protections. Running this script will enable many additional settings to increase security.
.PARAMETER NoControlledFolders
Adding this parameter will not enable Controlled Folders
.EXAMPLE
-NoControlledFolders
.NOTES
9/2021 v1 Initial release dinger1986
11/24/2021 v1.1 adding command parameters for Controller Folder access by Tremor and silversword
#>
param (
[switch] $NoControlledFolders
)
# Verifies that script is running on Windows 10 or greater
function Check-IsWindows10
{
@@ -92,8 +110,14 @@ if (!(Check-IsWindows10-1709))
Write-Host # `nUpdating Windows Defender Exploit Guard settings`n# -ForegroundColor Green
Write-Host # Enabling Controlled Folder Access and setting to block mode#
Set-MpPreference -EnableControlledFolderAccess Enabled
if ($NoControlledFolders) # Check if user has run with -NoControlledFolders parameter
{
Write-Host "Skipping enabling Controlled folders"
}
else {
Write-Host "Enabling Controlled folders"
Set-MpPreference -EnableControlledFolderAccess Enabled
}
Write-Host # Enabling Network Protection and setting to block mode#
Set-MpPreference -EnableNetworkProtection Enabled

View File

@@ -1,2 +1,2 @@
Write-Host "Running Windows Defender Full Scan in Background" -ForegroundColor Green
Write-Host "Running Windows Defender Full Scan in Background" -ForegroundColor Green
Start-MpScan -ScanPath C:\ -ScanType FullScan -AsJob

View File

@@ -1,2 +1,2 @@
Write-Host "Running Windows Defender Quick Scan in Background" -ForegroundColor Green
Write-Host "Running Windows Defender Quick Scan in Background" -ForegroundColor Green
Start-MpScan -ScanType QuickScan -AsJob

View File

@@ -5,7 +5,7 @@
This will check Event Log for Windows Defender Malware and Antispyware reports, otherwise will report as Healthy. By default if no command parameter is provided it will check the last 1 day (good for a scheduled daily task).
If a number is provided as a command parameter it will search back that number of days back provided (good for collecting all AV alerts on the computer).
.EXAMPLE
Win_Defender_Status_reports.ps1 365
365
.NOTES
v1 dinger initial release 2021
v1.1 bdrayer Adding full message output if items found

View File

@@ -1,2 +1,2 @@
#Install Adobe Reader DC
#Install Adobe Reader DC
choco install adobereader -params '"/EnableUpdateService /UpdateMode:3 /DesktopIcon"' --yes --no-progress --force

View File

@@ -1,16 +1,32 @@
$ErrorActionPreference= 'silentlycontinue'
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
if (Get-WinEvent -FilterHashtable @{LogName='security';ID='4720','4720','4728','4732','4756','4767';StartTime=$TimeSpan})
{
Write-Output "A change has been made to local users"
Get-WinEvent -FilterHashtable @{LogName='security';ID='4720','4720','4728','4732','4756','4767';StartTime=$TimeSpan}
exit 1
<#
.Synopsis
Event Viewer - New User Notification
.DESCRIPTION
Event Viewer Monitor - Notify when new Local user is created
.EXAMPLE
365
.NOTES
v1 dinger initial release
v1.1 silversword adding parameter options 11/2021
#>
$ErrorActionPreference = 'silentlycontinue'
if ($Args.Count -eq 0) {
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
}
else {
$TimeSpan = (Get-Date) - (New-TimeSpan -Day $param1)
}
else
{
Write-Output "No changes all looks fine"
exit 0
if (Get-WinEvent -FilterHashtable @{LogName = 'security'; ID = '4720', '4720', '4728', '4732', '4756', '4767'; StartTime = $TimeSpan }) {
Write-Output "A change has been made to local users"
Get-WinEvent -FilterHashtable @{LogName = 'security'; ID = '4720', '4720', '4728', '4732', '4756', '4767'; StartTime = $TimeSpan }
exit 1
}
else {
Write-Output "No changes all looks fine"
exit 0
}

View File

@@ -0,0 +1,9 @@
@echo off
sc stop spooler
ping 127.0.0.1 -n 6 > nul
del C:\Windows\System32\spool\printers\* /Q /F /S
sc start spooler

View File

@@ -0,0 +1,28 @@
<#
.SYNOPSIS
Restarts stuck printer jobs.
.DESCRIPTION
Cycles through each printer and restarts any jobs that are stuck with error status.
.NOTES
Change Log
----------------------------------------------------------------------------------
V1.0 Initial Release by https://github.com/bc24fl/tacticalrmm-scripts/
#>
$allPrinters = Get-Printer
foreach ($printer in $allPrinters) {
$printJobs = Get-PrintJob -PrinterName $($printer.Name)
if ($printJobs) {
foreach ($job in $printJobs) {
if ($job.JobStatus -match 'Error') {
$stuckPrinterName = $job.PrinterName
$stuckPrinterJob = $job.Id
Write-Host "Restarting Job Id $stuckPrinterJob on printer $stuckPrinterName"
Restart-PrintJob -InputObject $job
}
}
}
}

View File

@@ -0,0 +1,35 @@
<#
.Synopsis
Software Install - Reports new installs
.DESCRIPTION
This will check for software install events in the application Event Viewer log
If a number is provided as a command parameter it will search that number of days back.
.EXAMPLE
365
.NOTES
v1 silversword initial release 11/2021
#>
$param1 = $args[0]
$ErrorActionPreference = 'silentlycontinue'
if ($Args.Count -eq 0) {
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
}
else {
$TimeSpan = (Get-Date) - (New-TimeSpan -Day $param1)
}
if (Get-WinEvent -FilterHashtable @{LogName = 'application'; ID = '11707'; StartTime = $TimeSpan }) {
Write-Output "Software installed"
Get-WinEvent -FilterHashtable @{LogName = 'application'; ID = '11707'; StartTime = $TimeSpan }
exit 1
}
{
else
Write-Output "No Software install events detected in the past 24 hours."
exit 0
}
Exit $LASTEXITCODE

View File

@@ -0,0 +1,202 @@
<#
.SYNOPSIS
Installs Sophos Endpoint via the Sophos API https://developer.sophos.com/apis
.REQUIREMENTS
You will need API credentials to use this script. The instructions are slightly different depending who you are.
For Partners : https://developer.sophos.com/getting-started (Only Step 1 Required For API Credentials)
For Organizations: https://developer.sophos.com/getting-started-organization (Only Step 1 Required For API Credentials)
For Tenants : https://developer.sophos.com/getting-started-tenant (Only Step 1 Required For API Credentials)
.INSTRUCTIONS
1. Get your API Credentials (Client Id, Client Secret) using the steps in the Requirements section
2. In Tactical RMM, Go to Settings >> Global Settings >> Custom Fields and under Clients, create the following custom fields:
a) SophosTenantName as type text
b) SophosClientId as type text
c) SophosClientSecret as type text
3. In Tactical RMM, Right-click on each client and select Edit. Fill in the SophosTenantName, SophosClientId, and SophosClientSecret.
Make sure the SophosTenantName is EXACTLY how it is displayed in your Sophos Partner / Central Dashboard. A partner can find the list of tenants on the left menu under Sophos Central Customers
4. Create the follow script arguments
a) -ClientId {{client.SophosClientId}}
b) -ClientSecret {{client.SophosClientSecret}}
c) -TenantName {{client.SophosTenantName}}
d) -Products (Optional Parameter) - A list of products to install, comma-separated. Available options are: antivirus, intercept, mdr, deviceEncryption or all. Example - To install Antivirus, Intercept, and Device encryption you would pass "antivirus,intercept,deviceEncryption".
.NOTES
V1.0 Initial Release by https://github.com/bc24fl/tacticalrmm-scripts/
V1.1 Added error handling for each Invoke-Rest Call for easier troubleshooting and graceful exit.
V1.2 Added support for more than 100 tenants.
#>
param(
$ClientId,
$ClientSecret,
$TenantName,
$Products
)
if ([string]::IsNullOrEmpty($ClientId)) {
Write-Output "ClientId must be defined. Use -ClientId <value> to pass it."
Exit 1
}
if ([string]::IsNullOrEmpty($ClientSecret)) {
Write-Output "ClientSecret must be defined. Use -ClientSecret <value> to pass it."
Exit 1
}
if ([string]::IsNullOrEmpty($TenantName)) {
Write-Output "TenantName must be defined. Use -TenantName <value> to pass it."
Exit 1
}
if ([string]::IsNullOrEmpty($Products)) {
Write-Output "No product options specified installing default antivirus and intercept."
$Products = "antivirus,intercept"
}
Write-Host "Running Sophos Endpoint Installation Script On: $env:COMPUTERNAME"
# Find if workstation or server. osInfo.ProductType returns 1 = workstation, 2 = domain controller, 3 = server
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
$urlAuth = "https://id.sophos.com/api/v2/oauth2/token"
$urlWhoami = "https://api.central.sophos.com/whoami/v1"
$urlTenant = "https://api.central.sophos.com/partner/v1/tenants?pageTotal=true"
$authBody = @{
"grant_type"="client_credentials"
"client_id"=$ClientId
"client_secret"=$ClientSecret
"scope"="token"
}
$authResponse = (Invoke-RestMethod -Method 'post' -Uri $urlAuth -Body $authBody)
$authToken = $authResponse.access_token
$authHeaders = @{Authorization = "Bearer $authToken"}
if ($authToken.length -eq 0){
Write-Host "Error, no authentication token received. Please check your api credentials. Exiting script."
Exit 1
}
$whoAmIResponse = (Invoke-RestMethod -Method 'Get' -headers $authHeaders -Uri $urlWhoami)
$myId = $whoAmIResponse.Id
$myIdType = $whoAmIResponse.idType
if ($myIdType.length -eq 0){
Write-Host "Error, no Whoami Id Type received. Please check your api credentials or network connections. Exiting script."
Exit 1
}
if($myIdType -eq 'partner'){
$requestHeaders =@{
'Authorization'="Bearer $authToken"
'X-Partner-ID'=$myId
}
}
elseif($myIdType -eq 'organization') {
$requestHeaders =@{
'Authorization' = "Bearer $authToken"
'X-Organization-ID' = $myId
}
}
elseif($myIdType -eq 'tenant'){
$requestHeaders =@{
'Authorization' = "Bearer $authToken"
'X-Tenant-ID' = $myId
}
}
else {
Write-Host "Error finding id type. This script only supports Partner, Organization, and Tenant API's."
Exit 1
}
# Cycle through all tenants until a tenant match, or all pages have exhausted.
$currentPage = 1
do {
Write-Output "Looking for tenant on page $currentPage. Please wait..."
if ($currentPage -ge 2){
Start-Sleep -s 5
$urlTenant = "https://api.central.sophos.com/partner/v1/tenants?page=$currentPage"
}
$tenantResponse = (Invoke-RestMethod -Method 'Get' -headers $requestHeaders -Uri $urlTenant)
$tenants = $tenantResponse.items
$totalPages = [int]$tenantResponse.pages.total
foreach ($tenant in $tenants) {
if ($tenant.name -eq $TenantName){
$tenantRegion = $tenant.dataRegion
$tenantId = $tenant.id
}
}
$currentPage += 1
} until( $currentPage -gt $totalPages -Or ($tenantId.length -gt 1 ) )
if ($tenantId.length -eq 0){
Write-Host "Error, no tenant found with the provided name. Please check the name and try again. Exiting script."
Exit 1
}
$requestHeaders =@{
'Authorization' = "Bearer $authToken"
'X-Tenant-ID' = $tenantId
}
$urlEndpoint = "https://api-" + $tenantRegion + ".central.sophos.com/endpoint/v1/downloads"
$endpointDownloadResponse = (Invoke-RestMethod -Method 'Get' -headers $requestHeaders -Uri $urlEndpoint)
$endpointInstallers = $endpointDownloadResponse.installers
if ($endpointInstallers.length -eq 0){
Write-Host "Error, no installers received. Please check your api credentials or network connections. Exiting script."
Exit 1
}
foreach ($installer in $endpointInstallers){
if ( ($installer.platform -eq "windows") -And ($installer.productName = "Sophos Endpoint Protection") ){
if ( ($osInfo.ProductType -eq 1) -And ($installer.type = "computer") ){
# Workstation Install
$installUrl = $installer.downloadUrl
}
elseif ( ( ($osInfo.ProductType -eq 2) -Or ($osInfo.ProductType -eq 3) ) -And ($installer.type = "server") ){
# Server Install
$installUrl = $installer.downloadUrl
}
else{
Write-Host "Error, this script only supports producttype of 1) Work Station, 2) Domain Controller, or 3) Server."
Exit 1
}
}
}
try{
Write-Host "Checking if Sophos Endpoint installed. Please wait..."
$AppInstalled = & "choco" "list" "-li"
if ($AppInstalled -like '*Sophos Endpoint Agent*'){
Write-Host "Sophos Endpoint is installed. Skipping installation."
} else {
Write-Host "Sophos Endpoint is NOT installed. Installing now..."
Write-Host "Downloading Sophos from " + $installUrl + " Please wait..."
$tmpDir = [System.IO.Path]::GetTempPath()
$outpath = $tmpDir + "SophosSetup.exe"
Write-Host "Saving file to " + $outpath
Invoke-WebRequest -Uri $installUrl -OutFile $outpath
Write-Host "Running Sophos Setup... Please wait up to 20 minutes for install to complete."
$appArgs = @("--products=" + $Products + " --quiet ")
Start-Process -Filepath $outpath -ArgumentList $appArgs
}
}
catch{
Write-Host "Installation failed with error message: $($PSItem.ToString())"
}

View File

@@ -0,0 +1,10 @@
# Retrieve Splashtop SUUID from device registry.
if (!$ErrorCount -eq 0) {
exit 1
}
$key = 'HKLM:\SOFTWARE\WOW6432Node\Splashtop Inc.\Splashtop Remote Server'
(Get-ItemProperty -Path $key -Name SUUID).SUUID
Write-Output $key.SUUID

View File

@@ -1,4 +1,4 @@
Function Start-Cleanup {
Function Start-Cleanup {
<#
.SYNOPSIS
Automate cleaning up a C:\ drive with low disk space

View File

@@ -0,0 +1,16 @@
$pools = Get-VirtualDisk | select -ExpandProperty HealthStatus
$err = $False
ForEach ($pool in $pools) {
if ($pool -ne "Healthy") {
$err = $True
}
}
if ($err) {
exit 1
}
else {
exit 0
}

View File

@@ -1,17 +1,34 @@
$ErrorActionPreference= 'silentlycontinue'
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
if (Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-TaskScheduler/Operational';ID='106';StartTime=$TimeSpan} | Where-Object -Property Message -notlike *$env:COMPUTERNAME*)
{
Write-Output "New Task Has Been Added"
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-TaskScheduler/Operational';ID='106';StartTime=$TimeSpan}
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-TaskScheduler/Operational';ID='141';StartTime=$TimeSpan}
exit 1
<#
.Synopsis
Event Viewer - Task Scheduler New Item Notification
.DESCRIPTION
Event Viewer Monitor - Notify when new Task Scheduler item is created
.EXAMPLE
365
.NOTES
v1 dinger initial release
v1.1 silversword adding command parameters 11/2021
#>
$ErrorActionPreference = 'silentlycontinue'
if ($Args.Count -eq 0) {
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
}
else {
$TimeSpan = (Get-Date) - (New-TimeSpan -Day $param1)
}
else
{
Write-Output "No changes with Task Scheduler"
exit 0
if (Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-TaskScheduler/Operational'; ID = '106'; StartTime = $TimeSpan } | Where-Object -Property Message -notlike *$env:COMPUTERNAME*) {
Write-Output "New Task Has Been Added"
Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-TaskScheduler/Operational'; ID = '106'; StartTime = $TimeSpan }
Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-TaskScheduler/Operational'; ID = '141'; StartTime = $TimeSpan }
exit 1
}
else {
Write-Output "No changes with Task Scheduler"
exit 0
}

View File

@@ -1,8 +1,8 @@
<#
.SYNOPSIS
Used to enable or disable users
User - Enable or disable a user
.DESCRIPTION
For installing packages using chocolatey. If you're running against more than 10, include the Hosts parameter to limit the speed. If running on more than 30 agents at a time make sure you also change the script timeout setting.
Used to enable or disable local user
.PARAMETER Name
Required: Username
.PARAMETER Enabled

View File

@@ -1,4 +1,4 @@
<#
<#
.SYNOPSIS
Reset-WindowsUpdate.ps1 - Resets the Windows Update components

View File

@@ -1,4 +1,4 @@
# Tactical RMM Patch management disables Windows Automatic Update settings by setting the registry key below to 1.
# Run this to revert back to default
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUOptions" -Value "0"
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUOptions"

View File

@@ -0,0 +1 @@
"c:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe" /applyUpdates

View File

@@ -9,6 +9,6 @@ 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
"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT
start tactical.exe
powershell Remove-MpPreference -ExclusionPath C:\TEMP\TRMM

View File

@@ -65,7 +65,7 @@ if ($install -eq $NULL) {
If (Get-Service $serviceName -ErrorAction SilentlyContinue) {
write-host ('Tactical RMM Is Already Installed')
& 'C:\Program Files\TacticalAgent\unins000.exe' /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS
& 'C:\Program Files\TacticalAgent\unins000.exe' /VERYSILENT
Start-Sleep -s 20
}
Invoke-Expression ((new-object System.Net.WebClient).DownloadString($rmmURI))

View File

@@ -0,0 +1,70 @@
# From Chase 12/14/2021
function Create-TacticalRMMClient {
param(
$APIKey,
$Customer,
$Site,
$URL
)
$Headers = @{
"Content-Type" = "application/json"
"X-API-KEY" = $APIKey
}
$AllClients = Invoke-RestMethod -Uri "$URL/clients/" -Headers $Headers -Method GET -UseBasicParsing
$ClientCheck = ($AllClients | Where name -eq $Customer)
if (!$ClientCheck) {
$CreateClientBody = @"
{
"client": {"name": "$Customer"},
"site": {"name": "Unspecified"},
"custom_fields": []
}
"@
Invoke-RestMethod -URI "$URL/clients/" -Headers $Headers -Method Post -Body $CreateClientBody -UseBasicParsing | Out-Null
$NewSearch = Invoke-RestMethod -Uri "$URL/clients/" -Headers $Headers -Method GET -UseBasicParsing
$ClientID = ($NewSearch | Where { $_.name -eq $Customer }).id
$ClientName = ($NewSearch | Where { $_.name -eq $Customer }).name
$ClientSites = ($NewSearch | Where { $_.name -eq $Customer }).sites
}
else {
$ClientID = $ClientCheck.ID
$ClientName = $ClientCheck.Name
$ClientSites = $ClientCheck.Sites
}
$SiteCheck = ($ClientSites | Where Name -eq $Site)
if (!$SiteCheck) {
$CreateSiteBody = @"
{
"site": {"client": $ClientID, "name": "$Site"},
"custom_fields": []
}
"@
Invoke-RestMethod -Uri "$URL/clients/sites/" -Headers $Headers -Method POST -Body $CreateSiteBody -UseBasicParsing | Out-Null
$SiteNewSearch = (Invoke-RestMethod -Uri "$URL/clients/$ClientID/" -Headers $Headers -Method GET -UseBasicParsing).sites
$SiteID = ($SiteNewSearch | Where name -eq $Site).id
}
$Output = @()
$Output += New-Object -TypeName psobject -Property @{ClientID = "$ClientID"; SiteID = "$SiteID" }
Write-Output $Output
}

View File

@@ -9,7 +9,7 @@ else {
$ChkReg = Test-Path 'HKLM:\SOFTWARE\TacticalRMM\'
If ($ChkReg -eq $True) {
$regrmm = Get-ItemProperty -Path HKLM:\SOFTWARE\TacticalRMM\
& 'C:\Program Files\TacticalAgent\unins000.exe' /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS
& 'C:\Program Files\TacticalAgent\unins000.exe' /VERYSILENT
start-sleep -s 20
}
dsregcmd.exe /debug /leave

View File

@@ -1,6 +1,6 @@
#!/bin/bash
SCRIPT_VERSION="126"
SCRIPT_VERSION="127"
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/update.sh'
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
YELLOW='\033[1;33m'
@@ -41,12 +41,6 @@ if [ "$ORIGUSER" != "$USER" ]; then
exit 1
fi
CHECK_TOO_OLD=$(grep natsapi /etc/nginx/sites-available/rmm.conf)
if ! [[ $CHECK_TOO_OLD ]]; then
printf >&2 "${RED}Your version of TRMM is no longer supported. Refusing to update.${NC}\n"
exit 1
fi
TMP_SETTINGS=$(mktemp -p "" "rmmsettings_XXXXXXXXXX")
curl -s -L "${LATEST_SETTINGS_URL}" > ${TMP_SETTINGS}
SETTINGS_FILE="/rmm/api/tacticalrmm/tacticalrmm/settings.py"
@@ -68,27 +62,39 @@ NATS_SERVER_VER=$(grep "^NATS_SERVER_VER" "$TMP_SETTINGS" | awk -F'[= "]' '{prin
CURRENT_PIP_VER=$(grep "^PIP_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
CURRENT_NPM_VER=$(grep "^NPM_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
if [ -f /etc/systemd/system/natsapi.service ]; then
printf >&2 "${GREEN}Removing natsapi.service${NC}\n"
sudo systemctl stop natsapi.service
sudo systemctl disable natsapi.service
sudo rm -f /etc/systemd/system/natsapi.service
sudo systemctl daemon-reload
fi
cls() {
printf "\033c"
}
CHECK_HAS_DAPHNE=$(grep daphne.sock /etc/nginx/sites-available/rmm.conf)
if ! [[ $CHECK_HAS_DAPHNE ]]; then
cls
echo -ne "${RED}Nginx config changes required before continuing.${NC}\n"
echo -ne "${RED}Please check the v0.5.0 release notes on github for instructions, then re-run this script.${NC}\n"
echo -ne "${YELLOW}https://github.com/wh1te909/tacticalrmm/releases/tag/v0.5.0${NC}\n"
echo -ne "${RED}Aborting...${NC}\n"
exit 1
CHECK_NATS_LIMITNOFILE=$(grep LimitNOFILE /etc/systemd/system/nats.service)
if ! [[ $CHECK_NATS_LIMITNOFILE ]]; then
sudo rm -f /etc/systemd/system/nats.service
natsservice="$(cat << EOF
[Unit]
Description=NATS Server
After=network.target
[Service]
PrivateTmp=true
Type=simple
ExecStart=/usr/local/bin/nats-server -c /rmm/api/tacticalrmm/nats-rmm.conf
ExecReload=/usr/bin/kill -s HUP \$MAINPID
ExecStop=/usr/bin/kill -s SIGINT \$MAINPID
User=${USER}
Group=www-data
Restart=always
RestartSec=5s
LimitNOFILE=1000000
[Install]
WantedBy=multi-user.target
EOF
)"
echo "${natsservice}" | sudo tee /etc/systemd/system/nats.service > /dev/null
sudo systemctl daemon-reload
fi
if ! sudo nginx -t > /dev/null 2>&1; then
@@ -99,30 +105,6 @@ if ! sudo nginx -t > /dev/null 2>&1; then
exit 1
fi
if ! [ -f /etc/systemd/system/daphne.service ]; then
daphneservice="$(cat << EOF
[Unit]
Description=django channels daemon
After=network.target
[Service]
User=${USER}
Group=www-data
WorkingDirectory=/rmm/api/tacticalrmm
Environment="PATH=/rmm/api/env/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/rmm/api/env/bin/daphne -u /rmm/daphne.sock tacticalrmm.asgi:application
Restart=always
RestartSec=3s
[Install]
WantedBy=multi-user.target
EOF
)"
echo "${daphneservice}" | sudo tee /etc/systemd/system/daphne.service > /dev/null
sudo systemctl daemon-reload
sudo systemctl enable daphne.service
fi
if [ ! -f /etc/systemd/system/nats-api.service ]; then
natsapi="$(cat << EOF
[Unit]

2168
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,18 +10,18 @@
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
},
"dependencies": {
"@quasar/extras": "^1.12.0",
"@quasar/extras": "^1.12.2",
"apexcharts": "^3.27.1",
"axios": "^0.24.0",
"dotenv": "^8.6.0",
"qrcode.vue": "^3.2.2",
"quasar": "^2.3.2",
"quasar": "^2.3.4",
"vue3-ace-editor": "^2.2.1",
"vue3-apexcharts": "^1.4.0",
"vuex": "^4.0.2"
},
"devDependencies": {
"@quasar/app": "^3.2.3",
"@quasar/app": "^3.2.5",
"@quasar/cli": "^1.2.2"
},
"browserslist": [
@@ -30,4 +30,4 @@
"last 2 Edge versions",
"last 1 Safari versions"
]
}
}

View File

@@ -564,11 +564,11 @@ export default {
removeAgent(agent) {
this.$q
.dialog({
title: `Please type <code style="color:red">${agent.hostname}</code> to confirm deletion.`,
title: `Please type <code style="color:red">yes</code> in the box below to confirm deletion.`,
prompt: {
model: "",
type: "text",
isValid: val => val === agent.hostname,
isValid: val => val === "yes",
},
cancel: true,
ok: { label: "Uninstall", color: "negative" },

View File

@@ -127,6 +127,9 @@
<q-item clickable v-close-popup @click="openHelp('docs')">
<q-item-section>Documentation</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="openHelp('github')">
<q-item-section>GitHub Repo</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="openHelp('bug')">
<q-item-section>Bug Report</q-item-section>
</q-item>
@@ -227,6 +230,9 @@ export default {
openHelp(mode) {
let url;
switch (mode) {
case "github":
url = "https://github.com/wh1te909/tacticalrmm/";
break;
case "docs":
url = "https://wh1te909.github.io/tacticalrmm/";
break;

View File

@@ -95,7 +95,7 @@ const columns = [
field: "cpu_percent",
align: "left",
sortable: true,
sort: (a, b, rowA, rowB) => parseInt(b) < parseInt(a),
sort: (a, b, rowA, rowB) => parseFloat(b) < parseFloat(a),
},
{
name: "membytes",

View File

@@ -5,6 +5,7 @@
<template v-slot:before>
<q-tabs dense v-model="tab" vertical class="text-primary">
<q-tab name="general" label="General" />
<q-tab name="customfields" label="Custom Fields" />
<q-tab name="patch" label="Patches" />
</q-tabs>
</template>
@@ -15,7 +16,7 @@
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<div class="scroll" style="max-height: 65vh">
<div class="scroll" style="height: 65vh; max-height: 65vh">
<q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-up">
<!-- general -->
<q-tab-panel name="general">
@@ -105,11 +106,18 @@
<q-checkbox v-model="agent.overdue_text_alert" label="Get overdue sms alerts" />
<q-checkbox v-model="agent.overdue_dashboard_alert" label="Get overdue dashboard alerts" />
</q-card-section>
<div class="text-h6" v-if="customFields.length > 0">Custom Fields</div>
</q-tab-panel>
<!-- custom fields -->
<q-tab-panel name="customfields">
<div class="text-subtitle" v-if="customFields.length === 0">
No agent custom fields found. Go to **Settings > Global Settings > Custom Settings**
</div>
<q-card-section v-for="field in customFields" :key="field.id">
<CustomField v-model="custom_fields[field.name]" :field="field" />
</q-card-section>
</q-tab-panel>
<!-- patch -->
<q-tab-panel name="patch">
<PatchPolicyForm :agent="agent" />

View File

@@ -1,6 +1,6 @@
<template>
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent @keydown.esc="onDialogHide" :maximized="maximized">
<q-card class="q-dialog-plugin" :style="maximized ? '' : 'width: 80vw; max-width: 90vw'">
<q-card class="q-dialog-plugin" :style="maximized ? '' : 'width: 90vw; max-width: 90vw'">
<q-bar>
{{ title }}
<q-space />
@@ -15,81 +15,83 @@
</q-btn>
</q-bar>
<q-form @submit="submitForm">
<div class="q-pt-sm q-px-sm row">
<q-input
filled
dense
class="col-2"
:readonly="readonly"
v-model="formScript.name"
label="Name"
:rules="[val => !!val || '*Required']"
/>
<q-select
class="q-pl-sm col-2"
:readonly="readonly"
options-dense
filled
dense
v-model="formScript.shell"
:options="shellOptions"
emit-value
map-options
label="Shell Type"
/>
<q-input
type="number"
class="q-pl-sm col-2"
filled
dense
:readonly="readonly"
v-model.number="formScript.default_timeout"
label="Timeout (seconds)"
:rules="[val => val >= 5 || 'Minimum is 5']"
/>
<tactical-dropdown
class="q-pl-sm col-3"
filled
v-model="formScript.category"
:options="categories"
use-input
clearable
new-value-mode="add-unique"
filterable
label="Category"
:readonly="readonly"
hide-bottom-space
/>
<q-input
class="q-pl-sm col-3"
filled
dense
:readonly="readonly"
v-model="formScript.description"
label="Description"
/>
<tactical-dropdown
v-model="formScript.args"
label="Script Arguments (press Enter after typing each argument)"
class="q-pb-sm col-12 row"
filled
use-input
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
:readonly="readonly"
<div class="row q-pa-sm">
<div class="col-4 q-gutter-sm q-pr-sm">
<q-input
filled
dense
:readonly="readonly"
v-model="formScript.name"
label="Name"
:rules="[val => !!val || '*Required']"
hide-bottom-space
/>
<q-input filled dense :readonly="readonly" v-model="formScript.description" label="Description" />
<q-select
:readonly="readonly"
options-dense
filled
dense
v-model="formScript.shell"
:options="shellOptions"
emit-value
map-options
label="Shell Type"
/>
<tactical-dropdown
filled
v-model="formScript.category"
:options="categories"
use-input
clearable
new-value-mode="add-unique"
filterable
label="Category"
:readonly="readonly"
hide-bottom-space
/>
<tactical-dropdown
v-model="formScript.args"
label="Script Arguments (press Enter after typing each argument)"
filled
use-input
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
:readonly="readonly"
/>
<q-input
type="number"
filled
dense
:readonly="readonly"
v-model.number="formScript.default_timeout"
label="Timeout (seconds)"
:rules="[val => val >= 5 || 'Minimum is 5']"
hide-bottom-space
/>
<q-input
label="Syntax"
type="textarea"
style="height: 150px; overflow-y: auto; resize: none"
v-model="formScript.syntax"
dense
filled
:readonly="readonly"
/>
</div>
<v-ace-editor
v-model:value="formScript.script_body"
class="col-8"
:lang="formScript.shell === 'cmd' ? 'batchfile' : formScript.shell"
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
:style="{ height: `${maximized ? '87vh' : '64vh'}` }"
wrap
:printMargin="false"
:options="{ fontSize: '14px' }"
/>
</div>
<v-ace-editor
v-model:value="code"
:lang="formScript.shell === 'cmd' ? 'batchfile' : formScript.shell"
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
:style="{ height: `${maximized ? '72vh' : '64vh'}` }"
wrap
:printMargin="false"
:options="{ fontSize: '14px' }"
/>
<q-card-actions>
<tactical-dropdown
style="width: 350px"
@@ -109,7 +111,7 @@
dense
flat
label="Test Script"
:disable="!agent || !code || !formScript.default_timeout"
:disable="!agent || !formScript.script_body || !formScript.default_timeout"
@click="openTestScriptModal"
/>
</template>
@@ -175,11 +177,10 @@ export default {
// script form logic
const script = props.script
? ref(Object.assign({}, props.script))
: ref({ shell: "powershell", default_timeout: 90, args: [] });
? ref(Object.assign({}, { ...props.script, script_body: "" }))
: ref({ shell: "powershell", default_timeout: 90, args: [], script_body: "" });
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
const code = ref("");
const maximized = ref(false);
const loading = ref(false);
const agentLoading = ref(false);
@@ -199,16 +200,13 @@ export default {
// get code if editing or cloning script
if (props.script)
downloadScript(script.value.id, { with_snippets: props.readonly }).then(r => {
code.value = r.code;
script.value.script_body = r.code;
});
async function submitForm() {
loading.value = true;
let result = "";
try {
// base64 encode the script text
script.value.code_base64 = btoa(code.value);
// edit existing script
if (props.script && !props.clone) {
result = await editScript(script.value);
@@ -229,7 +227,7 @@ export default {
$q.dialog({
component: TestScriptModal,
componentProps: {
script: { ...script.value, code: code.value },
script: { ...script.value },
agent: agent.value,
},
});
@@ -245,7 +243,6 @@ export default {
return {
// reactive data
formScript: script.value,
code,
maximized,
loading,
agentOptions,

View File

@@ -118,12 +118,12 @@ export default {
// base64 encode the script and delete file
const reader = new FileReader();
reader.onloadend = () => {
script.value.code_base64 = reader.result.replace(/^data:.+;base64,/, "");
script.value.script_body = reader.result;
};
reader.readAsDataURL(file.value);
reader.readAsText(file.value);
} else {
script.value.code_base64 = "";
script.value.script_body = "";
}
});

View File

@@ -46,7 +46,7 @@ export default {
async function runTestScript() {
loading.value = true;
const data = {
code: props.script.code,
code: props.script.script_body,
timeout: props.script.default_timeout,
args: props.script.args,
shell: props.script.shell,

View File

@@ -65,7 +65,9 @@ export function useCheckModal({ editCheck, initialState }) {
async function getAgentServiceOptions() {
const { services } = await fetchAgent(check.value.agent)
serviceOptions.value = Object.freeze(services.map(service => ({ label: service.display_name, value: service.name })))
const tmp = services.map(service => ({ label: service.display_name, value: service.name }))
serviceOptions.value = Object.freeze(tmp.sort((a, b) => a.label.localeCompare(b.label)))
check.value.svc_name = serviceOptions.value[0].value
check.value.svc_display_name = serviceOptions.value[0].label
}
@@ -1194,4 +1196,4 @@ export const defaultServiceOptions = [
value: "tacticalagent",
label: "Tactical RMM Agent"
}
]
].sort((a, b) => a.label.localeCompare(b.label))