perf: 修改改密

pull/8970/head
ibuler 2022-10-13 17:47:29 +08:00
commit 8c91cd7eb6
40 changed files with 4850 additions and 240 deletions

View File

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

View File

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

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,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,8 +0,0 @@
id: change_password_sqlserver
name: Change password for SQLServer
version: 1
category: database
type:
- sqlserver
method: change_password

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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')),
('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')),

View File

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

View File

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

View File

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

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

View File

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

View File

@ -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': '收集账号方式'},
}

View File

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

View File

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