diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index 15e7dae14..af1e27c2d 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -288,15 +288,14 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): raise ValueError("Remote App not has asset attr") def get_target_ip(self): + target_ip = '' if self.category_remote_app: asset = self.get_remote_app_asset() - target_ip = asset.ip + target_ip = asset.ip if asset else target_ip elif self.category_cloud: target_ip = self.attrs.get('cluster') elif self.category_db: target_ip = self.attrs.get('host') - else: - target_ip = '' return target_ip diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 4afa7a306..9e16d31ba 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -484,7 +484,8 @@ class UserConnectionTokenViewSet( tp = 'app' if application else 'asset' data = { "id": token, 'secret': secret, - 'type': tp, 'protocol': system_user.protocol + 'type': tp, 'protocol': system_user.protocol, + 'expire_time': self.get_token_ttl(token), } return Response(data, status=201) diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index 9481c3ff6..42bbb27cb 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -1,5 +1,7 @@ from django.shortcuts import redirect, reverse +from django.utils.deprecation import MiddlewareMixin from django.http import HttpResponse +from django.conf import settings class MFAMiddleware: @@ -34,3 +36,15 @@ class MFAMiddleware: url = reverse('authentication:login-mfa') + '?_=middleware' return redirect(url) + + +class SessionCookieMiddleware(MiddlewareMixin): + + @staticmethod + def process_response(request, response: HttpResponse): + key = settings.SESSION_COOKIE_NAME_PREFIX_KEY + value = settings.SESSION_COOKIE_NAME_PREFIX + if request.COOKIES.get(key) == value: + return response + response.set_cookie(key, value) + return response diff --git a/apps/common/cache.py b/apps/common/cache.py index b988a8673..67aba9191 100644 --- a/apps/common/cache.py +++ b/apps/common/cache.py @@ -1,5 +1,7 @@ import time +from channels_redis.core import RedisChannelLayer as _RedisChannelLayer + from common.utils.lock import DistributedLock from common.utils.connection import get_redis_client from common.utils import lazyproperty @@ -216,3 +218,29 @@ class CacheValueDesc: def to_internal_value(self, value): return self.field_type.field_type(value) + + +class RedisChannelLayer(_RedisChannelLayer): + async def _brpop_with_clean(self, index, channel, timeout): + cleanup_script = """ + local backed_up = redis.call('ZRANGE', ARGV[2], 0, -1, 'WITHSCORES') + for i = #backed_up, 1, -2 do + redis.call('ZADD', ARGV[1], backed_up[i], backed_up[i - 1]) + end + redis.call('DEL', ARGV[2]) + """ + backup_queue = self._backup_channel_name(channel) + async with self.connection(index) as connection: + # 部分云厂商的 Redis 此操作会报错(不支持,比如阿里云有限制) + try: + await connection.eval(cleanup_script, keys=[], args=[channel, backup_queue]) + except: + pass + result = await connection.bzpopmin(channel, timeout=timeout) + + if result is not None: + _, member, timestamp = result + await connection.zadd(backup_queue, float(timestamp), member) + else: + member = None + return member diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 3cc8a067d..28ee9d8e9 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -157,6 +157,7 @@ class Config(dict): 'DEFAULT_EXPIRED_YEARS': 70, 'SESSION_COOKIE_DOMAIN': None, 'CSRF_COOKIE_DOMAIN': None, + 'SESSION_COOKIE_NAME_PREFIX': None, 'SESSION_COOKIE_AGE': 3600 * 24, 'SESSION_EXPIRE_AT_BROWSER_CLOSE': False, 'LOGIN_URL': reverse_lazy('authentication:login'), diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index a4441711a..b654a0365 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -94,6 +94,7 @@ MIDDLEWARE = [ 'authentication.backends.oidc.middleware.OIDCRefreshIDTokenMiddleware', 'authentication.backends.cas.middleware.CASMiddleware', 'authentication.middleware.MFAMiddleware', + 'authentication.middleware.SessionCookieMiddleware', 'simple_history.middleware.HistoryRequestMiddleware', ] @@ -128,6 +129,20 @@ LOGIN_URL = reverse_lazy('authentication:login') SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN CSRF_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN + +# 设置 SESSION_COOKIE_NAME_PREFIX_KEY +# 解决 不同域 session csrf cookie 获取混乱问题 +SESSION_COOKIE_NAME_PREFIX_KEY = 'SESSION_COOKIE_NAME_PREFIX' +SESSION_COOKIE_NAME_PREFIX = CONFIG.SESSION_COOKIE_NAME_PREFIX +if SESSION_COOKIE_NAME_PREFIX is not None: + pass +elif SESSION_COOKIE_DOMAIN is not None: + SESSION_COOKIE_NAME_PREFIX = SESSION_COOKIE_DOMAIN.split('.')[0] +else: + SESSION_COOKIE_NAME_PREFIX = 'jms_' +CSRF_COOKIE_NAME = '{}csrftoken'.format(SESSION_COOKIE_NAME_PREFIX) +SESSION_COOKIE_NAME = '{}sessionid'.format(SESSION_COOKIE_NAME_PREFIX) + SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 自定义的配置,SESSION_EXPIRE_AT_BROWSER_CLOSE 始终为 True, 下面这个来控制是否强制关闭后过期 cookie diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 11aa0ba16..59446b9de 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -96,12 +96,12 @@ else: CHANNEL_LAYERS = { 'default': { - 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'BACKEND': 'common.cache.RedisChannelLayer', 'CONFIG': { "hosts": [{ 'address': (CONFIG.REDIS_HOST, CONFIG.REDIS_PORT), 'db': CONFIG.REDIS_DB_WS, - 'password': CONFIG.REDIS_PASSWORD, + 'password': CONFIG.REDIS_PASSWORD or None, 'ssl': context }], }, diff --git a/apps/terminal/api/endpoint.py b/apps/terminal/api/endpoint.py index 14efb738f..d612db4a8 100644 --- a/apps/terminal/api/endpoint.py +++ b/apps/terminal/api/endpoint.py @@ -8,6 +8,7 @@ from assets.models import Asset from orgs.utils import tmp_to_root_org from applications.models import Application from terminal.models import Session +from common.permissions import IsValidUser from ..models import Endpoint, EndpointRule from .. import serializers @@ -20,9 +21,6 @@ class EndpointViewSet(JMSBulkModelViewSet): search_fields = filterset_fields serializer_class = serializers.EndpointSerializer queryset = Endpoint.objects.all() - rbac_perms = { - 'smart': 'terminal.view_endpoint' - } @staticmethod def get_target_ip(request): @@ -57,7 +55,7 @@ class EndpointViewSet(JMSBulkModelViewSet): target_ip = instance.get_target_ip() return target_ip - @action(methods=['get'], detail=False, url_path='smart') + @action(methods=['get'], detail=False, permission_classes=[IsValidUser], url_path='smart') def smart(self, request, *args, **kwargs): protocol = request.GET.get('protocol') if not protocol: