diff --git a/apps/assets/automations/change_secret/database/mysql/main.yml b/apps/assets/automations/change_secret/database/mysql/main.yml index 39560a383..c76b53b08 100644 --- a/apps/assets/automations/change_secret/database/mysql/main.yml +++ b/apps/assets/automations/change_secret/database/mysql/main.yml @@ -27,6 +27,7 @@ password: "{{ account.secret }}" host: "%" when: db_info is succeeded + register: change_info - name: Verify password community.mysql.mysql_info: @@ -35,3 +36,6 @@ login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" filter: version + when: + - db_info is succeeded + - change_info is succeeded \ No newline at end of file diff --git a/apps/assets/automations/change_secret/database/postgresql/main.yml b/apps/assets/automations/change_secret/database/postgresql/main.yml index 816d4c0e2..40c326704 100644 --- a/apps/assets/automations/change_secret/database/postgresql/main.yml +++ b/apps/assets/automations/change_secret/database/postgresql/main.yml @@ -1,8 +1,8 @@ - hosts: postgre gather_facts: no vars: -# ansible_python_interpreter: /usr/local/bin/python - ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + ansible_python_interpreter: /usr/local/bin/python + tasks: - name: Test PostgreSQL connection community.postgresql.postgresql_ping: @@ -37,4 +37,6 @@ login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" db: "{{ jms_asset.database }}" - when: db_info is succeeded and change_info is changed + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/assets/automations/change_secret/host/aix/main.yml b/apps/assets/automations/change_secret/host/aix/main.yml index 41e093df8..1a4e6a6a4 100644 --- a/apps/assets/automations/change_secret/host/aix/main.yml +++ b/apps/assets/automations/change_secret/host/aix/main.yml @@ -1,29 +1,58 @@ - hosts: demo + gather_facts: no tasks: - - name: ping + - name: Test privileged account ansible.builtin.ping: - - #- name: print variables - # debug: - # msg: "Username: {{ account.username }}, Password: {{ account.password }}" + # + # - name: print variables + # debug: + # msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ secret_type }}" - name: Change password - user: + ansible.builtin.user: name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" + password: "{{ account.secret | password_hash('sha512') }}" update_password: always - when: account.password + when: secret_type == "password" - - name: Change public key - authorized_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" + - kwargs.strategy == "set_jms" + + - name: Change SSH key + ansible.builtin.authorized_key: user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key + 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_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko + 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" diff --git a/apps/assets/automations/change_secret/host/linux/main.yml b/apps/assets/automations/change_secret/host/posix/main.yml similarity index 95% rename from apps/assets/automations/change_secret/host/linux/main.yml rename to apps/assets/automations/change_secret/host/posix/main.yml index d295079ba..1a4e6a6a4 100644 --- a/apps/assets/automations/change_secret/host/linux/main.yml +++ b/apps/assets/automations/change_secret/host/posix/main.yml @@ -25,7 +25,9 @@ dest: "{{ kwargs.dest }}" regexp: "{{ kwargs.regexp }}" state: absent - when: secret_type == "ssh_key" and kwargs.strategy == "set_jms" + when: + - secret_type == "ssh_key" + - kwargs.strategy == "set_jms" - name: Change SSH key ansible.builtin.authorized_key: diff --git a/apps/assets/automations/change_secret/host/linux/manifest.yml b/apps/assets/automations/change_secret/host/posix/manifest.yml similarity index 52% rename from apps/assets/automations/change_secret/host/linux/manifest.yml rename to apps/assets/automations/change_secret/host/posix/manifest.yml index afd9b9671..491fb14a2 100644 --- a/apps/assets/automations/change_secret/host/linux/manifest.yml +++ b/apps/assets/automations/change_secret/host/posix/manifest.yml @@ -1,5 +1,5 @@ -id: change_secret_linux -name: Change password for Linux +id: change_secret_posix +name: Change secret for posix category: host type: - unix diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py index 0efbc55ea..11330370a 100644 --- a/apps/assets/automations/endpoint.py +++ b/apps/assets/automations/endpoint.py @@ -1,8 +1,6 @@ -# from .backup.manager import AccountBackupExecutionManager -# -# from .change_secret.manager import ChangeSecretManager from .gather_facts.manager import GatherFactsManager +from .gather_accounts.manager import GatherAccountsManager from ..const import AutomationTypes @@ -10,6 +8,7 @@ class ExecutionManager: manager_type_mapper = { AutomationTypes.change_secret: ChangeSecretManager, AutomationTypes.gather_facts: GatherFactsManager, + AutomationTypes.gather_accounts: GatherAccountsManager, } def __init__(self, execution): diff --git a/apps/assets/automations/gather_accounts/__init__.py b/apps/assets/automations/gather_accounts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/automations/gather_accounts/database/mysql/main.yml b/apps/assets/automations/gather_accounts/database/mysql/main.yml new file mode 100644 index 000000000..cc934f20f --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/mysql/main.yml @@ -0,0 +1,21 @@ +- hosts: mysql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + community.mysql.mysql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: users + register: db_info + + - name: Define info by set_fact + set_fact: + info: "{{ db_info.users }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/database/mysql/manifest.yml b/apps/assets/automations/gather_accounts/database/mysql/manifest.yml new file mode 100644 index 000000000..e69cca67b --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/mysql/manifest.yml @@ -0,0 +1,6 @@ +id: gather_accounts_mysql +name: Gather account from MySQL +category: database +type: + - mysql +method: gather_accounts diff --git a/apps/assets/automations/gather_accounts/database/postgresql/main.yml b/apps/assets/automations/gather_accounts/database/postgresql/main.yml new file mode 100644 index 000000000..2e12f51e5 --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/postgresql/main.yml @@ -0,0 +1,22 @@ +- hosts: postgresql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + community.postgresql.postgresql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_db: "{{ jms_asset.database }}" + filter: "roles" + register: db_info + + - name: Define info by set_fact + set_fact: + info: "{{ db_info.roles }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/database/postgresql/manifest.yml b/apps/assets/automations/gather_accounts/database/postgresql/manifest.yml new file mode 100644 index 000000000..3e563053a --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: gather_accounts_postgresql +name: Gather account for PostgreSQL +category: database +type: + - postgresql +method: gather_accounts diff --git a/apps/assets/automations/gather_accounts/filter.py b/apps/assets/automations/gather_accounts/filter.py new file mode 100644 index 000000000..0c8f32536 --- /dev/null +++ b/apps/assets/automations/gather_accounts/filter.py @@ -0,0 +1,56 @@ +from django.utils import timezone + +__all__ = ['GatherAccountsFilter'] + + +# TODO 后期会挪到playbook中 +class GatherAccountsFilter: + + def __init__(self, tp): + self.tp = tp + + @staticmethod + def mysql_filter(info): + result = {} + for _, user_dict in info.items(): + for username, data in user_dict.items(): + if data.get('account_locked') == 'N': + result[username] = {} + return result + + @staticmethod + def postgresql_filter(info): + result = {} + for username in info: + result[username] = {} + return result + + @staticmethod + def posix_filter(info): + result = {} + for line in info: + data = line.split('@') + if len(data) != 3: + continue + username, address, dt = data + date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z') + result[username] = {'address': address, 'date': date} + return result + + @staticmethod + def windows_filter(info): + # TODO + result = {} + return result + + def run(self, method_id_meta_mapper, info): + run_method_name = None + for k, v in method_id_meta_mapper.items(): + if self.tp not in v['type']: + continue + run_method_name = k.replace(f'{v["method"]}_', '') + + if not run_method_name: + return info + + return getattr(self, f'{run_method_name}_filter')(info) diff --git a/apps/assets/automations/gather_accounts/host/posix/main.yml b/apps/assets/automations/gather_accounts/host/posix/main.yml new file mode 100644 index 000000000..97326431d --- /dev/null +++ b/apps/assets/automations/gather_accounts/host/posix/main.yml @@ -0,0 +1,14 @@ +- hosts: demo + gather_facts: no + tasks: + - 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 \ No newline at end of file diff --git a/apps/assets/automations/gather_accounts/host/posix/manifest.yml b/apps/assets/automations/gather_accounts/host/posix/manifest.yml new file mode 100644 index 000000000..a761c9796 --- /dev/null +++ b/apps/assets/automations/gather_accounts/host/posix/manifest.yml @@ -0,0 +1,7 @@ +id: gather_accounts_posix +name: Gather posix account +category: host +type: + - linux + - unix +method: gather_accounts diff --git a/apps/assets/automations/gather_accounts/host/windows/main.yml b/apps/assets/automations/gather_accounts/host/windows/main.yml new file mode 100644 index 000000000..377ffd10a --- /dev/null +++ b/apps/assets/automations/gather_accounts/host/windows/main.yml @@ -0,0 +1,18 @@ +- hosts: windows + gather_facts: yes + 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 }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/host/windows/manifest.yml b/apps/assets/automations/gather_accounts/host/windows/manifest.yml new file mode 100644 index 000000000..ffc2ef7ee --- /dev/null +++ b/apps/assets/automations/gather_accounts/host/windows/manifest.yml @@ -0,0 +1,7 @@ +id: gather_accounts_windows +name: Gather account windows +version: 1 +method: gather_accounts +category: host +type: + - windows diff --git a/apps/assets/automations/gather_accounts/manager.py b/apps/assets/automations/gather_accounts/manager.py new file mode 100644 index 000000000..d6881f96c --- /dev/null +++ b/apps/assets/automations/gather_accounts/manager.py @@ -0,0 +1,45 @@ +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 ..base.manager import BasePlaybookManager + +logger = get_logger(__name__) + + +class GatherAccountsManager(BasePlaybookManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.host_asset_mapper = {} + + @classmethod + def method_type(cls): + return AutomationTypes.gather_accounts + + def host_callback(self, host, asset=None, **kwargs): + super().host_callback(host, asset=asset, **kwargs) + self.host_asset_mapper[host['name']] = asset + return host + + def filter_success_result(self, host, result): + result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result) + return result + + def on_host_success(self, host, result): + info = result.get('debug', {}).get('res', {}).get('info', {}) + asset = self.host_asset_mapper.get(host) + org_id = asset.org_id + if asset and info: + result = self.filter_success_result(host, info) + with tmp_to_org(org_id): + GatheredUser.objects.filter(asset=asset, present=True).update(present=False) + for username, data in result.items(): + defaults = {'asset': asset, 'present': True, 'username': username} + if data.get('date'): + defaults['date_last_login'] = data['date'] + if data.get('address'): + 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)) diff --git a/apps/assets/automations/gather_facts/database/mysql/main.yml b/apps/assets/automations/gather_facts/database/mysql/main.yml index c4e90835e..8ba210283 100644 --- a/apps/assets/automations/gather_facts/database/mysql/main.yml +++ b/apps/assets/automations/gather_facts/database/mysql/main.yml @@ -13,7 +13,7 @@ filter: version register: db_info - - name: Define Mysql info by set_fact + - name: Define info by set_fact set_fact: info: version: "{{ db_info.version.full }}" diff --git a/apps/assets/automations/gather_facts/database/postgresql/main.yml b/apps/assets/automations/gather_facts/database/postgresql/main.yml index 55731a4fa..82adcdc16 100644 --- a/apps/assets/automations/gather_facts/database/postgresql/main.yml +++ b/apps/assets/automations/gather_facts/database/postgresql/main.yml @@ -1,4 +1,4 @@ -- hosts: postgre +- hosts: postgresql gather_facts: no vars: ansible_python_interpreter: /usr/local/bin/python @@ -13,7 +13,7 @@ login_db: "{{ jms_asset.database }}" register: db_info - - name: Define Postgresql info by set_fact + - name: Define info by set_fact set_fact: info: version: "{{ db_info.server_version.raw }}" diff --git a/apps/assets/automations/gather_facts/host/posix/main.yml b/apps/assets/automations/gather_facts/host/posix/main.yml index f42635458..81aef9aac 100644 --- a/apps/assets/automations/gather_facts/host/posix/main.yml +++ b/apps/assets/automations/gather_facts/host/posix/main.yml @@ -1,4 +1,4 @@ -- hosts: website +- hosts: demo gather_facts: yes tasks: - name: Get info diff --git a/apps/assets/automations/ping/manager.py b/apps/assets/automations/ping/manager.py index 36017438b..84712fb44 100644 --- a/apps/assets/automations/ping/manager.py +++ b/apps/assets/automations/ping/manager.py @@ -1,75 +1,9 @@ -import os -import shutil -from copy import deepcopy -from collections import defaultdict - -import yaml -from django.utils.translation import gettext as _ - -from ops.ansible import PlaybookRunner +from common.utils import get_logger +from assets.const import AutomationTypes from ..base.manager import BasePlaybookManager -from assets.automations.methods import platform_automation_methods - - -class ChangePasswordManager(BasePlaybookManager): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.id_method_mapper = { - method['id']: method - for method in platform_automation_methods - } - self.method_hosts_mapper = defaultdict(list) - self.playbooks = [] - - def inventory_kwargs(self): - return { - 'host_callback': self.host_duplicator - } - - def generate_playbook(self): - playbook = [] - for method_id, host_names in self.method_hosts_mapper.items(): - method = self.id_method_mapper[method_id] - method_playbook_dir_path = method['dir'] - method_playbook_dir_name = os.path.basename(method_playbook_dir_path) - sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) - shutil.copytree(method_playbook_dir_path, sub_playbook_dir) - sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') - - with open(sub_playbook_path, 'r') as f: - host_playbook_play = yaml.safe_load(f) - - if isinstance(host_playbook_play, list): - host_playbook_play = host_playbook_play[0] - - step = 10 - hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)] - for i, hosts in enumerate(hosts_grouped): - plays = [] - play = deepcopy(host_playbook_play) - play['hosts'] = ':'.join(hosts) - plays.append(play) - - 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) - - playbook.append({ - 'name': method['name'] + ' for part {}'.format(i), - 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) - }) - - with open(self.playbook_path, 'w') as f: - yaml.safe_dump(playbook, f) - - print("Generate playbook done: " + self.playbook_path) - - def get_runner(self): - return PlaybookRunner( - self.inventory_path, - self.playbook_path, - self.runtime_dir - ) + +logger = get_logger(__name__) +class PingManager(BasePlaybookManager): + pass diff --git a/apps/assets/const/automation.py b/apps/assets/const/automation.py index 54b3cc019..6b3b6dbd4 100644 --- a/apps/assets/const/automation.py +++ b/apps/assets/const/automation.py @@ -15,7 +15,7 @@ class AutomationTypes(TextChoices): push_account = 'push_account', _('Create account') change_secret = 'change_secret', _('Change secret') verify_account = 'verify_account', _('Verify account') - gather_account = 'gather_account', _('Gather account') + gather_accounts = 'gather_accounts', _('Gather accounts') class SecretStrategy(TextChoices): diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index b77872ad0..f9cf83b85 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -166,6 +166,7 @@ class AllTypes(ChoicesMixin): data['protocols'] = protocols automation = constraints.get('automation', {}) + enable_fields = {k: v for k, v in automation.items() if k.endswith('_enabled')} for k, v in enable_fields.items(): auto_item = k.replace('_enabled', '') diff --git a/apps/assets/migrations/0108_auto_20221027_1053.py b/apps/assets/migrations/0108_auto_20221027_1053.py new file mode 100644 index 000000000..c59dcf7e7 --- /dev/null +++ b/apps/assets/migrations/0108_auto_20221027_1053.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.14 on 2022-10-27 02:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0107_auto_20221019_1115'), + ] + + operations = [ + migrations.CreateModel( + name='GatherAccountsAutomation', + 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')), + ], + options={ + 'verbose_name': 'Gather asset accounts', + }, + bases=('assets.baseautomation',), + ), + migrations.AlterField( + model_name='baseautomation', + name='type', + field=models.CharField(choices=[('ping', 'Ping'), ('gather_facts', 'Gather facts'), ('push_account', 'Create account'), ('change_secret', 'Change secret'), ('verify_account', 'Verify account'), ('gather_accounts', 'Gather accounts')], max_length=16, verbose_name='Type'), + ), + ] diff --git a/apps/assets/models/automations/__init__.py b/apps/assets/models/automations/__init__.py index 77a885b1b..1c62bbddd 100644 --- a/apps/assets/models/automations/__init__.py +++ b/apps/assets/models/automations/__init__.py @@ -3,3 +3,4 @@ from .discovery_account import * from .push_account import * from .verify_secret import * from .gather_facts import * +from .gather_accounts import * diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 1f9e1ac04..aabfd241f 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -10,6 +10,7 @@ from orgs.mixins.models import OrgModelMixin from ops.mixin import PeriodTaskModelMixin from assets.models import Node, Asset from assets.tasks import execute_automation +from assets.const import AutomationTypes class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): @@ -20,7 +21,7 @@ class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): assets = models.ManyToManyField( 'assets.Asset', blank=True, verbose_name=_("Assets") ) - type = models.CharField(max_length=16, verbose_name=_('Type')) + type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type')) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) comment = models.TextField(blank=True, verbose_name=_('Comment')) diff --git a/apps/assets/models/automations/gather_accounts.py b/apps/assets/models/automations/gather_accounts.py new file mode 100644 index 000000000..861031af4 --- /dev/null +++ b/apps/assets/models/automations/gather_accounts.py @@ -0,0 +1,15 @@ +from django.utils.translation import ugettext_lazy as _ + +from assets.const import AutomationTypes +from .base import BaseAutomation + +__all__ = ['GatherAccountsAutomation'] + + +class GatherAccountsAutomation(BaseAutomation): + def save(self, *args, **kwargs): + self.type = AutomationTypes.gather_accounts + super().save(*args, **kwargs) + + class Meta: + verbose_name = _("Gather asset accounts") diff --git a/apps/authentication/migrations/0013_connectiontoken_protocol.py b/apps/authentication/migrations/0013_connectiontoken_protocol.py new file mode 100644 index 000000000..f6e310e24 --- /dev/null +++ b/apps/authentication/migrations/0013_connectiontoken_protocol.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-10-27 12:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0012_auto_20220816_1629'), + ] + + operations = [ + migrations.AddField( + model_name='connectiontoken', + name='protocol', + field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S'), ('http', 'HTTP'), ('None', ' Settings')], default='ssh', max_length=16, verbose_name='Protocol'), + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index c76d6e0f4..9476d348c 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -9,6 +9,7 @@ from django.db import models from common.utils import lazyproperty from common.utils.timezone import as_current_tz from common.db.models import JMSBaseModel +from assets.const import Protocol def date_expired_default(): @@ -26,10 +27,14 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): ) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) - protocol = '' account = models.CharField(max_length=128, default='', verbose_name=_("Account")) + protocol = models.CharField( + choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") + ) secret = models.CharField(max_length=64, default='', verbose_name=_("Secret")) - date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_("Date expired")) + date_expired = models.DateTimeField( + default=date_expired_default, verbose_name=_("Date expired") + ) class Meta: ordering = ('-date_expired',) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 1093da1f9..86388155b 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -17,7 +17,6 @@ __all__ = [ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) is_valid = serializers.BooleanField(read_only=True, label=_('Validity')) expire_time = serializers.IntegerField(read_only=True, label=_('Expired time')) @@ -29,13 +28,13 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): 'created_by', 'updated_by', 'org_id', 'org_name', ] fields_fk = [ - 'user', 'system_user', 'asset', 'application', + 'user', 'system_user', 'asset', ] read_only_fields = [ # 普通 Token 不支持指定 user 'user', 'is_valid', 'expire_time', - 'type_display', 'user_display', 'system_user_display', - 'asset_display', 'application_display', + 'user_display', 'system_user_display', + 'asset_display', ] fields = fields_small + fields_fk + read_only_fields @@ -54,28 +53,23 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): return self.request_user def construct_internal_fields_attrs(self, attrs): - user = self.get_user(attrs) - system_user = attrs.get('system_user') or '' asset = attrs.get('asset') or '' - application = attrs.get('application') or '' + asset_display = pretty_string(str(asset), max_length=128) + user = self.get_user(attrs) + user_display = pretty_string(str(user), max_length=128) secret = attrs.get('secret') or random_string(16) date_expired = attrs.get('date_expired') or ConnectionToken.get_default_date_expired() - - if isinstance(asset, Asset): - tp = ConnectionToken.Type.asset - org_id = asset.org_id - else: - raise serializers.ValidationError(_('Asset or application required')) + org_id = asset.org_id + if not isinstance(asset, Asset): + error = '' + raise serializers.ValidationError(error) return { - 'type': tp, 'user': user, 'secret': secret, + 'user_display': user_display, + 'asset_display': asset_display, 'date_expired': date_expired, - 'user_display': pretty_string(str(user), max_length=128), - 'system_user_display': pretty_string(str(system_user), max_length=128), - 'asset_display': pretty_string(str(asset), max_length=128), - 'application_display': pretty_string(str(application), max_length=128), 'org_id': org_id, } @@ -155,7 +149,6 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) - account = serializers.CharField(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) domain = ConnectionTokenDomainSerializer(read_only=True) cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) @@ -165,6 +158,6 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken fields = [ - 'id', 'secret', 'type', 'user', 'asset', 'account', + 'id', 'secret', 'type', 'user', 'asset', 'account', 'protocol', 'cmd_filter_rules', 'domain', 'gateway', 'actions', 'expired_at', ] diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index c544cfcd6..35344ad6a 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -106,7 +106,7 @@ class JMSInventory: 'jms_asset': { 'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'type': asset.type, 'category': asset.category, - 'protocol': asset.protocol, 'port': asset.port, + 'protocol': asset.protocol, 'port': asset.port,'database': '', 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], }, 'jms_account': { @@ -117,6 +117,10 @@ class JMSInventory: ansible_config = dict(automation.ansible_config) ansible_connection = ansible_config.get('ansible_connection', 'ssh') host.update(ansible_config) + + if platform.category == 'database': + host['jms_asset']['database'] = asset.database.db_name + gateway = None if asset.domain: gateway = asset.domain.select_gateway() diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index 61d13db17..85a5c00d2 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -99,7 +99,7 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): - queryset = CeleryTask.objects.all() + queryset = CeleryTask.objects.filter(name__in=['ops.tasks.hello', 'ops.tasks.hello_error', 'ops.tasks.hello_random']) serializer_class = CeleryTaskSerializer http_method_names = ('get', 'head', 'options',) diff --git a/apps/ops/migrations/0028_celerytask_last_published_time.py b/apps/ops/migrations/0028_celerytask_last_published_time.py new file mode 100644 index 000000000..6732508e3 --- /dev/null +++ b/apps/ops/migrations/0028_celerytask_last_published_time.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-10-27 06:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0027_auto_20221024_1709'), + ] + + operations = [ + migrations.AddField( + model_name='celerytask', + name='last_published_time', + field=models.DateTimeField(null=True), + ), + ] diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 6ea4e2641..2cc989fc3 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -13,18 +13,28 @@ from ops.celery import app class CeleryTask(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4) name = models.CharField(max_length=1024) + last_published_time = models.DateTimeField(null=True) @property - def verbose_name(self): + def meta(self): task = app.tasks.get(self.name, None) - if task: - return getattr(task, 'verbose_name', None) - + return { + "verbose_name": getattr(task, 'verbose_name', None), + "comment": getattr(task, 'comment', None), + "queue": getattr(task, 'queue', 'default') + } @property - def description(self): - task = app.tasks.get(self.name, None) - if task: - return getattr(task, 'description', None) + def state(self): + last_five_executions = CeleryTaskExecution.objects.filter(name=self.name).order_by('-date_published')[:5] + + if len(last_five_executions) > 0: + if last_five_executions[0].state == 'FAILURE': + return "red" + + for execution in last_five_executions: + if execution.state == 'FAILURE': + return "yellow" + return "green" class CeleryTaskExecution(models.Model): diff --git a/apps/ops/serializers/celery.py b/apps/ops/serializers/celery.py index b2aa6eb7a..3fd72fde3 100644 --- a/apps/ops/serializers/celery.py +++ b/apps/ops/serializers/celery.py @@ -31,7 +31,7 @@ class CeleryTaskSerializer(serializers.ModelSerializer): class Meta: model = CeleryTask fields = [ - 'id', 'name', 'verbose_name', 'description', + 'id', 'name', 'meta', 'publish_count', 'state', 'success_count', 'last_published_time', ] diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index a444558bc..b713ccbf4 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -92,3 +92,4 @@ def task_sent_handler(headers=None, body=None, **kwargs): 'kwargs': kwargs } CeleryTaskExecution.objects.create(**data) + CeleryTask.objects.filter(name=task).update(last_published_time=timezone.now()) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index dc3ac6e68..c749cb66c 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -1,5 +1,6 @@ # coding: utf-8 import os +import random import subprocess from django.conf import settings @@ -76,7 +77,7 @@ def run_playbook(pid, **kwargs): @shared_task @after_app_shutdown_clean_periodic -@register_as_period_task(interval=3600*24, description=_("Clean task history period")) +@register_as_period_task(interval=3600 * 24, description=_("Clean task history period")) def clean_tasks_adhoc_period(): logger.debug("Start clean task adhoc and run history") tasks = Task.objects.all() @@ -89,7 +90,7 @@ def clean_tasks_adhoc_period(): @shared_task @after_app_shutdown_clean_periodic -@register_as_period_task(interval=3600*24, description=_("Clean celery log period")) +@register_as_period_task(interval=3600 * 24, description=_("Clean celery log period")) def clean_celery_tasks_period(): logger.debug("Start clean celery task history") expire_days = get_log_keep_day('TASK_LOG_KEEP_DAYS') @@ -143,7 +144,7 @@ def check_server_performance_period(): ServerPerformanceCheckUtil().check_and_publish() -@shared_task(queue="ansible", verbose_name=_("Hello")) +@shared_task(queue="ansible", verbose_name=_("Hello"), comment="an test shared task") def hello(name, callback=None): from users.models import User import time @@ -155,6 +156,18 @@ def hello(name, callback=None): return gettext("Hello") +@shared_task(verbose_name="Hello Error", comment="an test shared task error") +def hello_error(): + raise Exception("must be error") + + +@shared_task(verbose_name="Hello Random", comment="some time error and some time success") +def hello_random(): + i = random.randint(0, 1) + if i == 1: + raise Exception("must be error") + + @shared_task def hello_callback(result): print(result) @@ -171,5 +184,3 @@ def execute_automation_strategy(pid, trigger): return with tmp_to_org(instance.org): instance.execute(trigger) - - diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 34b839fab..63bfcc723 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -51,7 +51,6 @@ class PermAccountUtil(AssetPermissionUtil): user, asset, with_actions=True, with_perms=True ) perm = perms.first() - # Todo: 后面可能需要加上 protocol 进行过滤, 因为同名的账号协议是不一样可能会存在多个 account = accounts.filter(username=account_username).first() actions = account.actions if account else [] expire_at = perm.date_expired if perm else time.time() diff --git a/apps/rbac/const.py b/apps/rbac/const.py index 71c8ea26d..541b0b3da 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -57,7 +57,6 @@ exclude_permissions = ( ('rbac', 'role', '*', '*'), ('ops', 'adhoc', 'delete,change', '*'), ('ops', 'adhocexecution', 'add,delete,change', '*'), - ('ops', 'celerytask', '*', '*'), ('ops', 'task', 'add,change', 'task'), ('ops', 'commandexecution', 'delete,change', 'commandexecution'), ('orgs', 'organizationmember', '*', '*'),