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
fit2bot 2022-04-12 17:45:10 +08:00 committed by GitHub
parent 4cf90df17c
commit f481463c64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 695 additions and 1027 deletions

View File

@ -247,6 +247,14 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
def category_remote_app(self): def category_remote_app(self):
return self.category == const.AppCategory.remote_app.value 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): def get_rdp_remote_app_setting(self):
from applications.serializers.attrs import get_serializer_class_by_application_type from applications.serializers.attrs import get_serializer_class_by_application_type
if not self.category_remote_app: if not self.category_remote_app:
@ -279,6 +287,18 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
if raise_exception: if raise_exception:
raise ValueError("Remote App not has asset attr") 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 ApplicationUser(SystemUser):
class Meta: class Meta:

View File

@ -235,6 +235,9 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
def __str__(self): def __str__(self):
return '{0.hostname}({0.ip})'.format(self) return '{0.hostname}({0.ip})'.format(self)
def get_target_ip(self):
return self.ip
def set_admin_user_relation(self): def set_admin_user_relation(self):
from .authbook import AuthBook from .authbook import AuthBook
if not self.admin_user: if not self.admin_user:

View File

@ -31,12 +31,13 @@ from perms.models.base import Action
from perms.utils.application.permission import get_application_actions from perms.utils.application.permission import get_application_actions
from perms.utils.asset.permission import get_asset_actions from perms.utils.asset.permission import get_asset_actions
from common.const.http import PATCH from common.const.http import PATCH
from terminal.models import EndpointRule
from ..serializers import ( from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer, ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
) )
logger = get_logger(__name__) logger = get_logger(__name__)
__all__ = ['UserConnectionTokenViewSet'] __all__ = ['UserConnectionTokenViewSet', 'TokenCacheMixin']
class ClientProtocolMixin: class ClientProtocolMixin:
@ -51,6 +52,17 @@ class ClientProtocolMixin:
request: Request request: Request
get_serializer: Callable get_serializer: Callable
create_token: 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): def get_request_resource(self, serializer):
asset = serializer.validated_data.get('asset') asset = serializer.validated_data.get('asset')
@ -122,10 +134,10 @@ class ClientProtocolMixin:
options['screen mode id:i'] = '2' if full_screen else '1' options['screen mode id:i'] = '2' if full_screen else '1'
# RDP Server 地址 # RDP Server 地址
address = settings.TERMINAL_RDP_ADDR endpoint = self.get_smart_endpoint(
if not address or address == 'localhost:3389': protocol='rdp', asset=asset, application=application
address = self.request.get_host().split(':')[0] + ':3389' )
options['full address:s'] = address options['full address:s'] = f'{endpoint.host}:{endpoint.rdp_port}'
# 用户名 # 用户名
options['username:s'] = '{}|{}'.format(user.username, token) options['username:s'] = '{}|{}'.format(user.username, token)
if system_user.ad_domain: if system_user.ad_domain:
@ -169,9 +181,12 @@ class ClientProtocolMixin:
else: else:
name = '*' name = '*'
endpoint = self.get_smart_endpoint(
protocol='ssh', asset=asset, application=application
)
content = { content = {
'ip': settings.TERMINAL_KOKO_HOST, 'ip': endpoint.host,
'port': str(settings.TERMINAL_KOKO_SSH_PORT), 'port': endpoint.ssh_port,
'username': f'JMS-{token}', 'username': f'JMS-{token}',
'password': secret 'password': secret
} }
@ -345,6 +360,7 @@ class SecretDetailMixin:
class TokenCacheMixin: class TokenCacheMixin:
""" endpoint smart view 用到此类来解析token中的资产、应用 """
CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}' CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}'
def get_token_cache_key(self, token): def get_token_cache_key(self, token):

View File

@ -4,7 +4,7 @@ import json
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.core.validators import MinValueValidator, MaxValueValidator
from ..utils import signer, crypto from ..utils import signer, crypto
@ -13,7 +13,7 @@ __all__ = [
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField', 'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField', 'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField', 'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField',
'EncryptJsonDictCharField', 'EncryptJsonDictCharField', 'PortField'
] ]
@ -180,3 +180,13 @@ class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
class EncryptJsonDictCharField(EncryptMixin, JsonDictCharField): class EncryptJsonDictCharField(EncryptMixin, JsonDictCharField):
pass 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)

View File

@ -310,16 +310,12 @@ class Config(dict):
'TERMINAL_HOST_KEY': '', 'TERMINAL_HOST_KEY': '',
'TERMINAL_TELNET_REGEX': '', 'TERMINAL_TELNET_REGEX': '',
'TERMINAL_COMMAND_STORAGE': {}, 'TERMINAL_COMMAND_STORAGE': {},
'TERMINAL_RDP_ADDR': lambda: urlparse(settings.SITE_URL).hostname + ':3389', # 未来废弃(当下迁移会用)
'XRDP_ENABLED': True, 'TERMINAL_RDP_ADDR': '',
'TERMINAL_KOKO_HOST': lambda: urlparse(settings.SITE_URL).hostname, # 保留(Luna还在用)
'TERMINAL_KOKO_SSH_PORT': 2222,
'TERMINAL_MAGNUS_ENABLED': True, 'TERMINAL_MAGNUS_ENABLED': True,
'TERMINAL_MAGNUS_HOST': lambda: urlparse(settings.SITE_URL).hostname, # 保留(Luna还在用)
'TERMINAL_MAGNUS_MYSQL_PORT': 33060, 'XRDP_ENABLED': True,
'TERMINAL_MAGNUS_MARIADB_PORT': 33061,
'TERMINAL_MAGNUS_POSTGRE_PORT': 54320,
# 安全配置 # 安全配置
'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启 'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启

View File

@ -140,9 +140,6 @@ CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS = CONFIG.CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS
XRDP_ENABLED = CONFIG.XRDP_ENABLED 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
SMS_ENABLED = CONFIG.SMS_ENABLED SMS_ENABLED = CONFIG.SMS_ENABLED
SMS_BACKEND = CONFIG.SMS_BACKEND SMS_BACKEND = CONFIG.SMS_BACKEND
@ -170,11 +167,3 @@ ANNOUNCEMENT = CONFIG.ANNOUNCEMENT
# help # help
HELP_DOCUMENT_URL = CONFIG.HELP_DOCUMENT_URL HELP_DOCUMENT_URL = CONFIG.HELP_DOCUMENT_URL
HELP_SUPPORT_URL = CONFIG.HELP_SUPPORT_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

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:097c6d06ed8dcf2e1807560b6eb52d98cba31f25fe8d67ce4315668c150ca6b8 oid sha256:70685e92cbf84f4178224a44fd84eb884a0acfb9749200541ea6655a9a397a72
size 129989 size 125019

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3d6a0d40534209f3ffab0b0ecfab7ec82f137156fc8e7b19ce1711036d14aeca oid sha256:9c13775875a335e3c8dbc7f666af622c5aa12050100b15e616c210e8e3043e38
size 107709 size 103490

File diff suppressed because it is too large Load Diff

View File

@ -64,13 +64,6 @@ class PublicSettingApi(generics.RetrieveAPIView):
"AUTH_FEISHU": settings.AUTH_FEISHU, "AUTH_FEISHU": settings.AUTH_FEISHU,
# Terminal # Terminal
"XRDP_ENABLED": settings.XRDP_ENABLED, "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
"ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED, "ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED,
"ANNOUNCEMENT": settings.ANNOUNCEMENT, "ANNOUNCEMENT": settings.ANNOUNCEMENT,

View File

@ -33,33 +33,5 @@ class TerminalSettingSerializer(serializers.Serializer):
help_text=_("The login success message varies with devices. " help_text=_("The login success message varies with devices. "
"if you cannot log in to the device through Telnet, set this parameter") "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_ENABLED = serializers.BooleanField(label=_("Enable database proxy"))
TERMINAL_MAGNUS_HOST = serializers.CharField( XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP"))
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')
)

View File

@ -7,3 +7,4 @@ from .task import *
from .storage import * from .storage import *
from .status import * from .status import *
from .sharing import * from .sharing import *
from .endpoint import *

View File

@ -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()

View File

@ -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),
]

View File

@ -6,3 +6,4 @@ from .task import *
from .terminal import * from .terminal import *
from .sharing import * from .sharing import *
from .replay import * from .replay import *
from .endpoint import *

View File

@ -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

View File

@ -11,9 +11,11 @@ from django.core.files.storage import default_storage
from django.core.cache import cache from django.core.cache import cache
from assets.models import Asset from assets.models import Asset
from applications.models import Application
from users.models import User from users.models import User
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from django.db.models import TextChoices from django.db.models import TextChoices
from common.utils import get_object_or_none
from ..backends import get_multi_command_storage from ..backends import get_multi_command_storage
@ -194,6 +196,13 @@ class Session(OrgModelMixin):
def login_from_display(self): def login_from_display(self):
return self.get_login_from_display() 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 @classmethod
def generate_fake(cls, count=100, is_finished=True): def generate_fake(cls, count=100, is_finished=True):
import random import random

View File

@ -4,3 +4,4 @@ from .terminal import *
from .session import * from .session import *
from .storage import * from .storage import *
from .sharing import * from .sharing import *
from .endpoint import *

View File

@ -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 = {
}

View File

@ -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'command-storages', api.CommandStorageViewSet, 'command-storage')
router.register(r'session-sharings', api.SessionSharingViewSet, 'session-sharing') router.register(r'session-sharings', api.SessionSharingViewSet, 'session-sharing')
router.register(r'session-join-records', api.SessionJoinRecordsViewSet, 'session-sharing-record') 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 = [ urlpatterns = [
path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'), path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'),