From 9054c233f41ba002ec05345623144040ce608343 Mon Sep 17 00:00:00 2001 From: Charlie Powell Date: Thu, 15 Aug 2024 00:41:04 -0400 Subject: [PATCH 01/23] Proposed work for amidaware/community-scripts#245 Modify the load_community_scripts logic to add env and run_as_user keys. --- api/tacticalrmm/scripts/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/tacticalrmm/scripts/models.py b/api/tacticalrmm/scripts/models.py index 45ab2b53..ae09adbc 100644 --- a/api/tacticalrmm/scripts/models.py +++ b/api/tacticalrmm/scripts/models.py @@ -118,8 +118,12 @@ class Script(BaseAuditModel): args = script["args"] if "args" in script.keys() else [] + env = script["env"] if "env" in script.keys() else [] + syntax = script["syntax"] if "syntax" in script.keys() else "" + run_as_user = script["run_as_user"] if "run_as_user" in script.keys() else False + supported_platforms = ( script["supported_platforms"] if "supported_platforms" in script.keys() @@ -135,7 +139,9 @@ class Script(BaseAuditModel): i.shell = script["shell"] i.default_timeout = default_timeout i.args = args + i.env_vars = env i.syntax = syntax + i.run_as_user = run_as_user i.filename = script["filename"] i.supported_platforms = supported_platforms @@ -163,8 +169,10 @@ class Script(BaseAuditModel): category=category, default_timeout=default_timeout, args=args, + env_vars=env, filename=script["filename"], syntax=syntax, + run_as_user=run_as_user, supported_platforms=supported_platforms, ) # new_script.hash_script_body() # also saves script From 706757d215943c07aba9a74e37cadc1a04118edf Mon Sep 17 00:00:00 2001 From: Charlie Powell Date: Thu, 15 Aug 2024 00:57:04 -0400 Subject: [PATCH 02/23] Black didn't like the format of that line whatever, quick fix. --- api/tacticalrmm/scripts/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/tacticalrmm/scripts/models.py b/api/tacticalrmm/scripts/models.py index ae09adbc..5c9b0d29 100644 --- a/api/tacticalrmm/scripts/models.py +++ b/api/tacticalrmm/scripts/models.py @@ -122,7 +122,9 @@ class Script(BaseAuditModel): syntax = script["syntax"] if "syntax" in script.keys() else "" - run_as_user = script["run_as_user"] if "run_as_user" in script.keys() else False + run_as_user = ( + script["run_as_user"] if "run_as_user" in script.keys() else False + ) supported_platforms = ( script["supported_platforms"] From a029c1d0dbf405dd5576f9d7b32b40934a390f1b Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:40:19 +0000 Subject: [PATCH 03/23] set alert template when moving site to another client fixes #1975 --- api/tacticalrmm/clients/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/tacticalrmm/clients/models.py b/api/tacticalrmm/clients/models.py index c18f5dc2..2d242a7b 100644 --- a/api/tacticalrmm/clients/models.py +++ b/api/tacticalrmm/clients/models.py @@ -133,6 +133,7 @@ class Site(BaseAuditModel): old_site.alert_template != self.alert_template or old_site.workstation_policy != self.workstation_policy or old_site.server_policy != self.server_policy + or old_site.client != self.client ): cache_agents_alert_template.delay() From 22c152f600ab72af6594e2e4a4d4bb8299ec3625 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:32:37 +0000 Subject: [PATCH 04/23] update reqs --- api/tacticalrmm/requirements.txt | 30 ++++++++++++------------- api/tacticalrmm/tacticalrmm/settings.py | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index 2be73509..ee4d3c20 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -1,14 +1,14 @@ -adrf==0.1.6 +adrf==0.1.7 asgiref==3.8.1 celery==5.4.0 -certifi==2024.7.4 -cffi==1.16.0 +certifi==2024.8.30 +cffi==1.17.0 channels==4.1.0 channels_redis==4.2.0 cryptography==42.0.8 -Django==4.2.14 +Django==4.2.16 django-cors-headers==4.4.0 -django-filter==24.2 +django-filter==24.3 django-rest-knox==4.2.0 djangorestframework==3.15.2 drf-spectacular==0.27.2 @@ -16,32 +16,32 @@ hiredis==2.3.2 kombu==5.3.7 meshctrl==0.1.15 msgpack==1.0.8 -nats-py==2.8.0 +nats-py==2.9.0 packaging==24.1 -psutil==5.9.8 -psycopg[binary]==3.1.19 +psutil==6.0.0 +psycopg[binary]==3.2.1 pycparser==2.22 pycryptodome==3.20.0 pyotp==2.9.0 -pyparsing==3.1.2 +pyparsing==3.1.4 python-ipware==2.0.2 qrcode==7.4.2 -redis==5.0.7 +redis==5.0.8 requests==2.32.3 six==1.16.0 sqlparse==0.5.0 twilio==8.13.0 urllib3==2.2.2 -uvicorn[standard]==0.30.1 +uvicorn[standard]==0.30.6 uWSGI==2.0.26 validators==0.24.0 vine==5.1.0 -websockets==12.0 -zipp==3.19.2 +websockets==13.0.1 +zipp==3.20.1 pandas==2.2.2 kaleido==0.2.1 jinja2==3.1.4 -markdown==3.6 -plotly==5.22.0 +markdown==3.7 +plotly==5.24.0 weasyprint==62.3 ocxsect==0.1.5 \ No newline at end of file diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index 073e86c9..7f2f62a0 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -35,7 +35,7 @@ LATEST_AGENT_VER = "2.8.0" MESH_VER = "1.1.21" -NATS_SERVER_VER = "2.10.17" +NATS_SERVER_VER = "2.10.20" # Install Nushell on the agent # https://github.com/nushell/nushell From 25154a4331dd680053f74fb20490d205627670b4 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Mon, 30 Sep 2024 07:21:32 +0000 Subject: [PATCH 05/23] update nats --- api/tacticalrmm/tacticalrmm/settings.py | 2 +- docker/containers/tactical-nats/dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index 7f2f62a0..d86b74a4 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -35,7 +35,7 @@ LATEST_AGENT_VER = "2.8.0" MESH_VER = "1.1.21" -NATS_SERVER_VER = "2.10.20" +NATS_SERVER_VER = "2.10.21" # Install Nushell on the agent # https://github.com/nushell/nushell diff --git a/docker/containers/tactical-nats/dockerfile b/docker/containers/tactical-nats/dockerfile index bf1b4266..8129b1c6 100644 --- a/docker/containers/tactical-nats/dockerfile +++ b/docker/containers/tactical-nats/dockerfile @@ -1,4 +1,4 @@ -FROM nats:2.10.17-alpine +FROM nats:2.10.21-alpine ENV TACTICAL_DIR /opt/tactical ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready From e9c0f7e200667b73ac6dc9b7b611b126a18e377c Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Mon, 30 Sep 2024 08:20:22 +0000 Subject: [PATCH 06/23] update reqs --- api/tacticalrmm/requirements.txt | 24 ++++++++++++------------ api/tacticalrmm/tacticalrmm/settings.py | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index ee4d3c20..adafa7b3 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -2,10 +2,10 @@ adrf==0.1.7 asgiref==3.8.1 celery==5.4.0 certifi==2024.8.30 -cffi==1.17.0 +cffi==1.17.1 channels==4.1.0 channels_redis==4.2.0 -cryptography==42.0.8 +cryptography==43.0.1 Django==4.2.16 django-cors-headers==4.4.0 django-filter==24.3 @@ -15,11 +15,11 @@ drf-spectacular==0.27.2 hiredis==2.3.2 kombu==5.3.7 meshctrl==0.1.15 -msgpack==1.0.8 +msgpack==1.1.0 nats-py==2.9.0 packaging==24.1 psutil==6.0.0 -psycopg[binary]==3.2.1 +psycopg[binary]==3.2.3 pycparser==2.22 pycryptodome==3.20.0 pyotp==2.9.0 @@ -29,19 +29,19 @@ qrcode==7.4.2 redis==5.0.8 requests==2.32.3 six==1.16.0 -sqlparse==0.5.0 +sqlparse==0.5.1 twilio==8.13.0 -urllib3==2.2.2 -uvicorn[standard]==0.30.6 -uWSGI==2.0.26 +urllib3==2.2.3 +uvicorn[standard]==0.31.0 +uWSGI==2.0.27 validators==0.24.0 vine==5.1.0 -websockets==13.0.1 -zipp==3.20.1 -pandas==2.2.2 +websockets==13.1 +zipp==3.20.2 +pandas==2.2.3 kaleido==0.2.1 jinja2==3.1.4 markdown==3.7 -plotly==5.24.0 +plotly==5.24.1 weasyprint==62.3 ocxsect==0.1.5 \ No newline at end of file diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index d86b74a4..a860f4f3 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -81,10 +81,10 @@ INSTALL_DENO_URL = "" DENO_DEFAULT_PERMISSIONS = "--allow-all" # for the update script, bump when need to recreate venv -PIP_VER = "44" +PIP_VER = "45" -SETUPTOOLS_VER = "70.2.0" -WHEEL_VER = "0.43.0" +SETUPTOOLS_VER = "75.1.0" +WHEEL_VER = "0.44.0" AGENT_BASE_URL = "https://agents.tacticalrmm.com" From 71a2e3cfcac418fd3a8ef02939a56b0474ae10c7 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:27:14 +0000 Subject: [PATCH 07/23] remove extra mgmt cmd --- restore.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/restore.sh b/restore.sh index e48157ea..ffb1cc14 100755 --- a/restore.sh +++ b/restore.sh @@ -494,7 +494,6 @@ echo "Running management commands...please wait..." API=$(python manage.py get_config api) WEB_VERSION=$(python manage.py get_config webversion) FRONTEND=$(python manage.py get_config webdomain) -webdomain=$(python manage.py get_config webdomain) meshdomain=$(python manage.py get_config meshdomain) WEBTAR_URL=$(python manage.py get_webtar_url) CERT_PUB_KEY=$(python manage.py get_config certfile) @@ -620,9 +619,9 @@ sudo ln -s /etc/nginx/sites-available/rmm.conf /etc/nginx/sites-enabled/rmm.conf HAS_11=$(grep 127.0.1.1 /etc/hosts) if [[ $HAS_11 ]]; then - sudo sed -i "/127.0.1.1/s/$/ ${API} ${webdomain} ${meshdomain}/" /etc/hosts + sudo sed -i "/127.0.1.1/s/$/ ${API} ${FRONTEND} ${meshdomain}/" /etc/hosts else - echo "127.0.1.1 ${API} ${webdomain} ${meshdomain}" | sudo tee --append /etc/hosts >/dev/null + echo "127.0.1.1 ${API} ${FRONTEND} ${meshdomain}" | sudo tee --append /etc/hosts >/dev/null fi sudo systemctl enable nats.service From 6fa16e1a5e3e0df0f7702f852bacaca1521b84da Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Sat, 5 Oct 2024 20:25:40 +0000 Subject: [PATCH 08/23] update req --- api/tacticalrmm/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index adafa7b3..94d23ea1 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -25,7 +25,7 @@ pycryptodome==3.20.0 pyotp==2.9.0 pyparsing==3.1.4 python-ipware==2.0.2 -qrcode==7.4.2 +qrcode==8.0 redis==5.0.8 requests==2.32.3 six==1.16.0 From 9688dbdb366d5bbd0ad8820c572490e575ce8178 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Sun, 6 Oct 2024 01:49:27 +0000 Subject: [PATCH 09/23] add saving output of bulk script to custom field and agent note closes #1845 --- api/tacticalrmm/agents/models.py | 9 +++++++++ api/tacticalrmm/agents/views.py | 13 +++++++++++++ api/tacticalrmm/apiv3/views.py | 33 ++++++++++++++++++++++++++++++-- api/tacticalrmm/scripts/tasks.py | 12 ++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index 101795fc..446dc933 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -1122,6 +1122,15 @@ class AgentHistory(models.Model): on_delete=models.SET_NULL, ) script_results = models.JSONField(null=True, blank=True) + custom_field = models.ForeignKey( + "core.CustomField", + null=True, + blank=True, + related_name="history", + on_delete=models.SET_NULL, + ) + collector_all_output = models.BooleanField(default=False) + save_to_agent_note = models.BooleanField(default=False) def __str__(self) -> str: return f"{self.agent.hostname} - {self.type}" diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index f3a2808d..b3ac5bb5 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -1008,6 +1008,16 @@ def bulk(request): elif request.data["mode"] == "script": script = get_object_or_404(Script, pk=request.data["script"]) + # prevent API from breaking for those who haven't updated payload + try: + custom_field_pk = request.data["custom_field"] + collector_all_output = request.data["collector_all_output"] + save_to_agent_note = request.data["save_to_agent_note"] + except KeyError: + custom_field_pk = None + collector_all_output = False + save_to_agent_note = False + bulk_script_task.delay( script_pk=script.pk, agent_pks=agents, @@ -1016,6 +1026,9 @@ def bulk(request): username=request.user.username[:50], run_as_user=request.data["run_as_user"], env_vars=request.data["env_vars"], + custom_field_pk=custom_field_pk, + collector_all_output=collector_all_output, + save_to_agent_note=save_to_agent_note, ) return Response(f"{script.name} will now be run on {len(agents)} agents. {ht}") diff --git a/api/tacticalrmm/apiv3/views.py b/api/tacticalrmm/apiv3/views.py index a2ab0b64..7f1b0910 100644 --- a/api/tacticalrmm/apiv3/views.py +++ b/api/tacticalrmm/apiv3/views.py @@ -12,7 +12,7 @@ from rest_framework.response import Response from rest_framework.views import APIView from accounts.models import User -from agents.models import Agent, AgentHistory +from agents.models import Agent, AgentHistory, Note from agents.serializers import AgentHistorySerializer from alerts.tasks import cache_agents_alert_template from apiv3.utils import get_agent_config @@ -40,6 +40,7 @@ from tacticalrmm.constants import ( AuditActionType, AuditObjType, CheckStatus, + CustomFieldModel, DebugLogType, GoArch, MeshAgentIdent, @@ -581,11 +582,39 @@ class AgentHistoryResult(APIView): request.data["script_results"]["retcode"] = 1 hist = get_object_or_404( - AgentHistory.objects.filter(agent__agent_id=agentid), pk=pk + AgentHistory.objects.select_related("custom_field").filter( + agent__agent_id=agentid + ), + pk=pk, ) s = AgentHistorySerializer(instance=hist, data=request.data, partial=True) s.is_valid(raise_exception=True) s.save() + + if hist.custom_field: + if hist.custom_field.model == CustomFieldModel.AGENT: + field = hist.custom_field.get_or_create_field_value(hist.agent) + elif hist.custom_field.model == CustomFieldModel.CLIENT: + field = hist.custom_field.get_or_create_field_value(hist.agent.client) + elif hist.custom_field.model == CustomFieldModel.SITE: + field = hist.custom_field.get_or_create_field_value(hist.agent.site) + + r = request.data["script_results"]["stdout"] + value = ( + r.strip() + if hist.collector_all_output + else r.strip().split("\n")[-1].strip() + ) + + field.save_to_field(value) + + if hist.save_to_agent_note: + Note.objects.create( + agent=hist.agent, + user=request.user, + note=request.data["script_results"]["stdout"], + ) + return Response("ok") diff --git a/api/tacticalrmm/scripts/tasks.py b/api/tacticalrmm/scripts/tasks.py index 81ff379e..29767a8a 100644 --- a/api/tacticalrmm/scripts/tasks.py +++ b/api/tacticalrmm/scripts/tasks.py @@ -54,12 +54,21 @@ def bulk_script_task( username: str, run_as_user: bool = False, env_vars: list[str] = [], + custom_field_pk: int | None, + collector_all_output: bool = False, + save_to_agent_note: bool = False, ) -> None: script = Script.objects.get(pk=script_pk) # always override if set on script model if script.run_as_user: run_as_user = True + custom_field = None + if custom_field_pk: + from core.models import CustomField + + custom_field = CustomField.objects.get(pk=custom_field_pk) + items = [] agent: "Agent" for agent in Agent.objects.filter(pk__in=agent_pks): @@ -68,6 +77,9 @@ def bulk_script_task( type=AgentHistoryType.SCRIPT_RUN, script=script, username=username, + custom_field=custom_field, + collector_all_output=collector_all_output, + save_to_agent_note=save_to_agent_note, ) data = { "func": "runscriptfull", From da76a203452dc1e481e6c983a89288bb31295f45 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Sun, 6 Oct 2024 03:06:31 +0000 Subject: [PATCH 10/23] forgot to add migration --- ...nthistory_collector_all_output_and_more.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 api/tacticalrmm/agents/migrations/0060_agenthistory_collector_all_output_and_more.py diff --git a/api/tacticalrmm/agents/migrations/0060_agenthistory_collector_all_output_and_more.py b/api/tacticalrmm/agents/migrations/0060_agenthistory_collector_all_output_and_more.py new file mode 100644 index 00000000..28ad64d0 --- /dev/null +++ b/api/tacticalrmm/agents/migrations/0060_agenthistory_collector_all_output_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.16 on 2024-10-05 20:39 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0047_alter_coresettings_notify_on_warning_alerts"), + ("agents", "0059_alter_agenthistory_id"), + ] + + operations = [ + migrations.AddField( + model_name="agenthistory", + name="collector_all_output", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="agenthistory", + name="custom_field", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="history", + to="core.customfield", + ), + ), + migrations.AddField( + model_name="agenthistory", + name="save_to_agent_note", + field=models.BooleanField(default=False), + ), + ] From 787a2c507157857ea6ee270ebc2280810b2b6fe3 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Sun, 6 Oct 2024 05:58:15 +0000 Subject: [PATCH 11/23] add separate perms for global keystore #1984 --- ..._role_can_edit_global_keystore_and_more.py | 23 +++++++++++++++++++ api/tacticalrmm/accounts/models.py | 2 ++ api/tacticalrmm/core/permissions.py | 8 +++++++ api/tacticalrmm/core/views.py | 5 ++-- 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 api/tacticalrmm/accounts/migrations/0038_role_can_edit_global_keystore_and_more.py diff --git a/api/tacticalrmm/accounts/migrations/0038_role_can_edit_global_keystore_and_more.py b/api/tacticalrmm/accounts/migrations/0038_role_can_edit_global_keystore_and_more.py new file mode 100644 index 00000000..bc1b495a --- /dev/null +++ b/api/tacticalrmm/accounts/migrations/0038_role_can_edit_global_keystore_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.16 on 2024-10-06 05:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0037_role_can_run_server_scripts_role_can_use_webterm"), + ] + + operations = [ + migrations.AddField( + model_name="role", + name="can_edit_global_keystore", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="role", + name="can_view_global_keystore", + field=models.BooleanField(default=False), + ), + ] diff --git a/api/tacticalrmm/accounts/models.py b/api/tacticalrmm/accounts/models.py index f8c0437d..ee66f8a6 100644 --- a/api/tacticalrmm/accounts/models.py +++ b/api/tacticalrmm/accounts/models.py @@ -131,6 +131,8 @@ class Role(BaseAuditModel): can_manage_customfields = models.BooleanField(default=False) can_run_server_scripts = models.BooleanField(default=False) can_use_webterm = models.BooleanField(default=False) + can_view_global_keystore = models.BooleanField(default=False) + can_edit_global_keystore = models.BooleanField(default=False) # checks can_list_checks = models.BooleanField(default=False) diff --git a/api/tacticalrmm/core/permissions.py b/api/tacticalrmm/core/permissions.py index bb6cb7ff..eb59175d 100644 --- a/api/tacticalrmm/core/permissions.py +++ b/api/tacticalrmm/core/permissions.py @@ -11,6 +11,14 @@ class CoreSettingsPerms(permissions.BasePermission): return _has_perm(r, "can_edit_core_settings") +class GlobalKeyStorePerms(permissions.BasePermission): + def has_permission(self, r, view) -> bool: + if r.method == "GET": + return _has_perm(r, "can_view_global_keystore") + + return _has_perm(r, "can_edit_global_keystore") + + class URLActionPerms(permissions.BasePermission): def has_permission(self, r, view) -> bool: if r.method in {"GET", "PATCH"}: diff --git a/api/tacticalrmm/core/views.py b/api/tacticalrmm/core/views.py index 0ca5d9c5..d2915fa9 100644 --- a/api/tacticalrmm/core/views.py +++ b/api/tacticalrmm/core/views.py @@ -43,6 +43,7 @@ from .permissions import ( CodeSignPerms, CoreSettingsPerms, CustomFieldPerms, + GlobalKeyStorePerms, RunServerScriptPerms, ServerMaintPerms, URLActionPerms, @@ -310,7 +311,7 @@ class CodeSign(APIView): class GetAddKeyStore(APIView): - permission_classes = [IsAuthenticated, CoreSettingsPerms] + permission_classes = [IsAuthenticated, GlobalKeyStorePerms] def get(self, request): keys = GlobalKVStore.objects.all() @@ -325,7 +326,7 @@ class GetAddKeyStore(APIView): class UpdateDeleteKeyStore(APIView): - permission_classes = [IsAuthenticated, CoreSettingsPerms] + permission_classes = [IsAuthenticated, GlobalKeyStorePerms] def put(self, request, pk): key = get_object_or_404(GlobalKVStore, pk=pk) From 8ab23c8cd96a58dd5cedaa263c532735fd345f99 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:51:43 +0000 Subject: [PATCH 12/23] update reqs --- api/tacticalrmm/requirements.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index 94d23ea1..66e5b715 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -1,4 +1,3 @@ -adrf==0.1.7 asgiref==3.8.1 celery==5.4.0 certifi==2024.8.30 @@ -7,7 +6,7 @@ channels==4.1.0 channels_redis==4.2.0 cryptography==43.0.1 Django==4.2.16 -django-cors-headers==4.4.0 +django-cors-headers==4.5.0 django-filter==24.3 django-rest-knox==4.2.0 djangorestframework==3.15.2 @@ -21,7 +20,7 @@ packaging==24.1 psutil==6.0.0 psycopg[binary]==3.2.3 pycparser==2.22 -pycryptodome==3.20.0 +pycryptodome==3.21.0 pyotp==2.9.0 pyparsing==3.1.4 python-ipware==2.0.2 @@ -32,7 +31,7 @@ six==1.16.0 sqlparse==0.5.1 twilio==8.13.0 urllib3==2.2.3 -uvicorn[standard]==0.31.0 +uvicorn[standard]==0.31.1 uWSGI==2.0.27 validators==0.24.0 vine==5.1.0 From fb89922ecf97f34d64aa509a0652a5021a7211f8 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:24:07 +0000 Subject: [PATCH 13/23] format --- .../tactical-meshcentral/entrypoint.sh | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docker/containers/tactical-meshcentral/entrypoint.sh b/docker/containers/tactical-meshcentral/entrypoint.sh index 795b03ed..5d2dd8a7 100644 --- a/docker/containers/tactical-meshcentral/entrypoint.sh +++ b/docker/containers/tactical-meshcentral/entrypoint.sh @@ -25,7 +25,8 @@ if [ ! -f "/home/node/app/meshcentral-data/config.json" ] || [[ "${MESH_PERSISTE encoded_uri=$(node -p "encodeURI('mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@${MONGODB_HOST}:${MONGODB_PORT}')") - mesh_config="$(cat << EOF + mesh_config="$( + cat < /home/node/app/meshcentral-data/config.json + echo "${mesh_config}" >/home/node/app/meshcentral-data/config.json fi node node_modules/meshcentral --createaccount ${MESH_USER} --pass ${MESH_PASS} --email example@example.com node node_modules/meshcentral --adminaccount ${MESH_USER} if [ ! -f "${TACTICAL_DIR}/tmp/mesh_token" ]; then - mesh_token=$(node node_modules/meshcentral --logintokenkey) + mesh_token=$(node node_modules/meshcentral --logintokenkey) - if [[ ${#mesh_token} -eq 160 ]]; then - echo ${mesh_token} > /opt/tactical/tmp/mesh_token - else - echo "Failed to generate mesh token. Fix the error and restart the mesh container" - fi + if [[ ${#mesh_token} -eq 160 ]]; then + echo ${mesh_token} >/opt/tactical/tmp/mesh_token + else + echo "Failed to generate mesh token. Fix the error and restart the mesh container" + fi fi # wait for nginx container -until (echo > /dev/tcp/"${NGINX_HOST_IP}"/${NGINX_HOST_PORT}) &> /dev/null; do +until (echo >/dev/tcp/"${NGINX_HOST_IP}"/${NGINX_HOST_PORT}) &>/dev/null; do echo "waiting for nginx to start..." sleep 5 done From fc4b651e46be256a94e05c1beeca9c2ac89c7a03 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:25:22 +0000 Subject: [PATCH 14/23] change to match standard install --- docker/containers/tactical-meshcentral/entrypoint.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/containers/tactical-meshcentral/entrypoint.sh b/docker/containers/tactical-meshcentral/entrypoint.sh index 5d2dd8a7..74e9051b 100644 --- a/docker/containers/tactical-meshcentral/entrypoint.sh +++ b/docker/containers/tactical-meshcentral/entrypoint.sh @@ -40,8 +40,7 @@ if [ ! -f "/home/node/app/meshcentral-data/config.json" ] || [[ "${MESH_PERSISTE "aliasPort": 443, "allowLoginToken": true, "allowFraming": true, - "_agentPing": 60, - "agentPong": 300, + "agentPing": 35, "allowHighQualityDesktop": true, "agentCoreDump": false, "compression": ${MESH_COMPRESSION_ENABLED}, From dfccbceea65640922172325a93b30d0eff023f8d Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:28:38 +0000 Subject: [PATCH 15/23] bump mesh --- api/tacticalrmm/tacticalrmm/settings.py | 4 ++-- docker/containers/tactical-meshcentral/dockerfile | 2 +- install.sh | 4 ++-- restore.sh | 4 ++-- update.sh | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index a860f4f3..ebad1553 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -21,7 +21,7 @@ MAC_UNINSTALL = BASE_DIR / "core" / "mac_uninstall.sh" AUTH_USER_MODEL = "accounts.User" # latest release -TRMM_VERSION = "0.19.3" +TRMM_VERSION = "0.19.4-dev" # https://github.com/amidaware/tacticalrmm-web WEB_VERSION = "0.101.48" @@ -33,7 +33,7 @@ APP_VER = "0.0.194" # https://github.com/amidaware/rmmagent LATEST_AGENT_VER = "2.8.0" -MESH_VER = "1.1.21" +MESH_VER = "1.1.32" NATS_SERVER_VER = "2.10.21" diff --git a/docker/containers/tactical-meshcentral/dockerfile b/docker/containers/tactical-meshcentral/dockerfile index 0afde700..a6b9ef64 100644 --- a/docker/containers/tactical-meshcentral/dockerfile +++ b/docker/containers/tactical-meshcentral/dockerfile @@ -14,7 +14,7 @@ RUN MESH_VER=$(grep -o 'MESH_VER.*' /tmp/settings.py | cut -d'"' -f 2) && \ cat > package.json < Date: Tue, 15 Oct 2024 08:31:06 +0000 Subject: [PATCH 16/23] show more detail in checks tab #2014 --- api/tacticalrmm/checks/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/tacticalrmm/checks/models.py b/api/tacticalrmm/checks/models.py index e6fe3bd6..a74187dd 100644 --- a/api/tacticalrmm/checks/models.py +++ b/api/tacticalrmm/checks/models.py @@ -365,9 +365,11 @@ class CheckResult(models.Model): if len(self.history) > 15: self.history = self.history[-15:] - update_fields.extend(["history"]) + update_fields.extend(["history", "more_info"]) avg = int(mean(self.history)) + txt = "Memory Usage" if check.check_type == CheckType.MEMORY else "CPU Load" + self.more_info = f"Average {txt}: {avg}%" if check.error_threshold and avg > check.error_threshold: self.status = CheckStatus.FAILING From 527859967593c1890f3a69055b46471be91685ba Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Thu, 17 Oct 2024 20:26:04 +0000 Subject: [PATCH 17/23] update nats --- api/tacticalrmm/tacticalrmm/settings.py | 2 +- docker/containers/tactical-nats/dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index ebad1553..c1311fad 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -35,7 +35,7 @@ LATEST_AGENT_VER = "2.8.0" MESH_VER = "1.1.32" -NATS_SERVER_VER = "2.10.21" +NATS_SERVER_VER = "2.10.22" # Install Nushell on the agent # https://github.com/nushell/nushell diff --git a/docker/containers/tactical-nats/dockerfile b/docker/containers/tactical-nats/dockerfile index 8129b1c6..171ca6f1 100644 --- a/docker/containers/tactical-nats/dockerfile +++ b/docker/containers/tactical-nats/dockerfile @@ -1,4 +1,4 @@ -FROM nats:2.10.21-alpine +FROM nats:2.10.22-alpine ENV TACTICAL_DIR /opt/tactical ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready From 85166b6e8b3d966b97da026d8e2199f90679f8b4 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Thu, 17 Oct 2024 20:27:15 +0000 Subject: [PATCH 18/23] add run on server option to run script endpoint #1923 --- api/tacticalrmm/agents/tests/test_agents.py | 63 ++++++++++++++++++++- api/tacticalrmm/agents/views.py | 27 +++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/api/tacticalrmm/agents/tests/test_agents.py b/api/tacticalrmm/agents/tests/test_agents.py index addcaec0..13e41bd5 100644 --- a/api/tacticalrmm/agents/tests/test_agents.py +++ b/api/tacticalrmm/agents/tests/test_agents.py @@ -2,7 +2,7 @@ import json import os from itertools import cycle from typing import TYPE_CHECKING -from unittest.mock import patch +from unittest.mock import PropertyMock, patch from zoneinfo import ZoneInfo from django.conf import settings @@ -768,6 +768,67 @@ class TestAgentViews(TacticalTestCase): self.assertEqual(Note.objects.get(agent=self.agent).note, "ok") + # test run on server + with patch("core.utils.run_server_script") as mock_run_server_script: + mock_run_server_script.return_value = ("output", "error", 1.23456789, 0) + data = { + "script": script.pk, + "output": "wait", + "args": ["arg1", "arg2"], + "timeout": 15, + "run_as_user": False, + "env_vars": ["key1=val1", "key2=val2"], + "run_on_server": True, + } + + r = self.client.post(url, data, format="json") + self.assertEqual(r.status_code, 200) + hist = AgentHistory.objects.filter(agent=self.agent, script=script).last() + if not hist: + raise AgentHistory.DoesNotExist + + mock_run_server_script.assert_called_with( + body=script.script_body, + args=script.parse_script_args(self.agent, script.shell, data["args"]), + env_vars=script.parse_script_env_vars( + self.agent, script.shell, data["env_vars"] + ), + shell=script.shell, + timeout=18, + ) + + expected_ret = { + "stdout": "output", + "stderr": "error", + "execution_time": "1.2346", + "retcode": 0, + } + + self.assertEqual(r.data, expected_ret) + + hist.refresh_from_db() + expected_script_results = {**expected_ret, "id": hist.pk} + self.assertEqual(hist.script_results, expected_script_results) + + # test run on server with server scripts disabled + with patch( + "core.models.CoreSettings.server_scripts_enabled", + new_callable=PropertyMock, + ) as server_scripts_enabled: + server_scripts_enabled.return_value = False + + data = { + "script": script.pk, + "output": "wait", + "args": ["arg1", "arg2"], + "timeout": 15, + "run_as_user": False, + "env_vars": ["key1=val1", "key2=val2"], + "run_on_server": True, + } + r = self.client.post(url, data, format="json") + self.assertEqual(r.status_code, 400) + def test_get_notes(self): url = f"{base_url}/notes/" diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index b3ac5bb5..78ca3181 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -768,6 +768,10 @@ def run_script(request, agent_id): run_as_user: bool = request.data["run_as_user"] env_vars: list[str] = request.data["env_vars"] req_timeout = int(request.data["timeout"]) + 3 + run_on_server: bool | None = request.data.get("run_on_server") + + if run_on_server and not get_core_settings().server_scripts_enabled: + return notify_error("This feature is disabled.") AuditLog.audit_script_run( username=request.user.username, @@ -784,6 +788,29 @@ def run_script(request, agent_id): ) history_pk = hist.pk + if run_on_server: + from core.utils import run_server_script + + r = run_server_script( + body=script.script_body, + args=script.parse_script_args(agent, script.shell, args), + env_vars=script.parse_script_env_vars(agent, script.shell, env_vars), + shell=script.shell, + timeout=req_timeout, + ) + + ret = { + "stdout": r[0], + "stderr": r[1], + "execution_time": "{:.4f}".format(r[2]), + "retcode": r[3], + } + + hist.script_results = {**ret, "id": history_pk} + hist.save(update_fields=["script_results"]) + + return Response(ret) + if output == "wait": r = agent.run_script( scriptpk=script.pk, From 4166e92754f9326dcd4c067c1ddfb1dfddfc98c9 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Fri, 18 Oct 2024 06:20:13 +0000 Subject: [PATCH 19/23] don't trim script whitespace --- api/tacticalrmm/scripts/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/tacticalrmm/scripts/serializers.py b/api/tacticalrmm/scripts/serializers.py index 6c7d80e0..12d12f32 100644 --- a/api/tacticalrmm/scripts/serializers.py +++ b/api/tacticalrmm/scripts/serializers.py @@ -48,6 +48,7 @@ class ScriptSerializer(ModelSerializer): "run_as_user", "env_vars", ] + extra_kwargs = {"script_body": {"trim_whitespace": False}} class ScriptCheckSerializer(ModelSerializer): @@ -63,3 +64,4 @@ class ScriptSnippetSerializer(ModelSerializer): class Meta: model = ScriptSnippet fields = "__all__" + extra_kwargs = {"code": {"trim_whitespace": False}} From 15ec7173aa4297b7755eb50eb481d996626acf3f Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Wed, 23 Oct 2024 00:56:46 +0000 Subject: [PATCH 20/23] bump web vers --- api/tacticalrmm/tacticalrmm/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index c1311fad..5379fa89 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -24,11 +24,11 @@ AUTH_USER_MODEL = "accounts.User" TRMM_VERSION = "0.19.4-dev" # https://github.com/amidaware/tacticalrmm-web -WEB_VERSION = "0.101.48" +WEB_VERSION = "0.101.49" # bump this version everytime vue code is changed # to alert user they need to manually refresh their browser -APP_VER = "0.0.194" +APP_VER = "0.0.195" # https://github.com/amidaware/rmmagent LATEST_AGENT_VER = "2.8.0" From b618cbdf7cefb73188a5c258aa3e19f62cd0ed94 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Wed, 23 Oct 2024 00:56:57 +0000 Subject: [PATCH 21/23] update reqs --- api/tacticalrmm/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index 66e5b715..4114ae46 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -4,7 +4,7 @@ certifi==2024.8.30 cffi==1.17.1 channels==4.1.0 channels_redis==4.2.0 -cryptography==43.0.1 +cryptography==43.0.3 Django==4.2.16 django-cors-headers==4.5.0 django-filter==24.3 From d36fadf3ca12577add9ece0839479029e65ccd58 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:22:48 +0000 Subject: [PATCH 22/23] update wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b71b562..f02f891d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Demo database resets every hour. A lot of features are disabled for obvious reas ## Mac agent versions supported -- 64 bit Intel and Apple Silicon (M1, M2) +- 64 bit Intel and Apple Silicon (M-Series) ## Installation / Backup / Restore / Usage From e5c355e8f901fb004f67e18e4dee3127efbae5cf Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:23:00 +0000 Subject: [PATCH 23/23] bump version --- api/tacticalrmm/tacticalrmm/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index 5379fa89..52363243 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -21,7 +21,7 @@ MAC_UNINSTALL = BASE_DIR / "core" / "mac_uninstall.sh" AUTH_USER_MODEL = "accounts.User" # latest release -TRMM_VERSION = "0.19.4-dev" +TRMM_VERSION = "0.19.4" # https://github.com/amidaware/tacticalrmm-web WEB_VERSION = "0.101.49"