merge: with merge remote

pull/9019/head
ibuler 2022-11-04 11:43:34 +08:00
commit fbf65f437a
24 changed files with 312 additions and 86 deletions

View File

@ -4,6 +4,7 @@ from rest_framework import status, viewsets
from rest_framework.response import Response from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from common.const.choices import Trigger
from assets import serializers from assets import serializers
from assets.tasks import execute_account_backup_plan from assets.tasks import execute_account_backup_plan
from assets.models import ( from assets.models import (
@ -38,9 +39,7 @@ class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
pid = serializer.data.get('plan') pid = serializer.data.get('plan')
task = execute_account_backup_plan.delay( task = execute_account_backup_plan.delay(pid=pid, trigger=Trigger.manual)
pid=pid, trigger=AccountBackupPlanExecution.Trigger.manual
)
return Response({'task': task.id}, status=status.HTTP_201_CREATED) return Response({'task': task.id}, status=status.HTTP_201_CREATED)
def filter_queryset(self, queryset): def filter_queryset(self, queryset):

View File

@ -82,15 +82,16 @@ class AssetAccountHandler(BaseAccountHandler):
# TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作 # TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作
qs = Account.objects.filter( qs = Account.objects.filter(
asset__platform__category__in=categories asset__platform__type__in=categories
).annotate(category=F('asset__platform__category')) ).annotate(category=F('asset__platform__type'))
print(qs, categories)
if not qs.exists(): if not qs.exists():
return data_map return data_map
category_dict = {} category_dict = {}
for i in AllTypes.grouped_choices_to_objs(): for i in AllTypes.grouped_choices_to_objs():
for j in i['children']: for j in i['children']:
category_dict[j['value']] = j['label'] category_dict[j['value']] = j['display_name']
header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first())) header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first()))
account_category_map = defaultdict(list) account_category_map = defaultdict(list)

View File

@ -12,7 +12,7 @@ from .handlers import AccountBackupHandler
logger = get_logger(__name__) logger = get_logger(__name__)
class AccountBackupExecutionManager: class AccountBackupManager:
def __init__(self, execution): def __init__(self, execution):
self.execution = execution self.execution = execution
self.date_start = timezone.now() self.date_start = timezone.now()

View File

@ -1,17 +1,28 @@
import os
import time
import random import random
import string import string
from copy import deepcopy from copy import deepcopy
from openpyxl import Workbook
from collections import defaultdict from collections import defaultdict
from django.utils import timezone from django.utils import timezone
from django.conf import settings
from common.utils import lazyproperty, gen_key_pair from common.utils.timezone import local_now_display
from common.utils.file import encrypt_and_compress_zip_file
from common.utils import get_logger, lazyproperty, gen_key_pair
from users.models import User
from assets.models import ChangeSecretRecord from assets.models import ChangeSecretRecord
from assets.notifications import ChangeSecretExecutionTaskMsg
from assets.serializers import ChangeSecretRecordBackUpSerializer
from assets.const import ( from assets.const import (
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES
) )
from ..base.manager import BasePlaybookManager from ..base.manager import BasePlaybookManager
logger = get_logger(__name__)
class ChangeSecretManager(BasePlaybookManager): class ChangeSecretManager(BasePlaybookManager):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -125,7 +136,7 @@ class ChangeSecretManager(BasePlaybookManager):
new_secret = self.get_secret() new_secret = self.get_secret()
recorder = ChangeSecretRecord( recorder = ChangeSecretRecord(
account=account, execution=self.execution, asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret, old_secret=account.secret, new_secret=new_secret,
) )
records.append(recorder) records.append(recorder)
@ -172,4 +183,49 @@ class ChangeSecretManager(BasePlaybookManager):
recorder.save() recorder.save()
def on_runner_failed(self, runner, e): def on_runner_failed(self, runner, e):
pass logger.error("Change secret error: ", e)
def run(self, *args, **kwargs):
super().run(*args, **kwargs)
recorders = self.name_recorder_mapper.values()
recorders = list(recorders)
self.send_recorder_mail(recorders)
def send_recorder_mail(self, recorders):
recipients = self.execution.recipients
if not recorders or not recipients:
return
recipients = User.objects.filter(id__in=list(recipients))
name = self.execution.snapshot['name']
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
filename = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.xlsx')
if not self.create_file(recorders, filename):
return
for user in recipients:
attachments = []
if user.secret_key:
password = user.secret_key.encode('utf8')
attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, [filename])
attachments = [attachment]
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
os.remove(filename)
@staticmethod
def create_file(recorders, filename):
serializer_cls = ChangeSecretRecordBackUpSerializer
serializer = serializer_cls(recorders, many=True)
header = [v.label for v in serializer.child.fields.values()]
rows = [list(row.values()) for row in serializer.data]
if not rows:
return False
rows.insert(0, header)
wb = Workbook(filename)
ws = wb.create_sheet('Sheet1')
for row in rows:
ws.append(row)
wb.save(filename)
return True

