Merge branch 'dev' of https://github.com/jumpserver/jumpserver into pr@dev@feat_windows_winrm

pull/10104/head
jiangweidong 2023-03-29 17:11:07 +08:00
commit bc186df8d5
18 changed files with 57 additions and 706 deletions

View File

@ -3,7 +3,10 @@ name: 需求建议
about: 提出针对本项目的想法和建议
title: "[Feature] "
labels: 类型:需求
assignees: ibuler
assignees:
- ibuler
- baijiangjie
---

View File

@ -3,7 +3,9 @@ name: Bug 提交
about: 提交产品缺陷帮助我们更好的改进
title: "[Bug] "
labels: 类型:bug
assignees: wojiushixiaobai
assignees:
- wojiushixiaobai
- baijiangjie
---

View File

@ -3,7 +3,9 @@ name: 问题咨询
about: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[Question] "
labels: 类型:提问
assignees: wojiushixiaobai
assignees:
- wojiushixiaobai
- baijiangjie
---

View File

@ -1,6 +1,6 @@
ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM ghcr.io/jumpserver/core:${VERSION}
FROM jumpserver/core:${VERSION}
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
WORKDIR /opt/jumpserver

View File

@ -78,7 +78,9 @@ class ChangeSecretManager(AccountBasePlaybookManager):
accounts = accounts.filter(secret_type=self.secret_type)
if settings.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED:
accounts = accounts.filter(privileged=False)
accounts = accounts.filter(privileged=False).exclude(
username__in=['root', 'administrator']
)
return accounts
def host_callback(

View File

@ -136,8 +136,8 @@ class AllTypes(ChoicesMixin):
(Category.HOST, HostTypes),
(Category.DEVICE, DeviceTypes),
(Category.DATABASE, DatabaseTypes),
(Category.CLOUD, CloudTypes),
(Category.WEB, WebTypes),
(Category.CLOUD, CloudTypes)
)
@classmethod

View File

@ -1,4 +0,0 @@
## Navicat Premium
- 需要先手动导入License激活

View File

@ -1,411 +0,0 @@
import os
import shutil
import time
import winreg
import win32api
import win32con
import const as c
from pywinauto import Application
from pywinauto.keyboard import send_keys
from pywinauto.controls.uia_controls import (
EditWrapper, ComboBoxWrapper, ButtonWrapper
)
from common import wait_pid, BaseApplication, _messageBox
_default_path = r'C:\Program Files\PremiumSoft\Navicat Premium 16\navicat.exe'
class AppletApplication(BaseApplication):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.path = _default_path
self.username = self.account.username
self.password = self.account.secret
self.privileged = self.account.privileged
self.host = self.asset.address
self.port = self.asset.get_protocol_port(self.protocol)
self.db = self.asset.spec_info.db_name
self.name = '%s-%s-%s' % (self.host, self.db, int(time.time()))
self.use_ssl = self.asset.spec_info.use_ssl
self.client_key = self.asset.secret_info.client_key
self.client_key_path = None
self.pid = None
self.app = None
@staticmethod
def get_cert_path():
win_user_name = win32api.GetUserName()
cert_path = r'C:\Users\%s\AppData\Roaming\Navicat\certs' % win_user_name
return cert_path
def clean_up(self):
protocols = (
'NavicatMARIADB', 'NavicatMONGODB', 'Navicat',
'NavicatORA', 'NavicatMSSQL', 'NavicatPG'
)
for p in protocols:
sub_key = r'Software\PremiumSoft\%s\Servers' % p
try:
win32api.RegDeleteTree(winreg.HKEY_CURRENT_USER, sub_key)
except Exception:
pass
cert_path = self.get_cert_path()
shutil.rmtree(cert_path, ignore_errors=True)
def gen_asset_file(self):
if self.use_ssl and self.client_key:
cert_path = self.get_cert_path()
if not os.path.exists(cert_path):
os.makedirs(cert_path, exist_ok=True)
filepath = os.path.join(cert_path, str(int(time.time())))
with open(filepath, 'w') as f:
f.write(self.client_key)
self.client_key_path = filepath
@staticmethod
def edit_regedit():
sub_key = r'Software\PremiumSoft\NavicatPremium'
try:
key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, sub_key)
# 禁止弹出欢迎页面
winreg.SetValueEx(key, 'AlreadyShowNavicatV16WelcomeScreen', 0, winreg.REG_DWORD, 1)
# 禁止开启自动检查更新
winreg.SetValueEx(key, 'AutoCheckUpdate', 0, winreg.REG_DWORD, 0)
# 禁止弹出初始化界面
winreg.SetValueEx(key, 'ShareUsageData', 0, winreg.REG_DWORD, 0)
except Exception as err:
print('Launch error: %s' % err)
def launch(self):
# 清理因为异常未关闭的会话历史记录
self.clean_up()
# 生成资产依赖的相关文件
self.gen_asset_file()
# 修改注册表,达到一些配置目的
self.edit_regedit()
@staticmethod
def _exec_commands(commands):
for command in commands:
pre_check = command.get('pre_check', lambda: True)
if not pre_check():
_messageBox('程序启动异常,请重新连接!!', 'Error', win32con.MB_DEFAULT_DESKTOP_ONLY)
return
time.sleep(0.5)
if command['type'] == 'key':
send_keys(' '.join(command['commands']))
elif command['type'] == 'action':
for f in command['commands']:
f()
def _pre_check_is_password_input(self):
try:
self.app.window(best_match='Connection Password')
except Exception:
return False
return True
def _action_ele_click(self, ele_name, conn_win=None):
if not conn_win:
conn_win = self.app.window(best_match='Dialog'). \
child_window(title_re='New Connection')
conn_win.child_window(best_match=ele_name).click()
def _fill_mysql_auth_info(self):
conn_window = self.app.window(best_match='Dialog'). \
child_window(title_re='New Connection')
name_ele = conn_window.child_window(best_match='Edit5')
EditWrapper(name_ele.element_info).set_edit_text(self.name)
host_ele = conn_window.child_window(best_match='Edit4')
EditWrapper(host_ele.element_info).set_edit_text(self.host)
port_ele = conn_window.child_window(best_match='Edit2')
EditWrapper(port_ele.element_info).set_edit_text(self.port)
username_ele = conn_window.child_window(best_match='Edit1')
EditWrapper(username_ele.element_info).set_edit_text(self.username)
def _get_mysql_commands(self):
commands = [
{
'type': 'key',
'commands': [
'%f', c.DOWN, c.RIGHT, c.ENTER
],
},
{
'type': 'action',
'commands': [
self._fill_mysql_auth_info, lambda: self._action_ele_click('Save password')
]
},
{
'type': 'key',
'commands': [c.ENTER]
}
]
return commands
def _get_mariadb_commands(self):
commands = [
{
'type': 'key',
'commands': [
'%f', c.DOWN, c.RIGHT, c.DOWN * 5, c.ENTER,
],
},
{
'type': 'action',
'commands': [
self._fill_mysql_auth_info, lambda: self._action_ele_click('Save password')
]
},
{
'type': 'key',
'commands': [c.ENTER]
}
]
return commands
def _fill_mongodb_auth_info(self):
conn_window = self.app.window(best_match='Dialog'). \
child_window(title_re='New Connection')
auth_type_ele = conn_window.child_window(best_match='ComboBox2')
ComboBoxWrapper(auth_type_ele.element_info).select('Password')
name_ele = conn_window.child_window(best_match='Edit5')
EditWrapper(name_ele.element_info).set_edit_text(self.name)
host_ele = conn_window.child_window(best_match='Edit4')
EditWrapper(host_ele.element_info).set_edit_text(self.host)
port_ele = conn_window.child_window(best_match='Edit2')
EditWrapper(port_ele.element_info).set_edit_text(self.port)
db_ele = conn_window.child_window(best_match='Edit6')
EditWrapper(db_ele.element_info).set_edit_text(self.db)
username_ele = conn_window.child_window(best_match='Edit1')
EditWrapper(username_ele.element_info).set_edit_text(self.username)
def _get_mongodb_commands(self):
commands = [
{
'type': 'key',
'commands': [
'%f', c.DOWN, c.RIGHT, c.DOWN * 6, c.ENTER,
],
},
{
'type': 'action',
'commands': [
self._fill_mongodb_auth_info, lambda: self._action_ele_click('Save password')
]
},
{
'type': 'key',
'commands': [c.ENTER]
}
]
if self.use_ssl:
ssl_commands = [
{
'type': 'key',
'commands': [c.TAB * 5, c.RIGHT * 3, c.TAB]
},
{
'type': 'action',
'commands': [
lambda: self._action_ele_click('Use SSL'),
lambda: self._action_ele_click('Use authentication'),
]
},
{
'type': 'key',
'commands': [c.TAB, self.client_key_path]
},
{
'type': 'action',
'commands': [lambda: self._action_ele_click('Allow invalid host names')]
}
]
commands = commands[:2] + ssl_commands + commands[2:]
return commands
def _fill_postgresql_auth_info(self):
conn_window = self.app.window(best_match='Dialog'). \
child_window(title_re='New Connection')
name_ele = conn_window.child_window(best_match='Edit6')
EditWrapper(name_ele.element_info).set_edit_text(self.name)
host_ele = conn_window.child_window(best_match='Edit5')
EditWrapper(host_ele.element_info).set_edit_text(self.host)
port_ele = conn_window.child_window(best_match='Edit2')
EditWrapper(port_ele.element_info).set_edit_text(self.port)
db_ele = conn_window.child_window(best_match='Edit4')
EditWrapper(db_ele.element_info).set_edit_text(self.db)
username_ele = conn_window.child_window(best_match='Edit1')
EditWrapper(username_ele.element_info).set_edit_text(self.username)
def _get_postgresql_commands(self):
commands = [
{
'type': 'key',
'commands': [
'%f', c.DOWN, c.RIGHT, c.DOWN, c.ENTER,
],
},
{
'type': 'action',
'commands': [
self._fill_postgresql_auth_info, lambda: self._action_ele_click('Save password')
]
},
{
'type': 'key',
'commands': [c.ENTER]
}
]
return commands
def _fill_sqlserver_auth_info(self):
conn_window = self.app.window(best_match='Dialog'). \
child_window(title_re='New Connection')
name_ele = conn_window.child_window(best_match='Edit5')
EditWrapper(name_ele.element_info).set_edit_text(self.name)
host_ele = conn_window.child_window(best_match='Edit4')
EditWrapper(host_ele.element_info).set_edit_text('%s,%s' % (self.host, self.port))
db_ele = conn_window.child_window(best_match='Edit3')
EditWrapper(db_ele.element_info).set_edit_text(self.db)
username_ele = conn_window.child_window(best_match='Edit6')
EditWrapper(username_ele.element_info).set_edit_text(self.username)
def _get_sqlserver_commands(self):
commands = [
{
'type': 'key',
'commands': [
'%f', c.DOWN, c.RIGHT, c.DOWN * 4, c.ENTER,
],
},
{
'type': 'action',
'commands': [
self._fill_sqlserver_auth_info, lambda: self._action_ele_click('Save password')
]
},
{
'type': 'key',
'commands': [c.ENTER]
}
]
return commands
def _fill_oracle_auth_info(self):
conn_window = self.app.window(best_match='Dialog'). \
child_window(title_re='New Connection')
name_ele = conn_window.child_window(best_match='Edit6')
EditWrapper(name_ele.element_info).set_edit_text(self.name)
host_ele = conn_window.child_window(best_match='Edit5')
EditWrapper(host_ele.element_info).set_edit_text(self.host)
port_ele = conn_window.child_window(best_match='Edit3')
EditWrapper(port_ele.element_info).set_edit_text(self.port)
db_ele = conn_window.child_window(best_match='Edit2')
EditWrapper(db_ele.element_info).set_edit_text(self.db)
username_ele = conn_window.child_window(best_match='Edit')
EditWrapper(username_ele.element_info).set_edit_text(self.username)
if self.privileged:
conn_window.child_window(best_match='Advanced', control_type='TabItem').click_input()
role_ele = conn_window.child_window(best_match='ComboBox2')
ComboBoxWrapper(role_ele.element_info).select('SYSDBA')
def _get_oracle_commands(self):
commands = [
{
'type': 'key',
'commands': [
'%f', c.DOWN, c.RIGHT, c.DOWN * 2, c.ENTER,
],
},
{
'type': 'action',
'commands': [
lambda: self._action_ele_click('Save password'), self._fill_oracle_auth_info
]
},
{
'type': 'key',
'commands': [c.ENTER]
}
]
return commands
def run(self):
self.launch()
self.app = Application(backend='uia')
work_dir = os.path.dirname(self.path)
self.app.start(self.path, work_dir=work_dir)
self.pid = self.app.process
# 检测是否为试用版本
try:
trial_btn = self.app.top_window().child_window(
best_match='Trial', control_type='Button'
)
ButtonWrapper(trial_btn.element_info).click()
time.sleep(0.5)
except Exception:
pass
# 根据协议获取相应操作命令
action = getattr(self, '_get_%s_commands' % self.protocol, None)
if action is None:
raise ValueError('This protocol is not supported: %s' % self.protocol)
commands = action()
# 关闭掉桌面许可弹框
commands.insert(0, {'type': 'key', 'commands': (c.ESC,)})
# 登录
commands.extend([
{
'type': 'key',
'commands': (
'%f', c.DOWN * 5, c.ENTER
)
},
{
'type': 'key',
'commands': (self.password, c.ENTER),
'pre_check': self._pre_check_is_password_input
}
])
self._exec_commands(commands)
def wait(self):
try:
wait_pid(self.pid)
except Exception:
pass
finally:
self.clean_up()

View File

@ -1,215 +0,0 @@
import abc
import base64
import json
import locale
import os
import subprocess
import sys
import time
from subprocess import CREATE_NO_WINDOW
_blockInput = None
_messageBox = None
if sys.platform == 'win32':
import ctypes
from ctypes import wintypes
import win32ui
# import win32con
_messageBox = win32ui.MessageBox
_blockInput = ctypes.windll.user32.BlockInput
_blockInput.argtypes = [wintypes.BOOL]
_blockInput.restype = wintypes.BOOL
def block_input():
if _blockInput:
_blockInput(True)
def unblock_input():
if _blockInput:
_blockInput(False)
def decode_content(content: bytes) -> str:
for encoding_name in ['utf-8', 'gbk', 'gb2312']:
try:
return content.decode(encoding_name)
except Exception as e:
print(e)
encoding_name = locale.getpreferredencoding()
return content.decode(encoding_name)
def notify_err_message(msg):
if _messageBox:
_messageBox(msg, 'Error')
def check_pid_alive(pid) -> bool:
# tasklist /fi "PID eq 508" /fo csv
# '"映像名称","PID","会话名 ","会话# ","内存使用 "\r\n"wininit.exe","508","Services","0","6,920 K"\r\n'
try:
csv_ret = subprocess.check_output(["tasklist", "/fi", f'PID eq {pid}', "/fo", "csv"],
creationflags=CREATE_NO_WINDOW)
content = decode_content(csv_ret)
content_list = content.strip().split("\r\n")
if len(content_list) != 2:
print("check pid {} ret invalid: {}".format(pid, content))
return False
ret_pid = content_list[1].split(",")[1].strip('"')
return str(pid) == ret_pid
except Exception as e:
print("check pid {} err: {}".format(pid, e))
return False
def wait_pid(pid):
while 1:
time.sleep(5)
ok = check_pid_alive(pid)
if not ok:
print("pid {} is not alive".format(pid))
break
class DictObj:
def __init__(self, in_dict: dict):
assert isinstance(in_dict, dict)
for key, val in in_dict.items():
if isinstance(val, (list, tuple)):
setattr(self, key, [DictObj(x) if isinstance(x, dict) else x for x in val])
else:
setattr(self, key, DictObj(val) if isinstance(val, dict) else val)
class User(DictObj):
id: str
name: str
username: str
class Specific(DictObj):
# web
autofill: str
username_selector: str
password_selector: str
submit_selector: str
script: list
# database
db_name: str
use_ssl: str
class Secret(DictObj):
client_key: str
class Category(DictObj):
value: str
label: str
class Protocol(DictObj):
id: str
name: str
port: int
class Asset(DictObj):
id: str
name: str
address: str
protocols: list[Protocol]
category: Category
spec_info: Specific
secret_info: Secret
def get_protocol_port(self, protocol):
for item in self.protocols:
if item.name == protocol:
return item.port
return None
class LabelValue(DictObj):
label: str
value: str
class Account(DictObj):
id: str
name: str
username: str
secret: str
privileged: bool
secret_type: LabelValue
class Platform(DictObj):
charset: str
name: str
charset: LabelValue
type: LabelValue
class Manifest(DictObj):
name: str
version: str
path: str
exec_type: str
connect_type: str
protocols: list[str]
def get_manifest_data() -> dict:
current_dir = os.path.dirname(__file__)
manifest_file = os.path.join(current_dir, 'manifest.json')
try:
with open(manifest_file, "r", encoding='utf8') as f:
return json.load(f)
except Exception as e:
print(e)
return {}
def read_app_manifest(app_dir) -> dict:
main_json_file = os.path.join(app_dir, "manifest.json")
if not os.path.exists(main_json_file):
return {}
with open(main_json_file, 'r', encoding='utf8') as f:
return json.load(f)
def convert_base64_to_dict(base64_str: str) -> dict:
try:
data_json = base64.decodebytes(base64_str.encode('utf-8')).decode('utf-8')
return json.loads(data_json)
except Exception as e:
print(e)
return {}
class BaseApplication(abc.ABC):
def __init__(self, *args, **kwargs):
self.app_name = kwargs.get('app_name', '')
self.protocol = kwargs.get('protocol', '')
self.manifest = Manifest(kwargs.get('manifest', {}))
self.user = User(kwargs.get('user', {}))
self.asset = Asset(kwargs.get('asset', {}))
self.account = Account(kwargs.get('account', {}))
self.platform = Platform(kwargs.get('platform', {}))
@abc.abstractmethod
def run(self):
raise NotImplementedError('run')
@abc.abstractmethod
def wait(self):
raise NotImplementedError('wait')

