From 1293d72189fdd8d3b565ddccc9052d2809661a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 18 Dec 2018 11:29:21 +0800 Subject: [PATCH] Session task (#2196) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Bugfix] 修复错误 * [Update] 增加会话定期清理 --- apps/common/forms.py | 9 ++--- apps/common/signals_handler.py | 9 +++-- apps/jumpserver/conf.py | 1 + apps/jumpserver/settings.py | 2 ++ apps/locale/zh/LC_MESSAGES/django.mo | Bin 59907 -> 60196 bytes apps/locale/zh/LC_MESSAGES/django.po | 50 ++++++++++++++++----------- apps/terminal/api/v1/session.py | 41 ++++------------------ apps/terminal/models.py | 32 +++++++++++++++++ apps/terminal/tasks.py | 32 ++++++++++++++++- 9 files changed, 111 insertions(+), 65 deletions(-) diff --git a/apps/common/forms.py b/apps/common/forms.py index d052819b6..f29d19ec3 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -15,8 +15,6 @@ class BaseForm(forms.Form): super().__init__(*args, **kwargs) for name, field in self.fields.items(): value = getattr(settings, name, None) - # django_value = getattr(settings, name) if hasattr(settings, name) else None - if value is None: # and django_value is None: continue @@ -24,8 +22,6 @@ class BaseForm(forms.Form): if isinstance(value, dict): value = json.dumps(value) initial_value = value - # elif django_value is False or django_value: - # initial_value = django_value else: initial_value = '' field.initial = initial_value @@ -157,6 +153,11 @@ class TerminalSettingForm(BaseForm): TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField( choices=PAGE_SIZE_CHOICES, initial='auto', label=_("List page size"), ) + TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField( + label=_("Session keep duration"), + help_text=_("Units: days, Session, record, command will be delete " + "if more than duration, only in database") + ) class TerminalCommandStorage(BaseForm): diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py index 207dd2ce5..96142e394 100644 --- a/apps/common/signals_handler.py +++ b/apps/common/signals_handler.py @@ -26,21 +26,20 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): def refresh_all_settings_on_django_ready(sender, **kwargs): logger.debug("Receive django ready signal") logger.debug(" - fresh all settings") - CACHE_KEY_PREFIX = '_SETTING_' + cache_key_prefix = '_SETTING_' def monkey_patch_getattr(self, name): - key = CACHE_KEY_PREFIX + name + key = cache_key_prefix + name cached = cache.get(key) if cached is not None: return cached if self._wrapped is empty: self._setup(name) val = getattr(self._wrapped, name) - # self.__dict__[name] = val # Never set it return val def monkey_patch_setattr(self, name, value): - key = CACHE_KEY_PREFIX + name + key = cache_key_prefix + name cache.set(key, value, None) if name == '_wrapped': self.__dict__.clear() @@ -51,7 +50,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs): def monkey_patch_delattr(self, name): super(LazySettings, self).__delattr__(name) self.__dict__.pop(name, None) - key = CACHE_KEY_PREFIX + name + key = cache_key_prefix + name cache.delete(key) try: diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index b1d33c3cd..d537b9e16 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -318,6 +318,7 @@ defaults = { 'TERMINAL_HEARTBEAT_INTERVAL': 5, 'TERMINAL_ASSET_LIST_SORT_BY': 'hostname', 'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto', + 'TERMINAL_SESSION_KEEP_DURATION': 9999, } diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index a58642877..167de3180 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -467,6 +467,7 @@ DEFAULT_TERMINAL_REPLAY_STORAGE = { TERMINAL_REPLAY_STORAGE = { } + SECURITY_MFA_AUTH = False SECURITY_LOGIN_LIMIT_COUNT = 7 SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute @@ -490,6 +491,7 @@ TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE +TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html BOOTSTRAP3 = { diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index bbbee180bb402790b79c2a3e0ad2573935d7d393..2192ac16e338d572c34aa4c8f2faf64acb2b7b62 100644 GIT binary patch delta 15877 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 QGG5R8GdSbg<+^$P4=%nw82|tP diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 7f42c17be..fda3df537 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-12-17 20:06+0800\n" +"POT-Creation-Date: 2018-12-18 10:13+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -1976,47 +1976,57 @@ msgstr "资产列表排序" #: common/forms.py:158 msgid "List page size" -msgstr "资产列表页面大小" +msgstr "资产分页每页数量" -#: common/forms.py:170 +#: common/forms.py:161 +msgid "Session keep duration" +msgstr "会话保留时长" + +#: common/forms.py:162 +msgid "" +"Units: days, Session, record, command will be delete if more than duration, " +"only in database" +msgstr "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不受影响)" + +#: common/forms.py:175 msgid "MFA Secondary certification" msgstr "MFA 二次认证" -#: common/forms.py:172 +#: common/forms.py:177 msgid "" "After opening, the user login must use MFA secondary authentication (valid " "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: common/forms.py:179 +#: common/forms.py:184 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: common/forms.py:184 +#: common/forms.py:189 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: common/forms.py:186 +#: common/forms.py:191 msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" -#: common/forms.py:193 +#: common/forms.py:198 msgid "Connection max idle time" msgstr "SSH最大空闲时间" -#: common/forms.py:195 +#: common/forms.py:200 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" -#: common/forms.py:201 +#: common/forms.py:206 msgid "Password expiration time" msgstr "密码过期时间" -#: common/forms.py:204 +#: common/forms.py:209 msgid "" "Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " @@ -2026,45 +2036,45 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: common/forms.py:213 +#: common/forms.py:218 msgid "Password minimum length" msgstr "密码最小长度 " -#: common/forms.py:219 +#: common/forms.py:224 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: common/forms.py:221 +#: common/forms.py:226 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: common/forms.py:227 +#: common/forms.py:232 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: common/forms.py:228 +#: common/forms.py:233 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: common/forms.py:234 +#: common/forms.py:239 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: common/forms.py:235 +#: common/forms.py:240 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: common/forms.py:241 +#: common/forms.py:246 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: common/forms.py:242 +#: common/forms.py:247 msgid "" "After opening, the user password changes and resets must contain special " "characters" diff --git a/apps/terminal/api/v1/session.py b/apps/terminal/api/v1/session.py index de3e09a55..5788df775 100644 --- a/apps/terminal/api/v1/session.py +++ b/apps/terminal/api/v1/session.py @@ -94,44 +94,15 @@ class SessionReplayViewSet(viewsets.ViewSet): serializer_class = serializers.ReplaySerializer permission_classes = (IsOrgAdminOrAppUser,) session = None - upload_to = 'replay' # 仅添加到本地存储中 - - def get_session_path(self, version=2): - """ - 获取session日志的文件路径 - :param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz - :return: - """ - suffix = '.replay.gz' - if version == 1: - suffix = '.gz' - date = self.session.date_start.strftime('%Y-%m-%d') - return os.path.join(date, str(self.session.id) + suffix) - - def get_local_path(self, version=2): - session_path = self.get_session_path(version=version) - if version == 2: - local_path = os.path.join(self.upload_to, session_path) - else: - local_path = session_path - return local_path - - def save_to_storage(self, f): - local_path = self.get_local_path() - try: - name = default_storage.save(local_path, f) - return name, None - except OSError as e: - return None, e def create(self, request, *args, **kwargs): session_id = kwargs.get('pk') - self.session = get_object_or_404(Session, id=session_id) + session = get_object_or_404(Session, id=session_id) serializer = self.serializer_class(data=request.data) if serializer.is_valid(): file = serializer.validated_data['file'] - name, err = self.save_to_storage(file) + name, err = session.save_to_storage(file) if not name: msg = "Failed save replay `{}`: {}".format(session_id, err) logger.error(msg) @@ -145,7 +116,7 @@ class SessionReplayViewSet(viewsets.ViewSet): def retrieve(self, request, *args, **kwargs): session_id = kwargs.get('pk') - self.session = get_object_or_404(Session, id=session_id) + session = get_object_or_404(Session, id=session_id) data = { 'type': 'guacamole' if self.session.protocol == 'rdp' else 'json', @@ -153,9 +124,9 @@ class SessionReplayViewSet(viewsets.ViewSet): } # 新版本和老版本的文件后缀不同 - session_path = self.get_session_path() # 存在外部存储上的路径 - local_path = self.get_local_path() - local_path_v1 = self.get_local_path(version=1) + session_path = session.get_rel_replay_path() # 存在外部存储上的路径 + local_path = session.get_local_path() + local_path_v1 = session.get_local_path(version=1) # 去default storage中查找 for _local_path in (local_path, local_path_v1, session_path): diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 661b4a57d..6491bdf35 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -1,11 +1,13 @@ from __future__ import unicode_literals +import os import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.conf import settings +from django.core.files.storage import default_storage from users.models import User from orgs.mixins import OrgModelMixin @@ -148,6 +150,36 @@ class Session(OrgModelMixin): date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) + upload_to = 'replay' + + def get_rel_replay_path(self, version=2): + """ + 获取session日志的文件路径 + :param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz + :return: + """ + suffix = '.replay.gz' + if version == 1: + suffix = '.gz' + date = self.date_start.strftime('%Y-%m-%d') + return os.path.join(date, str(self.id) + suffix) + + def get_local_path(self, version=2): + rel_path = self.get_rel_replay_path(version=version) + if version == 2: + local_path = os.path.join(self.upload_to, rel_path) + else: + local_path = rel_path + return local_path + + def save_to_storage(self, f): + local_path = self.get_local_path() + try: + name = default_storage.save(local_path, f) + return name, None + except OSError as e: + return None, e + class Meta: db_table = "terminal_session" ordering = ["-date_start"] diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py index 4e57c5f5e..77aa66226 100644 --- a/apps/terminal/tasks.py +++ b/apps/terminal/tasks.py @@ -4,15 +4,20 @@ import datetime from celery import shared_task +from celery.utils.log import get_task_logger from django.utils import timezone +from django.conf import settings +from django.core.files.storage import default_storage + from ops.celery.utils import register_as_period_task, after_app_ready_start, \ after_app_shutdown_clean -from .models import Status, Session +from .models import Status, Session, Command CACHE_REFRESH_INTERVAL = 10 RUNNING = False +logger = get_task_logger(__name__) @shared_task @@ -34,3 +39,28 @@ def clean_orphan_session(): if not session.terminal or not session.terminal.is_active: session.is_finished = True session.save() + + +@shared_task +@register_as_period_task(interval=3600*24) +@after_app_ready_start +@after_app_shutdown_clean +def clean_expired_session_period(): + logger.info("Start clean expired session record, commands and replay") + days = settings.TERMINAL_SESSION_KEEP_DURATION + dt = timezone.now() - timezone.timedelta(days=days) + expired_sessions = Session.objects.filter(date_start__lt=dt) + for session in expired_sessions: + logger.info("Clean session: {}".format(session.id)) + Command.objects.filter(session=str(session.id)).delete() + # 删除录像文件 + session_path = session.get_rel_replay_path() + local_path = session.get_local_path() + local_path_v1 = session.get_local_path(version=1) + + # 去default storage中查找 + for _local_path in (local_path, local_path_v1, session_path): + if default_storage.exists(_local_path): + default_storage.delete(_local_path) + # 删除session记录 + session.delete()