Merge branch 'dev' into pr@dev@revert_dockerfile

pull/11262/head
老广 2023-08-15 18:37:38 +08:00 committed by GitHub
commit 479378aa46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1533 additions and 1422 deletions

View File

@ -43,7 +43,7 @@ class BaseVault(ABC):
'name', 'username', 'secret_type',
'connectivity', 'su_from', 'privileged'
])
metadata = {field: str(value) for field, value in metadata.items()}
metadata = {k: str(v)[:500] for k, v in metadata.items() if v}
return self._save_metadata(instance, metadata)
# -------- abstractmethod -------- #

View File

@ -1,9 +1,12 @@
from common.db.utils import get_logger
from .entries import build_entry
from .service import VaultKVClient
from ..base import BaseVault
__all__ = ['Vault']
logger = get_logger(__name__)
class Vault(BaseVault):
def __init__(self, *args, **kwargs):
@ -43,5 +46,8 @@ class Vault(BaseVault):
instance.mark_secret_save_to_vault()
def _save_metadata(self, instance, metadata):
entry = build_entry(instance)
self.client.update_metadata(path=entry.full_path, metadata=metadata)
try:
entry = build_entry(instance)
self.client.update_metadata(path=entry.full_path, metadata=metadata)
except Exception as e:
logger.error(f'save metadata error: {e}')

View File

@ -59,7 +59,6 @@ class VaultKVClient(object):
mount_point=self.mount_point
)
except exceptions.InvalidPath as e:
logger.error('Get secret error: {}'.format(e))
return {}
data = response.get('data', {})
return data

View File