View File

@ -1,8 +0,0 @@
UP = '{UP}'
LEFT = '{LEFT}'
DOWN = '{DOWN}'
RIGHT = '{RIGHT}'
TAB = '{VK_TAB}'
ESC = '{ESC}'
ENTER = '{VK_RETURN}'

View File

@ -1,3 +0,0 @@
- zh:
display_name: Navicat premium 16
comment: 数据库管理软件

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,22 +0,0 @@
import sys
from common import (block_input, unblock_input)
from common import convert_base64_to_dict
from app import AppletApplication
def main():
base64_str = sys.argv[1]
data = convert_base64_to_dict(base64_str)
applet_app = AppletApplication(**data)
block_input()
applet_app.run()
unblock_input()
applet_app.wait()
if __name__ == '__main__':
try:
main()
except Exception as e:
print(e)

View File

@ -1,17 +0,0 @@
name: navicat
display_name: Navicat premium 16
comment: Database management software
version: 0.1
exec_type: python
author: JumpServer Team
type: general
update_policy: always
tags:
- database
protocols:
- mysql
- mariadb
- postgresql
- sqlserver
- oracle
- mongodb

View File

@ -1,6 +0,0 @@
type: manual # exe, zip, manual
# 从这里下载的: https://www.navicat.com.cn/download/direct-download?product=navicat_premium_en_x64.exe
source:
destination: C:\Program Files\PremiumSoft\Navicat Premium 16
program: C:\Program Files\PremiumSoft\Navicat Premium 16\navicat.exe
md5: 6c2c25fa56c75254c6bbcba043000063

View File

@ -183,19 +183,6 @@
GOOGLE_DEFAULT_CLIENT_ID: ''
GOOGLE_DEFAULT_CLIENT_SECRET: ''
- name: Download navicat161_premium_en package (navicat)
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/navicat161_premium_en_x64.exe"
dest: "{{ ansible_env.TEMP }}\\navicat161_premium_en_x64.exe"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Install navicat (navicat)
ansible.windows.win_package:
path: "{{ ansible_env.TEMP }}\\navicat161_premium_en_x64.exe"
state: present
arguments:
- /SILENT
- name: Generate tinkerd component config
ansible.windows.win_shell:
"tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}

View File

@ -1,11 +1,12 @@
import uuid
from django.core.cache import cache
from django.db import models
from django.forms.models import model_to_dict
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger
logger = get_logger(__name__)
@ -21,9 +22,44 @@ class Status(models.Model):
terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True)
CACHE_KEY = 'TERMINAL_STATUS_{}'
class Meta:
db_table = 'terminal_status'
get_latest_by = 'date_created'
verbose_name = _("Status")
@classmethod
def get_terminal_latest_stat(cls, terminal):
key = cls.CACHE_KEY.format(terminal.id)
data = cache.get(key)
if not data:
return None
data.pop('terminal', None)
stat = cls(**data)
stat.terminal = terminal
stat.is_alive = terminal.is_alive
stat.keep_one_decimal_place()
return stat
def keep_one_decimal_place(self):
keys = ['cpu_load', 'memory_used', 'disk_used']
for key in keys:
value = getattr(self, key, 0)
if not isinstance(value, (int, float)):
continue
value = '%.1f' % value
setattr(self, key, float(value))
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
self.terminal.set_alive(ttl=120)
return self.save_to_cache()
def save_to_cache(self):
if not self.terminal:
return
key = self.CACHE_KEY.format(self.terminal.id)
data = model_to_dict(self)
cache.set(key, data, 60 * 3)
return data

View File

@ -1,6 +1,7 @@
import time
from django.conf import settings
from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
@ -35,6 +36,10 @@ class TerminalStatusMixin:
return False
return time.time() - self.last_stat.date_created.timestamp() < 150
def set_alive(self, ttl=120):
key = self.ALIVE_KEY.format(self.id)
cache.set(key, True, ttl)
class StorageMixin:
command_storage: str