2022-10-13 09:47:29 +00:00
|
|
|
import random
|
|
|
|
import string
|
2022-10-14 08:33:24 +00:00
|
|
|
from copy import deepcopy
|
|
|
|
from collections import defaultdict
|
|
|
|
|
|
|
|
from django.utils import timezone
|
2022-10-13 09:47:29 +00:00
|
|
|
|
|
|
|
from common.utils import lazyproperty, gen_key_pair
|
2022-10-19 09:05:21 +00:00
|
|
|
from assets.models import ChangeSecretRecord
|
|
|
|
from assets.const import (
|
|
|
|
AutomationTypes, SecretType, SecretStrategy, DEFAULT_PASSWORD_RULES
|
|
|
|
)
|
2022-10-14 08:33:24 +00:00
|
|
|
from ..base.manager import BasePlaybookManager
|
2022-10-13 09:47:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ChangeSecretManager(BasePlaybookManager):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.method_hosts_mapper = defaultdict(list)
|
2022-10-19 09:05:21 +00:00
|
|
|
self.secret_strategy = self.execution.plan_snapshot['secret_strategy']
|
|
|
|
self.ssh_key_change_strategy = self.execution.plan_snapshot['ssh_key_change_strategy']
|
2022-10-13 09:47:29 +00:00
|
|
|
self._password_generated = None
|
|
|
|
self._ssh_key_generated = None
|
|
|
|
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def method_type(cls):
|
2022-10-19 09:05:21 +00:00
|
|
|
return AutomationTypes.change_secret
|
2022-10-13 09:47:29 +00:00
|
|
|
|
|
|
|
@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):
|
2022-10-19 09:05:21 +00:00
|
|
|
kwargs = self.automation.plan_snapshot['password_rules'] or {}
|
2022-10-13 09:47:29 +00:00
|
|
|
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']
|
2022-10-19 09:05:21 +00:00
|
|
|
|
|
|
|
no_special_chars = string.ascii_letters + string.digits
|
|
|
|
chars = no_special_chars + symbol_set
|
|
|
|
|
|
|
|
first_char = random.choice(no_special_chars)
|
|
|
|
password = ''.join([random.choice(chars) for _ in range(length - 1)])
|
|
|
|
password = first_char + password
|
2022-10-13 09:47:29 +00:00
|
|
|
return password
|
|
|
|
|
|
|
|
def get_ssh_key(self):
|
2022-10-19 09:05:21 +00:00
|
|
|
if self.secret_strategy == SecretStrategy.custom:
|
|
|
|
ssh_key = self.automation.plan_snapshot['ssh_key']
|
|
|
|
if not ssh_key:
|
|
|
|
raise ValueError("Automation SSH key must be set")
|
|
|
|
return ssh_key
|
|
|
|
elif self.secret_strategy == SecretStrategy.random_one:
|
2022-10-13 09:47:29 +00:00
|
|
|
if not self._ssh_key_generated:
|
|
|
|
self._ssh_key_generated = self.generate_ssh_key()
|
|
|
|
return self._ssh_key_generated
|
|
|
|
else:
|
2022-10-19 09:05:21 +00:00
|
|
|
return self.generate_ssh_key()
|
2022-10-13 09:47:29 +00:00
|
|
|
|
|
|
|
def get_password(self):
|
2022-10-19 09:05:21 +00:00
|
|
|
if self.secret_strategy == SecretStrategy.custom:
|
|
|
|
password = self.automation.plan_snapshot['password']
|
|
|
|
if not password:
|
2022-10-13 09:47:29 +00:00
|
|
|
raise ValueError("Automation Password must be set")
|
2022-10-19 09:05:21 +00:00
|
|
|
return password
|
|
|
|
elif self.secret_strategy == SecretStrategy.random_one:
|
2022-10-13 09:47:29 +00:00
|
|
|
if not self._password_generated:
|
|
|
|
self._password_generated = self.generate_password()
|
|
|
|
return self._password_generated
|
|
|
|
else:
|
2022-10-19 09:05:21 +00:00
|
|
|
return self.generate_password()
|
2022-10-13 09:47:29 +00:00
|
|
|
|
|
|
|
def get_secret(self, account):
|
2022-10-19 09:05:21 +00:00
|
|
|
if account.secret_type == SecretType.ssh_key:
|
2022-10-13 09:47:29 +00:00
|
|
|
secret = self.get_ssh_key()
|
2022-10-19 09:05:21 +00:00
|
|
|
elif account.secret_type == SecretType.password:
|
2022-10-13 09:47:29 +00:00
|
|
|
secret = self.get_password()
|
2022-10-19 09:05:21 +00:00
|
|
|
else:
|
2022-10-13 09:47:29 +00:00
|
|
|
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
|
|
|
|
|
2022-10-14 08:33:24 +00:00
|
|
|
def on_host_success(self, host, result):
|
|
|
|
recorder = self.name_recorder_mapper.get(host)
|
|
|
|
if not recorder:
|
|
|
|
return
|
|
|
|
recorder.status = 'succeed'
|
|
|
|
recorder.date_finished = timezone.now()
|
|
|
|
recorder.save()
|
|
|
|
|
|
|
|
account = recorder.account
|
|
|
|
account.secret = recorder.new_secret
|
|
|
|
account.save(update_fields=['secret'])
|
|
|
|
|
|
|
|
def on_host_error(self, host, error, result):
|
|
|
|
recorder = self.name_recorder_mapper.get(host)
|
|
|
|
if not recorder:
|
|
|
|
return
|
|
|
|
recorder.status = 'failed'
|
|
|
|
recorder.date_finished = timezone.now()
|
|
|
|
recorder.error = error
|
|
|
|
recorder.save()
|
2022-10-13 09:47:29 +00:00
|
|
|
|
|
|
|
def on_runner_failed(self, runner, e):
|
|
|
|
pass
|