Compare commits

...

18 Commits
v3.4 ... v3.3

Author SHA1 Message Date
ibuler
94374d1de1 fix: 修复 private storage permission 2023-09-11 11:18:53 +08:00
feng
6a75ece739 fix: 修复自动化任务原子性error 导致整个任务失败问题 2023-06-25 14:42:34 +08:00
jiangweidong
aa52dd51b1 fix: 解决具有超级工单权限的用户无法给指定人申请工单问题 (#10597) 2023-05-31 10:20:52 +08:00
Bai
c7ea62488d perf: 优化短信设置的字段名称显示 2023-05-26 15:18:47 +08:00
老广
4ef5a19c03 Merge pull request #10563 from O-Jiangweidong/pr@v3.3@perf_operate_log_display
perf: 优化命令过滤规则操作日志显示问题
2023-05-26 14:10:11 +08:00
jiangweidong
4e099fd9fc perf: 优化命令过滤规则操作日志显示问题 2023-05-26 14:03:54 +08:00
老广
c34cf23cc7 Merge pull request #10555 from jumpserver/pr@v3.3@fix_chrome_path
fix: 修正 Chrome 环境变量
2023-05-25 19:20:08 +08:00
Bai
ae47003d2c fix: 修复用户登录认证 MFA 输入错误时没有记录具体错误信息的问题 2023-05-25 18:24:48 +08:00
吴小白
da48811335 fix: 修正 Chrome 环境变量 2023-05-25 09:42:46 +00:00
feng
04175a4c1a perf: 改密过程原子性优化 2023-05-25 17:12:30 +08:00
feng
1147e5b5aa fix: 自定义平台无自动化任务 2023-05-25 16:52:07 +08:00
halo
6e014cee81 perf: 优化账号收集,使用正则处理结果 2023-05-25 13:39:55 +08:00
Bai
bf4ef35e5b fix: 修复终端端点使用资产标签匹配机制时 500 的问题 2023-05-24 17:35:35 +08:00
jiangweidong
e33dbb6aef fix: 验证账号可连接性(自定义ssh)使用的key值错误 (#10524) 2023-05-22 17:45:09 +08:00
fit2bot
e6b894ea61 fix: 账号导入500 (#10521)
Co-authored-by: feng <1304903146@qq.com>
2023-05-22 14:22:14 +08:00
ibuler
e2602127c4 fix: 修复 applet 账号选择问题 2023-05-19 17:01:57 +08:00
ibuler
a6b59ad7d6 perf: 修改过期默认值 2023-05-19 10:57:39 +08:00
jiangweidong
64f7eb2416 fix: 修复某待审核用户返回时,登录其他用户可绕开mfa的问题 2023-05-19 10:57:19 +08:00
29 changed files with 104 additions and 70 deletions

View File

@@ -9,6 +9,7 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed

View File

@@ -9,6 +9,7 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed

View File

@@ -21,6 +21,7 @@
groups: "{{ user_info.groups[0].name }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection

View File

@@ -1,3 +1,5 @@
import re
from django.utils import timezone
__all__ = ['GatherAccountsFilter']
@@ -27,18 +29,25 @@ class GatherAccountsFilter:
@staticmethod
def posix_filter(info):
username_pattern = re.compile(r'^(\S+)')
ip_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
login_time_pattern = re.compile(r'\w{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}')
result = {}
for line in info:
data = line.split('@')
if len(data) == 1:
result[line] = {}
usernames = username_pattern.findall(line)
username = ''.join(usernames)
if username:
result[username] = {}
else:
continue
if len(data) != 3:
continue
username, address, dt = data
date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z')
result[username] = {'address': address, 'date': date}
ip_addrs = ip_pattern.findall(line)
ip_addr = ''.join(ip_addrs)
if ip_addr:
result[username].update({'address': ip_addr})
login_times = login_time_pattern.findall(line)
if login_times:
date = timezone.datetime.strptime(f'{login_times[0]} +0800', '%b %d %H:%M:%S %Y %z')
result[username].update({'date': date})
return result
@staticmethod

View File

@@ -5,7 +5,7 @@
ansible.builtin.shell:
cmd: >
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $1"@"$3"@"$5,$6,$7,$8 }')
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $0 }')
if [ -n "$k" ]; then
echo $k
else

View File

@@ -43,6 +43,7 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: remove jumpserver ssh key

View File

@@ -43,6 +43,7 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: remove jumpserver ssh key

View File

@@ -17,6 +17,7 @@
groups: "{{ params.groups }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection

View File

@@ -10,5 +10,5 @@
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"

View File

@@ -22,8 +22,8 @@ logger = get_logger(__name__)
class AccountCreateUpdateSerializerMixin(serializers.Serializer):
template = serializers.PrimaryKeyRelatedField(
queryset=AccountTemplate.objects,
required=False, label=_("Template"), write_only=True
queryset=AccountTemplate.objects, required=False,
label=_("Template"), write_only=True, allow_null=True
)
push_now = serializers.BooleanField(
default=False, label=_("Push now"), write_only=True
@@ -33,7 +33,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
)
on_invalid = LabeledChoiceField(
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
write_only=True, label=_('Exist policy')
write_only=True, allow_null=True, label=_('Exist policy'),
)
_template = None
clean_auth_fields: callable

View File

@@ -46,10 +46,8 @@ class OperatorLogHandler(metaclass=Singleton):
pre_value, value = self._consistent_type_to_str(pre_value, value)
if sorted(str(value)) == sorted(str(pre_value)):
continue
if pre_value:
before[key] = pre_value
if value:
after[key] = value
before[key] = pre_value
after[key] = value
return before, after
def cache_instance_before_data(self, instance_dict):

View File

@@ -70,8 +70,10 @@ def _get_instance_field_value(
if getattr(f, 'primary_key', False):
f.verbose_name = 'id'
elif isinstance(value, (list, dict)):
elif isinstance(value, list):
value = copy.deepcopy(value)
elif isinstance(value, dict):
value = dict(copy.deepcopy(value))
elif isinstance(value, datetime):
value = as_current_tz(value).strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(f, models.OneToOneField) and isinstance(value, models.Model):

View File

@@ -3,6 +3,7 @@ import json
import os
import urllib.parse
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
@@ -379,19 +380,18 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
expire_now = request.data.get('expire_now', None)
asset_type = token.asset.type
asset_category = token.asset.category
# 设置默认值
if expire_now is None:
# TODO 暂时特殊处理 k8s 不过期
if asset_type in ['k8s', 'kubernetes']:
expire_now = False
elif asset_category in ['database', 'db']:
expire_now = False
else:
expire_now = True
expire_now = not settings.CONNECTION_TOKEN_REUSABLE
if is_false(expire_now) or token.is_reusable:
logger.debug('Token is reusable or specify, not expire')
if is_false(expire_now):
logger.debug('Api specified, now expire now')
elif token.is_reusable and settings.CONNECTION_TOKEN_REUSABLE:
logger.debug('Token is reusable, not expire now')
else:
token.expire()

View File

@@ -1,5 +1,6 @@
from django.conf import settings
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger
from django.contrib.auth import get_user_model
from authentication.signals import user_auth_failed, user_auth_success

View File

@@ -89,8 +89,6 @@ class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
msg: str
def __init__(self, username, request, ip, mfa_type, error):
super().__init__(username=username, request=request)
util = MFABlockUtils(username, ip)
times_remainder = util.incr_failed_count()
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
@@ -101,6 +99,7 @@ class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
)
else:
self.msg = const.block_mfa_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
super().__init__(username=username, request=request)
class BlockMFAError(AuthFailedNeedLogMixin, AuthFailedError):

View File

@@ -221,7 +221,8 @@ class MFAMixin:
self._do_check_user_mfa(code, mfa_type, user=user)
def check_user_mfa_if_need(self, user):
if self.request.session.get('auth_mfa'):
if self.request.session.get('auth_mfa') and \
self.request.session.get('auth_mfa_username') == user.username:
return
if not user.mfa_enabled:
return
@@ -229,15 +230,16 @@ class MFAMixin:
active_mfa_names = user.active_mfa_backends_mapper.keys()
raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names))
def mark_mfa_ok(self, mfa_type):
def mark_mfa_ok(self, mfa_type, user):
self.request.session['auth_mfa'] = 1
self.request.session['auth_mfa_username'] = user.username
self.request.session['auth_mfa_time'] = time.time()
self.request.session['auth_mfa_required'] = 0
self.request.session['auth_mfa_type'] = mfa_type
MFABlockUtils(self.request.user.username, self.get_request_ip()).clean_failed_count()
MFABlockUtils(user.username, self.get_request_ip()).clean_failed_count()
def clean_mfa_mark(self):
keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type']
keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type', 'auth_mfa_username']
for k in keys:
self.request.session.pop(k, '')
@@ -272,7 +274,7 @@ class MFAMixin:
ok, msg = mfa_backend.check_code(code)
if ok:
self.mark_mfa_ok(mfa_type)
self.mark_mfa_ok(mfa_type, user)
return
raise errors.MFAFailedError(

View File

@@ -12,7 +12,7 @@ from common.utils import get_object_or_none
from orgs.utils import tmp_to_root_org
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
class IsValidUser(permissions.IsAuthenticated):
"""Allows access to valid user, is active and not expired"""
def has_permission(self, request, view):

View File

@@ -16,6 +16,8 @@ def allow_access(private_file):
path_base = path_list[1] if len(path_list) > 1 else None
path_perm = path_perms_map.get(path_base, None)
if ".." in request_path:
return False
if not path_perm:
return False
if path_perm == '*' or request.user.has_perms([path_perm]):

View File

@@ -1,4 +1,5 @@
from collections import defaultdict
from functools import reduce
class DefaultCallback:
@@ -18,6 +19,7 @@ class DefaultCallback:
failures=defaultdict(dict),
dark=defaultdict(dict),
skipped=defaultdict(dict),
ignored=defaultdict(dict),
)
self.summary = dict(
ok=[],
@@ -59,6 +61,14 @@ class DefaultCallback:
}
self.result['ok'][host][task] = detail
def runner_on_skipped(self, event_data, host=None, task=None, **kwargs):
detail = {
'action': event_data.get('task_action', ''),
'res': {},
'rc': 0,
}
self.result['skipped'][host][task] = detail
def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
detail = {
'action': event_data.get('task_action', ''),
@@ -67,15 +77,9 @@ class DefaultCallback:
'stdout': res.get('stdout', ''),
'stderr': ';'.join([res.get('stderr', ''), res.get('msg', '')]).strip(';')
}
self.result['failures'][host][task] = detail
def runner_on_skipped(self, event_data, host=None, task=None, **kwargs):
detail = {
'action': event_data.get('task_action', ''),
'res': {},
'rc': 0,
}
self.result['skipped'][host][task] = detail
ignore_errors = event_data.get('ignore_errors', False)
error_key = 'ignored' if ignore_errors else 'failures'
self.result[error_key][host][task] = detail
def runner_on_unreachable(self, event_data, host=None, task=None, res=None, **kwargs):
detail = {
@@ -105,16 +109,21 @@ class DefaultCallback:
pass
def playbook_on_stats(self, event_data, **kwargs):
failed = []
for i in ['dark', 'failures']:
for host, tasks in self.result[i].items():
failed.append(host)
error = ''
for task, detail in tasks.items():
error += f'{task}: {detail["stderr"]};'
self.summary[i][host] = error.strip(';')
self.summary['ok'] = list(set(self.result['ok'].keys()) - set(failed))
self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(failed))
error_func = lambda err, task_detail: err + f"{task_detail[0]}: {task_detail[1]['stderr']};"
for tp in ['dark', 'failures']:
for host, tasks in self.result[tp].items():
error = reduce(error_func, tasks.items(), '').strip(';')
self.summary[tp][host] = error
failures = list(self.result['failures'].keys())
dark_or_failures = list(self.result['dark'].keys()) + failures
for host, tasks in self.result.get('ignored', {}).items():
ignore_errors = reduce(error_func, tasks.items(), '').strip(';')
if host in failures:
self.summary['failures'][host] += {ignore_errors}
self.summary['ok'] = list(set(self.result['ok'].keys()) - set(dark_or_failures))
self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(dark_or_failures))
def playbook_on_include(self, event_data, **kwargs):
pass

View File

@@ -166,6 +166,7 @@ class UsernameHintsAPI(APIView):
top_accounts = Account.objects \
.exclude(username__startswith='jms_') \
.exclude(username__startswith='js_') \
.filter(username__icontains=query) \
.filter(asset__in=assets) \
.values('username') \

View File

@@ -38,25 +38,25 @@ class BaseSMSSettingSerializer(serializers.Serializer):
class AlibabaSMSSettingSerializer(BaseSMSSettingSerializer):
ALIBABA_ACCESS_KEY_ID = serializers.CharField(max_length=256, required=True, label='AccessKeyId')
ALIBABA_ACCESS_KEY_ID = serializers.CharField(max_length=256, required=True, label='Access Key ID')
ALIBABA_ACCESS_KEY_SECRET = EncryptedField(
max_length=256, required=False, label='access_key_secret',
max_length=256, required=False, label='Access Key Secret',
)
ALIBABA_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))
ALIBABA_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code'))
class TencentSMSSettingSerializer(BaseSMSSettingSerializer):
TENCENT_SECRET_ID = serializers.CharField(max_length=256, required=True, label='Secret id')
TENCENT_SECRET_KEY = EncryptedField(max_length=256, required=False, label='Secret key')
TENCENT_SDKAPPID = serializers.CharField(max_length=256, required=True, label='SDK app id')
TENCENT_SECRET_ID = serializers.CharField(max_length=256, required=True, label='Secret ID')
TENCENT_SECRET_KEY = EncryptedField(max_length=256, required=False, label='Secret Key')
TENCENT_SDKAPPID = serializers.CharField(max_length=256, required=True, label='SDK APP ID')
TENCENT_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))
TENCENT_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code'))
class HuaweiSMSSettingSerializer(BaseSMSSettingSerializer):
HUAWEI_APP_KEY = serializers.CharField(max_length=256, required=True, label='App key')
HUAWEI_APP_SECRET = EncryptedField(max_length=256, required=False, label='App secret')
HUAWEI_APP_KEY = serializers.CharField(max_length=256, required=True, label='App Key')
HUAWEI_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
HUAWEI_SMS_ENDPOINT = serializers.CharField(max_length=1024, required=True, label=_('App Access Address'))
HUAWEI_SIGN_CHANNEL_NUM = serializers.CharField(max_length=1024, required=True, label=_('Signature channel number'))
HUAWEI_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))

View File

@@ -182,7 +182,7 @@
- name: Set chromium and driver on the global system path (Chromium)
ansible.windows.win_path:
elements:
- 'C:\Program Files\Chrome\chrome-win'
- 'C:\Program Files\Chrome\chrome-win32'
- 'C:\Program Files\JumpServer\drivers\chromedriver_win32'
- name: Set Chromium variables disable Google Api (Chromium)

View File

@@ -94,6 +94,7 @@ class Applet(JMSBaseModel):
@classmethod
def load_platform_if_need(cls, d):
from assets.serializers import PlatformSerializer
from assets.const import CustomTypes
if not os.path.exists(os.path.join(d, 'platform.yml')):
return
@@ -111,6 +112,9 @@ class Applet(JMSBaseModel):
except KeyError:
raise ValidationError({'error': _('Missing type in platform.yml')})
if not data.get('automation'):
data['automation'] = CustomTypes._get_automation_constrains()['*']
s = PlatformSerializer(data=data)
s.add_type_choices(tp, tp)
s.is_valid(raise_exception=True)
@@ -193,7 +197,7 @@ class Applet(JMSBaseModel):
if private_account and private_account.username not in accounts_username_used:
account = private_account
else:
accounts = accounts.exclude(username__in=accounts_username_used)
accounts = accounts.exclude(username__in=accounts_username_used).filter(username__startswith='jms_')
account = self.random_select_prefer_account(user, host, accounts)
if not account:
return

