Compare commits

...

28 Commits

Author SHA1 Message Date
fit2bot
46d5ab950b feat: Update v2.28.8 2023-03-02 16:24:46 +08:00
Bai
c628ba1c4b fix: 修复翻译 2023-03-02 12:25:29 +08:00
fit2bot
ebbae36c49 perf: k8s update api (#9833)
Co-authored-by: feng <1304903146@qq.com>
2023-03-02 11:03:29 +08:00
Bai
69ef25666e fix: 修复认证MFA失败次数清空问题 2023-02-24 14:44:32 +08:00
Bai
d0475397d0 fix: 修复第三方用户登录复核可以跳过的问题 2023-02-09 19:50:13 +08:00
老广
dad45e7ace Merge pull request #9233 from jumpserver/pr@v2.28@perf_dockerfile
build(deps): 优化龙芯构建依赖包
2022-12-22 12:43:37 +08:00
吴小白
720f9cd397 build(deps): 优化龙芯构建依赖包 2022-12-22 12:40:53 +08:00
Bai
81dee0c403 perf: 修改方法名称 check_db_port_mapper 2022-12-22 10:43:07 +08:00
老广
105ef791b8 Merge pull request #9230 from jumpserver/pr@v2.28@v2.28_perf_dbportmapper
fix: 修改db_port_mapper策略; 启动时进行check校验;
2022-12-21 18:59:47 +08:00
Bai
a19c0bde60 fix: 修改db_port_mapper策略; 启动时进行check校验; 2022-12-21 18:43:44 +08:00
halo
3996daf4a7 fix: 导入翻译引用 2022-12-16 11:47:39 +08:00
halo
ac235f788e perf: 优化oauth2的服务地址参数拼接 2022-12-16 11:47:39 +08:00
老广
67e334bf43 Merge pull request #9213 from jumpserver/pr@v2.28@perf_fingerprint
fix: 修复非 ssh 协议的系统用户存在错误私钥,引发的解析问题
2022-12-15 15:25:40 +08:00
Eric
f7f9fb1bdf fix: 修复非 ssh 协议的系统用户存在错误私钥,引发的解析问题 2022-12-15 15:17:36 +08:00
Eric
8979228e0b fix: 修复 ssh 私钥推送等问题 2022-12-13 16:21:54 +08:00
吴小白
024beca690 Merge pull request #9200 from jumpserver/pr@v2.28@perf_support_openid_pkce
perf: OpenID支持PKCE方式对接
2022-12-13 16:12:17 +08:00
jiangweidong
5c0359e394 perf: OpenID支持PKCE方式对接 2022-12-13 15:11:21 +08:00
feng
4ce4bde368 fix: ticket xss inject 2022-12-12 17:03:29 +08:00
halo
809bad271a fix: 密钥指纹参数 2022-12-09 13:41:38 +08:00
Eric
d3bfc03849 fix: 替换解析公钥的方式 2022-12-08 16:57:22 +08:00
Bai
04c0121b37 fix: 降级 Djanog==3.2.15 2022-12-08 14:53:40 +08:00
jiangweidong
b97b50ab31 perf: 支持sentinel开启ssl(Sentinel和Redis公用一套证书,无额外增加配置项) 2022-12-08 12:54:58 +08:00
Eric
d8a8c8153b fix: TraditionalOpenSSL private ssh key 2022-12-08 11:03:52 +08:00
Eric
a68ad7be68 perf: support ed25519 SSH Key
fix: codacy ci
fix: password use bytes
2022-12-08 11:03:52 +08:00
Bai
4041f1aeec fix: 修改 random_string 方法,支持只生成随机数字 2022-12-01 20:13:47 +08:00
feng
59388655ea fix: es 默认存储500 2022-11-18 17:04:43 +08:00
Bai
ef7463c588 fix: flower db file 持久化存储flower信息 2022-11-18 15:36:21 +08:00
Bryan
7e7d6d94e6 fix: 修复 channels-redis 库升级导致 ws 查看任务日志失败的问题; 修改 REDIS_LAYERS_HOST 变量; 修改 Channel SSL 配置项; 2022-11-18 15:26:44 +08:00
34 changed files with 605 additions and 373 deletions

4
Dockerfile-ee Normal file
View File

@@ -0,0 +1,4 @@
ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM jumpserver/core:${VERSION}
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack

View File

@@ -38,8 +38,8 @@ ARG TOOLS=" \
default-mysql-client \
iputils-ping \
locales \
netcat \
redis-server \
procps \
redis-tools \
telnet \
vim \
unzip \

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
c628ba1c4be9e751903889692ebcc66b6a9144e3

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.drf.serializers import BulkModelSerializer
from common.drf.serializers import MethodSerializer

View File

@@ -1,18 +1,14 @@
# -*- coding: utf-8 -*-
from urllib3.exceptions import MaxRetryError
from urllib.parse import urlencode
from kubernetes import client
from kubernetes.client import api_client
from kubernetes.client.api import core_v1_api
from kubernetes import client
from kubernetes.client.exceptions import ApiException
from rest_framework.generics import get_object_or_404
from common.utils import get_logger
from common.tree import TreeNode
from assets.models import SystemUser
from common.tree import TreeNode
from common.utils import get_logger
from .. import const
logger = get_logger(__file__)
@@ -23,7 +19,8 @@ class KubernetesClient:
self.url = url
self.token = token
def get_api(self):
@property
def api(self):
configuration = client.Configuration()
configuration.host = self.url
configuration.verify_ssl = False
@@ -32,63 +29,46 @@ class KubernetesClient:
api = core_v1_api.CoreV1Api(c)
return api
def get_namespace_list(self):
api = self.get_api()
namespace_list = []
for ns in api.list_namespace().items:
namespace_list.append(ns.metadata.name)
return namespace_list
def get_namespaces(self):
namespaces = []
resp = self.api.list_namespace()
for ns in resp.items:
namespaces.append(ns.metadata.name)
return namespaces
def get_services(self):
api = self.get_api()
ret = api.list_service_for_all_namespaces(watch=False)
for i in ret.items:
print("%s \t%s \t%s \t%s \t%s \n" % (
i.kind, i.metadata.namespace, i.metadata.name, i.spec.cluster_ip, i.spec.ports))
def get_pods(self, namespace):
pods = []
resp = self.api.list_namespaced_pod(namespace)
for pd in resp.items:
pods.append(pd.metadata.name)
return pods
def get_pod_info(self, namespace, pod):
api = self.get_api()
resp = api.read_namespaced_pod(namespace=namespace, name=pod)
return resp
def get_containers(self, namespace, pod_name):
containers = []
resp = self.api.read_namespaced_pod(pod_name, namespace)
for container in resp.spec.containers:
containers.append(container.name)
return containers
def get_pod_logs(self, namespace, pod):
api = self.get_api()
log_content = api.read_namespaced_pod_log(pod, namespace, pretty=True, tail_lines=200)
return log_content
@classmethod
def run(cls, asset, secret, tp='namespace'):
k8s_url = f'{asset.address}'
k8s = cls(k8s_url, secret)
func_name = f'get_{tp}s'
if hasattr(k8s, func_name):
return getattr(k8s, func_name)()
return []
def get_pods(self):
api = self.get_api()
try:
ret = api.list_pod_for_all_namespaces(watch=False, _request_timeout=(3, 3))
except MaxRetryError:
logger.warning('Kubernetes connection timed out')
return
except ApiException as e:
if e.status == 401:
logger.warning('Kubernetes User not authenticated')
else:
logger.warning(e)
return
data = {}
for i in ret.items:
namespace = i.metadata.namespace
pod_info = {
'pod_name': i.metadata.name,
'containers': [j.name for j in i.spec.containers]
}
if namespace in data:
data[namespace].append(pod_info)
else:
data[namespace] = [pod_info, ]
return data
@staticmethod
def get_kubernetes_data(app_id, system_user_id):
@classmethod
def get_kubernetes_data(cls, app_id, system_user_id, tp, *args):
from ..models import Application
app = get_object_or_404(Application, id=app_id)
system_user = get_object_or_404(SystemUser, id=system_user_id)
k8s = KubernetesClient(app.attrs['cluster'], system_user.token)
return k8s.get_pods()
k8s = cls(app.attrs['cluster'], system_user.token)
func_name = f'get_{tp}s'
if hasattr(k8s, func_name):
return getattr(k8s, func_name)(*args)
return []
class KubernetesTree:
@@ -118,11 +98,10 @@ class KubernetesTree:
)
return node
def as_namespace_pod_tree_node(self, name, meta, type, counts=0, is_container=False):
def as_namespace_pod_tree_node(self, name, meta, type, is_container=False):
from ..models import ApplicationTreeNodeMixin
i = ApplicationTreeNodeMixin.create_tree_id(self.tree_id, type, name)
meta.update({type: name})
name = name if is_container else f'{name}({counts})'
node = self.create_tree_node(
i, self.tree_id, name, type, meta, icon='cloud', is_container=is_container
)
@@ -157,30 +136,30 @@ class KubernetesTree:
system_user_id = parent_info.get('system_user_id')
tree_nodes = []
data = KubernetesClient.get_kubernetes_data(app_id, system_user_id)
if not data:
return tree_nodes
if pod_name:
for container in next(
filter(
lambda x: x['pod_name'] == pod_name, data[namespace]
)
)['containers']:
tp = 'container'
containers = KubernetesClient.get_kubernetes_data(
app_id, system_user_id, tp, namespace, pod_name
)
for container in containers:
container_node = self.as_namespace_pod_tree_node(
container, parent_info, 'container', is_container=True
container, parent_info, tp, is_container=True
)
tree_nodes.append(container_node)
elif namespace:
for pod in data[namespace]:
pod_nodes = self.as_namespace_pod_tree_node(
pod['pod_name'], parent_info, 'pod', len(pod['containers'])
tp = 'pod'
pods = KubernetesClient.get_kubernetes_data(app_id, system_user_id, tp, namespace)
for pod in pods:
pod_node = self.as_namespace_pod_tree_node(
pod, parent_info, tp
)
tree_nodes.append(pod_nodes)
tree_nodes.append(pod_node)
elif system_user_id:
for namespace, pods in data.items():
tp = 'namespace'
namespaces = KubernetesClient.get_kubernetes_data(app_id, system_user_id, tp)
for namespace in namespaces:
namespace_node = self.as_namespace_pod_tree_node(
namespace, parent_info, 'namespace', len(pods)
namespace, parent_info, tp
)
tree_nodes.append(namespace_node)
return tree_nodes

View File

@@ -1,28 +1,27 @@
# -*- coding: utf-8 -*-
#
import io
import os
import uuid
from hashlib import md5
import sshpubkeys
from django.conf import settings
from django.core.cache import cache
from django.db import models
from django.db.models import QuerySet
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.db.models import QuerySet
from common.utils import random_string, signer
from common.db import fields
from common.utils import random_string
from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty
)
from common.utils.encode import ssh_pubkey_gen
from common.validators import alphanumeric
from common.db import fields
from common.utils.encode import (
parse_ssh_public_key_str, parse_ssh_private_key_str
)
from orgs.mixins.models import OrgModelMixin
logger = get_logger(__file__)
@@ -64,16 +63,16 @@ class AuthMixin:
@property
def ssh_key_fingerprint(self):
public_key = None
if self.public_key:
public_key = self.public_key
elif self.private_key:
try:
public_key = ssh_pubkey_gen(private_key=self.private_key, password=self.password)
public_key = parse_ssh_public_key_str(self.private_key, password=self.password)
except IOError as e:
return str(e)
else:
if not public_key:
return ''
public_key_obj = sshpubkeys.SSHKey(public_key)
fingerprint = public_key_obj.hash_md5()
return fingerprint
@@ -88,24 +87,27 @@ class AuthMixin:
@property
def private_key_file(self):
if not self.private_key_obj:
if not self.private_key:
return None
private_key_str = parse_ssh_private_key_str(self.private_key,
password=self.password)
if not private_key_str:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path)
with open(key_path, 'w') as f:
f.write(private_key_str)
os.chmod(key_path, 0o400)
return key_path
def get_private_key(self):
if not self.private_key_obj:
if not self.private_key:
return None
string_io = io.StringIO()
self.private_key_obj.write_private_key(string_io)
private_key = string_io.getvalue()
return private_key
return parse_ssh_private_key_str(self.private_key,
password=self.password)
@property
def public_key_obj(self):
@@ -234,4 +236,3 @@ class BaseUser(OrgModelMixin, AuthMixin):
class Meta:
abstract = True

View File

@@ -1,24 +1,24 @@
# -*- coding: utf-8 -*-
#
from io import StringIO
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key
from common.drf.fields import EncryptedField
from assets.models import Type
from common.drf.fields import EncryptedField
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str, parse_ssh_public_key_str
from .utils import validate_password_for_ansible
class AuthSerializer(serializers.ModelSerializer):
password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024, label=_('Password'))
private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=16384, label=_('Private key'))
private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=16384,
label=_('Private key'))
def gen_keys(self, private_key=None, password=None):
if private_key is None:
return None, None
public_key = ssh_pubkey_gen(private_key=private_key, password=password)
public_key = parse_ssh_public_key_str(text=private_key, password=password)
return private_key, public_key
def save(self, **kwargs):
@@ -57,10 +57,7 @@ class AuthSerializerMixin(serializers.ModelSerializer):
if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error"))
private_key = ssh_private_key_gen(private_key, password=passphrase)
string_io = StringIO()
private_key.write_private_key(string_io)
private_key = string_io.getvalue()
private_key = parse_ssh_private_key_str(private_key, password=passphrase)
return private_key
def validate_public_key(self, public_key):

View File

@@ -1,16 +1,16 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.mixins.serializers import BulkSerializerMixin
from common.utils import ssh_pubkey_gen
from common.drf.fields import EncryptedField
from common.drf.serializers import SecretReadableMixin
from common.mixins.serializers import BulkSerializerMixin
from common.utils import parse_ssh_public_key_str
from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser, Asset
from .utils import validate_password_for_ansible
from .base import AuthSerializerMixin
from .utils import validate_password_for_ansible
from ..models import SystemUser, Asset
__all__ = [
'SystemUserSerializer', 'MiniSystemUserSerializer',
@@ -214,7 +214,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
elif attrs.get('private_key'):
private_key = attrs['private_key']
password = attrs.get('password')
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
public_key = parse_ssh_public_key_str(private_key, password=password)
attrs['public_key'] = public_key
return attrs

View File

@@ -1,6 +1,8 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
def validate_password_for_ansible(password):
""" 校验 Ansible 不支持的特殊字符 """
@@ -15,3 +17,9 @@ def validate_password_for_ansible(password):
if '"' in password:
raise serializers.ValidationError(_('Password can not contains `"` '))
def validate_ssh_key(ssh_key, passphrase=None):
valid = validate_ssh_private_key(ssh_key, password=passphrase)
if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error"))
return parse_ssh_private_key_str(ssh_key, passphrase)

View File

@@ -20,6 +20,7 @@ class TicketStatusApi(mixins.AuthMixin, APIView):
try:
self.check_user_login_confirm()
self.request.session['auth_third_party_done'] = 1
self.request.session.pop('auth_third_party_required', '')
return Response({"msg": "ok"})
except errors.LoginConfirmOtherError as e:
reason = e.msg

View File

@@ -2,6 +2,7 @@
#
import requests
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model
from django.utils.http import urlencode
from django.conf import settings
@@ -90,8 +91,12 @@ class OAuth2Backend(JMSModelBackend):
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
)
}
access_token_url = '{url}?{query}'.format(
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, query=urlencode(query_dict)
if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT:
separator = '&'
else:
separator = '?'
access_token_url = '{url}{separator}{query}'.format(
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, separator=separator, query=urlencode(query_dict)
)
token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower()
requests_func = getattr(requests, token_method, requests.get)
@@ -118,8 +123,12 @@ class OAuth2Backend(JMSModelBackend):
}
logger.debug(log_prompt.format('Get userinfo endpoint'))
userinfo_url = '{url}?{query}'.format(
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT,
if '?' in settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT:
separator = '&'
else:
separator = '?'
userinfo_url = '{url}{separator}{query}'.format(
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT, separator=separator,
query=urlencode(query_dict)
)
userinfo_response = requests.get(userinfo_url, headers=headers)

View File

@@ -26,8 +26,13 @@ class OAuth2AuthRequestView(View):
)
}
redirect_url = '{url}?{query}'.format(
if '?' in settings.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT:
separator = '&'
else:
separator = '?'
redirect_url = '{url}{separator}{query}'.format(
url=settings.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT,
separator=separator,
query=urlencode(query_dict)
)
logger.debug(log_prompt.format('Redirect login url'))

View File

@@ -88,7 +88,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
"""
@ssl_verification
def authenticate(self, request, nonce=None, **kwargs):
def authenticate(self, request, nonce=None, code_verifier=None, **kwargs):
""" Authenticates users in case of the OpenID Connect Authorization code flow. """
log_prompt = "Process authenticate [OIDCAuthCodeBackend]: {}"
logger.debug(log_prompt.format('start'))
@@ -134,6 +134,8 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
request, path=reverse(settings.AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME)
)
}
if settings.AUTH_OPENID_PKCE and code_verifier:
token_payload['code_verifier'] = code_verifier
if settings.AUTH_OPENID_CLIENT_AUTH_METHOD == 'client_secret_post':
token_payload.update({
'client_id': settings.AUTH_OPENID_CLIENT_ID,

View File

@@ -9,7 +9,10 @@
"""
import base64
import hashlib
import time
import secrets
from django.conf import settings
from django.contrib import auth
@@ -38,6 +41,19 @@ class OIDCAuthRequestView(View):
http_method_names = ['get', ]
@staticmethod
def gen_code_verifier(length=128):
# length range 43 ~ 128
return secrets.token_urlsafe(length-32)
@staticmethod
def gen_code_challenge(code_verifier, code_challenge_method):
if code_challenge_method == 'plain':
return code_verifier
h = hashlib.sha256(code_verifier.encode('ascii')).digest()
b = base64.urlsafe_b64encode(h)
return b.decode('ascii')[:-1]
def get(self, request):
""" Processes GET requests. """
@@ -56,6 +72,16 @@ class OIDCAuthRequestView(View):
)
})
if settings.AUTH_OPENID_PKCE:
code_verifier = self.gen_code_verifier()
code_challenge_method = settings.AUTH_OPENID_CODE_CHALLENGE_METHOD or 'S256'
code_challenge = self.gen_code_challenge(code_verifier, code_challenge_method)
authentication_request_params.update({
'code_challenge_method': code_challenge_method,
'code_challenge': code_challenge
})
request.session['oidc_auth_code_verifier'] = code_verifier
# States should be used! They are recommended in order to maintain state between the
# authentication request and the callback.
if settings.AUTH_OPENID_USE_STATE:
@@ -138,8 +164,9 @@ class OIDCAuthCallbackView(View):
# Authenticates the end-user.
next_url = request.session.get('oidc_auth_next_url', None)
code_verifier = request.session.get('oidc_auth_code_verifier', None)
logger.debug(log_prompt.format('Process authenticate'))
user = auth.authenticate(nonce=nonce, request=request)
user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier)
if user and user.is_valid:
logger.debug(log_prompt.format('Login: {}'.format(user)))
auth.login(self.request, user)