View File

@ -3,6 +3,7 @@ from .gather_facts.manager import GatherFactsManager
from .gather_accounts.manager import GatherAccountsManager from .gather_accounts.manager import GatherAccountsManager
from .verify_account.manager import VerifyAccountManager from .verify_account.manager import VerifyAccountManager
from .push_account.manager import PushAccountManager from .push_account.manager import PushAccountManager
from .backup_account.manager import AccountBackupManager
from ..const import AutomationTypes from ..const import AutomationTypes
@ -13,6 +14,8 @@ class ExecutionManager:
AutomationTypes.gather_accounts: GatherAccountsManager, AutomationTypes.gather_accounts: GatherAccountsManager,
AutomationTypes.verify_account: VerifyAccountManager, AutomationTypes.verify_account: VerifyAccountManager,
AutomationTypes.push_account: PushAccountManager, AutomationTypes.push_account: PushAccountManager,
# TODO 后期迁移到自动化策略中
'backup_account': AccountBackupManager,
} }
def __init__(self, execution): def __init__(self, execution):
@ -21,4 +24,3 @@ class ExecutionManager:
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
return self._runner.run(*args, **kwargs) return self._runner.run(*args, **kwargs)

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.14 on 2022-11-03 08:44
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0108_auto_20221027_1053'),
]
operations = [
migrations.RenameField(
model_name='accountbackupplan',
old_name='categories',
new_name='types',
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.14 on 2022-11-03 13:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0109_rename_categories_to_types'),
]
operations = [
migrations.AddField(
model_name='changesecretrecord',
name='asset',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.asset'),
),
]

View File

@ -111,6 +111,13 @@ class AutomationExecution(OrgModelMixin):
def manager_type(self): def manager_type(self):
return self.snapshot['type'] return self.snapshot['type']
@property
def recipients(self):
recipients = self.snapshot.get('recipients')
if not recipients:
return []
return recipients.values()
def start(self): def start(self):
from assets.automations.endpoint import ExecutionManager from assets.automations.endpoint import ExecutionManager
manager = ExecutionManager(execution=self) manager = ExecutionManager(execution=self)

View File

@ -51,6 +51,7 @@ class ChangeSecretAutomation(BaseAutomation):
class ChangeSecretRecord(JMSBaseModel): class ChangeSecretRecord(JMSBaseModel):
execution = models.ForeignKey('assets.AutomationExecution', on_delete=models.CASCADE) execution = models.ForeignKey('assets.AutomationExecution', on_delete=models.CASCADE)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
account = models.ForeignKey('assets.Account', on_delete=models.CASCADE, null=True) account = models.ForeignKey('assets.Account', on_delete=models.CASCADE, null=True)
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret')) old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))

View File

@ -2,7 +2,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid import uuid
from functools import reduce
from celery import current_task from celery import current_task
from django.db import models from django.db import models
@ -11,9 +10,9 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from ops.mixin import PeriodTaskModelMixin from ops.mixin import PeriodTaskModelMixin
from common.utils import get_logger from common.utils import get_logger
from common.const.choices import Trigger
from common.db.encoder import ModelJSONFieldEncoder from common.db.encoder import ModelJSONFieldEncoder
from common.mixins.models import CommonModelMixin from common.mixins.models import CommonModelMixin
from common.const.choices import Trigger
__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution'] __all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution']
@ -22,7 +21,7 @@ logger = get_logger(__file__)
class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
categories = models.JSONField(default=list) types = models.JSONField(default=list)
recipients = models.ManyToManyField( recipients = models.ManyToManyField(
'users.User', related_name='recipient_escape_route_plans', blank=True, 'users.User', related_name='recipient_escape_route_plans', blank=True,
verbose_name=_("Recipient") verbose_name=_("Recipient")
@ -53,7 +52,7 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
'crontab': self.crontab, 'crontab': self.crontab,
'org_id': self.org_id, 'org_id': self.org_id,
'created_by': self.created_by, 'created_by': self.created_by,
'categories': self.categories, 'types': self.types,
'recipients': { 'recipients': {
str(recipient.id): (str(recipient), bool(recipient.secret_key)) str(recipient.id): (str(recipient), bool(recipient.secret_key))
for recipient in self.recipients.all() for recipient in self.recipients.all()
@ -100,9 +99,9 @@ class AccountBackupPlanExecution(OrgModelMixin):
verbose_name = _('Account backup execution') verbose_name = _('Account backup execution')
@property @property
def categories(self): def types(self):
categories = self.plan_snapshot.get('categories') types = self.plan_snapshot.get('types')
return categories return types
@property @property
def recipients(self): def recipients(self):
@ -111,7 +110,11 @@ class AccountBackupPlanExecution(OrgModelMixin):
return [] return []
return recipients.values() return recipients.values()
@property
def manager_type(self):
return 'backup_account'
def start(self): def start(self):
from ..task_handlers import ExecutionManager from assets.automations.endpoint import ExecutionManager
manager = ExecutionManager(execution=self) manager = ExecutionManager(execution=self)
return manager.run() return manager.run()

View File

@ -2,10 +2,9 @@
# #
import io import io
import os import os
import uuid import sshpubkeys
from hashlib import md5 from hashlib import md5
import sshpubkeys
from django.db import models from django.db import models
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 _
@ -14,11 +13,11 @@ from django.db.models import QuerySet
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,
random_string, ssh_pubkey_gen, random_string, ssh_pubkey_gen, lazyproperty
) )
from common.db import fields from common.db import fields
from assets.const import Connectivity
from orgs.mixins.models import JMSOrgBaseModel from orgs.mixins.models import JMSOrgBaseModel
from assets.const import Connectivity, SecretType
logger = get_logger(__file__) logger = get_logger(__file__)
@ -48,12 +47,6 @@ class AbsConnectivity(models.Model):
class BaseAccount(JMSOrgBaseModel): class BaseAccount(JMSOrgBaseModel):
class SecretType(models.TextChoices):
password = 'password', _('Password')
ssh_key = 'ssh_key', _('SSH key')
access_key = 'access_key', _('Access key')
token = 'token', _('Token')
name = models.CharField(max_length=128, verbose_name=_("Name")) name = models.CharField(max_length=128, verbose_name=_("Name"))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
secret_type = models.CharField( secret_type = models.CharField(
@ -65,28 +58,34 @@ class BaseAccount(JMSOrgBaseModel):
comment = models.TextField(blank=True, verbose_name=_('Comment')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
@property
def password(self):
return self.secret
@property @property
def has_secret(self): def has_secret(self):
return bool(self.secret) return bool(self.secret)
@property @property
def private_key(self): def specific(self):
if self.secret_type == self.SecretType.ssh_key: data = {}
return self.secret if self.secret_type != SecretType.ssh_key:
return None return data
data['ssh_key_fingerprint'] = self.ssh_key_fingerprint
return data
@property @property
def public_key(self): def private_key(self):
return '' if self.secret_type == SecretType.ssh_key:
return self.secret
return None
@private_key.setter @private_key.setter
def private_key(self, value): def private_key(self, value):
self.secret = value self.secret = value
self.secret_type = 'private_key' self.secret_type = SecretType.ssh_key
@lazyproperty
def public_key(self):
if self.secret_type == SecretType.ssh_key:
return ssh_pubkey_gen(private_key=self.private_key)
return None
@property @property
def ssh_key_fingerprint(self): def ssh_key_fingerprint(self):
@ -94,7 +93,7 @@ class BaseAccount(JMSOrgBaseModel):
public_key = self.public_key public_key = self.public_key
elif self.private_key: elif self.private_key:
try: try:
public_key = ssh_pubkey_gen(private_key=self.private_key, password=self.password) public_key = ssh_pubkey_gen(private_key=self.private_key)
except IOError as e: except IOError as e:
return str(e) return str(e)
else: else:
@ -107,14 +106,14 @@ class BaseAccount(JMSOrgBaseModel):
@property @property
def private_key_obj(self): def private_key_obj(self):
if self.private_key: if self.private_key:
key_obj = ssh_key_string_to_obj(self.private_key, password=self.password) key_obj = ssh_key_string_to_obj(self.private_key)
return key_obj return key_obj
else: else:
return None return None
@property @property
def private_key_path(self): def private_key_path(self):
if not self.secret_type != 'ssh_key' or not self.secret: if not self.secret_type != SecretType.ssh_key or not self.secret:
return None return None
project_dir = settings.PROJECT_DIR project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp') tmp_dir = os.path.join(project_dir, 'tmp')
@ -156,7 +155,6 @@ class BaseAccount(JMSOrgBaseModel):
return { return {
'name': self.name, 'name': self.name,
'username': self.username, 'username': self.username,
'password': self.password,
'public_key': self.public_key, 'public_key': self.public_key,
} }

View File

@ -15,11 +15,35 @@ class AccountBackupExecutionTaskMsg(object):
def message(self): def message(self):
name = self.name name = self.name
if self.user.secret_key: if self.user.secret_key:
return _('{} - The account backup passage task has been completed. See the attachment for details').format(name) return _('{} - The account backup passage task has been completed. See the attachment for details').format(
name)
return _("{} - The account backup passage task has been completed: the encryption password has not been set - " return _("{} - The account backup passage task has been completed: the encryption password has not been set - "
"please go to personal information -> file encryption password to set the encryption password").format(name) "please go to personal information -> file encryption password to set the encryption password").format(
name)
def publish(self, attachment_list=None): def publish(self, attachment_list=None):
send_mail_attachment_async.delay( send_mail_attachment_async(
self.subject, self.message, [self.user.email], attachment_list self.subject, self.message, [self.user.email], attachment_list
) )
class ChangeSecretExecutionTaskMsg(object):
subject = _('Notification of implementation result of encryption change plan')
def __init__(self, name: str, user: User):
self.name = name
self.user = user
@property
def message(self):
name = self.name
if self.user.secret_key:
return _('{} - The encryption change task has been completed. See the attachment for details').format(name)
return _("{} - The encryption change task has been completed: the encryption password has not been set - "
"please go to personal information -> file encryption password to set the encryption password").format(
name)
def publish(self, attachments=None):
send_mail_attachment_async(
self.subject, self.message, [self.user.email], attachments
)

View File

@ -11,3 +11,4 @@ from .account import *
from assets.serializers.account.backup import * from assets.serializers.account.backup import *
from .platform import * from .platform import *
from .cagegory import * from .cagegory import *
from .automation import *

View File

@ -3,6 +3,7 @@ from rest_framework import serializers
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from common.drf.fields import ObjectRelatedField from common.drf.fields import ObjectRelatedField
from assets.tasks import push_accounts_to_assets
from assets.models import Account, AccountTemplate, Asset from assets.models import Account, AccountTemplate, Asset
from .base import BaseAccountSerializer from .base import BaseAccountSerializer
@ -47,8 +48,7 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
instance = super().create(validated_data) instance = super().create(validated_data)
if self.push_now: if self.push_now:
# Todo: push it push_accounts_to_assets.delay([instance.id], [instance.asset_id])
print("Start push account to asset")
return instance return instance

View File

@ -21,9 +21,13 @@ class BaseAccountSerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = BaseAccount model = BaseAccount
fields_mini = ['id', 'name', 'username'] fields_mini = ['id', 'name', 'username']
fields_small = fields_mini + ['privileged', 'secret_type', 'secret', 'has_secret'] fields_small = fields_mini + ['privileged', 'secret_type', 'secret', 'has_secret', 'specific']
fields_other = ['created_by', 'date_created', 'date_updated', 'comment'] fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
fields = fields_small + fields_other fields = fields_small + fields_other
read_only_fields = [
'has_secret', 'specific',
'date_verified', 'created_by', 'date_created',
]
extra_kwargs = { extra_kwargs = {
'secret': {'write_only': True}, 'secret': {'write_only': True},
'passphrase': {'write_only': True}, 'passphrase': {'write_only': True},

View File

@ -1,6 +1,3 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from assets.models import AccountTemplate from assets.models import AccountTemplate
from .base import BaseAccountSerializer from .base import BaseAccountSerializer
@ -9,15 +6,14 @@ class AccountTemplateSerializer(BaseAccountSerializer):
class Meta(BaseAccountSerializer.Meta): class Meta(BaseAccountSerializer.Meta):
model = AccountTemplate model = AccountTemplate
@classmethod # @classmethod
def validate_required(cls, attrs): # def validate_required(cls, attrs):
# Todo: why ? # # TODO 选择模版后检查一些必填项
required_field_dict = {} # required_field_dict = {}
error = _('This field is required.') # error = _('This field is required.')
for k, v in cls().fields.items(): # for k, v in cls().fields.items():
if v.required and k not in attrs: # if v.required and k not in attrs:
required_field_dict[k] = error # required_field_dict[k] = error
if not required_field_dict: # if not required_field_dict:
return # return
raise serializers.ValidationError(required_field_dict) # raise serializers.ValidationError(required_field_dict)

View File

@ -0,0 +1,35 @@
from django.utils.translation import ugettext as _
from rest_framework import serializers
from common.utils import get_logger
from assets.models import ChangeSecretRecord
logger = get_logger(__file__)
class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
asset = serializers.SerializerMethodField(label=_('Asset'))
account = serializers.SerializerMethodField(label=_('Account'))
is_success = serializers.SerializerMethodField(label=_('Is success'))
class Meta:
model = ChangeSecretRecord
fields = [
'id', 'asset', 'account', 'old_secret', 'new_secret',
'status', 'error', 'is_success'
]
@staticmethod
def get_asset(instance):
return str(instance.asset)
@staticmethod
def get_account(instance):
return str(instance.account)
@staticmethod
def get_is_success(obj):
if obj.status == 'success':
return _("Success")
return _("Failed")

View File

@ -11,8 +11,9 @@ __all__ = [
@org_aware_func("assets") @org_aware_func("assets")
def push_accounts_to_assets_util(accounts, assets, task_name): def push_accounts_to_assets_util(accounts, assets):
from assets.models import PushAccountAutomation from assets.models import PushAccountAutomation
task_name = gettext_noop("Push accounts to assets")
task_name = PushAccountAutomation.generate_unique_name(task_name) task_name = PushAccountAutomation.generate_unique_name(task_name)
account_usernames = list(accounts.values_list('username', flat=True)) account_usernames = list(accounts.values_list('username', flat=True))
@ -33,5 +34,4 @@ def push_accounts_to_assets(account_ids, asset_ids):
assets = Asset.objects.get(id=asset_ids) assets = Asset.objects.get(id=asset_ids)
accounts = Account.objects.get(id=account_ids) accounts = Account.objects.get(id=account_ids)
task_name = gettext_noop("Push accounts to assets") return push_accounts_to_assets_util(accounts, assets)
return push_accounts_to_assets_util(accounts, assets, task_name)

View File

@ -4,3 +4,4 @@ from .common import *
from .nodes import * from .nodes import *
from .assets import * from .assets import *
from .nodes_with_assets import * from .nodes_with_assets import *
from .accounts import *

View File

@ -0,0 +1,24 @@
from rest_framework import generics
from assets.serializers import AccountSerializer
from perms.utils.account import PermAccountUtil
from .mixin import RoleAdminMixin, RoleUserMixin
__all__ = ['UserAllGrantedAccountsApi', 'MyAllGrantedAccountsApi']
class UserAllGrantedAccountsApi(RoleAdminMixin, generics.ListAPIView):
""" 授权给用户的所有账号列表 """
serializer_class = AccountSerializer
filterset_fields = ("name", "username", "privileged", "version")
search_fields = filterset_fields
def get_queryset(self):
util = PermAccountUtil()
accounts = util.get_perm_accounts_for_user(self.user)
return accounts
class MyAllGrantedAccountsApi(RoleUserMixin, UserAllGrantedAccountsApi):
""" 授权给我的所有账号列表 """
pass

View File

@ -58,9 +58,12 @@ user_permission_urlpatterns = [
# 收藏的资产 # 收藏的资产
path('<uuid:pk>/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), path('<uuid:pk>/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
# v3 中上面的 API 基本不用动
# 获取所有和资产-用户关联的账号列表 # 获取授权给用户的所有账号
path('<uuid:pk>/accounts/', api.UserAllGrantedAccountsApi.as_view(), name='user-accounts'),
path('accounts/', api.MyAllGrantedAccountsApi.as_view(), name='my-accounts'),
# 获取授权给用户某个资产的所有账号
path('<uuid:pk>/assets/<uuid:asset_id>/accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'), path('<uuid:pk>/assets/<uuid:asset_id>/accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'),
path('assets/<uuid:asset_id>/accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), path('assets/<uuid:asset_id>/accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'),
# 用户登录资产的特殊账号, @INPUT, @USER 等 # 用户登录资产的特殊账号, @INPUT, @USER 等

View File

@ -27,13 +27,19 @@ class DeployAppletHostManager:
def generate_playbook(self): def generate_playbook(self):
playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml') playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml')
base_site_url = settings.BASE_SITE_URL
bootstrap_token = settings.BOOTSTRAP_TOKEN
host_id = str(self.deployment.host.id)
if not base_site_url:
base_site_url = "localhost:8080"
with open(playbook_src) as f: with open(playbook_src) as f:
plays = yaml.safe_load(f) plays = yaml.safe_load(f)
for play in plays: for play in plays:
play['vars'].update(self.deployment.host.deploy_options) play['vars'].update(self.deployment.host.deploy_options)
play['vars']['DownloadHost'] = settings.BASE_URL + '/download/' play['vars']['DownloadHost'] = base_site_url + '/download/'
play['vars']['CORE_HOST'] = settings.BASE_URL play['vars']['CORE_HOST'] = base_site_url
play['vars']['BOOTSTRAP_TOKEN'] = settings.BOOSTRAP_TOKEN play['vars']['BOOTSTRAP_TOKEN'] = bootstrap_token
play['vars']['HOST_ID'] = host_id
play['vars']['HOST_NAME'] = self.deployment.host.name play['vars']['HOST_NAME'] = self.deployment.host.name
playbook_dir = os.path.join(self.run_dir, 'playbook') playbook_dir = os.path.join(self.run_dir, 'playbook')
@ -70,6 +76,3 @@ class DeployAppletHostManager:
self.deployment.date_finished = timezone.now() self.deployment.date_finished = timezone.now()
with safe_db_connection(): with safe_db_connection():
self.deployment.save() self.deployment.save()

View File

@ -5,6 +5,7 @@
DownloadHost: https://demo.jumpserver.org/download DownloadHost: https://demo.jumpserver.org/download
Initial: 0 Initial: 0
HOST_NAME: test HOST_NAME: test
HOST_ID: 00000000-0000-0000-0000-000000000000
CORE_HOST: https://demo.jumpserver.org CORE_HOST: https://demo.jumpserver.org
BOOTSTRAP_TOKEN: PleaseChangeMe BOOTSTRAP_TOKEN: PleaseChangeMe
RDS_Licensing: true RDS_Licensing: true
@ -13,6 +14,7 @@
RDS_fSingleSessionPerUser: 1 RDS_fSingleSessionPerUser: 1
RDS_MaxDisconnectionTime: 60000 RDS_MaxDisconnectionTime: 60000
RDS_RemoteAppLogoffTimeLimit: 0 RDS_RemoteAppLogoffTimeLimit: 0
TinkerInstaller: JumpServer-Remoteapp_v0.0.1.exe
tasks: tasks:
- name: Install RDS-Licensing (RDS) - name: Install RDS-Licensing (RDS)
@ -29,16 +31,26 @@
include_management_tools: yes include_management_tools: yes
register: rds_install register: rds_install
- name: Download Jmservisor (jumpserver) - name: Download JumpServer Remoteapp installer (jumpserver)
ansible.windows.win_get_url: ansible.windows.win_get_url:
url: "{{ DownloadHost }}/Jmservisor.msi" url: "{{ DownloadHost }}/{{ TinkerInstaller }}"
dest: "{{ ansible_env.TEMP }}\\Jmservisor.msi" dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
- name: Install the Jmservisor (jumpserver) - name: Install JumpServer Remoteapp agent (jumpserver)
ansible.windows.win_package: ansible.windows.win_package:
path: "{{ ansible_env.TEMP }}\\Jmservisor.msi" path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
arguments:
- /VERYSILENT
- /SUPPRESSMSGBOXES
- /NORESTART
state: present state: present
- name: Set remote-server on the global system path (remote-server)
ansible.windows.win_path:
elements:
- '%USERPROFILE%\AppData\Local\Programs\JumpServer-Remoteapp\'
scope: user
- name: Download python-3.10.8 - name: Download python-3.10.8
ansible.windows.win_get_url: ansible.windows.win_get_url:
url: "{{ DownloadHost }}/python-3.10.8-amd64.exe" url: "{{ DownloadHost }}/python-3.10.8-amd64.exe"
@ -116,12 +128,12 @@
- name: Download chromedriver (chrome) - name: Download chromedriver (chrome)
ansible.windows.win_get_url: ansible.windows.win_get_url:
url: "{{ DownloadHost }}/chromedriver_win32.106.zip" url: "{{ DownloadHost }}/chromedriver_win32.107.zip"
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.106.zip" dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
- name: Unzip chromedriver (chrome) - name: Unzip chromedriver (chrome)
community.windows.win_unzip: community.windows.win_unzip:
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.106.zip" src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
dest: C:\Program Files\JumpServer\drivers dest: C:\Program Files\JumpServer\drivers
- name: Set chromedriver on the global system path (chrome) - name: Set chromedriver on the global system path (chrome)
@ -142,8 +154,27 @@
- /quiet - /quiet
- name: Generate component config - name: Generate component config
ansible.windows.win_shell: > ansible.windows.win_shell:
echo "Todo: Set config" "remoteapp-server config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}
--token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}"
- name: Install remoteapp-server service
ansible.windows.win_shell:
"remoteapp-server service install"
- name: Start remoteapp-server service
ansible.windows.win_shell:
"remoteapp-server service start"
- name: Wait Tinker api health
ansible.windows.win_uri:
url: http://localhost:6068/api/health/
status_code: 200
method: GET
register: _result
until: _result.status_code == 200
retries: 30
delay: 5
- name: Sync all remote applets - name: Sync all remote applets
ansible.windows.win_shell: > ansible.windows.win_shell: >