View File

@@ -82,7 +82,7 @@ class Endpoint(JMSBaseModel):
return None
endpoints = cls.objects.filter(name__in=values).order_by('-date_updated')
for endpoint in endpoints:
if endpoint.is_valid_for(protocol):
if endpoint.is_valid_for(instance, protocol):
return endpoint

View File

@@ -1,13 +1,15 @@
from rest_framework import permissions
from common.utils import get_logger
logger = get_logger(__file__)
__all__ = ['IsSessionAssignee']
class IsSessionAssignee(permissions.BasePermission):
class IsSessionAssignee(permissions.IsAuthenticated):
def has_permission(self, request, view):
return False
def has_object_permission(self, request, view, obj):
try:

View File

@@ -64,7 +64,6 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
def perform_create(self, serializer):
instance = serializer.save()
instance.applicant = self.request.user
instance.save(update_fields=['applicant'])
instance.open()

View File

@@ -1,12 +1,12 @@
from rest_framework import permissions
class IsAssignee(permissions.BasePermission):
class IsAssignee(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj):
return obj.has_current_assignee(request.user)
class IsApplicant(permissions.BasePermission):
class IsApplicant(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj):
return obj.applicant == request.user

View File

@@ -59,6 +59,7 @@ class TicketApplySerializer(TicketSerializer):
org_id = serializers.CharField(
required=True, max_length=36, allow_blank=True, label=_("Organization")
)
applicant = serializers.CharField(required=False, allow_blank=True)
def get_applicant(self, applicant_id):
current_user = self.context['request'].user

View File

@@ -1,6 +1,5 @@
from rest_framework import permissions
from rbac.builtin import BuiltinRole
from .utils import is_auth_password_time_valid
@@ -11,7 +10,7 @@ class IsAuthPasswdTimeValid(permissions.IsAuthenticated):
and is_auth_password_time_valid(request.session)
class UserObjectPermission(permissions.BasePermission):
class UserObjectPermission(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj):
if view.action not in ['update', 'partial_update', 'destroy']: