From dc841650cf5e6fb6eba98c690e6eb6dbe4929201 Mon Sep 17 00:00:00 2001 From: wangruidong <940853815@qq.com> Date: Tue, 31 Oct 2023 14:15:07 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20AKSK=E6=B7=BB=E5=8A=A0=E8=AE=BF?= =?UTF-8?q?=E9=97=AEIP=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/drf.py | 13 ++++++- .../migrations/0024_accesskey_ip_group.py | 19 ++++++++++ apps/authentication/models/access_key.py | 5 +++ apps/authentication/serializers/token.py | 8 +++- apps/common/auth/signature.py | 12 +++++- apps/locale/ja/LC_MESSAGES/django.po | 38 ++++++++++++------- apps/locale/zh/LC_MESSAGES/django.po | 37 +++++++++++------- 7 files changed, 100 insertions(+), 32 deletions(-) create mode 100644 apps/authentication/migrations/0024_accesskey_ip_group.py diff --git a/apps/authentication/backends/drf.py b/apps/authentication/backends/drf.py index 4ba879cc2..3822ec4a5 100644 --- a/apps/authentication/backends/drf.py +++ b/apps/authentication/backends/drf.py @@ -8,7 +8,7 @@ from django.utils.translation import gettext as _ from rest_framework import authentication, exceptions from common.auth import signature -from common.utils import get_object_or_none +from common.utils import get_object_or_none, get_request_ip_or_data, contains_ip from ..models import AccessKey, PrivateToken @@ -122,3 +122,14 @@ class SignatureAuthentication(signature.SignatureAuthentication): return user, secret except (AccessKey.DoesNotExist, exceptions.ValidationError): return None, None + + def is_ip_allow(self, key_id, request): + try: + ak = AccessKey.objects.get(id=key_id) + ip_group = ak.ip_group + ip = get_request_ip_or_data(request) + if not contains_ip(ip, ip_group): + return False + return True + except (AccessKey.DoesNotExist, exceptions.ValidationError): + return False diff --git a/apps/authentication/migrations/0024_accesskey_ip_group.py b/apps/authentication/migrations/0024_accesskey_ip_group.py new file mode 100644 index 000000000..ba26ff6af --- /dev/null +++ b/apps/authentication/migrations/0024_accesskey_ip_group.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.10 on 2023-10-31 05:37 + +import authentication.models.access_key +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0023_auto_20231010_1101'), + ] + + operations = [ + migrations.AddField( + model_name='accesskey', + name='ip_group', + field=models.JSONField(default=authentication.models.access_key.defatult_ip_group, verbose_name='IP group'), + ), + ] diff --git a/apps/authentication/models/access_key.py b/apps/authentication/models/access_key.py index 5d9571569..51e5b8849 100644 --- a/apps/authentication/models/access_key.py +++ b/apps/authentication/models/access_key.py @@ -12,9 +12,14 @@ def default_secret(): return random_string(36) +def defatult_ip_group(): + return ["*"] + + class AccessKey(models.Model): id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, default=uuid.uuid4, editable=False) secret = models.CharField(verbose_name='AccessKeySecret', default=default_secret, max_length=36) + ip_group = models.JSONField(default=defatult_ip_group, verbose_name=_('IP group')) user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User', on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='access_keys') is_active = models.BooleanField(default=True, verbose_name=_('Active')) diff --git a/apps/authentication/serializers/token.py b/apps/authentication/serializers/token.py index e4dab13a5..6cd3af912 100644 --- a/apps/authentication/serializers/token.py +++ b/apps/authentication/serializers/token.py @@ -4,6 +4,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from acls.serializers.rules import ip_group_child_validator, ip_group_help_text from common.utils import get_object_or_none, random_string from users.models import User from users.serializers import UserProfileSerializer @@ -17,9 +18,14 @@ __all__ = [ class AccessKeySerializer(serializers.ModelSerializer): + ip_group = serializers.ListField( + default=['*'], label=_('AccessIP'), help_text=ip_group_help_text, + child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]) + ) + class Meta: model = AccessKey - fields = ['id', 'is_active', 'date_created', 'date_last_used'] + fields = ['id', 'is_active', 'date_created', 'date_last_used'] + ['ip_group'] read_only_fields = ['id', 'date_created', 'date_last_used'] diff --git a/apps/common/auth/signature.py b/apps/common/auth/signature.py index 9661b9620..c1e392f30 100644 --- a/apps/common/auth/signature.py +++ b/apps/common/auth/signature.py @@ -17,6 +17,7 @@ Reusing failure exceptions serves several purposes: """ FAILED = exceptions.AuthenticationFailed('Invalid signature.') +IP_NOT_ALLOW = exceptions.AuthenticationFailed('Ip is not in access ip list.') class SignatureAuthentication(authentication.BaseAuthentication): @@ -43,6 +44,9 @@ class SignatureAuthentication(authentication.BaseAuthentication): """Returns a tuple (User, secret) or (None, None).""" raise NotImplementedError() + def is_ip_allow(self, key_id, request): + raise NotImplementedError() + def authenticate_header(self, request): """ DRF sends this for unauthenticated responses if we're the primary @@ -50,7 +54,7 @@ class SignatureAuthentication(authentication.BaseAuthentication): """ h = " ".join(self.required_headers) return 'Signature realm="%s",headers="%s"' % ( - self.www_authenticate_realm, h) + self.www_authenticate_realm, h) def authenticate(self, request): """ @@ -78,15 +82,19 @@ class SignatureAuthentication(authentication.BaseAuthentication): if len({"keyid", "algorithm", "signature"} - set(fields.keys())) > 0: raise FAILED + key_id = fields["keyid"] # Fetch the secret associated with the keyid user, secret = self.fetch_user_data( - fields["keyid"], + key_id, algorithm=fields["algorithm"] ) if not (user and secret): raise FAILED + if not self.is_ip_allow(key_id, request): + raise IP_NOT_ALLOW + # Gather all request headers and translate them as stated in the Django docs: # https://docs.djangoproject.com/en/1.6/ref/request-response/#django.http.HttpRequest.META headers = {} diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index e32694415..0f9d4b4a6 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-10-30 11:28+0800\n" +"POT-Creation-Date: 2023-10-31 14:04+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" @@ -41,7 +41,7 @@ msgstr "パスワード" msgid "SSH key" msgstr "SSH キー" -#: accounts/const/account.py:8 authentication/models/access_key.py:37 +#: accounts/const/account.py:8 authentication/models/access_key.py:42 msgid "Access key" msgstr "アクセスキー" @@ -843,7 +843,7 @@ msgstr "关联平台,可以配置推送参数,如果不关联,则使用默 #: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 #: ops/models/job.py:145 ops/models/playbook.py:31 rbac/models/role.py:37 #: settings/models.py:37 terminal/models/applet/applet.py:45 -#: terminal/models/applet/applet.py:321 terminal/models/applet/host.py:143 +#: terminal/models/applet/applet.py:317 terminal/models/applet/host.py:143 #: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:104 #: terminal/models/session/session.py:46 tickets/models/comment.py:32 @@ -1014,7 +1014,7 @@ msgstr "1-100、低い値は最初に一致します" msgid "Reviewers" msgstr "レビュー担当者" -#: acls/models/base.py:43 authentication/models/access_key.py:20 +#: acls/models/base.py:43 authentication/models/access_key.py:25 #: authentication/models/connection_token.py:53 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:81 terminal/models/session/sharing.py:29 @@ -1712,7 +1712,7 @@ msgstr "アセットの自動化タスク" #: assets/models/automations/base.py:113 audits/models.py:207 #: audits/serializers.py:51 ops/models/base.py:49 ops/models/job.py:220 -#: terminal/models/applet/applet.py:320 terminal/models/applet/host.py:140 +#: terminal/models/applet/applet.py:316 terminal/models/applet/host.py:140 #: terminal/models/component/status.py:30 terminal/serializers/applet.py:18 #: terminal/serializers/applet_host.py:124 tickets/models/ticket/general.py:283 #: tickets/serializers/super_ticket.py:13 @@ -2653,7 +2653,7 @@ msgid "Added on" msgstr "に追加" #: authentication/backends/passkey/models.py:14 -#: authentication/models/access_key.py:21 +#: authentication/models/access_key.py:26 #: authentication/models/private_token.py:8 msgid "Date last used" msgstr "最後に使用した日付" @@ -2956,6 +2956,11 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" +#: authentication/models/access_key.py:22 +#: terminal/models/component/endpoint.py:95 +msgid "IP group" +msgstr "IP グループ" + #: authentication/models/connection_token.py:38 #: terminal/serializers/storage.py:113 msgid "Account name" @@ -3108,7 +3113,13 @@ msgstr "メール" msgid "The {} cannot be empty" msgstr "{} 空にしてはならない" -#: authentication/serializers/token.py:86 perms/serializers/permission.py:37 +#: authentication/serializers/token.py:23 +#, fuzzy +#| msgid "Access key" +msgid "AccessIP" +msgstr "アクセスキー" + +#: authentication/serializers/token.py:93 perms/serializers/permission.py:37 #: perms/serializers/permission.py:59 users/serializers/user.py:98 #: users/serializers/user.py:168 msgid "Is valid" @@ -4615,7 +4626,7 @@ msgid "My assets" msgstr "私の資産" #: rbac/tree.py:56 terminal/models/applet/applet.py:52 -#: terminal/models/applet/applet.py:317 terminal/models/applet/host.py:30 +#: terminal/models/applet/applet.py:313 terminal/models/applet/host.py:30 #: terminal/serializers/applet.py:15 msgid "Applet" msgstr "リモートアプリケーション" @@ -6346,7 +6357,7 @@ msgstr "カスタムプラットフォームのみをサポート" msgid "Missing type in platform.yml" msgstr "platform.ymlにタイプがありません" -#: terminal/models/applet/applet.py:319 terminal/models/applet/host.py:36 +#: terminal/models/applet/applet.py:315 terminal/models/applet/host.py:36 #: terminal/models/applet/host.py:138 msgid "Hosting" msgstr "ホスト マシン" @@ -6425,10 +6436,6 @@ msgstr "Redis ポート" msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/component/endpoint.py:95 -msgid "IP group" -msgstr "IP グループ" - #: terminal/models/component/endpoint.py:108 msgid "Endpoint rule" msgstr "エンドポイントルール" @@ -6732,7 +6739,10 @@ msgid "" "Connect to the host using the same account first. For security reasons, " "please set the configuration item CACHE_LOGIN_PASSWORD_ENABLED=true and " "restart the service to enable it." -msgstr "同じアカウントを使用してホストに接続します。セキュリティ上の理由から、構成項目 CACHE_LOGIN_PASSWORD_ENABLED=true を設定してサービスを再起動して有効にしてください。" +msgstr "" +"同じアカウントを使用してホストに接続します。セキュリティ上の理由から、構成項" +"目 CACHE_LOGIN_PASSWORD_ENABLED=true を設定してサービスを再起動して有効にして" +"ください。" #: terminal/serializers/command.py:19 msgid "Session ID" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 5a066450d..07c704639 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-10-30 11:28+0800\n" +"POT-Creation-Date: 2023-10-31 14:04+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" @@ -40,7 +40,7 @@ msgstr "密码" msgid "SSH key" msgstr "SSH 密钥" -#: accounts/const/account.py:8 authentication/models/access_key.py:37 +#: accounts/const/account.py:8 authentication/models/access_key.py:42 msgid "Access key" msgstr "Access key" @@ -841,7 +841,7 @@ msgstr "关联平台,可配置推送参数,如果不关联,将使用默认 #: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 #: ops/models/job.py:145 ops/models/playbook.py:31 rbac/models/role.py:37 #: settings/models.py:37 terminal/models/applet/applet.py:45 -#: terminal/models/applet/applet.py:321 terminal/models/applet/host.py:143 +#: terminal/models/applet/applet.py:317 terminal/models/applet/host.py:143 #: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:104 #: terminal/models/session/session.py:46 tickets/models/comment.py:32 @@ -1011,7 +1011,7 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)" msgid "Reviewers" msgstr "审批人" -#: acls/models/base.py:43 authentication/models/access_key.py:20 +#: acls/models/base.py:43 authentication/models/access_key.py:25 #: authentication/models/connection_token.py:53 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:81 terminal/models/session/sharing.py:29 @@ -1705,7 +1705,7 @@ msgstr "资产自动化任务" #: assets/models/automations/base.py:113 audits/models.py:207 #: audits/serializers.py:51 ops/models/base.py:49 ops/models/job.py:220 -#: terminal/models/applet/applet.py:320 terminal/models/applet/host.py:140 +#: terminal/models/applet/applet.py:316 terminal/models/applet/host.py:140 #: terminal/models/component/status.py:30 terminal/serializers/applet.py:18 #: terminal/serializers/applet_host.py:124 tickets/models/ticket/general.py:283 #: tickets/serializers/super_ticket.py:13 @@ -2632,7 +2632,7 @@ msgid "Added on" msgstr "附加" #: authentication/backends/passkey/models.py:14 -#: authentication/models/access_key.py:21 +#: authentication/models/access_key.py:26 #: authentication/models/private_token.py:8 msgid "Date last used" msgstr "最后使用日期" @@ -2925,6 +2925,11 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" +#: authentication/models/access_key.py:22 +#: terminal/models/component/endpoint.py:95 +msgid "IP group" +msgstr "IPグループ" + #: authentication/models/connection_token.py:38 #: terminal/serializers/storage.py:113 msgid "Account name" @@ -3077,7 +3082,13 @@ msgstr "邮箱" msgid "The {} cannot be empty" msgstr "{} 不能为空" -#: authentication/serializers/token.py:86 perms/serializers/permission.py:37 +#: authentication/serializers/token.py:23 +#, fuzzy +#| msgid "Access key" +msgid "AccessIP" +msgstr "アクセスIP" + +#: authentication/serializers/token.py:93 perms/serializers/permission.py:37 #: perms/serializers/permission.py:59 users/serializers/user.py:98 #: users/serializers/user.py:168 msgid "Is valid" @@ -4563,7 +4574,7 @@ msgid "My assets" msgstr "我的资产" #: rbac/tree.py:56 terminal/models/applet/applet.py:52 -#: terminal/models/applet/applet.py:317 terminal/models/applet/host.py:30 +#: terminal/models/applet/applet.py:313 terminal/models/applet/host.py:30 #: terminal/serializers/applet.py:15 msgid "Applet" msgstr "远程应用" @@ -6256,7 +6267,7 @@ msgstr "只支持自定义平台" msgid "Missing type in platform.yml" msgstr "在 platform.yml 中缺少类型" -#: terminal/models/applet/applet.py:319 terminal/models/applet/host.py:36 +#: terminal/models/applet/applet.py:315 terminal/models/applet/host.py:36 #: terminal/models/applet/host.py:138 msgid "Hosting" msgstr "宿主机" @@ -6333,10 +6344,6 @@ msgstr "Redis 端口" msgid "Endpoint" msgstr "端点" -#: terminal/models/component/endpoint.py:95 -msgid "IP group" -msgstr "IP 组" - #: terminal/models/component/endpoint.py:108 msgid "Endpoint rule" msgstr "端点规则" @@ -6637,7 +6644,9 @@ msgid "" "Connect to the host using the same account first. For security reasons, " "please set the configuration item CACHE_LOGIN_PASSWORD_ENABLED=true and " "restart the service to enable it." -msgstr "优先使用同名账号连接发布机。为了安全,需配置文件中开启配置 CACHE_LOGIN_PASSWORD_ENABLED=true, 修改后重启服务" +msgstr "" +"优先使用同名账号连接发布机。为了安全,需配置文件中开启配置 " +"CACHE_LOGIN_PASSWORD_ENABLED=true, 修改后重启服务" #: terminal/serializers/command.py:19 msgid "Session ID"