feat: support jdmc proxy

(cherry picked from commit 0319ee5d53bcd6a6962ce9b5dba0e7b24b5e16db)
This commit is contained in:
Bai
2026-03-05 19:05:08 +08:00
parent 964401944f
commit 0f8ef23860
7 changed files with 127 additions and 0 deletions

View File

@@ -8,5 +8,6 @@ from .encode import *
from .http import *
from .ip import *
from .jumpserver import *
from .proxy import *
from .random import *
from .translate import *

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
#
import requests
import requests_unixsocket
from django.http import HttpResponse, QueryDict
def _get_headers(environ):
headers = {}
for key, value in environ.items():
if key.startswith('HTTP_') and key != 'HTTP_HOST':
headers[key[5:].replace('_', '-')] = value
elif key in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
headers[key.replace('_', '-')] = value
return headers
def unix_socket_proxy_view(
request, url, requests_args=None, rewrite_location=None,
error_prefix='Proxy request failed'
):
requests_args = (requests_args or {}).copy()
headers = _get_headers(request.META)
params = request.GET.copy()
if 'headers' not in requests_args:
requests_args['headers'] = {}
if 'data' not in requests_args:
requests_args['data'] = request.body
if 'params' not in requests_args:
requests_args['params'] = QueryDict('', mutable=True)
headers.update(requests_args['headers'])
params.update(requests_args['params'])
for key in list(headers.keys()):
if key.lower() == 'content-length':
del headers[key]
requests_args['headers'] = headers
requests_args['params'] = params
try:
with requests_unixsocket.Session() as session:
upstream_response = session.request(request.method, url, **requests_args)
except requests.exceptions.RequestException as exc:
return HttpResponse(f'{error_prefix}: {exc}', status=502)
response = HttpResponse(upstream_response.content, status=upstream_response.status_code)
excluded_headers = {
'connection', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers', 'transfer-encoding',
'upgrade', 'content-encoding', 'content-length',
}
for key, value in upstream_response.headers.items():
if key.lower() in excluded_headers:
continue
if key.lower() == 'location' and callable(rewrite_location):
value = rewrite_location(value)
response[key] = value
return response

View File

@@ -746,6 +746,10 @@ class Config(dict):
'OAUTH2_PROVIDER_ACCESS_TOKEN_EXPIRE_SECONDS': 60 * 60,
'OAUTH2_PROVIDER_REFRESH_TOKEN_EXPIRE_SECONDS': 60 * 60 * 24 * 7,
'VENDOR': 'jumpserver',
# JDMC
'JDMC_ENABLED': False,
'JDMC_SOCK_PATH': '',
}
old_config_map = {

View File

@@ -1,6 +1,11 @@
# -*- coding: utf-8 -*-
#
from pathlib import Path
<<<<<<< HEAD
=======
import os
from urllib.parse import quote
>>>>>>> 0319ee5d5 (feat: support jdmc proxy)
from .base import TEMPLATES, STATIC_DIR
from ..const import CONFIG
@@ -275,3 +280,8 @@ VENDOR = CONFIG.VENDOR
VENDOR_TEMPLATES_DIR = Path(STATIC_DIR) / VENDOR
if Path(VENDOR_TEMPLATES_DIR).is_dir():
TEMPLATES[0]['DIRS'].insert(0, VENDOR_TEMPLATES_DIR)
# JDMC
JDMC_ENABLED = CONFIG.JDMC_ENABLED
JDMC_SOCK_PATH = CONFIG.JDMC_SOCK_PATH
JDMC_BASE_URL = f"http+unix://{quote(JDMC_SOCK_PATH, safe='')}"

View File

@@ -109,5 +109,10 @@ if os.environ.get('DEBUG_TOOLBAR', False):
path('__debug__/', include('debug_toolbar.urls')),
]
if settings.JDMC_ENABLED:
urlpatterns += [
re_path(r'jdmc/(?P<subpath>.*)$', views.jdmc_proxy_view, name='jdmc-proxy'),
]
handler404 = 'jumpserver.views.handler404'
handler500 = 'jumpserver.views.handler500'

View File

@@ -5,3 +5,4 @@ from .error_views import *
from .index import *
from .other import *
from .swagger import *
from .jdmc import *

View File

@@ -0,0 +1,44 @@
from django.conf import settings
from django.http import HttpResponse
from django.contrib.auth.views import redirect_to_login
from django.views.decorators.csrf import csrf_exempt
from urllib.parse import quote, urlsplit, urlunsplit
from common.utils.proxy import unix_socket_proxy_view
__all__ = ['jdmc_proxy_view']
def _rewrite_location(location):
if not location:
return location
if location.startswith('http+unix://'):
parsed = urlsplit(location)
return urlunsplit(('', '', parsed.path, parsed.query, parsed.fragment))
return location
@csrf_exempt
def jdmc_proxy_view(request, subpath=''):
if not request.user.is_authenticated or not request.user.is_superuser:
return redirect_to_login(request.get_full_path(), settings.LOGIN_URL)
upstream_url = f"{settings.JDMC_BASE_URL}{request.path}"
requests_args = {
'headers': {
'X-Forwarded-Proto': request.scheme,
'X-Forwarded-Host': request.get_host(),
},
'allow_redirects': False,
'timeout': (5, 60),
}
if request.method in {'GET', 'HEAD'}:
requests_args['data'] = None
return unix_socket_proxy_view(
request=request,
url=upstream_url,
requests_args=requests_args,
rewrite_location=_rewrite_location,
error_prefix='JDMC proxy failed',
)