merge: with remote

pull/9115/head
ibuler 2022-11-22 21:56:30 +08:00
commit 075cadb1ab
23 changed files with 463 additions and 169 deletions

View File

@ -57,10 +57,11 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
&& apt-get -y install --no-install-recommends ${TOOLS} \ && apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \ && mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \
&& sed -i "s@# alias l@alias l@g" ~/.bashrc \
&& echo "set mouse-=a" > ~/.vimrc \ && echo "set mouse-=a" > ~/.vimrc \
&& echo "no" | dpkg-reconfigure dash \ && echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
ARG DOWNLOAD_URL=https://download.jumpserver.org ARG DOWNLOAD_URL=https://download.jumpserver.org

96
Dockerfile.loong64 Normal file
View File

@ -0,0 +1,96 @@
FROM python:3.8-slim as stage-build
ARG TARGETARCH
ARG VERSION
ENV VERSION=$VERSION
WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.8-slim
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \
g++ \
make \
pkg-config"
ARG DEPENDENCIES=" \
default-libmysqlclient-dev \
freetds-dev \
libpq-dev \
libffi-dev \
libjpeg-dev \
libldap2-dev \
libsasl2-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libaio-dev \
openssh-client \
sshpass"
ARG TOOLS=" \
ca-certificates \
curl \
default-mysql-client \
iputils-ping \
locales \
netcat \
redis-server \
telnet \
vim \
unzip \
wget"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
set -ex \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \
&& echo "set mouse-=a" > ~/.vimrc \
&& echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /tmp/build
COPY ./requirements ./requirements
ARG PIP_MIRROR=https://pypi.douban.com/simple
ENV PIP_MIRROR=$PIP_MIRROR
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \
&& pip install --upgrade pip \
&& pip install --upgrade setuptools wheel \
&& pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.1-cp38-cp38-linux_loongarch64.whl \
&& pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp38-cp38-linux_loongarch64.whl \
&& pip install $(grep 'PyNaCl' requirements/requirements.txt) \
&& GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
&& pip install -r requirements/requirements.txt
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
RUN echo > /opt/jumpserver/config.yml \
&& rm -rf /tmp/build
WORKDIR /opt/jumpserver
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8
EXPOSE 8070
EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]

View File

@ -6,13 +6,11 @@ from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from assets import serializers from assets import serializers
from assets.models import Asset
from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend
from assets.models import Asset, Gateway
from assets.tasks import ( from assets.tasks import (
push_accounts_to_assets, push_accounts_to_assets, test_assets_connectivity_manual,
test_assets_connectivity_manual, update_assets_hardware_info_manual, verify_accounts_connectivity,
update_assets_hardware_info_manual,
verify_accounts_connectivity,
) )
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from common.mixins.api import SuggestionMixin from common.mixins.api import SuggestionMixin
@ -74,7 +72,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
def gateways(self, *args, **kwargs): def gateways(self, *args, **kwargs):
asset = self.get_object() asset = self.get_object()
if not asset.domain: if not asset.domain:
gateways = Gateway.objects.none() gateways = Asset.objects.none()
else: else:
gateways = asset.domain.gateways.filter(protocol="ssh") gateways = asset.domain.gateways.filter(protocol="ssh")
return self.get_paginated_response_from_queryset(gateways) return self.get_paginated_response_from_queryset(gateways)

View File

@ -1,4 +1,3 @@
from assets.models import Host from assets.models import Host
from assets.serializers import HostSerializer from assets.serializers import HostSerializer
from .asset import AssetViewSet from .asset import AssetViewSet

View File

@ -7,21 +7,20 @@ from rest_framework.serializers import ValidationError
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..models import Domain, Gateway from ..models import Domain, Host
from .. import serializers from .. import serializers
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"] __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(OrgBulkModelViewSet): class DomainViewSet(OrgBulkModelViewSet):
model = Domain model = Domain
filterset_fields = ("name", ) filterset_fields = ("name",)
search_fields = filterset_fields search_fields = filterset_fields
serializer_class = serializers.DomainSerializer serializer_class = serializers.DomainSerializer
ordering_fields = ('name',) ordering_fields = ('name',)
ordering = ('name', ) ordering = ('name',)
def get_serializer_class(self): def get_serializer_class(self):
if self.request.query_params.get('gateway'): if self.request.query_params.get('gateway'):
@ -30,21 +29,26 @@ class DomainViewSet(OrgBulkModelViewSet):
class GatewayViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet):
model = Gateway filterset_fields = ("domain__name", "name", "domain")
filterset_fields = ("domain__name", "name", "username", "domain") search_fields = ("domain__name",)
search_fields = ("domain__name", "name", "username", )
serializer_class = serializers.GatewaySerializer serializer_class = serializers.GatewaySerializer
def get_queryset(self):
queryset = Host.get_gateway_queryset()
return queryset
class GatewayTestConnectionApi(SingleObjectMixin, APIView): class GatewayTestConnectionApi(SingleObjectMixin, APIView):
queryset = Gateway.objects.all()
object = None
rbac_perms = { rbac_perms = {
'POST': 'assets.test_gateway' 'POST': 'assets.test_gateway'
} }
def get_queryset(self):
queryset = Host.get_gateway_queryset()
return queryset
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object(Gateway.objects.all()) self.object = self.get_object()
local_port = self.request.data.get('port') or self.object.port local_port = self.request.data.get('port') or self.object.port
try: try:
local_port = int(local_port) local_port = int(local_port)

View File

@ -1,3 +1,5 @@
from .base import *
from .host import *
from .types import * from .types import *
from .account import * from .account import *
from .protocol import * from .protocol import *

View File

@ -1,5 +1,7 @@
from .base import BaseType from .base import BaseType
GATEWAY_NAME = 'Gateway'
class HostTypes(BaseType): class HostTypes(BaseType):
LINUX = 'linux', 'Linux' LINUX = 'linux', 'Linux'
@ -67,7 +69,7 @@ class HostTypes(BaseType):
return { return {
cls.LINUX: [ cls.LINUX: [
{'name': 'Linux'}, {'name': 'Linux'},
{'name': 'Gateway'} {'name': GATEWAY_NAME}
], ],
cls.UNIX: [ cls.UNIX: [
{'name': 'Unix'}, {'name': 'Unix'},

View File

@ -0,0 +1,73 @@
# Generated by Django 3.2.13 on 2022-09-29 11:03
from django.db import migrations
from assets.const.host import GATEWAY_NAME
def _create_account_obj(secret, secret_type, gateway, asset, account_model):
return account_model(
asset=asset,
secret=secret,
org_id=gateway.org_id,
secret_type=secret_type,
username=gateway.username,
name=f'{gateway.name}-{secret_type}-{GATEWAY_NAME.lower()}',
)
def migrate_gateway_to_asset(apps, schema_editor):
db_alias = schema_editor.connection.alias
gateway_model = apps.get_model('assets', 'Gateway')
platform_model = apps.get_model('assets', 'Platform')
gateway_platform = platform_model.objects.using(db_alias).get(name=GATEWAY_NAME)
print('>>> migrate gateway to asset')
asset_dict = {}
host_model = apps.get_model('assets', 'Host')
asset_model = apps.get_model('assets', 'Asset')
protocol_model = apps.get_model('assets', 'Protocol')
gateways = gateway_model.objects.all()
for gateway in gateways:
comment = gateway.comment if gateway.comment else ''
data = {
'comment': comment,
'name': f'{gateway.name}-{GATEWAY_NAME.lower()}',
'address': gateway.ip,
'domain': gateway.domain,
'org_id': gateway.org_id,
'is_active': gateway.is_active,
'platform': gateway_platform,
}
asset = asset_model.objects.using(db_alias).create(**data)
asset_dict[gateway.id] = asset
protocol_model.objects.using(db_alias).create(name='ssh', port=gateway.port, asset=asset)
hosts = [host_model(asset_ptr=asset) for asset in asset_dict.values()]
host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True)
print('>>> migrate gateway to account')
accounts = []
account_model = apps.get_model('assets', 'Account')
for gateway in gateways:
password = gateway.password
private_key = gateway.private_key
asset = asset_dict[gateway.id]
if password:
accounts.append(_create_account_obj(
password, 'password', gateway, asset, account_model
))
if private_key:
accounts.append(_create_account_obj(
private_key, 'ssh_key', gateway, asset, account_model
))
account_model.objects.using(db_alias).bulk_create(accounts)
class Migration(migrations.Migration):
dependencies = [
('assets', '0111_alter_automationexecution_status'),
]
operations = [
migrations.RunPython(migrate_gateway_to_asset),
]

View File

@ -2,8 +2,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import logging
import uuid import uuid
import logging
from collections import defaultdict from collections import defaultdict
from django.db import models from django.db import models

View File

@ -1,6 +1,13 @@
from assets.const import Category from assets.const import GATEWAY_NAME
from .common import Asset from .common import Asset
class Host(Asset): class Host(Asset):
pass pass
@classmethod
def get_gateway_queryset(cls):
queryset = cls.objects.filter(
platform__name=GATEWAY_NAME
)
return queryset

View File

@ -65,3 +65,9 @@ class ChangeSecretRecord(JMSBaseModel):
def __str__(self): def __str__(self):
return self.account.__str__() return self.account.__str__()
@property
def timedelta(self):
if self.date_started and self.date_finished:
return self.date_finished - self.date_started
return None

View File

@ -6,10 +6,10 @@ import sshpubkeys
from hashlib import md5 from hashlib import md5
from django.db import models from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from django.utils import timezone
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils.translation import ugettext_lazy as _
from common.utils import ( from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger, ssh_key_string_to_obj, ssh_key_gen, get_logger,

View File

@ -1,22 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import socket
import uuid import uuid
import socket
import random import random
from django.core.cache import cache
import paramiko import paramiko
from django.db import models from django.db import models
from django.core.cache import cache
from django.db.models.query import QuerySet
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, lazyproperty
from common.db import fields from common.db import fields
from common.utils import get_logger, lazyproperty
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from .base import BaseAccount from .base import BaseAccount
from ..const import SecretType, GATEWAY_NAME
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['Domain', 'Gateway'] __all__ = ['Domain', 'GatewayMixin']
class Domain(OrgModelMixin): class Domain(OrgModelMixin):
@ -33,12 +35,9 @@ class Domain(OrgModelMixin):
def __str__(self): def __str__(self):
return self.name return self.name
def has_gateway(self):
return self.gateway_set.filter(is_active=True).exists()
@lazyproperty @lazyproperty
def gateways(self): def gateways(self):
return self.gateway_set.filter(is_active=True) return self.assets.filter(platform__name=GATEWAY_NAME, is_active=True)
def select_gateway(self): def select_gateway(self):
return self.random_gateway() return self.random_gateway()
@ -53,18 +52,141 @@ class Domain(OrgModelMixin):
return random.choice(self.gateways) return random.choice(self.gateways)
class Gateway(BaseAccount): class GatewayMixin:
UNCONNECTIVE_KEY_TMPL = 'asset_unconnective_gateway_{}' id: uuid.UUID
UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}' port: int
UNCONNECTIVE_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5 address: str
accounts: QuerySet
private_key_path: str
private_key_obj: paramiko.RSAKey
UNCONNECTED_KEY_TMPL = 'asset_unconnective_gateway_{}'
UNCONNECTED_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}'
UNCONNECTED_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5
def set_unconnected(self):
unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id)
unconnected_silence_period_key = self.UNCONNECTED_SILENCE_PERIOD_KEY_TMPL.format(self.id)
unconnected_silence_period = cache.get(
unconnected_silence_period_key, self.UNCONNECTED_SILENCE_PERIOD_BEGIN_VALUE
)
cache.set(unconnected_silence_period_key, unconnected_silence_period * 2)
cache.set(unconnected_key, unconnected_silence_period, unconnected_silence_period)
def set_connective(self):
unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id)
unconnected_silence_period_key = self.UNCONNECTED_SILENCE_PERIOD_KEY_TMPL.format(self.id)
cache.delete(unconnected_key)
cache.delete(unconnected_silence_period_key)
def get_is_unconnected(self):
unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id)
return cache.get(unconnected_key, False)
@property
def is_connective(self):
return not self.get_is_unconnected()
@is_connective.setter
def is_connective(self, value):
if value:
self.set_connective()
else:
self.set_unconnected()
def test_connective(self, local_port=None):
# TODO 走ansible runner
if local_port is None:
local_port = self.port
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.SSHClient()
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
proxy.connect(self.address, port=self.port,
username=self.username,
password=self.password,
pkey=self.private_key_obj)
except(paramiko.AuthenticationException,
paramiko.BadAuthenticationType,
paramiko.SSHException,
paramiko.ChannelException,
paramiko.ssh_exception.NoValidConnectionsError,
socket.gaierror) as e:
err = str(e)
if err.startswith('[Errno None] Unable to connect to port'):
err = _('Unable to connect to port {port} on {address}')
err = err.format(port=self.port, ip=self.address)
elif err == 'Authentication failed.':
err = _('Authentication failed')
elif err == 'Connect failed':
err = _('Connect failed')
self.is_connective = False
return False, err
try:
sock = proxy.get_transport().open_channel(
'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0)
)
client.connect("127.0.0.1", port=local_port,
username=self.username,
password=self.password,
key_filename=self.private_key_path,
sock=sock,
timeout=5)
except (paramiko.SSHException,
paramiko.ssh_exception.SSHException,
paramiko.ChannelException,
paramiko.AuthenticationException,
TimeoutError) as e:
err = getattr(e, 'text', str(e))
if err == 'Connect failed':
err = _('Connect failed')
self.is_connective = False
return False, err
finally:
client.close()
self.is_connective = True
return True, None
@lazyproperty
def username(self):
account = self.accounts.all().first()
if account:
return account.username
logger.error(f'Gateway {self} has no account')
return ''
def get_secret(self, secret_type):
account = self.accounts.filter(secret_type=secret_type).first()
if account:
return account.secret
logger.error(f'Gateway {self} has no {secret_type} account')
@lazyproperty
def password(self):
secret_type = SecretType.PASSWORD
return self.get_secret(secret_type)
@lazyproperty
def private_key(self):
secret_type = SecretType.SSH_KEY
return self.get_secret(secret_type)
class Gateway(BaseAccount):
class Protocol(models.TextChoices): class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH' ssh = 'ssh', 'SSH'
name = models.CharField(max_length=128, verbose_name='Name') name = models.CharField(max_length=128, verbose_name='Name')
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
port = models.IntegerField(default=22, verbose_name=_('Port')) port = models.IntegerField(default=22, verbose_name=_('Port'))
protocol = models.CharField(choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")) protocol = models.CharField(
choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")
)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain")) domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active")) is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
@ -85,91 +207,3 @@ class Gateway(BaseAccount):
permissions = [ permissions = [
('test_gateway', _('Test gateway')) ('test_gateway', _('Test gateway'))
] ]
def set_unconnective(self):
unconnective_key = self.UNCONNECTIVE_KEY_TMPL.format(self.id)
unconnective_silence_period_key = self.UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL.format(self.id)
unconnective_silence_period = cache.get(unconnective_silence_period_key,
self.UNCONNECTIVE_SILENCE_PERIOD_BEGIN_VALUE)
cache.set(unconnective_silence_period_key, unconnective_silence_period * 2)
cache.set(unconnective_key, unconnective_silence_period, unconnective_silence_period)
def set_connective(self):
unconnective_key = self.UNCONNECTIVE_KEY_TMPL.format(self.id)
unconnective_silence_period_key = self.UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL.format(self.id)
cache.delete(unconnective_key)
cache.delete(unconnective_silence_period_key)
def get_is_unconnective(self):
unconnective_key = self.UNCONNECTIVE_KEY_TMPL.format(self.id)
return cache.get(unconnective_key, False)
@property
def is_connective(self):
return not self.get_is_unconnective()
@is_connective.setter
def is_connective(self, value):
if value:
self.set_connective()
else:
self.set_unconnective()
def test_connective(self, local_port=None):
if local_port is None:
local_port = self.port
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.SSHClient()
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
proxy.connect(self.ip, port=self.port,
username=self.username,
password=self.password,
pkey=self.private_key_obj)
except(paramiko.AuthenticationException,
paramiko.BadAuthenticationType,
paramiko.SSHException,
paramiko.ChannelException,
paramiko.ssh_exception.NoValidConnectionsError,
socket.gaierror) as e:
err = str(e)
if err.startswith('[Errno None] Unable to connect to port'):
err = _('Unable to connect to port {port} on {address}')
err = err.format(port=self.port, ip=self.ip)
elif err == 'Authentication failed.':
err = _('Authentication failed')
elif err == 'Connect failed':
err = _('Connect failed')
self.is_connective = False
return False, err
try:
sock = proxy.get_transport().open_channel(
'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0)
)
client.connect("127.0.0.1", port=local_port,
username=self.username,
password=self.password,
key_filename=self.private_key_file,
sock=sock,
timeout=5)
except (paramiko.SSHException,
paramiko.ssh_exception.SSHException,
paramiko.ChannelException,
paramiko.AuthenticationException,
TimeoutError) as e:
err = getattr(e, 'text', str(e))
if err == 'Connect failed':
err = _('Connect failed')
self.is_connective = False
return False, err
finally:
client.close()
self.is_connective = True
return True, None

View File

@ -42,6 +42,19 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
)}, )},
}} }}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_secret_type_choices()
def set_secret_type_choices(self):
secret_type = self.fields.get('secret_type')
if not secret_type:
return
choices = secret_type._choices
choices.pop(SecretType.ACCESS_KEY, None)
choices.pop(SecretType.TOKEN, None)
secret_type._choices = choices
def validate_password_rules(self, password_rules): def validate_password_rules(self, password_rules):
secret_type = self.initial_secret_type secret_type = self.initial_secret_type
if secret_type != SecretType.PASSWORD: if secret_type != SecretType.PASSWORD:
@ -93,8 +106,8 @@ class ChangeSecretRecordSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ChangeSecretRecord model = ChangeSecretRecord
fields = [ fields = [
'id', 'asset', 'account', 'date_started', 'id', 'asset', 'account', 'date_started', 'date_finished',
'date_finished', 'is_success', 'error', 'execution', 'timedelta', 'is_success', 'error', 'execution',
] ]
read_only_fields = fields read_only_fields = fields

View File

@ -3,11 +3,9 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.validators import alphanumeric
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from ..models import Domain, Gateway from ..models import Domain, Asset
from .base import AuthValidateMixin
class DomainSerializer(BulkOrgResourceModelSerializer): class DomainSerializer(BulkOrgResourceModelSerializer):
@ -35,32 +33,23 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
@staticmethod @staticmethod
def get_gateway_count(obj): def get_gateway_count(obj):
return obj.gateway_set.all().count() return obj.gateways.count()
class GatewaySerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): class GatewaySerializer(BulkOrgResourceModelSerializer):
is_connective = serializers.BooleanField(required=False, label=_('Connectivity')) is_connective = serializers.BooleanField(required=False, label=_('Connectivity'))
class Meta: class Meta:
model = Gateway model = Asset
fields_mini = ['id', 'username'] fields_mini = ['id']
fields_write_only = [ fields_small = fields_mini + [
'password', 'private_key', 'public_key', 'passphrase' 'address', 'port', 'protocol',
]
fields_small = fields_mini + fields_write_only + [
'ip', 'port', 'protocol',
'is_active', 'is_connective', 'is_active', 'is_connective',
'date_created', 'date_updated', 'date_created', 'date_updated',
'created_by', 'comment', 'created_by', 'comment',
] ]
fields_fk = ['domain'] fields_fk = ['domain']
fields = fields_small + fields_fk fields = fields_small + fields_fk
extra_kwargs = {
'username': {"validators": [alphanumeric]},
'password': {'write_only': True},
'private_key': {"write_only": True},
'public_key': {"write_only": True},
}
class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer): class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer):