View File

@@ -61,6 +61,18 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
return response
if not request.session.get('auth_third_party_required'):
return response
white_urls = [
'jsi18n/', '/static/',
'login/guard', 'login/wait-confirm',
'login-confirm-ticket/status',
'settings/public/open',
'core/auth/login', 'core/auth/logout'
]
for url in white_urls:
if request.path.find(url) > -1:
return response
ip = get_request_ip(request)
try:
self.request = request
@@ -88,7 +100,6 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
guard_url = "%s?%s" % (guard_url, args)
response = redirect(guard_url)
finally:
request.session.pop('auth_third_party_required', '')
return response

View File

@@ -225,6 +225,7 @@ class MFAMixin:
self.request.session['auth_mfa_time'] = time.time()
self.request.session['auth_mfa_required'] = 0
self.request.session['auth_mfa_type'] = mfa_type
MFABlockUtils(self.request.user.username, self.get_request_ip()).clean_failed_count()
def clean_mfa_mark(self):
keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type']
@@ -369,7 +370,7 @@ class AuthACLMixin:
def check_user_login_confirm(self):
ticket = self.get_ticket()
if not ticket:
raise errors.LoginConfirmOtherError('', "Not found")
raise errors.LoginConfirmOtherError('', "Not found", '')
elif ticket.is_state(ticket.State.approved):
self.request.session["auth_confirm_required"] = ''
return
@@ -512,4 +513,14 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost
args = self.request.META.get('QUERY_STRING', '')
if args:
guard_url = "%s?%s" % (guard_url, args)
return redirect(guard_url)
response = redirect(guard_url)
self.set_browser_default_language_if_need(response)
return response
def set_browser_default_language_if_need(self, response):
# en, ja, zh-CN,zh;q=0.9
default_lang = self.request.headers.get('Accept-Language')
if 'zh' in default_lang:
default_lang = 'zh'
lang = response.cookies.get(settings.LANGUAGE_COOKIE_NAME) or default_lang
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)

View File

@@ -32,11 +32,14 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
return super().get(*args, **kwargs)
def form_valid(self, form):
from users.utils import MFABlockUtils
code = form.cleaned_data.get('code')
mfa_type = form.cleaned_data.get('mfa_type')
try:
self._do_check_user_mfa(code, mfa_type)
user, ip = self.get_user_from_session(), self.get_request_ip()
MFABlockUtils(user.username, ip).clean_failed_count()
return redirect_to_guard_view('mfa_ok')
except (errors.MFAFailedError, errors.BlockMFAError) as e:
form.add_error('code', e.msg)

View File

@@ -9,6 +9,10 @@ class FlowerService(BaseService):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@property
def db_file(self):
return os.path.join(BASE_DIR, 'data', 'flower')
@property
def cmd(self):
print("\n- Start Flower as Task Monitor")
@@ -20,11 +24,11 @@ class FlowerService(BaseService):
'-A', 'ops',
'flower',
'-logging=info',
'-db={}'.format(self.db_file),
'--url_prefix=/core/flower',
'--auto_refresh=False',
'--max_tasks=1000',
'--persistent=True',
'-db=/opt/jumpserver/data/flower.db',
'--state_save_interval=600000'
]
return cmd

View File

@@ -1,24 +1,23 @@
# -*- coding: utf-8 -*-
#
import re
import json
from six import string_types
import base64
import os
import time
import hashlib
import json
import os
import re
import time
from io import StringIO
from itertools import chain
import paramiko
import sshpubkeys
from cryptography.hazmat.primitives import serialization
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from itsdangerous import (
TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer,
BadSignature, SignatureExpired
)
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.fields.files import FileField
from six import string_types
from .http import http_date
@@ -69,22 +68,25 @@ class Signer(metaclass=Singleton):
return None
_supported_paramiko_ssh_key_types = (
paramiko.RSAKey,
paramiko.DSSKey,
paramiko.Ed25519Key,
paramiko.ECDSAKey,
)
def ssh_key_string_to_obj(text, password=None):
key = None
try:
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
else:
return key
try:
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
else:
return key
for ssh_key_type in _supported_paramiko_ssh_key_types:
if not issubclass(ssh_key_type, paramiko.PKey):
continue
try:
key = ssh_key_type.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
else:
return key
return key
@@ -98,7 +100,7 @@ def ssh_private_key_gen(private_key, password=None):
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
private_key = ssh_private_key_gen(private_key, password=password)
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
if not isinstance(private_key, _supported_paramiko_ssh_key_types):
raise IOError('Invalid private key')
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
@@ -137,17 +139,63 @@ def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', h
def validate_ssh_private_key(text, password=None):
if isinstance(text, bytes):
try:
text = text.decode("utf-8")
except UnicodeDecodeError:
return False
key = parse_ssh_private_key_str(text, password=password)
return bool(key)
key = ssh_key_string_to_obj(text, password=password)
if key is None:
return False
else:
return True
def parse_ssh_private_key_str(text: bytes, password=None) -> str:
private_key = _parse_ssh_private_key(text, password=password)
if private_key is None:
return ""
private_key_bytes = private_key.private_bytes(serialization.Encoding.PEM,
serialization.PrivateFormat.OpenSSH,
serialization.NoEncryption())
return private_key_bytes.decode('utf-8')
def parse_ssh_public_key_str(text: bytes = "", password=None) -> str:
private_key = _parse_ssh_private_key(text, password=password)
if private_key is None:
return ""
public_key_bytes = private_key.public_key().public_bytes(
serialization.Encoding.OpenSSH,
serialization.PublicFormat.OpenSSH)
return public_key_bytes.decode('utf-8')
def _parse_ssh_private_key(text, password=None):
"""
text: bytes
password: str
return:private key types:
ec.EllipticCurvePrivateKey,
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ed25519.Ed25519PrivateKey,
"""
if isinstance(text, str):
try:
text = text.encode("utf-8")
except UnicodeDecodeError:
return None
if password is not None:
if isinstance(password, str):
try:
password = password.encode("utf-8")
except UnicodeDecodeError:
return None
try:
if is_openssh_format_key(text):
return serialization.load_ssh_private_key(text, password=password)
return serialization.load_pem_private_key(text, password=password)
except (ValueError, TypeError):
pass
return None
def is_openssh_format_key(text: bytes):
return text.startswith(b"-----BEGIN OPENSSH PRIVATE KEY-----")
def validate_ssh_public_key(text):

View File

@@ -18,25 +18,35 @@ def random_ip():
return socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff)))
def random_string(length, lower=True, upper=True, digit=True, special_char=False):
chars = string.ascii_letters
if digit:
chars += string.digits
def random_string(length: int, lower=True, upper=True, digit=True, special_char=False):
args_names = ['lower', 'upper', 'digit', 'special_char']
args_values = [lower, upper, digit, special_char]
args_string = [string.ascii_lowercase, string.ascii_uppercase, string.digits, string_punctuation]
args_string_map = dict(zip(args_names, args_string))
kwargs = dict(zip(args_names, args_values))
kwargs_keys = list(kwargs.keys())
kwargs_values = list(kwargs.values())
args_true_count = len([i for i in kwargs_values if i])
assert any(kwargs_values), f'Parameters {kwargs_keys} must have at least one `True`'
assert length >= args_true_count, f'Expected length >= {args_true_count}, bug got {length}'
can_startswith_special_char = args_true_count == 1 and special_char
chars = ''.join([args_string_map[k] for k, v in kwargs.items() if v])
while True:
password = list(random.choice(chars) for i in range(length))
if upper and not any(c.upper() for c in password):
continue
if lower and not any(c.lower() for c in password):
continue
if digit and not any(c.isdigit() for c in password):
continue
break
if special_char:
spc = random.choice(string_punctuation)
i = random.choice(range(1, len(password)))
password[i] = spc
for k, v in kwargs.items():
if v and not (set(password) & set(args_string_map[k])):
# 没有包含指定的字符, retry
break
else:
if not can_startswith_special_char and password[0] in args_string_map['special_char']:
# 首位不能为特殊字符, retry
continue
else:
# 满足要求终止 while 循环
break
password = ''.join(password)
return password

View File

@@ -202,6 +202,7 @@ class Config(dict):
'REDIS_SSL_KEY': None,
'REDIS_SSL_CERT': None,
'REDIS_SSL_CA': None,
'REDIS_SSL_REQUIRED': 'none',
# Redis Sentinel
'REDIS_SENTINEL_HOSTS': '',
'REDIS_SENTINEL_PASSWORD': '',
@@ -269,6 +270,8 @@ class Config(dict):
'AUTH_OPENID_USER_ATTR_MAP': {
'name': 'name', 'username': 'preferred_username', 'email': 'email'
},
'AUTH_OPENID_PKCE': False,
'AUTH_OPENID_CODE_CHALLENGE_METHOD': 'S256',
# OpenID 新配置参数 (version >= 1.5.9)
'AUTH_OPENID_PROVIDER_ENDPOINT': 'https://oidc.example.com/',

View File

@@ -78,6 +78,8 @@ AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION
AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION
AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER
AUTH_OPENID_USER_ATTR_MAP = CONFIG.AUTH_OPENID_USER_ATTR_MAP
AUTH_OPENID_PKCE = CONFIG.AUTH_OPENID_PKCE
AUTH_OPENID_CODE_CHALLENGE_METHOD = CONFIG.AUTH_OPENID_CODE_CHALLENGE_METHOD
AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login'
AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback'
AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout'

View File

@@ -1,6 +1,9 @@
import os
import platform
from redis.sentinel import SentinelManagedSSLConnection
if platform.system() == 'Darwin' and platform.machine() == 'arm64':
import pymysql
@@ -195,7 +198,7 @@ DATABASES = {
}
}
DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'certs', 'db_ca.pem')
DB_CA_PATH = os.path.join(CERTS_DIR, 'db_ca.pem')
DB_USE_SSL = False
if CONFIG.DB_ENGINE.lower() == 'mysql':
DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'"
@@ -317,10 +320,19 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
'CLIENT_CLASS': 'django_redis.client.SentinelClient',
'SENTINELS': REDIS_SENTINELS, 'PASSWORD': CONFIG.REDIS_PASSWORD,
'SENTINEL_KWARGS': {
'ssl': REDIS_USE_SSL,
'ssl_cert_reqs': REDIS_SSL_REQUIRED,
"ssl_keyfile": REDIS_SSL_KEY,
"ssl_certfile": REDIS_SSL_CERT,
"ssl_ca_certs": REDIS_SSL_CA,
'password': REDIS_SENTINEL_PASSWORD,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT
}
})
if REDIS_USE_SSL:
REDIS_OPTIONS['CONNECTION_POOL_KWARGS'].update({
'connection_class': SentinelManagedSSLConnection
})
DJANGO_REDIS_CONNECTION_FACTORY = 'django_redis.pool.SentinelConnectionFactory'
else:
REDIS_LOCATION_NO_DB = '%(protocol)s://:%(password)s@%(host)s:%(port)s/{}' % {

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
#
import os
import ssl
from urllib.parse import urlencode
from .base import (
REDIS_SSL_CA, REDIS_SSL_CERT, REDIS_SSL_KEY, REDIS_SSL_REQUIRED, REDIS_USE_SSL,
REDIS_SENTINEL_SERVICE_NAME, REDIS_SENTINELS, REDIS_SENTINEL_PASSWORD,
REDIS_PROTOCOL, REDIS_SENTINEL_SERVICE_NAME, REDIS_SENTINELS, REDIS_SENTINEL_PASSWORD,
REDIS_SENTINEL_SOCKET_TIMEOUT
)
from ..const import CONFIG, PROJECT_DIR
@@ -81,41 +81,54 @@ BOOTSTRAP3 = {
}
# Django channels support websocket
if not REDIS_USE_SSL:
redis_ssl = None
else:
redis_ssl = ssl.SSLContext()
redis_ssl.check_hostname = bool(CONFIG.REDIS_SSL_REQUIRED)
if REDIS_SSL_CA:
redis_ssl.load_verify_locations(REDIS_SSL_CA)
if REDIS_SSL_CERT and REDIS_SSL_KEY:
redis_ssl.load_cert_chain(REDIS_SSL_CERT, REDIS_SSL_KEY)
REDIS_HOST = {
REDIS_LAYERS_HOST = {
'db': CONFIG.REDIS_DB_WS,
'password': CONFIG.REDIS_PASSWORD or None,
'ssl': redis_ssl,
}
REDIS_LAYERS_SSL_PARAMS = {}
if REDIS_USE_SSL:
REDIS_LAYERS_SSL_PARAMS.update({
'ssl': REDIS_USE_SSL,
'ssl_cert_reqs': REDIS_SSL_REQUIRED,
"ssl_keyfile": REDIS_SSL_KEY,
"ssl_certfile": REDIS_SSL_CERT,
"ssl_ca_certs": REDIS_SSL_CA
})
REDIS_LAYERS_HOST.update(REDIS_LAYERS_SSL_PARAMS)
if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
REDIS_HOST['sentinels'] = REDIS_SENTINELS
REDIS_HOST['master_name'] = REDIS_SENTINEL_SERVICE_NAME
REDIS_HOST['sentinel_kwargs'] = {
REDIS_LAYERS_HOST['sentinels'] = REDIS_SENTINELS
REDIS_LAYERS_HOST['master_name'] = REDIS_SENTINEL_SERVICE_NAME
REDIS_LAYERS_HOST['sentinel_kwargs'] = {
'password': REDIS_SENTINEL_PASSWORD,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT,
'ssl': REDIS_USE_SSL,
'ssl_cert_reqs': REDIS_SSL_REQUIRED,
"ssl_keyfile": REDIS_SSL_KEY,
"ssl_certfile": REDIS_SSL_CERT,
"ssl_ca_certs": REDIS_SSL_CA
}
else:
REDIS_HOST['address'] = (CONFIG.REDIS_HOST, CONFIG.REDIS_PORT)
# More info see: https://github.com/django/channels_redis/issues/334
# REDIS_LAYERS_HOST['address'] = (CONFIG.REDIS_HOST, CONFIG.REDIS_PORT)
REDIS_LAYERS_ADDRESS = '{protocol}://:{password}@{host}:{port}/{db}'.format(
protocol=REDIS_PROTOCOL, password=CONFIG.REDIS_PASSWORD,
host=CONFIG.REDIS_HOST, port=CONFIG.REDIS_PORT, db=CONFIG.REDIS_DB_WS
)
REDIS_LAYERS_SSL_PARAMS.pop('ssl', None)
REDIS_LAYERS_HOST['address'] = '{}?{}'.format(REDIS_LAYERS_ADDRESS, urlencode(REDIS_LAYERS_SSL_PARAMS))
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'common.cache.RedisChannelLayer',
'CONFIG': {
"hosts": [REDIS_HOST],
"hosts": [REDIS_LAYERS_HOST],
},
},
}
ASGI_APPLICATION = 'jumpserver.routing.application'
# Dump all celery log to here
@@ -132,13 +145,18 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
'master_name': REDIS_SENTINEL_SERVICE_NAME,
'sentinel_kwargs': {
'password': REDIS_SENTINEL_PASSWORD,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT,
'ssl': REDIS_USE_SSL,
'ssl_cert_reqs': REDIS_SSL_REQUIRED,
"ssl_keyfile": REDIS_SSL_KEY,
"ssl_certfile": REDIS_SSL_CERT,
"ssl_ca_certs": REDIS_SSL_CA
}
}
CELERY_BROKER_TRANSPORT_OPTIONS = CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS = SENTINEL_OPTIONS
else:
CELERY_BROKER_URL = CELERY_BROKER_URL_FORMAT % {
'protocol': 'rediss' if REDIS_USE_SSL else 'redis',
'protocol': REDIS_PROTOCOL,
'password': CONFIG.REDIS_PASSWORD,
'host': CONFIG.REDIS_HOST,
'port': CONFIG.REDIS_PORT,

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-17 17:34+0800\n"
"POT-Creation-Date: 2023-03-02 12:21+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"
@@ -24,13 +24,13 @@ msgstr "Acls"
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
#: applications/models/application.py:219 assets/models/asset.py:138
#: assets/models/base.py:175 assets/models/cluster.py:18
#: assets/models/base.py:177 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:27 assets/models/domain.py:23
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29
#: settings/models.py:33 settings/serializers/sms.py:6
#: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87
#: terminal/models/storage.py:27 terminal/models/task.py:16
#: terminal/models/storage.py:26 terminal/models/task.py:16
#: terminal/models/terminal.py:101 users/forms/profile.py:33
#: users/models/group.py:15 users/models/user.py:673
#: xpack/plugins/cloud/models.py:27
@@ -55,14 +55,14 @@ msgstr "アクティブ"
#: acls/models/base.py:32 applications/models/application.py:232
#: assets/models/asset.py:143 assets/models/asset.py:231
#: assets/models/backup.py:54 assets/models/base.py:180
#: assets/models/backup.py:54 assets/models/base.py:182
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:100 assets/models/domain.py:24
#: assets/models/domain.py:65 assets/models/group.py:23
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38
#: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97
#: terminal/models/storage.py:30 terminal/models/terminal.py:115
#: terminal/models/storage.py:29 terminal/models/terminal.py:115
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
#: users/models/group.py:16 users/models/user.py:712
#: xpack/plugins/change_auth_plan/models/base.py:44
@@ -155,7 +155,7 @@ msgid "Format for comma-delimited string, with * indicating a match all. "
msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。"
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:178
#: assets/models/gathered_user.py:15 audits/models.py:139
#: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260
@@ -186,7 +186,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:54
#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:58
msgid "IP"
msgstr "IP"
@@ -310,7 +310,7 @@ msgstr "カテゴリ"
#: assets/models/cmd_filter.py:86 assets/models/user.py:251
#: authentication/models.py:70 perms/models/application_permission.py:24
#: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:59 terminal/models/storage.py:145
#: terminal/models/storage.py:58 terminal/models/storage.py:147
#: tickets/models/comment.py:26 tickets/models/flow.py:57
#: tickets/models/ticket/apply_application.py:18
#: tickets/models/ticket/general.py:273
@@ -353,7 +353,7 @@ msgid "Type display"
msgstr "タイプ表示"
#: applications/serializers/application.py:105 assets/models/asset.py:230
#: assets/models/base.py:181 assets/models/cluster.py:26
#: assets/models/base.py:183 assets/models/cluster.py:26
#: assets/models/cmd_filter.py:53 assets/models/domain.py:26
#: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/account.py:18
@@ -367,7 +367,7 @@ msgstr "タイプ表示"
msgid "Date created"
msgstr "作成された日付"
#: applications/serializers/application.py:106 assets/models/base.py:182
#: applications/serializers/application.py:106 assets/models/base.py:184
#: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20
#: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29
#: assets/serializers/cmd_filter.py:49 common/db/models.py:117
@@ -650,7 +650,7 @@ msgstr "資産番号"
msgid "Labels"
msgstr "ラベル"
#: assets/models/asset.py:229 assets/models/base.py:183
#: assets/models/asset.py:229 assets/models/base.py:185
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:56
#: assets/models/cmd_filter.py:103 assets/models/group.py:21
#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71
@@ -786,32 +786,32 @@ msgstr "成功は"
msgid "Account backup execution"
msgstr "アカウントバックアップの実行"
#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5
#: assets/models/base.py:29 assets/tasks/const.py:51 audits/const.py:5
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
#: common/utils/ip/utils.py:84
msgid "Unknown"
msgstr "不明"
#: assets/models/base.py:31
#: assets/models/base.py:30
msgid "Ok"
msgstr "OK"
#: assets/models/base.py:32 audits/models.py:136
#: assets/models/base.py:31 audits/models.py:136
#: xpack/plugins/change_auth_plan/serializers/app.py:88
#: xpack/plugins/change_auth_plan/serializers/asset.py:199
#: xpack/plugins/cloud/const.py:41
msgid "Failed"
msgstr "失敗しました"
#: assets/models/base.py:38 assets/serializers/domain.py:47
#: assets/models/base.py:37 assets/serializers/domain.py:47
msgid "Connectivity"
msgstr "接続性"
#: assets/models/base.py:40 authentication/models.py:263
#: assets/models/base.py:39 authentication/models.py:263
msgid "Date verified"
msgstr "確認済みの日付"
#: assets/models/base.py:177 assets/serializers/base.py:15
#: assets/models/base.py:179 assets/serializers/base.py:14
#: assets/serializers/base.py:37 assets/serializers/system_user.py:29
#: audits/signal_handlers.py:58 authentication/confirm/password.py:9
#: authentication/forms.py:32
@@ -829,14 +829,14 @@ msgstr "確認済みの日付"
msgid "Password"
msgstr "パスワード"
#: assets/models/base.py:178 assets/serializers/base.py:41
#: assets/models/base.py:180 assets/serializers/base.py:41
#: xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key"
msgstr "SSH秘密鍵"
#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56
#: assets/models/base.py:181 xpack/plugins/change_auth_plan/models/asset.py:56
#: xpack/plugins/change_auth_plan/models/asset.py:126
#: xpack/plugins/change_auth_plan/models/asset.py:202
msgid "SSH public key"
@@ -954,7 +954,7 @@ msgstr "テストゲートウェイ"
msgid "Unable to connect to port {port} on {ip}"
msgstr "{ip} でポート {port} に接続できません"
#: assets/models/domain.py:134 authentication/middleware.py:75
#: assets/models/domain.py:134 authentication/middleware.py:87
#: xpack/plugins/cloud/providers/fc.py:48
msgid "Authentication failed"
msgstr "認証に失敗しました"
@@ -1192,7 +1192,7 @@ msgstr "ssh秘密鍵"
msgid "Key password"
msgstr "キーパスワード"
#: assets/serializers/base.py:58
#: assets/serializers/base.py:58 assets/serializers/utils.py:24
msgid "private key invalid or passphrase error"
msgstr "秘密鍵が無効またはpassphraseエラー"
@@ -1305,15 +1305,15 @@ msgstr "組織名"
msgid "Asset hostname"
msgstr "資産ホスト名"
#: assets/serializers/utils.py:11
#: assets/serializers/utils.py:13
msgid "Password can not contains `{{` "
msgstr "パスワードには '{{' を含まない"
#: assets/serializers/utils.py:14
#: assets/serializers/utils.py:16
msgid "Password can not contains `'` "
msgstr "パスワードには `'` を含まない"
#: assets/serializers/utils.py:16
#: assets/serializers/utils.py:18
msgid "Password can not contains `\"` "
msgstr "パスワードには `\"` を含まない"
@@ -1722,7 +1722,7 @@ msgid "Authentication"
msgstr "認証"
#: authentication/backends/custom.py:58
#: authentication/backends/oauth2/backends.py:158 authentication/models.py:158
#: authentication/backends/oauth2/backends.py:167 authentication/models.py:158
msgid "User invalid, disabled or expired"
msgstr "ユーザーが無効、無効、または期限切れです"
@@ -1937,15 +1937,15 @@ msgstr "本を飛ばすは拘束されていません"
msgid "Your password is invalid"
msgstr "パスワードが無効です"
#: authentication/errors/redirect.py:85 authentication/mixins.py:306
#: authentication/errors/redirect.py:85 authentication/mixins.py:307
msgid "Your password is too simple, please change it for security"
msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください"
#: authentication/errors/redirect.py:93 authentication/mixins.py:313
#: authentication/errors/redirect.py:93 authentication/mixins.py:314
msgid "You should to change your password before login"
msgstr "ログインする前にパスワードを変更する必要があります"
#: authentication/errors/redirect.py:101 authentication/mixins.py:320
#: authentication/errors/redirect.py:101 authentication/mixins.py:321
msgid "Your password has expired, please reset before logging in"
msgstr ""
"パスワードの有効期限が切れました。ログインする前にリセットしてください。"
@@ -2044,15 +2044,15 @@ msgstr "電話番号を設定して有効にする"
msgid "Clear phone number to disable"
msgstr "無効にする電話番号をクリアする"
#: authentication/middleware.py:76 settings/utils/ldap.py:652
#: authentication/middleware.py:88 settings/utils/ldap.py:652
msgid "Authentication failed (before login check failed): {}"
msgstr "認証に失敗しました (ログインチェックが失敗する前): {}"
#: authentication/mixins.py:256
#: authentication/mixins.py:257
msgid "The MFA type ({}) is not enabled"
msgstr "MFAタイプ ({}) が有効になっていない"
#: authentication/mixins.py:296
#: authentication/mixins.py:297
msgid "Please change your password"
msgstr "パスワードを変更してください"
@@ -2263,7 +2263,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:413 ops/tasks.py:145 ops/tasks.py:148
#: jumpserver/conf.py:416 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@@ -2747,11 +2747,11 @@ msgstr "特殊文字を含むべきではない"
msgid "The mobile phone number format is incorrect"
msgstr "携帯電話番号の形式が正しくありません"
#: jumpserver/conf.py:412
#: jumpserver/conf.py:415
msgid "Create account successfully"
msgstr "アカウントを正常に作成"
#: jumpserver/conf.py:414
#: jumpserver/conf.py:417
msgid "Your account has been created successfully"
msgstr "アカウントが正常に作成されました"
@@ -3603,7 +3603,7 @@ msgstr "CAS"
msgid "Enable CAS Auth"
msgstr "CAS 認証の有効化"
#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49
#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:54
msgid "Server url"
msgstr "サービス側アドレス"
@@ -3723,11 +3723,11 @@ msgstr "クライアントID"
msgid "Client Secret"
msgstr "クライアント秘密"
#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63
#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:68
msgid "Provider auth endpoint"
msgstr "認証エンドポイントアドレス"
#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66
#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:71
msgid "Provider token endpoint"
msgstr "プロバイダートークンエンドポイント"
@@ -3735,15 +3735,15 @@ msgstr "プロバイダートークンエンドポイント"
msgid "Client authentication method"
msgstr "クライアント認証方式"
#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72
#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:77
msgid "Provider userinfo endpoint"
msgstr "プロバイダーuserinfoエンドポイント"
#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75
#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:80
msgid "Provider end session endpoint"
msgstr "プロバイダーのセッション終了エンドポイント"
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:98
#: settings/serializers/auth/saml2.py:35
msgid "Always update user"
msgstr "常にユーザーを更新"
@@ -3772,51 +3772,59 @@ msgstr ""
"ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン"
"グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です"
#: settings/serializers/auth/oidc.py:46
#: settings/serializers/auth/oidc.py:41
msgid "Enable PKCE"
msgstr "启启PKCE"
#: settings/serializers/auth/oidc.py:43
msgid "Code challenge method"
msgstr "Code暗号化方式です"
#: settings/serializers/auth/oidc.py:51
msgid "Use Keycloak"
msgstr "Keycloakを使用する"
#: settings/serializers/auth/oidc.py:52
#: settings/serializers/auth/oidc.py:57
msgid "Realm name"
msgstr "レルム名"
#: settings/serializers/auth/oidc.py:58
#: settings/serializers/auth/oidc.py:63
msgid "Enable OPENID Auth"
msgstr "OIDC認証の有効化"
#: settings/serializers/auth/oidc.py:60
#: settings/serializers/auth/oidc.py:65
msgid "Provider endpoint"
msgstr "プロバイダーエンドポイント"
#: settings/serializers/auth/oidc.py:69
#: settings/serializers/auth/oidc.py:74
msgid "Provider jwks endpoint"
msgstr "プロバイダーjwksエンドポイント"
#: settings/serializers/auth/oidc.py:78
#: settings/serializers/auth/oidc.py:83
msgid "Provider sign alg"
msgstr "プロビダーサインalg"
#: settings/serializers/auth/oidc.py:81
#: settings/serializers/auth/oidc.py:86
msgid "Provider sign key"
msgstr "プロバイダ署名キー"
#: settings/serializers/auth/oidc.py:83
#: settings/serializers/auth/oidc.py:88
msgid "Scopes"
msgstr "スコープ"
#: settings/serializers/auth/oidc.py:85
#: settings/serializers/auth/oidc.py:90
msgid "Id token max age"
msgstr "IDトークンの最大年齢"
#: settings/serializers/auth/oidc.py:88
#: settings/serializers/auth/oidc.py:93
msgid "Id token include claims"
msgstr "IDトークンにはクレームが含まれます"
#: settings/serializers/auth/oidc.py:90
#: settings/serializers/auth/oidc.py:95
msgid "Use state"
msgstr "使用状態"
#: settings/serializers/auth/oidc.py:91
#: settings/serializers/auth/oidc.py:96
msgid "Use nonce"
msgstr "Nonceを使用"
@@ -4731,8 +4739,8 @@ msgstr "期限切れです。"
#, python-format
msgid ""
"\n"
" Your password has expired, please click <a href="
"\"%(user_password_update_url)s\"> this link </a> update password.\n"
" Your password has expired, please click <a "
"href=\"%(user_password_update_url)s\"> this link </a> update password.\n"
" "
msgstr ""
"\n"
@@ -4753,34 +4761,34 @@ msgid ""
" "
msgstr ""
"\n"
" クリックしてください <a href=\"%(user_password_update_url)s"
"\"> リンク </a> パスワードの更新\n"
" クリックしてください <a "
"href=\"%(user_password_update_url)s\"> リンク </a> パスワードの更新\n"
" "
#: templates/_message.html:43
#, python-format
msgid ""
"\n"
" Your information was incomplete. Please click <a href="
"\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" Your information was incomplete. Please click <a "
"href=\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" "
msgstr ""
"\n"
" あなたの情報が不完全なので、クリックしてください。 <a href="
"\"%(first_login_url)s\"> リンク </a> 補完\n"
" あなたの情報が不完全なので、クリックしてください。 <a "
"href=\"%(first_login_url)s\"> リンク </a> 補完\n"
" "
#: templates/_message.html:56
#, python-format
msgid ""
"\n"
" Your ssh public key not set or expired. Please click <a href="
"\"%(user_pubkey_update)s\"> this link </a>to update\n"
" Your ssh public key not set or expired. Please click <a "
"href=\"%(user_pubkey_update)s\"> this link </a>to update\n"
" "
msgstr ""
"\n"
" SSHキーが設定されていないか無効になっている場合は、 <a href="
"\"%(user_pubkey_update)s\"> リンク </a> 更新\n"
" SSHキーが設定されていないか無効になっている場合は、 <a "
"href=\"%(user_pubkey_update)s\"> リンク </a> 更新\n"
" "
#: templates/_mfa_login_field.html:28
@@ -5000,7 +5008,7 @@ msgid "RDP Port"
msgstr "RDP ポート"
#: terminal/models/endpoint.py:27 terminal/models/endpoint.py:95
#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38
#: terminal/serializers/endpoint.py:61 terminal/serializers/storage.py:38
#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80
#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98
msgid "Endpoint"
@@ -5139,15 +5147,15 @@ msgstr "スレッド"
msgid "Boot Time"
msgstr "ブート時間"
#: terminal/models/storage.py:29
#: terminal/models/storage.py:28
msgid "Default storage"
msgstr "デフォルトのストレージ"
#: terminal/models/storage.py:139 terminal/models/terminal.py:109
#: terminal/models/storage.py:141 terminal/models/terminal.py:109
msgid "Command storage"
msgstr "コマンドストレージ"
#: terminal/models/storage.py:199 terminal/models/terminal.py:110
#: terminal/models/storage.py:201 terminal/models/terminal.py:110
msgid "Replay storage"
msgstr "再生ストレージ"
@@ -5191,15 +5199,15 @@ msgstr "レベル"
msgid "Batch danger command alert"
msgstr "一括危険コマンド警告"
#: terminal/serializers/endpoint.py:14
#: terminal/serializers/endpoint.py:15
msgid "Magnus listen db port"
msgstr "Magnus がリッスンするデータベース ポート"
#: terminal/serializers/endpoint.py:17
#: terminal/serializers/endpoint.py:18
msgid "Magnus Listen port range"
msgstr "Magnus がリッスンするポート範囲"
#: terminal/serializers/endpoint.py:19
#: terminal/serializers/endpoint.py:20
msgid ""
"The range of ports that Magnus listens on is modified in the configuration "
"file"
@@ -5207,10 +5215,15 @@ msgstr "Magnus がリッスンするポート範囲を構成ファイルで変
#: terminal/serializers/endpoint.py:51
msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels"
"Format for comma-delimited string, with * indicating a match all. 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<br>If asset IP addresses under different endpoints "
"conflict, use asset labels"
msgstr ""
"異なるエンドポイントの下に競合するアセットIPがある場合は、アセットタグを使用"
"して実装します"
"コンマ区切り文字列の形式。* はすべて一致することを示します。例: "
"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<br>異なるエンドポイントの下に競合するアセットIPがある場合"
"は、アセットタグを使用して実装します"
#: terminal/serializers/session.py:15 terminal/serializers/session.py:42
msgid "Terminal display"
@@ -5334,7 +5347,7 @@ msgstr "見つかりません"
msgid "view"
msgstr "表示"
#: terminal/utils/db_port_mapper.py:65
#: terminal/utils/db_port_mapper.py:78
msgid ""
"No available port is matched. The number of databases may have exceeded the "
"number of ports open to the database agent service, Contact the "
@@ -5344,7 +5357,7 @@ msgstr ""
"サービスによって開かれたポートの数を超えた可能性があります。さらにポートを開"
"くには、管理者に連絡してください。"
#: terminal/utils/db_port_mapper.py:91
#: terminal/utils/db_port_mapper.py:104
msgid ""
"No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file."
@@ -5352,7 +5365,7 @@ msgstr ""
"使用できるポートがありません。設定ファイルで Magnus がリッスンするポート数の"
"制限を確認して変更してください. "
#: terminal/utils/db_port_mapper.py:93
#: terminal/utils/db_port_mapper.py:106
msgid "All available port count: {}, Already use port count: {}"
msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}"
@@ -5444,19 +5457,19 @@ msgstr ""
"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: "
"{}"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "Change field"
msgstr "フィールドを変更"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "Before change"
msgstr "変更前"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "After change"
msgstr "変更後"
#: tickets/handlers/base.py:96
#: tickets/handlers/base.py:98
msgid "{} {} the ticket"
msgstr "{} {} チケット"
@@ -7071,3 +7084,9 @@ msgstr "究極のエディション"
#: xpack/plugins/license/models.py:77
msgid "Community edition"
msgstr "コミュニティ版"
#~ msgid ""
#~ "If asset IP addresses under different endpoints conflict, use asset labels"
#~ msgstr ""
#~ "異なるエンドポイントの下に競合するアセットIPがある場合は、アセットタグを使"
#~ "用して実装します"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-17 17:34+0800\n"
"POT-Creation-Date: 2023-03-02 12:21+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"
@@ -23,13 +23,13 @@ msgstr "访问控制"
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
#: applications/models/application.py:219 assets/models/asset.py:138
#: assets/models/base.py:175 assets/models/cluster.py:18
#: assets/models/base.py:177 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:27 assets/models/domain.py:23
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29
#: settings/models.py:33 settings/serializers/sms.py:6
#: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87
#: terminal/models/storage.py:27 terminal/models/task.py:16
#: terminal/models/storage.py:26 terminal/models/task.py:16
#: terminal/models/terminal.py:101 users/forms/profile.py:33
#: users/models/group.py:15 users/models/user.py:673
#: xpack/plugins/cloud/models.py:27
@@ -54,14 +54,14 @@ msgstr "激活中"
#: acls/models/base.py:32 applications/models/application.py:232
#: assets/models/asset.py:143 assets/models/asset.py:231
#: assets/models/backup.py:54 assets/models/base.py:180
#: assets/models/backup.py:54 assets/models/base.py:182
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:100 assets/models/domain.py:24
#: assets/models/domain.py:65 assets/models/group.py:23
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38
#: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97
#: terminal/models/storage.py:30 terminal/models/terminal.py:115
#: terminal/models/storage.py:29 terminal/models/terminal.py:115
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
#: users/models/group.py:16 users/models/user.py:712
#: xpack/plugins/change_auth_plan/models/base.py:44
@@ -154,7 +154,7 @@ msgid "Format for comma-delimited string, with * indicating a match all. "
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:178
#: assets/models/gathered_user.py:15 audits/models.py:139
#: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260
@@ -184,7 +184,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:54
#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:58
msgid "IP"
msgstr "IP"
@@ -305,7 +305,7 @@ msgstr "类别"
#: assets/models/cmd_filter.py:86 assets/models/user.py:251
#: authentication/models.py:70 perms/models/application_permission.py:24
#: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:59 terminal/models/storage.py:145
#: terminal/models/storage.py:58 terminal/models/storage.py:147
#: tickets/models/comment.py:26 tickets/models/flow.py:57
#: tickets/models/ticket/apply_application.py:18
#: tickets/models/ticket/general.py:273
@@ -348,7 +348,7 @@ msgid "Type display"
msgstr "类型名称"
#: applications/serializers/application.py:105 assets/models/asset.py:230
#: assets/models/base.py:181 assets/models/cluster.py:26
#: assets/models/base.py:183 assets/models/cluster.py:26
#: assets/models/cmd_filter.py:53 assets/models/domain.py:26
#: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/account.py:18
@@ -362,7 +362,7 @@ msgstr "类型名称"
msgid "Date created"
msgstr "创建日期"
#: applications/serializers/application.py:106 assets/models/base.py:182
#: applications/serializers/application.py:106 assets/models/base.py:184
#: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20
#: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29
#: assets/serializers/cmd_filter.py:49 common/db/models.py:117
@@ -643,7 +643,7 @@ msgstr "资产编号"
msgid "Labels"
msgstr "标签管理"
#: assets/models/asset.py:229 assets/models/base.py:183
#: assets/models/asset.py:229 assets/models/base.py:185
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:56
#: assets/models/cmd_filter.py:103 assets/models/group.py:21
#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71
@@ -779,32 +779,32 @@ msgstr "是否成功"
msgid "Account backup execution"
msgstr "账号备份执行"
#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5
#: assets/models/base.py:29 assets/tasks/const.py:51 audits/const.py:5
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
#: common/utils/ip/utils.py:84
msgid "Unknown"
msgstr "未知"
#: assets/models/base.py:31
#: assets/models/base.py:30
msgid "Ok"
msgstr "成功"
#: assets/models/base.py:32 audits/models.py:136
#: assets/models/base.py:31 audits/models.py:136
#: xpack/plugins/change_auth_plan/serializers/app.py:88
#: xpack/plugins/change_auth_plan/serializers/asset.py:199
#: xpack/plugins/cloud/const.py:41
msgid "Failed"
msgstr "失败"
#: assets/models/base.py:38 assets/serializers/domain.py:47
#: assets/models/base.py:37 assets/serializers/domain.py:47
msgid "Connectivity"
msgstr "可连接性"
#: assets/models/base.py:40 authentication/models.py:263
#: assets/models/base.py:39 authentication/models.py:263
msgid "Date verified"
msgstr "校验日期"
#: assets/models/base.py:177 assets/serializers/base.py:15
#: assets/models/base.py:179 assets/serializers/base.py:14
#: assets/serializers/base.py:37 assets/serializers/system_user.py:29
#: audits/signal_handlers.py:58 authentication/confirm/password.py:9
#: authentication/forms.py:32
@@ -822,14 +822,14 @@ msgstr "校验日期"
msgid "Password"
msgstr "密码"
#: assets/models/base.py:178 assets/serializers/base.py:41
#: assets/models/base.py:180 assets/serializers/base.py:41
#: xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key"
msgstr "SSH密钥"
#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56
#: assets/models/base.py:181 xpack/plugins/change_auth_plan/models/asset.py:56
#: xpack/plugins/change_auth_plan/models/asset.py:126
#: xpack/plugins/change_auth_plan/models/asset.py:202
msgid "SSH public key"
@@ -947,7 +947,7 @@ msgstr "测试网关"
msgid "Unable to connect to port {port} on {ip}"
msgstr "无法连接到 {ip} 上的端口 {port}"
#: assets/models/domain.py:134 authentication/middleware.py:75
#: assets/models/domain.py:134 authentication/middleware.py:87
#: xpack/plugins/cloud/providers/fc.py:48
msgid "Authentication failed"
msgstr "认证失败"
@@ -1182,7 +1182,7 @@ msgstr "ssh私钥"
msgid "Key password"
msgstr "密钥密码"
#: assets/serializers/base.py:58
#: assets/serializers/base.py:58 assets/serializers/utils.py:24
msgid "private key invalid or passphrase error"
msgstr "密钥不合法或密钥密码错误"
@@ -1295,15 +1295,15 @@ msgstr "组织名称"
msgid "Asset hostname"
msgstr "资产主机名"
#: assets/serializers/utils.py:11
#: assets/serializers/utils.py:13
msgid "Password can not contains `{{` "
msgstr "密码不能包含 `{{` 字符"
#: assets/serializers/utils.py:14
#: assets/serializers/utils.py:16
msgid "Password can not contains `'` "
msgstr "密码不能包含 `'` 字符"
#: assets/serializers/utils.py:16
#: assets/serializers/utils.py:18
msgid "Password can not contains `\"` "
msgstr "密码不能包含 `\"` 字符"
@@ -1706,7 +1706,7 @@ msgid "Authentication"
msgstr "认证"
#: authentication/backends/custom.py:58
#: authentication/backends/oauth2/backends.py:158 authentication/models.py:158
#: authentication/backends/oauth2/backends.py:167 authentication/models.py:158
msgid "User invalid, disabled or expired"
msgstr "用户无效,已禁用或已过期"
@@ -1913,15 +1913,15 @@ msgstr "没有绑定飞书"
msgid "Your password is invalid"
msgstr "您的密码无效"
#: authentication/errors/redirect.py:85 authentication/mixins.py:306
#: authentication/errors/redirect.py:85 authentication/mixins.py:307
msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改"
#: authentication/errors/redirect.py:93 authentication/mixins.py:313
#: authentication/errors/redirect.py:93 authentication/mixins.py:314
msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码"
#: authentication/errors/redirect.py:101 authentication/mixins.py:320
#: authentication/errors/redirect.py:101 authentication/mixins.py:321
msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录"
@@ -2018,15 +2018,15 @@ msgstr "设置手机号码启用"
msgid "Clear phone number to disable"
msgstr "清空手机号码禁用"
#: authentication/middleware.py:76 settings/utils/ldap.py:652
#: authentication/middleware.py:88 settings/utils/ldap.py:652
msgid "Authentication failed (before login check failed): {}"
msgstr "认证失败(登录前检查失败): {}"
#: authentication/mixins.py:256
#: authentication/mixins.py:257
msgid "The MFA type ({}) is not enabled"
msgstr "该 MFA ({}) 方式没有启用"
#: authentication/mixins.py:296
#: authentication/mixins.py:297
msgid "Please change your password"
msgstr "请修改密码"
@@ -2233,7 +2233,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:413 ops/tasks.py:145 ops/tasks.py:148
#: jumpserver/conf.py:416 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@@ -2708,11 +2708,11 @@ msgstr "不能包含特殊字符"
msgid "The mobile phone number format is incorrect"
msgstr "手机号格式不正确"
#: jumpserver/conf.py:412
#: jumpserver/conf.py:415
msgid "Create account successfully"
msgstr "创建账号成功"
#: jumpserver/conf.py:414
#: jumpserver/conf.py:417
msgid "Your account has been created successfully"
msgstr "你的账号已创建成功"
@@ -3555,7 +3555,7 @@ msgstr "CAS"
msgid "Enable CAS Auth"
msgstr "启用 CAS 认证"
#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49
#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:54
msgid "Server url"
msgstr "服务端地址"
@@ -3675,11 +3675,11 @@ msgstr "客户端 ID"
msgid "Client Secret"
msgstr "客户端密钥"
#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63
#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:68
msgid "Provider auth endpoint"
msgstr "授权端点地址"
#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66
#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:71
msgid "Provider token endpoint"
msgstr "token 端点地址"
@@ -3687,15 +3687,15 @@ msgstr "token 端点地址"
msgid "Client authentication method"
msgstr "客户端认证方式"
#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72
#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:77
msgid "Provider userinfo endpoint"
msgstr "用户信息端点地址"
#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75
#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:80
msgid "Provider end session endpoint"
msgstr "注销会话端点地址"
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:98
#: settings/serializers/auth/saml2.py:35
msgid "Always update user"
msgstr "总是更新用户信息"
@@ -3724,51 +3724,59 @@ msgstr ""
"用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上username, name,"
"email 是jumpserver的用户需要属性"
#: settings/serializers/auth/oidc.py:46
#: settings/serializers/auth/oidc.py:41
msgid "Enable PKCE"
msgstr "启用 PKCE"
#: settings/serializers/auth/oidc.py:43
msgid "Code challenge method"
msgstr "Code加密方式"
#: settings/serializers/auth/oidc.py:51
msgid "Use Keycloak"
msgstr "使用 Keycloak"
#: settings/serializers/auth/oidc.py:52
#: settings/serializers/auth/oidc.py:57
msgid "Realm name"
msgstr "域"
#: settings/serializers/auth/oidc.py:58
#: settings/serializers/auth/oidc.py:63
msgid "Enable OPENID Auth"
msgstr "启用 OIDC 认证"
#: settings/serializers/auth/oidc.py:60
#: settings/serializers/auth/oidc.py:65
msgid "Provider endpoint"
msgstr "端点地址"
#: settings/serializers/auth/oidc.py:69
#: settings/serializers/auth/oidc.py:74
msgid "Provider jwks endpoint"
msgstr "jwks 端点地址"
#: settings/serializers/auth/oidc.py:78
#: settings/serializers/auth/oidc.py:83
msgid "Provider sign alg"
msgstr "签名算法"
#: settings/serializers/auth/oidc.py:81
#: settings/serializers/auth/oidc.py:86
msgid "Provider sign key"
msgstr "签名 Key"
#: settings/serializers/auth/oidc.py:83
#: settings/serializers/auth/oidc.py:88
msgid "Scopes"
msgstr "连接范围"
#: settings/serializers/auth/oidc.py:85
#: settings/serializers/auth/oidc.py:90
msgid "Id token max age"
msgstr "令牌有效时间"
#: settings/serializers/auth/oidc.py:88
#: settings/serializers/auth/oidc.py:93
msgid "Id token include claims"
msgstr "声明"
#: settings/serializers/auth/oidc.py:90
#: settings/serializers/auth/oidc.py:95
msgid "Use state"
msgstr "使用状态"
#: settings/serializers/auth/oidc.py:91
#: settings/serializers/auth/oidc.py:96
msgid "Use nonce"
msgstr "临时使用"
@@ -4652,13 +4660,13 @@ msgstr "过期。"
#, python-format
msgid ""
"\n"
" Your password has expired, please click <a href="
"\"%(user_password_update_url)s\"> this link </a> update password.\n"
" Your password has expired, please click <a "
"href=\"%(user_password_update_url)s\"> this link </a> update password.\n"
" "
msgstr ""
"\n"
" 您的密码已经过期,请点击 <a href="
"\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n"
" 您的密码已经过期,请点击 <a "
"href=\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n"
" "
#: templates/_message.html:30
@@ -4682,8 +4690,8 @@ msgstr ""
#, python-format
msgid ""
"\n"
" Your information was incomplete. Please click <a href="
"\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" Your information was incomplete. Please click <a "
"href=\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" "
msgstr ""
"\n"
@@ -4695,13 +4703,13 @@ msgstr ""
#, python-format
msgid ""
"\n"
" Your ssh public key not set or expired. Please click <a href="
"\"%(user_pubkey_update)s\"> this link </a>to update\n"
" Your ssh public key not set or expired. Please click <a "
"href=\"%(user_pubkey_update)s\"> this link </a>to update\n"
" "
msgstr ""
"\n"
" 您的SSH密钥没有设置或已失效请点击 <a href="
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" 您的SSH密钥没有设置或已失效请点击 <a "
"href=\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" "
#: templates/_mfa_login_field.html:28
@@ -4914,7 +4922,7 @@ msgid "RDP Port"
msgstr "RDP 端口"
#: terminal/models/endpoint.py:27 terminal/models/endpoint.py:95
#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38
#: terminal/serializers/endpoint.py:61 terminal/serializers/storage.py:38
#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80
#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98
msgid "Endpoint"
@@ -5053,15 +5061,15 @@ msgstr "线程数"
msgid "Boot Time"
msgstr "运行时间"
#: terminal/models/storage.py:29
#: terminal/models/storage.py:28
msgid "Default storage"
msgstr "默认存储"
#: terminal/models/storage.py:139 terminal/models/terminal.py:109
#: terminal/models/storage.py:141 terminal/models/terminal.py:109
msgid "Command storage"
msgstr "命令存储"
#: terminal/models/storage.py:199 terminal/models/terminal.py:110
#: terminal/models/storage.py:201 terminal/models/terminal.py:110
msgid "Replay storage"
msgstr "录像存储"
@@ -5105,15 +5113,15 @@ msgstr "级别"
msgid "Batch danger command alert"
msgstr "批量危险命令告警"
#: terminal/serializers/endpoint.py:14
#: terminal/serializers/endpoint.py:15
msgid "Magnus listen db port"
msgstr "Magnus 监听的数据库端口"
#: terminal/serializers/endpoint.py:17
#: terminal/serializers/endpoint.py:18
msgid "Magnus Listen port range"
msgstr "Magnus 监听的端口范围"
#: terminal/serializers/endpoint.py:19
#: terminal/serializers/endpoint.py:20
msgid ""
"The range of ports that Magnus listens on is modified in the configuration "
"file"
@@ -5121,8 +5129,14 @@ msgstr "请在配置文件中修改 Magnus 监听的端口范围"
#: terminal/serializers/endpoint.py:51
msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels"
msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现"
"Format for comma-delimited string, with * indicating a match all. 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<br>If asset IP addresses under different endpoints "
"conflict, use asset labels"
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<br>如果不同端点"
"下的资产 IP 有冲突,使用资产标签实现"
#: terminal/serializers/session.py:15 terminal/serializers/session.py:42
msgid "Terminal display"
@@ -5246,7 +5260,7 @@ msgstr "没有发现"
msgid "view"
msgstr "查看"
#: terminal/utils/db_port_mapper.py:65
#: terminal/utils/db_port_mapper.py:78
msgid ""
"No available port is matched. The number of databases may have exceeded the "
"number of ports open to the database agent service, Contact the "
@@ -5255,13 +5269,13 @@ msgstr ""
"未匹配到可用端口,数据库的数量可能已经超过数据库代理服务开放的端口数量,请联"
"系管理员开放更多端口。"
#: terminal/utils/db_port_mapper.py:91
#: terminal/utils/db_port_mapper.py:104
msgid ""
"No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file."
msgstr "没有端口可以使用,检查并修改配置文件中 Magnus 监听的端口数量限制。"
#: terminal/utils/db_port_mapper.py:93
#: terminal/utils/db_port_mapper.py:106
msgid "All available port count: {}, Already use port count: {}"
msgstr "所有可用端口数量:{},已使用端口数量:{}"
@@ -5351,19 +5365,19 @@ msgid ""
msgstr ""
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "Change field"
msgstr "变更字段"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "Before change"
msgstr "变更前"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "After change"
msgstr "变更后"
#: tickets/handlers/base.py:96
#: tickets/handlers/base.py:98
msgid "{} {} the ticket"
msgstr "{} {} 工单"
@@ -6961,3 +6975,7 @@ msgstr "旗舰版"
#: xpack/plugins/license/models.py:77
msgid "Community edition"
msgstr "社区版"
#~ msgid ""
#~ "If asset IP addresses under different endpoints conflict, use asset labels"
#~ msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现"