@ -1,8 +1,9 @@
# Generated by Django 4.1.10 on 2023-08-03 08:28
from django.conf import settings
from django.db import migrations, models
import common.db.encoder
def migrate_recipients(apps, schema_editor):
account_backup_model = apps.get_model('accounts', 'AccountBackupAutomation')
@ -13,13 +14,22 @@ def migrate_recipients(apps, schema_editor):
continue
account_backup.recipients_part_one.set(recipients)
execution_bojs = []
objs = []
for execution in execution_model.objects.all():
snapshot = execution.snapshot
recipients = snapshot.pop('recipients', {})
snapshot.update({'recipients_part_one': recipients, 'recipients_part_two': {}})
execution_bojs.append(execution)
execution_model.objects.bulk_update(execution_bojs, ['snapshot'])
objs.append(execution)
execution_model.objects.bulk_update(objs, ['snapshot'])
def migrate_snapshot(apps, schema_editor):
model = apps.get_model('accounts', 'AccountBackupExecution')
objs = []
for execution in model.objects.all():
execution.snapshot = execution.plan_snapshot
objs.append(execution)
model.objects.bulk_update(objs, ['snapshot'])
class Migration(migrations.Migration):
@ -45,12 +55,20 @@ class Migration(migrations.Migration):
to=settings.AUTH_USER_MODEL, verbose_name='Recipient part two'
),
),
migrations.RenameField(
migrations.AddField(
model_name='accountbackupexecution',
old_name='plan_snapshot',
new_name='snapshot',
name='snapshot',
field=models.JSONField(
default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder,
null=True, blank=True, verbose_name='Account backup snapshot'
),
),
migrations.RunPython(migrate_snapshot),
migrations.RunPython(migrate_recipients),
migrations.RemoveField(
model_name='accountbackupexecution',
name='plan_snapshot',
),
migrations.RemoveField(
model_name='accountbackupautomation',
name='recipients',

View File

@ -86,7 +86,7 @@ class ChangeSecretRecord(JMSBaseModel):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE, null=True)
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
status = models.CharField(max_length=16, default='pending')

View File

@ -52,17 +52,18 @@ class VaultModelMixin(models.Model):
abstract = True
# 缓存 secret 值, lazy-property 不能用
__secret = False
__secret = None
@property
def secret(self):
if self.__secret is False:
from accounts.backends import vault_client
secret = vault_client.get(self)
if not secret and not self.secret_has_save_to_vault:
# vault_client 获取不到, 并且 secret 没有保存到 vault, 就从 self._secret 获取
secret = self._secret
self.__secret = secret
if self.__secret:
return self.__secret
from accounts.backends import vault_client
secret = vault_client.get(self)
if not secret and not self.secret_has_save_to_vault:
# vault_client 获取不到, 并且 secret 没有保存到 vault, 就从 self._secret 获取
secret = self._secret
self.__secret = secret
return self.__secret
@secret.setter
@ -72,6 +73,7 @@ class VaultModelMixin(models.Model):
先保存到 db, 再保存到 vault 同时删除本地 db _secret
"""
self._secret = value
self.__secret = value
_secret_save_to_vault_mark = '# Secret-has-been-saved-to-vault #'

View File

@ -95,6 +95,8 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
field.name for field in template._meta.fields
if field.name not in ignore_fields
]
field_names = [name if name != '_secret' else 'secret' for name in field_names]
attrs = {}
for name in field_names:
value = getattr(template, name, None)

View File

@ -1,4 +1,5 @@
import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from celery import shared_task
from django.utils.translation import gettext_lazy as _
@ -12,6 +13,22 @@ from ..const import VaultTypeChoices
logger = get_logger(__name__)
def sync_instance(instance):
instance_desc = f'[{instance._meta.verbose_name}-{instance.id}-{instance}]'
if instance.secret_has_save_to_vault:
msg = f'\033[32m- 跳过同步: {instance_desc}, 原因: [已同步]'
return "skipped", msg
try:
vault_client.create(instance)
except Exception as e:
msg = f'\033[31m- 同步失败: {instance_desc}, 原因: [{e}]'
return "failed", msg
else:
msg = f'\033[32m- 同步成功: {instance_desc}'
return "succeeded", msg
@shared_task(verbose_name=_('Sync secret to vault'))
def sync_secret_to_vault():
if vault_client.is_type(VaultTypeChoices.local):
@ -19,38 +36,34 @@ def sync_secret_to_vault():
print('\033[35m>>> 当前 Vault 类型为本地数据库, 不需要同步')
return
print('\033[33m>>> 开始同步密钥数据到 Vault ({})'.format(datetime.datetime.now()))
failed, skipped, succeeded = 0, 0, 0
to_sync_models = [Account, AccountTemplate, Account.history.model]
print(f'\033[33m>>> 开始同步密钥数据到 Vault ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})')
with tmp_to_root_org():
to_sync_models = [Account, AccountTemplate, Account.history.model]
instances = []
for model in to_sync_models:
print(f'\033[33m>>> 开始同步: {model.__module__}')
succeeded = []
failed = []
skipped = []
instances = model.objects.all()
for instance in instances:
instance_desc = f'[{instance}]'
if instance.secret_has_save_to_vault:
print(f'\033[32m- 跳过同步: {instance_desc}, 原因: [已同步]')
skipped.append(instance)
continue
try:
vault_client.create(instance)
except Exception as e:
failed.append(instance)
print(f'\033[31m- 同步失败: {instance_desc}, 原因: [{e}]')
else:
succeeded.append(instance)
print(f'\033[32m- 同步成功: {instance_desc}')
instances += list(model.objects.all())
total = len(succeeded) + len(failed) + len(skipped)
print(
f'\033[33m>>> 同步完成: {model.__module__}, '
f'共计: {total}, '
f'成功: {len(succeeded)}, '
f'失败: {len(failed)}, '
f'跳过: {len(skipped)}'
)
with ThreadPoolExecutor(max_workers=10) as executor:
tasks = [executor.submit(sync_instance, instance) for instance in instances]
print('\033[33m>>> 全部同步完成 ({})'.format(datetime.datetime.now()))
for future in as_completed(tasks):
status, msg = future.result()
print(msg)
if status == "succeeded":
succeeded += 1
elif status == "failed":
failed += 1
elif status == "skipped":
skipped += 1
total = succeeded + failed + skipped
print(
f'\033[33m>>> 同步完成: {model.__module__}, '
f'共计: {total}, '
f'成功: {succeeded}, '
f'失败: {failed}, '
f'跳过: {skipped}'
)
print(f'\033[33m>>> 全部同步完成 ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})')
print('\033[0m')

View File

@ -224,7 +224,7 @@ class AllTypes(ChoicesMixin):
return dict(id='ROOT', name=_('All types'), title=_('All types'), open=True, isParent=True)
@classmethod
def get_tree_nodes(cls, resource_platforms, include_asset=False):
def get_tree_nodes(cls, resource_platforms, include_asset=False, get_root=True):
from ..models import Platform
platform_count = defaultdict(int)
for platform_id in resource_platforms:
@ -239,10 +239,10 @@ class AllTypes(ChoicesMixin):
category_type_mapper[p.category] += platform_count[p.id]
tp_platforms[p.category + '_' + p.type].append(p)
nodes = [cls.get_root_nodes()]
nodes = [cls.get_root_nodes()] if get_root else []
for category, type_cls in cls.category_types():
# Category 格式化
meta = {'type': 'category', 'category': category.value}
meta = {'type': 'category', 'category': category.value, '_type': category.value}
category_node = cls.choice_to_node(category, 'ROOT', meta=meta)
category_count = category_type_mapper.get(category, 0)
category_node['name'] += f'({category_count})'

View File

@ -52,7 +52,11 @@ class UserResetPasswordSendCodeApi(CreateAPIView):
other_args = {}
target = serializer.validated_data[form_type]
query_key = 'phone' if form_type == 'sms' else form_type
if form_type == 'sms':
query_key = 'phone'
target = target.lstrip('+')
else:
query_key = form_type
user, err = self.is_valid_user(username=username, **{query_key: target})
if not user:
return Response({'error': err}, status=400)

View File

@ -1,15 +1,14 @@
import time
import requests
import requests.exceptions
from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings
from django.contrib import auth
from django.core.exceptions import MiddlewareNotUsed
from common.utils import get_logger
from .utils import validate_and_return_id_token
from .decorator import ssl_verification
from .utils import validate_and_return_id_token
logger = get_logger(__file__)
@ -25,11 +24,16 @@ class OIDCRefreshIDTokenMiddleware:
def __call__(self, request):
# Refreshes tokens only in the applicable cases.
if request.method == 'GET' and not request.is_ajax() and request.user.is_authenticated and settings.AUTH_OPENID:
if request.method == 'GET' and not self.is_ajax(request) and \
request.user.is_authenticated and settings.AUTH_OPENID:
self.refresh_token(request)
response = self.get_response(request)
return response
@staticmethod
def is_ajax(request):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
@ssl_verification
def refresh_token(self, request):
""" Refreshes the token of the current user. """

View File

@ -191,7 +191,7 @@ class ConnectionToken(JMSOrgBaseModel):
raise JMSException({'error': 'No host account available'})
host, account, lock_key, ttl = bulk_get(host_account, ('host', 'account', 'lock_key', 'ttl'))
gateway = host.gateway.select_gateway() if host.domain else None
gateway = host.domain.select_gateway() if host.domain else None
data = {
'id': account.id,

View File

@ -116,9 +116,13 @@ class FeiShu(RequestMixin):
'receive_id_type': 'user_id'
}
"""
https://open.feishu.cn/document/common-capabilities/message-card/message-cards-content
/using-markdown-tags
"""
body = {
'msg_type': 'text',
'content': json.dumps({'text': msg})
'msg_type': 'interactive',
'content': json.dumps({'elements': [{'tag': 'markdown', 'content': msg}]})
}
invalid_users = []

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7bc2e996c082d5f9348277e69cd70b7b9884dc416d9d83e075656a4d8b9bc141
size 152939
oid sha256:810d46e14e09a2309a8d898bc391f33082bfc5c164dec246bd95cea8436e33ee
size 154540

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d1a6a042b4813d67922799caf3ac81ce3f1e831aed1a771dc9a16dab147a0692
size 125568
oid sha256:da4f312ed86d27fa8b6bde8da3bc70b0f32fe40811ee855f9fe81d89a68a646f
size 126402

File diff suppressed because it is too large Load Diff

View File

@ -177,8 +177,10 @@ class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi(
return []
pid = f'ROOT_{str(assets[0].category).upper()}_{tp}'
return self.serialize_assets(assets, pid=pid)
params = self.request.query_params
get_root = not list(filter(lambda x: params.get(x), ('type', 'n')))
resource_platforms = assets.order_by('id').values_list('platform_id', flat=True)
node_all = AllTypes.get_tree_nodes(resource_platforms)
node_all = AllTypes.get_tree_nodes(resource_platforms, get_root=get_root)
pattern = re.compile(r'\(0\)?')
nodes = []
for node in node_all:

View File

@ -144,7 +144,9 @@ only_system_permissions = (
('terminal', 'task', '*', '*'),
('terminal', 'endpoint', '*', '*'),
('terminal', 'endpointrule', '*', '*'),
('authentication', '*', '*', '*'),
('authentication', 'accesskey', '*', '*'),
('authentication', 'superconnectiontoken', '*', '*'),
('authentication', 'temptoken', '*', '*'),
('tickets', '*', '*', '*'),
('orgs', 'organization', 'view', 'rootorg'),
('terminal', 'applet', '*', '*'),

View File

@ -70,6 +70,9 @@ special_pid_mapper = {
'xpack.syncinstancedetail': 'cloud_import',
'xpack.syncinstancetask': 'cloud_import',
'xpack.syncinstancetaskexecution': 'cloud_import',
'xpack.strategy': 'cloud_import',
'xpack.strategyaction': 'cloud_import',
'xpack.strategyrule': 'cloud_import',
'terminal.applet': 'remote_application',
'terminal.applethost': 'remote_application',
'accounts.accountbackupautomation': "backup_account_node",

View File

@ -28,6 +28,11 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'basic': serializers.BasicSettingSerializer,
'terminal': serializers.TerminalSettingSerializer,
'security': serializers.SecuritySettingSerializer,
'security_auth': serializers.SecurityAuthSerializer,
'security_basic': serializers.SecurityBasicSerializer,
'security_session': serializers.SecuritySessionSerializer,
'security_password': serializers.SecurityPasswordRuleSerializer,
'security_login_limit': serializers.SecurityLoginLimitSerializer,
'ldap': serializers.LDAPSettingSerializer,
'email': serializers.EmailSettingSerializer,
'email_content': serializers.EmailContentSettingSerializer,
@ -39,7 +44,6 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'keycloak': serializers.KeycloakSettingSerializer,
'radius': serializers.RadiusSettingSerializer,
'cas': serializers.CASSettingSerializer,
'sso': serializers.SSOSettingSerializer,
'saml2': serializers.SAML2SettingSerializer,
'oauth2': serializers.OAuth2SettingSerializer,
'clean': serializers.CleaningSerializer,
@ -51,6 +55,9 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'cmpp2': serializers.CMPP2SMSSettingSerializer,
'custom': serializers.CustomSMSSettingSerializer,
'vault': serializers.VaultSettingSerializer,
'announcement': serializers.AnnouncementSettingSerializer,
'ticket': serializers.TicketSettingSerializer,
'ops': serializers.OpsSettingSerializer,
}
rbac_category_permissions = {

View File

@ -1,18 +1,16 @@
import importlib
from collections import OrderedDict
from django.utils.translation import gettext_lazy as _
from rest_framework import status
from rest_framework.exceptions import APIException
from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from rest_framework import status
from django.utils.translation import gettext_lazy as _
from common.sdk.sms import BACKENDS
from common.exceptions import JMSException
from settings.serializers.sms import SMSBackendSerializer
from common.sdk.sms import BACKENDS
from settings.models import Setting
from settings.serializers import SMSBackendSerializer
from .. import serializers

View File

@ -4,11 +4,11 @@
from .auth import *
from .basic import *
from .cleaning import *
from .email import *
from .feature import *
from .msg import *
from .msg import *
from .other import *
from .public import *
from .security import *
from .settings import *
from .terminal import *
from .vault import *

View File

@ -9,14 +9,16 @@ __all__ = [
class AuthSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Authentication')
AUTH_LDAP = serializers.BooleanField(required=False, label=_('LDAP Auth'))
AUTH_CAS = serializers.BooleanField(required=False, label=_('CAS Auth'))
AUTH_OPENID = serializers.BooleanField(required=False, label=_('OPENID Auth'))
AUTH_SAML2 = serializers.BooleanField(default=False, label=_("SAML2 Auth"))
AUTH_OAUTH2 = serializers.BooleanField(default=False, label=_("OAuth2 Auth"))
AUTH_RADIUS = serializers.BooleanField(required=False, label=_('RADIUS Auth'))
AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('DingTalk Auth'))
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('FeiShu Auth'))
AUTH_WECOM = serializers.BooleanField(default=False, label=_('WeCom Auth'))
AUTH_SSO = serializers.BooleanField(default=False, label=_("SSO Auth"))
AUTH_SAML2 = serializers.BooleanField(default=False, label=_("SAML2 Auth"))
FORGOT_PASSWORD_URL = serializers.CharField(
required=False, allow_blank=True, max_length=1024,
label=_("Forgot password url")

View File

@ -7,6 +7,9 @@ __all__ = [
class SSOSettingSerializer(serializers.Serializer):
"""
不对外开放了只能通过配置文件修改比较这个稍微有点危险
"""
PREFIX_TITLE = _('SSO')
AUTH_SSO = serializers.BooleanField(

View File

@ -1,51 +1,30 @@
import uuid
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
class AnnouncementSerializer(serializers.Serializer):
ID = serializers.CharField(required=False, allow_blank=True, allow_null=True)
SUBJECT = serializers.CharField(required=True, max_length=1024, label=_("Subject"))
CONTENT = serializers.CharField(label=_("Content"))
LINK = serializers.URLField(
required=False, allow_null=True, allow_blank=True,
label=_("More url"), default='',
)
def to_representation(self, instance):
defaults = {'ID': '', 'SUBJECT': '', 'CONTENT': '', 'LINK': '', 'ENABLED': False}
data = {**defaults, **instance}
return super().to_representation(data)
def to_internal_value(self, data):
data['ID'] = str(uuid.uuid4())
return super().to_internal_value(data)
class BasicSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Basic')
SITE_URL = serializers.URLField(
required=True, label=_("Site url"),
help_text=_('eg: http://dev.jumpserver.org:8080')
help_text=_('Email links or other system callbacks are used to access it, eg: http://dev.jumpserver.org:8080')
)
USER_GUIDE_URL = serializers.URLField(
required=False, allow_blank=True, allow_null=True, label=_("User guide url"),
help_text=_('User first login update profile done redirect to it')
)
FORGOT_PASSWORD_URL = serializers.URLField(
required=False, allow_blank=True, allow_null=True, label=_("Forgot password url"),
help_text=_('The forgot password url on login page, If you use '
'ldap or cas external authentication, you can set it')
)
GLOBAL_ORG_DISPLAY_NAME = serializers.CharField(
required=False, max_length=1024, allow_blank=True, allow_null=True, label=_("Global organization name"),
help_text=_('The name of global organization to display')
)
ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True)
ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement"))
TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets"))
HELP_DOCUMENT_URL = serializers.URLField(
required=False, allow_blank=True, allow_null=True, label=_("Help Docs URL"),
help_text=_('default: http://docs.jumpserver.org')
)
HELP_SUPPORT_URL = serializers.URLField(
required=False, allow_blank=True, allow_null=True, label=_("Help Support URL"),
help_text=_('default: http://www.jumpserver.org/support/')
)
@staticmethod
def validate_SITE_URL(s):

View File

@ -0,0 +1,88 @@
import uuid
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import VaultTypeChoices
from common.serializers.fields import EncryptedField
__all__ = [
'AnnouncementSettingSerializer', 'OpsSettingSerializer',
'VaultSettingSerializer', 'TicketSettingSerializer'
]
class AnnouncementSerializer(serializers.Serializer):
ID = serializers.CharField(required=False, allow_blank=True, allow_null=True)
SUBJECT = serializers.CharField(required=True, max_length=1024, label=_("Subject"))
CONTENT = serializers.CharField(label=_("Content"))
LINK = serializers.URLField(
required=False, allow_null=True, allow_blank=True,
label=_("More url"), default='',
)
def to_representation(self, instance):
defaults = {'ID': '', 'SUBJECT': '', 'CONTENT': '', 'LINK': '', 'ENABLED': False}
data = {**defaults, **instance}
return super().to_representation(data)
def to_internal_value(self, data):
data['ID'] = str(uuid.uuid4())
return super().to_internal_value(data)
class AnnouncementSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Announcement')
ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True)
ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement"))
class VaultSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Vault')
VAULT_TYPE = serializers.ChoiceField(
default=VaultTypeChoices.local, choices=VaultTypeChoices.choices,
required=False, label=_('Type')
)
VAULT_HCP_HOST = serializers.CharField(
max_length=256, allow_blank=True, required=False, label=_('Host')
)
VAULT_HCP_TOKEN = EncryptedField(
max_length=256, allow_blank=True, required=False, label=_('Token'), default=''
)
VAULT_HCP_MOUNT_POINT = serializers.CharField(
max_length=256, allow_blank=True, required=False, label=_('Mount Point')
)
def validate(self, attrs):
attrs.pop('VAULT_TYPE', None)
return attrs
class TicketSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Ticket')
TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets"))
TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField(
min_value=1, max_value=999999, required=False,
label=_("Ticket authorize default time")
)
TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.ChoiceField(
choices=[('day', _("day")), ('hour', _("hour"))],
label=_("Ticket authorize default time unit"), required=False,
)
class OpsSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Feature')
SECURITY_COMMAND_EXECUTION = serializers.BooleanField(
required=False, label=_('Operation center'),
help_text=_('Allow user run batch command or not using ansible')
)
SECURITY_COMMAND_BLACKLIST = serializers.ListField(
child=serializers.CharField(max_length=1024, ),
label=_('Operation center command blacklist'),
help_text=_("Commands that are not allowed execute.")
)

View File

@ -6,7 +6,10 @@ from rest_framework import serializers
from common.serializers.fields import EncryptedField
__all__ = ['MailTestSerializer', 'EmailSettingSerializer', 'EmailContentSettingSerializer']
__all__ = [
'MailTestSerializer', 'EmailSettingSerializer',
'EmailContentSettingSerializer', 'SMSBackendSerializer',
]
class MailTestSerializer(serializers.Serializer):
@ -44,6 +47,10 @@ class EmailSettingSerializer(serializers.Serializer):
EMAIL_SUBJECT_PREFIX = serializers.CharField(
max_length=1024, required=True, label=_('Subject prefix')
)
EMAIL_SUFFIX = serializers.CharField(
required=False, max_length=1024, label=_("Email suffix"),
help_text=_('This is used by default if no email is returned during SSO authentication')
)
class EmailContentSettingSerializer(serializers.Serializer):
@ -69,3 +76,8 @@ class EmailContentSettingSerializer(serializers.Serializer):
max_length=512, allow_blank=True, required=False, label=_('Signature'),
help_text=_('Tips: Email signature (eg:jumpserver)')
)
class SMSBackendSerializer(serializers.Serializer):
name = serializers.CharField(max_length=256, required=True, label=_('Name'))
label = serializers.CharField(max_length=256, required=True, label=_('Label'))

View File

@ -1,46 +1,17 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
__all__ = ['OtherSettingSerializer']
class OtherSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('More...')
EMAIL_SUFFIX = serializers.CharField(
required=False, max_length=1024, label=_("Email suffix"),
help_text=_('This is used by default if no email is returned during SSO authentication')
)
OTP_ISSUER_NAME = serializers.CharField(
required=False, max_length=16, label=_('OTP issuer name'),
)
OTP_VALID_WINDOW = serializers.IntegerField(
min_value=1, max_value=10,
label=_("OTP valid window")
)
PERM_SINGLE_ASSET_TO_UNGROUP_NODE = serializers.BooleanField(
required=False, label=_("Perm ungroup node"),
help_text=_("Perm single to ungroup node")
)
TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField(
min_value=1, max_value=999999, required=False,
label=_("Ticket authorize default time")
)
TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.ChoiceField(
choices=[('day', _("day")), ('hour', _("hour"))],
label=_("Ticket authorize default time unit"), required=False,
)
HELP_DOCUMENT_URL = serializers.URLField(
required=False, allow_blank=True, allow_null=True, label=_("Help Docs URL"),
help_text=_('default: http://docs.jumpserver.org')
)
HELP_SUPPORT_URL = serializers.URLField(
required=False, allow_blank=True, allow_null=True, label=_("Help Support URL"),
help_text=_('default: http://www.jumpserver.org/support/')
)
# 准备废弃
# PERIOD_TASK_ENABLED = serializers.BooleanField(
# required=False, label=_("Enable period task")

View File

@ -3,8 +3,31 @@ from rest_framework import serializers
from acls.serializers.rules import ip_group_help_text, ip_group_child_validator
__all__ = [
'SecurityPasswordRuleSerializer', 'SecuritySessionSerializer',
'SecurityAuthSerializer', 'SecuritySettingSerializer',
'SecurityLoginLimitSerializer', 'SecurityBasicSerializer',
]
class SecurityPasswordRuleSerializer(serializers.Serializer):
SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(
min_value=1, max_value=99999, required=True,
label=_('User password expiration (day)'),
help_text=_(
'If the user does not update the password during the time, '
'the user password will expire failure;The password expiration reminder mail will be '
'automatic sent to the user by system within 5 days (daily) before the password expires'
)
)
OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField(
min_value=0, max_value=99999, required=True,
label=_('Number of repeated historical passwords'),
help_text=_(
'Tip: When the user resets the password, it cannot be '
'the previous n historical passwords of the user'
)
)
SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField(
min_value=6, max_value=30, required=True,
label=_('Password minimum length')
@ -33,20 +56,7 @@ login_ip_limit_time_help_text = _(
)
class SecurityAuthSerializer(serializers.Serializer):
SECURITY_MFA_AUTH = serializers.ChoiceField(
choices=(
[0, _('Not enabled')],
[1, _('All users')],
[2, _('Only admin users')],
),
required=False, label=_("Global MFA auth")
)
SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = serializers.BooleanField(
required=False, default=True,
label=_('Third-party login users perform MFA authentication'),
help_text=_('The third-party login modes include OIDC, CAS, and SAML2'),
)
class SecurityLoginLimitSerializer(serializers.Serializer):
SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(
min_value=3, max_value=99999,
label=_('Limit the number of user login failures')
@ -56,6 +66,7 @@ class SecurityAuthSerializer(serializers.Serializer):
label=_('Block user login interval (minute)'),
help_text=login_ip_limit_time_help_text
)
SECURITY_LOGIN_IP_LIMIT_COUNT = serializers.IntegerField(
min_value=3, max_value=99999,
label=_('Limit the number of IP login failures')
@ -75,23 +86,6 @@ class SecurityAuthSerializer(serializers.Serializer):
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]),
help_text=ip_group_help_text
)
SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(
min_value=1, max_value=99999, required=True,
label=_('User password expiration (day)'),
help_text=_(
'If the user does not update the password during the time, '
'the user password will expire failure;The password expiration reminder mail will be '
'automatic sent to the user by system within 5 days (daily) before the password expires'
)
)
OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField(
min_value=0, max_value=99999, required=True,
label=_('Number of repeated historical passwords'),
help_text=_(
'Tip: When the user resets the password, it cannot be '
'the previous n historical passwords of the user'
)
)
USER_LOGIN_SINGLE_MACHINE_ENABLED = serializers.BooleanField(
required=False, default=False, label=_("Only single device login"),
help_text=_("After the user logs in on the new device, other logged-in devices will automatically log out")
@ -99,24 +93,59 @@ class SecurityAuthSerializer(serializers.Serializer):
ONLY_ALLOW_EXIST_USER_AUTH = serializers.BooleanField(
required=False, default=False, label=_("Only exist user login"),
help_text=_(
"If enabled, non-existent users will not be allowed to log in; if disabled, users of other authentication methods except local authentication methods are allowed to log in and automatically create users (if the user does not exist)")
"If enabled, non-existent users will not be allowed to log in; if disabled, "
"users of other authentication methods except local authentication methods are allowed "
"to log in and automatically create users (if the user does not exist)"
)
)
ONLY_ALLOW_AUTH_FROM_SOURCE = serializers.BooleanField(
required=False, default=False, label=_("Only from source login"),
help_text=_(
"If it is enabled, the user will only authenticate to the source when logging in; if it is disabled, the user will authenticate all the enabled authentication methods in a certain order when logging in, and as long as one of the authentication methods is successful, they can log in directly")
"If it is enabled, the user will only authenticate to the source when logging in; "
"if it is disabled, the user will authenticate all the enabled authentication methods "
"in a certain order when logging in, and as long as one of the authentication methods is successful, "
"they can log in directly"
)
)
class SecurityAuthSerializer(serializers.Serializer):
SECURITY_MFA_AUTH = serializers.ChoiceField(
choices=(
[0, _('Not enabled')],
[1, _('All users')],
[2, _('Only admin users')],
),
required=False, label=_("Global MFA auth")
)
SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = serializers.BooleanField(
required=False, default=True,
label=_('Third-party login users perform MFA authentication'),
help_text=_('The third-party login modes include OIDC, CAS, and SAML2'),
)
OTP_ISSUER_NAME = serializers.CharField(
required=False, max_length=16, label=_('OTP issuer name'),
)
OTP_VALID_WINDOW = serializers.IntegerField(
min_value=1, max_value=10,
label=_("OTP valid window")
)
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField(
min_value=5, max_value=60 * 60 * 10,
label=_("MFA verify TTL (secend)"),
label=_("MFA verify TTL"),
help_text=_(
"The verification MFA takes effect only when you view the account password"
"Unit: second, The verification MFA takes effect only when you view the account password"
)
)
SECURITY_MFA_IN_LOGIN_PAGE = serializers.BooleanField(
required=False, default=False,
label=_("MFA in login page"),
help_text=_("Eu security regulations(GDPR) require MFA to be on the login page")
)
VERIFY_CODE_TTL = serializers.IntegerField(
min_value=5, max_value=60 * 60 * 10,
label=_("Verify code TTL"),
help_text=_("Unit: second, reset password and send SMS code expiration time")
label=_("Verify code TTL (second)"),
help_text=_("Reset password and send SMS code expiration time")
)
SECURITY_LOGIN_CHALLENGE_ENABLED = serializers.BooleanField(
required=False, default=False,
@ -124,15 +153,22 @@ class SecurityAuthSerializer(serializers.Serializer):
help_text=_("The password and additional code are sent to a third party "
"authentication system for verification")
)
SECURITY_MFA_IN_LOGIN_PAGE = serializers.BooleanField(
required=False, default=False,
label=_("MFA in login page"),
help_text=_("Eu security regulations(GDPR) require MFA to be on the login page")
)
SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField(
required=False, default=False, label=_("Enable Login captcha"),
help_text=_("Enable captcha to prevent robot authentication")
)
SECURITY_CHECK_DIFFERENT_CITY_LOGIN = serializers.BooleanField(
required=False, label=_('Remote Login Protection'),
help_text=_(
'The system determines whether the login IP address belongs to a common login city. '
'If the account is logged in from a common login city, the system sends a remote login reminder'
)
)
SECURITY_UNCOMMON_USERS_TTL = serializers.IntegerField(
min_value=30, max_value=99999, required=False,
label=_('Unused user timeout (day)'),
help_text=_("Detect infrequent users daily and disable them if they exceed the predetermined time limit.")
)
def validate(self, attrs):
if attrs.get('SECURITY_MFA_AUTH') != 1:
@ -149,15 +185,7 @@ class SecurityAuthSerializer(serializers.Serializer):
return data
class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSerializer):
PREFIX_TITLE = _('Security')
SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(
required=True, label=_('Enable terminal register'),
help_text=_(
"Allow terminal register, after all terminal setup, you should disable this for security"
)
)
class SecuritySessionSerializer(serializers.Serializer):
SECURITY_WATERMARK_ENABLED = serializers.BooleanField(
required=True, label=_('Enable watermark'),
help_text=_('Enabled, the web session and replay contains watermark information')
@ -175,6 +203,13 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri
SECURITY_LUNA_REMEMBER_AUTH = serializers.BooleanField(
label=_("Remember manual auth")
)
SECURITY_SESSION_SHARE = serializers.BooleanField(
required=True, label=_('Session share'),
help_text=_("Enabled, Allows user active session to be shared with other users")
)
class SecurityBasicSerializer(serializers.Serializer):
SECURITY_INSECURE_COMMAND = serializers.BooleanField(
required=False, label=_('Insecure command alert')
)
@ -182,28 +217,11 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri
max_length=8192, required=False, allow_blank=True, label=_('Email recipient'),
help_text=_('Multiple user using , split')
)
SECURITY_COMMAND_EXECUTION = serializers.BooleanField(
required=False, label=_('Operation center'),
help_text=_('Allow user run batch command or not using ansible')
)
SECURITY_COMMAND_BLACKLIST = serializers.ListField(
child=serializers.CharField(max_length=1024, ),
label=_('Operation center command blacklist'),
help_text=_("Commands that are not allowed execute.")
)
SECURITY_SESSION_SHARE = serializers.BooleanField(
required=True, label=_('Session share'),
help_text=_("Enabled, Allows user active session to be shared with other users")
)
SECURITY_UNCOMMON_USERS_TTL = serializers.IntegerField(
min_value=30, max_value=99999, required=False,
label=_('Unused user timeout (day)'),
help_text=_("Detect infrequent users daily and disable them if they exceed the predetermined time limit.")
)
SECURITY_CHECK_DIFFERENT_CITY_LOGIN = serializers.BooleanField(
required=False, label=_('Remote Login Protection'),
help_text=_(
'The system determines whether the login IP address belongs to a common login city. '
'If the account is logged in from a common login city, the system sends a remote login reminder'
)
)
class SecuritySettingSerializer(
SecurityPasswordRuleSerializer, SecurityAuthSerializer,
SecuritySessionSerializer, SecurityBasicSerializer,
SecurityLoginLimitSerializer,
):
PREFIX_TITLE = _('Security')

View File

@ -9,12 +9,12 @@ from .auth import (
CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer,
WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer,
TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, AuthSettingSerializer,
SAML2SettingSerializer, OAuth2SettingSerializer, SSOSettingSerializer,
SAML2SettingSerializer, OAuth2SettingSerializer,
CustomSMSSettingSerializer,
)
from .basic import BasicSettingSerializer
from .cleaning import CleaningSerializer
from .email import EmailSettingSerializer, EmailContentSettingSerializer
from .msg import EmailSettingSerializer, EmailContentSettingSerializer
from .other import OtherSettingSerializer
from .security import SecuritySettingSerializer
from .terminal import TerminalSettingSerializer
@ -42,7 +42,6 @@ class SettingsSerializer(
KeycloakSettingSerializer,
CASSettingSerializer,
RadiusSettingSerializer,
SSOSettingSerializer,
CleaningSerializer,
AlibabaSMSSettingSerializer,
TencentSMSSettingSerializer,

View File

@ -1,7 +0,0 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
class SMSBackendSerializer(serializers.Serializer):
name = serializers.CharField(max_length=256, required=True, label=_('Name'))
label = serializers.CharField(max_length=256, required=True, label=_('Label'))

View File

@ -18,6 +18,12 @@ class TerminalSettingSerializer(serializers.Serializer):
('25', '25'),
('50', '50'),
)
SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(
required=True, label=_('Enable terminal register'),
help_text=_(
"Allow terminal register, after all terminal setup, you should disable this for security"
)
)
TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False, label=_('Password auth'))
TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField(
required=False, label=_('Public key auth'),

View File

@ -1,27 +0,0 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import VaultTypeChoices
from common.serializers.fields import EncryptedField
__all__ = ['VaultSettingSerializer']
class VaultSettingSerializer(serializers.Serializer):
VAULT_TYPE = serializers.ChoiceField(
default=VaultTypeChoices.local, choices=VaultTypeChoices.choices,
required=False, label=_('Type')
)
VAULT_HCP_HOST = serializers.CharField(
max_length=256, allow_blank=True, required=False, label=_('Host')
)
VAULT_HCP_TOKEN = EncryptedField(
max_length=256, allow_blank=True, required=False, label=_('Token'), default=''
)
VAULT_HCP_MOUNT_POINT = serializers.CharField(
max_length=256, allow_blank=True, required=False, label=_('Mount Point')
)
def validate(self, attrs):
attrs.pop('VAULT_TYPE', None)
return attrs

View File

@ -147,7 +147,7 @@ class AppletHostDeployment(JMSBaseModel):
def start(self, **kwargs):
# 重新初始化部署applet host 关联的终端需要删除
# 否则 tinker 会因终端注册名称相同,造成冲突,执行任务失败
# 否则 tinker 会因组件注册名称相同,造成冲突,执行任务失败
if self.host.terminal:
terminal = self.host.terminal
self.host.terminal = None

View File

@ -96,3 +96,6 @@ REDIS_PORT: 6379
# 仅允许已存在的用户登录,不允许第三方认证后,自动创建用户
# ONLY_ALLOW_EXIST_USER_AUTH: False
# 当前存储的类型,默认 local新增类型 hcp 为远端 vault 存储
# VAULT_TYPE: local

46
poetry.lock generated
View File

@ -2259,44 +2259,24 @@ url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "tsinghua"
[[package]]
name = "elastic-transport"
version = "8.4.0"
description = "Transport classes and utilities shared among Python Elastic client libraries"
name = "elasticsearch"
version = "7.8.0"
description = "Python client for Elasticsearch"
optional = false
python-versions = ">=3.6"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
files = [
{file = "elastic-transport-8.4.0.tar.gz", hash = "sha256:b9ad708ceb7fcdbc6b30a96f886609a109f042c0b9d9f2e44403b3133ba7ff10"},
{file = "elastic_transport-8.4.0-py3-none-any.whl", hash = "sha256:19db271ab79c9f70f8c43f8f5b5111408781a6176b54ab2e54d713b6d9ceb815"},
{file = "elasticsearch-7.8.0-py2.py3-none-any.whl", hash = "sha256:6fb566dd23b91b5871ce12212888674b4cf33374e92b71b1080916c931e44dcb"},
{file = "elasticsearch-7.8.0.tar.gz", hash = "sha256:e637d8cf4e27e279b5ff8ca8edc0c086f4b5df4bf2b48e2f950b7833aca3a792"},
]
[package.dependencies]
certifi = "*"
urllib3 = ">=1.26.2,<2"
urllib3 = ">=1.21.1"
[package.extras]
develop = ["aiohttp", "mock", "pytest", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests", "trustme"]
[package.source]
type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "tsinghua"
[[package]]
name = "elasticsearch"
version = "8.8.2"
description = "Python client for Elasticsearch"
optional = false
python-versions = ">=3.6, <4"
files = [
{file = "elasticsearch-8.8.2-py3-none-any.whl", hash = "sha256:bffd6ce4faaacf90e6f617241773b3da8fb94e2e83554f5508e2fab92ca79643"},
{file = "elasticsearch-8.8.2.tar.gz", hash = "sha256:bed8cf8fcc6c3be7c254b579de4c29afab021f373c832246f912d37aef3c6bd5"},
]
[package.dependencies]
elastic-transport = ">=8,<9"
[package.extras]
async = ["aiohttp (>=3,<4)"]
async = ["aiohttp (>=3,<4)", "yarl"]
develop = ["black", "coverage", "jinja2", "mock", "pytest", "pytest-cov", "pyyaml", "requests (>=2.0.0,<3.0.0)", "sphinx (<1.7)", "sphinx-rtd-theme"]
docs = ["sphinx (<1.7)", "sphinx-rtd-theme"]
requests = ["requests (>=2.4.0,<3.0.0)"]
[package.source]
@ -3343,12 +3323,12 @@ reference = "tsinghua"
[[package]]
name = "jms-storage"
version = "0.0.50"
version = "0.0.51"
description = "Jumpserver storage python sdk tools"
optional = false
python-versions = "*"
files = [
{file = "jms-storage-0.0.50.tar.gz", hash = "sha256:f757180e145a9eb3390b8b32663a5feebcb1e720a6f2c36cc16f726ac047f28b"},
{file = "jms-storage-0.0.51.tar.gz", hash = "sha256:47a50ac4d952a21693b0e2f926f42fa0d02bc1fa8e507a8284059743b2b81911"},
]
[package.dependencies]
@ -3360,7 +3340,7 @@ certifi = "2023.7.22"
chardet = "5.1.0"
crcmod = "1.7"
docutils = "0.20.1"
elasticsearch = "8.8.2"
elasticsearch = "7.8.0"
esdk-obs-python = "3.21.4"
idna = "3.4"
oss2 = "2.18.1"

View File

@ -47,7 +47,7 @@ pynacl = "1.5.0"
python-dateutil = "2.8.2"
pyyaml = "6.0.1"
requests = "2.31.0"
jms-storage = "0.0.50"
jms-storage = "0.0.51"
simplejson = "3.19.1"
six = "1.16.0"
sshtunnel = "0.4.0"