View File

@ -1,8 +1,8 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from assets.models import Asset, Gateway, Domain, CommandFilterRule, Account, Platform
from assets.serializers import PlatformSerializer from assets.serializers import PlatformSerializer
from assets.models import Asset, Domain, CommandFilterRule, Account, Platform
from authentication.models import ConnectionToken from authentication.models import ConnectionToken
from orgs.mixins.serializers import OrgResourceModelSerializerMixin from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from perms.serializers.permission import ActionChoicesField from perms.serializers.permission import ActionChoicesField
@ -106,8 +106,8 @@ class ConnectionTokenGatewaySerializer(serializers.ModelSerializer):
""" Gateway """ """ Gateway """
class Meta: class Meta:
model = Gateway model = Asset
fields = ['id', 'ip', 'port', 'username', 'password', 'private_key'] fields = ['id', 'address', 'port', 'username', 'password', 'private_key']
class ConnectionTokenDomainSerializer(serializers.ModelSerializer): class ConnectionTokenDomainSerializer(serializers.ModelSerializer):

View File

@ -1,14 +1,16 @@
import ast import ast
from celery import signals
from django.db import transaction from django.db import transaction
from django.core.cache import cache
from django.dispatch import receiver from django.dispatch import receiver
from django.db.utils import ProgrammingError
from django.utils import translation, timezone from django.utils import translation, timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.core.cache import cache
from celery import signals, current_app
from common.db.utils import close_old_connections, get_logger
from common.signals import django_ready from common.signals import django_ready
from common.db.utils import close_old_connections, get_logger
from .celery import app from .celery import app
from .models import CeleryTaskExecution, CeleryTask from .models import CeleryTaskExecution, CeleryTask
@ -23,15 +25,15 @@ def sync_registered_tasks(*args, **kwargs):
with transaction.atomic(): with transaction.atomic():
try: try:
db_tasks = CeleryTask.objects.all() db_tasks = CeleryTask.objects.all()
except Exception as e: celery_task_names = [key for key in app.tasks]
return db_task_names = db_tasks.values_list('name', flat=True)
celery_task_names = [key for key in app.tasks]
db_task_names = db_tasks.values_list('name', flat=True)
db_tasks.exclude(name__in=celery_task_names).delete() db_tasks.exclude(name__in=celery_task_names).delete()
not_in_db_tasks = set(celery_task_names) - set(db_task_names) not_in_db_tasks = set(celery_task_names) - set(db_task_names)
tasks_to_create = [CeleryTask(name=name) for name in not_in_db_tasks] tasks_to_create = [CeleryTask(name=name) for name in not_in_db_tasks]
CeleryTask.objects.bulk_create(tasks_to_create) CeleryTask.objects.bulk_create(tasks_to_create)
except ProgrammingError:
pass
@signals.before_task_publish.connect @signals.before_task_publish.connect
@ -45,7 +47,7 @@ def before_task_publish(headers=None, **kwargs):
@signals.task_prerun.connect @signals.task_prerun.connect
def on_celery_task_pre_run(task_id='', **kwargs): def on_celery_task_pre_run(task_id='', **kwargs):
# 更新状态 # 更新状态
CeleryTaskExecution.objects.filter(id=task_id)\ CeleryTaskExecution.objects.filter(id=task_id) \
.update(state='RUNNING', date_start=timezone.now()) .update(state='RUNNING', date_start=timezone.now())
# 关闭之前的数据库连接 # 关闭之前的数据库连接
close_old_connections() close_old_connections()

View File

@ -14,7 +14,7 @@ from .serializers import (
) )
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import ( from assets.models import (
Asset, Domain, Label, Node, Gateway, Asset, Domain, Label, Node,
CommandFilter, CommandFilterRule, GatheredUser CommandFilter, CommandFilterRule, GatheredUser
) )
from perms.models import AssetPermission from perms.models import AssetPermission
@ -27,7 +27,7 @@ logger = get_logger(__file__)
# 部分 org 相关的 model需要清空这些数据之后才能删除该组织 # 部分 org 相关的 model需要清空这些数据之后才能删除该组织
org_related_models = [ org_related_models = [
User, UserGroup, Asset, Label, Domain, Gateway, Node, Label, User, UserGroup, Asset, Label, Domain, Node, Label,
CommandFilter, CommandFilterRule, GatheredUser, CommandFilter, CommandFilterRule, GatheredUser,
AssetPermission, AssetPermission,
] ]

View File

@ -6,7 +6,7 @@ from orgs.utils import current_org, tmp_to_org
from common.cache import Cache, IntegerField from common.cache import Cache, IntegerField
from common.utils import get_logger from common.utils import get_logger
from users.models import UserGroup, User from users.models import UserGroup, User
from assets.models import Node, Domain, Gateway, Asset, Account from assets.models import Node, Domain, Asset, Account
from terminal.models import Session from terminal.models import Session
from perms.models import AssetPermission from perms.models import AssetPermission
@ -54,7 +54,7 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
nodes_amount = IntegerField(queryset=Node.objects) nodes_amount = IntegerField(queryset=Node.objects)
accounts_amount = IntegerField(queryset=Account.objects) accounts_amount = IntegerField(queryset=Account.objects)
domains_amount = IntegerField(queryset=Domain.objects) domains_amount = IntegerField(queryset=Domain.objects)
gateways_amount = IntegerField(queryset=Gateway.objects) # gateways_amount = IntegerField(queryset=Gateway.objects)
asset_perms_amount = IntegerField(queryset=AssetPermission.objects) asset_perms_amount = IntegerField(queryset=AssetPermission.objects)
total_count_online_users = IntegerField() total_count_online_users = IntegerField()

View File

@ -8,7 +8,7 @@ from users.models import UserGroup, User
from users.signals import pre_user_leave_org from users.signals import pre_user_leave_org
from terminal.models import Session from terminal.models import Session
from rbac.models import OrgRoleBinding, SystemRoleBinding, RoleBinding from rbac.models import OrgRoleBinding, SystemRoleBinding, RoleBinding
from assets.models import Asset, Domain, Gateway from assets.models import Asset, Domain
from orgs.caches import OrgResourceStatisticsCache from orgs.caches import OrgResourceStatisticsCache
from orgs.utils import current_org from orgs.utils import current_org
from common.utils import get_logger from common.utils import get_logger
@ -75,7 +75,6 @@ def on_user_delete_refresh_cache(sender, instance, **kwargs):
class OrgResourceStatisticsRefreshUtil: class OrgResourceStatisticsRefreshUtil:
model_cache_field_mapper = { model_cache_field_mapper = {
AssetPermission: ['asset_perms_amount'], AssetPermission: ['asset_perms_amount'],
Gateway: ['gateways_amount'],
Domain: ['domains_amount'], Domain: ['domains_amount'],
Node: ['nodes_amount'], Node: ['nodes_amount'],
Asset: ['assets_amount'], Asset: ['assets_amount'],

View File

@ -7,6 +7,7 @@ from functools import partial
import django.db.utils import django.db.utils
from django.dispatch import receiver from django.dispatch import receiver
from django.conf import settings from django.conf import settings
from django.db.utils import ProgrammingError, OperationalError
from django.utils.functional import LazyObject from django.utils.functional import LazyObject
from django.db.models.signals import post_save, pre_delete, m2m_changed from django.db.models.signals import post_save, pre_delete, m2m_changed
@ -48,7 +49,7 @@ def subscribe_orgs_mapping_expire(sender, **kwargs):
if settings.DEBUG: if settings.DEBUG:
try: try:
set_to_default_org() set_to_default_org()
except django.db.utils.OperationalError: except (ProgrammingError, OperationalError):
pass pass
def keep_subscribe_org_mapping(): def keep_subscribe_org_mapping():

View File

@ -30,7 +30,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
'domain', 'platform', 'domain', 'platform',
"comment", "org_id", "is_active", "comment", "org_id", "is_active",
] ]
fields = only_fields + ['category', 'type'] + ['org_name'] fields = only_fields + ['protocols', 'category', 'type'] + ['org_name']
read_only_fields = fields read_only_fields = fields

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('tickets', '0022_alter_applyassetticket_apply_actions'), ('tickets', '0022_alter_applyassetticket_apply_actions'),
] ]
@ -15,4 +14,73 @@ class Migration(migrations.Migration):
name='apply_actions', name='apply_actions',
field=models.IntegerField(default=31, verbose_name='Actions'), field=models.IntegerField(default=31, verbose_name='Actions'),
), ),
migrations.AlterField(
model_name='approvalrule',
name='strategy',
field=models.CharField(
choices=[
('org_admin', 'Org admin'), ('custom_user', 'Custom user'),
('super_admin', 'Super admin'), ('super_org_admin', 'Super admin and org admin')
], default='super_admin', max_length=64,
verbose_name='Approve strategy'),
),
migrations.AlterField(
model_name='ticket',
name='state',
field=models.CharField(
choices=[
('pending', 'Open'), ('closed', 'Cancel'),
('reopen', 'Reopen'), ('approved', 'Approved'),
('rejected', 'Rejected')
], default='pending', max_length=16, verbose_name='State'),
),
migrations.AlterField(
model_name='ticket',
name='type',
field=models.CharField(
choices=[
('general', 'General'), ('apply_asset', 'Apply for asset'),
('login_confirm', 'Login confirm'), ('command_confirm', 'Command confirm'),
('login_asset_confirm', 'Login asset confirm')
],
default='general', max_length=64, verbose_name='Type'),
),
migrations.AlterField(
model_name='ticketassignee',
name='state',
field=models.CharField(
choices=[
('pending', 'Open'), ('closed', 'Cancel'),
('reopen', 'Reopen'), ('approved', 'Approved'),
('rejected', 'Rejected')
], default='pending', max_length=64),
),
migrations.AlterField(
model_name='ticketflow',
name='type',
field=models.CharField(
choices=[
('general', 'General'), ('apply_asset', 'Apply for asset'),
('login_confirm', 'Login confirm'), ('command_confirm', 'Command confirm'),
('login_asset_confirm', 'Login asset confirm')
],
default='general', max_length=64, verbose_name='Type'),
),
migrations.AlterField(
model_name='ticketstep',
name='state',
field=models.CharField(
choices=[
('pending', 'Pending'), ('closed', 'Closed'),
('reopen', 'Reopen'), ('approved', 'Approved'),
('rejected', 'Rejected')
], default='pending', max_length=64, verbose_name='State'),
),
migrations.AlterField(
model_name='ticketstep',
name='status',
field=models.CharField(
choices=[('active', 'Active'), ('closed', 'Closed'), ('pending', 'Pending')],
default='pending', max_length=16),
),
] ]