Skip to content

Commit 1293d72

Browse files
authored
Session task (jumpserver#2196)
* [Bugfix] 修复错误 * [Update] 增加会话定期清理
1 parent b56d73b commit 1293d72

9 files changed

Lines changed: 111 additions & 65 deletions

File tree

apps/common/forms.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,13 @@ def __init__(self, *args, **kwargs):
1515
super().__init__(*args, **kwargs)
1616
for name, field in self.fields.items():
1717
value = getattr(settings, name, None)
18-
# django_value = getattr(settings, name) if hasattr(settings, name) else None
19-
2018
if value is None: # and django_value is None:
2119
continue
2220

2321
if value is not None:
2422
if isinstance(value, dict):
2523
value = json.dumps(value)
2624
initial_value = value
27-
# elif django_value is False or django_value:
28-
# initial_value = django_value
2925
else:
3026
initial_value = ''
3127
field.initial = initial_value
@@ -157,6 +153,11 @@ class TerminalSettingForm(BaseForm):
157153
TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField(
158154
choices=PAGE_SIZE_CHOICES, initial='auto', label=_("List page size"),
159155
)
156+
TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField(
157+
label=_("Session keep duration"),
158+
help_text=_("Units: days, Session, record, command will be delete "
159+
"if more than duration, only in database")
160+
)
160161

161162

162163
class TerminalCommandStorage(BaseForm):

apps/common/signals_handler.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,20 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
2626
def refresh_all_settings_on_django_ready(sender, **kwargs):
2727
logger.debug("Receive django ready signal")
2828
logger.debug(" - fresh all settings")
29-
CACHE_KEY_PREFIX = '_SETTING_'
29+
cache_key_prefix = '_SETTING_'
3030

3131
def monkey_patch_getattr(self, name):
32-
key = CACHE_KEY_PREFIX + name
32+
key = cache_key_prefix + name
3333
cached = cache.get(key)
3434
if cached is not None:
3535
return cached
3636
if self._wrapped is empty:
3737
self._setup(name)
3838
val = getattr(self._wrapped, name)
39-
# self.__dict__[name] = val # Never set it
4039
return val
4140

4241
def monkey_patch_setattr(self, name, value):
43-
key = CACHE_KEY_PREFIX + name
42+
key = cache_key_prefix + name
4443
cache.set(key, value, None)
4544
if name == '_wrapped':
4645
self.__dict__.clear()
@@ -51,7 +50,7 @@ def monkey_patch_setattr(self, name, value):
5150
def monkey_patch_delattr(self, name):
5251
super(LazySettings, self).__delattr__(name)
5352
self.__dict__.pop(name, None)
54-
key = CACHE_KEY_PREFIX + name
53+
key = cache_key_prefix + name
5554
cache.delete(key)
5655

5756
try:

apps/jumpserver/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ def __getattr__(self, item):
318318
'TERMINAL_HEARTBEAT_INTERVAL': 5,
319319
'TERMINAL_ASSET_LIST_SORT_BY': 'hostname',
320320
'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto',
321+
'TERMINAL_SESSION_KEEP_DURATION': 9999,
321322
}
322323

323324

apps/jumpserver/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ def get_xpack_templates_dir():
467467
TERMINAL_REPLAY_STORAGE = {
468468
}
469469

470+
470471
SECURITY_MFA_AUTH = False
471472
SECURITY_LOGIN_LIMIT_COUNT = 7
472473
SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute
@@ -490,6 +491,7 @@ def get_xpack_templates_dir():
490491
TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL
491492
TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY
492493
TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE
494+
TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION
493495

494496
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
495497
BOOTSTRAP3 = {
289 Bytes
Binary file not shown.

apps/locale/zh/LC_MESSAGES/django.po

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgid ""
88
msgstr ""
99
"Project-Id-Version: Jumpserver 0.3.3\n"
1010
"Report-Msgid-Bugs-To: \n"
11-
"POT-Creation-Date: 2018-12-17 20:06+0800\n"
11+
"POT-Creation-Date: 2018-12-18 10:13+0800\n"
1212
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313
"Last-Translator: ibuler <ibuler@qq.com>\n"
1414
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
@@ -1976,47 +1976,57 @@ msgstr "资产列表排序"
19761976

19771977
#: common/forms.py:158
19781978
msgid "List page size"
1979-
msgstr "资产列表页面大小"
1979+
msgstr "资产分页每页数量"
19801980

1981-
#: common/forms.py:170
1981+
#: common/forms.py:161
1982+
msgid "Session keep duration"
1983+
msgstr "会话保留时长"
1984+
1985+
#: common/forms.py:162
1986+
msgid ""
1987+
"Units: days, Session, record, command will be delete if more than duration, "
1988+
"only in database"
1989+
msgstr "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不受影响)"
1990+
1991+
#: common/forms.py:175
19821992
msgid "MFA Secondary certification"
19831993
msgstr "MFA 二次认证"
19841994

1985-
#: common/forms.py:172
1995+
#: common/forms.py:177
19861996
msgid ""
19871997
"After opening, the user login must use MFA secondary authentication (valid "
19881998
"for all users, including administrators)"
19891999
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
19902000

1991-
#: common/forms.py:179
2001+
#: common/forms.py:184
19922002
msgid "Limit the number of login failures"
19932003
msgstr "限制登录失败次数"
19942004

1995-
#: common/forms.py:184
2005+
#: common/forms.py:189
19962006
msgid "No logon interval"
19972007
msgstr "禁止登录时间间隔"
19982008

1999-
#: common/forms.py:186
2009+
#: common/forms.py:191
20002010
msgid ""
20012011
"Tip: (unit/minute) if the user has failed to log in for a limited number of "
20022012
"times, no login is allowed during this time interval."
20032013
msgstr ""
20042014
"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
20052015

2006-
#: common/forms.py:193
2016+
#: common/forms.py:198
20072017
msgid "Connection max idle time"
20082018
msgstr "SSH最大空闲时间"
20092019

2010-
#: common/forms.py:195
2020+
#: common/forms.py:200
20112021
msgid ""
20122022
"If idle time more than it, disconnect connection(only ssh now) Unit: minute"
20132023
msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)"
20142024

2015-
#: common/forms.py:201
2025+
#: common/forms.py:206
20162026
msgid "Password expiration time"
20172027
msgstr "密码过期时间"
20182028

2019-
#: common/forms.py:204
2029+
#: common/forms.py:209
20202030
msgid ""
20212031
"Tip: (unit: day) If the user does not update the password during the time, "
20222032
"the user password will expire failure;The password expiration reminder mail "
@@ -2026,45 +2036,45 @@ msgstr ""
20262036
"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期"
20272037
"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户"
20282038

2029-
#: common/forms.py:213
2039+
#: common/forms.py:218
20302040
msgid "Password minimum length"
20312041
msgstr "密码最小长度 "
20322042

2033-
#: common/forms.py:219
2043+
#: common/forms.py:224
20342044
msgid "Must contain capital letters"
20352045
msgstr "必须包含大写字母"
20362046

2037-
#: common/forms.py:221
2047+
#: common/forms.py:226
20382048
msgid ""
20392049
"After opening, the user password changes and resets must contain uppercase "
20402050
"letters"
20412051
msgstr "开启后,用户密码修改、重置必须包含大写字母"
20422052

2043-
#: common/forms.py:227
2053+
#: common/forms.py:232
20442054
msgid "Must contain lowercase letters"
20452055
msgstr "必须包含小写字母"
20462056

2047-
#: common/forms.py:228
2057+
#: common/forms.py:233
20482058
msgid ""
20492059
"After opening, the user password changes and resets must contain lowercase "
20502060
"letters"
20512061
msgstr "开启后,用户密码修改、重置必须包含小写字母"
20522062

2053-
#: common/forms.py:234
2063+
#: common/forms.py:239
20542064
msgid "Must contain numeric characters"
20552065
msgstr "必须包含数字字符"
20562066

2057-
#: common/forms.py:235
2067+
#: common/forms.py:240
20582068
msgid ""
20592069
"After opening, the user password changes and resets must contain numeric "
20602070
"characters"
20612071
msgstr "开启后,用户密码修改、重置必须包含数字字符"
20622072

2063-
#: common/forms.py:241
2073+
#: common/forms.py:246
20642074
msgid "Must contain special characters"
20652075
msgstr "必须包含特殊字符"
20662076

2067-
#: common/forms.py:242
2077+
#: common/forms.py:247
20682078
msgid ""
20692079
"After opening, the user password changes and resets must contain special "
20702080
"characters"

apps/terminal/api/v1/session.py

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -94,44 +94,15 @@ class SessionReplayViewSet(viewsets.ViewSet):
9494
serializer_class = serializers.ReplaySerializer
9595
permission_classes = (IsOrgAdminOrAppUser,)
9696
session = None
97-
upload_to = 'replay' # 仅添加到本地存储中
98-
99-
def get_session_path(self, version=2):
100-
"""
101-
获取session日志的文件路径
102-
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
103-
:return:
104-
"""
105-
suffix = '.replay.gz'
106-
if version == 1:
107-
suffix = '.gz'
108-
date = self.session.date_start.strftime('%Y-%m-%d')
109-
return os.path.join(date, str(self.session.id) + suffix)
110-
111-
def get_local_path(self, version=2):
112-
session_path = self.get_session_path(version=version)
113-
if version == 2:
114-
local_path = os.path.join(self.upload_to, session_path)
115-
else:
116-
local_path = session_path
117-
return local_path
118-
119-
def save_to_storage(self, f):
120-
local_path = self.get_local_path()
121-
try:
122-
name = default_storage.save(local_path, f)
123-
return name, None
124-
except OSError as e:
125-
return None, e
12697

