Compare commits

...

37 Commits

Author SHA1 Message Date
ibuler
9bccbe9143 fix: 修复 private storage permission 2023-09-11 12:20:00 +08:00
feng
37f04720e3 fix: ansible task 500 2023-08-01 16:06:35 +08:00
ibuler
bd1db91e99 perf: 修改 ansible version 2023-08-01 10:51:41 +08:00
ibuler
eae99e23f2 perf: 修改 inventory 2023-08-01 10:50:33 +08:00
Bai
c46743901d fix: 修复客户端方式访问资产 Endpoint 标签匹配策略不生效的问题 2023-07-19 18:14:11 +08:00
老广
9ddd04adbf Merge pull request #11004 from jumpserver/pr@v3.4@fix_ansiblejobrunerror
fix: 修复批量执行命令时资产名称包含 [ 特殊字符执行报错的问题(issue: 10986)
2023-07-18 18:13:54 +08:00
Bai
db55402fb8 fix: 修复批量执行命令时资产名称包含 [ 特殊字符执行报错的问题(issue: 10986) 2023-07-18 10:06:41 +00:00
老广
c8924274c3 Merge pull request #10968 from jumpserver/pr@v3.4@fix_ansibletesterror
fix: 修复 Ansible 测试资产可连接性报错的问题(Connection to UNKNOWN port 65535 timed out)
2023-07-17 10:28:12 +08:00
Bai
6d180cbdd2 fix: 修复 Ansible 测试资产可连接性报错的问题(Connection to UNKNOWN port 65535 timed out) 2023-07-14 11:19:32 +00:00
Bai
550c93b48b fix: 增加 TypeError 捕获 2023-07-11 11:16:07 +08:00
Bai
7d581737cb fix: 修复 beat 定时任务重复执行的问题 2023-07-11 11:16:07 +08:00
Bai
0e0c860d59 fix: 修复导入LDAP数据库超时导致 Lock wait timeout 的问题 2023-07-05 19:10:42 +08:00
吴小白
e0da9ee30c Merge pull request #10911 from jumpserver/pr@v3.4@perf_chrome
perf: 修正 Chrome driver 路径
2023-07-05 18:37:53 +08:00
Eric
499e008c6d perf: 修正 Chrome driver 路径 2023-07-05 10:29:33 +00:00
Bai
896cb061ab fix: 修复用户导入时手机号为dict类型报错的问题 2023-07-05 16:49:27 +08:00
feng
11464151e2 fix: 有默认值 required 为false 2023-07-05 15:31:09 +08:00
feng
4311e52681 fix: 推送账号 不填写home 推送失败 2023-07-05 15:31:09 +08:00
吴小白
a7c06f62f6 perf: 更新 Chrome 2023-07-04 18:45:33 +08:00
Eric
33db6de372 fix: 修复自定义远程应用的连接问题 2023-06-27 14:42:35 +08:00
feng
0bea545b6f fix: 修复自动化任务原子性error 导致整个任务失败问题 2023-06-25 18:20:28 +08:00
jiangweidong
424066b38f perf: 优化飞书接收到的工单审批的连接无法点击的问题 2023-06-25 11:08:56 +08:00
Eric
13d895b22e fix: 修复远程应用会话无法监控的问题 2023-06-21 12:05:19 +08:00
Bai
d5c51a4f0e fix: 修复迁移文件时触发信号记录操作日志导致迁移失败的问题 2023-06-21 11:02:14 +08:00
Eric
7c965706d4 perf: 修复 terminal 显示问题 2023-06-20 16:33:43 +08:00
feng
f59499f77e fix: 修改 push_account_params 数据迁移逻辑,不在导入公共方法生成数据 2023-06-19 18:23:23 +08:00
ibuler
98536cbc24 fix: 修改原来 platform 为 device 时,导致的 asset 类型不对 2023-06-19 17:54:17 +08:00
老广
7c29e60a82 Merge pull request #10757 from jumpserver/pr@v3.4@fix_custom_asset_detail_error
perf: 修复自定义资产详情没有 auto_config 的问题
2023-06-16 18:48:41 +08:00
ibuler
9e68d7d1a0 perf: 修复自定义资产详情没有 auto_config 的问题 2023-06-16 10:45:00 +00:00
ibuler
77b7134404 perf: 优化自定义 platform field 2023-06-16 17:02:46 +08:00
老广
fb7a839c16 Merge pull request #10755 from jumpserver/pr@v3.4@fix_permed_asset_duplicate
fix: 修复授权资产根据协议搜索重复的问题
2023-06-16 16:53:21 +08:00
ibuler
e80855068e fix: 修复授权资产根据协议搜索重复的问题 2023-06-16 08:44:53 +00:00
ibuler
5832246e5f perf: 修改 login acls 迁移冲突问题
perf: 修改 login acls 迁移,避免冲突
2023-06-16 13:58:51 +08:00
ibuler
dbef45e23b perf: 修复 acl 迁移后无法使用 2023-06-15 18:46:33 +08:00
feng
dedc42d775 feat: 添加自动化任务rdp ping 2023-06-15 18:46:04 +08:00
老广
f64073f48f Merge pull request #10739 from jumpserver/pr@v3.4@pr@dev@change_base_image
perf: 修改基础镜像版本
2023-06-15 17:36:33 +08:00
ibuler
70754ad748 perf: 修改基础镜像版本 2023-06-15 17:33:04 +08:00
fit2bot
3158980057 perf: 修改翻译 (#10734)
Co-authored-by: feng <1304903146@qq.com>
2023-06-15 15:41:25 +08:00
46 changed files with 378 additions and 172 deletions

View File

@@ -1,4 +1,4 @@
FROM python:3.9-slim-buster as stage-build
FROM python:3.9-slim-bullseye as stage-build
ARG TARGETARCH
ARG VERSION
@@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim-buster
FROM python:3.9-slim-bullseye
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>

View File

@@ -1,11 +1,12 @@
- hosts: custom
gather_facts: no
vars:
ansible_shell_type: sh
ansible_connection: local
tasks:
- name: Verify account
ssh_ping:
- name: Verify account (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"

View File

@@ -4,7 +4,7 @@
ansible_connection: local
tasks:
- name: Verify account
- name: Verify account (paramiko)
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"

View File

@@ -63,15 +63,17 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
@staticmethod
def get_snapshot(obj):
tp = obj.snapshot['type']
tp = obj.snapshot.get('type', '')
type_display = tp if not hasattr(AutomationTypes, tp) \
else getattr(AutomationTypes, tp).label
snapshot = {
'type': tp,
'name': obj.snapshot['name'],
'comment': obj.snapshot['comment'],
'accounts': obj.snapshot['accounts'],
'node_amount': len(obj.snapshot['nodes']),
'asset_amount': len(obj.snapshot['assets']),
'type_display': getattr(AutomationTypes, tp).label,
'name': obj.snapshot.get('name'),
'comment': obj.snapshot.get('comment'),
'accounts': obj.snapshot.get('accounts'),
'node_amount': len(obj.snapshot.get('nodes', [])),
'asset_amount': len(obj.snapshot.get('assets', [])),
'type_display': type_display,
}
return snapshot

View File

@@ -1,5 +1,4 @@
# Generated by Django 3.2.17 on 2023-06-06 10:57
from collections import defaultdict
from django.db import migrations, models
@@ -8,17 +7,20 @@ import common.db.fields
def migrate_users_login_acls(apps, schema_editor):
login_acl_model = apps.get_model('acls', 'LoginACL')
name_used = defaultdict(int)
for login_acl in login_acl_model.objects.all():
name = login_acl.name
if name_used[name] > 0:
login_acl.name += "_{}".format(name_used[name])
name_used[name] += 1
name_used = []
login_acls = []
for login_acl in login_acl_model.objects.all().select_related('user'):
name = '{}_{}'.format(login_acl.name, login_acl.user.username)
if name.lower() in name_used:
name += '_{}'.format(str(login_acl.user_id)[:4])
name_used.append(name.lower())
login_acl.name = name
login_acl.users = {
"type": "ids", "ids": [str(login_acl.user_id)]
}
login_acl.save()
login_acls.append(login_acl)
login_acl_model.objects.bulk_update(login_acls, ['name', 'users'])
class Migration(migrations.Migration):

View File

@@ -82,7 +82,7 @@ class AssetFilterSet(BaseFilterSet):
@staticmethod
def filter_protocols(queryset, name, value):
value = value.split(',')
return queryset.filter(protocols__name__in=value)
return queryset.filter(protocols__name__in=value).distinct()
@staticmethod
def filter_labels(queryset, name, value):
@@ -91,7 +91,7 @@ class AssetFilterSet(BaseFilterSet):
queryset = queryset.filter(labels__name=n, labels__value=v)
else:
q = Q(labels__name__contains=value) | Q(labels__value__contains=value)
queryset = queryset.filter(q)
queryset = queryset.filter(q).distinct()
return queryset

View File

@@ -57,6 +57,7 @@ class BasePlaybookManager:
data = self.params.get(method_id)
if not data:
data = automation_params.get(method_id, {})
params = serializer(data).data
return {
field_name: automation_params.get(field_name, '')
@@ -262,7 +263,7 @@ class BasePlaybookManager:
info = self.file_to_json(runner.inventory)
servers, not_valid = [], []
for k, host in info['all']['hosts'].items():
jms_asset, jms_gateway = host['jms_asset'], host.get('gateway')
jms_asset, jms_gateway = host.get('jms_asset'), host.get('gateway')
if not jms_gateway:
continue

View File

@@ -0,0 +1,15 @@
- hosts: custom
gather_facts: no
vars:
ansible_shell_type: sh
ansible_connection: local
tasks:
- name: Test asset connection (pyfreerdp)
rdp_ping:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"

View File

@@ -0,0 +1,13 @@
id: ping_by_rdp
name: "{{ 'Ping by pyfreerdp' | trans }}"
category:
- device
- host
type:
- windows
method: ping
i18n:
Ping by pyfreerdp:
zh: 使用 Python 模块 pyfreerdp 测试主机可连接性
en: Ping by pyfreerdp module
ja: Pyfreerdpモジュールを使用してホストにPingする

View File

@@ -4,7 +4,7 @@
ansible_connection: local
tasks:
- name: Test asset connection
- name: Test asset connection (paramiko)
ssh_ping:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"

View File

@@ -2,6 +2,7 @@
import django.db
from django.db import migrations, models
import common.db.fields
@@ -118,7 +119,7 @@ class Migration(migrations.Migration):
primary_key=True, serialize=False, to='assets.asset')),
],
options={
'verbose_name': 'Host',
'verbose_name': 'Host',
},
),
migrations.CreateModel(

View File

@@ -137,6 +137,24 @@ def migrate_to_nodes(apps, *args):
parent.save()
def migrate_ori_host_to_devices(apps, *args):
device_model = apps.get_model('assets', 'Device')
asset_model = apps.get_model('assets', 'Asset')
host_model = apps.get_model('assets', 'Host')
hosts_need_migrate_to_device = host_model.objects.filter(asset_ptr__platform__category='device')
assets = asset_model.objects.filter(id__in=hosts_need_migrate_to_device.values_list('asset_ptr_id', flat=True))
assets_map = {asset.id: asset for asset in assets}
for host in hosts_need_migrate_to_device:
asset = assets_map.get(host.asset_ptr_id)
if not asset:
continue
device = device_model(asset_ptr_id=asset.id)
device.__dict__.update(asset.__dict__)
device.save()
host.delete(keep_parents=True)
class Migration(migrations.Migration):
dependencies = [
('assets', '0097_auto_20220426_1558'),
@@ -146,5 +164,6 @@ class Migration(migrations.Migration):
operations = [
migrations.RunPython(migrate_database_to_asset),
migrations.RunPython(migrate_cloud_to_asset),
migrations.RunPython(migrate_to_nodes)
migrations.RunPython(migrate_to_nodes),
migrations.RunPython(migrate_ori_host_to_devices),
]

View File

@@ -2,16 +2,13 @@
from django.db import migrations, models
from assets.const import AllTypes
def migrate_automation_push_account_params(apps, schema_editor):
platform_automation_model = apps.get_model('assets', 'PlatformAutomation')
platform_automation_methods = AllTypes.get_automation_methods()
methods_id_data_map = {
i['id']: None if i['params_serializer'] is None else i['params_serializer']({}).data
for i in platform_automation_methods
if i['method'] == 'push_account'
'push_account_aix': {'sudo': '/bin/whoami', 'shell': '/bin/bash', 'home': '', 'groups': ''},
'push_account_posix': {'sudo': '/bin/whoami', 'shell': '/bin/bash', 'home': '', 'groups': ''},
'push_account_local_windows': {'groups': 'Users,Remote Desktop Users'},
}
automation_objs = []
for automation in platform_automation_model.objects.all():

View File

@@ -206,15 +206,14 @@ class Asset(NodesRelationMixin, AbsConnectivity, JSONFilterMixin, JMSOrgBaseMode
@lazyproperty
def auto_config(self):
platform = self.platform
automation = self.platform.automation
auto_config = {
'su_enabled': platform.su_enabled,
'domain_enabled': platform.domain_enabled,
'ansible_enabled': False
}
automation = getattr(self.platform, 'automation', None)
if not automation:
return auto_config
auto_config.update(model_to_dict(automation))
return auto_config

View File

@@ -51,14 +51,14 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
from assets.const import AutomationTypes as AssetTypes
from accounts.const import AutomationTypes as AccountTypes
tp_dict = dict(AssetTypes.choices) | dict(AccountTypes.choices)
tp = obj.snapshot['type']
tp = obj.snapshot.get('type', '')
snapshot = {
'type': {'value': tp, 'label': tp_dict.get(tp, tp)},
'name': obj.snapshot['name'],
'comment': obj.snapshot['comment'],
'accounts': obj.snapshot['accounts'],
'node_amount': len(obj.snapshot['nodes']),
'asset_amount': len(obj.snapshot['assets']),
'name': obj.snapshot.get('name'),
'comment': obj.snapshot.get('comment'),
'accounts': obj.snapshot.get('accounts'),
'node_amount': len(obj.snapshot.get('nodes', [])),
'asset_amount': len(obj.snapshot.get('assets', [])),
}
return snapshot

View File

@@ -42,7 +42,7 @@ def _get_instance_field_value(
if getattr(f, 'attname', None) in model_need_continue_fields:
continue
value = getattr(instance, f.name) or getattr(instance, f.attname)
value = getattr(instance, f.name, None) or getattr(instance, f.attname, None)
if not isinstance(value, bool) and not value:
continue

View File

@@ -22,7 +22,7 @@ from common.utils.http import is_true, is_false
from orgs.mixins.api import RootOrgViewMixin
from perms.models import ActionChoices
from terminal.connect_methods import NativeClient, ConnectMethodUtil
from terminal.models import EndpointRule
from terminal.models import EndpointRule, Endpoint
from ..models import ConnectionToken, date_expired_default
from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
@@ -165,11 +165,13 @@ class RDPFileClientProtocolURLMixin:
return data
def get_smart_endpoint(self, protocol, asset=None):
target_ip = asset.get_target_ip() if asset else ''
endpoint = EndpointRule.match_endpoint(
target_instance=asset, target_ip=target_ip,
protocol=protocol, request=self.request
)
endpoint = Endpoint.match_by_instance_label(asset, protocol)
if not endpoint:
target_ip = asset.get_target_ip() if asset else ''
endpoint = EndpointRule.match_endpoint(
target_instance=asset, target_ip=target_ip,
protocol=protocol, request=self.request
)
return endpoint

View File

@@ -328,13 +328,13 @@ class RelatedManager:
q = Q()
if isinstance(val, str):
val = [val]
if ['*'] in val:
return Q()
for ip in val:
if not ip:
continue
try:
if ip == '*':
return Q()
elif '/' in ip:
if '/' in ip:
network = ipaddress.ip_network(ip)
ips = network.hosts()
q |= Q(**{"{}__in".format(name): ips})
@@ -378,7 +378,7 @@ class RelatedManager:
if match == 'ip_in':
q = cls.get_ip_in_q(name, val)
elif match in ("exact", "contains", "startswith", "endswith", "gte", "lte", "gt", "lt"):
elif match in ("contains", "startswith", "endswith", "gte", "lte", "gt", "lt"):
lookup = "{}__{}".format(name, match)
q = Q(**{lookup: val})
elif match == 'regex':
@@ -470,9 +470,9 @@ class JSONManyToManyDescriptor:
continue
if rule_match == 'in':
res &= value in rule_value
res &= value in rule_value or '*' in rule_value
elif rule_match == 'exact':
res &= value == rule_value
res &= value == rule_value or rule_value == '*'
elif rule_match == 'contains':
res &= rule_value in value
elif rule_match == 'startswith':
@@ -499,7 +499,7 @@ class JSONManyToManyDescriptor:
elif rule['match'] == 'ip_in':
if isinstance(rule_value, str):
rule_value = [rule_value]
res &= contains_ip(value, rule_value)
res &= '*' in rule_value or contains_ip(value, rule_value)
elif rule['match'] == 'm2m':
if isinstance(value, Manager):
value = value.values_list('id', flat=True)

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

@@ -44,19 +44,21 @@ def set_default_by_type(tp, data, field_info):
def create_serializer_class(serializer_name, fields_info):
serializer_fields = {}
fields_name = ['name', 'label', 'default', 'type', 'help_text']
fields_name = ['name', 'label', 'default', 'required', 'type', 'help_text']
for i, field_info in enumerate(fields_info):
data = {k: field_info.get(k) for k in fields_name}
field_type = data.pop('type', 'str')
if data.get('default') is None:
# 用户定义 default 和 required 可能会冲突, 所以要处理一下
default = data.get('default')
if default is not None:
data['required'] = False
else:
data.pop('default', None)
data['required'] = field_info.get('required', True)
data['required'] = True
data = set_default_by_type(field_type, data, field_info)
data = set_default_if_need(data, i)
if data.get('default', None) is not None:
data['required'] = False
field_name = data.pop('name')
field_class = type_field_map.get(field_type, serializers.CharField)
serializer_fields[field_name] = field_class(**data)

View File

@@ -212,6 +212,23 @@ class BitChoicesField(TreeChoicesField):
class PhoneField(serializers.CharField):
def to_internal_value(self, data):
if isinstance(data, dict):
code = data.get('code')
phone = data.get('phone', '')
if code and phone:
data = '{}{}'.format(code, phone)
else:
data = phone
try:
phone = phonenumbers.parse(data, 'CN')
data = '{}{}'.format(phone.country_code, phone.national_number)
except phonenumbers.NumberParseException:
data = '+86{}'.format(data)
return super().to_internal_value(data)
def to_representation(self, value):
if value:
try:

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,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a76aa384867a4732eb7d2365515a1a972502ebadcba4de8236c1dcb3c5c7fdd2
size 145757
oid sha256:cd4fb6a0396c8636f8a36645354a5102790c020d73cdeb1f0e1d1f1b34ea39e9
size 145760

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-14 20:40+0800\n"
"POT-Creation-Date: 2023-06-15 15:35+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -28,7 +28,7 @@ msgstr "パラメータ 'action' は [{}] でなければなりません。"
#: authentication/confirm/password.py:9 authentication/forms.py:32
#: authentication/templates/authentication/login.html:274
#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47
#: users/forms/profile.py:22 users/serializers/user.py:105
#: users/forms/profile.py:22 users/serializers/user.py:104
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28
@@ -505,7 +505,7 @@ msgstr "特権アカウント"
#: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39
#: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:114
#: terminal/models/applet/applet.py:39 users/serializers/user.py:170
#: terminal/models/applet/applet.py:39 users/serializers/user.py:169
msgid "Is active"
msgstr "アクティブです。"
@@ -1281,7 +1281,7 @@ msgstr "資産ハードウェア情報の収集"
#: assets/models/asset/common.py:159 assets/serializers/asset/custom.py:14
msgid "Custom info"
msgstr "自动化信息"
msgstr "カスタム属性"
#: assets/models/asset/common.py:335
msgid "Can refresh asset hardware info"
@@ -1645,8 +1645,8 @@ msgstr "プロトコルが必要です: {}"
msgid "Default database"
msgstr "デフォルト・データベース"
#: assets/serializers/asset/database.py:28 common/db/fields.py:570
#: common/db/fields.py:575 common/serializers/fields.py:104
#: assets/serializers/asset/database.py:28 common/db/fields.py:579
#: common/db/fields.py:584 common/serializers/fields.py:104
#: tickets/serializers/ticket/common.py:58
#: xpack/plugins/cloud/serializers/account_attrs.py:56
#: xpack/plugins/cloud/serializers/account_attrs.py:79
@@ -2654,7 +2654,7 @@ msgstr "アクション"
#: authentication/serializers/connection_token.py:44
#: perms/serializers/permission.py:38 perms/serializers/permission.py:57
#: users/serializers/user.py:97 users/serializers/user.py:173
#: users/serializers/user.py:96 users/serializers/user.py:172
msgid "Is expired"
msgstr "期限切れです"
@@ -2680,8 +2680,8 @@ msgid "The {} cannot be empty"
msgstr "{} 空にしてはならない"
#: authentication/serializers/token.py:79 perms/serializers/permission.py:37
#: perms/serializers/permission.py:58 users/serializers/user.py:98
#: users/serializers/user.py:171
#: perms/serializers/permission.py:58 users/serializers/user.py:97
#: users/serializers/user.py:170
msgid "Is valid"
msgstr "有効です"
@@ -3116,7 +3116,7 @@ msgstr "テキストフィールドへのマーシャルデータ"
msgid "Encrypt field using Secret Key"
msgstr "Secret Keyを使用したフィールドの暗号化"
#: common/db/fields.py:558
#: common/db/fields.py:567
msgid ""
"Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or "
"{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', "
@@ -3126,19 +3126,19 @@ msgstr ""
"'type''ids''ids':[]}或 #タイプ:属性、属性:[名前ip、照合正確、"
"値1.1.1.1"
#: common/db/fields.py:565
#: common/db/fields.py:574
msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\""
msgstr "無効なタイプです。all、ids、またはattrsでなければなりません"
#: common/db/fields.py:568
#: common/db/fields.py:577
msgid "Invalid ids for ids, should be a list"
msgstr "無効なID、リストでなければなりません"
#: common/db/fields.py:573 common/db/fields.py:578
#: common/db/fields.py:582 common/db/fields.py:587
msgid "Invalid attrs, should be a list of dict"
msgstr "無効な属性、dictリストでなければなりません"
#: common/db/fields.py:580
#: common/db/fields.py:589
msgid "Invalid attrs, should be has name and value"
msgstr "名前と値が必要な無効な属性"
@@ -3654,7 +3654,7 @@ msgstr "Material"
msgid "Material Type"
msgstr "Material を選択してオプションを設定します。"
#: ops/models/job.py:460
#: ops/models/job.py:461
msgid "Job Execution"
msgstr "ジョブ実行"
@@ -6711,7 +6711,7 @@ msgstr "公開キー"
msgid "Force enable"
msgstr "強制有効"
#: users/models/user.py:765 users/serializers/user.py:172
#: users/models/user.py:765 users/serializers/user.py:171
msgid "Is service account"
msgstr "サービスアカウントです"
@@ -6723,7 +6723,7 @@ msgstr "アバター"
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:773 users/serializers/user.py:109
#: users/models/user.py:773 users/serializers/user.py:108
msgid "Phone"
msgstr "電話"
@@ -6740,7 +6740,7 @@ msgid "Secret key"
msgstr "秘密キー"
#: users/models/user.py:794 users/serializers/profile.py:149
#: users/serializers/user.py:169
#: users/serializers/user.py:168
msgid "Is first login"
msgstr "最初のログインです"
@@ -6823,55 +6823,55 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする
msgid "The newly set password is inconsistent"
msgstr "新しく設定されたパスワードが一致しない"
#: users/serializers/user.py:43
#: users/serializers/user.py:42
msgid "System roles"
msgstr "システムの役割"
#: users/serializers/user.py:47
#: users/serializers/user.py:46
msgid "Org roles"
msgstr "組織ロール"
#: users/serializers/user.py:90
#: users/serializers/user.py:89
msgid "Password strategy"
msgstr "パスワード戦略"
#: users/serializers/user.py:92
#: users/serializers/user.py:91
msgid "MFA enabled"
msgstr "MFA有効化"
#: users/serializers/user.py:94
#: users/serializers/user.py:93
msgid "MFA force enabled"
msgstr "MFAフォース有効化"
#: users/serializers/user.py:96
#: users/serializers/user.py:95
msgid "Login blocked"
msgstr "ログインがロックされました"
#: users/serializers/user.py:99 users/serializers/user.py:177
#: users/serializers/user.py:98 users/serializers/user.py:176
msgid "Is OTP bound"
msgstr "仮想MFAがバインドされているか"
#: users/serializers/user.py:101
#: users/serializers/user.py:100
msgid "Can public key authentication"
msgstr "公開鍵認証が可能"
#: users/serializers/user.py:174
#: users/serializers/user.py:173
msgid "Avatar url"
msgstr "アバターURL"
#: users/serializers/user.py:178
#: users/serializers/user.py:177
msgid "MFA level"
msgstr "MFA レベル"
#: users/serializers/user.py:284
#: users/serializers/user.py:283
msgid "Select users"
msgstr "ユーザーの選択"
#: users/serializers/user.py:285
#: users/serializers/user.py:284
msgid "For security, only list several users"
msgstr "セキュリティのために、複数のユーザーのみをリストします"
#: users/serializers/user.py:318
#: users/serializers/user.py:317
msgid "name not unique"
msgstr "名前が一意ではない"

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f32e327dd50762b76d209f80b7de470df6faf5242af383dd6152b0c7ea5a7974
oid sha256:0efb248e80873f34d20f0fc3d4dd5c5a346048cb683c2b6bda3df939697fc52c
size 119261

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-14 20:40+0800\n"
"POT-Creation-Date: 2023-06-15 15:35+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@@ -27,7 +27,7 @@ msgstr "参数 'action' 必须是 [{}]"
#: authentication/confirm/password.py:9 authentication/forms.py:32
#: authentication/templates/authentication/login.html:274
#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47
#: users/forms/profile.py:22 users/serializers/user.py:105
#: users/forms/profile.py:22 users/serializers/user.py:104
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28
@@ -504,7 +504,7 @@ msgstr "特权账号"
#: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39
#: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:114
#: terminal/models/applet/applet.py:39 users/serializers/user.py:170
#: terminal/models/applet/applet.py:39 users/serializers/user.py:169
msgid "Is active"
msgstr "激活"
@@ -1274,7 +1274,7 @@ msgstr "收集资产硬件信息"
#: assets/models/asset/common.py:159 assets/serializers/asset/custom.py:14
msgid "Custom info"
msgstr "自动化信息"
msgstr "自定义属性"
#: assets/models/asset/common.py:335
msgid "Can refresh asset hardware info"
@@ -1636,8 +1636,8 @@ msgstr "协议是必填的: {}"
msgid "Default database"
msgstr "默认数据库"
#: assets/serializers/asset/database.py:28 common/db/fields.py:570
#: common/db/fields.py:575 common/serializers/fields.py:104
#: assets/serializers/asset/database.py:28 common/db/fields.py:579
#: common/db/fields.py:584 common/serializers/fields.py:104
#: tickets/serializers/ticket/common.py:58
#: xpack/plugins/cloud/serializers/account_attrs.py:56
#: xpack/plugins/cloud/serializers/account_attrs.py:79
@@ -2629,7 +2629,7 @@ msgstr "动作"
#: authentication/serializers/connection_token.py:44
#: perms/serializers/permission.py:38 perms/serializers/permission.py:57
#: users/serializers/user.py:97 users/serializers/user.py:173
#: users/serializers/user.py:96 users/serializers/user.py:172
msgid "Is expired"
msgstr "已过期"
@@ -2653,8 +2653,8 @@ msgid "The {} cannot be empty"
msgstr "{} 不能为空"
#: authentication/serializers/token.py:79 perms/serializers/permission.py:37
#: perms/serializers/permission.py:58 users/serializers/user.py:98
#: users/serializers/user.py:171
#: perms/serializers/permission.py:58 users/serializers/user.py:97
#: users/serializers/user.py:170
msgid "Is valid"
msgstr "是否有效"
@@ -3081,7 +3081,7 @@ msgstr "编码数据为 text"
msgid "Encrypt field using Secret Key"
msgstr "加密的字段"
#: common/db/fields.py:558
#: common/db/fields.py:567
msgid ""
"Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or "
"{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', "
@@ -3091,19 +3091,19 @@ msgstr ""
"{'type': 'attrs', 'attrs': [{'name': 'ip', 'match': 'exact', 'value': "
"'1.1.1.1'}}"
#: common/db/fields.py:565
#: common/db/fields.py:574
msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\""
msgstr "无效类型,应为 all、ids 或 attrs"
#: common/db/fields.py:568
#: common/db/fields.py:577
msgid "Invalid ids for ids, should be a list"
msgstr "无效的ID应为列表"
#: common/db/fields.py:573 common/db/fields.py:578
#: common/db/fields.py:582 common/db/fields.py:587
msgid "Invalid attrs, should be a list of dict"
msgstr "无效的属性应为dict列表"
#: common/db/fields.py:580
#: common/db/fields.py:589
msgid "Invalid attrs, should be has name and value"
msgstr "无效属性,应具有名称和值"
@@ -3612,7 +3612,7 @@ msgstr "Material"
msgid "Material Type"
msgstr "Material 类型"
#: ops/models/job.py:460
#: ops/models/job.py:461
msgid "Job Execution"
msgstr "作业执行"
@@ -6618,7 +6618,7 @@ msgstr "SSH公钥"
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:765 users/serializers/user.py:172
#: users/models/user.py:765 users/serializers/user.py:171
msgid "Is service account"
msgstr "服务账号"
@@ -6630,7 +6630,7 @@ msgstr "头像"
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:773 users/serializers/user.py:109
#: users/models/user.py:773 users/serializers/user.py:108
msgid "Phone"
msgstr "手机"
@@ -6647,7 +6647,7 @@ msgid "Secret key"
msgstr "Secret key"
#: users/models/user.py:794 users/serializers/profile.py:149
#: users/serializers/user.py:169
#: users/serializers/user.py:168
msgid "Is first login"
msgstr "首次登录"
@@ -6730,55 +6730,55 @@ msgstr "新密码不能是最近 {} 次的密码"
msgid "The newly set password is inconsistent"
msgstr "两次密码不一致"
#: users/serializers/user.py:43
#: users/serializers/user.py:42
msgid "System roles"
msgstr "系统角色"
#: users/serializers/user.py:47
#: users/serializers/user.py:46
msgid "Org roles"
msgstr "组织角色"
#: users/serializers/user.py:90
#: users/serializers/user.py:89
msgid "Password strategy"
msgstr "密码策略"
#: users/serializers/user.py:92
#: users/serializers/user.py:91
msgid "MFA enabled"
msgstr "MFA 已启用"
#: users/serializers/user.py:94
#: users/serializers/user.py:93
msgid "MFA force enabled"
msgstr "强制 MFA"
#: users/serializers/user.py:96
#: users/serializers/user.py:95
msgid "Login blocked"
msgstr "登录被锁定"
#: users/serializers/user.py:99 users/serializers/user.py:177
#: users/serializers/user.py:98 users/serializers/user.py:176
msgid "Is OTP bound"
msgstr "是否绑定了虚拟 MFA"
#: users/serializers/user.py:101
#: users/serializers/user.py:100
msgid "Can public key authentication"
msgstr "可以使用公钥认证"
#: users/serializers/user.py:174
#: users/serializers/user.py:173
msgid "Avatar url"
msgstr "头像路径"
#: users/serializers/user.py:178
#: users/serializers/user.py:177
msgid "MFA level"
msgstr "MFA 级别"
#: users/serializers/user.py:284
#: users/serializers/user.py:283
msgid "Select users"
msgstr "选择用户"
#: users/serializers/user.py:285
#: users/serializers/user.py:284
msgid "For security, only list several users"
msgstr "为了安全,仅列出几个用户"
#: users/serializers/user.py:318
#: users/serializers/user.py:317
msgid "name not unique"
msgstr "名称重复"

View File

@@ -194,7 +194,7 @@ class Message(metaclass=MessageType):
return self.markdown_msg
def get_feishu_msg(self) -> dict:
return self.text_msg
return self.markdown_msg
def get_email_msg(self) -> dict:
return self.html_msg_with_sign

View File

@@ -72,8 +72,7 @@ def create_system_messages(app_config: AppConfig, **kwargs):
sub, created = SystemMsgSubscription.objects.get_or_create(message_type=message_type)
if created:
obj.post_insert_to_db(sub)
logger.info(
f'Create SystemMsgSubscription: package={app_config.module.__package__} type={message_type}')
logger.info(f'Create MsgSubscription: package={app_config.module.__package__} type={message_type}')
except ModuleNotFoundError:
pass

View File

@@ -1,7 +1,8 @@
[defaults]
forks = 10
forks = 10
host_key_checking = False
library = /opt/jumpserver/apps/ops/ansible/modules:./modules
library = /opt/jumpserver/apps/ops/ansible/modules:./modules
timeout = 65
[inventory]
[privilege_escalation]
[paramiko_connection]

View File

@@ -109,21 +109,21 @@ class DefaultCallback:
pass
def playbook_on_stats(self, event_data, **kwargs):
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():
failed.append(host)
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 failed:
if host in failures:
self.summary['failures'][host] += {ignore_errors}
self.summary['ok'] = list(set(self.result['ok'].keys()) - set(failed))
self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(failed))
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

@@ -268,9 +268,15 @@ class JMSInventory:
data = {'all': {'hosts': {}}}
for host in hosts:
name = host.pop('name')
name = name.replace('[', '_').replace(']', '_')
data['all']['hosts'][name] = host
if self.exclude_localhost and data['all']['hosts'].__contains__('localhost'):
data['all']['hosts'].update({'localhost': {'ansible_host': '255.255.255.255'}})
if not self.exclude_localhost:
data['all']['hosts'].update({
'localhost': {
'ansible_host': '127.0.0.1',
'ansible_connection': 'local'
}
})
return data
def write_to_file(self, path):

View File

@@ -39,10 +39,6 @@ import pyfreerdp
from typing import NamedTuple
from ansible.module_utils.basic import AnsibleModule
from ops.ansible.modules_utils.custom_common import (
common_argument_spec
)
# =========================================
# Module execution.
@@ -55,6 +51,18 @@ class Param(NamedTuple):
password: str
def common_argument_spec():
options = dict(
login_host=dict(type='str', required=False, default='localhost'),
login_port=dict(type='int', required=False, default=22),
login_user=dict(type='str', required=False, default='root'),
login_password=dict(type='str', required=False, no_log=True),
login_secret_type=dict(type='str', required=False, default='password'),
login_private_key_path=dict(type='str', required=False, no_log=True),
)
return options
def main():
options = common_argument_spec()
module = AnsibleModule(argument_spec=options, supports_check_mode=True)

View File

View File

@@ -0,0 +1,80 @@
import logging
from celery.utils.log import get_logger
from django.db import close_old_connections
from django.core.exceptions import ObjectDoesNotExist
from django.db.utils import DatabaseError, InterfaceError
from django_celery_beat.schedulers import DatabaseScheduler as DJDatabaseScheduler
logger = get_logger(__name__)
debug, info, warning = logger.debug, logger.info, logger.warning
__all__ = ['DatabaseScheduler']
class DatabaseScheduler(DJDatabaseScheduler):
def sync(self):
if logger.isEnabledFor(logging.DEBUG):
debug('Writing entries...')
_tried = set()
_failed = set()
try:
close_old_connections()
while self._dirty:
name = self._dirty.pop()
try:
# 源码
# self.schedule[name].save()
# _tried.add(name)
"""
::Debug Description (2023.07.10)::
如果调用 self.schedule 可能会导致 self.save() 方法之前重新获取数据库中的数据, 而不是临时设置的 last_run_at 数据
如果这里调用 self.schedule
那么可能会导致调用 save 的 self.schedule[name] 的 last_run_at 是从数据库中获取回来的老数据
而不是任务执行后临时设置的 last_run_at (在 __next__() 方法中设置的)
当 `max_interval` 间隔之后, 下一个任务检测周期还是会再次执行任务
::Demo::
任务信息:
beat config: max_interval = 60s
任务名称: cap
任务执行周期: 每 3 分钟执行一次
任务最后执行时间: 18:00
任务第一次执行: 18:03 (执行时设置 last_run_at = 18:03, 此时在内存中)
任务执行完成后,
检测到需要 sync, sync 中调用了 self.schedule,
self.schedule 中发现 schedule_changed() 为 True, 需要调用 all_as_schedule()
此时sync 中调用的 self.schedule[name] 的 last_run_at 是 18:00
这时候在 self.sync() 进行 self.save()
beat: Waking up 60s ...
任务第二次执行: 18:04 (因为获取回来的 last_run_at 是 18:00, entry.is_due() == True)
::解决方法::
所以这里为了避免从数据库中获取,直接使用 _schedule #
"""
self._schedule[name].save()
_tried.add(name)
except (KeyError, TypeError, ObjectDoesNotExist):
_failed.add(name)
except DatabaseError as exc:
logger.exception('Database error while sync: %r', exc)
except InterfaceError:
warning(
'DatabaseScheduler: InterfaceError in sync(), '
'waiting to retry in next call...'
)
finally:
# retry later, only for the failed ones
self._dirty |= _failed

View File

@@ -41,8 +41,8 @@ def get_parent_keys(key, include_self=True):
class JMSPermedInventory(JMSInventory):
def __init__(self, assets, account_policy='privileged_first',
account_prefer='root,Administrator', host_callback=None, exclude_localhost=False, user=None):
super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost)
account_prefer='root,Administrator', host_callback=None, user=None):
super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost=True)
self.user = user
self.assets_accounts_mapper = self.get_assets_accounts_mapper()

View File

@@ -23,7 +23,6 @@ def sync_ldap_user():
@shared_task(verbose_name=_('Periodic import ldap user'))
@transaction.atomic
def import_ldap_user():
logger.info("Start import ldap user task")
util_server = LDAPServerUtil()

View File

@@ -157,35 +157,43 @@
script: |
pip install -r '{{ ansible_env.TEMP }}\pip_packages\pip_packages\requirements.txt' --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages\pip_packages'
- name: Download chromedriver (Chromium)
- name: Download chromedriver (Chrome)
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chromedriver_win32.zip"
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Unzip chromedriver (Chromium)
- name: Unzip chromedriver (Chrome)
community.windows.win_unzip:
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip"
dest: C:\Program Files\JumpServer\drivers
dest: C:\Program Files\JumpServer\drivers\chromedriver_win32
- name: Download chromium zip package (Chromium)
- name: Download Chrome zip package (Chrome)
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chrome-win.zip"
dest: "{{ ansible_env.TEMP }}\\chrome-win.zip"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Unzip Chromium (Chromium)
- name: Unzip Chrome (Chrome)
community.windows.win_unzip:
src: "{{ ansible_env.TEMP }}\\chrome-win.zip"
dest: C:\Program Files\Chrome
dest: C:\Program Files\JumpServer\applications
- name: Set chromium and driver on the global system path (Chromium)
- name: Check and Clean global system path (Chrome)
ansible.windows.win_path:
elements:
- 'C:\Program Files\Chrome\chrome-win32'
- 'C:\Program Files\Chrome\chrome-win'
- 'C:\Program Files\chrome-win'
state: absent
- name: Set Chrome and driver on the global system path (Chrome)
ansible.windows.win_path:
elements:
- 'C:\Program Files\JumpServer\applications\Chrome\Application'
- 'C:\Program Files\JumpServer\drivers\chromedriver_win32'
- name: Set Chromium variables disable Google Api (Chromium)
- name: Set Chrome variables disable Google Api (Chrome)
ansible.windows.win_environment:
level: machine
variables:
@@ -221,4 +229,4 @@
- name: Sync all remote applets
ansible.windows.win_powershell:
script: |
tinkerd install all
tinkerd install all

View File

@@ -10,12 +10,11 @@ from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from assets.const import Protocol
from assets.models import Asset
from common.utils import get_object_or_none, lazyproperty
from orgs.mixins.models import OrgModelMixin
from terminal.backends import get_multi_command_storage
from terminal.const import SessionType
from terminal.const import SessionType, TerminalType
from users.models import User
@@ -112,6 +111,7 @@ class Session(OrgModelMixin):
return rel_path
except:
pass
@property
def asset_obj(self):
return Asset.objects.get(id=self.asset_id)
@@ -132,10 +132,7 @@ class Session(OrgModelMixin):
if self.type != SessionType.normal:
# 会话监控仅支持 normal不支持 tunnel 和 command
return False
if self.protocol in [
Protocol.ssh, Protocol.vnc, Protocol.rdp,
Protocol.telnet, Protocol.k8s
]:
if self.terminal.type in [TerminalType.lion, TerminalType.koko]:
return True
else:
return False

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

@@ -1,7 +1,6 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from assets.const import Protocol
from common.serializers.fields import LabeledChoiceField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .terminal import TerminalSmallSerializer
@@ -14,11 +13,9 @@ __all__ = [
]
class SessionSerializer(BulkOrgResourceModelSerializer):
org_id = serializers.CharField(allow_blank=True)
protocol = serializers.ChoiceField(choices=Protocol.choices, label=_("Protocol"))
protocol = serializers.CharField(max_length=128, label=_("Protocol"))
type = LabeledChoiceField(
choices=SessionType.choices, label=_("Type"), default=SessionType.normal
)
@@ -33,7 +30,7 @@ class SessionSerializer(BulkOrgResourceModelSerializer):
"user", "asset", "user_id", "asset_id", 'account', 'account_id',
"protocol", 'type', "login_from", "remote_addr",
"is_success", "is_finished", "has_replay", "has_command",
"date_start", "date_end", "comment"
"date_start", "date_end", "comment", "terminal_display"
]
fields_fk = ["terminal", ]
fields_custom = ["can_replay", "can_join", "can_terminate"]

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

@@ -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']:

View File

@@ -1,6 +1,6 @@
aiofiles==22.1.0
amqp==5.0.9
git+https://github.com/jumpserver/ansible@master#egg=ansible-core
ansible-core@https://github.com/jumpserver/ansible/releases/download/v2.14.1.2/ansible-2.14.1.2.zip
ansible==7.1.0
ansible-runner==2.2.1
asn1crypto==0.24.0

View File

@@ -0,0 +1,37 @@
import os
import sys
import django
if os.path.exists('../apps'):
sys.path.insert(0, '../apps')
elif os.path.exists('./apps'):
sys.path.insert(0, './apps')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
django.setup()
from assets.models import Asset as asset_model, Host as host_model, Device as device_model
from orgs.models import Organization
def clean_host():
root = Organization.root()
root.change_to()
devices = host_model.objects.filter(platform__category='device')
assets = asset_model.objects.filter(id__in=devices.values_list('asset_ptr_id', flat=True))
assets_map = {asset.id: asset for asset in assets}
for host in devices:
asset = assets_map.get(host.asset_ptr_id)
if not asset:
continue
device = device_model(asset_ptr_id=asset.id)
device.__dict__.update(asset.__dict__)
device.save()
host.delete(keep_parents=True)
if __name__ == "__main__":
clean_host()

View File

@@ -54,7 +54,7 @@ else:
connection_params['port'] = settings.REDIS_PORT
redis_client = Redis(**connection_params)
scheduler = "django_celery_beat.schedulers:DatabaseScheduler"
scheduler = "ops.celery.beat.schedulers:DatabaseScheduler"
processes = []
cmd = [
'celery',