mirror of https://github.com/jumpserver/jumpserver
perf: 修改改密
commit
8c91cd7eb6
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ logger = get_logger(__name__)
|
|||
|
||||
class PlaybookCallback(DefaultCallback):
|
||||
def playbook_on_stats(self, event_data, **kwargs):
|
||||
print("\n*** 分任务结果")
|
||||
super().playbook_on_stats(event_data, **kwargs)
|
||||
|
||||
|
||||
|
@ -127,7 +126,7 @@ class BasePlaybookManager:
|
|||
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)
|
||||
self.playbooks.append([playbook_path, hosts])
|
||||
|
||||
main_playbook.append({
|
||||
'name': method['name'] + ' for part {}'.format(i),
|
||||
|
@ -157,6 +156,9 @@ class BasePlaybookManager:
|
|||
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()
|
||||
|
@ -168,6 +170,7 @@ class BasePlaybookManager:
|
|||
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)
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
id: change_password_oracle
|
||||
name: Change password for Oracle
|
||||
method: change_password
|
||||
category: database
|
||||
type:
|
||||
- oracle
|
|
@ -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,8 +0,0 @@
|
|||
id: change_password_sqlserver
|
||||
name: Change password for SQLServer
|
||||
version: 1
|
||||
category: database
|
||||
type:
|
||||
- sqlserver
|
||||
method: change_password
|
||||
|
|
@ -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,29 +0,0 @@
|
|||
- 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,53 +0,0 @@
|
|||
from copy import deepcopy
|
||||
from collections import defaultdict
|
||||
|
||||
from ..base.manager import BasePlaybookManager
|
||||
|
||||
|
||||
class ChangePasswordManager(BasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.method_hosts_mapper = defaultdict(list)
|
||||
self.playbooks = []
|
||||
|
||||
@classmethod
|
||||
def method_type(cls):
|
||||
return 'change_password'
|
||||
|
||||
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('exclude'):
|
||||
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 = []
|
||||
for account in accounts:
|
||||
h = deepcopy(host)
|
||||
h['name'] += '_' + account.username
|
||||
h['account'] = {
|
||||
'name': account.name,
|
||||
'username': account.username,
|
||||
'secret_type': account.secret_type,
|
||||
'secret': account.secret,
|
||||
}
|
||||
inventory_hosts.append(h)
|
||||
method_hosts.append(h['name'])
|
||||
self.method_hosts_mapper[method_attr] = method_hosts
|
||||
return inventory_hosts
|
||||
|
||||
def on_runner_done(self, runner, cb):
|
||||
pass
|
||||
|
||||
def on_runner_failed(self, runner, e):
|
||||
pass
|
||||
|
||||
|
|
@ -4,13 +4,13 @@
|
|||
ansible_python_interpreter: /usr/local/bin/python
|
||||
jms_account:
|
||||
username: root
|
||||
password: redhat
|
||||
secret: redhat
|
||||
jms_asset:
|
||||
address: 127.0.0.1
|
||||
port: 3306
|
||||
account:
|
||||
username: web1
|
||||
password: jumpserver
|
||||
secret: jumpserver
|
||||
|
||||
tasks:
|
||||
- name: Test MySQL connection
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
# msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ account.secret_type }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
password: "{{ account.secret | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
when: account.secret_type == 'password'
|
||||
|
||||
|
@ -26,5 +26,5 @@
|
|||
ping:
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
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
|
|
@ -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
|
||||
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
# from .backup.manager import AccountBackupExecutionManager
|
||||
#
|
||||
#
|
||||
from .change_password.manager import ChangePasswordManager
|
||||
from .change_secret.manager import ChangeSecretManager
|
||||
|
||||
|
||||
class ExecutionManager:
|
||||
manager_type_mapper = {
|
||||
'change_password': ChangePasswordManager,
|
||||
'change_secret': ChangeSecretManager,
|
||||
}
|
||||
|
||||
def __init__(self, execution):
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,5 +6,5 @@
|
|||
password: {{ account.password }}
|
||||
public_key: {{ account.public_key }}
|
||||
roles:
|
||||
- change_password
|
||||
- change_secret
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
id: win_ping
|
||||
name: Windows ping
|
||||
version: 1
|
||||
method: change_password
|
||||
method: change_secret
|
||||
category: host
|
||||
type:
|
||||
- windows
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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')),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 3.2.14 on 2022-10-10 01:59
|
||||
# Generated by Django 3.2.14 on 2022-10-13 06:29
|
||||
|
||||
import common.db.fields
|
||||
from django.conf import settings
|
||||
|
@ -15,6 +15,22 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
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')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Automation strategy execution',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BaseAutomation',
|
||||
fields=[
|
||||
|
@ -30,6 +46,7 @@ class Migration(migrations.Migration):
|
|||
('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')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('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')),
|
||||
|
@ -85,28 +102,43 @@ class Migration(migrations.Migration):
|
|||
bases=('assets.baseautomation',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AutomationExecution',
|
||||
name='ChangeSecretRecord',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('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)),
|
||||
('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')),
|
||||
('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': 'Automation strategy execution',
|
||||
'verbose_name': 'Change secret',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automationexecution',
|
||||
name='automation',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation strategy'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ChangePasswordAutomation',
|
||||
name='ChangeSecretAutomation',
|
||||
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')),
|
||||
('secret_types', models.JSONField(default=list, verbose_name='Secret types')),
|
||||
('password_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16, verbose_name='Password strategy')),
|
||||
('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')),
|
||||
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
|
||||
('ssh_key_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16)),
|
||||
('ssh_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH key')),
|
||||
('ssh_key_change_strategy', 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')),
|
||||
('recipients', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Change auth strategy',
|
|
@ -9,7 +9,6 @@ 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
|
||||
from assets.automations.endpoint import ExecutionManager
|
||||
|
||||
|
||||
class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
|
||||
|
@ -21,6 +20,7 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
|
|||
'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):
|
||||
|
@ -94,5 +94,6 @@ class AutomationExecution(OrgModelMixin):
|
|||
return self.snapshot['type']
|
||||
|
||||
def start(self):
|
||||
from assets.automations.endpoint import ExecutionManager
|
||||
manager = ExecutionManager(execution=self)
|
||||
return manager.run()
|
||||
|
|
|
@ -2,55 +2,58 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.db import fields
|
||||
from ops.const import PasswordStrategy, StrategyChoice
|
||||
from ops.utils import generate_random_password
|
||||
from common.db.models import JMSBaseModel
|
||||
from .base import BaseAutomation
|
||||
|
||||
|
||||
class ChangePasswordAutomation(BaseAutomation):
|
||||
class PasswordStrategy(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')
|
||||
__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'))
|
||||
recipients = models.ManyToManyField(
|
||||
'users.User', related_name='recipients_change_auth_strategy', blank=True,
|
||||
verbose_name=_("Recipient")
|
||||
)
|
||||
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_password'
|
||||
self.type = 'change_secret'
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Change auth strategy")
|
||||
|
||||
def gen_execute_password(self):
|
||||
if self.password_strategy == PasswordStrategy.custom:
|
||||
return self.password
|
||||
elif self.password_strategy == PasswordStrategy.random_one:
|
||||
return generate_random_password(**self.password_rules)
|
||||
else:
|
||||
return None
|
||||
|
||||
def to_attr_json(self):
|
||||
attr_json = super().to_attr_json()
|
||||
attr_json.update({
|
||||
'type': StrategyChoice.change_auth,
|
||||
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'))
|
||||
|
||||
'password': self.gen_execute_password(),
|
||||
'is_password': self.is_password,
|
||||
'password_rules': self.password_rules,
|
||||
'password_strategy': self.password_strategy,
|
||||
class Meta:
|
||||
verbose_name = _("Change secret")
|
||||
|
||||
'is_ssh_key': self.is_ssh_key,
|
||||
'public_key': self.public_key,
|
||||
'private_key': self.private_key,
|
||||
'ssh_key_strategy': self.ssh_key_strategy,
|
||||
'recipients': {
|
||||
str(recipient.id): (str(recipient), bool(recipient.secret_key))
|
||||
for recipient in self.recipients.all()
|
||||
}
|
||||
})
|
||||
return attr_json
|
||||
def __str__(self):
|
||||
return self.account.__str__()
|
||||
|
|
|
@ -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'},
|
||||
{
|
||||
|
|
|
@ -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': '收集账号方式'},
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class JMSInventory:
|
|||
def clean_assets(assets):
|
||||
from assets.models import Asset
|
||||
asset_ids = [asset.id for asset in assets]
|
||||
assets = Asset.objects.filter(id__in=asset_ids)\
|
||||
assets = Asset.objects.filter(id__in=asset_ids, is_active=True)\
|
||||
.prefetch_related('platform', 'domain', 'accounts')
|
||||
return assets
|
||||
|
||||
|
@ -58,9 +58,9 @@ class JMSInventory:
|
|||
)
|
||||
return {"ansible_ssh_common_args": proxy_command}
|
||||
|
||||
def asset_to_host(self, asset, account, automation, protocols):
|
||||
def asset_to_host(self, asset, account, automation, protocols, platform):
|
||||
host = {
|
||||
'name': asset.name,
|
||||
'name': '{}'.format(asset.name),
|
||||
'jms_asset': {
|
||||
'id': str(asset.id), 'name': asset.name, 'address': asset.address,
|
||||
'type': asset.type, 'category': asset.category,
|
||||
|
@ -72,7 +72,9 @@ class JMSInventory:
|
|||
'secret': account.secret, 'secret_type': account.secret_type
|
||||
} if account else None
|
||||
}
|
||||
ansible_connection = automation.ansible_config.get('ansible_connection', 'ssh')
|
||||
ansible_config = dict(automation.ansible_config)
|
||||
ansible_connection = ansible_config.pop('ansible_connection', 'ssh')
|
||||
host.update(ansible_config)
|
||||
gateway = None
|
||||
if asset.domain:
|
||||
gateway = asset.domain.select_gateway()
|
||||
|
@ -136,9 +138,9 @@ class JMSInventory:
|
|||
automation = platform.automation
|
||||
protocols = platform.protocols.all()
|
||||
|
||||
for asset in self.assets:
|
||||
for asset in assets:
|
||||
account = self.select_account(asset)
|
||||
host = self.asset_to_host(asset, account, automation, protocols)
|
||||
host = self.asset_to_host(asset, account, automation, protocols, platform)
|
||||
|
||||
if not automation.ansible_enabled:
|
||||
host['error'] = _('Ansible disabled')
|
||||
|
|
|
@ -6,7 +6,7 @@ class StrategyChoice(models.TextChoices):
|
|||
push = 'push', _('Push')
|
||||
verify = 'verify', _('Verify')
|
||||
collect = 'collect', _('Collect')
|
||||
change_password = 'change_password', _('Change password')
|
||||
change_secret = 'change_secret', _('Change password')
|
||||
|
||||
|
||||
class SSHKeyStrategy(models.TextChoices):
|
||||
|
|
Loading…
Reference in New Issue