Compare commits

...

19 Commits

Author SHA1 Message Date
ibuler
e3cbaa52d5 fix: 修复用户最后登录禁用 2023-11-30 10:04:58 +08:00
ibuler
d96b220619 perf: 优化 api key 认证记录用户的时间 2023-11-16 18:17:01 +08:00
fit2bot
11db46e61e fix:修复es6.8查询不到数据问题 (#12070)
Co-authored-by: feng <1304903146@qq.com>
2023-11-09 14:18:04 +08:00
ibuler
07bb44dfc3 fix: 修改可能迁移的问题 2023-11-01 03:41:20 -05:00
ibuler
1204a869a9 fix: 优化选择发布机 2023-10-30 16:06:40 +08:00
halo
0c8b36a6f4 fix: 修复DB2平台已经存在的问题 2023-10-26 01:25:26 -05:00
feng
d6cfda693f fix: 改密校验可连接性失败 2023-10-25 03:15:59 -05:00
Bai
7aa96462a4 fix: 修复登录日志和在线用户会话的 IP 地址获取方式 2023-10-25 01:39:15 -05:00
ibuler
1986653214 perf: 修复资产类型的 bug 2023-10-24 16:19:29 +08:00
fit2bot
4dc46cc754 fix: change secret perm 没有生成 (#11952)
Co-authored-by: feng <1304903146@qq.com>
2023-10-24 15:18:49 +08:00
fit2bot
dcb9270d02 fix: 改密切换至检测可连接性 失败 (#11951)
Co-authored-by: feng <1304903146@qq.com>
2023-10-24 15:18:27 +08:00
jiangweidong
f51d375f48 perf: 使用scan命令扫描在线用户 2023-10-23 04:33:17 -05:00
feng
cea24f0ccf fix: 删除错误的改密权限 2023-10-23 04:32:23 -05:00
ibuler
0292f2161f fix: 优化替换 DOMAINS 中端口 的问题 2023-10-22 22:32:30 -05:00
老广
778918be48 Merge pull request #11928 from jumpserver/pr@v3.8@database_list
fix: 资产数据库 不分页时list接口错误
2023-10-20 03:50:37 -05:00
feng
36fdf754e9 fix: 资产数据库 不分页时list接口错误 2023-10-20 16:40:00 +08:00
fit2bot
ce9515a19a perf: ticket 迁移文件 (#11921)
Co-authored-by: feng <1304903146@qq.com>
2023-10-19 20:00:54 +08:00
Bai
83108f84a3 fix: 修复账号改密密码规则提交不生效的问题 2023-10-19 04:30:28 -05:00
jiangweidong
e0b38fbcb6 fix: 可以清空云同步中的策略 2023-10-19 04:11:33 -05:00
23 changed files with 127 additions and 54 deletions

View File

@@ -136,7 +136,8 @@ class ChangeSecretManager(AccountBasePlaybookManager):
'username': account.username,
'secret_type': secret_type,
'secret': new_secret,
'private_key_path': private_key_path
'private_key_path': private_key_path,
'become': account.get_ansible_become_auth(),
}
if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None

View File

@@ -56,7 +56,8 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
'username': account.username,
'secret_type': secret_type,
'secret': new_secret,
'private_key_path': private_key_path
'private_key_path': private_key_path,
'become': account.get_ansible_become_auth(),
}
if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None

View File

@@ -38,7 +38,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Automation execution',
'verbose_name_plural': 'Automation executions',
'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexection', 'Can add change secret execution'),
('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution')],
'proxy': True,
@@ -184,7 +184,7 @@ class Migration(migrations.Migration):
migrations.AlterModelOptions(
name='automationexecution',
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexection', 'Can add change secret execution'),
('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
('view_pushaccountexecution', 'Can view push account execution'),

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.1.10 on 2023-10-24 05:59
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0016_accounttemplate_password_rules'),
]
operations = [
migrations.AlterModelOptions(
name='automationexecution',
options={
'permissions': [
('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
('view_pushaccountexecution', 'Can view push account execution'),
('add_pushaccountexecution', 'Can add push account execution')
],
'verbose_name': 'Automation execution', 'verbose_name_plural': 'Automation executions'},
),
]

View File

@@ -33,7 +33,7 @@ class AutomationExecution(AssetAutomationExecution):
verbose_name_plural = _("Automation executions")
permissions = [
('view_changesecretexecution', _('Can view change secret execution')),
('add_changesecretexection', _('Can add change secret execution')),
('add_changesecretexecution', _('Can add change secret execution')),
('view_gatheraccountsexecution', _('Can view gather accounts execution')),
('add_gatheraccountsexecution', _('Can add gather accounts execution')),

View File

@@ -71,7 +71,6 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
return password_rules
length = password_rules.get('length')
symbol_set = password_rules.get('symbol_set', '')
try:
length = int(length)
@@ -84,10 +83,6 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
msg = _('* Password length range 6-30 bits')
raise serializers.ValidationError(msg)
if not isinstance(symbol_set, str):
symbol_set = str(symbol_set)
password_rules = {'length': length, 'symbol_set': ''.join(symbol_set)}
return password_rules
def validate(self, attrs):

View File

@@ -64,14 +64,14 @@ class BaseType(TextChoices):
@classmethod
def _parse_protocols(cls, protocol, tp):
from .protocol import Protocol
settings = Protocol.settings()
_settings = Protocol.settings()
choices = protocol.get('choices', [])
if choices == '__self__':
choices = [tp]
protocols = []
for name in choices:
protocol = {'name': name, **settings.get(name, {})}
protocol = {'name': name, **_settings.get(name, {})}
setting = protocol.pop('setting', {})
setting_values = {k: v.get('default', None) for k, v in setting.items()}
protocol['setting'] = setting_values
@@ -112,7 +112,7 @@ class BaseType(TextChoices):
@classmethod
def get_choices(cls):
if not settings.XPACK_LICENSE_IS_VALID:
if not settings.XPACK_ENABLED:
return [
(tp.value, tp.label)
for tp in cls.get_community_types()

View File

@@ -107,8 +107,9 @@ def create_app_nodes(apps, org_id):
'key': next_key, 'value': name, 'parent_key': parent_key,
'full_value': full_value, 'org_id': org_id
}
node, created = node_model.objects.get_or_create(
node, __ = node_model.objects.get_or_create(
defaults=defaults, value=name, org_id=org_id,
parent_key=parent_key
)
node.parent = parent
return node

View File

@@ -6,17 +6,22 @@ from django.db import migrations
def add_db2_platform(apps, schema_editor):
platform_cls = apps.get_model('assets', 'Platform')
automation_cls = apps.get_model('assets', 'PlatformAutomation')
platform = platform_cls.objects.create(
name='DB2', internal=True, category='database', type='db2',
domain_enabled=True, su_enabled=False, comment='DB2',
created_by='System', updated_by='System',
platform, _ = platform_cls.objects.update_or_create(
name='DB2', defaults={
'name': 'DB2', 'category': 'database',
'internal': True, 'type': 'db2',
'domain_enabled': True, 'su_enabled': False,
'su_method': None, 'comment': 'DB2', 'created_by': 'System',
'updated_by': 'System', 'custom_fields': []
}
)
platform.protocols.create(name='db2', port=5000, primary=True, setting={})
automation_cls.objects.create(ansible_enabled=False, platform=platform)
platform.protocols.update_or_create(name='db2', defaults={
'name': 'db2', 'port': 50000, 'primary': True, 'setting': {}
})
automation_cls.objects.update_or_create(platform=platform, defaults={'ansible_enabled': False})
class Migration(migrations.Migration):
dependencies = [
('assets', '0123_device_automation_ansible_enabled'),
]

View File

@@ -1,3 +1,4 @@
from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
@@ -34,7 +35,7 @@ class DatabaseSerializer(AssetSerializer):
if not platform_id and self.instance:
platform = self.instance.platform
elif getattr(self, 'instance', None):
if isinstance(self.instance, list):
if isinstance(self.instance, (list, QuerySet)):
return
platform = self.instance.platform
elif self.context.get('request'):

View File

@@ -287,7 +287,7 @@ class UserSession(models.Model):
def get_keys():
session_store_cls = import_module(settings.SESSION_ENGINE).SessionStore
cache_key_prefix = session_store_cls.cache_key_prefix
keys = caches[settings.SESSION_CACHE_ALIAS].keys('*')
keys = caches[settings.SESSION_CACHE_ALIAS].iter_keys('*')
return [k.replace(cache_key_prefix, '') for k in keys]
@classmethod

View File

@@ -14,7 +14,7 @@ from acls.notifications import UserLoginReminderMsg
from audits.models import UserLoginLog
from authentication.signals import post_auth_failed, post_auth_success
from authentication.utils import check_different_city_login_if_need
from common.utils import get_request_ip, get_logger
from common.utils import get_request_ip_or_data, get_logger
from users.models import User
from ..const import LoginTypeChoices
from ..models import UserSession
@@ -60,7 +60,7 @@ def get_login_backend(request):
def generate_data(username, request, login_type=None):
user_agent = request.META.get('HTTP_USER_AGENT', '')
login_ip = get_request_ip(request) or '0.0.0.0'
login_ip = get_request_ip_or_data(request) or '0.0.0.0'
if login_type is None and isinstance(request, Request):
login_type = request.META.get('HTTP_X_JMS_LOGIN_TYPE', 'U')

View File

@@ -39,6 +39,7 @@ def on_m2m_changed(sender, action, instance, reverse, model, pk_set, **kwargs):
log_id, before_instance = get_instance_dict_from_cache(instance_id)
field_name = str(model._meta.verbose_name)
pk_set = pk_set or {}
objs = model.objects.filter(pk__in=pk_set)
objs_display = [str(o) for o in objs]
action = M2M_ACTION[action]

View File

@@ -8,6 +8,7 @@ from django.utils.translation import gettext as _
from rest_framework import authentication, exceptions
from common.auth import signature
from common.decorators import delay_run
from common.utils import get_object_or_none
from ..models import AccessKey, PrivateToken
@@ -16,14 +17,22 @@ def date_more_than(d, seconds):
return d is None or (timezone.now() - d).seconds > seconds
def after_authenticate_update_date(user, token=None):
if date_more_than(user.date_api_key_last_used, 60):
user.date_api_key_last_used = timezone.now()
user.save(update_fields=['date_api_key_last_used'])
@delay_run(ttl=60)
def update_token_last_used(token):
token.date_last_used = timezone.now()
token.save(update_fields=['date_last_used'])
if token and hasattr(token, 'date_last_used') and date_more_than(token.date_last_used, 60):
token.date_last_used = timezone.now()
token.save(update_fields=['date_last_used'])
@delay_run(ttl=60)
def update_user_last_used(user):
user.date_api_key_last_used = timezone.now()
user.save(update_fields=['date_api_key_last_used'])
def after_authenticate_update_date(user, token=None):
update_user_last_used(user)
if token:
update_token_last_used(token)
class AccessTokenAuthentication(authentication.BaseAuthentication):

View File

@@ -177,9 +177,10 @@ class ES(object):
body = self.get_query_body(**query)
data = self.es.search(
index=self.query_index, doc_type=self.doc_type, body=body,
index=self.query_index, body=body,
from_=from_, size=size, sort=sort
)
source_data = []
for item in data['hits']['hits']:
if item:
@@ -191,7 +192,7 @@ class ES(object):
def count(self, **query):
try:
body = self.get_query_body(**query)
data = self.es.count(index=self.query_index, doc_type=self.doc_type, body=body)
data = self.es.count(index=self.query_index, body=body)
count = data["count"]
except Exception as e:
logger.error('ES count error: {}'.format(e))

View File

@@ -1,5 +1,6 @@
import os
import platform
import re
from redis.sentinel import SentinelManagedSSLConnection
@@ -79,8 +80,8 @@ if CONFIG.SITE_URL:
ALLOWED_DOMAINS = DOMAINS.split(',') if DOMAINS else ['localhost:8080']
ALLOWED_DOMAINS = [host.strip() for host in ALLOWED_DOMAINS]
ALLOWED_DOMAINS = [host.replace('http://', '').replace('https://', '') for host in ALLOWED_DOMAINS if host]
ALLOWED_DOMAINS = [host.replace(':80', '').replace(':443', '') for host in ALLOWED_DOMAINS]
ALLOWED_DOMAINS = [host.split('/')[0] for host in ALLOWED_DOMAINS if host]
ALLOWED_DOMAINS = [re.sub(':80$|:443$', '', host) for host in ALLOWED_DOMAINS]
DEBUG_HOSTS = ('127.0.0.1', 'localhost', 'core')
DEBUG_PORT = ['8080', '80', ]

View File

@@ -26,6 +26,9 @@ def common_argument_spec():
class SSHClient:
SLEEP_INTERVAL = 0.3
COMPLETE_FLAG = 'complete'
def __init__(self, module):
self.module = module
self.channel = None
@@ -85,7 +88,10 @@ class SSHClient:
su_output, err_msg = self.execute(commands)
if err_msg:
return err_msg
i_output, err_msg = self.execute(['whoami'], delay_time=1)
i_output, err_msg = self.execute(
['whoami && echo "complete"'],
validate_output=True
)
if err_msg:
return err_msg
@@ -153,15 +159,21 @@ class SSHClient:
output = self.channel.recv(size).decode(encoding)
return output
def execute(self, commands, delay_time=0.3):
def execute(self, commands, validate_output=False):
if not self.is_connect:
self.connect()
output, error_msg = '', ''
try:
for command in commands:
self.channel.send(command + '\n')
time.sleep(delay_time)
output = self._get_recv()
if not validate_output:
time.sleep(self.SLEEP_INTERVAL)
output += self._get_recv()
continue
while self.COMPLETE_FLAG not in output:
time.sleep(self.SLEEP_INTERVAL)
received_output = self._get_recv().replace(f'"{self.COMPLETE_FLAG}"', '')
output += received_output
except Exception as e:
error_msg = str(e)
return output, error_msg

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.10 on 2023-10-20 07:01
from django.db import migrations
def migrate_remove_add_changesecretexection_permission(apps, *args):
perm_model = apps.get_model('auth', 'Permission')
perm_model.objects.filter(codename='add_changesecretexection').delete()
class Migration(migrations.Migration):
dependencies = [
('rbac', '0011_remove_redundant_permission'),
]
operations = [
migrations.RunPython(migrate_remove_add_changesecretexection_permission)
]

View File

@@ -88,7 +88,7 @@ special_pid_mapper = {
"accounts.add_gatheraccountsexecution": "gather_account_node",
"accounts.changesecretautomation": "asset_change_plan_node",
"accounts.view_changesecretexecution": "asset_change_plan_node",
"accounts.add_changesecretexection": "asset_change_plan_node",
"accounts.add_changesecretexecution": "asset_change_plan_node",
"accounts.view_changesecretrecord": "asset_change_plan_node",
'orgs.organization': 'view_setting',
'settings.setting': 'view_setting',

View File

@@ -1,26 +1,25 @@
# -*- coding: utf-8 -*-
#
from django.utils import translation
from django.utils import timezone
from rest_framework import generics
from rest_framework.fields import DateTimeField
from rest_framework.response import Response
from acls.models import CommandFilterACL, CommandGroup
from terminal.models import CommandStorage, Session, Command
from terminal.filters import CommandFilter
from orgs.utils import current_org
from common.api import JMSBulkModelViewSet
from common.utils import get_logger
from terminal.serializers import (
SessionCommandSerializer, InsecureCommandAlertSerializer
)
from terminal.exceptions import StorageInvalid
from orgs.utils import current_org
from terminal.backends import (
get_command_storage, get_multi_command_storage
)
from terminal.notifications import CommandAlertMessage, CommandWarningMessage
from terminal.const import RiskLevelChoices
from terminal.exceptions import StorageInvalid
from terminal.filters import CommandFilter
from terminal.models import CommandStorage, Session, Command
from terminal.notifications import CommandAlertMessage, CommandWarningMessage
from terminal.serializers import (
SessionCommandSerializer, InsecureCommandAlertSerializer
)
logger = get_logger(__name__)
__all__ = ['CommandViewSet', 'InsecureCommandAlertAPI']
@@ -140,8 +139,8 @@ class CommandViewSet(JMSBulkModelViewSet):
if session_id and not command_storage_id:
# 会话里的命令列表肯定会提供 session_id这里防止 merge 的时候取全量的数据
return self.merge_all_storage_list(request, *args, **kwargs)
queryset = self.filter_queryset(self.get_queryset())
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
page = self.paginate_queryset(queryset)
if page is not None:

View File

@@ -162,7 +162,7 @@ class Applet(JMSBaseModel):
for host_id in using_host_ids.values():
counts[host_id] += 1
hosts = list(sorted(hosts, key=lambda h: counts[h.id]))
hosts = list(sorted(hosts, key=lambda h: counts[str(h.id)]))
return hosts[0] if hosts else None
def select_host(self, user, asset):

View File

@@ -27,6 +27,7 @@ class Migration(migrations.Migration):
],
options={
'abstract': False,
'verbose_name': 'Apply Login Ticket'
},
bases=('tickets.ticket',),
),
@@ -101,6 +102,7 @@ class Migration(migrations.Migration):
],
options={
'abstract': False,
'verbose_name': 'Apply Login Asset Ticket'
},
bases=('tickets.ticket',),
),
@@ -130,6 +132,7 @@ class Migration(migrations.Migration):
],
options={
'abstract': False,
'verbose_name': 'Apply Command Ticket'
},
bases=('tickets.ticket',),
),

View File

@@ -88,8 +88,8 @@ def check_unused_users():
uncommon_users_ttl = settings.SECURITY_UNCOMMON_USERS_TTL
seconds_to_subtract = uncommon_users_ttl * 24 * 60 * 60
t = timezone.now() - timedelta(seconds=seconds_to_subtract)
last_login_q = Q(last_login__lte=t) | Q(last_login__isnull=True)
api_key_q = Q(date_api_key_last_used__lte=t) | Q(date_api_key_last_used__isnull=True)
last_login_q = Q(last_login__lte=t) | (Q(last_login__isnull=True) & Q(date_joined__lte=t))
api_key_q = Q(date_api_key_last_used__lte=t) | (Q(date_api_key_last_used__isnull=True) & Q(date_joined__lte=t))
users = User.objects \
.filter(date_joined__lt=t) \