diff --git a/README.md b/README.md index 37a703847..595677321 100644 --- a/README.md +++ b/README.md @@ -17,157 +17,156 @@ Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向 ## 核心功能列表 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
身份验证 Authentication登录认证 - 资源统一登录和认证 -
LDAP 认证 -
支持 OpenID,实现单点登录 -
多因子认证 - MFA(Google Authenticator) -
账号管理 Account集中账号管理 - 管理用户管理 -
系统用户管理 -
统一密码管理 - 资产密码托管 -
自动生成密码 -
密码自动推送 -
密码过期设置 -
批量密码变更(X-PACK) - 定期批量修改密码 -
生成随机密码 -
多云环境的资产纳管(X-PACK) - 对私有云、公有云资产统一纳管 -
授权控制 Authorization资产授权管理 - 资产树 -
资产或资产组灵活授权 -
节点内资产自动继承授权 -
RemoteApp(X-PACK) - 实现更细粒度的应用级授权 -
组织管理(X-PACK) - 实现多租户管理,权限隔离 -
多维度授权 - 可对用户、用户组或系统角色授权 -
指令限制 - 限制特权指令使用,支持黑白名单 -
统一文件传输 - SFTP 文件上传/下载 -
文件管理 - Web SFTP 文件管理 -
安全审计 Audit会话管理 - 在线会话管理 -
历史会话管理 -
录像管理 - Linux 录像支持 -
Windows 录像支持 -
指令审计 - 指令记录 -
文件传输审计 - 上传/下载记录审计 -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
身份认证
Authentication
登录认证资源统一登录与认证
LDAP/AD 认证
RADIUS 认证
OpenID 认证(实现单点登录)
MFA认证MFA 二次认证(Google Authenticator)
RADIUS 二次认证
登录复核(X-PACK)用户登录行为受管理员的监管与控制
账号管理
Account
集中账号管理用户管理
系统用户管理
统一密码资产密码托管
自动生成密码
自动推送密码
密码过期设置
批量改密(X-PACK)定期批量改密
多种密码策略
多云纳管(X-PACK)对私有云、公有云资产自动统一纳管
收集用户(X-PACK)自定义任务定期收集主机用户
密码匣子(X-PACK)统一对资产主机的用户密码进行查看、更新、测试操作
授权控制
Authorization
多维授权对用户、用户组、资产、资产节点、应用以及系统用户进行授权
资产授权资产以树状结构进行展示
资产和节点均可灵活授权
节点内资产自动继承授权
子节点自动继承父节点授权
应用授权实现更细粒度的应用级授权
MySQL 数据库应用、RemoteApp 远程应用(X-PACK)
动作授权实现对授权资产的文件上传、下载以及连接动作的控制
时间授权实现对授权资源使用时间段的限制
特权指令实现对特权指令的使用(支持黑白名单)
命令过滤实现对授权系统用户所执行的命令进行控制
文件传输SFTP 文件上传/下载
文件管理实现 Web SFTP 文件管理
工单管理(X-PACK)支持对用户登录请求行为进行控制
组织管理(X-PACK)实现多租户管理与权限隔离
安全审计
Audit
操作审计用户操作行为审计
会话审计在线会话内容审计
历史会话内容审计
录像审计支持对 Linux、Windows 等资产操作的录像进行回放审计
支持对 RemoteApp(X-PACK)、MySQL 等应用操作的录像进行回放审计
指令审计支持对资产和应用等操作的命令进行审计
文件传输可对文件的上传、下载记录进行审计
## 安装及使用指南 diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 594da38b7..3b23de31b 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -10,6 +10,7 @@ from django.dispatch import receiver from common.utils import get_logger, timeit from common.decorator import on_transaction_commit from .models import Asset, SystemUser, Node, AuthBook +from .utils import TreeService from .tasks import ( update_assets_hardware_info_util, test_asset_connectivity_util, @@ -131,16 +132,22 @@ def on_asset_nodes_add(sender, instance=None, action='', model=None, pk_set=None if action != "post_add": return logger.debug("Assets node add signal recv: {}".format(action)) - queryset = model.objects.filter(pk__in=pk_set).values_list('id', flat=True) + queryset = model.objects.filter(pk__in=pk_set).values_list('key', flat=True) if model == Node: nodes = queryset assets = [instance] else: nodes = [instance] assets = queryset - # 节点资产发生变化时,将资产关联到节点关联的系统用户, 只关注新增的 + # 节点资产发生变化时,将资产关联到节点及祖先节点关联的系统用户, 只关注新增的 + nodes_ancestors_keys = set() + node_tree = TreeService.new() + for node in nodes: + ancestors_keys = node_tree.ancestors_ids(nid=node) + nodes_ancestors_keys.update(ancestors_keys) + system_users = SystemUser.objects.filter(nodes__key__in=nodes_ancestors_keys) + system_users_assets = defaultdict(set) - system_users = SystemUser.objects.filter(nodes__in=nodes) for system_user in system_users: system_users_assets[system_user].update(set(assets)) for system_user, _assets in system_users_assets.items(): diff --git a/apps/assets/utils.py b/apps/assets/utils.py index e0b316ad7..eaf3d502a 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -84,11 +84,15 @@ class TreeService(Tree): children_ids = self.all_children_ids(nid, with_self=with_self) return [self.get_node(i, deep=deep) for i in children_ids] - def ancestors(self, nid, with_self=False, deep=False): + def ancestors_ids(self, nid, with_self=True): ancestor_ids = list(self.rsearch(nid)) ancestor_ids.pop() if not with_self: ancestor_ids.pop(0) + return ancestor_ids + + def ancestors(self, nid, with_self=False, deep=False): + ancestor_ids = self.ancestors_ids(nid, with_self=with_self) return [self.get_node(i, deep=deep) for i in ancestor_ids] def get_node_full_tag(self, nid): diff --git a/apps/authentication/backends/ldap.py b/apps/authentication/backends/ldap.py index bd1ce68b8..e01803707 100644 --- a/apps/authentication/backends/ldap.py +++ b/apps/authentication/backends/ldap.py @@ -27,13 +27,16 @@ class LDAPAuthorizationBackend(LDAPBackend): is_valid = getattr(user, 'is_valid', None) return is_valid or is_valid is None - def pre_check(self, username): + def pre_check(self, username, password): if not settings.AUTH_LDAP: return False logger.info('Authentication LDAP backend') if not username: logger.info('Authenticate failed: username is None') return False + if not password: + logger.info('Authenticate failed: password is None') + return False if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS: user_model = self.get_user_model() exist = user_model.objects.filter(username=username).exists() @@ -44,7 +47,7 @@ class LDAPAuthorizationBackend(LDAPBackend): return True def authenticate(self, request=None, username=None, password=None, **kwargs): - match = self.pre_check(username) + match = self.pre_check(username, password) if not match: return None ldap_user = LDAPUser(self, username=username.strip(), request=request) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index b412c1624..4d19d007c 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -35,6 +35,7 @@ REPLAY_STORAGE_TYPE_SWIFT_FIELDS = [ {'name': 'SECRET_KEY', 'write_only': True}, {'name': 'REGION'}, {'name': 'ENDPOINT'}, + {'name': 'PROTOCOL'}, ] REPLAY_STORAGE_TYPE_OSS_FIELDS = [ {'name': 'BUCKET'}, diff --git a/apps/terminal/forms/storage.py b/apps/terminal/forms/storage.py index 28092380e..a94375e81 100644 --- a/apps/terminal/forms/storage.py +++ b/apps/terminal/forms/storage.py @@ -145,6 +145,12 @@ class ReplayStorageSwiftForm(BaseReplayStorageForm): swift_endpoint = forms.CharField( max_length=128, label=_('Endpoint'), required=False, ) + swift_protocol = forms.ChoiceField( + choices=( + ('HTTP', 'http'), + ('HTTPS', 'https') + ), initial='http', label=_('Protocol'), required=True, + ) class CommandStorageTypeESForm(BaseCommandStorageForm): diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 6ad820563..04bc74c67 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -362,18 +362,23 @@ class ReplayStorage(CommonModelMixin): return self.name def convert_type(self): - s3_type_list = [ - const.REPLAY_STORAGE_TYPE_CEPH, const.REPLAY_STORAGE_TYPE_SWIFT - ] + s3_type_list = [const.REPLAY_STORAGE_TYPE_CEPH] tp = self.type if tp in s3_type_list: tp = const.REPLAY_STORAGE_TYPE_S3 return tp + def get_extra_config(self): + extra_config = {'TYPE': self.convert_type()} + if self.type == const.REPLAY_STORAGE_TYPE_SWIFT: + extra_config.update({'signer': 'S3SignerType'}) + return extra_config + @property def config(self): config = self.meta - config.update({'TYPE': self.convert_type()}) + extra_config = self.get_extra_config() + config.update(extra_config) return config def in_defaults(self):