mirror of https://github.com/jumpserver/jumpserver
[Feature] 修改命令上传api
parent
26607bc327
commit
7910292e0f
|
@ -1,8 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import base64
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
|
import tarfile
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from rest_framework import viewsets, serializers
|
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.utils import timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from common.utils import get_object_or_none
|
||||||
from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask
|
from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask
|
||||||
from .serializers import TerminalSerializer, TerminalStatusSerializer, \
|
from .serializers import TerminalSerializer, TerminalStatusSerializer, \
|
||||||
TerminalSessionSerializer, TerminalTaskSerializer
|
TerminalSessionSerializer, TerminalTaskSerializer
|
||||||
from .hands import IsSuperUserOrAppUser, IsAppUser, ProxyLog, \
|
from .hands import IsSuperUserOrAppUser, IsAppUser, ProxyLog, \
|
||||||
IsSuperUserOrAppUserOrUserReadonly
|
IsSuperUserOrAppUserOrUserReadonly
|
||||||
from common.utils import get_object_or_none
|
from .backends import get_command_store, get_replay_store, SessionCommandSerializer
|
||||||
|
|
||||||
logger = logging.getLogger(__file__)
|
logger = logging.getLogger(__file__)
|
||||||
|
|
||||||
|
@ -157,11 +160,14 @@ class SessionReplayAPI(APIView):
|
||||||
session = get_object_or_404(TerminalSession, id=session_id)
|
session = get_object_or_404(TerminalSession, id=session_id)
|
||||||
record_dir = settings.CONFIG.SESSION_RECORDE_DIR
|
record_dir = settings.CONFIG.SESSION_RECORDE_DIR
|
||||||
date = session.date_start.strftime("%Y-%m-%d")
|
date = session.date_start.strftime("%Y-%m-%d")
|
||||||
record_dir = os.path.join(record_dir, date)
|
record_dir = os.path.join(record_dir, date, str(session.id))
|
||||||
record_filename = os.path.join(record_dir, str(session.id))
|
record_filename = os.path.join(record_dir, "replay.tar.gz2")
|
||||||
|
|
||||||
if not os.path.exists(record_dir):
|
if not os.path.isdir(record_dir):
|
||||||
os.makedirs(record_dir)
|
try:
|
||||||
|
os.makedirs(record_dir)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
|
||||||
archive_stream = request.data.get("archive")
|
archive_stream = request.data.get("archive")
|
||||||
if not archive_stream:
|
if not archive_stream:
|
||||||
|
@ -173,3 +179,41 @@ class SessionReplayAPI(APIView):
|
||||||
session.has_replay = True
|
session.has_replay = True
|
||||||
session.save()
|
session.save()
|
||||||
return Response({"session_id": session.id}, status=201)
|
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)
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
|
@ -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()
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
|
@ -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
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
|
@ -6,6 +6,7 @@ from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
from .backends.command.models import AbstractSessionCommand
|
||||||
|
|
||||||
|
|
||||||
class Terminal(models.Model):
|
class Terminal(models.Model):
|
||||||
|
@ -88,7 +89,7 @@ class TerminalSession(models.Model):
|
||||||
is_finished = models.BooleanField(default=False)
|
is_finished = models.BooleanField(default=False)
|
||||||
has_replay = models.BooleanField(default=False, verbose_name=_("Replay"))
|
has_replay = models.BooleanField(default=False, verbose_name=_("Replay"))
|
||||||
has_command = models.BooleanField(default=False, verbose_name=_("Command"))
|
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_start = models.DateTimeField(verbose_name=_("Date Start"))
|
||||||
date_end = models.DateTimeField(verbose_name=_("Date End"), null=True)
|
date_end = models.DateTimeField(verbose_name=_("Date End"), null=True)
|
||||||
|
|
||||||
|
@ -110,3 +111,9 @@ class TerminalTask(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "terminal_task"
|
db_table = "terminal_task"
|
||||||
|
|
||||||
|
|
||||||
|
class SessionCommand(AbstractSessionCommand):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "session_command"
|
||||||
|
|
|
@ -28,9 +28,9 @@
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center">{% trans 'Name' %}</th>
|
<th class="text-center">{% trans 'Name' %}</th>
|
||||||
<th class="text-center">{% trans 'Addr' %}</th>
|
<th class="text-center">{% trans 'Addr' %}</th>
|
||||||
<th class="text-center">{% trans 'SSH Port' %}</th>
|
<th class="text-center">{% trans 'SSH port' %}</th>
|
||||||
<th class="text-center">{% trans 'Http Port' %}</th>
|
<th class="text-center">{% trans 'Http port' %}</th>
|
||||||
<th class="text-center">{% trans 'Connected' %}</th>
|
<th class="text-center">{% trans 'Sessions' %}</th>
|
||||||
<th class="text-center">{% trans 'Active' %}</th>
|
<th class="text-center">{% trans 'Active' %}</th>
|
||||||
<th class="text-center">{% trans 'Alive' %}</th>
|
<th class="text-center">{% trans 'Alive' %}</th>
|
||||||
<th class="text-center">{% trans 'Action' %}</th>
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
|
|
|
@ -13,10 +13,9 @@ router = routers.DefaultRouter()
|
||||||
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?status', api.TerminalStatusViewSet, 'terminal-status')
|
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?status', api.TerminalStatusViewSet, 'terminal-status')
|
||||||
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?sessions', api.TerminalSessionViewSet, 'terminal-sessions')
|
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?sessions', api.TerminalSessionViewSet, 'terminal-sessions')
|
||||||
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||||
|
router.register(r'v1/command', api.SessionCommandViewSet, 'command')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# url(r'^v1/terminate/connection/$', api.TerminateConnectionView.as_view(),
|
|
||||||
# name='terminate-connection')
|
|
||||||
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-_]+)/replay/$', api.SessionReplayAPI.as_view(), name='session-replay'),
|
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-_]+)/replay/$', api.SessionReplayAPI.as_view(), name='session-replay'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||||
url(r'^v1/system-user/(?P<pk>[0-9]+)/auth-info/', api.SystemUserAuthInfoApi.as_view(),
|
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]+)/auth-info/', api.SystemUserAuthInfoApi.as_view(),
|
||||||
name='system-user-auth-info'),
|
name='system-user-auth-info'),
|
||||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]+)/groups/$',
|
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]+)/groups/$',
|
||||||
api.AssetUpdateGroupApi.as_view(), name='asset-update-group'),
|
api.AssetUpdateGroupApi.as_view(), name='asset-update-group'),
|
||||||
|
|
|
@ -8,7 +8,7 @@ app_name = 'audits'
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'v1/proxy-log', api.ProxyLogViewSet, 'proxy-log')
|
router.register(r'v1/proxy-log', api.ProxyLogViewSet, 'proxy-log')
|
||||||
router.register(r'v1/command-log', api.CommandLogViewSet, 'command-log')
|
router.register(r'v1/command-log', api.CommandLogViewSet, 'command-log')
|
||||||
router.register(r'v1/record-log', api.RecordLogViewSet, 'record-log')
|
router.register(r'v1/replay-log', api.RecordLogViewSet, 'replay-log')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^v1/proxy-log/receive/$', api.ProxyLogReceiveView.as_view(),
|
url(r'^v1/proxy-log/receive/$', api.ProxyLogReceiveView.as_view(),
|
||||||
|
|
|
@ -363,8 +363,8 @@ CAPTCHA_FOREGROUND_COLOR = '#001100'
|
||||||
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
|
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
|
||||||
CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE
|
CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE
|
||||||
|
|
||||||
COMMAND_STORE_BACKEND = 'audits.backends.command.db'
|
COMMAND_STORE_BACKEND = 'applications.backends.command.db'
|
||||||
RECORD_STORE_BACKEND = 'audits.backends.record.db'
|
RECORD_STORE_BACKEND = 'applications.backends.replay.db'
|
||||||
|
|
||||||
|
|
||||||
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
||||||
|
|
|
@ -1434,7 +1434,7 @@ msgid "Task summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ops/templates/ops/task_detail.html:19
|
#: ops/templates/ops/task_detail.html:19
|
||||||
msgid "Task record detail"
|
msgid "Task replay detail"
|
||||||
msgstr "任务记录详情"
|
msgstr "任务记录详情"
|
||||||
|
|
||||||
#: ops/templates/ops/task_detail.html:62
|
#: ops/templates/ops/task_detail.html:62
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<div class="panel-options">
|
<div class="panel-options">
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="active">
|
<li class="active">
|
||||||
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task record detail' %} </a>
|
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task replay detail' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue