1
0
mirror of https://github.com/jumpserver/jumpserver.git synced 2025-05-03 21:56:46 +00:00

Merge pull request from O-Jiangweidong/pr@dev@feat_windows_winrm

feat: Windows类型资产增加winrm协议
This commit is contained in:
老广 2023-04-04 14:08:34 +08:00 committed by GitHub
commit 9af2974bad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 36 deletions
apps
assets
ops/ansible
requirements

View File

@ -1,11 +1,12 @@
import json import json
import os import os
import shutil import shutil
import yaml
from collections import defaultdict from collections import defaultdict
from hashlib import md5 from hashlib import md5
from socket import gethostname from socket import gethostname
import yaml
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -239,10 +240,12 @@ class BasePlaybookManager:
jms_asset, jms_gateway = host['jms_asset'], host.get('gateway') jms_asset, jms_gateway = host['jms_asset'], host.get('gateway')
if not jms_gateway: if not jms_gateway:
continue continue
server = SSHTunnelForwarder( server = SSHTunnelForwarder(
(jms_gateway['address'], jms_gateway['port']), (jms_gateway['address'], jms_gateway['port']),
ssh_username=jms_gateway['username'], ssh_username=jms_gateway['username'],
ssh_password=jms_gateway['secret'], ssh_password=jms_gateway['secret'],
ssh_pkey=jms_gateway['private_key_path'],
remote_bind_address=(jms_asset['address'], jms_asset['port']) remote_bind_address=(jms_asset['address'], jms_asset['port'])
) )
try: try:
@ -252,8 +255,8 @@ class BasePlaybookManager:
print('\033[31m %s \033[0m\n' % err_msg) print('\033[31m %s \033[0m\n' % err_msg)
not_valid.append(k) not_valid.append(k)
else: else:
jms_asset['address'] = '127.0.0.1' host['ansible_host'] = jms_asset['address'] = '127.0.0.1'
jms_asset['port'] = server.local_bind_port host['ansible_port'] = jms_asset['port'] = server.local_bind_port
servers.append(server) servers.append(server)
# 网域不可连接的,就不继续执行此资源的后续任务了 # 网域不可连接的,就不继续执行此资源的后续任务了

View File

@ -36,7 +36,7 @@ class HostTypes(BaseType):
'choices': ['ssh', 'telnet', 'vnc', 'rdp'] 'choices': ['ssh', 'telnet', 'vnc', 'rdp']
}, },
cls.WINDOWS: { cls.WINDOWS: {
'choices': ['rdp', 'ssh', 'vnc'] 'choices': ['rdp', 'ssh', 'vnc', 'winrm']
} }
} }
@ -58,7 +58,7 @@ class HostTypes(BaseType):
cls.WINDOWS: { cls.WINDOWS: {
'ansible_config': { 'ansible_config': {
'ansible_shell_type': 'cmd', 'ansible_shell_type': 'cmd',
'ansible_connection': 'ssh', 'ansible_connection': 'smart',
}, },
}, },
cls.OTHER_HOST: { cls.OTHER_HOST: {

View File

@ -10,6 +10,7 @@ class Protocol(ChoicesMixin, models.TextChoices):
rdp = 'rdp', 'RDP' rdp = 'rdp', 'RDP'
telnet = 'telnet', 'Telnet' telnet = 'telnet', 'Telnet'
vnc = 'vnc', 'VNC' vnc = 'vnc', 'VNC'
winrm = 'winrm', 'WinRM'
mysql = 'mysql', 'MySQL' mysql = 'mysql', 'MySQL'
mariadb = 'mariadb', 'MariaDB' mariadb = 'mariadb', 'MariaDB'
@ -51,6 +52,13 @@ class Protocol(ChoicesMixin, models.TextChoices):
'port': 23, 'port': 23,
'secret_types': ['password'], 'secret_types': ['password'],
}, },
cls.winrm: {
'port': 5985,
'secret_types': ['password'],
'setting': {
'use_ssl': False,
}
},
} }
@classmethod @classmethod

View File

@ -2,6 +2,8 @@
from django.db import migrations, models from django.db import migrations, models
from assets.const import AllTypes
def migrate_platform_charset(apps, schema_editor): def migrate_platform_charset(apps, schema_editor):
platform_model = apps.get_model('assets', 'Platform') platform_model = apps.get_model('assets', 'Platform')
@ -20,6 +22,11 @@ def migrate_platform_protocol_primary(apps, schema_editor):
p.save() 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
@ -34,4 +41,5 @@ class Migration(migrations.Migration):
), ),
migrations.RunPython(migrate_platform_charset), migrations.RunPython(migrate_platform_charset),
migrations.RunPython(migrate_platform_protocol_primary), migrations.RunPython(migrate_platform_protocol_primary),
migrations.RunPython(migrate_internal_platforms),
] ]

View File

@ -41,6 +41,9 @@ class ProtocolSettingSerializer(serializers.Serializer):
# Redis # Redis
auth_username = serializers.BooleanField(default=False, label=_("Auth with username")) 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 PlatformAutomationSerializer(serializers.ModelSerializer):
class Meta: class Meta:

View File

@ -72,15 +72,14 @@ class JMSInventory:
var['ansible_ssh_private_key_file'] = account.private_key_path var['ansible_ssh_private_key_file'] = account.private_key_path
return var 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: if not account:
host['error'] = _("No account available") host['error'] = _("No account available")
return host return host
ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) port = protocol.port if protocol else 22
ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None
host['ansible_host'] = asset.address host['ansible_host'] = asset.address
host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 host['ansible_port'] = port
su_from = account.su_from su_from = account.su_from
if platform.su_enabled and su_from: if platform.su_enabled and su_from:
@ -97,28 +96,55 @@ class JMSInventory:
host.update(self.make_account_ansible_vars(account)) host.update(self.make_account_ansible_vars(account))
if gateway: if gateway:
host.update(self.make_proxy_command(gateway)) ansible_connection = host.get('ansible_connection', 'ssh')
if ansible_connection in ('local', 'winrm'):
host['gateway'] = {
'address': gateway.address, 'port': gateway.port,
'username': gateway.username, 'secret': gateway.password,
'private_key_path': gateway.private_key_path
}
host['jms_asset']['port'] = port
else:
host.update(self.make_proxy_command(gateway))
@staticmethod @staticmethod
def get_primary_protocol(protocols): def get_primary_protocol(ansible_config, protocols):
if protocols: invalid_protocol = type('protocol', (), {'name': 'null', 'port': 0})
primary = protocols[0] ansible_connection = ansible_config.get('ansible_connection')
protocol = primary.name # 数值越小,优先级越高,若用户在 ansible_config 中配置了,则提高用户配置方式的优先级
port = primary.port protocol_priority = {'ssh': 10, 'winrm': 9, ansible_connection: 1}
else: protocol_sorted = sorted(protocols, key=lambda x: protocol_priority.get(x.name, 999))
protocol = 'null' protocol = protocol_sorted[0] if protocol_sorted else invalid_protocol
port = 0 return protocol
return protocol, port
@staticmethod
def fill_ansible_config(ansible_config, protocol):
if protocol.name in ('ssh', 'winrm'):
ansible_config['ansible_connection'] = protocol.name
if protocol.name == '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): def asset_to_host(self, asset, account, automation, protocols, platform):
protocol, port = self.get_primary_protocol(protocols) try:
ansible_config = dict(automation.ansible_config)
except (AttributeError, TypeError):
ansible_config = {}
protocol = self.get_primary_protocol(ansible_config, protocols)
host = { host = {
'name': '{}'.format(asset.name.replace(' ', '_')), 'name': '{}'.format(asset.name.replace(' ', '_')),
'jms_asset': { 'jms_asset': {
'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'id': str(asset.id), 'name': asset.name, 'address': asset.address,
'type': asset.type, 'category': asset.category, 'type': asset.type, 'category': asset.category,
'protocol': protocol, 'port': port, 'protocol': protocol.name, 'port': protocol.port,
'spec_info': asset.spec_info, 'secret_info': asset.secret_info, 'spec_info': asset.spec_info, 'secret_info': asset.secret_info,
'protocols': [{'name': p.name, 'port': p.port} for p in protocols], 'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
}, },
@ -131,25 +157,16 @@ class JMSInventory:
if host['jms_account'] and asset.platform.type == 'oracle': if host['jms_account'] and asset.platform.type == 'oracle':
host['jms_account']['mode'] = 'sysdba' if account.privileged else None host['jms_account']['mode'] = 'sysdba' if account.privileged else None
try: ansible_config = self.fill_ansible_config(ansible_config, protocol)
ansible_config = dict(automation.ansible_config)
except Exception as e:
ansible_config = {}
ansible_connection = ansible_config.get('ansible_connection', 'ssh')
host.update(ansible_config) host.update(ansible_config)
gateway = None gateway = None
if not asset.is_gateway and asset.domain: if not asset.is_gateway and asset.domain:
gateway = asset.domain.select_gateway() gateway = asset.domain.select_gateway()
if ansible_connection == 'local': self.make_account_vars(
if gateway: host, asset, account, automation, protocol, platform, gateway
host['gateway'] = { )
'address': gateway.address, 'port': gateway.port,
'username': gateway.username, 'secret': gateway.password
}
else:
self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway)
return host return host
def get_asset_sorted_accounts(self, asset): def get_asset_sorted_accounts(self, asset):
@ -194,14 +211,23 @@ class JMSInventory:
else: else:
return None return None
@staticmethod
def set_platform_protocol_setting_to_asset(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): def generate(self, path_dir):
hosts = [] hosts = []
platform_assets = self.group_by_platform(self.assets) platform_assets = self.group_by_platform(self.assets)
for platform, assets in platform_assets.items(): for platform, assets in platform_assets.items():
automation = platform.automation automation = platform.automation
platform_protocols = {
p['name']: p['setting'] for p in platform.protocols.values('name', 'setting')
}
for asset in assets: for asset in assets:
protocols = asset.protocols.all() protocols = self.set_platform_protocol_setting_to_asset(asset, platform_protocols)
account = self.select_account(asset) account = self.select_account(asset)
host = self.asset_to_host(asset, account, automation, protocols, platform) host = self.asset_to_host(asset, account, automation, protocols, platform)

View File

@ -65,6 +65,7 @@ pyjwkest==1.4.2
jsonfield2==4.0.0.post0 jsonfield2==4.0.0.post0
geoip2==4.5.0 geoip2==4.5.0
ipip-ipdb==1.6.1 ipip-ipdb==1.6.1
pywinrm==0.4.3
# Django environment # Django environment
Django==3.2.17 Django==3.2.17
django-bootstrap3==14.2.0 django-bootstrap3==14.2.0