diff --git a/_modules/win_agent.py b/_modules/win_agent.py index 3c3b301b..b631a3a5 100644 --- a/_modules/win_agent.py +++ b/_modules/win_agent.py @@ -26,15 +26,22 @@ def get_services(): return [svc.as_dict() for svc in psutil.win_service_iter()] -def run_python_script(filename, timeout): +def run_python_script(filename, timeout, script_type="userdefined"): python_bin = os.path.join("c:\\salt\\bin", "python.exe") file_path = os.path.join("c:\\windows\\temp", filename) - __salt__["cp.get_file"]( - "salt://scripts/userdefined/{0}".format(filename), file_path - ) - return __salt__["cmd.run_all"]( - "{0} {1}".format(python_bin, file_path), timeout=timeout - ) + + if os.path.exists(file_path): + try: + os.remove(file_path) + except: + pass + + if script_type == "userdefined": + __salt__["cp.get_file"](f"salt://scripts/userdefined/{filename}", file_path) + else: + __salt__["cp.get_file"](f"salt://scripts/{filename}", file_path) + + return __salt__["cmd.run_all"](f"{python_bin} {file_path}", timeout=timeout) def uninstall_agent(): diff --git a/api/tacticalrmm/autotasks/serializers.py b/api/tacticalrmm/autotasks/serializers.py index f2a96a60..c612f325 100644 --- a/api/tacticalrmm/autotasks/serializers.py +++ b/api/tacticalrmm/autotasks/serializers.py @@ -35,7 +35,7 @@ class AutoTaskSerializer(serializers.ModelSerializer): class TaskRunnerScriptField(serializers.ModelSerializer): class Meta: model = Script - fields = ["id", "filepath", "filename", "shell"] + fields = ["id", "filepath", "filename", "shell", "script_type"] class TaskRunnerGetSerializer(serializers.ModelSerializer): diff --git a/api/tacticalrmm/core/management/commands/post_update_tasks.py b/api/tacticalrmm/core/management/commands/post_update_tasks.py index a058c403..249befc8 100644 --- a/api/tacticalrmm/core/management/commands/post_update_tasks.py +++ b/api/tacticalrmm/core/management/commands/post_update_tasks.py @@ -1,3 +1,5 @@ +import os +import subprocess from time import sleep from django.core.management.base import BaseCommand @@ -19,3 +21,47 @@ class Command(BaseCommand): for chunk in chunks: r = Agent.salt_batch_async(minions=chunk, func="saltutil.sync_modules") sleep(5) + + has_old_config = True + rmm_conf = "/etc/nginx/sites-available/rmm.conf" + if os.path.exists(rmm_conf): + with open(rmm_conf) as f: + for line in f: + if "location" and "builtin" in line: + has_old_config = False + break + + if has_old_config: + new_conf = """ + location /builtin/ { + internal; + add_header "Access-Control-Allow-Origin" "https://rmm.yourwebsite.com"; + alias /srv/salt/scripts/; + } + """ + self.stdout.write(self.style.ERROR("*" * 100)) + self.stdout.write("\n") + self.stdout.write( + self.style.ERROR( + "WARNING: A recent update requires you to manually edit your nginx config" + ) + ) + self.stdout.write("\n") + self.stdout.write( + self.style.ERROR("Please add the following location block to ") + + self.style.WARNING(rmm_conf) + ) + self.stdout.write(self.style.SUCCESS(new_conf)) + self.stdout.write("\n") + self.stdout.write( + self.style.ERROR( + "Make sure to replace rmm.yourwebsite.com with your domain" + ) + ) + self.stdout.write( + self.style.ERROR("After editing, restart nginx with the command ") + + self.style.WARNING("sudo systemctl restart nginx") + ) + self.stdout.write("\n") + self.stdout.write(self.style.ERROR("*" * 100)) + input("Press Enter to continue...") diff --git a/api/tacticalrmm/scripts/migrations/0002_script_script_type.py b/api/tacticalrmm/scripts/migrations/0002_script_script_type.py new file mode 100644 index 00000000..16287010 --- /dev/null +++ b/api/tacticalrmm/scripts/migrations/0002_script_script_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2020-08-16 20:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('scripts', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='script', + name='script_type', + field=models.CharField(choices=[('userdefined', 'User Defined'), ('builtin', 'Built In')], default='userdefined', max_length=100), + ), + ] diff --git a/api/tacticalrmm/scripts/models.py b/api/tacticalrmm/scripts/models.py index d0a87dde..7a70eddb 100644 --- a/api/tacticalrmm/scripts/models.py +++ b/api/tacticalrmm/scripts/models.py @@ -6,6 +6,11 @@ SCRIPT_SHELLS = [ ("python", "Python"), ] +SCRIPT_TYPES = [ + ("userdefined", "User Defined"), + ("builtin", "Built In"), +] + class Script(models.Model): name = models.CharField(max_length=255) @@ -14,6 +19,9 @@ class Script(models.Model): shell = models.CharField( max_length=100, choices=SCRIPT_SHELLS, default="powershell" ) + script_type = models.CharField( + max_length=100, choices=SCRIPT_TYPES, default="userdefined" + ) def __str__(self): return self.filename @@ -21,11 +29,17 @@ class Script(models.Model): @property def filepath(self): # for the windows agent when using 'salt-call' - return f"salt://scripts//userdefined//{self.filename}" + if self.script_type == "userdefined": + return f"salt://scripts//userdefined//{self.filename}" + else: + return f"salt://scripts//{self.filename}" @property def file(self): - return f"/srv/salt/scripts/userdefined/{self.filename}" + if self.script_type == "userdefined": + return f"/srv/salt/scripts/userdefined/{self.filename}" + else: + return f"/srv/salt/scripts/{self.filename}" @property def code(self): diff --git a/api/tacticalrmm/scripts/views.py b/api/tacticalrmm/scripts/views.py index 4a7c048a..cd71dfd7 100644 --- a/api/tacticalrmm/scripts/views.py +++ b/api/tacticalrmm/scripts/views.py @@ -1,4 +1,5 @@ import os +from loguru import logger from django.shortcuts import get_object_or_404 from django.conf import settings @@ -11,6 +12,9 @@ from rest_framework.parsers import FileUploadParser from .models import Script from .serializers import ScriptSerializer +from tacticalrmm.utils import notify_error + +logger.configure(**settings.LOG_CONFIG) class GetAddScripts(APIView): @@ -32,6 +36,7 @@ class GetAddScripts(APIView): "filename": filename, "description": request.data["description"], "shell": request.data["shell"], + "script_type": "userdefined", # force all uploads to be userdefined. built in scripts cannot be edited by user } serializer = ScriptSerializer(data=data, partial=True) @@ -55,6 +60,10 @@ class GetUpdateDeleteScript(APIView): def put(self, request, pk, format=None): script = get_object_or_404(Script, pk=pk) + # this will never trigger but check anyway + if script.script_type == "builtin": + return notify_error("Built in scripts cannot be edited") + data = { "name": request.data["name"], "description": request.data["description"], @@ -86,6 +95,10 @@ class GetUpdateDeleteScript(APIView): def delete(self, request, pk): script = get_object_or_404(Script, pk=pk) + # this will never trigger but check anyway + if script.script_type == "builtin": + return notify_error("Built in scripts cannot be deleted") + try: os.remove(script.file) except OSError: @@ -98,8 +111,22 @@ class GetUpdateDeleteScript(APIView): @api_view() def download(request, pk): script = get_object_or_404(Script, pk=pk) + use_nginx = False + conf = "/etc/nginx/sites-available/rmm.conf" - if settings.DEBUG: + if os.path.exists(conf): + try: + with open(conf) as f: + for line in f.readlines(): + if "location" and "builtin" in line: + use_nginx = True + break + except Exception as e: + logger.error(e) + else: + use_nginx = True + + if settings.DEBUG or not use_nginx: with open(script.file, "rb") as f: response = HttpResponse(f.read(), content_type="text/plain") response["Content-Disposition"] = f"attachment; filename={script.filename}" @@ -107,5 +134,10 @@ def download(request, pk): else: response = HttpResponse() response["Content-Disposition"] = f"attachment; filename={script.filename}" - response["X-Accel-Redirect"] = f"/saltscripts/{script.filename}" + + response["X-Accel-Redirect"] = ( + f"/saltscripts/{script.filename}" + if script.script_type == "userdefined" + else f"/builtin/{script.filename}" + ) return response diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index 5d7028c2..6558f03e 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -11,7 +11,7 @@ AUTH_USER_MODEL = "accounts.User" # bump this version everytime vue code is changed # to alert user they need to manually refresh their browser -APP_VER = "0.0.22" +APP_VER = "0.0.23" # https://github.com/wh1te909/salt LATEST_SALT_VER = "1.0.3" diff --git a/docker/api/api.conf b/docker/api/api.conf index 2a1660e3..50819692 100644 --- a/docker/api/api.conf +++ b/docker/api/api.conf @@ -46,6 +46,12 @@ http { alias /srv/salt/scripts/userdefined/; } + location /builtin/ { + internal; + add_header "Access-Control-Allow-Origin" "https://${APP_HOST}"; + alias /srv/salt/scripts/; + } + location / { uwsgi_pass tacticalrmm; include /etc/nginx/uwsgi_params; diff --git a/install.sh b/install.sh index 29cdfc18..369bc7d7 100755 --- a/install.sh +++ b/install.sh @@ -377,6 +377,12 @@ server { alias /srv/salt/scripts/userdefined/; } + location /builtin/ { + internal; + add_header "Access-Control-Allow-Origin" "https://${frontenddomain}"; + alias /srv/salt/scripts/; + } + location / { uwsgi_pass tacticalrmm; diff --git a/web/src/components/ScriptManager.vue b/web/src/components/ScriptManager.vue index d1c52a0c..1d996546 100644 --- a/web/src/components/ScriptManager.vue +++ b/web/src/components/ScriptManager.vue @@ -34,7 +34,7 @@ /> {{ truncateText(props.row.description) }} {{ props.row.filename }} {{ props.row.shell }} + {{ props.row.script_type }} @@ -127,8 +128,8 @@ export default { code: null, pagination: { rowsPerPage: 0, - sortBy: "id", - descending: false + sortBy: "script_type", + descending: true, }, columns: [ { name: "id", label: "ID", field: "id" }, @@ -137,31 +138,38 @@ export default { label: "Name", field: "name", align: "left", - sortable: true + sortable: true, }, { name: "desc", label: "Description", field: "description", align: "left", - sortable: false + sortable: false, }, { name: "file", label: "File", field: "filename", align: "left", - sortable: true + sortable: true, }, { name: "shell", - label: "Type", + label: "Shell", field: "shell", align: "left", - sortable: true - } + sortable: true, + }, + { + name: "script_type", + label: "Type", + field: "script_type", + align: "left", + sortable: true, + }, ], - visibleColumns: ["name", "desc", "file", "shell"] + visibleColumns: ["name", "desc", "file", "shell", "script_type"], }; }, methods: { @@ -181,7 +189,7 @@ export default { title: this.filename, message: `
${this.code}
`, html: true, - style: "width: 70vw; max-width: 80vw;" + style: "width: 70vw; max-width: 80vw;", }); }, deleteScript() { @@ -189,7 +197,7 @@ export default { .dialog({ title: "Delete script?", cancel: true, - ok: { label: "Delete", color: "negative" } + ok: { label: "Delete", color: "negative" }, }) .onOk(() => { axios @@ -226,16 +234,23 @@ export default { }, truncateText(txt) { return txt.length >= 60 ? txt.substring(0, 60) + "..." : txt; - } + }, + isBuiltInScript(pk) { + try { + return this.scripts.find(i => i.id === pk).script_type === "builtin" ? true : false; + } catch (e) { + return false; + } + }, }, computed: { ...mapState({ toggleScriptManager: state => state.toggleScriptManager, - scripts: state => state.scripts - }) + scripts: state => state.scripts, + }), }, mounted() { this.getScripts(); - } + }, }; \ No newline at end of file diff --git a/web/src/components/modals/scripts/ScriptModal.vue b/web/src/components/modals/scripts/ScriptModal.vue index ef82cd75..3b2837a1 100644 --- a/web/src/components/modals/scripts/ScriptModal.vue +++ b/web/src/components/modals/scripts/ScriptModal.vue @@ -10,13 +10,25 @@
Name:
- +
Description:
- +
@@ -40,6 +52,7 @@
Type:
- + @@ -75,13 +95,15 @@ \ No newline at end of file