diff --git a/Dockerfile b/Dockerfile index 7e6eac422..2f65985b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,65 +1,77 @@ +FROM python:3.8-slim as stage-build +ARG TARGETARCH + +ARG VERSION +ENV VERSION=$VERSION + +WORKDIR /opt/jumpserver +ADD . . +RUN cd utils && bash -ixeu build.sh + FROM python:3.8-slim +ARG TARGETARCH MAINTAINER JumpServer Team ARG BUILD_DEPENDENCIES=" \ - g++ \ - make \ - pkg-config" + g++ \ + make \ + pkg-config" ARG DEPENDENCIES=" \ - default-libmysqlclient-dev \ - freetds-dev \ - libpq-dev \ - libffi-dev \ - libldap2-dev \ - libsasl2-dev \ - libxml2-dev \ - libxmlsec1-dev \ - libxmlsec1-openssl \ - libaio-dev \ - openssh-client \ - sshpass" + default-libmysqlclient-dev \ + freetds-dev \ + libpq-dev \ + libffi-dev \ + libjpeg-dev \ + libldap2-dev \ + libsasl2-dev \ + libxml2-dev \ + libxmlsec1-dev \ + libxmlsec1-openssl \ + libaio-dev \ + openssh-client \ + sshpass" ARG TOOLS=" \ - curl \ - default-mysql-client \ - iproute2 \ - iputils-ping \ - locales \ - procps \ - redis-tools \ - telnet \ - vim \ - unzip \ - wget" + ca-certificates \ + curl \ + default-mysql-client \ + iputils-ping \ + locales \ + procps \ + redis-tools \ + telnet \ + vim \ + unzip \ + wget" -RUN sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ +ARG APT_MIRROR=http://mirrors.ustc.edu.cn + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ + sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \ + && rm -f /etc/apt/apt.conf.d/docker-clean \ + && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ && apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ - && localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 \ - && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && mkdir -p /root/.ssh/ \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ && rm -rf /var/lib/apt/lists/* -ARG TARGETARCH -ARG ORACLE_LIB_MAJOR=19 -ARG ORACLE_LIB_MINOR=10 -ENV ORACLE_FILE="instantclient-basiclite-linux.${TARGETARCH:-amd64}-${ORACLE_LIB_MAJOR}.${ORACLE_LIB_MINOR}.0.0.0dbru.zip" +ARG DOWNLOAD_URL=https://download.jumpserver.org RUN mkdir -p /opt/oracle/ \ && cd /opt/oracle/ \ - && wget https://download.jumpserver.org/files/oracle/${ORACLE_FILE} \ - && unzip instantclient-basiclite-linux.${TARGETARCH-amd64}-19.10.0.0.0dbru.zip \ - && mv instantclient_${ORACLE_LIB_MAJOR}_${ORACLE_LIB_MINOR} instantclient \ - && echo "/opt/oracle/instantclient" > /etc/ld.so.conf.d/oracle-instantclient.conf \ + && wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \ + && unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \ + && sh -c "echo /opt/oracle/instantclient_19_10 > /etc/ld.so.conf.d/oracle-instantclient.conf" \ && ldconfig \ - && rm -f ${ORACLE_FILE} + && rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip WORKDIR /tmp/build COPY ./requirements ./requirements @@ -68,21 +80,18 @@ ARG PIP_MIRROR=https://pypi.douban.com/simple ENV PIP_MIRROR=$PIP_MIRROR ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR -# 因为以 jms 或者 jumpserver 开头的 mirror 上可能没有 -RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \ - && pip install --no-cache-dir $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ - && pip install --no-cache-dir -r requirements/requirements.txt -i ${PIP_MIRROR} \ - && rm -rf ~/.cache/pip -ARG VERSION -ENV VERSION=$VERSION +RUN --mount=type=cache,target=/root/.cache/pip \ + set -ex \ + && pip config set global.index-url ${PIP_MIRROR} \ + && pip install --upgrade pip \ + && pip install --upgrade setuptools wheel \ + && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ + && pip install -r requirements/requirements.txt -ADD . . -RUN cd utils \ - && bash -ixeu build.sh \ - && mv ../release/jumpserver /opt/jumpserver \ - && rm -rf /tmp/build \ - && echo > /opt/jumpserver/config.yml +COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver +RUN echo > /opt/jumpserver/config.yml \ + && rm -rf /tmp/build WORKDIR /opt/jumpserver VOLUME /opt/jumpserver/data diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index dedd90a3c..48f94f6f7 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -19,7 +19,6 @@ __all__ = [ 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodeChildrenAsTreeApi', - 'UserGroupGrantedAssetAccountsApi', ] @@ -191,17 +190,3 @@ class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIVie nodes = self.get_nodes() nodes = self.serialize_nodes(nodes) return Response(data=nodes) - - -class UserGroupGrantedAssetAccountsApi(uapi.UserGrantedAssetAccountsApi): - - @lazyproperty - def user_group(self): - group_id = self.kwargs.get('pk') - return UserGroup.objects.get(id=group_id) - - def get_queryset(self): - accounts = PermAccountUtil().get_perm_accounts_for_user_group_asset( - self.user_group, self.asset, with_actions=True - ) - return accounts diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index a67f5bd8f..96c09667f 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -1,7 +1,7 @@ from django.shortcuts import get_object_or_404 from rest_framework.generics import ListAPIView, get_object_or_404 -from common.utils import get_logger +from common.utils import get_logger, lazyproperty from perms import serializers from perms.hands import Asset from perms.utils import PermAccountUtil @@ -10,14 +10,14 @@ from .mixin import SelfOrPKUserMixin logger = get_logger(__name__) __all__ = [ - 'UserGrantedAssetAccountsApi', + 'UserPermedAssetAccountsApi', ] -class UserGrantedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): - serializer_class = serializers.AccountsGrantedSerializer +class UserPermedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): + serializer_class = serializers.AccountsPermedSerializer - @property + @lazyproperty def asset(self): asset_id = self.kwargs.get('asset_id') kwargs = {'id': asset_id, 'is_active': True} diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 510ed9f1a..9ff8ed0f1 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -2,8 +2,11 @@ # from django.shortcuts import get_object_or_404 from rest_framework.request import Request +from django.utils.translation import ugettext_lazy as _ from common.http import is_true +from common.utils import is_uuid +from common.exceptions import JMSObjectDoesNotExist from common.mixins.api import RoleAdminMixin, RoleUserMixin from perms.utils.user_permission import UserGrantedTreeRefreshController from rbac.permissions import RBACPermission @@ -43,6 +46,12 @@ class SelfOrPKUserMixin: request: Request permission_classes = (RBACPermission,) + def get_rbac_perms(self): + if self.request_user_is_self(): + return self.self_rbac_perms + else: + return self.admin_rbac_perms + @property def self_rbac_perms(self): return ( @@ -61,18 +70,15 @@ class SelfOrPKUserMixin: ('GET', 'perms.view_userassets'), ) - def get_rbac_perms(self): - if self.request_user_is_self(): - return self.self_rbac_perms - else: - return self.admin_rbac_perms - - def request_user_is_self(self): - return self.kwargs.get('user') in ['my', 'self'] - @property def user(self): if self.request_user_is_self(): - return self.request.user + user = self.request.user + elif is_uuid(self.kwargs.get('user')): + user = get_object_or_404(User, pk=self.kwargs.get('user')) else: - return get_object_or_404(User, pk=self.kwargs.get('user')) + raise JMSObjectDoesNotExist(object_name=_('User')) + return user + + def request_user_is_self(self): + return self.kwargs.get('user') in ['my', 'self'] diff --git a/apps/perms/const.py b/apps/perms/const.py index 3c8f9ee21..99495369c 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -6,15 +6,15 @@ from django.utils.translation import ugettext_lazy as _ from common.db.fields import BitChoices from common.utils.integer import bit -__all__ = ["SpecialAccount", "ActionChoices"] +__all__ = ["ActionChoices"] class ActionChoices(BitChoices): - connect = bit(1), _("Connect") - upload = bit(2), _("Upload") - download = bit(3), _("Download") - copy = bit(4), _("Copy") - paste = bit(5), _("Paste") + connect = bit(0), _("Connect") + upload = bit(1), _("Upload") + download = bit(2), _("Download") + copy = bit(3), _("Copy") + paste = bit(4), _("Paste") @classmethod def is_tree(cls): @@ -23,6 +23,7 @@ class ActionChoices(BitChoices): @classmethod def branches(cls): return ( + cls.connect, (_("Transfer"), [cls.upload, cls.download]), (_("Clipboard"), [cls.copy, cls.paste]), ) @@ -31,7 +32,3 @@ class ActionChoices(BitChoices): def has_perm(cls, action_name, total): action_value = getattr(cls, action_name) return action_value & total == action_value - - -class SpecialAccount(models.TextChoices): - ALL = "@ALL", "All" diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 47cb1e8e6..1b9f068b0 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -11,7 +11,7 @@ from common.db.models import UnionQuerySet from common.utils import date_expired_default from orgs.mixins.models import OrgManager from orgs.mixins.models import OrgModelMixin -from perms.const import ActionChoices, SpecialAccount +from perms.const import ActionChoices __all__ = ['AssetPermission', 'ActionChoices'] @@ -37,7 +37,7 @@ class AssetPermissionQuerySet(models.QuerySet): def filter_by_accounts(self, accounts): q = Q(accounts__contains=list(accounts)) | \ - Q(accounts__contains=SpecialAccount.ALL.value) + Q(accounts__contains=Account.AliasAccount.ALL.value) return self.filter(q) @@ -127,7 +127,7 @@ class AssetPermission(OrgModelMixin): """ asset_ids = self.get_all_assets(flat=True) q = Q(asset_id__in=asset_ids) - if SpecialAccount.ALL in self.accounts: + if Account.AliasAccount.ALL in self.accounts: q &= Q(username__in=self.accounts) accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username') if not flat: diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 6cee0e793..dc9fb475f 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -11,7 +11,7 @@ from perms.serializers.permission import ActionChoicesField __all__ = [ 'NodeGrantedSerializer', 'AssetGrantedSerializer', - 'ActionsSerializer', 'AccountsGrantedSerializer' + 'ActionsSerializer', 'AccountsPermedSerializer' ] @@ -48,7 +48,7 @@ class ActionsSerializer(serializers.Serializer): actions = ActionChoicesField(read_only=True) -class AccountsGrantedSerializer(serializers.ModelSerializer): +class AccountsPermedSerializer(serializers.ModelSerializer): actions = ActionChoicesField(read_only=True) class Meta: diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py index 7b5f66897..c7413dbbc 100644 --- a/apps/perms/urls/user_permission.py +++ b/apps/perms/urls/user_permission.py @@ -55,8 +55,9 @@ user_permission_urlpatterns = [ name='my-ungrouped-assets'), # 获取授权给用户某个资产的所有账号 - path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), - name='user-asset-accounts'), + # user params: ['my', 'self'] or user.id + path('/assets//accounts/', api.UserPermedAssetAccountsApi.as_view(), + name='user-permed-asset-accounts'), ] user_group_permission_urlpatterns = [ @@ -68,10 +69,6 @@ user_group_permission_urlpatterns = [ name='user-group-nodes-children-as-tree'), path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), - - # 获取所有和资产-用户组关联的账号列表 - path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), - name='user-group-asset-accounts'), ] user_permission_urlpatterns = [ diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index baaedb8fc..a9c2bc279 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -9,8 +9,28 @@ __all__ = ['PermAccountUtil'] class PermAccountUtil(AssetPermissionUtil): """ 资产授权账号相关的工具 """ + def validate_permission(self, user, asset, account_username): + """ 校验用户有某个资产下某个账号名的权限 + :param user: User + :param asset: Asset + :param account_username: 可能是 @USER @INPUT 字符串 + """ + permed_accounts = self.get_permed_accounts_for_user(user, asset) + accounts_mapper = {account.username: account for account in permed_accounts} + + account = accounts_mapper.get(account_username) + actions, date_expired = (account.actions, account.date_expired) if account else (False, None) + return actions, date_expired + + def get_permed_accounts_for_user(self, user, asset): + """ 获取授权给用户某个资产的账号 """ + perms = self.get_permissions_for_user_asset(user, asset) + permed_accounts = self.get_permed_accounts_from_perms(perms, user, asset) + return permed_accounts + @staticmethod def get_permed_accounts_from_perms(perms, user, asset): + # alias: is a collection of account usernames and special accounts [@ALL, @INPUT, @USER] alias_action_bit_mapper = defaultdict(int) alias_expired_mapper = defaultdict(list) @@ -21,23 +41,26 @@ class PermAccountUtil(AssetPermissionUtil): asset_accounts = asset.accounts.all() username_account_mapper = {account.username: account for account in asset_accounts} + cleaned_accounts_action_bit = defaultdict(int) cleaned_accounts_expired = defaultdict(list) # @ALL 账号先处理,后面的每个最多映射一个账号 - all_action_bit = alias_action_bit_mapper.pop('@ALL', None) + all_action_bit = alias_action_bit_mapper.pop(Account.AliasAccount.ALL, None) if all_action_bit: for account in asset_accounts: cleaned_accounts_action_bit[account] |= all_action_bit - cleaned_accounts_expired[account].extend(alias_expired_mapper['@ALL']) + cleaned_accounts_expired[account].extend( + alias_expired_mapper[Account.AliasAccount.ALL] + ) for alias, action_bit in alias_action_bit_mapper.items(): - if alias == '@USER': + if alias == Account.AliasAccount.USER: if user.username in username_account_mapper: account = username_account_mapper[user.username] else: account = Account.get_user_account(user.username) - elif alias == '@INPUT': + elif alias == Account.AliasAccount.INPUT: account = Account.get_manual_account() elif alias in username_account_mapper: account = username_account_mapper[alias] @@ -54,35 +77,3 @@ class PermAccountUtil(AssetPermissionUtil): account.date_expired = max(cleaned_accounts_expired[account]) accounts.append(account) return accounts - - def get_permed_accounts_for_user(self, user, asset): - """ 获取授权给用户某个资产的账号 """ - perms = self.get_permissions_for_user_asset(user, asset) - permed_accounts = self.get_permed_accounts_from_perms(perms, user, asset) - return permed_accounts - - @staticmethod - def get_accounts_for_permission(perm, with_actions=False): - """ 获取授权规则包含的账号 """ - aid_actions_map = defaultdict(int) - # 这里不行,速度太慢, 别情有很多查询 - account_ids = perm.get_all_accounts(flat=True) - actions = perm.actions - for aid in account_ids: - aid_actions_map[str(aid)] |= actions - account_ids = list(aid_actions_map.keys()) - accounts = Account.objects.filter(id__in=account_ids) - return accounts - - def validate_permission(self, user, asset, account_username): - """ 校验用户有某个资产下某个账号名的权限 - :param account_username: 可能是 @USER @INPUT 的 - """ - permed_accounts = self.get_permed_accounts_for_user(user, asset) - accounts_mapper = {account.username: account for account in permed_accounts} - - account = accounts_mapper.get(account_username) - if not account: - return False, None - else: - return account.actions, account.date_expired diff --git a/entrypoint.sh b/entrypoint.sh index fe81b1470..58ed0b104 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -11,6 +11,9 @@ action="${1-start}" service="${2-all}" trap cleanup EXIT + +rm -f /opt/jumpserver/tmp/*.pid + if [[ "$action" == "bash" || "$action" == "sh" ]];then bash elif [[ "$action" == "sleep" ]];then @@ -19,4 +22,3 @@ elif [[ "$action" == "sleep" ]];then else python jms "${action}" "${service}" fi -