Compare commits

...

11 Commits
v2.5.2 ... v2.5

Author SHA1 Message Date
fit2bot
08ed363d44 fix: 修复 celery 等日志文件的访问漏洞 (#5475)
Co-authored-by: xinwen <coderWen@126.com>
2021-01-19 14:36:30 +08:00
ibuler
043d24a8f7 fix: bug 2021-01-14 10:38:11 +08:00
xinwen
1a011f34a1 fix: 系统审计员不应该能添加到组 2020-12-15 19:25:04 +08:00
xinwen
9e16f6c1a3 fix(orgs): 用户离开组织后授权的资产没主动刷新 2020-12-15 14:01:54 +08:00
xinwen
4a32016f14 fix: 工单申请资产审批时系统用户没有推荐 2020-12-15 13:07:25 +08:00
fit2bot
30c4723fc9 perf: 数据库应用database字段添加allow_null=True (#5197)
* perf: 数据库应用database字段修改为required

* perf: 数据库应用database字段添加allow_null=True

Co-authored-by: Bai <bugatti_it@163.com>
2020-12-09 13:44:36 +08:00
ibuler
03ff53546e fix: 修复centos的mirror问题 2020-12-07 10:50:22 +08:00
Bai
fedb650cf9 fix: Node ordering [parent_key, value]; 修复默认组织Default节点显示问题(存在key为0的Default节点) 2020-12-03 10:45:45 +08:00
fit2bot
a99cda7bc7 build(pip): 锁定pip版本 (#5153)
* build(pip): 锁定pip版本
2020-12-02 11:10:42 +08:00
xinwen
8eb46b6450 fix(assets): 推送动态系统用户未指定 username 取全部 usernames 2020-12-01 20:09:28 +08:00
xinwen
c389c5f5f6 fix(perms): 新建授权时动态用户可能推送不成功 2020-12-01 20:09:28 +08:00
12 changed files with 152 additions and 55 deletions

View File

@@ -20,14 +20,16 @@ WORKDIR /opt/jumpserver
COPY ./requirements ./requirements
RUN useradd jumpserver
RUN wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo
RUN wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo \
&& sed -i 's@/centos/@/centos-vault/@g' /etc/yum.repos.d/CentOS-Base.repo \
&& sed -i 's@$releasever@6.10@g' /etc/yum.repos.d/CentOS-Base.repo
RUN yum -y install epel-release && \
echo -e "[mysql]\nname=mysql\nbaseurl=${MYSQL_MIRROR}\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
RUN yum -y install $(cat requirements/rpm_requirements.txt)
RUN pip install --upgrade pip setuptools==49.6.0 wheel -i ${PIP_MIRROR} && \
RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel -i ${PIP_MIRROR} && \
pip config set global.index-url ${PIP_MIRROR}
RUN pip install $(grep 'jms' requirements/requirements.txt) -i ${PIP_JMS_MIRROR}
RUN pip install -r requirements/requirements.txt
RUN pip install --no-cache-dir $(grep 'jms' requirements/requirements.txt) -i ${PIP_JMS_MIRROR}
RUN pip install --no-cache-dir -r requirements/requirements.txt
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config

View File

@@ -12,9 +12,8 @@ from .. import models
class DBAttrsSerializer(serializers.Serializer):
host = serializers.CharField(max_length=128, label=_('Host'))
port = serializers.IntegerField(label=_('Port'))
database = serializers.CharField(
max_length=128, required=False, allow_blank=True, allow_null=True, label=_('Database')
)
# 添加allow_null=True兼容之前数据库中database字段为None的情况
database = serializers.CharField(max_length=128, required=True, allow_null=True, label=_('Database'))
class MySQLAttrsSerializer(DBAttrsSerializer):

View File

@@ -354,7 +354,8 @@ class SomeNodesMixin:
def org_root(cls):
root = cls.objects.filter(parent_key='')\
.filter(key__regex=r'^[0-9]+$')\
.exclude(key__startswith='-')
.exclude(key__startswith='-')\
.order_by('key')
if root:
return root[0]
else:
@@ -411,7 +412,7 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
class Meta:
verbose_name = _("Node")
ordering = ['value']
ordering = ['parent_key', 'value']
def __str__(self):
return self.full_value

View File

@@ -78,6 +78,7 @@ def on_system_user_update(instance: SystemUser, created, **kwargs):
@receiver(m2m_changed, sender=SystemUser.assets.through)
@on_transaction_commit
def on_system_user_assets_change(instance, action, model, pk_set, **kwargs):
"""
当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
@@ -96,6 +97,7 @@ def on_system_user_assets_change(instance, action, model, pk_set, **kwargs):
@receiver(m2m_changed, sender=SystemUser.users.through)
@on_transaction_commit
def on_system_user_users_change(sender, instance: SystemUser, action, model, pk_set, reverse, **kwargs):
"""
当系统用户和用户关系发生变化时,应该重新推送系统用户资产中
@@ -117,6 +119,7 @@ def on_system_user_users_change(sender, instance: SystemUser, action, model, pk_
@receiver(m2m_changed, sender=SystemUser.nodes.through)
@on_transaction_commit
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
"""
当系统用户和节点关系发生变化时,应该将节点下资产关联到新的系统用户上

View File

@@ -2,13 +2,13 @@
from itertools import groupby
from celery import shared_task
from common.db.utils import get_object_if_need, get_objects_if_need, get_objects
from common.db.utils import get_object_if_need, get_objects
from django.utils.translation import ugettext as _
from django.db.models import Empty
from common.utils import encrypt_password, get_logger
from assets.models import SystemUser, Asset
from orgs.utils import org_aware_func
from assets.models import SystemUser, Asset, AuthBook
from orgs.utils import org_aware_func, tmp_to_root_org
from . import const
from .utils import clean_ansible_task_hosts, group_asset_by_platform
@@ -190,15 +190,12 @@ def get_push_system_user_tasks(system_user, platform="unixlike", username=None):
@org_aware_func("system_user")
def push_system_user_util(system_user, assets, task_name, username=None):
from ops.utils import update_or_create_ansible_task
hosts = clean_ansible_task_hosts(assets, system_user=system_user)
if not hosts:
assets = clean_ansible_task_hosts(assets, system_user=system_user)
if not assets:
return {}
platform_hosts_map = {}
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
for i in platform_hosts:
platform_hosts_map[i[0]] = list(i[1])
assets_sorted = sorted(assets, key=group_asset_by_platform)
platform_hosts = groupby(assets_sorted, key=group_asset_by_platform)
def run_task(_tasks, _hosts):
if not _tasks:
@@ -209,27 +206,59 @@ def push_system_user_util(system_user, assets, task_name, username=None):
)
task.run()
for platform, _hosts in platform_hosts_map.items():
if not _hosts:
if system_user.username_same_with_user:
if username is None:
# 动态系统用户,但是没有指定 username
usernames = list(system_user.users.all().values_list('username', flat=True).distinct())
else:
usernames = [username]
else:
# 非动态系统用户指定 username 无效
assert username is None, 'Only Dynamic user can assign `username`'
usernames = [system_user.username]
for platform, _assets in platform_hosts:
_assets = list(_assets)
if not _assets:
continue
print(_("Start push system user for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_hosts)))
print(_("Hosts count: {}").format(len(_assets)))
# 如果没有特殊密码设置,就不需要单独推送某台机器了
if not system_user.has_special_auth(username=username):
logger.debug("System user not has special auth")
tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, _hosts)
continue
id_asset_map = {_asset.id: _asset for _asset in _assets}
assets_id = id_asset_map.keys()
no_special_auth = []
special_auth_set = set()
for _host in _hosts:
system_user.load_asset_special_auth(_host, username=username)
tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, [_host])
auth_books = AuthBook.objects.filter(username__in=usernames, asset_id__in=assets_id)
for auth_book in auth_books:
special_auth_set.add((auth_book.username, auth_book.asset_id))
for _username in usernames:
no_special_assets = []
for asset_id in assets_id:
if (_username, asset_id) not in special_auth_set:
no_special_assets.append(id_asset_map[asset_id])
if no_special_assets:
no_special_auth.append((_username, no_special_assets))
for _username, no_special_assets in no_special_auth:
tasks = get_push_system_user_tasks(system_user, platform, username=_username)
run_task(tasks, no_special_assets)
for auth_book in auth_books:
system_user._merge_auth(auth_book)
tasks = get_push_system_user_tasks(system_user, platform, username=auth_book.username)
asset = id_asset_map[auth_book.asset_id]
run_task(tasks, [asset])
@shared_task(queue="ansible")
@tmp_to_root_org()
def push_system_user_to_assets_manual(system_user, username=None):
"""
将系统用户推送到与它关联的所有资产上
"""
system_user = get_object_if_need(SystemUser, system_user)
assets = system_user.get_related_assets()
task_name = _("Push system users to assets: {}").format(system_user.name)
@@ -237,7 +266,11 @@ def push_system_user_to_assets_manual(system_user, username=None):
@shared_task(queue="ansible")
@tmp_to_root_org()
def push_system_user_a_asset_manual(system_user, asset, username=None):
"""
将系统用户推送到一个资产上
"""
if username is None:
username = system_user.username
task_name = _("Push system users to asset: {}({}) => {}").format(
@@ -247,10 +280,15 @@ def push_system_user_a_asset_manual(system_user, asset, username=None):
@shared_task(queue="ansible")
@tmp_to_root_org()
def push_system_user_to_assets(system_user_id, assets_id, username=None):
"""
推送系统用户到指定的若干资产上
"""
system_user = SystemUser.objects.get(id=system_user_id)
assets = get_objects(Asset, assets_id)
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name, username=username)
# @shared_task

View File

@@ -54,12 +54,3 @@ class UserConnectionTokenApi(RootOrgViewMixin, APIView):
return Response(value)
else:
return Response({'user': value['user']})
def get_permissions(self):
if self.request.query_params.get('user-only', None):
self.permission_classes = (AllowAny,)
return super().get_permissions()

View File

@@ -2,6 +2,7 @@
#
import json
import os
import uuid
from django.conf import settings
from django.utils.timezone import get_current_timezone
@@ -101,6 +102,10 @@ def get_celery_periodic_task(task_name):
def get_celery_task_log_path(task_id):
task_id = str(task_id)
try:
uuid.UUID(task_id)
except:
return
rel_path = os.path.join(task_id[0], task_id[1], task_id + '.log')
path = os.path.join(settings.CELERY_LOG_DIR, rel_path)
os.makedirs(os.path.dirname(path), exist_ok=True)

View File

@@ -15,7 +15,11 @@ class CeleryLogWebsocket(JsonWebsocketConsumer):
disconnected = False
def connect(self):
self.accept()
user = self.scope["user"]
if user.is_authenticated:
self.accept()
else:
self.close()
def receive(self, text_data=None, bytes_data=None, **kwargs):
data = json.loads(text_data)

View File

@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from functools import partial
from django.db.models.signals import m2m_changed
from django.db.models.signals import post_save
@@ -7,10 +9,10 @@ from django.dispatch import receiver
from orgs.utils import tmp_to_org
from .models import Organization, OrganizationMember
from .hands import set_current_org, current_org, Node, get_current_org
from perms.models import (AssetPermission, DatabaseAppPermission,
K8sAppPermission, RemoteAppPermission)
from users.models import UserGroup
from .hands import set_current_org, Node, get_current_org
from perms.models import (AssetPermission, ApplicationPermission)
from users.models import UserGroup, User
from common.const.signals import PRE_REMOVE, POST_REMOVE
@receiver(post_save, sender=Organization)
@@ -34,11 +36,44 @@ def _remove_users(model, users, org):
users = (users, )
m2m_model = model.users.through
if model.users.reverse:
reverse = model.users.reverse
if reverse:
m2m_field_name = model.users.field.m2m_reverse_field_name()
else:
m2m_field_name = model.users.field.m2m_field_name()
m2m_model.objects.filter(**{'user__in': users, f'{m2m_field_name}__org_id': org.id}).delete()
relations = m2m_model.objects.filter(**{
'user__in': users,
f'{m2m_field_name}__org_id': org.id
})
object_id_users_id_map = defaultdict(set)
m2m_field_attr_name = f'{m2m_field_name}_id'
for relation in relations:
object_id = getattr(relation, m2m_field_attr_name)
object_id_users_id_map[object_id].add(relation.user_id)
objects = model.objects.filter(id__in=object_id_users_id_map.keys())
send_m2m_change_signal = partial(
m2m_changed.send,
sender=m2m_model, reverse=reverse, model=User, using=model.objects.db
)
for obj in objects:
send_m2m_change_signal(
instance=obj,
pk_set=object_id_users_id_map[obj.id],
action=PRE_REMOVE
)
relations.delete()
for obj in objects:
send_m2m_change_signal(
instance=obj,
pk_set=object_id_users_id_map[obj.id],
action=POST_REMOVE
)
def _clear_users_from_org(org, users):
@@ -48,8 +83,7 @@ def _clear_users_from_org(org, users):
if not users:
return
models = (AssetPermission, DatabaseAppPermission,
RemoteAppPermission, K8sAppPermission, UserGroup)
models = (AssetPermission, ApplicationPermission, UserGroup)
for m in models:
_remove_users(m, users, org)

View File

@@ -1,5 +1,3 @@
from itertools import chain
from rest_framework import serializers
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
@@ -9,7 +7,7 @@ from django.db.models import Q
from common.utils.timezone import dt_parser, dt_formater
from orgs.utils import tmp_to_root_org
from orgs.models import Organization, ROLE as ORG_ROLE
from assets.models.asset import Asset
from assets.models import Asset, SystemUser
from users.models.user import User
from perms.serializers import ActionsField
from perms.models import Action
@@ -130,12 +128,23 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
if hostname:
q |= Q(hostname__icontains=hostname)
data['confirmed_assets'] = list(
map(lambda x: str(x), chain(*Asset.objects.filter(q)[0: limit].values_list('id'))))
recomand_assets_id = Asset.objects.filter(q)[:limit].values_list('id', flat=True)
data['confirmed_assets'] = [str(i) for i in recomand_assets_id]
def _recommend_system_users(self, data, instance):
confirmed_system_users = data.get('confirmed_system_users')
if not confirmed_system_users and self._is_assignee(instance):
system_user = data.get('system_user')
recomand_system_users_id = SystemUser.objects.filter(
name__icontains=system_user
)[:3].values_list('id', flat=True)
data['confirmed_system_users'] = [str(i) for i in recomand_system_users_id]
def to_representation(self, instance):
data = super().to_representation(instance)
self._recommend_assets(data, instance)
self._recommend_system_users(data, instance)
return data
def _create_body(self, validated_data):

View File

@@ -28,3 +28,12 @@ class UserUserGroupRelationViewSet(JMSBulkRelationModelViewSet):
return False
else:
return True
def perform_create(self, serializer):
validated_data = []
for item in serializer.validated_data:
if item['user'].role == User.ROLE.AUDITOR:
continue
validated_data.append(item)
serializer._validated_data = validated_data
return super().perform_create(serializer)

View File

@@ -53,6 +53,8 @@ Pillow==7.1.0
pyasn1==0.4.8
pycparser==2.19
pycrypto==2.6.1
pycryptodome==3.9.9
pycryptodomex==3.9.9
pyotp==2.2.6
PyNaCl==1.2.1
python-dateutil==2.6.1