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

This commit is contained in:
Jiangjie.Bai 2022-10-13 18:19:56 +08:00
commit d52baf0af5
166 changed files with 7008 additions and 3186 deletions

View File

@ -18,7 +18,6 @@ __all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI']
class AccountViewSet(OrgBulkModelViewSet): class AccountViewSet(OrgBulkModelViewSet):
model = Account model = Account
filterset_fields = ("username", "asset", 'name')
search_fields = ('username', 'asset__address', 'name') search_fields = ('username', 'asset__address', 'name')
filterset_class = AccountFilterSet filterset_class = AccountFilterSet
serializer_classes = { serializer_classes = {
@ -33,7 +32,7 @@ class AccountViewSet(OrgBulkModelViewSet):
@action(methods=['post'], detail=True, url_path='verify') @action(methods=['post'], detail=True, url_path='verify')
def verify_account(self, request, *args, **kwargs): def verify_account(self, request, *args, **kwargs):
account = super().get_object() 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}) return Response(data={'task': task.id})
@ -54,7 +53,6 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
class AccountTaskCreateAPI(CreateAPIView): class AccountTaskCreateAPI(CreateAPIView):
serializer_class = serializers.AccountTaskSerializer serializer_class = serializers.AccountTaskSerializer
filterset_fields = AccountViewSet.filterset_fields
search_fields = AccountViewSet.search_fields search_fields = AccountViewSet.search_fields
filterset_class = AccountViewSet.filterset_class filterset_class = AccountViewSet.filterset_class
@ -67,8 +65,8 @@ class AccountTaskCreateAPI(CreateAPIView):
return queryset return queryset
def perform_create(self, serializer): def perform_create(self, serializer):
accounts = self.get_accounts() account_ids = self.get_accounts().values_list('id', flat=True)
task = test_accounts_connectivity_manual.delay(accounts) task = test_accounts_connectivity_manual.delay(account_ids)
data = getattr(serializer, '_data', {}) data = getattr(serializer, '_data', {})
data["task"] = task.id data["task"] = task.id
setattr(serializer, '_data', data) setattr(serializer, '_data', data)

View File

@ -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.api import JMSModelViewSet
from common.drf.serializers import GroupedChoiceSerializer from common.drf.serializers import GroupedChoiceSerializer
from assets.models import Platform from assets.models import Platform
from assets.serializers import PlatformSerializer, PlatformOpsMethodSerializer from assets.serializers import PlatformSerializer
from assets.const import AllTypes
from assets.playbooks import filter_platform_methods
__all__ = ['AssetPlatformViewSet'] __all__ = ['AssetPlatformViewSet']

View File

@ -0,0 +1 @@
from .methods import platform_automation_methods, filter_platform_methods

View File

@ -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"

View File

@ -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')

View File

@ -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

View File

@ -1,6 +1,6 @@
id: change_password_mysql id: change_secret_mysql
name: Change password for MySQL name: Change password for MySQL
category: database category: database
type: type:
- mysql - mysql
method: change_password method: change_secret

View File

@ -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 }}"

View File

@ -1,6 +1,6 @@
id: change_password_postgresql id: change_secret_postgresql
name: Change password for PostgreSQL name: Change password for PostgreSQL
category: database category: database
type: type:
- postgresql - postgresql
method: change_password method: change_secret

View File

@ -0,0 +1,2 @@
# all base inventory in base/base_inventory.txt
asset_name(ip)_account_username account={"username": "", "password": "xxx"} ...base_inventory_vars

View File

@ -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

View File

@ -1,6 +1,6 @@
id: change_password_aix id: change_secret_aix
name: Change password for AIX name: Change password for AIX
category: host category: host
type: type:
- aix - aix
method: change_password method: change_secret

View File

@ -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

View File

@ -1,7 +1,7 @@
id: change_password_linux id: change_secret_linux
name: Change password for Linux name: Change password for Linux
category: host category: host
type: type:
- unix - unix
- linux - linux
method: change_password method: change_secret

View File

@ -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

View File

@ -1,7 +1,7 @@
id: change_password_local_windows id: change_secret_local_windows
name: Change password local account for Windows name: Change password local account for Windows
version: 1 version: 1
method: change_password method: change_secret
category: host category: host
type: type:
- windows - windows

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,6 @@
id: gather_facts_postgresql
name: Gather facts for PostgreSQL
category: database
type:
- postgresql
method: gather_facts

View File

@ -6,5 +6,5 @@
password: {{ account.password }} password: {{ account.password }}
public_key: {{ account.public_key }} public_key: {{ account.public_key }}
roles: roles:
- change_password - change_secret
{% endfor %} {% endfor %}

View File

@ -1,8 +1,8 @@
id: change_password_sqlserver id: gather_facts_sqlserver
name: Change password for SQLServer name: Change password for SQLServer
version: 1 version: 1
category: database category: database
type: type:
- sqlserver - sqlserver
method: change_password method: gather_facts

View File

@ -0,0 +1,2 @@
# all base inventory in base/base_inventory.txt
asset_name(ip) ...base_inventory_vars

View File

@ -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

View File

@ -0,0 +1,8 @@
id: gather_facts_posix
name: Gather posix facts
category: host
type:
- linux
- windows
- unix
method: gather_facts

View File

@ -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

View File

@ -0,0 +1,7 @@
id: gather_facts_windows
name: Gather facts windows
version: 1
method: gather_facts
category: host
type:
- windows

View File

@ -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
)

View File

@ -1,5 +1,6 @@
import os import os
import yaml import yaml
import json
from functools import partial from functools import partial
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@ -62,4 +63,4 @@ platform_automation_methods = get_platform_automation_methods()
if __name__ == '__main__': if __name__ == '__main__':
print(get_platform_automation_methods()) print(json.dumps(platform_automation_methods, indent=4))

View File

View File

@ -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

View File

@ -0,0 +1,6 @@
id: mysql_ping
name: Ping MySQL
category: database
type:
- mysql
method: ping

View File

@ -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 }}"

View File

@ -0,0 +1,6 @@
id: ping_postgresql
name: Ping PostgreSQL
category: database
type:
- postgresql
method: ping

View File

@ -0,0 +1,2 @@
# all base inventory in base/base_inventory.txt
asset_name(ip)_account_username account={"username": "", "password": "xxx"} ...base_inventory_vars

View File

@ -0,0 +1,5 @@
- hosts: demo
gather_facts: no
tasks:
- name: Posix ping
ping:

View File

@ -0,0 +1,8 @@
id: posix_ping
name: Posix ping
category: host
type:
- linux
- windows
- unix
method: ping

View File

@ -0,0 +1,5 @@
- hosts: windows
gather_facts: no
tasks:
- name: Windows ping
win_ping:

View File

@ -0,0 +1,7 @@
id: win_ping
name: Windows ping
version: 1
method: change_secret
category: host
type:
- windows

View File

@ -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
)

View File

@ -24,7 +24,7 @@ class CloudTypes(BaseType):
'ansible_config': {}, 'ansible_config': {},
'gather_facts_enabled': False, 'gather_facts_enabled': False,
'verify_account_enabled': False, 'verify_account_enabled': False,
'change_password_enabled': False, 'change_secret_enabled': False,
'create_account_enabled': False, 'create_account_enabled': False,
'gather_accounts_enabled': False, 'gather_accounts_enabled': False,
} }

View File

@ -32,7 +32,7 @@ class DatabaseTypes(BaseType):
'gather_facts_enabled': True, 'gather_facts_enabled': True,
'gather_accounts_enabled': True, 'gather_accounts_enabled': True,
'verify_account_enabled': True, 'verify_account_enabled': True,
'change_password_enabled': True, 'change_secret_enabled': True,
'create_account_enabled': True, 'create_account_enabled': True,
} }
} }

View File

@ -39,7 +39,7 @@ class DeviceTypes(BaseType):
'gather_facts_enabled': False, 'gather_facts_enabled': False,
'gather_accounts_enabled': False, 'gather_accounts_enabled': False,
'verify_account_enabled': False, 'verify_account_enabled': False,
'change_password_enabled': False, 'change_secret_enabled': False,
'create_account_enabled': False, 'create_account_enabled': False,
} }
} }

View File

@ -48,12 +48,12 @@ class HostTypes(BaseType):
'gather_facts_enabled': True, 'gather_facts_enabled': True,
'gather_accounts_enabled': True, 'gather_accounts_enabled': True,
'verify_account_enabled': True, 'verify_account_enabled': True,
'change_password_enabled': True, 'change_secret_enabled': True,
'create_account_enabled': True, 'create_account_enabled': True,
}, },
cls.WINDOWS: { cls.WINDOWS: {
'ansible_config': { 'ansible_config': {
'ansible_shell_type': 'powershell', 'ansible_shell_type': 'cmd',
'ansible_connection': 'ssh', 'ansible_connection': 'ssh',
}, },
}, },
@ -71,7 +71,7 @@ class HostTypes(BaseType):
{'name': 'BSD'}, {'name': 'BSD'},
{'name': 'AIX', 'automation': { {'name': 'AIX', 'automation': {
'create_account_method': 'create_account_aix', 'create_account_method': 'create_account_aix',
'change_password_method': 'change_password_aix' 'change_secret_method': 'change_secret_aix'
}}, }},
], ],
cls.WINDOWS: [ cls.WINDOWS: [

View File

@ -38,7 +38,7 @@ class AllTypes(ChoicesMixin):
@classmethod @classmethod
def set_automation_methods(cls, category, tp, constraints): 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 = constraints.get('automation', {})
automation_methods = {} automation_methods = {}
for item, enabled in automation.items(): for item, enabled in automation.items():

View File

@ -22,7 +22,7 @@ class WebTypes(BaseType):
'*': { '*': {
'gather_facts_enabled': False, 'gather_facts_enabled': False,
'verify_account_enabled': False, 'verify_account_enabled': False,
'change_password_enabled': False, 'change_secret_enabled': False,
'create_account_enabled': False, 'create_account_enabled': False,
'gather_accounts_enabled': False, 'gather_accounts_enabled': False,
} }

View File

@ -3,8 +3,9 @@
from django.db.models import Q from django.db.models import Q
from rest_framework import filters from rest_framework import filters
from rest_framework.compat import coreapi, coreschema 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 assets.utils import is_query_node_all_assets, get_node_from_request
from .models import Label, Node, Account from .models import Label, Node, Account
@ -157,15 +158,16 @@ class IpInFilterBackend(filters.BaseFilterBackend):
class AccountFilterSet(BaseFilterSet): class AccountFilterSet(BaseFilterSet):
from django_filters import rest_framework as filters ip = drf_filters.CharFilter(field_name='address', lookup_expr='exact')
ip = filters.CharFilter(field_name='address', lookup_expr='exact') hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact')
hostname = filters.CharFilter(field_name='name', lookup_expr='exact') username = drf_filters.CharFilter(field_name="username", lookup_expr='exact')
username = filters.CharFilter(field_name="username", lookup_expr='exact') address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact')
address = filters.CharFilter(field_name="asset__address", lookup_expr='exact') asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact')
assets = UUIDInFilter(field_name='asset_id', lookup_expr='in') assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='in')
nodes = UUIDInFilter(method='filter_nodes') 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) nodes = Node.objects.filter(id__in=value)
if not nodes: if not nodes:
return queryset return queryset
@ -179,6 +181,4 @@ class AccountFilterSet(BaseFilterSet):
class Meta: class Meta:
model = Account model = Account
fields = [ fields = ['id']
'asset', 'id'
]

View File

@ -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')), ('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_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')), ('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_secret_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_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_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')), ('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')), ('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')),

View File

@ -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',),
),
]

View File

@ -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',
},
),
]

View File

@ -10,6 +10,7 @@ from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account import *
from .backup import * from .backup import *
from .automations import *
from ._user import * from ._user import *
# 废弃以下 # 废弃以下
# from ._authbook import * # from ._authbook import *

View File

@ -4,6 +4,7 @@
import logging import logging
import uuid import uuid
from collections import defaultdict
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
@ -41,6 +42,12 @@ class AssetQuerySet(models.QuerySet):
def has_protocol(self, name): def has_protocol(self, name):
return self.filter(protocols__contains=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: class NodesRelationMixin:
NODES_CACHE_KEY = 'ASSET_NODES_{}' NODES_CACHE_KEY = 'ASSET_NODES_{}'
@ -126,6 +133,22 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel):
names.append(n.name + ':' + n.value) names.append(n.name + ':' + n.value)
return names 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 @property
def protocols_as_list(self): def protocols_as_list(self):
return [{'name': p.name, 'port': p.port} for p in self.protocols.all()] return [{'name': p.name, 'port': p.port} for p in self.protocols.all()]

View File

@ -0,0 +1,4 @@
from .change_secret import *
from .account_discovery import *
from .account_reconcile import *
from .account_verify import *

View File

@ -1,12 +1,13 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ops.const import StrategyChoice 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: class Meta:
verbose_name = _("Collect strategy") verbose_name = _("Discovery strategy")
def to_attr_json(self): def to_attr_json(self):
attr_json = super().to_attr_json() attr_json = super().to_attr_json()

View File

@ -1,12 +1,12 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ops.const import StrategyChoice from ops.const import StrategyChoice
from .common import AutomationStrategy from .base import BaseAutomation
class PushStrategy(AutomationStrategy): class ReconcileAutomation(BaseAutomation):
class Meta: class Meta:
verbose_name = _("Push strategy") verbose_name = _("Reconcile strategy")
def to_attr_json(self): def to_attr_json(self):
attr_json = super().to_attr_json() attr_json = super().to_attr_json()

View File

@ -1,10 +1,10 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ops.const import StrategyChoice from ops.const import StrategyChoice
from .common import AutomationStrategy from .base import BaseAutomation
class VerifyStrategy(AutomationStrategy): class VerifyAutomation(BaseAutomation):
class Meta: class Meta:
verbose_name = _("Verify strategy") verbose_name = _("Verify strategy")

View File

@ -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()

View File

@ -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__()

View File

@ -76,6 +76,15 @@ class BaseAccount(OrgModelMixin):
def has_secret(self): def has_secret(self):
return bool(self.secret) 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 @property
def private_key(self): def private_key(self):
if self.secret_type == self.SecretType.ssh_key: if self.secret_type == self.SecretType.ssh_key:
@ -91,15 +100,6 @@ class BaseAccount(OrgModelMixin):
self.secret = value self.secret = value
self.secret_type = 'private_key' 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 @property
def ssh_key_fingerprint(self): def ssh_key_fingerprint(self):
if self.public_key: if self.public_key:
@ -125,8 +125,8 @@ class BaseAccount(OrgModelMixin):
return None return None
@property @property
def private_key_file(self): def private_key_path(self):
if not self.private_key_obj: if not self.secret_type != 'ssh_key' or not self.secret:
return None return None
project_dir = settings.PROJECT_DIR project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp') tmp_dir = os.path.join(project_dir, 'tmp')

View File

@ -40,6 +40,9 @@ class Domain(OrgModelMixin):
def gateways(self): def gateways(self):
return self.gateway_set.filter(is_active=True) return self.gateway_set.filter(is_active=True)
def select_gateway(self):
return self.random_gateway()
def random_gateway(self): def random_gateway(self):
gateways = [gw for gw in self.gateways if gw.is_connective] gateways = [gw for gw in self.gateways if gw.is_connective]
if gateways: if gateways:

View File

