perf: AKSK添加访问IP控制

pull/12001/head
wangruidong 2023-10-31 14:15:07 +08:00 committed by Bryan
parent bc54685a31
commit dc841650cf
7 changed files with 100 additions and 32 deletions

View File

@ -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

View File

@ -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'),
),
]

View File

@ -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'))

View File

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

View File

@ -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 = {}

View File

@ -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"

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-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"