merge: with remote

pull/9008/head
ibuler 2022-10-28 19:12:37 +08:00
commit 16e3604fcb
43 changed files with 334 additions and 151 deletions

View File

@ -1,19 +1,68 @@
import os
import shutil
import yaml
import shutil
from hashlib import md5
from copy import deepcopy
from socket import gethostname
from collections import defaultdict
from django.conf import settings
from django.utils import timezone
from django.db.models import Model
from django.utils.translation import gettext as _
from common.utils import get_logger
from common.utils import ssh_pubkey_gen, ssh_key_string_to_obj
from assets.const import SecretType
from assets.automations.methods import platform_automation_methods
from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback
logger = get_logger(__name__)
class PushOrVerifyHostCallbackMixin:
execution: Model()
host_account_mapper: dict
ignore_account: bool
generate_public_key: callable
generate_private_key_path: callable
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
accounts = asset.accounts.all()
if self.ignore_account and account:
accounts = accounts.exclude(id=account.id)
if '*' not in self.execution.snapshot['accounts']:
accounts = accounts.filter(username__in=self.execution.snapshot['accounts'])
inventory_hosts = []
for account in accounts:
h = deepcopy(host)
h['name'] += '_' + account.username
self.host_account_mapper[h['name']] = account
secret = account.secret
private_key_path = None
if account.secret_type == SecretType.ssh_key:
private_key_path = self.generate_private_key_path(secret, path_dir)
secret = self.generate_public_key(secret)
h['secret_type'] = account.secret_type
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': account.secret_type,
'secret': secret,
'private_key_path': private_key_path
}
inventory_hosts.append(h)
return inventory_hosts
class PlaybookCallback(DefaultCallback):
def playbook_on_stats(self, event_data, **kwargs):
super().playbook_on_stats(event_data, **kwargs)
@ -66,20 +115,33 @@ class BasePlaybookManager:
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
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'] = _('{} disabled'.format(self.__class__.method_type()))
return host
return host
@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_inventory(self, platformed_assets, inventory_path):
inventory = JMSInventory(
manager=self,
assets=platformed_assets,
account_policy=self.ansible_account_policy,
host_callback=self.host_callback,
)
inventory.write_to_file(inventory_path)
@ -105,7 +167,7 @@ class BasePlaybookManager:
def get_runners(self):
runners = []
for platform, assets in self.get_assets_group_by_platform().items():
assets_bulked = [assets[i:i+self.bulk_size] for i in range(0, len(assets), self.bulk_size)]
assets_bulked = [assets[i:i + self.bulk_size] for i in range(0, len(assets), self.bulk_size)]
for i, _assets in enumerate(assets_bulked, start=1):
sub_dir = '{}_{}'.format(platform.name, i)
@ -148,7 +210,7 @@ class BasePlaybookManager:
print(" inventory: {}".format(runner.inventory))
print(" playbook: {}".format(runner.playbook))
def run(self, *args, **kwargs):
def run(self, *args, **kwargs):
runners = self.get_runners()
if len(runners) > 1:
print("### 分批次执行开始任务, 总共 {}\n".format(len(runners)))

View File

@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.database }}"
login_db: "{{ jms_asset.category_property.db_name }}"
register: db_info
- name: Display PostgreSQL version
@ -24,7 +24,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.database }}"
db: "{{ jms_asset.category_property.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
when: db_info is succeeded
@ -36,7 +36,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.database }}"
db: "{{ jms_asset.category_property.db_name }}"
when:
- db_info is succeeded
- change_info is succeeded

View File

@ -1,58 +0,0 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
ansible.builtin.ping:
#
# - 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: secret_type == "password"
- 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"
- kwargs.strategy == "set_jms"
- name: Change SSH key
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ kwargs.exclusive }}"
when: secret_type == "ssh_key"
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password
ansible.builtin.ping:
become: no
vars:
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"

View File

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

View File

@ -1,14 +1,11 @@
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, ssh_pubkey_gen, ssh_key_string_to_obj
from common.utils import lazyproperty, gen_key_pair
from assets.models import ChangeSecretRecord
from assets.const import (
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES
@ -39,19 +36,6 @@ 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.execution.snapshot['password_rules'] or {}
length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length']))

View File

@ -1,6 +1,8 @@
from .change_secret.manager import ChangeSecretManager
from .gather_facts.manager import GatherFactsManager
from .gather_accounts.manager import GatherAccountsManager
from .verify_account.manager import VerifyAccountManager
from .push_account.manager import PushAccountManager
from ..const import AutomationTypes
@ -9,6 +11,8 @@ class ExecutionManager:
AutomationTypes.change_secret: ChangeSecretManager,
AutomationTypes.gather_facts: GatherFactsManager,
AutomationTypes.gather_accounts: GatherAccountsManager,
AutomationTypes.verify_account: VerifyAccountManager,
AutomationTypes.push_account: PushAccountManager,
}
def __init__(self, execution):

View File

@ -1,7 +1,7 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python
tasks:
- name: Get info
@ -9,7 +9,7 @@
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_port: 1234
filter: users
register: db_info

View File

@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.database }}"
login_db: "{{ jms_asset.category_property.db_name }}"
filter: "roles"
register: db_info

View File

@ -2,8 +2,10 @@
gather_facts: no
tasks:
- name: Gather posix account
ansible.builtin.win_shell:
cmd: net user
ansible.builtin.shell:
cmd: >
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
do last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $1"@"$3"@"$5,$6,$7,$8 }';done
register: result
- name: Define info by set_fact

View File

@ -1,18 +1,14 @@
- hosts: windows
gather_facts: yes
- hosts: demo
gather_facts: no
tasks:
- 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 }}"
- name: Gather posix account
ansible.builtin.win_shell:
cmd: net user
register: result
- name: Define info by set_fact
ansible.builtin.set_fact:
info: "{{ result.stdout_lines }}"
- debug:
var: info
var: info

View File

@ -2,7 +2,7 @@ from common.utils import get_logger
from assets.const import AutomationTypes
from orgs.utils import tmp_to_org
from .filter import GatherAccountsFilter
from ...models import Account, GatheredUser
from ...models import GatheredUser
from ..base.manager import BasePlaybookManager
logger = get_logger(__name__)
@ -42,4 +42,4 @@ class GatherAccountsManager(BasePlaybookManager):
defaults['ip_last_login'] = data['address'][:32]
GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username)
else:
logger.error("Not found info, task name must be 'Get info': {}".format(host))
logger.error("Not found info".format(host))

View File

@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.database }}"
login_db: "{{ jms_asset.category_property.db_name }}"
register: db_info
- name: Define info by set_fact

View File

@ -26,4 +26,4 @@ class GatherFactsManager(BasePlaybookManager):
asset.info = info
asset.save()
else:
logger.error("Not found info, task name must be 'Get info': {}".format(host))
logger.error("Not found info: {}".format(host))

View File

@ -2,12 +2,6 @@
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
@ -17,4 +11,3 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
filter: version
register: db_info

View File

@ -2,16 +2,6 @@
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
@ -20,4 +10,4 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.database }}"
login_db: "{{ jms_asset.category_property.db_name }}"

View File

@ -2,4 +2,4 @@
gather_facts: no
tasks:
- name: Windows ping
win_ping:
ansible.builtin.win_ping:

View File

@ -6,4 +6,15 @@ logger = get_logger(__name__)
class PingManager(BasePlaybookManager):
pass
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.host_asset_mapper = {}
@classmethod
def method_type(cls):
return AutomationTypes.ping
def host_callback(self, host, asset=None, **kwargs):
super().host_callback(host, asset=asset, **kwargs)
self.host_asset_mapper[host['name']] = asset
return host

View File

@ -0,0 +1,15 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Add user account.username
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: "%"

View File

@ -0,0 +1,6 @@
id: push_account_mysql
name: Push account from MySQL
category: database
type:
- mysql
method: push_account

View File

@ -0,0 +1,16 @@
- hosts: postgresql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Add user account.username
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.category_property.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"

View File

@ -0,0 +1,6 @@
id: push_account_postgresql
name: Push account for PostgreSQL
category: database
type:
- postgresql
method: push_account

View File

@ -0,0 +1,19 @@
- hosts: demo
gather_facts: no
tasks:
- name: Add user account.username
ansible.builtin.user:
name: "{{ account.username }}"
- name: Set account.username password
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
when: secret_type == "password"
- name: Set account.username SSH key
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
when: secret_type == "ssh_key"

View File

@ -0,0 +1,7 @@
id: push_account_posix
name: Push posix account
category: host
type:
- linux
- unix
method: push_account

View File

@ -0,0 +1,13 @@
- hosts: windows
gather_facts: yes
tasks:
- name: Add user account.username
ansible.windows.win_user:
vars:
fullname: "{{ account.username }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
state: present
password_expired: no
update_password: always
password_never_expires: yes

View File

@ -0,0 +1,7 @@
id: push_account_windows
name: Push account windows
version: 1
method: push_account
category: host
type:
- windows

View File

@ -0,0 +1,17 @@
from common.utils import get_logger
from assets.const import AutomationTypes
from ..base.manager import BasePlaybookManager, PushOrVerifyHostCallbackMixin
logger = get_logger(__name__)
class PushAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
ignore_account = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.host_account_mapper = {}
@classmethod
def method_type(cls):
return AutomationTypes.push_account

View File

@ -0,0 +1,13 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Verify account
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

@ -0,0 +1,6 @@
id: verify_account_mysql
name: Verify account from MySQL
category: database
type:
- mysql
method: verify_account

View File

@ -0,0 +1,13 @@
- hosts: postgresql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
tasks:
- name: Verify account
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.category_property.db_name }}"

View File

@ -0,0 +1,6 @@
id: verify_account_postgresql
name: Verify account for PostgreSQL
category: database
type:
- postgresql
method: verify_account

View File

@ -0,0 +1,11 @@
- hosts: demo
gather_facts: no
tasks:
- name: Verify account
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no

View File

@ -0,0 +1,7 @@
id: verify_account_posix
name: Verify posix account
category: host
type:
- linux
- unix
method: verify_account

View File

@ -0,0 +1,8 @@
- hosts: windows
gather_facts: yes
tasks:
- name: Verify account
ansible.windows.win_ping:
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"

View File

@ -0,0 +1,7 @@
id: verify_account_windows
name: Verify account windows
version: 1
method: verify_account
category: host
type:
- windows

View File

@ -0,0 +1,25 @@
from common.utils import get_logger
from assets.const import AutomationTypes, Connectivity
from ..base.manager import BasePlaybookManager, PushOrVerifyHostCallbackMixin
logger = get_logger(__name__)
class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
ignore_account = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.host_account_mapper = {}
@classmethod
def method_type(cls):
return AutomationTypes.verify_account
def on_host_success(self, host, result):
account = self.host_account_mapper.get(host)
account.set_connectivity(Connectivity.ok)
def on_host_error(self, host, error, result):
account = self.host_account_mapper.get(host)
account.set_connectivity(Connectivity.failed)

View File

@ -1,6 +1,6 @@
from .change_secret import *
from .discovery_account import *
from .push_account import *
from .verify_secret import *
from .gather_facts import *
from .gather_accounts import *
from .verify_account import *

View File

@ -2,7 +2,6 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.db import fields
from common.const.choices import Trigger
from common.db.models import JMSBaseModel
from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
from .base import BaseAutomation

View File

@ -1,15 +1,16 @@
from django.utils.translation import ugettext_lazy as _
from assets.const import AutomationTypes
from .base import BaseAutomation
__all__ = ['PushAccountAutomation']
class PushAccountAutomation(BaseAutomation):
class Meta:
verbose_name = _("Push automation")
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'type': 'push_account'
})
return attr_json
def save(self, *args, **kwargs):
self.type = AutomationTypes.verify_account
super().save(*args, **kwargs)
class Meta:
verbose_name = _("Push asset account")

View File

@ -1,12 +1,15 @@
from django.utils.translation import ugettext_lazy as _
from assets.const import AutomationTypes
from .base import BaseAutomation
__all__ = ['VerifyAccountAutomation']
class VerifyAccountAutomation(BaseAutomation):
class Meta:
verbose_name = _("Verify account automation")
def save(self, *args, **kwargs):
self.type = 'verify_account'
self.type = AutomationTypes.verify_account
super().save(*args, **kwargs)
class Meta:
verbose_name = _("Verify asset account")

View File

@ -49,7 +49,7 @@ class DefaultCallback:
}
self.result['ok'][host][task] = detail
def runer_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
detail = {
'action': event_data.get('task_action', ''),
'res': res,

View File

@ -163,9 +163,9 @@ class JMSInventory:
platform_assets = self.group_by_platform(self.assets)
for platform, assets in platform_assets.items():
automation = platform.automation
protocols = platform.protocols.all()
for asset in assets:
protocols = asset.protocols.all()
account = self.select_account(asset)
host = self.asset_to_host(asset, account, automation, protocols, platform)