perf: add TERMINAL_SSH_KEY_LIMIT_COUNT conf

pull/13961/head
wangruidong 2024-08-13 16:06:01 +08:00 committed by Bryan
parent 3b1701b1aa
commit 32ae77c42d
12 changed files with 167 additions and 36 deletions

View File

@ -1,8 +1,3 @@
from django.utils import timezone
from rest_framework.response import Response
from rest_framework.decorators import action
from rbac.permissions import RBACPermission
from common.api import JMSModelViewSet
from common.permissions import IsValidUser
from ..serializers import SSHKeySerializer
@ -14,6 +9,7 @@ class SSHkeyViewSet(JMSModelViewSet):
permission_classes = [IsValidUser]
filterset_fields = ('name', 'is_active')
search_fields = ('name',)
ordering = ('-date_last_used', '-date_created')
def get_queryset(self):
return self.request.user.ssh_keys.all()

View File

@ -1,14 +1,22 @@
# -*- coding: utf-8 -*-
#
from django.utils import timezone
from django.db.models import TextChoices
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.serializers.fields import ReadableHiddenField
from common.serializers.fields import ReadableHiddenField, LabeledChoiceField
from ..models import SSHKey
from common.utils import validate_ssh_public_key
from users.exceptions import CreateSSHKeyExceedLimit
__all__ = ['SSHKeySerializer']
__all__ = ['SSHKeySerializer', 'GenerateKeyType']
class GenerateKeyType(TextChoices):
auto = 'auto', _('Automatically Generate Key Pair')
# 目前只支持sftp方式
load = 'load', _('Import Existing Key Pair')
class SSHKeySerializer(serializers.ModelSerializer):
@ -19,16 +27,22 @@ class SSHKeySerializer(serializers.ModelSerializer):
public_key_hash_md5 = serializers.CharField(
source='get_public_key_hash_md5', required=False, read_only=True, max_length=128
)
generate_key_type = LabeledChoiceField(
choices=GenerateKeyType.choices, label=_('Create Type'), default=GenerateKeyType.auto.value, required=False,
help_text=_(
'Please download the private key after creation. Each private key can only be downloaded once'
)
)
class Meta:
model = SSHKey
fields_mini = ['name']
fields_small = fields_mini + [
'public_key', 'is_active',
'public_key', 'is_active', 'comment'
]
read_only_fields = [
'id', 'user', 'public_key_comment', 'public_key_hash_md5',
'date_last_used', 'date_created', 'date_updated'
'date_last_used', 'date_created', 'date_updated', 'generate_key_type',
]
fields = fields_small + read_only_fields
@ -42,3 +56,9 @@ class SSHKeySerializer(serializers.ModelSerializer):
if not validate_ssh_public_key(value):
raise serializers.ValidationError(_('Not a valid ssh public key'))
return value
def create(self, validated_data):
if not self.context["request"].user.can_create_ssh_key():
raise CreateSSHKeyExceedLimit()
validated_data.pop('generate_key_type', None)
return super().create(validated_data)

View File

