Merge branch 'dev' of http://github.com/jumpserver/jumpserver into pr@dev@fix_operatelog_root_org

pull/9689/head
jiangweidong 2023-02-22 15:57:17 +08:00
commit 0dba222796
11 changed files with 97 additions and 100 deletions

View File

@ -1,57 +1,11 @@
from copy import deepcopy
from accounts.automations.methods import platform_automation_methods from accounts.automations.methods import platform_automation_methods
from accounts.const import SecretType
from assets.automations.base.manager import BasePlaybookManager from assets.automations.base.manager import BasePlaybookManager
from common.utils import get_logger from common.utils import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
class VerifyHostCallbackMixin:
execution: callable
get_accounts: callable
host_account_mapper: dict
generate_public_key: callable
generate_private_key_path: callable
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super().host_callback(
host, asset=asset, account=account,
automation=automation, path_dir=path_dir, **kwargs
)
if host.get('error'):
return host
accounts = asset.accounts.all()
accounts = self.get_accounts(account, accounts)
inventory_hosts = []
for account in accounts:
h = deepcopy(host)
h['name'] += '(' + account.username + ')'
self.host_account_mapper[h['name']] = account
secret = account.secret
private_key_path = None
if account.secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(secret, path_dir)
secret = self.generate_public_key(secret)
h['secret_type'] = account.secret_type
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': account.secret_type,
'secret': secret,
'private_key_path': private_key_path
}
inventory_hosts.append(h)
return inventory_hosts
class AccountBasePlaybookManager(BasePlaybookManager): class AccountBasePlaybookManager(BasePlaybookManager):
pass
@property @property
def platform_automation_methods(self): def platform_automation_methods(self):

View File

@ -87,7 +87,9 @@ class ChangeSecretManager(AccountBasePlaybookManager):
accounts = accounts.filter(secret_type=self.secret_type) accounts = accounts.filter(secret_type=self.secret_type)
if not accounts: if not accounts:
print('没有发现待改密账号: %s 用户名: %s 类型: %s' % (asset.name, account.username, self.secret_type)) print('没有发现待改密账号: %s 用户名: %s 类型: %s' % (
asset.name, self.snapshot_account_usernames, self.secret_type
))
return [] return []
method_attr = getattr(automation, self.method_type() + '_method') method_attr = getattr(automation, self.method_type() + '_method')
@ -143,7 +145,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
recorder.save() recorder.save()
account = recorder.account account = recorder.account
if not account: if not account:
print("Account not found, deleted ?", recorder) print("Account not found, deleted ?")
return return
account.secret = recorder.new_secret account.secret = recorder.new_secret
account.save(update_fields=['secret']) account.save(update_fields=['secret'])

View File

@ -59,11 +59,11 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
accounts = asset.accounts.all() accounts = asset.accounts.all()
accounts = self.get_accounts(account, accounts) accounts = self.get_accounts(account, accounts)
inventory_hosts = [] inventory_hosts = []
host['secret_type'] = self.secret_type host['secret_type'] = self.secret_type
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY: if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
print(f'Windows {asset} does not support ssh key push \n') msg = f'Windows {asset} does not support ssh key push \n'
print(msg)
return inventory_hosts return inventory_hosts
for account in accounts: for account in accounts:

View File

@ -1,11 +1,11 @@
- hosts: demo - hosts: demo
gather_facts: no gather_facts: no
tasks: tasks:
- name: Verify account - name: Verify account connectivity
ansible.builtin.ping:
become: no become: no
ansible.builtin.ping:
vars: vars:
ansible_become: no
ansible_user: "{{ account.username }}" ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}" ansible_password: "{{ account.secret }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}" ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no

View File

@ -1,18 +1,67 @@
import os
from copy import deepcopy
from django.db.models import QuerySet from django.db.models import QuerySet
from accounts.const import AutomationTypes, Connectivity from accounts.const import AutomationTypes, Connectivity, SecretType
from common.utils import get_logger from common.utils import get_logger
from ..base.manager import VerifyHostCallbackMixin, AccountBasePlaybookManager from ..base.manager import AccountBasePlaybookManager
logger = get_logger(__name__) logger = get_logger(__name__)
class VerifyAccountManager(VerifyHostCallbackMixin, AccountBasePlaybookManager): class VerifyAccountManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.host_account_mapper = {} self.host_account_mapper = {}
def prepare_runtime_dir(self):
path = super().prepare_runtime_dir()
ansible_config_path = os.path.join(path, 'ansible.cfg')
with open(ansible_config_path, 'w') as f:
f.write('[ssh_connection]\n')
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
return path
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super().host_callback(
host, asset=asset, account=account,
automation=automation, path_dir=path_dir, **kwargs
)
if host.get('error'):
return host
# host['ssh_args'] = '-o ControlMaster=no -o ControlPersist=no'
accounts = asset.accounts.all()
accounts = self.get_accounts(account, accounts)
inventory_hosts = []
for account in accounts:
h = deepcopy(host)
h['name'] += '(' + account.username + ')'
self.host_account_mapper[h['name']] = account
secret = account.secret
private_key_path = None
if account.secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(secret, path_dir)
secret = self.generate_public_key(secret)
h['secret_type'] = account.secret_type
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': account.secret_type,
'secret': secret,
'private_key_path': private_key_path
}
inventory_hosts.append(h)
# print("Host: ")
# print(self.json_dumps(inventory_hosts))
return inventory_hosts
@classmethod @classmethod
def method_type(cls): def method_type(cls):
return AutomationTypes.verify_account return AutomationTypes.verify_account

View File

@ -101,9 +101,10 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
class Meta(BaseAccountSerializer.Meta): class Meta(BaseAccountSerializer.Meta):
model = Account model = Account
fields = BaseAccountSerializer.Meta.fields \ fields = BaseAccountSerializer.Meta.fields + [
+ ['su_from', 'asset'] \ 'su_from', 'asset', 'template',
+ ['template', 'push_now', 'source'] 'push_now', 'source', 'connectivity'
]
extra_kwargs = { extra_kwargs = {
**BaseAccountSerializer.Meta.extra_kwargs, **BaseAccountSerializer.Meta.extra_kwargs,
'name': {'required': False, 'allow_null': True}, 'name': {'required': False, 'allow_null': True},

View File

@ -1,12 +1,11 @@
import json import json
import os import os
import shutil import shutil
import yaml
from collections import defaultdict from collections import defaultdict
from hashlib import md5 from hashlib import md5
from socket import gethostname from socket import gethostname
import yaml
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -55,8 +54,7 @@ class BasePlaybookManager:
def get_assets_group_by_platform(self): def get_assets_group_by_platform(self):
return self.execution.all_assets_group_by_platform() return self.execution.all_assets_group_by_platform()
@lazyproperty def prepare_runtime_dir(self):
def runtime_dir(self):
ansible_dir = settings.ANSIBLE_DIR ansible_dir = settings.ANSIBLE_DIR
task_name = self.execution.snapshot['name'] task_name = self.execution.snapshot['name']
dir_name = '{}_{}'.format(task_name.replace(' ', '_'), self.execution.id) dir_name = '{}_{}'.format(task_name.replace(' ', '_'), self.execution.id)
@ -66,8 +64,14 @@ class BasePlaybookManager:
) )
if not os.path.exists(path): if not os.path.exists(path):
os.makedirs(path, exist_ok=True, mode=0o755) os.makedirs(path, exist_ok=True, mode=0o755)
return path
@lazyproperty
def runtime_dir(self):
path = self.prepare_runtime_dir()
if settings.DEBUG_DEV: if settings.DEBUG_DEV:
print(f'Ansible runtime dir: {path}') msg = 'Ansible runtime dir: {}'.format(path)
print(msg)
return path return path
@staticmethod @staticmethod
@ -158,7 +162,8 @@ class BasePlaybookManager:
def get_runners(self): def get_runners(self):
assets_group_by_platform = self.get_assets_group_by_platform() assets_group_by_platform = self.get_assets_group_by_platform()
if settings.DEBUG_DEV: if settings.DEBUG_DEV:
print("assets_group_by_platform: {}".format(assets_group_by_platform)) msg = 'Assets group by platform: {}'.format(dict(assets_group_by_platform))
print(msg)
runners = [] runners = []
for platform, assets in assets_group_by_platform.items(): for platform, assets in assets_group_by_platform.items():
assets_bulked = [assets[i:i + self.bulk_size] for i in range(0, len(assets), self.bulk_size)] assets_bulked = [assets[i:i + self.bulk_size] for i in range(0, len(assets), self.bulk_size)]
@ -199,8 +204,7 @@ class BasePlaybookManager:
if state == 'ok': if state == 'ok':
self.on_host_success(host, result) self.on_host_success(host, result)
elif state == 'skipped': elif state == 'skipped':
# TODO pass
print('skipped: ', hosts)
else: else:
error = hosts.get(host) error = hosts.get(host)
self.on_host_error(host, error, result) self.on_host_error(host, error, result)
@ -214,10 +218,14 @@ class BasePlaybookManager:
d = json.load(f) d = json.load(f)
return d return d
@staticmethod
def json_dumps(data):
return json.dumps(data, indent=4, sort_keys=True)
@staticmethod @staticmethod
def json_to_file(path, data): def json_to_file(path, data):
with open(path, 'w') as f: with open(path, 'w') as f:
json.dump(data, f) json.dump(data, f, indent=4, sort_keys=True)
def local_gateway_prepare(self, runner): def local_gateway_prepare(self, runner):
info = self.file_to_json(runner.inventory) info = self.file_to_json(runner.inventory)
@ -241,9 +249,8 @@ class BasePlaybookManager:
def local_gateway_clean(self, runner): def local_gateway_clean(self, runner):
servers = self.gateway_servers.get(runner.id, []) servers = self.gateway_servers.get(runner.id, [])
try:
for s in servers: for s in servers:
print('Server down: %s' % s) try:
s.stop() s.stop()
except Exception: except Exception:
pass pass
@ -252,36 +259,17 @@ class BasePlaybookManager:
self.local_gateway_prepare(runner) self.local_gateway_prepare(runner)
def after_runner_end(self, runner): def after_runner_end(self, runner):
self.delete_sensitive_data(runner.inventory)
self.local_gateway_clean(runner) self.local_gateway_clean(runner)
def delete_sensitive_data(self, path): def delete_runtime_dir(self):
if settings.DEBUG_DEV: if settings.DEBUG_DEV:
return return
shutil.rmtree(self.runtime_dir)
d = self.file_to_json(path)
def delete_keys(d, keys_to_delete):
"""
递归函数删除嵌套字典中的指定键
"""
if not isinstance(d, dict):
return d
keys = list(d.keys())
for key in keys:
if key in keys_to_delete:
del d[key]
else:
delete_keys(d[key], keys_to_delete)
return d
d = delete_keys(d, ['secret', 'ansible_password'])
self.json_to_file(path, d)
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
runners = self.get_runners() runners = self.get_runners()
if len(runners) > 1: if len(runners) > 1:
print("### 分次执行开始任务, 总共 {}\n".format(len(runners))) print("### 分次执行任务, 总共 {}\n".format(len(runners)))
elif len(runners) == 1: elif len(runners) == 1:
print(">>> 开始执行任务\n") print(">>> 开始执行任务\n")
else: else:
@ -303,3 +291,4 @@ class BasePlaybookManager:
self.execution.status = 'success' self.execution.status = 'success'
self.execution.date_finished = timezone.now() self.execution.date_finished = timezone.now()
self.execution.save() self.execution.save()
self.delete_runtime_dir()

View File

@ -329,9 +329,8 @@ class AllTypes(ChoicesMixin):
internal_platforms.append(d['name']) internal_platforms.append(d['name'])
user_platforms = platform_cls.objects.exclude(name__in=internal_platforms) user_platforms = platform_cls.objects.exclude(name__in=internal_platforms)
user_platforms.update(internal=False)
for platform in user_platforms: for platform in user_platforms:
print("\t- Update platform: {}".format(platform.name)) print("\t- Update platform: {}".format(platform.name))
platform_data = cls.get_type_default_platform(platform.category, platform.type) platform_data = cls.get_type_default_platform(platform.category, platform.type)
cls.create_or_update_by_platform_data(platform.name, platform_data, platform_cls=platform_cls) cls.create_or_update_by_platform_data(platform.name, platform_data, platform_cls=platform_cls)
user_platforms.update(internal=False)

View File

@ -60,6 +60,7 @@ exclude_permissions = (
('accounts', 'automationexecution', '*', 'automationexecution'), ('accounts', 'automationexecution', '*', 'automationexecution'),
('accounts', 'accountbackupexecution', 'delete,change', 'accountbackupexecution'), ('accounts', 'accountbackupexecution', 'delete,change', 'accountbackupexecution'),
('accounts', 'changesecretrecord', 'add,delete,change', 'changesecretrecord'), ('accounts', 'changesecretrecord', 'add,delete,change', 'changesecretrecord'),
('accounts', 'account', 'change', 'accountsecret'),
('perms', 'userassetgrantedtreenoderelation', '*', '*'), ('perms', 'userassetgrantedtreenoderelation', '*', '*'),
('perms', 'permedaccount', '*', '*'), ('perms', 'permedaccount', '*', '*'),

View File

@ -58,7 +58,7 @@ class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
def applets(self, request, *args, **kwargs): def applets(self, request, *args, **kwargs):
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)
applet_id = serializer.validated_data.pop('applet_id') applet_id = serializer.validated_data.pop('applet_id', '')
instance = serializer.save() instance = serializer.save()
task = run_applet_host_deployment_install_applet.delay(instance.id, applet_id) task = run_applet_host_deployment_install_applet.delay(instance.id, applet_id)
instance.save_task(task.id) instance.save_task(task.id)

View File

@ -112,7 +112,9 @@ class Applet(JMSBaseModel):
def select_host_account(self): def select_host_account(self):
# 选择激活的发布机 # 选择激活的发布机
hosts = list(self.hosts.filter(is_active=True).all()) hosts = [item for item in self.hosts.filter(is_active=True).all()
if item.load != 'offline']
if not hosts: if not hosts:
return None return None