perf: 优化ip黑白名单

pull/7246/head
feng626 2021-11-18 22:20:35 +08:00 committed by 老广
parent d94c515cfc
commit 24d0d52a0a
6 changed files with 141 additions and 83 deletions

View File

@ -51,10 +51,14 @@ invalid_login_msg = _(
"You can also try {times_try} times "
"(The account will be temporarily locked for {block_time} minutes)"
)
block_login_msg = _(
block_user_login_msg = _(
"The account has been locked "
"(please contact admin to unlock it or try again after {} minutes)"
)
block_ip_login_msg = _(
"The ip has been locked "
"(please contact admin to unlock it or try again after {} minutes)"
)
block_mfa_msg = _(
"The account has been locked "
"(please contact admin to unlock it or try again after {} minutes)"
@ -118,7 +122,7 @@ class BlockGlobalIpLoginError(AuthFailedError):
error = 'block_global_ip_login'
def __init__(self, username, ip, **kwargs):
self.msg = _("IP is not allowed")
self.msg = block_ip_login_msg.format(settings.SECURITY_LOGIN_IP_LIMIT_TIME)
LoginIpBlockUtil(ip).set_block_if_need()
super().__init__(username=username, ip=ip, **kwargs)
@ -133,7 +137,7 @@ class CredentialError(
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
if times_remainder < 1:
self.msg = block_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
self.msg = block_user_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
return
default_msg = invalid_login_msg.format(
@ -184,7 +188,7 @@ class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError):
error = 'block_login'
def __init__(self, username, ip):
self.msg = block_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
self.msg = block_user_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
super().__init__(username=username, ip=ip)

View File

@ -291,8 +291,11 @@ class Config(dict):
'SECURITY_COMMAND_EXECUTION': True,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_VIEW_AUTH_NEED_MFA': True,
'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_IP_BLACK_LIST': [],
'SECURITY_LOGIN_IP_WHITE_LIST': [],
'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_IP_LIMIT_COUNT': 99999,
'SECURITY_LOGIN_IP_LIMIT_TIME': 30,
'SECURITY_LOGIN_LIMIT_TIME': 30,
'SECURITY_MAX_IDLE_TIME': 30,
'SECURITY_PASSWORD_EXPIRATION_TIME': 9999,

View File

@ -35,6 +35,9 @@ SECURITY_MFA_AUTH = CONFIG.SECURITY_MFA_AUTH
SECURITY_COMMAND_EXECUTION = CONFIG.SECURITY_COMMAND_EXECUTION
SECURITY_LOGIN_LIMIT_COUNT = CONFIG.SECURITY_LOGIN_LIMIT_COUNT
SECURITY_LOGIN_IP_BLACK_LIST = CONFIG.SECURITY_LOGIN_IP_BLACK_LIST
SECURITY_LOGIN_IP_WHITE_LIST = CONFIG.SECURITY_LOGIN_IP_WHITE_LIST
SECURITY_LOGIN_IP_LIMIT_COUNT = CONFIG.SECURITY_LOGIN_IP_LIMIT_COUNT
SECURITY_LOGIN_IP_LIMIT_TIME = CONFIG.SECURITY_LOGIN_IP_LIMIT_TIME
SECURITY_LOGIN_LIMIT_TIME = CONFIG.SECURITY_LOGIN_LIMIT_TIME # Unit: minute
SECURITY_MAX_IDLE_TIME = CONFIG.SECURITY_MAX_IDLE_TIME # Unit: minute
SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-18 19:00+0800\n"
"POT-Creation-Date: 2021-11-19 10:40+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"
@ -1619,13 +1619,19 @@ msgstr ""
"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将"
"被临时 锁定 {block_time} 分钟)"
#: authentication/errors.py:55 authentication/errors.py:59
#: authentication/errors.py:55 authentication/errors.py:63
msgid ""
"The account has been locked (please contact admin to unlock it or try again "
"after {} minutes)"
msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)"
#: authentication/errors.py:63
#: authentication/errors.py:59
msgid ""
"The ip has been locked (please contact admin to unlock it or try again after "
"{} minutes)"
msgstr "IP已被锁定请联系管理员解锁 或 {}分钟后重试)"
#: authentication/errors.py:67
#, python-brace-format
msgid ""
"{error}, You can also try {times_try} times (The account will be temporarily "
@ -1633,63 +1639,63 @@ msgid ""
msgstr ""
"{error},您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)"
#: authentication/errors.py:67
#: authentication/errors.py:71
msgid "MFA required"
msgstr "需要 MFA 认证"
#: authentication/errors.py:68
#: authentication/errors.py:72
msgid "MFA not set, please set it first"
msgstr "MFA 没有设置,请先完成设置"
#: authentication/errors.py:69
#: authentication/errors.py:73
msgid "Login confirm required"
msgstr "需要登录复核"
#: authentication/errors.py:70
#: authentication/errors.py:74
msgid "Wait login confirm ticket for accept"
msgstr "等待登录复核处理"
#: authentication/errors.py:71
#: authentication/errors.py:75
msgid "Login confirm ticket was {}"
msgstr "登录复核 {}"
#: authentication/errors.py:121 authentication/errors.py:251
#: authentication/errors.py:255
msgid "IP is not allowed"
msgstr "来源 IP 不被允许登录"
#: authentication/errors.py:258
#: authentication/errors.py:262
msgid "Time Period is not allowed"
msgstr "该 时间段 不被允许登录"
#: authentication/errors.py:291
#: authentication/errors.py:295
msgid "SSO auth closed"
msgstr "SSO 认证关闭了"
#: authentication/errors.py:296 authentication/mixins.py:360
#: authentication/errors.py:300 authentication/mixins.py:360
msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改"
#: authentication/errors.py:305 authentication/mixins.py:367
#: authentication/errors.py:309 authentication/mixins.py:367
msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码"
#: authentication/errors.py:314 authentication/mixins.py:374
#: authentication/errors.py:318 authentication/mixins.py:374
msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录"
#: authentication/errors.py:348
#: authentication/errors.py:352
msgid "Your password is invalid"
msgstr "您的密码无效"
#: authentication/errors.py:353
#: authentication/errors.py:357
msgid "Please enter MFA code"
msgstr "请输入 MFA 验证码"
#: authentication/errors.py:358
#: authentication/errors.py:362
msgid "Please enter SMS code"
msgstr "请输入短信验证码"
#: authentication/errors.py:363 users/exceptions.py:15
#: authentication/errors.py:367 users/exceptions.py:15
msgid "Phone not set"
msgstr "手机号没有设置"
@ -3339,12 +3345,12 @@ msgid "Global MFA auth"
msgstr "全局启用 MFA 认证"
#: settings/serializers/security.py:50
msgid "Limit the number of login failures"
msgstr "限制登录失败次数"
msgid "Limit the number of user login failures"
msgstr "限制用户登录失败次数"
#: settings/serializers/security.py:54
msgid "Block logon interval"
msgstr "禁止登录时间间隔"
msgid "Block user login interval"
msgstr "禁止用户登录时间间隔"
#: settings/serializers/security.py:56
msgid ""
@ -3352,11 +3358,25 @@ msgid ""
"times, no login is allowed during this time interval."
msgstr "单位:分, 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
#: settings/serializers/security.py:61
msgid "Login IP Black List"
msgstr "登录 IP 黑名单"
#: settings/serializers/security.py:62
msgid "Limit the number of IP login failures"
msgstr "限制IP登录失败次数"
#: settings/serializers/security.py:64
#: settings/serializers/security.py:66
msgid "Block IP login interval"
msgstr "禁止IP登录时间间隔"
#: settings/serializers/security.py:68
msgid ""
"Unit: minute, If the IP has failed to log in for a limited number of times, "
"no login is allowed during this time interval."
msgstr "单位:分, 当IP登录失败次数达到限制后那么在此时间间隔内禁止登录"
#: settings/serializers/security.py:73
msgid "Login IP White List"
msgstr "IP登录白名单"
#: settings/serializers/security.py:76 settings/serializers/security.py:84
msgid ""
"Format for comma-delimited string. Such as: 192.168.10.1, 192.168.1.0/24, "
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64"
@ -3364,11 +3384,15 @@ msgstr ""
"格式为逗号分隔的字符串。例如: 192.168.10.1, 192.168.1.0/24, "
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
#: settings/serializers/security.py:70
#: settings/serializers/security.py:81
msgid "Login IP Black List"
msgstr "IP登录黑名单"
#: settings/serializers/security.py:90
msgid "User password expiration"
msgstr "用户密码过期时间"
#: settings/serializers/security.py:72
#: settings/serializers/security.py:92
msgid ""
"Unit: day, If the user does not update the password during the time, the "
"user password will expire failure;The password expiration reminder mail will "
@ -3378,55 +3402,55 @@ msgstr ""
"单位:天, 如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒邮件"
"将在密码过期前5天内由系统每天自动发送给用户"
#: settings/serializers/security.py:79
#: settings/serializers/security.py:99
msgid "Number of repeated historical passwords"
msgstr "不能设置近几次密码"
#: settings/serializers/security.py:81
#: settings/serializers/security.py:101
msgid ""
"Tip: When the user resets the password, it cannot be the previous n "
"historical passwords of the user"
msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码"
#: settings/serializers/security.py:86
#: settings/serializers/security.py:106
msgid "Only single device login"
msgstr "仅一台设备登录"
#: settings/serializers/security.py:87
#: settings/serializers/security.py:107
msgid "Next device login, pre login will be logout"
msgstr "下个设备登录,上次登录会被顶掉"
#: settings/serializers/security.py:90
#: settings/serializers/security.py:110
msgid "Only exist user login"
msgstr "仅已存在用户登录"
#: settings/serializers/security.py:91
#: settings/serializers/security.py:111
msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet"
msgstr "开启后如果系统中不存在该用户CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:94
#: settings/serializers/security.py:114
msgid "Only from source login"
msgstr "仅从用户来源登录"
#: settings/serializers/security.py:95
#: settings/serializers/security.py:115
msgid "Only log in from the user source property"
msgstr "开启后如果用户来源为本地CAS、OIDC 登录将会失败"
#: settings/serializers/security.py:99
#: settings/serializers/security.py:119
msgid "MFA verify TTL"
msgstr "MFA 校验有效期"
#: settings/serializers/security.py:101
#: settings/serializers/security.py:121
msgid ""
"Unit: second, The verification MFA takes effect only when you view the "
"account password"
msgstr "单位: 秒, 目前仅在查看账号密码校验 MFA 时生效"
#: settings/serializers/security.py:106
#: settings/serializers/security.py:126
msgid "Enable Login dynamic code"
msgstr "启用登录附加码"
#: settings/serializers/security.py:107
#: settings/serializers/security.py:127
msgid ""
"The password and additional code are sent to a third party authentication "
"system for verification"
@ -3434,89 +3458,89 @@ msgstr ""
"密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密"
"码+6位数字 完成认证"
#: settings/serializers/security.py:112
#: settings/serializers/security.py:132
msgid "MFA in login page"
msgstr "MFA 在登录页面输入"
#: settings/serializers/security.py:113
#: settings/serializers/security.py:133
msgid "Eu security regulations(GDPR) require MFA to be on the login page"
msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全"
#: settings/serializers/security.py:116
#: settings/serializers/security.py:136
msgid "Enable Login captcha"
msgstr "启用登录验证码"
#: settings/serializers/security.py:117
#: settings/serializers/security.py:137
msgid "Enable captcha to prevent robot authentication"
msgstr "开启验证码,防止机器人登录"
#: settings/serializers/security.py:137
#: settings/serializers/security.py:157
msgid "Enable terminal register"
msgstr "终端注册"
#: settings/serializers/security.py:139
#: settings/serializers/security.py:159
msgid ""
"Allow terminal register, after all terminal setup, you should disable this "
"for security"
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
#: settings/serializers/security.py:143
#: settings/serializers/security.py:163
msgid "Enable watermark"
msgstr "开启水印"
#: settings/serializers/security.py:144
#: settings/serializers/security.py:164
msgid "Enabled, the web session and replay contains watermark information"
msgstr "启用后Web 会话和录像将包含水印信息"
#: settings/serializers/security.py:148
#: settings/serializers/security.py:168
msgid "Connection max idle time"
msgstr "连接最大空闲时间"
#: settings/serializers/security.py:149
#: settings/serializers/security.py:169
msgid "If idle time more than it, disconnect connection Unit: minute"
msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)"
#: settings/serializers/security.py:152
#: settings/serializers/security.py:172
msgid "Remember manual auth"
msgstr "保存手动输入密码"
#: settings/serializers/security.py:155
#: settings/serializers/security.py:175
msgid "Enable change auth secure mode"
msgstr "启用改密安全模式"
#: settings/serializers/security.py:158
#: settings/serializers/security.py:178
msgid "Insecure command alert"
msgstr "危险命令告警"
#: settings/serializers/security.py:161
#: settings/serializers/security.py:181
msgid "Email recipient"
msgstr "邮件收件人"
#: settings/serializers/security.py:162
#: settings/serializers/security.py:182
msgid "Multiple user using , split"
msgstr "多个用户,使用 , 分割"
#: settings/serializers/security.py:165
#: settings/serializers/security.py:185
msgid "Batch command execution"
msgstr "批量命令执行"
#: settings/serializers/security.py:166
#: settings/serializers/security.py:186
msgid "Allow user run batch command or not using ansible"
msgstr "是否允许用户使用 ansible 执行批量命令"
#: settings/serializers/security.py:169
#: settings/serializers/security.py:189
msgid "Session share"
msgstr "会话分享"
#: settings/serializers/security.py:170
#: settings/serializers/security.py:190
msgid "Enabled, Allows user active session to be shared with other users"
msgstr "开启后允许用户分享已连接的资产会话给它人,协同工作"
#: settings/serializers/security.py:173
#: settings/serializers/security.py:193
msgid "Remote Login Protection"
msgstr "异地登录保护"
#: settings/serializers/security.py:175
#: settings/serializers/security.py:195
msgid ""
"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 "

View File

@ -47,16 +47,36 @@ class SecurityAuthSerializer(serializers.Serializer):
)
SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(
min_value=3, max_value=99999,
label=_('Limit the number of login failures')
label=_('Limit the number of user login failures')
)
SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField(
min_value=5, max_value=99999, required=True,
label=_('Block logon interval'),
label=_('Block user login interval'),
help_text=_(
'Unit: minute, If the user has failed to log in for a limited number of times, '
'no login is allowed during this time interval.'
)
)
SECURITY_LOGIN_IP_LIMIT_COUNT = serializers.IntegerField(
min_value=3, max_value=99999,
label=_('Limit the number of IP login failures')
)
SECURITY_LOGIN_IP_LIMIT_TIME = serializers.IntegerField(
min_value=5, max_value=99999, required=True,
label=_('Block IP login interval'),
help_text=_(
'Unit: minute, If the IP has failed to log in for a limited number of times, '
'no login is allowed during this time interval.'
)
)
SECURITY_LOGIN_IP_WHITE_LIST = serializers.ListField(
default=[], label=_('Login IP White List'), allow_empty=True,
child=serializers.CharField(max_length=1024, validators=[ip_child_validator]),
help_text=_(
'Format for comma-delimited string. Such as: '
'192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64'
)
)
SECURITY_LOGIN_IP_BLACK_LIST = serializers.ListField(
default=[], label=_('Login IP Black List'), allow_empty=True,
child=serializers.CharField(max_length=1024, validators=[ip_child_validator]),

View File

@ -180,34 +180,38 @@ class BlockGlobalIpUtilBase:
self.ip = ip
self.limit_key = self.LIMIT_KEY_TMPL.format(ip)
self.block_key = self.BLOCK_KEY_TMPL.format(ip)
self.key_ttl = int(settings.SECURITY_LOGIN_LIMIT_TIME) * 60
self.key_ttl = int(settings.SECURITY_LOGIN_IP_LIMIT_TIME) * 60
@property
def ip_in_black_list(self):
return self.ip in settings.SECURITY_LOGIN_IP_BLACK_LIST
def set_block_if_need(self):
if not self.ip_in_black_list:
return
count = cache.get(self.limit_key, 0)
count += 1
cache.set(self.limit_key, count, self.key_ttl)
@property
def ip_in_white_list(self):
return self.ip in settings.SECURITY_LOGIN_IP_WHITE_LIST
limit_count = settings.SECURITY_LOGIN_LIMIT_COUNT
if count < limit_count:
return
cache.set(self.block_key, True, self.key_ttl)
def set_block_if_need(self):
if not self.ip_in_white_list and not self.ip_in_black_list:
count = cache.get(self.limit_key, 0)
count += 1
cache.set(self.limit_key, count, self.key_ttl)
limit_count = settings.SECURITY_LOGIN_IP_LIMIT_COUNT
if count < limit_count:
return
cache.set(self.block_key, True, self.key_ttl)
def clean_block_if_need(self):
if not self.ip_in_black_list:
return
cache.delete(self.limit_key)
cache.delete(self.block_key)
def is_block(self):
if not self.ip_in_black_list:
if self.ip_in_white_list:
return False
return bool(cache.get(self.block_key))
if self.ip_in_black_list:
return True
if not self.ip_in_white_list and not self.ip_in_black_list:
return bool(cache.get(self.block_key))
class LoginBlockUtil(BlockUtilBase):