12798
def create(self, request, *args, **kwargs):
12899
session_id = kwargs.get('pk')
129-
self.session = get_object_or_404(Session, id=session_id)
100+
session = get_object_or_404(Session, id=session_id)
130101
serializer = self.serializer_class(data=request.data)
131102

132103
if serializer.is_valid():
133104
file = serializer.validated_data['file']
134-
name, err = self.save_to_storage(file)
105+
name, err = session.save_to_storage(file)
135106
if not name:
136107
msg = "Failed save replay `{}`: {}".format(session_id, err)
137108
logger.error(msg)
@@ -145,17 +116,17 @@ def create(self, request, *args, **kwargs):
145116

146117
def retrieve(self, request, *args, **kwargs):
147118
session_id = kwargs.get('pk')
148-
self.session = get_object_or_404(Session, id=session_id)
119+
session = get_object_or_404(Session, id=session_id)
149120

150121
data = {
151122
'type': 'guacamole' if self.session.protocol == 'rdp' else 'json',
152123
'src': '',
153124
}
154125

155126
# 新版本和老版本的文件后缀不同
156-
session_path = self.get_session_path() # 存在外部存储上的路径
157-
local_path = self.get_local_path()
158-
local_path_v1 = self.get_local_path(version=1)
127+
session_path = session.get_rel_replay_path() # 存在外部存储上的路径
128+
local_path = session.get_local_path()
129+
local_path_v1 = session.get_local_path(version=1)
159130

160131
# 去default storage中查找
161132
for _local_path in (local_path, local_path_v1, session_path):

apps/terminal/models.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from __future__ import unicode_literals
22

3+
import os
34
import uuid
45

56
from django.db import models
67
from django.utils.translation import ugettext_lazy as _
78
from django.utils import timezone
89
from django.conf import settings
10+
from django.core.files.storage import default_storage
911

1012
from users.models import User
1113
from orgs.mixins import OrgModelMixin
@@ -148,6 +150,36 @@ class Session(OrgModelMixin):
148150
date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now)
149151
date_end = models.DateTimeField(verbose_name=_("Date end"), null=True)
150152

153+
upload_to = 'replay'
154+
155+
def get_rel_replay_path(self, version=2):
156+
"""
157+
获取session日志的文件路径
158+
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
159+
:return:
160+
"""
161+
suffix = '.replay.gz'
162+
if version == 1:
163+
suffix = '.gz'
164+
date = self.date_start.strftime('%Y-%m-%d')
165+
return os.path.join(date, str(self.id) + suffix)
166+
167+
def get_local_path(self, version=2):
168+
rel_path = self.get_rel_replay_path(version=version)
169+
if version == 2:
170+
local_path = os.path.join(self.upload_to, rel_path)
171+
else:
172+
local_path = rel_path
173+
return local_path
174+
175+
def save_to_storage(self, f):
176+
local_path = self.get_local_path()
177+
try:
178+
name = default_storage.save(local_path, f)
179+
return name, None
180+
except OSError as e:
181+
return None, e
182+
151183
class Meta:
152184
db_table = "terminal_session"
153185
ordering = ["-date_start"]

apps/terminal/tasks.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44
import datetime
55

66
from celery import shared_task
7+
from celery.utils.log import get_task_logger
78
from django.utils import timezone
9+
from django.conf import settings
10+
from django.core.files.storage import default_storage
11+
812

913
from ops.celery.utils import register_as_period_task, after_app_ready_start, \
1014
after_app_shutdown_clean
11-
from .models import Status, Session
15+
from .models import Status, Session, Command
1216

1317

1418
CACHE_REFRESH_INTERVAL = 10
1519
RUNNING = False
20+
logger = get_task_logger(__name__)
1621

1722

1823
@shared_task
@@ -34,3 +39,28 @@ def clean_orphan_session():
3439
if not session.terminal or not session.terminal.is_active:
3540
session.is_finished = True
3641
session.save()
42+
43+
44+
@shared_task
45+
@register_as_period_task(interval=3600*24)
46+
@after_app_ready_start
47+
@after_app_shutdown_clean
48+
def clean_expired_session_period():
49+
logger.info("Start clean expired session record, commands and replay")
50+
days = settings.TERMINAL_SESSION_KEEP_DURATION
51+
dt = timezone.now() - timezone.timedelta(days=days)
52+
expired_sessions = Session.objects.filter(date_start__lt=dt)
53+
for session in expired_sessions:
54+
logger.info("Clean session: {}".format(session.id))
55+
Command.objects.filter(session=str(session.id)).delete()
56+
# 删除录像文件
57+
session_path = session.get_rel_replay_path()
58+
local_path = session.get_local_path()
59+
local_path_v1 = session.get_local_path(version=1)
60+
61+
# 去default storage中查找
62+
for _local_path in (local_path, local_path_v1, session_path):
63+
if default_storage.exists(_local_path):
64+
default_storage.delete(_local_path)
65+
# 删除session记录
66+
session.delete()

0 commit comments

Comments
 (0)