Merge branch 'v3' of github.com:jumpserver/jumpserver into v3

pull/9152/head
Bai 2022-12-02 17:48:51 +08:00
commit c832f762a5
18 changed files with 875 additions and 650 deletions

View File

@ -7,7 +7,7 @@ from rest_framework.response import Response
from assets import serializers from assets import serializers
from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend
from assets.models import Asset from assets.models import Asset, Gateway
from assets.tasks import ( from assets.tasks import (
push_accounts_to_assets, test_assets_connectivity_manual, push_accounts_to_assets, test_assets_connectivity_manual,
update_assets_hardware_info_manual, verify_accounts_connectivity, update_assets_hardware_info_manual, verify_accounts_connectivity,
@ -57,7 +57,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
("default", serializers.AssetSerializer), ("default", serializers.AssetSerializer),
("suggestion", serializers.MiniAssetSerializer), ("suggestion", serializers.MiniAssetSerializer),
("platform", serializers.PlatformSerializer), ("platform", serializers.PlatformSerializer),
("gateways", serializers.GatewayWithAuthSerializer), ("gateways", serializers.GatewaySerializer),
) )
rbac_perms = ( rbac_perms = (
("match", "assets.match_asset"), ("match", "assets.match_asset"),
@ -76,9 +76,9 @@ 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 = Asset.objects.none() gateways = Gateway.objects.none()
else: else:
gateways = asset.domain.gateways.filter(protocol="ssh") gateways = asset.domain.gateways
return self.get_paginated_response_from_queryset(gateways) return self.get_paginated_response_from_queryset(gateways)

View File

@ -6,7 +6,6 @@ from hashlib import md5
import sshpubkeys import sshpubkeys
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.db.models import QuerySet
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -35,7 +34,7 @@ class AbsConnectivity(models.Model):
@classmethod @classmethod
def bulk_set_connectivity(cls, queryset_or_id, connectivity): def bulk_set_connectivity(cls, queryset_or_id, connectivity):
if not isinstance(queryset_or_id, QuerySet): if not isinstance(queryset_or_id, models.QuerySet):
queryset = cls.objects.filter(id__in=queryset_or_id) queryset = cls.objects.filter(id__in=queryset_or_id)
else: else:
queryset = queryset_or_id queryset = queryset_or_id
@ -87,7 +86,7 @@ class BaseAccount(JMSOrgBaseModel):
@lazyproperty @lazyproperty
def public_key(self): def public_key(self):
if self.secret_type == SecretType.SSH_KEY and self.private_key: if self.secret_type == SecretType.SSH_KEY and self.private_key:
return parse_ssh_public_key_str(private_key=self.private_key) return parse_ssh_public_key_str(self.private_key)
return None return None
@property @property

View File

@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, lazyproperty from common.utils import get_logger, lazyproperty
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from assets.models import Host, Platform from assets.models import Host, Platform
from assets.const import GATEWAY_NAME from assets.const import GATEWAY_NAME, SecretType
from orgs.mixins.models import OrgManager from orgs.mixins.models import OrgManager
logger = get_logger(__file__) logger = get_logger(__file__)
@ -80,3 +80,40 @@ class Gateway(Host):
platform = self.default_platform platform = self.default_platform
self.platform_id = platform.id self.platform_id = platform.id
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@lazyproperty
def select_accounts(self) -> dict:
account_dict = {}
accounts = self.accounts.filter(is_active=True).order_by('-privileged', '-date_updated')
password_account = accounts.filter(secret_type=SecretType.PASSWORD).first()
if password_account:
account_dict[SecretType.PASSWORD] = password_account
ssh_key_account = accounts.filter(secret_type=SecretType.SSH_KEY).first()
if ssh_key_account:
account_dict[SecretType.SSH_KEY] = ssh_key_account
return account_dict
@property
def password(self):
account = self.select_accounts.get(SecretType.PASSWORD)
return account.secret if account else None
@property
def private_key(self):
account = self.select_accounts.get(SecretType.SSH_KEY)
return account.secret if account else None
def private_key_path(self):
account = self.select_accounts.get(SecretType.SSH_KEY)
return account.private_key_path if account else None
@lazyproperty
def username(self):
accounts = self.select_accounts.values()
if len(accounts) == 0:
return None
accounts = sorted(
accounts, key=lambda x: x['privileged'], reverse=True
)
return accounts[0].username

View File

@ -39,21 +39,26 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
class GatewaySerializer(HostSerializer): class GatewaySerializer(HostSerializer):
effective_accounts = serializers.SerializerMethodField()
class Meta(HostSerializer.Meta): class Meta(HostSerializer.Meta):
model = Gateway model = Gateway
fields = HostSerializer.Meta.fields + ['effective_accounts']
@staticmethod
class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer): def get_effective_accounts(obj):
class Meta(GatewaySerializer.Meta): accounts = obj.select_accounts.values()
extra_kwargs = { return [
'password': {'write_only': False}, {
'private_key': {"write_only": False}, 'id': account.id,
'public_key': {"write_only": False}, 'username': account.username,
} 'secret_type': account.secret_type,
} for account in accounts
]
class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer): class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer):
gateways = GatewayWithAuthSerializer(many=True, read_only=True) gateways = GatewaySerializer(many=True, read_only=True)
class Meta: class Meta:
model = Domain model = Domain

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4a5338177d87680e0030c77f187a06664136d5dea63c8dffc43fa686091f2da4 oid sha256:a2d20ebe29a2ae521e5026f493313abbee6a7a6b103901164766e5d1ae4ab564
size 117102 size 116377

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:30ae571e06eb7d2f0fee70013a812ea3bdb8e14715e1a1f4eb5e2c92311034f8 oid sha256:eb680a5e6725fcd4459a8e712b0eda8df3e9990915e7f3b9602b16307ff36221
size 104086 size 103614

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,7 @@ class JMSInventory:
0, "sshpass -p '{}'".format(gateway.password) 0, "sshpass -p '{}'".format(gateway.password)
) )
if gateway.private_key: if gateway.private_key:
proxy_command_list.append("-i {}".format(gateway.private_key_file)) proxy_command_list.append("-i {}".format(gateway.private_key_path))
proxy_command = "'-o ProxyCommand={}'".format( proxy_command = "'-o ProxyCommand={}'".format(
" ".join(proxy_command_list) " ".join(proxy_command_list)
@ -67,7 +67,7 @@ class JMSInventory:
if account.secret_type == 'password': if account.secret_type == 'password':
var['ansible_password'] = account.secret var['ansible_password'] = account.secret
elif account.secret_type == 'ssh_key': elif account.secret_type == 'ssh_key':
var['ansible_ssh_private_key_file'] = account.private_key_file var['ansible_ssh_private_key_file'] = account.private_key_path
return var return var
def make_ssh_account_vars(self, host, asset, account, automation, protocols, platform, gateway): def make_ssh_account_vars(self, host, asset, account, automation, protocols, platform, gateway):

View File

@ -28,10 +28,21 @@ class JobViewSet(OrgBulkModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
instance = serializer.save() instance = serializer.save()
if instance.instant: run_after_save = serializer.validated_data.get('run_after_save', False)
execution = instance.create_execution() if instance.instant or run_after_save:
task = run_ops_job_execution.delay(execution.id) self.run_job(instance, serializer)
set_task_to_serializer_data(serializer, task)
def perform_update(self, serializer):
instance = serializer.save()
run_after_save = serializer.validated_data.get('run_after_save', False)
if run_after_save:
self.run_job(instance, serializer)
@staticmethod
def run_job(job, serializer):
execution = job.create_execution()
task = run_ops_job_execution.delay(execution.id)
set_task_to_serializer_data(serializer, task)
class JobExecutionViewSet(OrgBulkModelViewSet): class JobExecutionViewSet(OrgBulkModelViewSet):

View File

@ -4,6 +4,7 @@ import zipfile
from django.conf import settings from django.conf import settings
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..exception import PlaybookNoValidEntry
from ..models import Playbook from ..models import Playbook
from ..serializers.playbook import PlaybookSerializer from ..serializers.playbook import PlaybookSerializer
@ -25,6 +26,10 @@ class PlaybookViewSet(OrgBulkModelViewSet):
instance = serializer.save() instance = serializer.save()
src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name) src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name)
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__()) dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
if os.path.exists(dest_path):
os.makedirs(dest_path)
unzip_playbook(src_path, dest_path) unzip_playbook(src_path, dest_path)
valid_entry = ('main.yml', 'main.yaml', 'main')
for f in os.listdir(dest_path):
if f in valid_entry:
return
os.remove(dest_path)
raise PlaybookNoValidEntry

6
apps/ops/exception.py Normal file
View File

@ -0,0 +1,6 @@
from common.exceptions import JMSException
from django.utils.translation import gettext_lazy as _
class PlaybookNoValidEntry(JMSException):
default_detail = _('no valid program entry found.')

View File

@ -71,5 +71,9 @@ class CeleryTaskExecution(models.Model):
return self.date_finished - self.date_start return self.date_finished - self.date_start
return None return None
@property
def is_success(self):
return self.state == 'SUCCESS'
def __str__(self): def __str__(self):
return "{}: {}".format(self.name, self.id) return "{}: {}".format(self.name, self.id)

View File

@ -136,7 +136,7 @@ class JobExecution(JMSOrgBaseModel):
) )
elif self.job.type == 'playbook': elif self.job.type == 'playbook':
runner = PlaybookRunner( runner = PlaybookRunner(
self.inventory_path, self.job.playbook.work_path self.inventory_path, self.job.playbook.entry
) )
else: else:
raise Exception("unsupported job type") raise Exception("unsupported job type")

View File

@ -5,6 +5,7 @@ from django.conf import settings
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ops.exception import PlaybookNoValidEntry
from orgs.mixins.models import JMSOrgBaseModel from orgs.mixins.models import JMSOrgBaseModel
@ -16,5 +17,10 @@ class Playbook(JMSOrgBaseModel):
comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
@property @property
def work_path(self): def entry(self):
return os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__(), "main.yaml") work_dir = os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__())
valid_entry = ('main.yml', 'main.yaml', 'main')
for f in os.listdir(work_dir):
if f in valid_entry:
return os.path.join(work_dir, f)
raise PlaybookNoValidEntry

View File

@ -1,6 +1,7 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import gettext_lazy as _
from django_celery_beat.models import PeriodicTask from django_celery_beat.models import PeriodicTask
@ -35,10 +36,12 @@ class CeleryTaskSerializer(serializers.ModelSerializer):
class CeleryTaskExecutionSerializer(serializers.ModelSerializer): class CeleryTaskExecutionSerializer(serializers.ModelSerializer):
is_success = serializers.BooleanField(required=False, read_only=True, label=_('Success'))
class Meta: class Meta:
model = CeleryTaskExecution model = CeleryTaskExecution
fields = [ fields = [
"id", "name", "args", "kwargs", "time_cost", "timedelta", "state", "is_finished", "date_published", "id", "name", "args", "kwargs", "time_cost", "timedelta", "is_success", "is_finished", "date_published",
"date_start", "date_start",
"date_finished" "date_finished"
] ]

View File

@ -1,18 +1,19 @@
from django.utils.translation import ugettext as _
from rest_framework import serializers from rest_framework import serializers
from common.drf.fields import ReadableHiddenField from common.drf.fields import ReadableHiddenField
from ops.mixin import PeriodTaskSerializerMixin from ops.mixin import PeriodTaskSerializerMixin
from ops.models import Job, JobExecution from ops.models import Job, JobExecution
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
_all_ = []
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) owner = ReadableHiddenField(default=serializers.CurrentUserDefault())
run_after_save = serializers.BooleanField(label=_("Run after save"), read_only=True, default=False, required=False)
class Meta: class Meta:
model = Job model = Job
read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost"] read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost",
"run_after_save"]
fields = read_only_fields + [ fields = read_only_fields + [
"name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner",
"use_parameter_define", "use_parameter_define",

View File

@ -14,6 +14,7 @@ def parse_playbook_name(path):
class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer): class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
path = serializers.FileField(required=False)
def create(self, validated_data): def create(self, validated_data):
name = validated_data.get('name') name = validated_data.get('name')
@ -26,5 +27,5 @@ class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerial
model = Playbook model = Playbook
read_only_fields = ["id", "date_created", "date_updated"] read_only_fields = ["id", "date_created", "date_updated"]
fields = read_only_fields + [ fields = read_only_fields + [
"id", "name", "comment", "creator", "id", 'path', "name", "comment", "creator",
] ]