mirror of https://github.com/jumpserver/jumpserver
perf: add TERMINAL_SSH_KEY_LIMIT_COUNT conf
parent
3b1701b1aa
commit
32ae77c42d
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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 "強制有効"
|
||||
|
||||
|
|
|
@ -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 "强制启用"
|
||||
|
||||
|
|
|
@ -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 "強制啟用"
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@ from .password import *
|
|||
from .mfa import *
|
||||
from .otp import *
|
||||
from .reset import *
|
||||
from .pubkey import *
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue