mirror of https://github.com/jumpserver/jumpserver
				
				
				
			Merge branch 'v3' of github.com:jumpserver/jumpserver into v3
						commit
						3e7004d784
					
				| 
						 | 
				
			
			@ -3,24 +3,36 @@
 | 
			
		|||
  tasks:
 | 
			
		||||
    - name: Test privileged account
 | 
			
		||||
      ansible.builtin.ping:
 | 
			
		||||
#
 | 
			
		||||
#    - name: print variables
 | 
			
		||||
#      debug:
 | 
			
		||||
#        msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ account.secret_type }}"
 | 
			
		||||
    #
 | 
			
		||||
    #    - name: print variables
 | 
			
		||||
    #      debug:
 | 
			
		||||
    #        msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ secret_type }}"
 | 
			
		||||
 | 
			
		||||
    - name: Change password
 | 
			
		||||
      ansible.builtin.user:
 | 
			
		||||
        name: "{{ account.username }}"
 | 
			
		||||
        password: "{{ account.secret | password_hash('sha512') }}"
 | 
			
		||||
        update_password: always
 | 
			
		||||
      when: account.secret_type == "password"
 | 
			
		||||
      when: "{{ secret_type == 'password' }}"
 | 
			
		||||
 | 
			
		||||
    - name: Change public key
 | 
			
		||||
    - name: create user If it already exists, no operation will be performed
 | 
			
		||||
      ansible.builtin.user:
 | 
			
		||||
        name: "{{ account.username }}"
 | 
			
		||||
      when: "{{ secret_type == 'ssh_key' }}"
 | 
			
		||||
 | 
			
		||||
    - name: remove jumpserver ssh key
 | 
			
		||||
      ansible.builtin.lineinfile:
 | 
			
		||||
        dest: "{{ kwargs.dest }}"
 | 
			
		||||
        regexp: "{{ kwargs.regexp }}"
 | 
			
		||||
        state: absent
 | 
			
		||||
      when: "{{ secret_type == 'ssh_key' and kwargs.strategy == 'set_jms' }}"
 | 
			
		||||
 | 
			
		||||
    - name: Change SSH key
 | 
			
		||||
      ansible.builtin.authorized_key:
 | 
			
		||||
        user: "{{ account.username }}"
 | 
			
		||||
        key: "{{ account.public_key }}"
 | 
			
		||||
        state: present
 | 
			
		||||
      when: account.secret_type == "public_key"
 | 
			
		||||
        key: "{{ account.secret }}"
 | 
			
		||||
        exclusive: "{{ kwargs.exclusive }}"
 | 
			
		||||
      when: "{{ secret_type == 'ssh_key' }}"
 | 
			
		||||
 | 
			
		||||
    - name: Refresh connection
 | 
			
		||||
      ansible.builtin.meta: reset_connection
 | 
			
		||||
| 
						 | 
				
			
			@ -32,3 +44,13 @@
 | 
			
		|||
        ansible_user: "{{ account.username }}"
 | 
			
		||||
        ansible_password: "{{ account.secret }}"
 | 
			
		||||
        ansible_become: no
 | 
			
		||||
      when: "{{ secret_type == 'password' }}"
 | 
			
		||||
 | 
			
		||||
    - name: Verify SSH key
 | 
			
		||||
      ansible.builtin.ping:
 | 
			
		||||
      become: no
 | 
			
		||||
      vars:
 | 
			
		||||
        ansible_user: "{{ account.username }}"
 | 
			
		||||
        ansible_ssh_private_key_file: "{{ account.private_key_path }}"
 | 
			
		||||
        ansible_become: no
 | 
			
		||||
      when: "{{ secret_type == 'ssh_key' }}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,17 @@
 | 
			
		|||
import os
 | 
			
		||||
import random
 | 
			
		||||
import string
 | 
			
		||||
from hashlib import md5
 | 
			
		||||
from copy import deepcopy
 | 
			
		||||
from socket import gethostname
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from common.utils import lazyproperty, gen_key_pair
 | 
			
		||||
from common.utils import lazyproperty, gen_key_pair, ssh_pubkey_gen, ssh_key_string_to_obj
 | 
			
		||||
from assets.models import ChangeSecretRecord
 | 
			
		||||
from assets.const import (
 | 
			
		||||
    AutomationTypes, SecretType, SecretStrategy, DEFAULT_PASSWORD_RULES
 | 
			
		||||
    AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES
 | 
			
		||||
)
 | 
			
		||||
from ..base.manager import BasePlaybookManager
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,15 +20,15 @@ class ChangeSecretManager(BasePlaybookManager):
 | 
			
		|||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.method_hosts_mapper = defaultdict(list)
 | 
			
		||||
        self.secret_type = self.execution.plan_snapshot.get('secret_type')
 | 
			
		||||
        self.secret_strategy = self.execution.plan_snapshot['secret_strategy']
 | 
			
		||||
        self.ssh_key_change_strategy = self.execution.plan_snapshot['ssh_key_change_strategy']
 | 
			
		||||
        self._password_generated = None
 | 
			
		||||
        self._ssh_key_generated = None
 | 
			
		||||
        self.name_recorder_mapper = {}  # 做个映射,方便后面处理
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def method_type(cls):
 | 
			
		||||
        return AutomationTypes.change_secret
 | 
			
		||||
        return AutomationTypes.method_id_meta_mapper
 | 
			
		||||
 | 
			
		||||
    @lazyproperty
 | 
			
		||||
    def related_accounts(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +39,19 @@ class ChangeSecretManager(BasePlaybookManager):
 | 
			
		|||
        private_key, public_key = gen_key_pair()
 | 
			
		||||
        return private_key
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def generate_public_key(private_key):
 | 
			
		||||
        return ssh_pubkey_gen(private_key=private_key, hostname=gethostname())
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def generate_private_key_path(secret, path_dir):
 | 
			
		||||
        key_name = '.' + md5(secret.encode('utf-8')).hexdigest()
 | 
			
		||||
        key_path = os.path.join(path_dir, key_name)
 | 
			
		||||
        if not os.path.exists(key_path):
 | 
			
		||||
            ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path)
 | 
			
		||||
            os.chmod(key_path, 0o400)
 | 
			
		||||
        return key_path
 | 
			
		||||
 | 
			
		||||
    def generate_password(self):
 | 
			
		||||
        kwargs = self.automation.plan_snapshot['password_rules'] or {}
 | 
			
		||||
        length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length']))
 | 
			
		||||
| 
						 | 
				
			
			@ -77,16 +93,29 @@ class ChangeSecretManager(BasePlaybookManager):
 | 
			
		|||
        else:
 | 
			
		||||
            return self.generate_password()
 | 
			
		||||
 | 
			
		||||
    def get_secret(self, account):
 | 
			
		||||
        if account.secret_type == SecretType.ssh_key:
 | 
			
		||||
    def get_secret(self):
 | 
			
		||||
        if self.secret_type == SecretType.ssh_key:
 | 
			
		||||
            secret = self.get_ssh_key()
 | 
			
		||||
        elif account.secret_type == SecretType.password:
 | 
			
		||||
        elif self.secret_type == SecretType.password:
 | 
			
		||||
            secret = self.get_password()
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Secret must be set")
 | 
			
		||||
        return secret
 | 
			
		||||
 | 
			
		||||
    def host_callback(self, host, asset=None, account=None, automation=None, **kwargs):
 | 
			
		||||
    def get_kwargs(self, account, secret):
 | 
			
		||||
        kwargs = {}
 | 
			
		||||
        if self.secret_type != SecretType.ssh_key:
 | 
			
		||||
            return kwargs
 | 
			
		||||
        kwargs['strategy'] = self.automation.plan_snapshot['ssh_key_change_strategy']
 | 
			
		||||
        kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
 | 
			
		||||
 | 
			
		||||
        if kwargs['strategy'] == SSHKeyStrategy.set_jms:
 | 
			
		||||
            kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username)
 | 
			
		||||
            kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
 | 
			
		||||
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
 | 
			
		||||
        host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs)
 | 
			
		||||
        if host.get('error'):
 | 
			
		||||
            return host
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +124,9 @@ class ChangeSecretManager(BasePlaybookManager):
 | 
			
		|||
        if account:
 | 
			
		||||
            accounts = accounts.exclude(id=account.id)
 | 
			
		||||
        if '*' not in self.automation.accounts:
 | 
			
		||||
            accounts = accounts.filter(username__in=self.automation.accounts)
 | 
			
		||||
            accounts = accounts.filter(
 | 
			
		||||
                username__in=self.automation.accounts, secret_type=self.secret_type
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        method_attr = getattr(automation, self.method_type() + '_method')
 | 
			
		||||
        method_hosts = self.method_hosts_mapper[method_attr]
 | 
			
		||||
| 
						 | 
				
			
			@ -103,11 +134,12 @@ class ChangeSecretManager(BasePlaybookManager):
 | 
			
		|||
        inventory_hosts = []
 | 
			
		||||
        records = []
 | 
			
		||||
 | 
			
		||||
        host['secret_type'] = self.secret_type
 | 
			
		||||
        for account in accounts:
 | 
			
		||||
            h = deepcopy(host)
 | 
			
		||||
            h['name'] += '_' + account.username
 | 
			
		||||
            new_secret = self.get_secret()
 | 
			
		||||
 | 
			
		||||
            new_secret = self.get_secret(account)
 | 
			
		||||
            recorder = ChangeSecretRecord(
 | 
			
		||||
                account=account, execution=self.execution,
 | 
			
		||||
                old_secret=account.secret, new_secret=new_secret,
 | 
			
		||||
| 
						 | 
				
			
			@ -115,11 +147,19 @@ class ChangeSecretManager(BasePlaybookManager):
 | 
			
		|||
            records.append(recorder)
 | 
			
		||||
            self.name_recorder_mapper[h['name']] = recorder
 | 
			
		||||
 | 
			
		||||
            private_key_path = None
 | 
			
		||||
            if self.secret_type == SecretType.ssh_key:
 | 
			
		||||
                private_key_path = self.generate_private_key_path(new_secret, path_dir)
 | 
			
		||||
                new_secret = self.generate_public_key(new_secret)
 | 
			
		||||
 | 
			
		||||
            h['kwargs'] = self.get_kwargs(account, new_secret)
 | 
			
		||||
 | 
			
		||||
            h['account'] = {
 | 
			
		||||
                'name': account.name,
 | 
			
		||||
                'username': account.username,
 | 
			
		||||
                'secret_type': account.secret_type,
 | 
			
		||||
                'secret': new_secret,
 | 
			
		||||
                'private_key_path': private_key_path
 | 
			
		||||
            }
 | 
			
		||||
            inventory_hosts.append(h)
 | 
			
		||||
            method_hosts.append(h['name'])
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -156,7 +156,7 @@ class JMSInventory:
 | 
			
		|||
            account_selected = accounts[0] if accounts else None
 | 
			
		||||
        return account_selected
 | 
			
		||||
 | 
			
		||||
    def generate(self):
 | 
			
		||||
    def generate(self, path_dir):
 | 
			
		||||
        hosts = []
 | 
			
		||||
        platform_assets = self.group_by_platform(self.assets)
 | 
			
		||||
        for platform, assets in platform_assets.items():
 | 
			
		||||
| 
						 | 
				
			
			@ -173,7 +173,8 @@ class JMSInventory:
 | 
			
		|||
                if self.host_callback is not None:
 | 
			
		||||
                    host = self.host_callback(
 | 
			
		||||
                        host, asset=asset, account=account,
 | 
			
		||||
                        platform=platform, automation=automation
 | 
			
		||||
                        platform=platform, automation=automation,
 | 
			
		||||
                        path_dir=path_dir
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                if isinstance(host, list):
 | 
			
		||||
| 
						 | 
				
			
			@ -195,8 +196,8 @@ class JMSInventory:
 | 
			
		|||
        return data
 | 
			
		||||
 | 
			
		||||
    def write_to_file(self, path):
 | 
			
		||||
        data = self.generate()
 | 
			
		||||
        path_dir = os.path.dirname(path)
 | 
			
		||||
        data = self.generate(path_dir)
 | 
			
		||||
        if not os.path.exists(path_dir):
 | 
			
		||||
            os.makedirs(path_dir, 0o700, True)
 | 
			
		||||
        with open(path, 'w') as f:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue