mirror of https://github.com/jumpserver/jumpserver
Merge branch 'v3' of github.com:jumpserver/jumpserver into v3
commit
d52baf0af5
|
@ -18,7 +18,6 @@ __all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI']
|
|||
|
||||
class AccountViewSet(OrgBulkModelViewSet):
|
||||
model = Account
|
||||
filterset_fields = ("username", "asset", 'name')
|
||||
search_fields = ('username', 'asset__address', 'name')
|
||||
filterset_class = AccountFilterSet
|
||||
serializer_classes = {
|
||||
|
@ -33,7 +32,7 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||
@action(methods=['post'], detail=True, url_path='verify')
|
||||
def verify_account(self, request, *args, **kwargs):
|
||||
account = super().get_object()
|
||||
task = test_accounts_connectivity_manual.delay([account])
|
||||
task = test_accounts_connectivity_manual.delay([account.id])
|
||||
return Response(data={'task': task.id})
|
||||
|
||||
|
||||
|
@ -54,7 +53,6 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
|||
|
||||
class AccountTaskCreateAPI(CreateAPIView):
|
||||
serializer_class = serializers.AccountTaskSerializer
|
||||
filterset_fields = AccountViewSet.filterset_fields
|
||||
search_fields = AccountViewSet.search_fields
|
||||
filterset_class = AccountViewSet.filterset_class
|
||||
|
||||
|
@ -67,8 +65,8 @@ class AccountTaskCreateAPI(CreateAPIView):
|
|||
return queryset
|
||||
|
||||
def perform_create(self, serializer):
|
||||
accounts = self.get_accounts()
|
||||
task = test_accounts_connectivity_manual.delay(accounts)
|
||||
account_ids = self.get_accounts().values_list('id', flat=True)
|
||||
task = test_accounts_connectivity_manual.delay(account_ids)
|
||||
data = getattr(serializer, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(serializer, '_data', data)
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.drf.api import JMSModelViewSet
|
||||
from common.drf.serializers import GroupedChoiceSerializer
|
||||
from assets.models import Platform
|
||||
from assets.serializers import PlatformSerializer, PlatformOpsMethodSerializer
|
||||
from assets.const import AllTypes
|
||||
from assets.playbooks import filter_platform_methods
|
||||
from assets.serializers import PlatformSerializer
|
||||
|
||||
|
||||
__all__ = ['AssetPlatformViewSet']
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .methods import platform_automation_methods, filter_platform_methods
|
|
@ -0,0 +1,14 @@
|
|||
## all connection vars
|
||||
hostname asset_name=name asset_type=type asset_primary_protocol=ssh asset_primary_port=22 asset_protocols=[]
|
||||
|
||||
## local connection
|
||||
hostname ansible_connection=local
|
||||
|
||||
## local connection with gateway
|
||||
hostname ansible_connection=ssh ansible_user=gateway.username ansible_port=gateway.port ansible_host=gateway.host ansible_ssh_private_key_file=gateway.key
|
||||
|
||||
## ssh connection for windows
|
||||
hostname ansible_connection=ssh ansible_shell_type=powershell/cmd ansible_user=windows.username ansible_port=windows.port ansible_host=windows.host ansible_ssh_private_key_file=windows.key
|
||||
|
||||
## ssh connection
|
||||
hostname ansible_user=user ansible_password=pass ansible_host=host ansible_port=port ansible_ssh_private_key_file=key ssh_args="-o StrictHostKeyChecking=no"
|
|
@ -0,0 +1,179 @@
|
|||
import os
|
||||
import shutil
|
||||
import yaml
|
||||
from copy import deepcopy
|
||||
from collections import defaultdict
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from common.utils import get_logger
|
||||
from assets.automations.methods import platform_automation_methods
|
||||
from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PlaybookCallback(DefaultCallback):
|
||||
def playbook_on_stats(self, event_data, **kwargs):
|
||||
super().playbook_on_stats(event_data, **kwargs)
|
||||
|
||||
|
||||
class BasePlaybookManager:
|
||||
bulk_size = 100
|
||||
ansible_account_policy = 'privileged_first'
|
||||
|
||||
def __init__(self, execution):
|
||||
self.execution = execution
|
||||
self.automation = execution.automation
|
||||
self.method_id_meta_mapper = {
|
||||
method['id']: method
|
||||
for method in platform_automation_methods
|
||||
if method['method'] == self.__class__.method_type()
|
||||
}
|
||||
# 根据执行方式就行分组, 不同资产的改密、推送等操作可能会使用不同的执行方式
|
||||
# 然后根据执行方式分组, 再根据 bulk_size 分组, 生成不同的 playbook
|
||||
# 避免一个 playbook 中包含太多的主机
|
||||
self.method_hosts_mapper = defaultdict(list)
|
||||
self.playbooks = []
|
||||
|
||||
@classmethod
|
||||
def method_type(cls):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def runtime_dir(self):
|
||||
ansible_dir = settings.ANSIBLE_DIR
|
||||
path = os.path.join(
|
||||
ansible_dir, self.automation.type,
|
||||
self.automation.name.replace(' ', '_'),
|
||||
timezone.now().strftime('%Y%m%d_%H%M%S')
|
||||
)
|
||||
return path
|
||||
|
||||
@property
|
||||
def inventory_path(self):
|
||||
return os.path.join(self.runtime_dir, 'inventory', 'hosts.json')
|
||||
|
||||
@property
|
||||
def playbook_path(self):
|
||||
return os.path.join(self.runtime_dir, 'project', 'main.yml')
|
||||
|
||||
def generate(self):
|
||||
self.prepare_playbook_dir()
|
||||
self.generate_inventory()
|
||||
self.generate_playbook()
|
||||
|
||||
def prepare_playbook_dir(self):
|
||||
inventory_dir = os.path.dirname(self.inventory_path)
|
||||
playbook_dir = os.path.dirname(self.playbook_path)
|
||||
for d in [inventory_dir, playbook_dir]:
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d, exist_ok=True, mode=0o755)
|
||||
|
||||
def host_callback(self, host, automation=None, **kwargs):
|
||||
enabled_attr = '{}_enabled'.format(self.__class__.method_type())
|
||||
method_attr = '{}_method'.format(self.__class__.method_type())
|
||||
|
||||
method_enabled = automation and \
|
||||
getattr(automation, enabled_attr) and \
|
||||
getattr(automation, method_attr) and \
|
||||
getattr(automation, method_attr) in self.method_id_meta_mapper
|
||||
|
||||
if not method_enabled:
|
||||
host['error'] = _('Change password disabled')
|
||||
return host
|
||||
|
||||
self.method_hosts_mapper[getattr(automation, method_attr)].append(host['name'])
|
||||
return host
|
||||
|
||||
def generate_inventory(self):
|
||||
inventory = JMSInventory(
|
||||
assets=self.automation.get_all_assets(),
|
||||
account_policy=self.ansible_account_policy,
|
||||
host_callback=self.host_callback
|
||||
)
|
||||
inventory.write_to_file(self.inventory_path)
|
||||
logger.debug("Generate inventory done: {}".format(self.inventory_path))
|
||||
|
||||
def generate_playbook(self):
|
||||
main_playbook = []
|
||||
for method_id, host_names in self.method_hosts_mapper.items():
|
||||
method = self.method_id_meta_mapper.get(method_id)
|
||||
if not method:
|
||||
logger.error("Method not found: {}".format(method_id))
|
||||
continue
|
||||
method_playbook_dir_path = method['dir']
|
||||
method_playbook_dir_name = os.path.basename(method_playbook_dir_path)
|
||||
sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name)
|
||||
sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml')
|
||||
shutil.copytree(method_playbook_dir_path, sub_playbook_dir)
|
||||
|
||||
with open(sub_playbook_path, 'r') as f:
|
||||
host_playbook_play = yaml.safe_load(f)
|
||||
|
||||
if isinstance(host_playbook_play, list):
|
||||
host_playbook_play = host_playbook_play[0]
|
||||
|
||||
hosts_bulked = [host_names[i:i+self.bulk_size] for i in range(0, len(host_names), self.bulk_size)]
|
||||
for i, hosts in enumerate(hosts_bulked):
|
||||
plays = []
|
||||
play = deepcopy(host_playbook_play)
|
||||
play['hosts'] = ':'.join(hosts)
|
||||
plays.append(play)
|
||||
|
||||
playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i))
|
||||
with open(playbook_path, 'w') as f:
|
||||
yaml.safe_dump(plays, f)
|
||||
self.playbooks.append([playbook_path, hosts])
|
||||
|
||||
main_playbook.append({
|
||||
'name': method['name'] + ' for part {}'.format(i),
|
||||
'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i))
|
||||
})
|
||||
|
||||
with open(self.playbook_path, 'w') as f:
|
||||
yaml.safe_dump(main_playbook, f)
|
||||
|
||||
logger.debug("Generate playbook done: " + self.playbook_path)
|
||||
|
||||
def get_runners(self):
|
||||
runners = []
|
||||
for playbook_path in self.playbooks:
|
||||
runer = PlaybookRunner(
|
||||
self.inventory_path,
|
||||
playbook_path,
|
||||
self.runtime_dir,
|
||||
callback=PlaybookCallback(),
|
||||
)
|
||||
runners.append(runer)
|
||||
return runners
|
||||
|
||||
def on_runner_done(self, runner, cb):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_runner_failed(self, runner, e):
|
||||
print("Runner failed: {} {}".format(e, self))
|
||||
|
||||
def before_runner_start(self, runner):
|
||||
pass
|
||||
|
||||
def run(self, **kwargs):
|
||||
self.generate()
|
||||
runners = self.get_runners()
|
||||
if len(runners) > 1:
|
||||
print("### 分批次执行开始任务, 总共 {}\n".format(len(runners)))
|
||||
else:
|
||||
print(">>> 开始执行任务\n")
|
||||
|
||||
for i, runner in enumerate(runners, start=1):
|
||||
if len(runners) > 1:
|
||||
print(">>> 开始执行第 {} 批任务".format(i))
|
||||
self.before_runner_start(runner)
|
||||
try:
|
||||
cb = runner.run(**kwargs)
|
||||
self.on_runner_done(runner, cb)
|
||||
except Exception as e:
|
||||
self.on_runner_failed(runner, e)
|
||||
print('\n\n')
|
|
@ -0,0 +1,46 @@
|
|||
- hosts: mysql
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/local/bin/python
|
||||
jms_account:
|
||||
username: root
|
||||
secret: redhat
|
||||
jms_asset:
|
||||
address: 127.0.0.1
|
||||
port: 3306
|
||||
account:
|
||||
username: web1
|
||||
secret: jumpserver
|
||||
|
||||
tasks:
|
||||
- name: Test MySQL connection
|
||||
community.mysql.mysql_info:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
filter: version
|
||||
register: db_info
|
||||
|
||||
- name: MySQL version
|
||||
debug:
|
||||
var: db_info.version.full
|
||||
|
||||
- name: Change MySQL password
|
||||
community.mysql.mysql_user:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
host: "%"
|
||||
when: db_info is succeeded
|
||||
|
||||
- name: Verify password
|
||||
community.mysql.mysql_info:
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
filter: version
|
|
@ -1,6 +1,6 @@
|
|||
id: change_password_mysql
|
||||
id: change_secret_mysql
|
||||
name: Change password for MySQL
|
||||
category: database
|
||||
type:
|
||||
- mysql
|
||||
method: change_password
|
||||
method: change_secret
|
|
@ -0,0 +1,47 @@
|
|||
- hosts: postgre
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/local/bin/python
|
||||
jms_account:
|
||||
username: postgre
|
||||
secret: postgre
|
||||
jms_asset:
|
||||
address: 127.0.0.1
|
||||
port: 5432
|
||||
database: testdb
|
||||
account:
|
||||
username: test
|
||||
secret: jumpserver
|
||||
|
||||
tasks:
|
||||
- name: Test PostgreSQL connection
|
||||
community.postgresql.postgresql_ping:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
login_db: "{{ jms_asset.database }}"
|
||||
register: db_info
|
||||
|
||||
- name: Display PostgreSQL version
|
||||
debug:
|
||||
var: db_info.version.full
|
||||
|
||||
- name: Change PostgreSQL password
|
||||
community.postgresql.postgresql_user:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
db: "{{ jms_asset.database }}"
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
when: db_info is succeeded
|
||||
|
||||
- name: Verify password
|
||||
community.postgresql.postgresql_ping:
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
db: "{{ jms_asset.database }}"
|
|
@ -1,6 +1,6 @@
|
|||
id: change_password_postgresql
|
||||
id: change_secret_postgresql
|
||||
name: Change password for PostgreSQL
|
||||
category: database
|
||||
type:
|
||||
- postgresql
|
||||
method: change_password
|
||||
method: change_secret
|
|
@ -0,0 +1,2 @@
|
|||
# all base inventory in base/base_inventory.txt
|
||||
asset_name(ip)_account_username account={"username": "", "password": "xxx"} ...base_inventory_vars
|
|
@ -0,0 +1,29 @@
|
|||
- hosts: demo
|
||||
tasks:
|
||||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,6 +1,6 @@
|
|||
id: change_password_aix
|
||||
id: change_secret_aix
|
||||
name: Change password for AIX
|
||||
category: host
|
||||
type:
|
||||
- aix
|
||||
method: change_password
|
||||
method: change_secret
|
|
@ -0,0 +1,30 @@
|
|||
- hosts: demo
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Test privileged account
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ account.secret_type }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
when: account.secret_type == 'password'
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.secret }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,7 +1,7 @@
|
|||
id: change_password_linux
|
||||
id: change_secret_linux
|
||||
name: Change password for Linux
|
||||
category: host
|
||||
type:
|
||||
- unix
|
||||
- linux
|
||||
method: change_password
|
||||
method: change_secret
|
|
@ -0,0 +1,30 @@
|
|||
- hosts: demo
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,7 +1,7 @@
|
|||
id: change_password_local_windows
|
||||
id: change_secret_local_windows
|
||||
name: Change password local account for Windows
|
||||
version: 1
|
||||
method: change_password
|
||||
method: change_secret
|
||||
category: host
|
||||
type:
|
||||
- windows
|
|
@ -0,0 +1,130 @@
|
|||
from copy import deepcopy
|
||||
from collections import defaultdict
|
||||
import random
|
||||
import string
|
||||
|
||||
from common.utils import lazyproperty, gen_key_pair
|
||||
from ..base.manager import BasePlaybookManager
|
||||
from assets.models import ChangeSecretRecord, SecretStrategy
|
||||
|
||||
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
|
||||
DEFAULT_PASSWORD_LENGTH = 30
|
||||
DEFAULT_PASSWORD_RULES = {
|
||||
'length': DEFAULT_PASSWORD_LENGTH,
|
||||
'symbol_set': string_punctuation
|
||||
}
|
||||
|
||||
|
||||
class ChangeSecretManager(BasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.method_hosts_mapper = defaultdict(list)
|
||||
self.playbooks = []
|
||||
self.password_strategy = self.execution.automation.password_strategy
|
||||
self.ssh_key_strategy = self.execution.automation.ssh_key_strategy
|
||||
self._password_generated = None
|
||||
self._ssh_key_generated = None
|
||||
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
||||
|
||||
@classmethod
|
||||
def method_type(cls):
|
||||
return 'change_secret'
|
||||
|
||||
@lazyproperty
|
||||
def related_accounts(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def generate_ssh_key():
|
||||
private_key, public_key = gen_key_pair()
|
||||
return private_key
|
||||
|
||||
def generate_password(self):
|
||||
kwargs = self.automation.password_rules or {}
|
||||
length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length']))
|
||||
symbol_set = kwargs.get('symbol_set')
|
||||
if symbol_set is None:
|
||||
symbol_set = DEFAULT_PASSWORD_RULES['symbol_set']
|
||||
chars = string.ascii_letters + string.digits + symbol_set
|
||||
password = ''.join([random.choice(chars) for _ in range(length)])
|
||||
return password
|
||||
|
||||
def get_ssh_key(self):
|
||||
if self.ssh_key_strategy == SecretStrategy.custom:
|
||||
return self.automation.ssh_key
|
||||
elif self.ssh_key_strategy == SecretStrategy.random_one:
|
||||
if not self._ssh_key_generated:
|
||||
self._ssh_key_generated = self.generate_ssh_key()
|
||||
return self._ssh_key_generated
|
||||
else:
|
||||
self.generate_ssh_key()
|
||||
|
||||
def get_password(self):
|
||||
if self.password_strategy == SecretStrategy.custom:
|
||||
if not self.automation.password:
|
||||
raise ValueError("Automation Password must be set")
|
||||
return self.automation.password
|
||||
elif self.password_strategy == SecretStrategy.random_one:
|
||||
if not self._password_generated:
|
||||
self._password_generated = self.generate_password()
|
||||
return self._password_generated
|
||||
else:
|
||||
self.generate_password()
|
||||
|
||||
def get_secret(self, account):
|
||||
if account.secret_type == 'ssh-key':
|
||||
secret = self.get_ssh_key()
|
||||
else:
|
||||
secret = self.get_password()
|
||||
if not secret:
|
||||
raise ValueError("Secret must be set")
|
||||
return secret
|
||||
|
||||
def host_callback(self, host, asset=None, account=None, automation=None, **kwargs):
|
||||
host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs)
|
||||
if host.get('error'):
|
||||
return host
|
||||
|
||||
accounts = asset.accounts.all()
|
||||
if account:
|
||||
accounts = accounts.exclude(id=account.id)
|
||||
if '*' not in self.automation.accounts:
|
||||
accounts = accounts.filter(username__in=self.automation.accounts)
|
||||
|
||||
method_attr = getattr(automation, self.method_type() + '_method')
|
||||
method_hosts = self.method_hosts_mapper[method_attr]
|
||||
method_hosts = [h for h in method_hosts if h != host['name']]
|
||||
inventory_hosts = []
|
||||
records = []
|
||||
|
||||
for account in accounts:
|
||||
h = deepcopy(host)
|
||||
h['name'] += '_' + account.username
|
||||
|
||||
new_secret = self.get_secret(account)
|
||||
recorder = ChangeSecretRecord(
|
||||
account=account, execution=self.execution,
|
||||
old_secret=account.secret, new_secret=new_secret,
|
||||
)
|
||||
records.append(recorder)
|
||||
self.name_recorder_mapper[h['name']] = recorder
|
||||
|
||||
h['account'] = {
|
||||
'name': account.name,
|
||||
'username': account.username,
|
||||
'secret_type': account.secret_type,
|
||||
'secret': new_secret,
|
||||
}
|
||||
inventory_hosts.append(h)
|
||||
method_hosts.append(h['name'])
|
||||
self.method_hosts_mapper[method_attr] = method_hosts
|
||||
ChangeSecretRecord.objects.bulk_create(records)
|
||||
return inventory_hosts
|
||||
|
||||
def on_runner_done(self, runner, cb):
|
||||
summary = runner.summary
|
||||
|
||||
def on_runner_failed(self, runner, e):
|
||||
pass
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# from .backup.manager import AccountBackupExecutionManager
|
||||
#
|
||||
#
|
||||
from .change_secret.manager import ChangeSecretManager
|
||||
|
||||
|
||||
class ExecutionManager:
|
||||
manager_type_mapper = {
|
||||
'change_secret': ChangeSecretManager,
|
||||
}
|
||||
|
||||
def __init__(self, execution):
|
||||
self.execution = execution
|
||||
self._runner = self.manager_type_mapper[execution.automation.type](execution)
|
||||
|
||||
def run(self, **kwargs):
|
||||
return self._runner.run(**kwargs)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
- hosts: mysql
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/local/bin/python
|
||||
jms_account:
|
||||
username: root
|
||||
secret: redhat
|
||||
jms_asset:
|
||||
address: 127.0.0.1
|
||||
port: 3306
|
||||
|
||||
tasks:
|
||||
- name: Gather facts info
|
||||
community.mysql.mysql_info:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
register: db_info
|
||||
|
||||
- name: Get info
|
||||
set_fact:
|
||||
info:
|
||||
version: "{{ db_info.version.full }}"
|
||||
|
||||
- debug:
|
||||
var: db_info
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
id: gather_facts_mysql
|
||||
name: Gather facts from MySQL
|
||||
category: database
|
||||
type:
|
||||
- mysql
|
||||
method: gather_facts
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,28 @@
|
|||
- hosts: postgre
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/local/bin/python
|
||||
jms_account:
|
||||
username: postgre
|
||||
secret: postgre
|
||||
jms_asset:
|
||||
address: 127.0.0.1
|
||||
port: 5432
|
||||
database: testdb
|
||||
account:
|
||||
username: test
|
||||
secret: jumpserver
|
||||
|
||||
tasks:
|
||||
- name: Test PostgreSQL connection
|
||||
community.postgresql.postgresql_info:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
login_db: "{{ jms_asset.database }}"
|
||||
register: db_info
|
||||
|
||||
- name: Debug it
|
||||
debug:
|
||||
var: db_info
|
|
@ -0,0 +1,6 @@
|
|||
id: gather_facts_postgresql
|
||||
name: Gather facts for PostgreSQL
|
||||
category: database
|
||||
type:
|
||||
- postgresql
|
||||
method: gather_facts
|
|
@ -6,5 +6,5 @@
|
|||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
- change_secret
|
||||
{% endfor %}
|
|
@ -1,8 +1,8 @@
|
|||
id: change_password_sqlserver
|
||||
id: gather_facts_sqlserver
|
||||
name: Change password for SQLServer
|
||||
version: 1
|
||||
category: database
|
||||
type:
|
||||
- sqlserver
|
||||
method: change_password
|
||||
method: gather_facts
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# all base inventory in base/base_inventory.txt
|
||||
asset_name(ip) ...base_inventory_vars
|
|
@ -0,0 +1,19 @@
|
|||
- hosts: website
|
||||
gather_facts: yes
|
||||
tasks:
|
||||
- name: Get info
|
||||
set_fact:
|
||||
info:
|
||||
arch: "{{ ansible_architecture }}"
|
||||
distribution: "{{ ansible_distribution }}"
|
||||
distribution_version: "{{ ansible_distribution_version }}"
|
||||
kernel: "{{ ansible_kernel }}"
|
||||
vendor: "{{ ansible_system_vendor }}"
|
||||
model: "{{ ansible_product_name }}"
|
||||
sn: "{{ ansible_product_serial }}"
|
||||
cpu_vcpus: "{{ ansible_processor_vcpus }}"
|
||||
memory: "{{ ansible_memtotal_mb }}"
|
||||
disk_total: "{{ (ansible_mounts | map(attribute='size_total') | sum / 1024 / 1024 / 1024) | round(2) }}"
|
||||
|
||||
- debug:
|
||||
var: info
|
|
@ -0,0 +1,8 @@
|
|||
id: gather_facts_posix
|
||||
name: Gather posix facts
|
||||
category: host
|
||||
type:
|
||||
- linux
|
||||
- windows
|
||||
- unix
|
||||
method: gather_facts
|
|
@ -0,0 +1,24 @@
|
|||
- hosts: windows
|
||||
gather_facts: yes
|
||||
tasks:
|
||||
# - name: Gather facts windows
|
||||
# setup:
|
||||
# register: facts
|
||||
#
|
||||
# - debug:
|
||||
# var: facts
|
||||
- name: Get info
|
||||
set_fact:
|
||||
info:
|
||||
arch: "{{ ansible_architecture2 }}"
|
||||
distribution: "{{ ansible_distribution }}"
|
||||
distribution_version: "{{ ansible_distribution_version }}"
|
||||
kernel: "{{ ansible_kernel }}"
|
||||
vendor: "{{ ansible_system_vendor }}"
|
||||
model: "{{ ansible_product_name }}"
|
||||
sn: "{{ ansible_product_serial }}"
|
||||
cpu_vcpus: "{{ ansible_processor_vcpus }}"
|
||||
memory: "{{ ansible_memtotal_mb }}"
|
||||
t
|
||||
- debug:
|
||||
var: info
|
|
@ -0,0 +1,7 @@
|
|||
id: gather_facts_windows
|
||||
name: Gather facts windows
|
||||
version: 1
|
||||
method: gather_facts
|
||||
category: host
|
||||
type:
|
||||
- windows
|
|
@ -0,0 +1,77 @@
|
|||
import os
|
||||
import shutil
|
||||
from copy import deepcopy
|
||||
from collections import defaultdict
|
||||
|
||||
import yaml
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from ops.ansible import PlaybookRunner
|
||||
from ..base.manager import BasePlaybookManager
|
||||
from assets.automations.methods import platform_automation_methods
|
||||
|
||||
|
||||
class GatherFactsManager(BasePlaybookManager):
|
||||
method_name = 'gather_facts'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.id_method_mapper = {
|
||||
method['id']: method
|
||||
for method in platform_automation_methods
|
||||
if method['method'] == self.method_name
|
||||
}
|
||||
self.method_hosts_mapper = defaultdict(list)
|
||||
self.playbooks = []
|
||||
|
||||
def inventory_kwargs(self):
|
||||
return {
|
||||
}
|
||||
|
||||
def generate_playbook(self):
|
||||
playbook = []
|
||||
for method_id, host_names in self.method_hosts_mapper.items():
|
||||
method = self.id_method_mapper[method_id]
|
||||
method_playbook_dir_path = method['dir']
|
||||
method_playbook_dir_name = os.path.basename(method_playbook_dir_path)
|
||||
sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name)
|
||||
shutil.copytree(method_playbook_dir_path, sub_playbook_dir)
|
||||
sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml')
|
||||
|
||||
with open(sub_playbook_path, 'r') as f:
|
||||
host_playbook_play = yaml.safe_load(f)
|
||||
|
||||
if isinstance(host_playbook_play, list):
|
||||
host_playbook_play = host_playbook_play[0]
|
||||
|
||||
step = 10
|
||||
hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)]
|
||||
for i, hosts in enumerate(hosts_grouped):
|
||||
plays = []
|
||||
play = deepcopy(host_playbook_play)
|
||||
play['hosts'] = ':'.join(hosts)
|
||||
plays.append(play)
|
||||
|
||||
playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i))
|
||||
with open(playbook_path, 'w') as f:
|
||||
yaml.safe_dump(plays, f)
|
||||
self.playbooks.append(playbook_path)
|
||||
|
||||
playbook.append({
|
||||
'name': method['name'] + ' for part {}'.format(i),
|
||||
'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i))
|
||||
})
|
||||
|
||||
with open(self.playbook_path, 'w') as f:
|
||||
yaml.safe_dump(playbook, f)
|
||||
|
||||
print("Generate playbook done: " + self.playbook_path)
|
||||
|
||||
def get_runner(self):
|
||||
return PlaybookRunner(
|
||||
self.inventory_path,
|
||||
self.playbook_path,
|
||||
self.runtime_dir
|
||||
)
|
||||
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import yaml
|
||||
import json
|
||||
from functools import partial
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
@ -62,4 +63,4 @@ platform_automation_methods = get_platform_automation_methods()
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(get_platform_automation_methods())
|
||||
print(json.dumps(platform_automation_methods, indent=4))
|
|
@ -0,0 +1,20 @@
|
|||
- hosts: mysql
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/local/bin/python
|
||||
jms_account:
|
||||
username: root
|
||||
password: redhat
|
||||
jms_asset:
|
||||
address: 127.0.0.1
|
||||
port: 3306
|
||||
|
||||
tasks:
|
||||
- name: Test MySQL connection
|
||||
community.mysql.mysql_info:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
filter: version
|
||||
register: db_info
|
|
@ -0,0 +1,6 @@
|
|||
id: mysql_ping
|
||||
name: Ping MySQL
|
||||
category: database
|
||||
type:
|
||||
- mysql
|
||||
method: ping
|
|
@ -0,0 +1,23 @@
|
|||
- hosts: postgre
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/local/bin/python
|
||||
jms_account:
|
||||
username: postgre
|
||||
secret: postgre
|
||||
jms_asset:
|
||||
address: 127.0.0.1
|
||||
port: 5432
|
||||
database: testdb
|
||||
account:
|
||||
username: test
|
||||
secret: jumpserver
|
||||
|
||||
tasks:
|
||||
- name: Test PostgreSQL connection
|
||||
community.postgresql.postgresql_ping:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
login_db: "{{ jms_asset.database }}"
|
|
@ -0,0 +1,6 @@
|
|||
id: ping_postgresql
|
||||
name: Ping PostgreSQL
|
||||
category: database
|
||||
type:
|
||||
- postgresql
|
||||
method: ping
|
|
@ -0,0 +1,2 @@
|
|||
# all base inventory in base/base_inventory.txt
|
||||
asset_name(ip)_account_username account={"username": "", "password": "xxx"} ...base_inventory_vars
|
|
@ -0,0 +1,5 @@
|
|||
- hosts: demo
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Posix ping
|
||||
ping:
|
|
@ -0,0 +1,8 @@
|
|||
id: posix_ping
|
||||
name: Posix ping
|
||||
category: host
|
||||
type:
|
||||
- linux
|
||||
- windows
|
||||
- unix
|
||||
method: ping
|
|
@ -0,0 +1,5 @@
|
|||
- hosts: windows
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Windows ping
|
||||
win_ping:
|
|
@ -0,0 +1,7 @@
|
|||
id: win_ping
|
||||
name: Windows ping
|
||||
version: 1
|
||||
method: change_secret
|
||||
category: host
|
||||
type:
|
||||
- windows
|
|
@ -0,0 +1,75 @@
|
|||
import os
|
||||
import shutil
|
||||
from copy import deepcopy
|
||||
from collections import defaultdict
|
||||
|
||||
import yaml
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from ops.ansible import PlaybookRunner
|
||||
from ..base.manager import BasePlaybookManager
|
||||
from assets.automations.methods import platform_automation_methods
|
||||
|
||||
|
||||
class ChangePasswordManager(BasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.id_method_mapper = {
|
||||
method['id']: method
|
||||
for method in platform_automation_methods
|
||||
}
|
||||
self.method_hosts_mapper = defaultdict(list)
|
||||
self.playbooks = []
|
||||
|
||||
def inventory_kwargs(self):
|
||||
return {
|
||||
'host_callback': self.host_duplicator
|
||||
}
|
||||
|
||||
def generate_playbook(self):
|
||||
playbook = []
|
||||
for method_id, host_names in self.method_hosts_mapper.items():
|
||||
method = self.id_method_mapper[method_id]
|
||||
method_playbook_dir_path = method['dir']
|
||||
method_playbook_dir_name = os.path.basename(method_playbook_dir_path)
|
||||
sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name)
|
||||
shutil.copytree(method_playbook_dir_path, sub_playbook_dir)
|
||||
sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml')
|
||||
|
||||
with open(sub_playbook_path, 'r') as f:
|
||||
host_playbook_play = yaml.safe_load(f)
|
||||
|
||||
if isinstance(host_playbook_play, list):
|
||||
host_playbook_play = host_playbook_play[0]
|
||||
|
||||
step = 10
|
||||
hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)]
|
||||
for i, hosts in enumerate(hosts_grouped):
|
||||
plays = []
|
||||
play = deepcopy(host_playbook_play)
|
||||
play['hosts'] = ':'.join(hosts)
|
||||
plays.append(play)
|
||||
|
||||
playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i))
|
||||
with open(playbook_path, 'w') as f:
|
||||
yaml.safe_dump(plays, f)
|
||||
self.playbooks.append(playbook_path)
|
||||
|
||||
playbook.append({
|
||||
'name': method['name'] + ' for part {}'.format(i),
|
||||
'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i))
|
||||
})
|
||||
|
||||
with open(self.playbook_path, 'w') as f:
|
||||
yaml.safe_dump(playbook, f)
|
||||
|
||||
print("Generate playbook done: " + self.playbook_path)
|
||||
|
||||
def get_runner(self):
|
||||
return PlaybookRunner(
|
||||
self.inventory_path,
|
||||
self.playbook_path,
|
||||
self.runtime_dir
|
||||
)
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ class CloudTypes(BaseType):
|
|||
'ansible_config': {},
|
||||
'gather_facts_enabled': False,
|
||||
'verify_account_enabled': False,
|
||||
'change_password_enabled': False,
|
||||
'change_secret_enabled': False,
|
||||
'create_account_enabled': False,
|
||||
'gather_accounts_enabled': False,
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class DatabaseTypes(BaseType):
|
|||
'gather_facts_enabled': True,
|
||||
'gather_accounts_enabled': True,
|
||||
'verify_account_enabled': True,
|
||||
'change_password_enabled': True,
|
||||
'change_secret_enabled': True,
|
||||
'create_account_enabled': True,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class DeviceTypes(BaseType):
|
|||
'gather_facts_enabled': False,
|
||||
'gather_accounts_enabled': False,
|
||||
'verify_account_enabled': False,
|
||||
'change_password_enabled': False,
|
||||
'change_secret_enabled': False,
|
||||
'create_account_enabled': False,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,12 +48,12 @@ class HostTypes(BaseType):
|
|||
'gather_facts_enabled': True,
|
||||
'gather_accounts_enabled': True,
|
||||
'verify_account_enabled': True,
|
||||
'change_password_enabled': True,
|
||||
'change_secret_enabled': True,
|
||||
'create_account_enabled': True,
|
||||
},
|
||||
cls.WINDOWS: {
|
||||
'ansible_config': {
|
||||
'ansible_shell_type': 'powershell',
|
||||
'ansible_shell_type': 'cmd',
|
||||
'ansible_connection': 'ssh',
|
||||
},
|
||||
},
|
||||
|
@ -71,7 +71,7 @@ class HostTypes(BaseType):
|
|||
{'name': 'BSD'},
|
||||
{'name': 'AIX', 'automation': {
|
||||
'create_account_method': 'create_account_aix',
|
||||
'change_password_method': 'change_password_aix'
|
||||
'change_secret_method': 'change_secret_aix'
|
||||
}},
|
||||
],
|
||||
cls.WINDOWS: [
|
||||
|
|
|
@ -38,7 +38,7 @@ class AllTypes(ChoicesMixin):
|
|||
|
||||
@classmethod
|
||||
def set_automation_methods(cls, category, tp, constraints):
|
||||
from assets.playbooks import filter_platform_methods
|
||||
from assets.automations import filter_platform_methods
|
||||
automation = constraints.get('automation', {})
|
||||
automation_methods = {}
|
||||
for item, enabled in automation.items():
|
||||
|
|
|
@ -22,7 +22,7 @@ class WebTypes(BaseType):
|
|||
'*': {
|
||||
'gather_facts_enabled': False,
|
||||
'verify_account_enabled': False,
|
||||
'change_password_enabled': False,
|
||||
'change_secret_enabled': False,
|
||||
'create_account_enabled': False,
|
||||
'gather_accounts_enabled': False,
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
from django.db.models import Q
|
||||
from rest_framework import filters
|
||||
from rest_framework.compat import coreapi, coreschema
|
||||
from django_filters import rest_framework as drf_filters
|
||||
|
||||
from common.drf.filters import BaseFilterSet, UUIDInFilter
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from assets.utils import is_query_node_all_assets, get_node_from_request
|
||||
from .models import Label, Node, Account
|
||||
|
||||
|
@ -157,15 +158,16 @@ class IpInFilterBackend(filters.BaseFilterBackend):
|
|||
|
||||
|
||||
class AccountFilterSet(BaseFilterSet):
|
||||
from django_filters import rest_framework as filters
|
||||
ip = filters.CharFilter(field_name='address', lookup_expr='exact')
|
||||
hostname = filters.CharFilter(field_name='name', lookup_expr='exact')
|
||||
username = filters.CharFilter(field_name="username", lookup_expr='exact')
|
||||
address = filters.CharFilter(field_name="asset__address", lookup_expr='exact')
|
||||
assets = UUIDInFilter(field_name='asset_id', lookup_expr='in')
|
||||
nodes = UUIDInFilter(method='filter_nodes')
|
||||
ip = drf_filters.CharFilter(field_name='address', lookup_expr='exact')
|
||||
hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact')
|
||||
username = drf_filters.CharFilter(field_name="username", lookup_expr='exact')
|
||||
address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact')
|
||||
asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact')
|
||||
assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='in')
|
||||
nodes = drf_filters.CharFilter(method='filter_nodes')
|
||||
|
||||
def filter_nodes(self, queryset, name, value):
|
||||
@staticmethod
|
||||
def filter_nodes(queryset, name, value):
|
||||
nodes = Node.objects.filter(id__in=value)
|
||||
if not nodes:
|
||||
return queryset
|
||||
|
@ -179,6 +181,4 @@ class AccountFilterSet(BaseFilterSet):
|
|||
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = [
|
||||
'asset', 'id'
|
||||
]
|
||||
fields = ['id']
|
||||
|
|
|
@ -33,8 +33,8 @@ class Migration(migrations.Migration):
|
|||
('gather_facts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')),
|
||||
('create_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')),
|
||||
('create_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')),
|
||||
('change_password_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')),
|
||||
('change_password_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')),
|
||||
('change_secret_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')),
|
||||
('change_secret_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')),
|
||||
('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')),
|
||||
('verify_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method')),
|
||||
('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')),
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
# Generated by Django 3.2.14 on 2022-10-10 01:59
|
||||
|
||||
import common.db.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0107_account_history'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BaseAutomation',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('is_periodic', models.BooleanField(default=False)),
|
||||
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
|
||||
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
|
||||
('accounts', models.JSONField(default=list, verbose_name='Accounts')),
|
||||
('type', models.CharField(max_length=16, verbose_name='Type')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('assets', models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets')),
|
||||
('nodes', models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Automation plan',
|
||||
'unique_together': {('org_id', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='updated_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DiscoveryAutomation',
|
||||
fields=[
|
||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Discovery strategy',
|
||||
},
|
||||
bases=('assets.baseautomation',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ReconcileAutomation',
|
||||
fields=[
|
||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Reconcile strategy',
|
||||
},
|
||||
bases=('assets.baseautomation',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VerifyAutomation',
|
||||
fields=[
|
||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Verify strategy',
|
||||
},
|
||||
bases=('assets.baseautomation',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AutomationExecution',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('status', models.CharField(default='pending', max_length=16)),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||
('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')),
|
||||
('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')),
|
||||
('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')),
|
||||
('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')),
|
||||
('automation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation strategy')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Automation strategy execution',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ChangePasswordAutomation',
|
||||
fields=[
|
||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
('password', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||
('recipients', models.ManyToManyField(blank=True, related_name='recipients_change_auth_strategy', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Change auth strategy',
|
||||
},
|
||||
bases=('assets.baseautomation',),
|
||||
),
|
||||
|
||||
]
|
|
@ -0,0 +1,83 @@
|
|||
# Generated by Django 3.2.14 on 2022-10-13 09:51
|
||||
|
||||
import common.db.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0108_migrate_automation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='ChangePasswordAutomation',
|
||||
new_name='ChangeSecretAutomation',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='baseautomation',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True, verbose_name='Is active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='changesecretautomation',
|
||||
name='password_rules',
|
||||
field=models.JSONField(default=dict, verbose_name='Password rules'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='changesecretautomation',
|
||||
name='password_strategy',
|
||||
field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16, verbose_name='Password strategy'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='changesecretautomation',
|
||||
name='secret_types',
|
||||
field=models.JSONField(default=list, verbose_name='Secret types'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='changesecretautomation',
|
||||
name='ssh_key',
|
||||
field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH key'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='changesecretautomation',
|
||||
name='ssh_key_change_strategy',
|
||||
field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key strategy'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='changesecretautomation',
|
||||
name='ssh_key_strategy',
|
||||
field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='changesecretautomation',
|
||||
name='recipients',
|
||||
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ChangeSecretRecord',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')),
|
||||
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||
('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')),
|
||||
('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
|
||||
('status', models.CharField(default='pending', max_length=16)),
|
||||
('error', models.TextField(blank=True, null=True, verbose_name='Error')),
|
||||
('account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.account')),
|
||||
('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.automationexecution')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Change secret',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -10,6 +10,7 @@ from .gathered_user import *
|
|||
from .favorite_asset import *
|
||||
from .account import *
|
||||
from .backup import *
|
||||
from .automations import *
|
||||
from ._user import *
|
||||
# 废弃以下
|
||||
# from ._authbook import *
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import logging
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
|
@ -41,6 +42,12 @@ class AssetQuerySet(models.QuerySet):
|
|||
def has_protocol(self, name):
|
||||
return self.filter(protocols__contains=name)
|
||||
|
||||
def group_by_platform(self) -> dict:
|
||||
groups = defaultdict(list)
|
||||
for asset in self.all():
|
||||
groups[asset.platform].append(asset)
|
||||
return groups
|
||||
|
||||
|
||||
class NodesRelationMixin:
|
||||
NODES_CACHE_KEY = 'ASSET_NODES_{}'
|
||||
|
@ -126,6 +133,22 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel):
|
|||
names.append(n.name + ':' + n.value)
|
||||
return names
|
||||
|
||||
@lazyproperty
|
||||
def primary_protocol(self):
|
||||
return self.protocols.first()
|
||||
|
||||
@lazyproperty
|
||||
def protocol(self):
|
||||
if not self.primary_protocol:
|
||||
return 'none'
|
||||
return self.primary_protocol.name
|
||||
|
||||
@lazyproperty
|
||||
def port(self):
|
||||
if not self.primary_protocol:
|
||||
return 0
|
||||
return self.primary_protocol.port
|
||||
|
||||
@property
|
||||
def protocols_as_list(self):
|
||||
return [{'name': p.name, 'port': p.port} for p in self.protocols.all()]
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
from .change_secret import *
|
||||
from .account_discovery import *
|
||||
from .account_reconcile import *
|
||||
from .account_verify import *
|
|
@ -1,12 +1,13 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ops.const import StrategyChoice
|
||||
from .common import AutomationStrategy
|
||||
from ops.ansible.runner import PlaybookRunner
|
||||
from .base import BaseAutomation
|
||||
|
||||
|
||||
class CollectStrategy(AutomationStrategy):
|
||||
class DiscoveryAutomation(BaseAutomation):
|
||||
class Meta:
|
||||
verbose_name = _("Collect strategy")
|
||||
verbose_name = _("Discovery strategy")
|
||||
|
||||
def to_attr_json(self):
|
||||
attr_json = super().to_attr_json()
|
|
@ -1,12 +1,12 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ops.const import StrategyChoice
|
||||
from .common import AutomationStrategy
|
||||
from .base import BaseAutomation
|
||||
|
||||
|
||||
class PushStrategy(AutomationStrategy):
|
||||
class ReconcileAutomation(BaseAutomation):
|
||||
class Meta:
|
||||
verbose_name = _("Push strategy")
|
||||
verbose_name = _("Reconcile strategy")
|
||||
|
||||
def to_attr_json(self):
|
||||
attr_json = super().to_attr_json()
|
|
@ -1,10 +1,10 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ops.const import StrategyChoice
|
||||
from .common import AutomationStrategy
|
||||
from .base import BaseAutomation
|
||||
|
||||
|
||||
class VerifyStrategy(AutomationStrategy):
|
||||
class VerifyAutomation(BaseAutomation):
|
||||
class Meta:
|
||||
verbose_name = _("Verify strategy")
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import uuid
|
||||
from celery import current_task
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.const.choices import Trigger
|
||||
from common.db.fields import EncryptJsonDictTextField
|
||||
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
||||
from ops.mixin import PeriodTaskModelMixin
|
||||
from ops.tasks import execute_automation_strategy
|
||||
from assets.models import Node, Asset
|
||||
|
||||
|
||||
class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
|
||||
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
|
||||
nodes = models.ManyToManyField(
|
||||
'assets.Node', blank=True, verbose_name=_("Nodes")
|
||||
)
|
||||
assets = models.ManyToManyField(
|
||||
'assets.Asset', blank=True, verbose_name=_("Assets")
|
||||
)
|
||||
type = models.CharField(max_length=16, verbose_name=_('Type'))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __str__(self):
|
||||
return self.name + '@' + str(self.created_by)
|
||||
|
||||
def get_all_assets(self):
|
||||
nodes = self.nodes.all()
|
||||
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
|
||||
direct_asset_ids = self.assets.all().values_list('id', flat=True)
|
||||
asset_ids = set(list(direct_asset_ids) + list(node_asset_ids))
|
||||
return Asset.objects.filter(id__in=asset_ids)
|
||||
|
||||
def all_assets_group_by_platform(self):
|
||||
assets = self.get_all_assets().prefetch_related('platform')
|
||||
return assets.group_by_platform()
|
||||
|
||||
def get_register_task(self):
|
||||
name = "automation_strategy_period_{}".format(str(self.id)[:8])
|
||||
task = execute_automation_strategy.name
|
||||
args = (str(self.id), Trigger.timing)
|
||||
kwargs = {}
|
||||
return name, task, args, kwargs
|
||||
|
||||
def to_attr_json(self):
|
||||
return {
|
||||
'name': self.name,
|
||||
'accounts': self.accounts,
|
||||
'assets': list(self.assets.all().values_list('id', flat=True)),
|
||||
'nodes': list(self.assets.all().values_list('id', flat=True)),
|
||||
}
|
||||
|
||||
def execute(self, trigger=Trigger.manual):
|
||||
try:
|
||||
eid = current_task.request.id
|
||||
except AttributeError:
|
||||
eid = str(uuid.uuid4())
|
||||
|
||||
execution = self.executions.create(
|
||||
id=eid, trigger=trigger,
|
||||
)
|
||||
return execution.start()
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org_id', 'name')]
|
||||
verbose_name = _("Automation plan")
|
||||
|
||||
|
||||
class AutomationExecution(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
automation = models.ForeignKey(
|
||||
'BaseAutomation', related_name='executions', on_delete=models.CASCADE,
|
||||
verbose_name=_('Automation strategy')
|
||||
)
|
||||
status = models.CharField(max_length=16, default='pending')
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
|
||||
date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished"))
|
||||
snapshot = EncryptJsonDictTextField(
|
||||
default=dict, blank=True, null=True, verbose_name=_('Automation snapshot')
|
||||
)
|
||||
trigger = models.CharField(
|
||||
max_length=128, default=Trigger.manual, choices=Trigger.choices,
|
||||
verbose_name=_('Trigger mode')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Automation strategy execution')
|
||||
|
||||
@property
|
||||
def manager_type(self):
|
||||
return self.snapshot['type']
|
||||
|
||||
def start(self):
|
||||
from assets.automations.endpoint import ExecutionManager
|
||||
manager = ExecutionManager(execution=self)
|
||||
return manager.run()
|
|
@ -0,0 +1,59 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.db import fields
|
||||
from common.db.models import JMSBaseModel
|
||||
from .base import BaseAutomation
|
||||
|
||||
|
||||
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'SecretStrategy']
|
||||
|
||||
|
||||
class SecretStrategy(models.TextChoices):
|
||||
custom = 'specific', _('Specific')
|
||||
random_one = 'random_one', _('All assets use the same random password')
|
||||
random_all = 'random_all', _('All assets use different random password')
|
||||
|
||||
|
||||
class SSHKeyStrategy(models.TextChoices):
|
||||
add = 'add', _('Append SSH KEY')
|
||||
set = 'set', _('Empty and append SSH KEY')
|
||||
set_jms = 'set_jms', _('Replace (The key generated by JumpServer) ')
|
||||
|
||||
|
||||
class ChangeSecretAutomation(BaseAutomation):
|
||||
secret_types = models.JSONField(default=list, verbose_name=_('Secret types'))
|
||||
password_strategy = models.CharField(choices=SecretStrategy.choices, max_length=16,
|
||||
default=SecretStrategy.random_one, verbose_name=_('Password strategy'))
|
||||
password = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
password_rules = models.JSONField(default=dict, verbose_name=_('Password rules'))
|
||||
|
||||
ssh_key_strategy = models.CharField(choices=SecretStrategy.choices, default=SecretStrategy.random_one, max_length=16)
|
||||
ssh_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH key'))
|
||||
ssh_key_change_strategy = models.CharField(choices=SSHKeyStrategy.choices, max_length=16,
|
||||
default=SSHKeyStrategy.add, verbose_name=_('SSH key strategy'))
|
||||
recipients = models.ManyToManyField('users.User', blank=True, verbose_name=_("Recipient"))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.type = 'change_secret'
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Change auth strategy")
|
||||
|
||||
|
||||
class ChangeSecretRecord(JMSBaseModel):
|
||||
execution = models.ForeignKey('assets.AutomationExecution', on_delete=models.CASCADE)
|
||||
account = models.ForeignKey('assets.Account', on_delete=models.CASCADE, null=True)
|
||||
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
|
||||
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
|
||||
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
|
||||
status = models.CharField(max_length=16, default='pending')
|
||||
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Change secret")
|
||||
|
||||
def __str__(self):
|
||||
return self.account.__str__()
|
|
@ -76,6 +76,15 @@ class BaseAccount(OrgModelMixin):
|
|||
def has_secret(self):
|
||||
return bool(self.secret)
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self.secret
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
self.secret = value
|
||||
self.secret_type = 'password'
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
if self.secret_type == self.SecretType.ssh_key:
|
||||
|
@ -91,15 +100,6 @@ class BaseAccount(OrgModelMixin):
|
|||
self.secret = value
|
||||
self.secret_type = 'private_key'
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self.secret
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
self.secret = value
|
||||
self.secret_type = 'password'
|
||||
|
||||
@property
|
||||
def ssh_key_fingerprint(self):
|
||||
if self.public_key:
|
||||
|
@ -125,8 +125,8 @@ class BaseAccount(OrgModelMixin):
|
|||
return None
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key_obj:
|
||||
def private_key_path(self):
|
||||
if not self.secret_type != 'ssh_key' or not self.secret:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
|
|
|
@ -40,6 +40,9 @@ class Domain(OrgModelMixin):
|
|||
def gateways(self):
|
||||
return self.gateway_set.filter(is_active=True)
|
||||
|
||||
def select_gateway(self):
|
||||
return self.random_gateway()
|
||||
|
||||
def random_gateway(self):
|
||||
gateways = [gw for gw in self.gateways if gw.is_connective]
|
||||
if gateways:
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
from orgs.mixins.models import JMSOrgBaseModel
|
||||
|
||||
|
||||
class Label(OrgModelMixin):
|
||||
class Label(JMSOrgBaseModel):
|
||||
SYSTEM_CATEGORY = "S"
|
||||
USER_CATEGORY = "U"
|
||||
CATEGORY_CHOICES = (
|
||||
("S", _("System")),
|
||||
("U", _("User"))
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
|
||||
default=USER_CATEGORY, verbose_name=_("Category"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(
|
||||
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_queryset_group_by_name(cls):
|
||||
|
|
|
@ -39,8 +39,8 @@ class PlatformAutomation(models.Model):
|
|||
gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method"))
|
||||
create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled"))
|
||||
create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method"))
|
||||
change_password_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled"))
|
||||
change_password_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method"))
|
||||
change_secret_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled"))
|
||||
change_secret_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method"))
|
||||
verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled"))
|
||||
verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method"))
|
||||
gather_accounts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled"))
|
||||
|
|
|
@ -33,7 +33,7 @@ def update_internal_platforms(platform_model):
|
|||
{
|
||||
'name': 'AIX', 'category': 'host', 'type': 'unix',
|
||||
'create_account_method': 'create_account_aix',
|
||||
'change_password_method': 'change_password_aix',
|
||||
'change_secret_method': 'change_secret_aix',
|
||||
},
|
||||
{'name': 'Windows', 'category': 'host', 'type': 'windows'},
|
||||
{
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import os
|
||||
import time
|
||||
import shutil
|
||||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
|
||||
class BaseRunner:
|
||||
src_filepath: str
|
||||
|
||||
def __init__(self, assets: List[Asset], strategy):
|
||||
self.assets = assets
|
||||
self.strategy = strategy
|
||||
self.temp_folder = self.temp_folder_path()
|
||||
|
||||
@staticmethod
|
||||
def temp_folder_path():
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
filepath = os.path.join(tmp_dir, str(time.time()))
|
||||
return filepath
|
||||
|
||||
def del_temp_folder(self):
|
||||
shutil.rmtree(self.temp_folder)
|
||||
|
||||
def generate_temp_playbook(self):
|
||||
src = self.src_filepath
|
||||
dst = os.path.join(self.temp_folder, self.strategy)
|
||||
shutil.copytree(src, dst)
|
||||
return dst
|
|
@ -1,47 +0,0 @@
|
|||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
|
||||
class BasePlaybookGenerator:
|
||||
def __init__(self, assets: list[Asset], strategy, ansible_connection='ssh'):
|
||||
self.assets = assets
|
||||
self.strategy = strategy
|
||||
self.playbook_dir = self.temp_folder_path()
|
||||
|
||||
def generate(self):
|
||||
self.prepare_playbook_dir()
|
||||
self.generate_inventory()
|
||||
self.generate_playbook()
|
||||
|
||||
def prepare_playbook_dir(self):
|
||||
pass
|
||||
|
||||
def generate_inventory(self):
|
||||
pass
|
||||
|
||||
def generate_playbook(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def base_dir(self):
|
||||
tmp_dir = os.path.join(settings.PROJECT_DIR, 'tmp')
|
||||
path = os.path.join(tmp_dir, self.strategy)
|
||||
return path
|
||||
|
||||
def temp_folder_path(self):
|
||||
return tempfile.mkdtemp(dir=self.base_dir)
|
||||
|
||||
def del_temp_folder(self):
|
||||
shutil.rmtree(self.playbook_dir)
|
||||
|
||||
def generate_temp_playbook(self):
|
||||
src = self.src_filepath
|
||||
dst = os.path.join(self.temp_folder, self.strategy)
|
||||
shutil.copytree(src, dst)
|
||||
return dst
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,6 +0,0 @@
|
|||
id: change_password_oracle
|
||||
name: Change password for Oracle
|
||||
method: change_password
|
||||
category: database
|
||||
type:
|
||||
- oracle
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,8 +0,0 @@
|
|||
- hosts: all
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
|
@ -1,23 +0,0 @@
|
|||
- name: Check connection
|
||||
ping:
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,10 +0,0 @@
|
|||
{% for account in accounts %}
|
||||
- hosts: {{ account.asset.name }}
|
||||
vars:
|
||||
account:
|
||||
username: {{ account.username }}
|
||||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
{% endfor %}
|
|
@ -1,27 +0,0 @@
|
|||
- name: ping
|
||||
ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
|
@ -1,106 +0,0 @@
|
|||
import os
|
||||
import yaml
|
||||
import jinja2
|
||||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
from assets.models import Asset
|
||||
from .base import BaseGeneratePlaybook
|
||||
|
||||
|
||||
class GenerateChangePasswordPlaybook(BaseGeneratePlaybook):
|
||||
|
||||
def __init__(
|
||||
self, assets: List[Asset], strategy, usernames, password='',
|
||||
private_key='', public_key='', key_strategy=''
|
||||
):
|
||||
super().__init__(assets, strategy)
|
||||
self.password = password
|
||||
self.public_key = public_key
|
||||
self.private_key = private_key
|
||||
self.key_strategy = key_strategy
|
||||
self.relation_asset_map = self.get_username_relation_asset_map(usernames)
|
||||
|
||||
def get_username_relation_asset_map(self, usernames):
|
||||
# TODO 没特权用户的资产 要考虑网关
|
||||
|
||||
complete_map = {
|
||||
asset: list(asset.accounts.value_list('username', flat=True))
|
||||
for asset in self.assets
|
||||
}
|
||||
|
||||
if '*' in usernames:
|
||||
return complete_map
|
||||
|
||||
relation_map = {}
|
||||
for asset, usernames in complete_map.items():
|
||||
usernames = list(set(usernames) & set(usernames))
|
||||
if not usernames:
|
||||
continue
|
||||
relation_map[asset] = list(set(usernames) & set(usernames))
|
||||
return relation_map
|
||||
|
||||
@property
|
||||
def src_filepath(self):
|
||||
return os.path.join(
|
||||
settings.BASE_DIR, 'assets', 'playbooks', 'strategy',
|
||||
'change_password', 'roles', self.strategy
|
||||
)
|
||||
|
||||
def generate_hosts(self):
|
||||
host_pathname = os.path.join(self.temp_folder, 'hosts')
|
||||
with open(host_pathname, 'w', encoding='utf8') as f:
|
||||
for asset in self.relation_asset_map.keys():
|
||||
f.write(f'{asset.name}\n')
|
||||
|
||||
def generate_host_vars(self):
|
||||
host_vars_pathname = os.path.join(self.temp_folder, 'hosts', 'host_vars')
|
||||
os.makedirs(host_vars_pathname, exist_ok=True)
|
||||
for asset, usernames in self.relation_asset_map.items():
|
||||
host_vars = {
|
||||
'ansible_host': asset.get_target_ip(),
|
||||
'ansible_port': asset.get_target_ssh_port(), # TODO 需要根绝协议取端口号
|
||||
'ansible_user': asset.admin_user.username,
|
||||
'ansible_pass': asset.admin_user.username,
|
||||
'usernames': usernames,
|
||||
}
|
||||
pathname = os.path.join(host_vars_pathname, f'{asset.name}.yml')
|
||||
with open(pathname, 'w', encoding='utf8') as f:
|
||||
f.write(yaml.dump(host_vars, allow_unicode=True))
|
||||
|
||||
def generate_secret_key_files(self):
|
||||
if not self.private_key and not self.public_key:
|
||||
return
|
||||
|
||||
file_pathname = os.path.join(self.temp_folder, self.strategy, 'files')
|
||||
public_pathname = os.path.join(file_pathname, 'id_rsa.pub')
|
||||
private_pathname = os.path.join(file_pathname, 'id_rsa')
|
||||
|
||||
os.makedirs(file_pathname, exist_ok=True)
|
||||
with open(public_pathname, 'w', encoding='utf8') as f:
|
||||
f.write(self.public_key)
|
||||
with open(private_pathname, 'w', encoding='utf8') as f:
|
||||
f.write(self.private_key)
|
||||
|
||||
def generate_role_main(self):
|
||||
task_main_pathname = os.path.join(self.temp_folder, 'main.yaml')
|
||||
context = {
|
||||
'password': self.password,
|
||||
'key_strategy': self.key_strategy,
|
||||
'private_key_file': 'id_rsa' if self.private_key else '',
|
||||
'exclusive': 'no' if self.key_strategy == 'all' else 'yes',
|
||||
'jms_key': self.public_key.split()[2].strip() if self.public_key else '',
|
||||
}
|
||||
with open(task_main_pathname, 'r+', encoding='utf8') as f:
|
||||
string_var = f.read()
|
||||
f.seek(0, 0)
|
||||
response = jinja2.Template(string_var).render(context)
|
||||
results = yaml.safe_load(response)
|
||||
f.write(yaml.dump(results, allow_unicode=True))
|
||||
|
||||
def execute(self):
|
||||
self.generate_temp_playbook()
|
||||
self.generate_hosts()
|
||||
self.generate_host_vars()
|
||||
self.generate_secret_key_files()
|
||||
self.generate_role_main()
|
|
@ -1,86 +0,0 @@
|
|||
import os
|
||||
import yaml
|
||||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
from assets.models import Asset
|
||||
from .base import BaseGeneratePlaybook
|
||||
|
||||
|
||||
class GenerateVerifyPlaybook(BaseGeneratePlaybook):
|
||||
|
||||
def __init__(
|
||||
self, assets: List[Asset], strategy, usernames
|
||||
):
|
||||
super().__init__(assets, strategy)
|
||||
self.relation_asset_map = self.get_account_relation_asset_map(usernames)
|
||||
|
||||
def get_account_relation_asset_map(self, usernames):
|
||||
# TODO 没特权用户的资产 要考虑网关
|
||||
complete_map = {
|
||||
asset: list(asset.accounts.all())
|
||||
for asset in self.assets
|
||||
}
|
||||
|
||||
if '*' in usernames:
|
||||
return complete_map
|
||||
|
||||
relation_map = {}
|
||||
for asset, accounts in complete_map.items():
|
||||
account_map = {account.username: account for account in accounts}
|
||||
accounts = [account_map[i] for i in (set(usernames) & set(account_map))]
|
||||
if not accounts:
|
||||
continue
|
||||
relation_map[asset] = accounts
|
||||
return relation_map
|
||||
|
||||
@property
|
||||
def src_filepath(self):
|
||||
return os.path.join(
|
||||
settings.BASE_DIR, 'assets', 'playbooks', 'strategy',
|
||||
'verify', 'roles', self.strategy
|
||||
)
|
||||
|
||||
def generate_hosts(self):
|
||||
host_pathname = os.path.join(self.temp_folder, 'hosts')
|
||||
with open(host_pathname, 'w', encoding='utf8') as f:
|
||||
for asset in self.relation_asset_map.keys():
|
||||
f.write(f'{asset.name}\n')
|
||||
|
||||
def generate_host_vars(self):
|
||||
host_vars_pathname = os.path.join(self.temp_folder, 'hosts', 'host_vars')
|
||||
os.makedirs(host_vars_pathname, exist_ok=True)
|
||||
for asset, accounts in self.relation_asset_map.items():
|
||||
account_info = []
|
||||
for account in accounts:
|
||||
private_key_filename = f'{asset.name}_{account.username}' if account.private_key else ''
|
||||
account_info.append({
|
||||
'username': account.username,
|
||||
'password': account.password,
|
||||
'private_key_filename': private_key_filename,
|
||||
})
|
||||
host_vars = {
|
||||
'ansible_host': asset.get_target_ip(),
|
||||
'ansible_port': asset.get_target_ssh_port(), # TODO 需要根绝协议取端口号
|
||||
'account_info': account_info,
|
||||
}
|
||||
pathname = os.path.join(host_vars_pathname, f'{asset.name}.yml')
|
||||
with open(pathname, 'w', encoding='utf8') as f:
|
||||
f.write(yaml.dump(host_vars, allow_unicode=True))
|
||||
|
||||
def generate_secret_key_files(self):
|
||||
file_pathname = os.path.join(self.temp_folder, self.strategy, 'files')
|
||||
os.makedirs(file_pathname, exist_ok=True)
|
||||
for asset, accounts in self.relation_asset_map.items():
|
||||
for account in accounts:
|
||||
if account.private_key:
|
||||
path_name = os.path.join(file_pathname, f'{asset.name}_{account.username}')
|
||||
with open(path_name, 'w', encoding='utf8') as f:
|
||||
f.write(account.private_key)
|
||||
|
||||
def execute(self):
|
||||
self.generate_temp_playbook()
|
||||
self.generate_hosts()
|
||||
self.generate_host_vars()
|
||||
self.generate_secret_key_files()
|
||||
# self.generate_role_main() # TODO Linux 暂时不需要
|
|
@ -1,13 +0,0 @@
|
|||
- hosts: centos
|
||||
gather_facts: no
|
||||
vars:
|
||||
account:
|
||||
username: web
|
||||
password: test123
|
||||
|
||||
tasks:
|
||||
- name: Verify password
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
|
@ -1,10 +0,0 @@
|
|||
id: ansible_posix_ping
|
||||
name: Ansible posix ping
|
||||
description: Ansible ping
|
||||
category: host
|
||||
type:
|
||||
- linux
|
||||
- unix
|
||||
- macos
|
||||
- bsd
|
||||
method: verify_account
|
|
@ -1,13 +0,0 @@
|
|||
- hosts: centos
|
||||
gather_facts: no
|
||||
vars:
|
||||
account:
|
||||
username: web
|
||||
password: test123
|
||||
|
||||
tasks:
|
||||
- name: Verify password
|
||||
win_ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
|
@ -1,6 +0,0 @@
|
|||
id: ansible_win_ping
|
||||
name: Ansible win ping
|
||||
category: host
|
||||
type:
|
||||
- windows
|
||||
method: verify_account
|
|
@ -1,12 +0,0 @@
|
|||
- hosts: all
|
||||
vars:
|
||||
connection_type: ssh
|
||||
password:
|
||||
value: {{ password }}
|
||||
public_key:
|
||||
value: {{ jms_key }}
|
||||
exclusive: {{ exclusive }}
|
||||
key_strategy: {{ key_strategy }}
|
||||
private_key_file: {{ private_key_file }}
|
||||
roles:
|
||||
- linux
|
|
@ -1,36 +0,0 @@
|
|||
- name: Check connection
|
||||
ping:
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ item }}"
|
||||
password: "{{ password.value | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
with_items: "{{ usernames }}"
|
||||
when: "{{ password.value }}"
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
user: "{{ item }}"
|
||||
key: "{{ lookup('file', id_rsa.pub) }}"
|
||||
state: present
|
||||
exclusive: "{{ public_key.exclusive }}"
|
||||
with_items: "{{ usernames }}"
|
||||
when: "{{ public_key.value and key_strategy != 'set_jms' }}"
|
||||
|
||||
- name: Change public key
|
||||
lineinfile:
|
||||
user: "{{ item }}"
|
||||
dest: /home/{{ item }}/.ssh/authorized_keys regexp='.*{{ public_key.value }}$
|
||||
state: absent
|
||||
with_items: "{{ usernames }}"
|
||||
when: "{{ public_key.value and key_strategy == 'set_jms' }}"
|
||||
|
||||
- name: Verify user
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ item }}"
|
||||
ansible_pass: "{{ password.value }}"
|
||||
ansible_ssh_private_key_file: "{{ private_key_file }}"
|
||||
ansible_connection: "{{ connection_type | default('ssh') }}"
|
||||
with_items: "{{ usernames }}"
|
|
@ -1,5 +0,0 @@
|
|||
- hosts: all
|
||||
vars:
|
||||
connection_type: ssh
|
||||
roles:
|
||||
- linux
|
|
@ -1,8 +0,0 @@
|
|||
- name: Verify user
|
||||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ item.username }}"
|
||||
ansible_pass: "{{ item.username }}"
|
||||
ansible_connection: "{{ connection_type | default('ssh') }}"
|
||||
ansible_ssh_private_key_file: "{{ item.private_key_file }}"
|
||||
with_items: "{{ account_info }}"
|
|
@ -39,7 +39,7 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
|
|||
'ping_enabled', 'ping_method',
|
||||
'gather_facts_enabled', 'gather_facts_method',
|
||||
'create_account_enabled', 'create_account_method',
|
||||
'change_password_enabled', 'change_password_method',
|
||||
'change_secret_enabled', 'change_secret_method',
|
||||
'verify_account_enabled', 'verify_account_method',
|
||||
'gather_accounts_enabled', 'gather_accounts_method',
|
||||
]
|
||||
|
@ -52,8 +52,8 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
|
|||
'verify_account_method': {'label': '校验账号方式'},
|
||||
'create_account_enabled': {'label': '启用创建账号'},
|
||||
'create_account_method': {'label': '创建账号方式'},
|
||||
'change_password_enabled': {'label': '启用账号创建改密'},
|
||||
'change_password_method': {'label': '账号创建改密方式'},
|
||||
'change_secret_enabled': {'label': '启用账号创建改密'},
|
||||
'change_secret_method': {'label': '账号创建改密方式'},
|
||||
'gather_accounts_enabled': {'label': '启用账号收集'},
|
||||
'gather_accounts_method': {'label': '收集账号方式'},
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from .endpoint import *
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue