diff --git a/apps/applications/api.py b/apps/applications/api.py index 1e2c38450..c8499b06b 100644 --- a/apps/applications/api.py +++ b/apps/applications/api.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- # +import base64 from collections import OrderedDict import copy import logging +import tarfile import os from rest_framework import viewsets, serializers @@ -12,12 +14,13 @@ from django.shortcuts import get_object_or_404 from django.utils import timezone from django.conf import settings +from common.utils import get_object_or_none from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask from .serializers import TerminalSerializer, TerminalStatusSerializer, \ TerminalSessionSerializer, TerminalTaskSerializer from .hands import IsSuperUserOrAppUser, IsAppUser, ProxyLog, \ IsSuperUserOrAppUserOrUserReadonly -from common.utils import get_object_or_none +from .backends import get_command_store, get_replay_store, SessionCommandSerializer logger = logging.getLogger(__file__) @@ -157,11 +160,14 @@ class SessionReplayAPI(APIView): session = get_object_or_404(TerminalSession, id=session_id) record_dir = settings.CONFIG.SESSION_RECORDE_DIR date = session.date_start.strftime("%Y-%m-%d") - record_dir = os.path.join(record_dir, date) - record_filename = os.path.join(record_dir, str(session.id)) + record_dir = os.path.join(record_dir, date, str(session.id)) + record_filename = os.path.join(record_dir, "replay.tar.gz2") - if not os.path.exists(record_dir): - os.makedirs(record_dir) + if not os.path.isdir(record_dir): + try: + os.makedirs(record_dir) + except FileExistsError: + pass archive_stream = request.data.get("archive") if not archive_stream: @@ -173,3 +179,41 @@ class SessionReplayAPI(APIView): session.has_replay = True session.save() return Response({"session_id": session.id}, status=201) + + +class SessionCommandViewSet(viewsets.ViewSet): + """接受app发送来的command log, 格式如下 + { + "user": "admin", + "asset": "localhost", + "system_user": "web", + "session": "xxxxxx", + "input": "whoami", + "output": "d2hvbWFp", # base64.b64encode(s) + "timestamp": 1485238673.0 + } + + """ + command_store = get_command_store() + serializer_class = SessionCommandSerializer + permission_classes = (IsSuperUserOrAppUser,) + + def get_queryset(self): + self.command_store.all() + + def create(self, request, *args, **kwargs): + serializer = self.serializer_class(data=request.data, many=True) + if serializer.is_valid(): + ok = self.command_store.bulk_save(serializer.validated_data) + if ok: + return Response("ok", status=201) + else: + return Response("save error", status=500) + else: + print(serializer.errors) + return Response({"msg": "Not valid: {}".format(serializer.errors)}, status=401) + + def list(self, request, *args, **kwargs): + queryset = list(self.command_store.all()) + serializer = self.serializer_class(queryset, many=True) + return Response(serializer.data) diff --git a/apps/applications/backends/__init__.py b/apps/applications/backends/__init__.py new file mode 100644 index 000000000..813f7f88c --- /dev/null +++ b/apps/applications/backends/__init__.py @@ -0,0 +1,17 @@ +from importlib import import_module + +from django.conf import settings + +from .command.serializers import SessionCommandSerializer + + +def get_command_store(): + command_engine = import_module(settings.COMMAND_STORE_BACKEND) + command_store = command_engine.CommandStore() + return command_store + + +def get_replay_store(): + replay_engine = import_module(settings.RECORD_STORE_BACKEND) + replay_store = replay_engine.RecordStore() + return replay_store diff --git a/apps/applications/backends/command/__init__.py b/apps/applications/backends/command/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/backends/command/base.py b/apps/applications/backends/command/base.py new file mode 100644 index 000000000..bb6838809 --- /dev/null +++ b/apps/applications/backends/command/base.py @@ -0,0 +1,22 @@ +# coding: utf-8 +import abc + + +class CommandBase(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def save(self, command): + pass + + @abc.abstractmethod + def bulk_save(self, commands): + pass + + @abc.abstractmethod + def filter(self, date_from=None, date_to=None, user=None, + asset=None, system_user=None, command=None, session=None): + pass + + + diff --git a/apps/applications/backends/command/db.py b/apps/applications/backends/command/db.py new file mode 100644 index 000000000..1d7219e9d --- /dev/null +++ b/apps/applications/backends/command/db.py @@ -0,0 +1,70 @@ +# ~*~ coding: utf-8 ~*~ +import datetime + +from django.utils import timezone + +from .base import CommandBase + + +class CommandStore(CommandBase): + + def __init__(self): + from applications.models import SessionCommand + self.model = SessionCommand + + def save(self, command): + """ + 保存命令到数据库 + """ + + self.model.objects.create( + user=command["user"], asset=command["asset"], + system_user=command["system_user"], input=command["input"], + output=command["output"], session=command["session"], + timestamp=command["timestamp"] + ) + + def bulk_save(self, commands): + """ + 批量保存命令到数据库, command的顺序和save中一致 + """ + _commands = [] + for c in commands: + _commands.append(self.model( + user=c["user"], asset=c["asset"], system_user=c["system_user"], + input=c["input"], output=c["output"], session=c["session"], + timestamp=c["timestamp"] + )) + return self.model.objects.bulk_create(_commands) + + def filter(self, date_from=None, date_to=None, user=None, + asset=None, system_user=None, _input=None, session=None): + filter_kwargs = {} + + if date_from: + filter_kwargs['timestamp__gte'] = int(date_from.timestamp()) + else: + week_ago = timezone.now() - datetime.timedelta(days=7) + filter_kwargs['timestamp__gte'] = int(week_ago.timestamp()) + if date_to: + filter_kwargs['timestamp__lte'] = int(date_to.timestamp()) + else: + filter_kwargs['timestamp__lte'] = int(timezone.now().timestamp()) + if user: + filter_kwargs['user'] = user + if asset: + filter_kwargs['asset'] = asset + if system_user: + filter_kwargs['system_user'] = system_user + if _input: + filter_kwargs['input__icontains'] = _input + if session: + filter_kwargs['session'] = session + + queryset = self.model.objects.filter(**filter_kwargs) + return queryset + + def all(self): + """返回所有数据""" + return self.model.objects.iterator() + diff --git a/apps/applications/backends/command/models.py b/apps/applications/backends/command/models.py new file mode 100644 index 000000000..f21ff7b81 --- /dev/null +++ b/apps/applications/backends/command/models.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +import uuid +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class AbstractSessionCommand(models.Model): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + user = models.CharField(max_length=64, verbose_name=_("User")) + asset = models.CharField(max_length=128, verbose_name=_("Asset")) + system_user = models.CharField(max_length=64, verbose_name=_("System user")) + input = models.CharField(max_length=128, db_index=True, verbose_name=_("Input")) + output = models.CharField(max_length=1024, verbose_name=_("Output")) + session = models.CharField(max_length=36, db_index=True, verbose_name=_("Session")) + timestamp = models.IntegerField(db_index=True) + + class Meta: + abstract = True + + def __str__(self): + return self.input diff --git a/apps/applications/backends/command/serializers.py b/apps/applications/backends/command/serializers.py new file mode 100644 index 000000000..4b740c8f0 --- /dev/null +++ b/apps/applications/backends/command/serializers.py @@ -0,0 +1,16 @@ +# ~*~ coding: utf-8 ~*~ +from rest_framework import serializers + + +class SessionCommandSerializer(serializers.Serializer): + """使用这个类作为基础Command Log Serializer类, 用来序列化""" + + id = serializers.UUIDField(read_only=True) + user = serializers.CharField(max_length=64) + asset = serializers.CharField(max_length=128) + system_user = serializers.CharField(max_length=64) + input = serializers.CharField(max_length=128) + output = serializers.CharField(max_length=1024) + session = serializers.CharField(max_length=36) + timestamp = serializers.IntegerField() + diff --git a/apps/applications/backends/replay/__init__.py b/apps/applications/backends/replay/__init__.py new file mode 100644 index 000000000..3e0d2fd0f --- /dev/null +++ b/apps/applications/backends/replay/__init__.py @@ -0,0 +1,2 @@ +# ~*~ coding: utf-8 ~*~ + diff --git a/apps/applications/backends/replay/base.py b/apps/applications/backends/replay/base.py new file mode 100644 index 000000000..e96b8b131 --- /dev/null +++ b/apps/applications/backends/replay/base.py @@ -0,0 +1,14 @@ +# coding: utf-8 +import abc + + +class RecordBase(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def save(self, proxy_log_id, output, timestamp): + pass + + @abc.abstractmethod + def filter(self, date_from_ts=None, proxy_log_id=None): + pass diff --git a/apps/applications/backends/replay/db.py b/apps/applications/backends/replay/db.py new file mode 100644 index 000000000..e55211f65 --- /dev/null +++ b/apps/applications/backends/replay/db.py @@ -0,0 +1,31 @@ +# ~*~ coding: utf-8 ~*~ + +from .base import RecordBase +from audits.models import RecordLog + + +class RecordStore(RecordBase): + model = RecordLog + queryset = [] + + def save(self, proxy_log_id, output, timestamp): + return self.model.objects.create( + proxy_log_id=proxy_log_id, output=output, timestamp=timestamp + ) + + def filter(self, date_from_ts=None, proxy_log_id=''): + filter_kwargs = {} + + if date_from_ts: + filter_kwargs['timestamp__gte'] = date_from_ts + if proxy_log_id: + filter_kwargs['proxy_log_id'] = proxy_log_id + + if filter_kwargs: + self.queryset = self.model.objects.filter(**filter_kwargs) + return self.queryset + + def all(self): + """返回所有数据""" + return self.model.objects.all() + diff --git a/apps/applications/backends/replay/serializers.py b/apps/applications/backends/replay/serializers.py new file mode 100644 index 000000000..30a5867e1 --- /dev/null +++ b/apps/applications/backends/replay/serializers.py @@ -0,0 +1,20 @@ +# ~*~ coding: utf-8 ~*~ +import base64 +from rest_framework import serializers +from audits.models import RecordLog +from audits.backends import record_store + + +class RecordSerializer(serializers.ModelSerializer): + """使用这个类作为基础Command Log Serializer类, 用来序列化""" + class Meta: + model = RecordLog + fields = '__all__' + + def create(self, validated_data): + try: + output = validated_data['output'] + validated_data['output'] = base64.b64decode(output) + except IndexError: + pass + return record_store.save(**dict(validated_data)) diff --git a/apps/applications/models.py b/apps/applications/models.py index 7f8e14ef7..569b81516 100644 --- a/apps/applications/models.py +++ b/apps/applications/models.py @@ -6,6 +6,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from users.models import User +from .backends.command.models import AbstractSessionCommand class Terminal(models.Model): @@ -88,7 +89,7 @@ class TerminalSession(models.Model): is_finished = models.BooleanField(default=False) has_replay = models.BooleanField(default=False, verbose_name=_("Replay")) has_command = models.BooleanField(default=False, verbose_name=_("Command")) - terminal = models.IntegerField(null=True, verbose_name=_("Terminal")) + terminal = models.UUIDField(null=True, verbose_name=_("Terminal")) date_start = models.DateTimeField(verbose_name=_("Date Start")) date_end = models.DateTimeField(verbose_name=_("Date End"), null=True) @@ -110,3 +111,9 @@ class TerminalTask(models.Model): class Meta: db_table = "terminal_task" + + +class SessionCommand(AbstractSessionCommand): + + class Meta: + db_table = "session_command" diff --git a/apps/applications/templates/applications/terminal_list.html b/apps/applications/templates/applications/terminal_list.html index f85b2d5e7..a34ab88d7 100644 --- a/apps/applications/templates/applications/terminal_list.html +++ b/apps/applications/templates/applications/terminal_list.html @@ -28,9 +28,9 @@