diff --git a/README.md b/README.md
index 5cfc92343..73abee037 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,8 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向
- 无插件: 仅需浏览器,极致的 Web Terminal 使用体验;
- 多云支持: 一套系统,同时管理不同云上面的资产;
- 云端存储: 审计录像云端存储,永不丢失;
-- 多租户: 一套系统,多个子公司和部门同时使用。
+- 多租户: 一套系统,多个子公司和部门同时使用;
+- 多应用支持: 数据库,Windows远程应用,Kubernetes。
## 版本说明
@@ -198,6 +199,54 @@ v2.1.0 是 v2.0.0 之后的功能版本。
文件传输 |
可对文件的上传、下载记录进行审计 |
+
+ 数据库审计 Database |
+ 连接方式 |
+ 命令方式 |
+
+
+ Web UI方式 (X-PACK) |
+
+
+
+ 支持的数据库 |
+ MySQL |
+
+
+ Oracle (X-PACK) |
+
+
+ MariaDB (X-PACK) |
+
+
+ PostgreSQL (X-PACK) |
+
+
+ 功能亮点 |
+ 语法高亮 |
+
+
+ SQL格式化 |
+
+
+ 支持快捷键 |
+
+
+ 支持选中执行 |
+
+
+ SQL历史查询 |
+
+
+ 支持页面创建 DB, TABLE |
+
+
+ 会话审计 |
+ 命令记录 |
+
+
+ 录像回放 |
+
## 快速开始
@@ -212,6 +261,11 @@ v2.1.0 是 v2.0.0 之后的功能版本。
- [Koko](https://github.com/jumpserver/koko) JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco)
- [Guacamole](https://github.com/jumpserver/docker-guacamole) JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/)
+## 致谢
+- [Apache Guacamole](https://guacamole.apache.org/) Web页面连接 RDP, SSH, VNC协议设备,JumpServer 图形化连接依赖
+- [OmniDB](https://omnidb.org/) Web页面连接使用数据库,JumpServer Web数据库依赖
+
+
## JumpServer 企业版
- [申请企业版试用](https://jinshuju.net/f/kyOYpi)
> 注:企业版支持离线安装,申请通过后会提供高速下载链接。
diff --git a/apps/assets/migrations/0061_auto_20201116_1757.py b/apps/assets/migrations/0061_auto_20201116_1757.py
new file mode 100644
index 000000000..71ddf21cf
--- /dev/null
+++ b/apps/assets/migrations/0061_auto_20201116_1757.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.13 on 2020-11-16 09:57
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0060_node_full_value'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='node',
+ options={'ordering': ['value'], 'verbose_name': 'Node'},
+ ),
+ ]
diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py
index 282c9b928..b7239da75 100644
--- a/apps/assets/models/base.py
+++ b/apps/assets/models/base.py
@@ -158,9 +158,11 @@ class AuthMixin:
if update_fields:
self.save(update_fields=update_fields)
- def has_special_auth(self, asset=None):
+ def has_special_auth(self, asset=None, username=None):
from .authbook import AuthBook
- queryset = AuthBook.objects.filter(username=self.username)
+ if username is None:
+ username = self.username
+ queryset = AuthBook.objects.filter(username=username)
if asset:
queryset = queryset.filter(asset=asset)
return queryset.exists()
diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py
index edfec4a32..6461ec955 100644
--- a/apps/assets/models/node.py
+++ b/apps/assets/models/node.py
@@ -210,6 +210,7 @@ class FamilyMixin:
if not full_value:
return []
nodes_family = full_value.split('/')
+ nodes_family = [v for v in nodes_family if v]
org_root = cls.org_root()
if nodes_family[0] == org_root.value:
nodes_family = nodes_family[1:]
@@ -217,6 +218,7 @@ class FamilyMixin:
@classmethod
def create_nodes_recurse(cls, values, parent=None):
+ values = [v for v in values if v]
if not values:
return None
if parent is None:
@@ -407,7 +409,7 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
class Meta:
verbose_name = _("Node")
- ordering = ['key']
+ ordering = ['value']
def __str__(self):
return self.full_value
diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py
index 1dfd79ad2..5a1f47284 100644
--- a/apps/assets/models/user.py
+++ b/apps/assets/models/user.py
@@ -165,6 +165,11 @@ class SystemUser(BaseUser):
def is_need_test_asset_connective(self):
return self.protocol not in self.application_category_protocols
+ def has_special_auth(self, asset=None, username=None):
+ if username is None and self.username_same_with_user:
+ raise TypeError('System user is dynamic, username should be pass')
+ return super().has_special_auth(asset=asset, username=username)
+
@property
def can_perm_to_asset(self):
return self.protocol not in self.application_category_protocols
diff --git a/apps/assets/tasks/push_system_user.py b/apps/assets/tasks/push_system_user.py
index 0072a862a..0bb9be407 100644
--- a/apps/assets/tasks/push_system_user.py
+++ b/apps/assets/tasks/push_system_user.py
@@ -139,6 +139,7 @@ def get_push_windows_system_user_tasks(system_user, username=None):
tasks = []
if not password:
+ logger.error("Error: no password found")
return tasks
task = {
'name': 'Add user {}'.format(username),
@@ -214,14 +215,15 @@ def push_system_user_util(system_user, assets, task_name, username=None):
print(_("Start push system user for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_hosts)))
- if not system_user.has_special_auth():
+ # 如果没有特殊密码设置,就不需要单独推送某台机器了
+ if not system_user.has_special_auth(username=username):
logger.debug("System user not has special auth")
tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, _hosts)
continue
for _host in _hosts:
- system_user.load_asset_special_auth(_host)
+ system_user.load_asset_special_auth(_host, username=username)
tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, [_host])
diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py
index e70bd0395..6d8d876ef 100644
--- a/apps/common/utils/crypto.py
+++ b/apps/common/utils/crypto.py
@@ -189,7 +189,7 @@ class Crypto:
if origin_text:
# 有时不同算法解密不报错,但是返回空字符串
return origin_text
- except (TypeError, ValueError, UnicodeDecodeError):
+ except (TypeError, ValueError, UnicodeDecodeError, IndexError):
continue
diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py
index dd2d02693..624212e6f 100644
--- a/apps/jumpserver/urls.py
+++ b/apps/jumpserver/urls.py
@@ -69,9 +69,9 @@ urlpatterns = [
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
-js_i18n_patterns = i18n_patterns(
- path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
-)
+js_i18n_patterns = [
+ path('core/jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
+]
urlpatterns += js_i18n_patterns
handler404 = 'jumpserver.views.handler404'
diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py
index 3c2fe1b90..adba3e81b 100644
--- a/apps/ops/ansible/callback.py
+++ b/apps/ops/ansible/callback.py
@@ -65,6 +65,8 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
"""
Task result Callback
"""
+ context = None
+
def clean_result(self, t, host, task_name, task_result):
contacted = self.results_summary["contacted"]
dark = self.results_summary["dark"]
@@ -133,7 +135,11 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
pass
def set_play_context(self, context):
- context.ssh_args = '-C -o ControlMaster=no'
+ # for k, v in context._attributes.items():
+ # print("{} ==> {}".format(k, v))
+ if self.context and isinstance(self.context, dict):
+ for k, v in self.context.items():
+ setattr(context, k, v)
class CommandResultCallback(AdHocResultCallback):
diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py
index 4486bdf1a..e741d8f8b 100644
--- a/apps/ops/ansible/runner.py
+++ b/apps/ops/ansible/runner.py
@@ -182,6 +182,13 @@ class AdHocRunner:
_options.update(options)
return _options
+ def set_control_master_if_need(self, cleaned_tasks):
+ modules = [task.get('action', {}).get('module') for task in cleaned_tasks]
+ if {'ping', 'win_ping'} & set(modules):
+ self.results_callback.context = {
+ 'ssh_args': '-C -o ControlMaster=no'
+ }
+
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
"""
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
@@ -193,6 +200,7 @@ class AdHocRunner:
self.check_pattern(pattern)
self.results_callback = self.get_result_callback()
cleaned_tasks = self.clean_tasks(tasks)
+ self.set_control_master_if_need(cleaned_tasks)
context.CLIARGS = ImmutableDict(self.options)
play_source = dict(
diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py
index b0fe5f8ef..f3de03210 100644
--- a/apps/ops/inventory.py
+++ b/apps/ops/inventory.py
@@ -76,7 +76,7 @@ class JMSInventory(JMSBaseInventory):
write you own inventory, construct you inventory,
user_info is obtained from admin_user or asset_user
"""
- def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None):
+ def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None, system_user=None):
"""
:param assets: assets
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
@@ -86,6 +86,7 @@ class JMSInventory(JMSBaseInventory):
self.assets = assets
self.using_admin = run_as_admin
self.run_as = run_as
+ self.system_user = system_user
self.become_info = become_info
host_list = []
@@ -104,18 +105,25 @@ class JMSInventory(JMSBaseInventory):
def get_run_user_info(self, host):
from assets.backends import AssetUserManager
- if self.run_as is None:
+ if not self.run_as and not self.system_user:
return {}
+ asset_id = host.get('id', '')
+ asset = self.assets.filter(id=asset_id).first()
+ if not asset:
+ logger.error('Host not found: ', asset_id)
+
+ if self.system_user:
+ self.system_user.load_asset_special_auth(asset=asset, username=self.run_as)
+ return self.system_user._to_secret_json()
+
try:
- asset = self.assets.get(id=host.get('id'))
manager = AssetUserManager()
- run_user = manager.get_latest(username=self.run_as, asset=asset)
+ run_user = manager.get_latest(username=self.run_as, asset=asset, prefer='system_user')
+ return run_user._to_secret_json()
except Exception as e:
logger.error(e, exc_info=True)
return {}
- else:
- return run_user._to_secret_json()
class JMSCustomInventory(JMSBaseInventory):
diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py
index 36ebd77e0..95a1106b1 100644
--- a/apps/ops/models/adhoc.py
+++ b/apps/ops/models/adhoc.py
@@ -184,7 +184,7 @@ class AdHoc(OrgModelMixin):
hid = str(uuid.uuid4())
execution = AdHocExecution(
id=hid, adhoc=self, task=self.task,
- task_display=str(self.task),
+ task_display=str(self.task)[:128],
date_start=timezone.now(),
hosts_amount=self.hosts.count(),
)
diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py
index 408c72915..5bf808dce 100644
--- a/apps/ops/models/command.py
+++ b/apps/ops/models/command.py
@@ -37,7 +37,7 @@ class CommandExecution(OrgModelMixin):
username = self.user.username
else:
username = self.run_as.username
- inv = JMSInventory(self.hosts.all(), run_as=username)
+ inv = JMSInventory(self.hosts.all(), run_as=username, system_user=self.run_as)
return inv
@lazyproperty
@@ -78,7 +78,7 @@ class CommandExecution(OrgModelMixin):
runner = CommandRunner(self.inventory)
try:
host = self.hosts.first()
- if host.is_windows():
+ if host and host.is_windows():
shell = 'win_shell'
else:
shell = 'shell'
diff --git a/apps/templates/_base_only_content.html b/apps/templates/_base_only_content.html
index 2c757d344..9da6c0157 100644
--- a/apps/templates/_base_only_content.html
+++ b/apps/templates/_base_only_content.html
@@ -11,7 +11,7 @@
{% include '_head_css_js.html' %}
-
+