Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
1293d721
Unverified
Commit
1293d721
authored
Dec 18, 2018
by
老广
Committed by
GitHub
Dec 18, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Session task (#2196)
* [Bugfix] 修复错误 * [Update] 增加会话定期清理
parent
b56d73ba
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
111 additions
and
65 deletions
+111
-65
forms.py
apps/common/forms.py
+5
-4
signals_handler.py
apps/common/signals_handler.py
+4
-5
conf.py
apps/jumpserver/conf.py
+1
-0
settings.py
apps/jumpserver/settings.py
+2
-0
django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+30
-20
session.py
apps/terminal/api/v1/session.py
+6
-35
models.py
apps/terminal/models.py
+32
-0
tasks.py
apps/terminal/tasks.py
+31
-1
No files found.
apps/common/forms.py
View file @
1293d721
...
...
@@ -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
):
...
...
apps/common/signals_handler.py
View file @
1293d721
...
...
@@ -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
:
...
...
apps/jumpserver/conf.py
View file @
1293d721
...
...
@@ -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
,
}
...
...
apps/jumpserver/settings.py
View file @
1293d721
...
...
@@ -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
=
{
...
...
apps/locale/zh/LC_MESSAGES/django.mo
View file @
1293d721
No preview for this file type
apps/locale/zh/LC_MESSAGES/django.po
View file @
1293d721
...
...
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-1
7 20:06
+0800\n"
"POT-Creation-Date: 2018-12-1
8 10:13
+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\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:17
2
#: common/forms.py:17
7
msgid ""
"After opening, the user login must use MFA secondary authentication (valid "
"for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
#: common/forms.py:1
79
#: common/forms.py:1
84
msgid "Limit the number of login failures"
msgstr "限制登录失败次数"
#: common/forms.py:18
4
#: common/forms.py:18
9
msgid "No logon interval"
msgstr "禁止登录时间间隔"
#: common/forms.py:1
86
#: common/forms.py:1
91
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:19
3
#: common/forms.py:19
8
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:20
1
#: common/forms.py:20
6
msgid "Password expiration time"
msgstr "密码过期时间"
#: common/forms.py:20
4
#: common/forms.py:20
9
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:21
3
#: common/forms.py:21
8
msgid "Password minimum length"
msgstr "密码最小长度 "
#: common/forms.py:2
19
#: common/forms.py:2
24
msgid "Must contain capital letters"
msgstr "必须包含大写字母"
#: common/forms.py:22
1
#: common/forms.py:22
6
msgid ""
"After opening, the user password changes and resets must contain uppercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: common/forms.py:2
27
#: common/forms.py:2
32
msgid "Must contain lowercase letters"
msgstr "必须包含小写字母"
#: common/forms.py:2
28
#: common/forms.py:2
33
msgid ""
"After opening, the user password changes and resets must contain lowercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: common/forms.py:23
4
#: common/forms.py:23
9
msgid "Must contain numeric characters"
msgstr "必须包含数字字符"
#: common/forms.py:2
35
#: common/forms.py:2
40
msgid ""
"After opening, the user password changes and resets must contain numeric "
"characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: common/forms.py:24
1
#: common/forms.py:24
6
msgid "Must contain special characters"
msgstr "必须包含特殊字符"
#: common/forms.py:24
2
#: common/forms.py:24
7
msgid ""
"After opening, the user password changes and resets must contain special "
"characters"
...
...
apps/terminal/api/v1/session.py
View file @
1293d721
...
...
@@ -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'
)
se
lf
.
se
ssion
=
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
=
se
lf
.
save_to_storage
(
file
)
name
,
err
=
se
ssion
.
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'
)
se
lf
.
se
ssion
=
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
=
se
lf
.
get_session
_path
()
# 存在外部存储上的路径
local_path
=
se
lf
.
get_local_path
()
local_path_v1
=
se
lf
.
get_local_path
(
version
=
1
)
session_path
=
se
ssion
.
get_rel_replay
_path
()
# 存在外部存储上的路径
local_path
=
se
ssion
.
get_local_path
()
local_path_v1
=
se
ssion
.
get_local_path
(
version
=
1
)
# 去default storage中查找
for
_local_path
in
(
local_path
,
local_path_v1
,
session_path
):
...
...
apps/terminal/models.py
View file @
1293d721
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"
]
...
...
apps/terminal/tasks.py
View file @
1293d721
...
...
@@ -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
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment