mirror of https://github.com/jumpserver/jumpserver
Merge branch 'v3' of github.com:jumpserver/jumpserver into v3
commit
c832f762a5
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
@ -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
|
@ -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):
|
||||||
|
|
|
@ -28,8 +28,19 @@ 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:
|
||||||
|
self.run_job(instance, serializer)
|
||||||
|
|
||||||
|
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)
|
task = run_ops_job_execution.delay(execution.id)
|
||||||
set_task_to_serializer_data(serializer, task)
|
set_task_to_serializer_data(serializer, task)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.')
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
]
|
]
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue