From b6dd03138db77f803d6d1c5f8feb0d347d0c2efb Mon Sep 17 00:00:00 2001 From: wh1te909 Date: Wed, 30 Jun 2021 06:45:50 +0000 Subject: [PATCH] rework agent installation auth token to have minimal perms --- .devcontainer/entrypoint.sh | 1 + .../commands/create_installer_user.py | 18 ++++++++++++++++++ .../migrations/0023_user_is_installer_user.py | 18 ++++++++++++++++++ api/tacticalrmm/accounts/models.py | 1 + api/tacticalrmm/accounts/views.py | 2 +- api/tacticalrmm/agents/views.py | 5 ++++- api/tacticalrmm/clients/views.py | 5 ++++- api/tacticalrmm/logs/views.py | 4 +++- api/tacticalrmm/tacticalrmm/test.py | 7 +++++++ docker/containers/tactical/entrypoint.sh | 1 + install.sh | 3 ++- update.sh | 3 ++- .../components/modals/agents/InstallAgent.vue | 2 +- 13 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 api/tacticalrmm/accounts/management/commands/create_installer_user.py create mode 100644 api/tacticalrmm/accounts/migrations/0023_user_is_installer_user.py diff --git a/.devcontainer/entrypoint.sh b/.devcontainer/entrypoint.sh index 2760ef64..d29265c7 100644 --- a/.devcontainer/entrypoint.sh +++ b/.devcontainer/entrypoint.sh @@ -114,6 +114,7 @@ EOF "${VIRTUAL_ENV}"/bin/python manage.py load_chocos "${VIRTUAL_ENV}"/bin/python manage.py load_community_scripts "${VIRTUAL_ENV}"/bin/python manage.py reload_nats + "${VIRTUAL_ENV}"/bin/python manage.py create_installer_user # 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 diff --git a/api/tacticalrmm/accounts/management/commands/create_installer_user.py b/api/tacticalrmm/accounts/management/commands/create_installer_user.py new file mode 100644 index 00000000..580492fb --- /dev/null +++ b/api/tacticalrmm/accounts/management/commands/create_installer_user.py @@ -0,0 +1,18 @@ +import uuid + +from django.core.management.base import BaseCommand +from accounts.models import User + + +class Command(BaseCommand): + help = "Creates the installer user" + + def handle(self, *args, **kwargs): + if User.objects.filter(is_installer_user=True).exists(): + return + + User.objects.create_user( # type: ignore + username=uuid.uuid4().hex, + is_installer_user=True, + password=User.objects.make_random_password(60), # type: ignore + ) diff --git a/api/tacticalrmm/accounts/migrations/0023_user_is_installer_user.py b/api/tacticalrmm/accounts/migrations/0023_user_is_installer_user.py new file mode 100644 index 00000000..1b2a9a13 --- /dev/null +++ b/api/tacticalrmm/accounts/migrations/0023_user_is_installer_user.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.4 on 2021-06-30 03:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0022_user_clear_search_when_switching'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='is_installer_user', + field=models.BooleanField(default=False), + ), + ] diff --git a/api/tacticalrmm/accounts/models.py b/api/tacticalrmm/accounts/models.py index 36c0efc3..9f5aaedb 100644 --- a/api/tacticalrmm/accounts/models.py +++ b/api/tacticalrmm/accounts/models.py @@ -47,6 +47,7 @@ class User(AbstractUser, BaseAuditModel): client_tree_splitter = models.PositiveIntegerField(default=11) loading_bar_color = models.CharField(max_length=255, default="red") clear_search_when_switching = models.BooleanField(default=True) + is_installer_user = models.BooleanField(default=False) agent = models.OneToOneField( "agents.Agent", diff --git a/api/tacticalrmm/accounts/views.py b/api/tacticalrmm/accounts/views.py index 17045c8d..b01f0929 100644 --- a/api/tacticalrmm/accounts/views.py +++ b/api/tacticalrmm/accounts/views.py @@ -87,7 +87,7 @@ class GetAddUsers(APIView): permission_classes = [IsAuthenticated, AccountsPerms] def get(self, request): - users = User.objects.filter(agent=None) + users = User.objects.filter(agent=None, is_installer_user=False) return Response(UserSerializer(users, many=True).data) diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index 180b0a51..6e48e5ab 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -390,6 +390,7 @@ class Reboot(APIView): @permission_classes([IsAuthenticated, InstallAgentPerms]) def install_agent(request): from knox.models import AuthToken + from accounts.models import User from agents.utils import get_winagent_url @@ -415,8 +416,10 @@ def install_agent(request): ) download_url = get_winagent_url(arch) + installer_user = User.objects.filter(is_installer_user=True).first() + _, token = AuthToken.objects.create( - user=request.user, expiry=dt.timedelta(hours=request.data["expires"]) + user=installer_user, expiry=dt.timedelta(hours=request.data["expires"]) ) if request.data["installMethod"] == "exe": diff --git a/api/tacticalrmm/clients/views.py b/api/tacticalrmm/clients/views.py index 05eae4a2..409d3474 100644 --- a/api/tacticalrmm/clients/views.py +++ b/api/tacticalrmm/clients/views.py @@ -251,16 +251,19 @@ class AgentDeployment(APIView): def post(self, request): from knox.models import AuthToken + from accounts.models import User client = get_object_or_404(Client, pk=request.data["client"]) site = get_object_or_404(Site, pk=request.data["site"]) + installer_user = User.objects.filter(is_installer_user=True).first() + expires = dt.datetime.strptime( request.data["expires"], "%Y-%m-%d %H:%M" ).astimezone(pytz.timezone("UTC")) now = djangotime.now() delta = expires - now - obj, token = AuthToken.objects.create(user=request.user, expiry=delta) + obj, token = AuthToken.objects.create(user=installer_user, expiry=delta) flags = { "power": request.data["power"], diff --git a/api/tacticalrmm/logs/views.py b/api/tacticalrmm/logs/views.py index c34baaf7..a85bb601 100644 --- a/api/tacticalrmm/logs/views.py +++ b/api/tacticalrmm/logs/views.py @@ -105,7 +105,9 @@ class FilterOptionsAuditLog(APIView): if request.data["type"] == "user": users = User.objects.filter( - username__icontains=request.data["pattern"], agent=None + username__icontains=request.data["pattern"], + agent=None, + is_installer_user=False, ) return Response(UserSerializer(users, many=True).data) diff --git a/api/tacticalrmm/tacticalrmm/test.py b/api/tacticalrmm/tacticalrmm/test.py index 114136fc..83cf11d0 100644 --- a/api/tacticalrmm/tacticalrmm/test.py +++ b/api/tacticalrmm/tacticalrmm/test.py @@ -1,3 +1,4 @@ +import uuid from django.test import TestCase, override_settings from model_bakery import baker from rest_framework.authtoken.models import Token @@ -20,6 +21,12 @@ class TacticalTestCase(TestCase): self.client_setup() self.client.force_authenticate(user=self.john) + User.objects.create_user( # type: ignore + username=uuid.uuid4().hex, + is_installer_user=True, + password=User.objects.make_random_password(60), # type: ignore + ) + def setup_agent_auth(self, agent): agent_user = User.objects.create_user( username=agent.agent_id, diff --git a/docker/containers/tactical/entrypoint.sh b/docker/containers/tactical/entrypoint.sh index 8a09d5c5..ad142fed 100644 --- a/docker/containers/tactical/entrypoint.sh +++ b/docker/containers/tactical/entrypoint.sh @@ -124,6 +124,7 @@ EOF python manage.py load_chocos python manage.py load_community_scripts python manage.py reload_nats + python manage.py create_installer_user # 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 diff --git a/install.sh b/install.sh index 5aab8e6f..b2601fcf 100644 --- a/install.sh +++ b/install.sh @@ -1,6 +1,6 @@ #!/bin/bash -SCRIPT_VERSION="50" +SCRIPT_VERSION="51" SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh' sudo apt install -y curl wget dirmngr gnupg lsb-release @@ -380,6 +380,7 @@ printf >&2 "\n" echo -ne "Username: " read djangousername python manage.py createsuperuser --username ${djangousername} --email ${letsemail} +python manage.py create_installer_user RANDBASE=$(python manage.py generate_totp) cls python manage.py generate_barcode ${RANDBASE} ${djangousername} ${frontenddomain} diff --git a/update.sh b/update.sh index 76a9809b..e3a87cfb 100644 --- a/update.sh +++ b/update.sh @@ -1,6 +1,6 @@ #!/bin/bash -SCRIPT_VERSION="123" +SCRIPT_VERSION="124" 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' @@ -274,6 +274,7 @@ python manage.py delete_tokens python manage.py collectstatic --no-input python manage.py reload_nats python manage.py load_chocos +python manage.py create_installer_user python manage.py post_update_tasks deactivate diff --git a/web/src/components/modals/agents/InstallAgent.vue b/web/src/components/modals/agents/InstallAgent.vue index 24c333f9..5a3b5866 100644 --- a/web/src/components/modals/agents/InstallAgent.vue +++ b/web/src/components/modals/agents/InstallAgent.vue @@ -97,7 +97,7 @@ export default { client: null, site: null, agenttype: "server", - expires: 720, + expires: 24, power: false, rdp: false, ping: false,