mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-03-18 11:02:09 +00:00
feat: support jdmc proxy
(cherry picked from commit 0319ee5d53bcd6a6962ce9b5dba0e7b24b5e16db)
This commit is contained in:
@@ -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 *
|
||||
|
||||
62
apps/common/utils/proxy.py
Normal file
62
apps/common/utils/proxy.py
Normal 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
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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='')}"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -5,3 +5,4 @@ from .error_views import *
|
||||
from .index import *
|
||||
from .other import *
|
||||
from .swagger import *
|
||||
from .jdmc import *
|
||||
|
||||
44
apps/jumpserver/views/jdmc.py
Normal file
44
apps/jumpserver/views/jdmc.py
Normal 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',
|
||||
)
|
||||
Reference in New Issue
Block a user