View File

@@ -38,6 +38,11 @@ class CommonSettingSerializer(serializers.Serializer):
help_text=_('User attr map present how to map OpenID user attr to '
'jumpserver, username,name,email is jumpserver attr')
)
AUTH_OPENID_PKCE = serializers.BooleanField(required=False, label=_('Enable PKCE'))
AUTH_OPENID_CODE_CHALLENGE_METHOD = serializers.ChoiceField(
default='S256', label=_('Code challenge method'),
choices=(('S256', 'HS256'), ('plain', 'Plain'))
)
class KeycloakSettingSerializer(CommonSettingSerializer):

View File

@@ -19,7 +19,6 @@ from .terminal import Terminal
from .command import Command
from .. import const
logger = get_logger(__file__)
@@ -37,10 +36,10 @@ class CommonStorageModelMixin(models.Model):
def set_to_default(self):
self.is_default = True
self.save()
self.__class__.objects.select_for_update()\
.filter(is_default=True)\
.exclude(id=self.id)\
self.save(update_fields=['is_default'])
self.__class__.objects.select_for_update() \
.filter(is_default=True) \
.exclude(id=self.id) \
.update(is_default=False)
@classmethod
@@ -128,7 +127,10 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin):
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
super().save()
super().save(
force_insert=force_insert, force_update=force_update,
using=using, update_fields=update_fields
)
if self.type in TYPE_ENGINE_MAPPING:
engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type])

View File

@@ -1,6 +1,7 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from common.drf.serializers import BulkModelSerializer
from common.utils import lazyproperty
from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
from ..utils import db_port_manager
from ..models import Endpoint, EndpointRule
@@ -46,9 +47,12 @@ class EndpointSerializer(BulkModelSerializer):
class EndpointRuleSerializer(BulkModelSerializer):
_ip_group_help_text = '{} <br> {}'.format(
ip_group_help_text,
_('If asset IP addresses under different endpoints conflict, use asset labels')
_ip_group_help_text = _(
'Format for comma-delimited string, with * indicating a match all. '
'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'
'<br>'
'If asset IP addresses under different endpoints conflict, use asset labels'
)
ip_group = serializers.ListField(
default=['*'], label=_('IP'), help_text=_ip_group_help_text,

View File

@@ -16,10 +16,10 @@ logger = get_logger(__file__)
@receiver(django_ready)
def init_db_port_mapper(sender, **kwargs):
def check_db_port_mapper(sender, **kwargs):
logger.info('Init db port mapper')
try:
db_port_manager.init()
db_port_manager.check()
except (ProgrammingError,) as e:
pass

View File

@@ -35,9 +35,22 @@ class DBPortManager(object):
def magnus_listen_port_range(self):
return settings.MAGNUS_PORTS
def init(self):
@staticmethod
def fetch_dbs():
with tmp_to_root_org():
db_ids = Application.objects.filter(category=AppCategory.db).values_list('id', flat=True)
dbs = Application.objects.filter(category=AppCategory.db)
return dbs
def check(self):
dbs = self.fetch_dbs()
for db in dbs:
port = self.get_port_by_db(db, raise_exception=False)
if not port:
self.add(db)
def init(self):
dbs = self.fetch_dbs()
db_ids = dbs.values_list('id', flat=True)
db_ids = [str(i) for i in db_ids]
mapper = dict(zip(self.all_available_ports, list(db_ids)))
self.set_mapper(mapper)

View File

@@ -1,3 +1,5 @@
from html import escape
from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
@@ -96,11 +98,19 @@ class BaseHandler:
approve_info = _('{} {} the ticket').format(user_display, state_display)
context = self._diff_prev_approve_context(state)
context.update({'approve_info': approve_info})
body = self.reject_html_script(
render_to_string('tickets/ticket_approve_diff.html', context)
)
data = {
'body': render_to_string('tickets/ticket_approve_diff.html', context),
'body': body,
'user': user,
'user_display': str(user),
'type': 'state',
'state': state
}
return self.ticket.comments.create(**data)
@staticmethod
def reject_html_script(unsafe_html):
safe_html = escape(unsafe_html)
return safe_html

View File

@@ -63,7 +63,7 @@ jsonfield2==4.0.0.post0
geoip2==4.5.0
ipip-ipdb==1.6.1
# Django environment
Django==3.2.16
Django==3.2.15
django-bootstrap3==14.2.0
django-filter==2.4.0
django-formtools==2.2

View File

@@ -26,7 +26,7 @@ connection_params = {
if settings.REDIS_USE_SSL:
connection_params['ssl'] = settings.REDIS_USE_SSL
connection_params['ssl_cert_reqs'] = settings.REDIS_SSL_REQUIRED
connection_params['ssl_cert_reqs'] = settings.REDIS_SSL_REQUIRED
connection_params['ssl_keyfile'] = settings.REDIS_SSL_KEY
connection_params['ssl_certfile'] = settings.REDIS_SSL_CERT
connection_params['ssl_ca_certs'] = settings.REDIS_SSL_CA
@@ -39,6 +39,11 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
connection_params['sentinels'] = REDIS_SENTINELS
sentinel_client = Sentinel(
**connection_params, sentinel_kwargs={
'ssl': settings.REDIS_USE_SSL,
'ssl_cert_reqs': settings.REDIS_SSL_REQUIRED,
'ssl_keyfile': settings.REDIS_SSL_KEY,
'ssl_certfile': settings.REDIS_SSL_CERT,
'ssl_ca_certs': settings.REDIS_SSL_CA,
'password': REDIS_SENTINEL_PASSWORD,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT
}