@ -63,6 +63,7 @@ urlpatterns = [
# Profile
path('profile/mfa/', users_view.MFASettingView.as_view(), name='user-mfa-setting'),
path('profile/pubkey/generate/', users_view.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
# OTP Setting
path('profile/otp/enable/start/', users_view.UserOtpEnableStartView.as_view(), name='user-otp-enable-start'),

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-12 18:34+0800\n"
"POT-Creation-Date: 2024-08-13 16:47+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"
@ -1582,7 +1582,7 @@ msgid "Gather facts"
msgstr "資産情報の収集"
#: assets/const/base.py:32 audits/const.py:58
#: terminal/serializers/applet_host.py:32 users/models/user/_auth.py:31
#: terminal/serializers/applet_host.py:32 users/models/user/_auth.py:32
msgid "Disabled"
msgstr "無効"
@ -2080,7 +2080,7 @@ msgstr "設定"
#: assets/models/platform.py:38 audits/const.py:59
#: authentication/backends/passkey/models.py:11 settings/models.py:38
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:32
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:33
msgid "Enabled"
msgstr "有効化"
@ -3119,7 +3119,7 @@ msgstr "MFAコードを入力してください"
msgid "Please enter SMS code"
msgstr "SMSコードを入力してください"
#: authentication/errors/failed.py:164 users/exceptions.py:15
#: authentication/errors/failed.py:164 users/exceptions.py:14
msgid "Phone not set"
msgstr "電話が設定されていない"
@ -3483,7 +3483,25 @@ msgstr "組織名"
msgid "The {} cannot be empty"
msgstr "{} 空にしてはならない"
#: authentication/serializers/ssh_key.py:43 users/forms/profile.py:161
#: authentication/serializers/ssh_key.py:17
msgid "Automatically Generate Key Pair"
msgstr "キーペアを自動作成"
#: authentication/serializers/ssh_key.py:19
msgid "Import Existing Key Pair"
msgstr "既存のキーペアをインポート"
#: authentication/serializers/ssh_key.py:31
msgid "Create Type"
msgstr "タイプを作成"
#: authentication/serializers/ssh_key.py:33
msgid ""
"Please download the private key after creation. Each private key can only be "
"downloaded once"
msgstr "作成完了後、秘密鍵をダウンロードしてください。各秘密鍵のダウンロードは一度きりです"
#: authentication/serializers/ssh_key.py:57 users/forms/profile.py:161
#: users/serializers/profile.py:133 users/serializers/profile.py:160
msgid "Not a valid ssh public key"
msgstr "有効なssh公開鍵ではありません"
@ -8445,14 +8463,18 @@ msgstr "置換"
msgid "Suffix"
msgstr "接尾辞を付ける"
#: users/exceptions.py:10
#: users/exceptions.py:9
msgid "MFA not enabled"
msgstr "MFAが有効化されていません"
#: users/exceptions.py:20
#: users/exceptions.py:19
msgid "Unable to delete all users"
msgstr "すべてのユーザーを削除できません"
#: users/exceptions.py:24
msgid "Create failed. The number of SSH keys has reached the limit"
msgstr "作成に失敗しました。SSHキーの数が上限に達しました"
#: users/forms/profile.py:48
msgid ""
"When enabled, you will enter the MFA binding process the next time you log "
@ -8578,7 +8600,7 @@ msgstr "ユーザーに一致できます"
msgid "User password history"
msgstr "ユーザーパスワード履歴"
#: users/models/user/_auth.py:33
#: users/models/user/_auth.py:34
msgid "Force enabled"
msgstr "強制有効"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-12 18:34+0800\n"
"POT-Creation-Date: 2024-08-13 16:47+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"
@ -1571,7 +1571,7 @@ msgid "Gather facts"
msgstr "收集资产信息"
#: assets/const/base.py:32 audits/const.py:58
#: terminal/serializers/applet_host.py:32 users/models/user/_auth.py:31
#: terminal/serializers/applet_host.py:32 users/models/user/_auth.py:32
msgid "Disabled"
msgstr "禁用"
@ -2067,7 +2067,7 @@ msgstr "设置"
#: assets/models/platform.py:38 audits/const.py:59
#: authentication/backends/passkey/models.py:11 settings/models.py:38
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:32
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:33
msgid "Enabled"
msgstr "启用"
@ -3083,7 +3083,7 @@ msgstr "请输入 MFA 验证码"
msgid "Please enter SMS code"
msgstr "请输入短信验证码"
#: authentication/errors/failed.py:164 users/exceptions.py:15
#: authentication/errors/failed.py:164 users/exceptions.py:14
msgid "Phone not set"
msgstr "手机号没有设置"
@ -3443,7 +3443,25 @@ msgstr "组织名称"
msgid "The {} cannot be empty"
msgstr "{} 不能为空"
#: authentication/serializers/ssh_key.py:43 users/forms/profile.py:161
#: authentication/serializers/ssh_key.py:17
msgid "Automatically Generate Key Pair"
msgstr "自动创建密钥对"
#: authentication/serializers/ssh_key.py:19
msgid "Import Existing Key Pair"
msgstr "导入已有密钥对"
#: authentication/serializers/ssh_key.py:31
msgid "Create Type"
msgstr "创建类型"
#: authentication/serializers/ssh_key.py:33
msgid ""
"Please download the private key after creation. Each private key can only be "
"downloaded once"
msgstr "创建完成后请下载私钥,每个私钥只有一次下载机会"
#: authentication/serializers/ssh_key.py:57 users/forms/profile.py:161
#: users/serializers/profile.py:133 users/serializers/profile.py:160
msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法"
@ -8293,14 +8311,18 @@ msgstr "替换"
msgid "Suffix"
msgstr "加后缀"
#: users/exceptions.py:10
#: users/exceptions.py:9
msgid "MFA not enabled"
msgstr "MFA 多因子认证没有开启"
#: users/exceptions.py:20
#: users/exceptions.py:19
msgid "Unable to delete all users"
msgstr "无法删除全部用户"
#: users/exceptions.py:24
msgid "Create failed. The number of SSH keys has reached the limit"
msgstr "创建失败SSH密钥数量已达到上限"
#: users/forms/profile.py:48
msgid ""
"When enabled, you will enter the MFA binding process the next time you log "
@ -8426,7 +8448,7 @@ msgstr "可以匹配用户"
msgid "User password history"
msgstr "用户密码历史"
#: users/models/user/_auth.py:33
#: users/models/user/_auth.py:34
msgid "Force enabled"
msgstr "强制启用"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-12 18:34+0800\n"
"POT-Creation-Date: 2024-08-13 16:47+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"
@ -1573,7 +1573,7 @@ msgid "Gather facts"
msgstr "收集資產資訊"
#: assets/const/base.py:32 audits/const.py:58
#: terminal/serializers/applet_host.py:32 users/models/user/_auth.py:31
#: terminal/serializers/applet_host.py:32 users/models/user/_auth.py:32
msgid "Disabled"
msgstr "禁用"
@ -2069,7 +2069,7 @@ msgstr "設置"
#: assets/models/platform.py:38 audits/const.py:59
#: authentication/backends/passkey/models.py:11 settings/models.py:38
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:32
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:33
msgid "Enabled"
msgstr "啟用"
@ -3085,7 +3085,7 @@ msgstr "請輸入 MFA 驗證碼"
msgid "Please enter SMS code"
msgstr "請輸入簡訊驗證碼"
#: authentication/errors/failed.py:164 users/exceptions.py:15
#: authentication/errors/failed.py:164 users/exceptions.py:14
msgid "Phone not set"
msgstr "手機號碼沒有設置"
@ -3445,7 +3445,25 @@ msgstr "組織名稱"
msgid "The {} cannot be empty"
msgstr "{} 不能為空"
#: authentication/serializers/ssh_key.py:43 users/forms/profile.py:161
#: authentication/serializers/ssh_key.py:17
msgid "Automatically Generate Key Pair"
msgstr "自動創建密鑰對"
#: authentication/serializers/ssh_key.py:19
msgid "Import Existing Key Pair"
msgstr "導入已有密鑰對"
#: authentication/serializers/ssh_key.py:31
msgid "Create Type"
msgstr "創建類型"
#: authentication/serializers/ssh_key.py:33
msgid ""
"Please download the private key after creation. Each private key can only be "
"downloaded once"
msgstr "創建完成後請下載私鑰,每個私鑰僅有一次下載機會"
#: authentication/serializers/ssh_key.py:57 users/forms/profile.py:161
#: users/serializers/profile.py:133 users/serializers/profile.py:160
msgid "Not a valid ssh public key"
msgstr "SSH金鑰不合法"
@ -8296,14 +8314,18 @@ msgstr "替換"
msgid "Suffix"
msgstr "加後綴"
#: users/exceptions.py:10
#: users/exceptions.py:9
msgid "MFA not enabled"
msgstr "MFA 多因子認證沒有開啟"
#: users/exceptions.py:20
#: users/exceptions.py:19
msgid "Unable to delete all users"
msgstr "無法刪除全部用戶"
#: users/exceptions.py:24
msgid "Create failed. The number of SSH keys has reached the limit"
msgstr "創建失敗SSH密鑰數量已達到上限"
#: users/forms/profile.py:48
msgid ""
"When enabled, you will enter the MFA binding process the next time you log "
@ -8429,7 +8451,7 @@ msgstr "可以匹配用戶"
msgid "User password history"
msgstr "用戶密碼歷史"
#: users/models/user/_auth.py:33
#: users/models/user/_auth.py:34
msgid "Force enabled"
msgstr "強制啟用"

View File

@ -509,6 +509,7 @@ class Config(dict):
# Terminal配置
'TERMINAL_PASSWORD_AUTH': True,
'TERMINAL_PUBLIC_KEY_AUTH': True,
'TERMINAL_SSH_KEY_LIMIT_COUNT': 10,
'TERMINAL_HEARTBEAT_INTERVAL': 20,
'TERMINAL_ASSET_LIST_SORT_BY': 'name',
'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto',

View File

@ -83,6 +83,7 @@ CACHE_LOGIN_PASSWORD_TTL = CONFIG.CACHE_LOGIN_PASSWORD_TTL
# Terminal other setting
TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH
TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH
TERMINAL_SSH_KEY_LIMIT_COUNT = CONFIG.TERMINAL_SSH_KEY_LIMIT_COUNT
TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL
TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY
TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE
@ -239,4 +240,4 @@ ANSIBLE_RECEPTOR_TCP_LISTEN_ADDRESS = CONFIG.ANSIBLE_RECEPTOR_TCP_LISTEN_ADDRESS
LOKI_LOG_ENABLED = CONFIG.LOKI_LOG_ENABLED
LOKI_BASE_URL = CONFIG.LOKI_BASE_URL
TOOL_USER_ENABLED = CONFIG.TOOL_USER_ENABLED
TOOL_USER_ENABLED = CONFIG.TOOL_USER_ENABLED

View File

@ -1,6 +1,5 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import status
from common.exceptions import JMSException
@ -18,3 +17,8 @@ class PhoneNotSet(JMSException):
class UnableToDeleteAllUsers(JMSException):
default_code = 'unable_to_delete_all_users'
default_detail = _('Unable to delete all users')
class CreateSSHKeyExceedLimit(JMSException):
default_code = 'create_ssh_key_exceed_limit'
default_detail = _('Create failed. The number of SSH keys has reached the limit')

View File

@ -18,6 +18,7 @@ from common.utils import (
lazyproperty,
)
from users.signals import post_user_change_password
from users.exceptions import CreateSSHKeyExceedLimit
logger = get_logger(__file__)
@ -133,6 +134,15 @@ class AuthMixin:
post_user_change_password.send(self.__class__, user=self)
super().set_password(raw_password) # noqa
def set_ssh_key(self, name, public_key, private_key):
if self.can_update_ssh_key():
from authentication.models import SSHKey
SSHKey.objects.create(name=name, public_key=public_key, private_key=private_key, user=self)
post_user_change_password.send(self.__class__, user=self)
def can_create_ssh_key(self):
return self.ssh_keys.count() < settings.TERMINAL_SSH_KEY_LIMIT_COUNT
def can_update_password(self):
return self.is_local

View File

@ -4,3 +4,4 @@ from .password import *
from .mfa import *
from .otp import *
from .reset import *
from .pubkey import *

View File

@ -0,0 +1,31 @@
# ~*~ coding: utf-8 ~*~
from django.http import HttpResponse
from django.views import View
from common.utils import get_logger, ssh_key_gen
from common.permissions import IsValidUser
from common.views.mixins import PermissionsMixin
from users.exceptions import CreateSSHKeyExceedLimit
__all__ = ['UserPublicKeyGenerateView']
logger = get_logger(__name__)
class UserPublicKeyGenerateView(PermissionsMixin, View):
permission_classes = [IsValidUser]
def get(self, request, *args, **kwargs):
username = request.user.username
key_name = request.GET.get('name', '')
if not request.user.can_create_ssh_key():
return HttpResponse(
CreateSSHKeyExceedLimit().default_detail, status=400
)
private, public = ssh_key_gen(username=username, hostname='jumpserver')
request.user.set_ssh_key(key_name, public, private)
response = HttpResponse(private, content_type='text/plain')
filename = "{0}-jumpserver.pem".format(username)
response['Content-Disposition'] = 'attachment; filename={}'.format(filename)
return response