feat(terminal):危险命令告警功能

This commit is contained in:
peijianbo
2020-10-27 11:35:31 +08:00
committed by 老广
parent 50a4735b07
commit 79b5aa68c8
14 changed files with 373 additions and 183 deletions

View File

@@ -1,25 +1,28 @@
# -*- coding: utf-8 -*-
#
import time
from django.conf import settings
from django.utils import timezone
from django.shortcuts import HttpResponse
from rest_framework import viewsets
from rest_framework import generics
from rest_framework.fields import DateTimeField
from rest_framework.response import Response
from rest_framework import status
from django.template import loader
from orgs.utils import current_org
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsAppUser
from common.utils import get_logger
from terminal.utils import send_command_alert_mail
from terminal.serializers import InsecureCommandAlertSerializer
from ..backends import (
get_command_storage, get_multi_command_storage,
SessionCommandSerializer,
)
logger = get_logger(__name__)
__all__ = ['CommandViewSet', 'CommandExportApi']
__all__ = ['CommandViewSet', 'CommandExportApi', 'InsecureCommandAlertAPI']
class CommandQueryMixin:
@@ -134,3 +137,19 @@ class CommandExportApi(CommandQueryMixin, generics.ListAPIView):
filename = 'command-report-{}.html'.format(int(time.time()))
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
return response
class InsecureCommandAlertAPI(generics.CreateAPIView):
permission_classes = [IsAppUser]
serializer_class = InsecureCommandAlertSerializer
def post(self, request, *args, **kwargs):
serializer = InsecureCommandAlertSerializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
commands = serializer.validated_data
for command in commands:
if command['risk_level'] >= settings.SECURITY_INSECURE_COMMAND_LEVEL and \
settings.SECURITY_INSECURE_COMMAND and \
settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER:
send_command_alert_mail(command)
return Response()

View File

@@ -27,6 +27,11 @@ class AbstractSessionCommand(OrgModelMixin):
class Meta:
abstract = True
@classmethod
def get_risk_level_str(cls, risk_level):
risk_mapper = dict(cls.RISK_LEVEL_CHOICES)
return risk_mapper.get(risk_level)
@classmethod
def from_dict(cls, d):
self = cls()

View File

@@ -24,3 +24,11 @@ class SessionCommandSerializer(serializers.Serializer):
def get_risk_level_display(obj):
risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES)
return risk_mapper.get(obj.risk_level)
class InsecureCommandAlertSerializer(serializers.Serializer):
input = serializers.CharField()
asset = serializers.CharField()
user = serializers.CharField()
risk_level = serializers.IntegerField()
session = serializers.UUIDField()

View File

@@ -12,6 +12,7 @@ from django.conf import settings
from django.core.files.storage import default_storage
from django.core.cache import cache
from assets.models import Asset
from users.models import User
from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
@@ -227,6 +228,10 @@ class Session(OrgModelMixin):
local_path = rel_path
return local_path
@property
def asset_obj(self):
return Asset.objects.get(id=self.asset_id)
@property
def _date_start_first_has_replay_rdp_session(self):
if self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION is None:

View File

@@ -3,3 +3,4 @@
from .terminal import *
from .session import *
from .storage import *
from .command import *

View File

@@ -0,0 +1,10 @@
# ~*~ coding: utf-8 ~*~
from rest_framework import serializers
class InsecureCommandAlertSerializer(serializers.Serializer):
input = serializers.CharField()
asset = serializers.CharField()
user = serializers.CharField()
risk_level = serializers.IntegerField()
session = serializers.UUIDField()

View File

@@ -31,6 +31,7 @@ urlpatterns = [
name='terminal-access-key'),
path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'),
path('commands/export/', api.CommandExportApi.as_view(), name="command-export"),
path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"),
path('replay-storages/<uuid:pk>/test-connective/', api.ReplayStorageTestConnectiveApi.as_view(), name='replay-storage-test-connective'),
path('command-storages/<uuid:pk>/test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective')
# v2: get session's replay

View File

@@ -4,12 +4,15 @@ import os
from django.conf import settings
from django.core.files.storage import default_storage
from django.utils.translation import ugettext as _
import jms_storage
from common.utils import get_logger
from common.tasks import send_mail_async
from common.utils import get_logger, reverse
from settings.models import Setting
from .backends import server_replay_storage
from .models import ReplayStorage
from .models import ReplayStorage, Session, Command
logger = get_logger(__name__)
@@ -63,3 +66,38 @@ def get_session_replay_url(session):
if local_path is None:
local_path, url = download_session_replay(session)
return local_path, url
def send_command_alert_mail(command):
session_obj = Session.objects.get(id=command['session'])
subject = _("Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s") % {
'name': command['user'],
'login_from': session_obj.get_login_from_display(),
'remote_addr': session_obj.remote_addr,
'command': command['input']
}
recipient_list = settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER.split(',')
message = _("""
Command: %(command)s
<br>
Asset: %(host_name)s (%(host_ip)s)
<br>
User: %(user)s
<br>
Level: %(risk_level)s
<br>
Session: <a href="%(session_detail_url)s">session detail</a>
<br>
""") % {
'command': command['input'],
'host_name': command['asset'],
'host_ip': session_obj.asset_obj.ip,
'user': command['user'],
'risk_level': Command.get_risk_level_str(command['risk_level']),
'session_detail_url': reverse('api-terminal:session-detail',
kwargs={'pk': command['session']},
external=True, api_to_ui=True),
}
logger.debug(message)
send_mail_async.delay(subject, message, recipient_list, html_message=message)