未验证 提交 07634042 编写于 作者: J Jiangjie.Bai 提交者: GitHub

Merge pull request #5021 from jumpserver/dev

Dev
......@@ -25,7 +25,8 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向
- 无插件: 仅需浏览器,极致的 Web Terminal 使用体验;
- 多云支持: 一套系统,同时管理不同云上面的资产;
- 云端存储: 审计录像云端存储,永不丢失;
- 多租户: 一套系统,多个子公司和部门同时使用。
- 多租户: 一套系统,多个子公司和部门同时使用;
- 多应用支持: 数据库,Windows远程应用,Kubernetes。
## 版本说明
......@@ -198,6 +199,54 @@ v2.1.0 是 v2.0.0 之后的功能版本。
<td>文件传输</td>
<td>可对文件的上传、下载记录进行审计</td>
</tr>
<tr>
<td rowspan="20">数据库审计<br>Database</td>
<td rowspan="2">连接方式</td>
<td>命令方式</td>
</tr>
<tr>
<td>Web UI方式 (X-PACK)</td>
</tr>
<tr>
<td rowspan="4">支持的数据库</td>
<td>MySQL</td>
</tr>
<tr>
<td>Oracle (X-PACK)</td>
</tr>
<tr>
<td>MariaDB (X-PACK)</td>
</tr>
<tr>
<td>PostgreSQL (X-PACK)</td>
</tr>
<tr>
<td rowspan="6">功能亮点</td>
<td>语法高亮</td>
</tr>
<tr>
<td>SQL格式化</td>
</tr>
<tr>
<td>支持快捷键</td>
</tr>
<tr>
<td>支持选中执行</td>
</tr>
<tr>
<td>SQL历史查询</td>
</tr>
<tr>
<td>支持页面创建 DB, TABLE</td>
</tr>
<tr>
<td rowspan="2">会话审计</td>
<td>命令记录</td>
</tr>
<tr>
<td>录像回放</td>
</tr>
</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)
> 注:企业版支持离线安装,申请通过后会提供高速下载链接。
......
# 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'},
),
]
......@@ -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()
......
......@@ -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
......
......@@ -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
......
......@@ -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])
......
......@@ -189,7 +189,7 @@ class Crypto:
if origin_text:
# 有时不同算法解密不报错,但是返回空字符串
return origin_text
except (TypeError, ValueError, UnicodeDecodeError):
except (TypeError, ValueError, UnicodeDecodeError, IndexError):
continue
......
......@@ -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'
......
......@@ -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):
......
......@@ -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(
......
......@@ -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):
......
......@@ -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(),
)
......
......@@ -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'
......
......@@ -11,7 +11,7 @@
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script>
<style>
.passwordBox {
......
......@@ -13,5 +13,7 @@
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script>
<script src="{% url 'javascript-catalog' %}"></script>
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
# Generated by Django 2.2.13 on 2020-11-16 09:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0028_auto_20201110_1918'),
]
operations = [
migrations.AlterField(
model_name='commandstorage',
name='name',
field=models.CharField(max_length=128, unique=True, verbose_name='Name'),
),
migrations.AlterField(
model_name='replaystorage',
name='name',
field=models.CharField(max_length=128, unique=True, verbose_name='Name'),
),
]
......@@ -401,7 +401,7 @@ class CommandStorage(CommonModelMixin):
TYPE_DEFAULTS = dict(const.REPLAY_STORAGE_TYPE_CHOICES_DEFAULT).keys()
TYPE_SERVER = const.COMMAND_STORAGE_TYPE_SERVER
name = models.CharField(max_length=32, verbose_name=_("Name"), unique=True)
name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True)
type = models.CharField(
max_length=16, choices=TYPE_CHOICES, verbose_name=_('Type'),
default=TYPE_SERVER
......@@ -438,7 +438,7 @@ class ReplayStorage(CommonModelMixin):
TYPE_SERVER = const.REPLAY_STORAGE_TYPE_SERVER
TYPE_DEFAULTS = dict(const.REPLAY_STORAGE_TYPE_CHOICES_DEFAULT).keys()
name = models.CharField(max_length=32, verbose_name=_("Name"), unique=True)
name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True)
type = models.CharField(
max_length=16, choices=TYPE_CHOICES, verbose_name=_('Type'),
default=TYPE_SERVER
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册