@ -1,29 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ 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" SYSTEM_CATEGORY = "S"
USER_CATEGORY = "U" USER_CATEGORY = "U"
CATEGORY_CHOICES = ( CATEGORY_CHOICES = (
("S", _("System")), ("S", _("System")),
("U", _("User")) ("U", _("User"))
) )
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_("Name")) name = models.CharField(max_length=128, verbose_name=_("Name"))
value = models.CharField(max_length=128, verbose_name=_("Value")) value = models.CharField(max_length=128, verbose_name=_("Value"))
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
default=USER_CATEGORY, verbose_name=_("Category")) default=USER_CATEGORY, verbose_name=_("Category"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active")) is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) 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 @classmethod
def get_queryset_group_by_name(cls): def get_queryset_group_by_name(cls):

View File

@ -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")) 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_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")) 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_secret_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_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_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")) 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")) gather_accounts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled"))

View File

@ -33,7 +33,7 @@ def update_internal_platforms(platform_model):
{ {
'name': 'AIX', 'category': 'host', 'type': 'unix', 'name': 'AIX', 'category': 'host', 'type': 'unix',
'create_account_method': 'create_account_aix', 'create_account_method': 'create_account_aix',
'change_password_method': 'change_password_aix', 'change_secret_method': 'change_secret_aix',
}, },
{'name': 'Windows', 'category': 'host', 'type': 'windows'}, {'name': 'Windows', 'category': 'host', 'type': 'windows'},
{ {

View File

@ -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

View File

@ -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

View File

@ -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 %}

View File

@ -1,6 +0,0 @@
id: change_password_oracle
name: Change password for Oracle
method: change_password
category: database
type:
- oracle

View File

@ -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

View File

@ -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 %}

View File

@ -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

View File

@ -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 %}

View File

@ -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

View File

@ -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 %}

View File

@ -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

View File

@ -1,8 +0,0 @@
- hosts: all
vars:
account:
username: {{ account.username }}
password: {{ account.password }}
public_key: {{ account.public_key }}
roles:
- change_password

View File

@ -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

View File

@ -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 %}

View File

@ -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

View File

@ -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()

View File

@ -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 暂时不需要

View File

@ -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 }}"

View File

@ -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

View File

@ -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 }}"

View File

@ -1,6 +0,0 @@
id: ansible_win_ping
name: Ansible win ping
category: host
type:
- windows
method: verify_account

View File

@ -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

View File

@ -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 }}"

View File

@ -1,5 +0,0 @@
- hosts: all
vars:
connection_type: ssh
roles:
- linux

View File

@ -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 }}"

View File

@ -39,7 +39,7 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
'ping_enabled', 'ping_method', 'ping_enabled', 'ping_method',
'gather_facts_enabled', 'gather_facts_method', 'gather_facts_enabled', 'gather_facts_method',
'create_account_enabled', 'create_account_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', 'verify_account_enabled', 'verify_account_method',
'gather_accounts_enabled', 'gather_accounts_method', 'gather_accounts_enabled', 'gather_accounts_method',
] ]
@ -52,8 +52,8 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
'verify_account_method': {'label': '校验账号方式'}, 'verify_account_method': {'label': '校验账号方式'},
'create_account_enabled': {'label': '启用创建账号'}, 'create_account_enabled': {'label': '启用创建账号'},
'create_account_method': {'label': '创建账号方式'}, 'create_account_method': {'label': '创建账号方式'},
'change_password_enabled': {'label': '启用账号创建改密'}, 'change_secret_enabled': {'label': '启用账号创建改密'},
'change_password_method': {'label': '账号创建改密方式'}, 'change_secret_method': {'label': '账号创建改密方式'},
'gather_accounts_enabled': {'label': '启用账号收集'}, 'gather_accounts_enabled': {'label': '启用账号收集'},
'gather_accounts_method': {'label': '收集账号方式'}, 'gather_accounts_method': {'label': '收集账号方式'},
} }

View File

@ -1 +0,0 @@
from .endpoint import *

Some files were not shown because too many files have changed in this diff Show More