mirror of https://github.com/jumpserver/jumpserver
feat: 添加Endpoint (#8041)
* feat: add Endpoint EndpointRule EndpointProtocol model * feat: add Endpoint EndpointRule EndpointProtocol API * feat: modify protocols field * feat: 修改序列类 * feat: 获取connect-url连接地址 * feat: 获取connect-url连接地址 * feat: 优化后台获取smart-endpoint逻辑 * feat: 优化后台获取smart-endpoint逻辑 * feat: 删除配置KOKO、XRDP、MAGNUS * feat: 删除配置KOKO、XRDP、MAGNUS * feat: 修改翻译 * feat: 修改smart endpoint * feat: 修改翻译 * feat: smart API 添加token解析 * feat: 删除 smart serializer * feat: 修改迁移逻辑 * feat: 解决冲突 * feat: 修改匹配 endpoint Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>pull/8048/head
parent
4cf90df17c
commit
f481463c64
|
@ -247,6 +247,14 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
|
|||
def category_remote_app(self):
|
||||
return self.category == const.AppCategory.remote_app.value
|
||||
|
||||
@property
|
||||
def category_cloud(self):
|
||||
return self.category == const.AppCategory.cloud.value
|
||||
|
||||
@property
|
||||
def category_db(self):
|
||||
return self.category == const.AppCategory.db.value
|
||||
|
||||
def get_rdp_remote_app_setting(self):
|
||||
from applications.serializers.attrs import get_serializer_class_by_application_type
|
||||
if not self.category_remote_app:
|
||||
|
@ -279,6 +287,18 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
|
|||
if raise_exception:
|
||||
raise ValueError("Remote App not has asset attr")
|
||||
|
||||
def get_target_ip(self):
|
||||
if self.category_remote_app:
|
||||
asset = self.get_remote_app_asset()
|
||||
target_ip = asset.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
|
||||
|
||||
|
||||
class ApplicationUser(SystemUser):
|
||||
class Meta:
|
||||
|
|
|
@ -235,6 +235,9 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
|
|||
def __str__(self):
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
||||
def get_target_ip(self):
|
||||
return self.ip
|
||||
|
||||
def set_admin_user_relation(self):
|
||||
from .authbook import AuthBook
|
||||
if not self.admin_user:
|
||||
|
|
|
@ -31,12 +31,13 @@ from perms.models.base import Action
|
|||
from perms.utils.application.permission import get_application_actions
|
||||
from perms.utils.asset.permission import get_asset_actions
|
||||
from common.const.http import PATCH
|
||||
from terminal.models import EndpointRule
|
||||
from ..serializers import (
|
||||
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
__all__ = ['UserConnectionTokenViewSet']
|
||||
__all__ = ['UserConnectionTokenViewSet', 'TokenCacheMixin']
|
||||
|
||||
|
||||
class ClientProtocolMixin:
|
||||
|
@ -51,6 +52,17 @@ class ClientProtocolMixin:
|
|||
request: Request
|
||||
get_serializer: Callable
|
||||
create_token: Callable
|
||||
get_serializer_context: Callable
|
||||
|
||||
def get_smart_endpoint(self, protocol, asset=None, application=None):
|
||||
if asset:
|
||||
target_ip = asset.get_target_ip()
|
||||
elif application:
|
||||
target_ip = application.get_target_ip()
|
||||
else:
|
||||
target_ip = ''
|
||||
endpoint = EndpointRule.match_endpoint(target_ip, protocol, self.request)
|
||||
return endpoint
|
||||
|
||||
def get_request_resource(self, serializer):
|
||||
asset = serializer.validated_data.get('asset')
|
||||
|
@ -122,10 +134,10 @@ class ClientProtocolMixin:
|
|||
options['screen mode id:i'] = '2' if full_screen else '1'
|
||||
|
||||
# RDP Server 地址
|
||||
address = settings.TERMINAL_RDP_ADDR
|
||||
if not address or address == 'localhost:3389':
|
||||
address = self.request.get_host().split(':')[0] + ':3389'
|
||||
options['full address:s'] = address
|
||||
endpoint = self.get_smart_endpoint(
|
||||
protocol='rdp', asset=asset, application=application
|
||||
)
|
||||
options['full address:s'] = f'{endpoint.host}:{endpoint.rdp_port}'
|
||||
# 用户名
|
||||
options['username:s'] = '{}|{}'.format(user.username, token)
|
||||
if system_user.ad_domain:
|
||||
|
@ -169,9 +181,12 @@ class ClientProtocolMixin:
|
|||
else:
|
||||
name = '*'
|
||||
|
||||
endpoint = self.get_smart_endpoint(
|
||||
protocol='ssh', asset=asset, application=application
|
||||
)
|
||||
content = {
|
||||
'ip': settings.TERMINAL_KOKO_HOST,
|
||||
'port': str(settings.TERMINAL_KOKO_SSH_PORT),
|
||||
'ip': endpoint.host,
|
||||
'port': endpoint.ssh_port,
|
||||
'username': f'JMS-{token}',
|
||||
'password': secret
|
||||
}
|
||||
|
@ -345,6 +360,7 @@ class SecretDetailMixin:
|
|||
|
||||
|
||||
class TokenCacheMixin:
|
||||
""" endpoint smart view 用到此类来解析token中的资产、应用 """
|
||||
CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}'
|
||||
|
||||
def get_token_cache_key(self, token):
|
||||
|
|
|
@ -4,7 +4,7 @@ import json
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from ..utils import signer, crypto
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ __all__ = [
|
|||
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
|
||||
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
|
||||
'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField',
|
||||
'EncryptJsonDictCharField',
|
||||
'EncryptJsonDictCharField', 'PortField'
|
||||
]
|
||||
|
||||
|
||||
|
@ -180,3 +180,13 @@ class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
|
|||
class EncryptJsonDictCharField(EncryptMixin, JsonDictCharField):
|
||||
pass
|
||||
|
||||
|
||||
class PortField(models.IntegerField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.update({
|
||||
'blank': False,
|
||||
'null': False,
|
||||
'validators': [MinValueValidator(0), MaxValueValidator(65535)]
|
||||
})
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
|
|
@ -310,16 +310,12 @@ class Config(dict):
|
|||
'TERMINAL_HOST_KEY': '',
|
||||
'TERMINAL_TELNET_REGEX': '',
|
||||
'TERMINAL_COMMAND_STORAGE': {},
|
||||
'TERMINAL_RDP_ADDR': lambda: urlparse(settings.SITE_URL).hostname + ':3389',
|
||||
'XRDP_ENABLED': True,
|
||||
'TERMINAL_KOKO_HOST': lambda: urlparse(settings.SITE_URL).hostname,
|
||||
'TERMINAL_KOKO_SSH_PORT': 2222,
|
||||
|
||||
# 未来废弃(当下迁移会用)
|
||||
'TERMINAL_RDP_ADDR': '',
|
||||
# 保留(Luna还在用)
|
||||
'TERMINAL_MAGNUS_ENABLED': True,
|
||||
'TERMINAL_MAGNUS_HOST': lambda: urlparse(settings.SITE_URL).hostname,
|
||||
'TERMINAL_MAGNUS_MYSQL_PORT': 33060,
|
||||
'TERMINAL_MAGNUS_MARIADB_PORT': 33061,
|
||||
'TERMINAL_MAGNUS_POSTGRE_PORT': 54320,
|
||||
# 保留(Luna还在用)
|
||||
'XRDP_ENABLED': True,
|
||||
|
||||
# 安全配置
|
||||
'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启
|
||||
|
|
|
@ -140,9 +140,6 @@ CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS = CONFIG.CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS
|
|||
|
||||
XRDP_ENABLED = CONFIG.XRDP_ENABLED
|
||||
|
||||
TERMINAL_KOKO_HOST = CONFIG.TERMINAL_KOKO_HOST
|
||||
TERMINAL_KOKO_SSH_PORT = CONFIG.TERMINAL_KOKO_SSH_PORT
|
||||
|
||||
# SMS enabled
|
||||
SMS_ENABLED = CONFIG.SMS_ENABLED
|
||||
SMS_BACKEND = CONFIG.SMS_BACKEND
|
||||
|
@ -170,11 +167,3 @@ ANNOUNCEMENT = CONFIG.ANNOUNCEMENT
|
|||
# help
|
||||
HELP_DOCUMENT_URL = CONFIG.HELP_DOCUMENT_URL
|
||||
HELP_SUPPORT_URL = CONFIG.HELP_SUPPORT_URL
|
||||
|
||||
# Magnus
|
||||
MAGNUS_ENABLED = CONFIG.MAGNUS_ENABLED
|
||||
TERMINAL_MAGNUS_HOST = CONFIG.TERMINAL_MAGNUS_HOST
|
||||
TERMINAL_MAGNUS_ENABLED = CONFIG.TERMINAL_MAGNUS_ENABLED
|
||||
TERMINAL_MAGNUS_MYSQL_PORT = CONFIG.TERMINAL_MAGNUS_MYSQL_PORT
|
||||
TERMINAL_MAGNUS_MARIADB_PORT = CONFIG.TERMINAL_MAGNUS_MARIADB_PORT
|
||||
TERMINAL_MAGNUS_POSTGRE_PORT = CONFIG.TERMINAL_MAGNUS_POSTGRE_PORT
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:097c6d06ed8dcf2e1807560b6eb52d98cba31f25fe8d67ce4315668c150ca6b8
|
||||
size 129989
|
||||
oid sha256:70685e92cbf84f4178224a44fd84eb884a0acfb9749200541ea6655a9a397a72
|
||||
size 125019
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3d6a0d40534209f3ffab0b0ecfab7ec82f137156fc8e7b19ce1711036d14aeca
|
||||
size 107709
|
||||
oid sha256:9c13775875a335e3c8dbc7f666af622c5aa12050100b15e616c210e8e3043e38
|
||||
size 103490
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -64,13 +64,6 @@ class PublicSettingApi(generics.RetrieveAPIView):
|
|||
"AUTH_FEISHU": settings.AUTH_FEISHU,
|
||||
# Terminal
|
||||
"XRDP_ENABLED": settings.XRDP_ENABLED,
|
||||
"TERMINAL_KOKO_HOST": settings.TERMINAL_KOKO_HOST,
|
||||
"TERMINAL_KOKO_SSH_PORT": settings.TERMINAL_KOKO_SSH_PORT,
|
||||
"TERMINAL_MAGNUS_ENABLED": settings.TERMINAL_MAGNUS_ENABLED,
|
||||
"TERMINAL_MAGNUS_HOST": settings.TERMINAL_MAGNUS_HOST,
|
||||
"TERMINAL_MAGNUS_MYSQL_PORT": settings.TERMINAL_MAGNUS_MYSQL_PORT,
|
||||
"TERMINAL_MAGNUS_MARIADB_PORT": settings.TERMINAL_MAGNUS_MARIADB_PORT,
|
||||
"TERMINAL_MAGNUS_POSTGRE_PORT": settings.TERMINAL_MAGNUS_POSTGRE_PORT,
|
||||
# Announcement
|
||||
"ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED,
|
||||
"ANNOUNCEMENT": settings.ANNOUNCEMENT,
|
||||
|
|
|
@ -33,33 +33,5 @@ class TerminalSettingSerializer(serializers.Serializer):
|
|||
help_text=_("The login success message varies with devices. "
|
||||
"if you cannot log in to the device through Telnet, set this parameter")
|
||||
)
|
||||
TERMINAL_RDP_ADDR = serializers.CharField(
|
||||
required=False, label=_("RDP address"), max_length=1024, allow_blank=True,
|
||||
help_text=_('RDP visit address, eg: dev.jumpserver.org:3389')
|
||||
)
|
||||
XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP"))
|
||||
|
||||
TERMINAL_KOKO_HOST = serializers.CharField(
|
||||
required=False, label=_("Koko host"), max_length=1024
|
||||
)
|
||||
TERMINAL_KOKO_SSH_PORT = serializers.CharField(
|
||||
required=False, label=_("Koko ssh port"), max_length=1024
|
||||
)
|
||||
|
||||
TERMINAL_MAGNUS_ENABLED = serializers.BooleanField(label=_("Enable database proxy"))
|
||||
TERMINAL_MAGNUS_HOST = serializers.CharField(
|
||||
required=False, label=_("Database proxy host"), max_length=1024, allow_blank=True,
|
||||
help_text=_('Database proxy host, eg: dev.jumpserver.org')
|
||||
)
|
||||
TERMINAL_MAGNUS_MYSQL_PORT = serializers.IntegerField(
|
||||
required=False, label=_("MySQL port"), default=33060,
|
||||
help_text=_('MySQL protocol listen port')
|
||||
)
|
||||
TERMINAL_MAGNUS_MARIADB_PORT = serializers.IntegerField(
|
||||
required=False, label=_("MariaDB port"), default=33061,
|
||||
help_text=_('MariaDB protocol listen port')
|
||||
)
|
||||
TERMINAL_MAGNUS_POSTGRE_PORT = serializers.IntegerField(
|
||||
required=False, label=_("PostgreSQL port"), default=54320,
|
||||
help_text=_('PostgreSQL protocol listen port')
|
||||
)
|
||||
XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP"))
|
||||
|
|
|
@ -7,3 +7,4 @@ from .task import *
|
|||
from .storage import *
|
||||
from .status import *
|
||||
from .sharing import *
|
||||
from .endpoint import *
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from common.drf.api import JMSBulkModelViewSet
|
||||
from common.utils import get_object_or_none
|
||||
from django.shortcuts import get_object_or_404
|
||||
from assets.models import Asset
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from applications.models import Application
|
||||
from terminal.models import Session
|
||||
from ..models import Endpoint, EndpointRule
|
||||
from .. import serializers
|
||||
|
||||
|
||||
__all__ = ['EndpointViewSet', 'EndpointRuleViewSet']
|
||||
|
||||
|
||||
class EndpointViewSet(JMSBulkModelViewSet):
|
||||
filterset_fields = ('name', 'host')
|
||||
search_fields = filterset_fields
|
||||
serializer_class = serializers.EndpointSerializer
|
||||
queryset = Endpoint.objects.all()
|
||||
rbac_perms = {
|
||||
'smart': 'terminal.view_endpoint'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_target_ip(request):
|
||||
# 用来方便测试
|
||||
target_ip = request.GET.get('target_ip')
|
||||
if target_ip:
|
||||
return target_ip
|
||||
|
||||
asset_id = request.GET.get('asset_id')
|
||||
app_id = request.GET.get('app_id')
|
||||
session_id = request.GET.get('session_id')
|
||||
token = request.GET.get('token')
|
||||
if token:
|
||||
from authentication.api.connection_token import TokenCacheMixin as TokenUtil
|
||||
value = TokenUtil().get_token_from_cache(token)
|
||||
if value:
|
||||
if value.get('type') == 'asset':
|
||||
asset_id = value.get('asset')
|
||||
else:
|
||||
app_id = value.get('application')
|
||||
if asset_id:
|
||||
pk, model = asset_id, Asset
|
||||
elif app_id:
|
||||
pk, model = app_id, Application
|
||||
elif session_id:
|
||||
pk, model = session_id, Session
|
||||
else:
|
||||
return ''
|
||||
|
||||
with tmp_to_root_org():
|
||||
instance = get_object_or_404(model, pk=pk)
|
||||
target_ip = instance.get_target_ip()
|
||||
return target_ip
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='smart')
|
||||
def smart(self, request, *args, **kwargs):
|
||||
protocol = request.GET.get('protocol')
|
||||
if not protocol:
|
||||
return Response(
|
||||
data={'error': _('Not found protocol query params')},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
target_ip = self.get_target_ip(request)
|
||||
endpoint = EndpointRule.match_endpoint(target_ip, protocol, request)
|
||||
serializer = self.get_serializer(endpoint)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class EndpointRuleViewSet(JMSBulkModelViewSet):
|
||||
filterset_fields = ('name',)
|
||||
search_fields = filterset_fields
|
||||
serializer_class = serializers.EndpointRuleSerializer
|
||||
queryset = EndpointRule.objects.all()
|
|
@ -0,0 +1,86 @@
|
|||
# Generated by Django 3.1.14 on 2022-04-12 07:39
|
||||
|
||||
import common.fields.model
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def migrate_endpoints(apps, schema_editor):
|
||||
endpoint_data = {
|
||||
'id': '00000000-0000-0000-0000-000000000001',
|
||||
'name': 'Default',
|
||||
'host': '',
|
||||
'https_port': 0,
|
||||
'http_port': 0,
|
||||
'created_by': 'System'
|
||||
}
|
||||
|
||||
if settings.XRDP_ENABLED:
|
||||
xrdp_addr = settings.TERMINAL_RDP_ADDR
|
||||
if ':' in xrdp_addr:
|
||||
hostname, port = xrdp_addr.strip().split(':')
|
||||
else:
|
||||
hostname, port = xrdp_addr, 3389
|
||||
endpoint_data.update({
|
||||
'host': '' if hostname.strip() in ['localhost', '127.0.0.1'] else hostname.strip(),
|
||||
'rdp_port': int(port)
|
||||
})
|
||||
Endpoint = apps.get_model("terminal", "Endpoint")
|
||||
Endpoint.objects.create(**endpoint_data)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('terminal', '0047_auto_20220302_1951'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Endpoint',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, blank=True, verbose_name='Name')),
|
||||
('host', models.CharField(max_length=256, verbose_name='Host')),
|
||||
('https_port', common.fields.model.PortField(default=443, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTPS Port')),
|
||||
('http_port', common.fields.model.PortField(default=80, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTP Port')),
|
||||
('ssh_port', common.fields.model.PortField(default=2222, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='SSH Port')),
|
||||
('rdp_port', common.fields.model.PortField(default=3389, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='RDP Port')),
|
||||
('mysql_port', common.fields.model.PortField(default=33060, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='MySQL Port')),
|
||||
('mariadb_port', common.fields.model.PortField(default=33061, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='MariaDB Port')),
|
||||
('postgresql_port', common.fields.model.PortField(default=54320, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='PostgreSQL Port')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Endpoint',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EndpointRule',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('ip_group', models.JSONField(default=list, verbose_name='IP group')),
|
||||
('priority', models.IntegerField(help_text='1-100, the lower the value will be match first', unique=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('endpoint', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rules', to='terminal.endpoint', verbose_name='Endpoint')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Endpoint rule',
|
||||
'ordering': ('priority', 'name'),
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migrate_endpoints),
|
||||
]
|
|
@ -6,3 +6,4 @@ from .task import *
|
|||
from .terminal import *
|
||||
from .sharing import *
|
||||
from .replay import *
|
||||
from .endpoint import *
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from common.db.models import JMSModel
|
||||
from common.fields.model import PortField
|
||||
from common.utils.ip import contains_ip
|
||||
|
||||
|
||||
class Endpoint(JMSModel):
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True)
|
||||
host = models.CharField(max_length=256, blank=True, verbose_name=_('Host'))
|
||||
# disabled value=0
|
||||
https_port = PortField(default=443, verbose_name=_('HTTPS Port'))
|
||||
http_port = PortField(default=80, verbose_name=_('HTTP Port'))
|
||||
ssh_port = PortField(default=2222, verbose_name=_('SSH Port'))
|
||||
rdp_port = PortField(default=3389, verbose_name=_('RDP Port'))
|
||||
mysql_port = PortField(default=33060, verbose_name=_('MySQL Port'))
|
||||
mariadb_port = PortField(default=33061, verbose_name=_('MariaDB Port'))
|
||||
postgresql_port = PortField(default=54320, verbose_name=_('PostgreSQL Port'))
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
default_id = '00000000-0000-0000-0000-000000000001'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Endpoint')
|
||||
ordering = ('name',)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_port(self, protocol):
|
||||
return getattr(self, f'{protocol}_port', 0)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.id == self.default_id:
|
||||
return
|
||||
return super().delete(using, keep_parents)
|
||||
|
||||
@classmethod
|
||||
def get_or_create_default(cls, request=None):
|
||||
data = {
|
||||
'id': cls.default_id,
|
||||
'name': 'Default',
|
||||
'host': '',
|
||||
'https_port': 0,
|
||||
'http_port': 0,
|
||||
}
|
||||
endpoint, created = cls.objects.get_or_create(id=cls.default_id, defaults=data)
|
||||
if not endpoint.host and request:
|
||||
endpoint.host = request.get_host().split(':')[0]
|
||||
return endpoint
|
||||
|
||||
|
||||
class EndpointRule(JMSModel):
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True)
|
||||
ip_group = models.JSONField(default=list, verbose_name=_('IP group'))
|
||||
priority = models.IntegerField(
|
||||
verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)],
|
||||
unique=True, help_text=_("1-100, the lower the value will be match first"),
|
||||
)
|
||||
endpoint = models.ForeignKey(
|
||||
'terminal.Endpoint', null=True, blank=True, related_name='rules',
|
||||
on_delete=models.SET_NULL, verbose_name=_("Endpoint"),
|
||||
)
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Endpoint rule')
|
||||
ordering = ('priority', 'name')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}({self.priority})'
|
||||
|
||||
@classmethod
|
||||
def match(cls, target_ip, protocol):
|
||||
for endpoint_rule in cls.objects.all().prefetch_related('endpoint'):
|
||||
if not contains_ip(target_ip, endpoint_rule.ip_group):
|
||||
continue
|
||||
if not endpoint_rule.endpoint:
|
||||
continue
|
||||
if not endpoint_rule.endpoint.host:
|
||||
continue
|
||||
if endpoint_rule.endpoint.get_port(protocol) == 0:
|
||||
continue
|
||||
return endpoint_rule
|
||||
|
||||
@classmethod
|
||||
def match_endpoint(cls, target_ip, protocol, request=None):
|
||||
endpoint_rule = cls.match(target_ip, protocol)
|
||||
if endpoint_rule:
|
||||
endpoint = endpoint_rule.endpoint
|
||||
else:
|
||||
endpoint = Endpoint.get_or_create_default(request)
|
||||
return endpoint
|
|
@ -11,9 +11,11 @@ from django.core.files.storage import default_storage
|
|||
from django.core.cache import cache
|
||||
|
||||
from assets.models import Asset
|
||||
from applications.models import Application
|
||||
from users.models import User
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from django.db.models import TextChoices
|
||||
from common.utils import get_object_or_none
|
||||
from ..backends import get_multi_command_storage
|
||||
|
||||
|
||||
|
@ -194,6 +196,13 @@ class Session(OrgModelMixin):
|
|||
def login_from_display(self):
|
||||
return self.get_login_from_display()
|
||||
|
||||
def get_target_ip(self):
|
||||
instance = get_object_or_none(Asset, pk=self.asset_id)
|
||||
if not instance:
|
||||
instance = get_object_or_none(Application, pk=self.asset_id)
|
||||
target_ip = instance.get_target_ip() if instance else ''
|
||||
return target_ip
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100, is_finished=True):
|
||||
import random
|
||||
|
|
|
@ -4,3 +4,4 @@ from .terminal import *
|
|||
from .session import *
|
||||
from .storage import *
|
||||
from .sharing import *
|
||||
from .endpoint import *
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common.drf.serializers import BulkModelSerializer
|
||||
from acls.serializers.rules import ip_group_help_text, ip_group_child_validator
|
||||
from ..models import Endpoint, EndpointRule
|
||||
|
||||
__all__ = ['EndpointSerializer', 'EndpointRuleSerializer']
|
||||
|
||||
|
||||
class EndpointSerializer(BulkModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Endpoint
|
||||
fields_mini = ['id', 'name']
|
||||
fields_small = [
|
||||
'host',
|
||||
'https_port', 'http_port', 'ssh_port',
|
||||
'rdp_port', 'mysql_port', 'mariadb_port',
|
||||
'postgresql_port',
|
||||
]
|
||||
fields = fields_mini + fields_small + [
|
||||
'comment', 'date_created', 'date_updated', 'created_by'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'https_port': {'default': 443},
|
||||
'http_port': {'default': 80},
|
||||
'ssh_port': {'default': 2222},
|
||||
'rdp_port': {'default': 3389},
|
||||
'mysql_port': {'default': 33060},
|
||||
'mariadb_port': {'default': 33061},
|
||||
'postgresql_port': {'default': 54320},
|
||||
}
|
||||
|
||||
|
||||
class EndpointRuleSerializer(BulkModelSerializer):
|
||||
ip_group = serializers.ListField(
|
||||
default=['*'], label=_('IP'), help_text=ip_group_help_text,
|
||||
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator])
|
||||
)
|
||||
endpoint_display = serializers.ReadOnlyField(source='endpoint.name', label=_('Endpoint'))
|
||||
|
||||
class Meta:
|
||||
model = EndpointRule
|
||||
fields_mini = ['id', 'name']
|
||||
fields_small = fields_mini + ['ip_group', 'priority']
|
||||
fields_fk = ['endpoint', 'endpoint_display']
|
||||
fields = fields_mini + fields_small + fields_fk + [
|
||||
'comment', 'date_created', 'date_updated', 'created_by'
|
||||
]
|
||||
extra_kwargs = {
|
||||
}
|
|
@ -22,6 +22,8 @@ router.register(r'replay-storages', api.ReplayStorageViewSet, 'replay-storage')
|
|||
router.register(r'command-storages', api.CommandStorageViewSet, 'command-storage')
|
||||
router.register(r'session-sharings', api.SessionSharingViewSet, 'session-sharing')
|
||||
router.register(r'session-join-records', api.SessionJoinRecordsViewSet, 'session-sharing-record')
|
||||
router.register(r'endpoints', api.EndpointViewSet, 'endpoint')
|
||||
router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule')
|
||||
|
||||
urlpatterns = [
|
||||
path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'),
|
||||
|
|
Loading…
Reference in New Issue