From cab72c69915acd3894811ef13d9e32bda06f6c68 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Wed, 29 Mar 2023 17:10:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Windows=E7=B1=BB=E5=9E=8B=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E5=A2=9E=E5=8A=A0winrm=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/host.py | 4 +- apps/assets/const/protocol.py | 8 +++ .../migrations/0111_auto_20230321_1633.py | 8 +++ apps/assets/serializers/platform.py | 3 + apps/ops/ansible/inventory.py | 56 ++++++++++++++++--- requirements/requirements.txt | 1 + 6 files changed, 69 insertions(+), 11 deletions(-) diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 7b6c8818c..afb92a447 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -36,7 +36,7 @@ class HostTypes(BaseType): 'choices': ['ssh', 'telnet', 'vnc', 'rdp'] }, cls.WINDOWS: { - 'choices': ['rdp', 'ssh', 'vnc'] + 'choices': ['rdp', 'ssh', 'vnc', 'winrm'] } } @@ -58,7 +58,7 @@ class HostTypes(BaseType): cls.WINDOWS: { 'ansible_config': { 'ansible_shell_type': 'cmd', - 'ansible_connection': 'ssh', + 'ansible_connection': 'smart', }, }, cls.OTHER_HOST: { diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index 6523c5dcc..45dbc10ec 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -10,6 +10,7 @@ class Protocol(ChoicesMixin, models.TextChoices): rdp = 'rdp', 'RDP' telnet = 'telnet', 'Telnet' vnc = 'vnc', 'VNC' + winrm = 'winrm', 'WinRM' mysql = 'mysql', 'MySQL' mariadb = 'mariadb', 'MariaDB' @@ -51,6 +52,13 @@ class Protocol(ChoicesMixin, models.TextChoices): 'port': 23, 'secret_types': ['password'], }, + cls.winrm: { + 'port': 5985, + 'secret_types': ['password'], + 'setting': { + 'use_ssl': False, + } + }, } @classmethod diff --git a/apps/assets/migrations/0111_auto_20230321_1633.py b/apps/assets/migrations/0111_auto_20230321_1633.py index 314d2ed7f..fce00cc98 100644 --- a/apps/assets/migrations/0111_auto_20230321_1633.py +++ b/apps/assets/migrations/0111_auto_20230321_1633.py @@ -2,6 +2,8 @@ from django.db import migrations, models +from assets.const import AllTypes + def migrate_platform_charset(apps, schema_editor): platform_model = apps.get_model('assets', 'Platform') @@ -20,6 +22,11 @@ def migrate_platform_protocol_primary(apps, schema_editor): p.save() +def migrate_internal_platforms(apps, schema_editor): + platform_cls = apps.get_model('assets', 'Platform') + AllTypes.create_or_update_internal_platforms(platform_cls) + + class Migration(migrations.Migration): dependencies = [ @@ -34,4 +41,5 @@ class Migration(migrations.Migration): ), migrations.RunPython(migrate_platform_charset), migrations.RunPython(migrate_platform_protocol_primary), + migrations.RunPython(migrate_internal_platforms), ] diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 53be193a6..5c458028d 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -41,6 +41,9 @@ class ProtocolSettingSerializer(serializers.Serializer): # Redis auth_username = serializers.BooleanField(default=False, label=_("Auth with username")) + # WinRM + use_ssl = serializers.BooleanField(default=False, label=_("Use SSL")) + class PlatformAutomationSerializer(serializers.ModelSerializer): class Meta: diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 6abf6d18e..e2b297b94 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -72,15 +72,13 @@ class JMSInventory: var['ansible_ssh_private_key_file'] = account.private_key_path return var - def make_ssh_account_vars(self, host, asset, account, automation, protocols, platform, gateway): + def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway): if not account: host['error'] = _("No account available") return host - ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) - ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None host['ansible_host'] = asset.address - host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 + host['ansible_port'] = protocol.port if protocol else 22 su_from = account.su_from if platform.su_enabled and su_from: @@ -110,6 +108,32 @@ class JMSInventory: port = 0 return protocol, port + @staticmethod + def get_ansible_protocol(ansible_config, protocols): + ansible_connection = ansible_config.get('ansible_connection') + if ansible_connection and not ansible_config == 'smart': + # 数值越小,优先级越高 + protocol_priority = {'ssh': 10, 'winrm': 9, ansible_connection: 1} + + protocol_matched = list(filter(lambda x: x.name in protocol_priority, protocols)) + protocol_sorted = sorted(protocol_matched, key=lambda x: protocol_priority[x.name]) + + protocol = protocol_sorted[0] if protocol_sorted else None + return protocol + + @staticmethod + def fill_ansible_config(ansible_config, protocol): + if protocol and protocol.name == 'winrm': + ansible_config['ansible_connection'] = 'winrm' + if protocol.setting.get('use_ssl', False): + ansible_config['ansible_winrm_scheme'] = 'https' + ansible_config['ansible_winrm_transport'] = 'ssl' + ansible_config['ansible_winrm_server_cert_validation'] = 'ignore' + else: + ansible_config['ansible_winrm_scheme'] = 'http' + ansible_config['ansible_winrm_transport'] = 'plaintext' + return ansible_config + def asset_to_host(self, asset, account, automation, protocols, platform): protocol, port = self.get_primary_protocol(protocols) @@ -133,15 +157,18 @@ class JMSInventory: try: ansible_config = dict(automation.ansible_config) - except Exception as e: + except (AttributeError, TypeError): ansible_config = {} - ansible_connection = ansible_config.get('ansible_connection', 'ssh') + + ansible_protocol = self.get_ansible_protocol(ansible_config, protocols) + ansible_config = self.fill_ansible_config(ansible_config, ansible_protocol) host.update(ansible_config) gateway = None if not asset.is_gateway and asset.domain: gateway = asset.domain.select_gateway() + ansible_connection = ansible_config.get('ansible_connection', 'ssh') if ansible_connection == 'local': if gateway: host['gateway'] = { @@ -149,7 +176,9 @@ class JMSInventory: 'username': gateway.username, 'secret': gateway.password } else: - self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway) + self.make_account_vars( + host, asset, account, automation, ansible_protocol, platform, gateway + ) return host def get_asset_sorted_accounts(self, asset): @@ -194,14 +223,23 @@ class JMSInventory: else: return None + @staticmethod + def _fill_attr_from_platform(asset, platform_protocols): + asset_protocols = asset.protocols.all() + for p in asset_protocols: + setattr(p, 'setting', platform_protocols.get(p.name, {})) + return asset_protocols + def generate(self, path_dir): hosts = [] platform_assets = self.group_by_platform(self.assets) for platform, assets in platform_assets.items(): automation = platform.automation - + platform_protocols = { + p['name']: p['setting'] for p in platform.protocols.values('name', 'setting') + } for asset in assets: - protocols = asset.protocols.all() + protocols = self._fill_attr_from_platform(asset, platform_protocols) account = self.select_account(asset) host = self.asset_to_host(asset, account, automation, protocols, platform) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8ef8712ab..612eb5112 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -65,6 +65,7 @@ pyjwkest==1.4.2 jsonfield2==4.0.0.post0 geoip2==4.5.0 ipip-ipdb==1.6.1 +pywinrm==0.4.3 # Django environment Django==3.2.17 django-bootstrap3==14.2.0