Compare commits

..

29 Commits

Author SHA1 Message Date
fit2bot
4f34ce5edb feat: Update v3.10.7 2024-03-26 11:42:08 +08:00
Bai
c49c394b5b perf: Update poetry.lock 2024-03-25 14:23:32 +08:00
wangruidong
a0f27a9476 fix: 修复 redis lock 导致 celery 异步任务卡住不执行的问题 2024-03-25 14:17:11 +08:00
Bai
5af6f89229 perf: Update poetry.lock 2024-03-22 17:24:21 +08:00
ibuler
adbd73182b fix: ansible playbook render and run in localhost 2024-03-22 17:05:56 +08:00
wangruidong
eb9f261459 fix: 作业命令用户隔离执行 2024-03-19 11:26:20 +08:00
wangruidong
c4d99ed8e2 fix: Another user can use this job id to spoof both the file name and
its contents
2024-03-18 14:21:46 +08:00
Bai
8d01c189f0 fix: 修复 Playbook 脚本文件问题 2024-03-14 11:40:42 +08:00
Bryan
a2701090de fix: 修复连接 Token 时报错的问题((1139, "Got error empty (sub)expression from regexp")) (#12769) 2024-03-07 12:37:12 +08:00
wangruidong
4582aa0a09 fix: 个别页面搜索不生效的问题 2024-03-05 11:19:58 +08:00
wangruidong
c7c379479f fix: 个别页面搜索不生效的问题 2024-03-05 11:19:58 +08:00
wangruidong
ce4479c23e fix: 控制台-仪表盘会话用户,资产排名不对 2024-03-04 11:13:46 +08:00
Bryan
eedc2f1b41 Merge pull request #12734 from jumpserver/master
v3.10.4 (branch-v3.10)
2024-02-29 16:39:55 +08:00
Bryan
7ba24293d1 Merge pull request #12736 from jumpserver/pr@dev@master_fix
fix: 解决冲突
2024-02-29 16:38:43 +08:00
Bai
f10114c9ed fix: 解决冲突 2024-02-29 16:37:10 +08:00
Bryan
cf31cbfb07 Merge pull request #12729 from jumpserver/dev
v3.10.4
2024-02-29 16:19:59 +08:00
wangruidong
0edad24d5d fix: 资产过期消息提示发送失败 2024-02-04 11:41:48 +08:00
ibuler
1f1c1a9157 fix: 修复定时检测用户是否活跃任务无法执行的问题 2024-01-23 09:28:38 +00:00
feng
6c9d271ae1 fix: redis 密码有特殊字符celery beat启动失败 2024-01-22 06:18:34 +00:00
Bai
6ff852e225 perf: 修复 Count 时没有去重的问题 2024-01-22 06:16:25 +00:00
Bryan
baa75dc735 Merge pull request #12566 from jumpserver/master
v3.10.2
2024-01-17 07:34:28 -04:00
Bryan
8a9f0436b8 Merge pull request #12565 from jumpserver/dev
v3.10.2
2024-01-17 07:23:30 -04:00
Bryan
a9620a3cbe Merge pull request #12461 from jumpserver/master
v3.10.1
2023-12-29 11:33:05 +05:00
Bryan
769e7dc8a0 Merge pull request #12460 from jumpserver/dev
v3.10.1
2023-12-29 11:20:36 +05:00
Bryan
2a70449411 Merge pull request #12458 from jumpserver/dev
v3.10.1
2023-12-29 11:01:13 +05:00
Bryan
8df720f19e Merge pull request #12401 from jumpserver/dev
v3.10
2023-12-21 15:14:19 +05:00
老广
dabbb45f6e Merge pull request #12144 from jumpserver/dev
v3.9.0
2023-11-16 18:23:05 +08:00
Bryan
ce24c1c3fd Merge pull request #11914 from jumpserver/dev
v3.8.0
2023-10-19 03:37:39 -05:00
Bryan
3c54c82ce9 Merge pull request #11636 from jumpserver/dev
v3.7.0
2023-09-21 17:02:48 +08:00
407 changed files with 5779 additions and 29425 deletions

11
.github/ISSUE_TEMPLATE/----.md vendored Normal file
View File

@@ -0,0 +1,11 @@
---
name: 需求建议
about: 提出针对本项目的想法和建议
title: "[Feature] "
labels: 类型:需求
assignees:
- ibuler
- baijiangjie
---
**请描述您的需求或者改进建议.**

View File

@@ -1,72 +0,0 @@
name: '🐛 Bug Report'
description: 'Report an Bug'
title: '[Bug] '
labels: ['🐛 Bug']
assignees:
- baijiangjie
body:
- type: input
attributes:
label: 'Product Version'
description: The versions prior to v2.28 (inclusive) are no longer supported.
validations:
required: true
- type: checkboxes
attributes:
label: 'Product Edition'
options:
- label: 'Community Edition'
- label: 'Enterprise Edition'
- label: 'Enterprise Trial Edition'
validations:
required: true
- type: checkboxes
attributes:
label: 'Installation Method'
options:
- label: 'Online Installation (One-click command installation)'
- label: 'Offline Package Installation'
- label: 'All-in-One'
- label: '1Panel'
- label: 'Kubernetes'
- label: 'Source Code'
- type: textarea
attributes:
label: 'Environment Information'
description: Please provide a clear and concise description outlining your environment information.
validations:
required: true
- type: textarea
attributes:
label: '🐛 Bug Description'
description:
Please provide a clear and concise description of the defect. If the issue is complex, please provide detailed explanations. <br/>
Unclear descriptions will not be processed. Please ensure you provide enough detail and information to support replicating and fixing the defect.
validations:
required: true
- type: textarea
attributes:
label: 'Recurrence Steps'
description: Please provide a clear and concise description outlining how to reproduce the issue.
validations:
required: true
- type: textarea
attributes:
label: 'Expected Behavior'
description: Please provide a clear and concise description of what you expect to happen.
- type: textarea
attributes:
label: 'Additional Information'
description: Please add any additional background information about the issue here.
- type: textarea
attributes:
label: 'Attempted Solutions'
description: If you have already attempted to solve the issue, please list the solutions you have tried here.

View File

@@ -1,72 +0,0 @@
name: '🐛 反馈缺陷'
description: '反馈一个缺陷'
title: '[Bug] '
labels: ['🐛 Bug']
assignees:
- baijiangjie
body:
- type: input
attributes:
label: '产品版本'
description: 不再支持 v2.28(含)之前的版本。
validations:
required: true
- type: checkboxes
attributes:
label: '版本类型'
options:
- label: '社区版'
- label: '企业版'
- label: '企业试用版'
validations:
required: true
- type: checkboxes
attributes:
label: '安装方式'
options:
- label: '在线安装 (一键命令安装)'
- label: '离线包安装'
- label: 'All-in-One'
- label: '1Panel'
- label: 'Kubernetes'
- label: '源码安装'
- type: textarea
attributes:
label: '环境信息'
description: 请提供一个清晰且简洁的描述,说明你的环境信息。
validations:
required: true
- type: textarea
attributes:
label: '🐛 缺陷描述'
description: |
请提供一个清晰且简洁的缺陷描述,如果问题比较复杂,也请详细说明。<br/>
针对不清晰的描述信息将不予处理。请确保提供足够的细节和信息,以支持对缺陷进行复现和修复。
validations:
required: true
- type: textarea
attributes:
label: '复现步骤'
description: 请提供一个清晰且简洁的描述,说明如何复现问题。
validations:
required: true
- type: textarea
attributes:
label: '期望结果'
description: 请提供一个清晰且简洁的描述,说明你期望发生什么。
- type: textarea
attributes:
label: '补充信息'
description: 在这里添加关于问题的任何其他背景信息。
- type: textarea
attributes:
label: '尝试过的解决方案'
description: 如果你已经尝试解决问题,请在此列出你尝试过的解决方案。

View File

@@ -1,56 +0,0 @@
name: '⭐️ Feature Request'
description: 'Suggest an idea'
title: '[Feature] '
labels: ['⭐️ Feature Request']
assignees:
- baijiangjie
- ibuler
body:
- type: input
attributes:
label: 'Product Version'
description: The versions prior to v2.28 (inclusive) are no longer supported.
validations:
required: true
- type: checkboxes
attributes:
label: 'Product Edition'
options:
- label: 'Community Edition'
- label: 'Enterprise Edition'
- label: 'Enterprise Trial Edition'
validations:
required: true
- type: checkboxes
attributes:
label: 'Installation Method'
options:
- label: 'Online Installation (One-click command installation)'
- label: 'Offline Package Installation'
- label: 'All-in-One'
- label: '1Panel'
- label: 'Kubernetes'
- label: 'Source Code'
- type: textarea
attributes:
label: '⭐️ Feature Description'
description: |
Please add a clear and concise description of the problem you aim to solve with this feature request.<br/>
Unclear descriptions will not be processed.
validations:
required: true
- type: textarea
attributes:
label: 'Proposed Solution'
description: Please provide a clear and concise description of the solution you desire.
validations:
required: true
- type: textarea
attributes:
label: 'Additional Information'
description: Please add any additional background information about the issue here.

View File

@@ -1,56 +0,0 @@
name: '⭐️ 功能需求'
description: '提出需求或建议'
title: '[Feature] '
labels: ['⭐️ Feature Request']
assignees:
- baijiangjie
- ibuler
body:
- type: input
attributes:
label: '产品版本'
description: 不再支持 v2.28(含)之前的版本。
validations:
required: true
- type: checkboxes
attributes:
label: '版本类型'
options:
- label: '社区版'
- label: '企业版'
- label: '企业试用版'
validations:
required: true
- type: checkboxes
attributes:
label: '安装方式'
options:
- label: '在线安装 (一键命令安装)'
- label: '离线包安装'
- label: 'All-in-One'
- label: '1Panel'
- label: 'Kubernetes'
- label: '源码安装'
- type: textarea
attributes:
label: '⭐️ 需求描述'
description: |
请添加一个清晰且简洁的问题描述,阐述你希望通过这个功能需求解决的问题。<br/>
针对不清晰的描述信息将不予处理。
validations:
required: true
- type: textarea
attributes:
label: '解决方案'
description: 请清晰且简洁地描述你想要的解决方案。
validations:
required: true
- type: textarea
attributes:
label: '补充信息'
description: 在这里添加关于问题的任何其他背景信息。

View File

@@ -1,60 +0,0 @@
name: '🤔 Question'
description: 'Pose a question'
title: '[Question] '
labels: ['🤔 Question']
assignees:
- baijiangjie
body:
- type: input
attributes:
label: 'Product Version'
description: The versions prior to v2.28 (inclusive) are no longer supported.
validations:
required: true
- type: checkboxes
attributes:
label: 'Product Edition'
options:
- label: 'Community Edition'
- label: 'Enterprise Edition'
- label: 'Enterprise Trial Edition'
validations:
required: true
- type: checkboxes
attributes:
label: 'Installation Method'
options:
- label: 'Online Installation (One-click command installation)'
- label: 'Offline Package Installation'
- label: 'All-in-One'
- label: '1Panel'
- label: 'Kubernetes'
- label: 'Source Code'
- type: textarea
attributes:
label: 'Environment Information'
description: Please provide a clear and concise description outlining your environment information.
validations:
required: true
- type: textarea
attributes:
label: '🤔 Question Description'
description: |
Please provide a clear and concise description of the defect. If the issue is complex, please provide detailed explanations. <br/>
Unclear descriptions will not be processed.
validations:
required: true
- type: textarea
attributes:
label: 'Expected Behavior'
description: Please provide a clear and concise description of what you expect to happen.
- type: textarea
attributes:
label: 'Additional Information'
description: Please add any additional background information about the issue here.

View File

@@ -1,61 +0,0 @@
name: '🤔 问题咨询'
description: '提出一个问题'
title: '[Question] '
labels: ['🤔 Question']
assignees:
- baijiangjie
body:
- type: input
attributes:
label: '产品版本'
description: 不再支持 v2.28(含)之前的版本。
validations:
required: true
- type: checkboxes
attributes:
label: '版本类型'
options:
- label: '社区版'
- label: '企业版'
- label: '企业试用版'
validations:
required: true
- type: checkboxes
attributes:
label: '安装方式'
options:
- label: '在线安装 (一键命令安装)'
- label: '离线包安装'
- label: 'All-in-One'
- label: '1Panel'
- label: 'Kubernetes'
- label: '源码安装'
- type: textarea
attributes:
label: '环境信息'
description: 请在此详细描述你的环境信息,如操作系统、浏览器和部署架构等。
validations:
required: true
- type: textarea
attributes:
label: '🤔 问题描述'
description: |
请提供一个清晰且简洁的问题描述,如果问题比较复杂,也请详细说明。<br/>
针对不清晰的描述信息将不予处理。
validations:
required: true
- type: textarea
attributes:
label: '期望结果'
description: 请提供一个清晰且简洁的描述,说明你期望发生什么。
- type: textarea
attributes:
label: '补充信息'
description: 在这里添加关于问题的任何其他背景信息。

22
.github/ISSUE_TEMPLATE/bug---.md vendored Normal file
View File

@@ -0,0 +1,22 @@
---
name: Bug 提交
about: 提交产品缺陷帮助我们更好的改进
title: "[Bug] "
labels: 类型:Bug
assignees:
- baijiangjie
---
**JumpServer 版本( v2.28 之前的版本不再支持 )**
**浏览器版本**
**Bug 描述**
**Bug 重现步骤(有截图更好)**
1.
2.
3.

10
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: 问题咨询
about: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[Question] "
labels: 类型:提问
assignees:
- baijiangjie
---
**请描述您的问题.**

View File

@@ -12,9 +12,7 @@ jobs:
uses: actions-cool/issues-helper@v2
with:
actions: 'close-issues'
labels: '⏳ Pending feedback'
labels: '状态:待反馈'
inactive-day: 30
body: |
You haven't provided feedback for over 30 days.
We will close this issue. If you have any further needs, you can reopen it or submit a new issue.
您超过 30 天未反馈信息,我们将关闭该 issue如有需求您可以重新打开或者提交新的 issue。

View File

@@ -13,4 +13,4 @@ jobs:
if: ${{ !github.event.issue.pull_request }}
with:
actions: 'remove-labels'
labels: '🔔 Pending processing,⏳ Pending feedback'
labels: '状态:待处理,状态:待反馈'

View File

@@ -13,13 +13,13 @@ jobs:
uses: actions-cool/issues-helper@v2
with:
actions: 'add-labels'
labels: '🔔 Pending processing'
labels: '状态:待处理'
- name: Remove require reply label
uses: actions-cool/issues-helper@v2
with:
actions: 'remove-labels'
labels: '⏳ Pending feedback'
labels: '状态:待反馈'
add-label-if-is-member:
runs-on: ubuntu-latest
@@ -55,11 +55,11 @@ jobs:
uses: actions-cool/issues-helper@v2
with:
actions: 'add-labels'
labels: '⏳ Pending feedback'
labels: '状态:待反馈'
- name: Remove require handle label
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
uses: actions-cool/issues-helper@v2
with:
actions: 'remove-labels'
labels: '🔔 Pending processing'
labels: '状态:待处理'

View File

@@ -13,4 +13,4 @@ jobs:
if: ${{ !github.event.issue.pull_request }}
with:
actions: 'add-labels'
labels: '🔔 Pending processing'
labels: '状态:待处理'

View File

@@ -1,32 +1,26 @@
name: "Run Build Test"
on:
push:
paths:
- 'Dockerfile'
- 'Dockerfile-*'
- 'pyproject.toml'
- 'poetry.lock'
branches:
- pr@*
- repr@*
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Check Dockerfile
run: |
test -f Dockerfile-ce || cp -f Dockerfile Dockerfile-ce
- uses: docker/setup-qemu-action@v2
- name: Build CE Image
uses: docker/build-push-action@v5
- uses: docker/setup-buildx-action@v2
- uses: docker/build-push-action@v3
with:
context: .
push: false
file: Dockerfile-ce
tags: jumpserver/core-ce:test
platforms: linux/amd64
file: Dockerfile-ce
build-args: |
APT_MIRROR=http://deb.debian.org
PIP_MIRROR=https://pypi.org/simple
@@ -34,22 +28,9 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Prepare EE Image
run: |
sed -i 's@^FROM registry.fit2cloud.com@# FROM registry.fit2cloud.com@g' Dockerfile-ee
sed -i 's@^COPY --from=build-xpack@# COPY --from=build-xpack@g' Dockerfile-ee
- name: Build EE Image
uses: docker/build-push-action@v5
- uses: LouisBrunner/checks-action@v1.5.0
if: always()
with:
context: .
push: false
file: Dockerfile-ee
tags: jumpserver/core-ee:test
platforms: linux/amd64
build-args: |
APT_MIRROR=http://deb.debian.org
PIP_MIRROR=https://pypi.org/simple
PIP_JMS_MIRROR=https://pypi.org/simple
cache-from: type=gha
cache-to: type=gha,mode=max
token: ${{ secrets.GITHUB_TOKEN }}
name: Check Build
conclusion: ${{ job.status }}

View File

@@ -10,4 +10,3 @@ jobs:
- uses: jumpserver/action-generic-handler@master
env:
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
I18N_TOKEN: ${{ secrets.I18N_TOKEN }}

1
.gitignore vendored
View File

@@ -43,4 +43,3 @@ releashe
data/*
test.py
.history/
.test/

View File

@@ -1,137 +0,0 @@
FROM python:3.11-slim-bullseye AS stage-1
ARG TARGETARCH
ARG VERSION
ENV VERSION=$VERSION
WORKDIR /opt/jumpserver
ADD . .
RUN echo > /opt/jumpserver/config.yml \
&& cd utils && bash -ixeu build.sh
FROM python:3.11-slim-bullseye as stage-2
ARG TARGETARCH
ARG BUILD_DEPENDENCIES=" \
g++ \
make \
pkg-config"
ARG DEPENDENCIES=" \
freetds-dev \
libffi-dev \
libjpeg-dev \
libkrb5-dev \
libldap2-dev \
libpq-dev \
libsasl2-dev \
libssl-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
freerdp2-dev \
libaio-dev"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
git \
git-lfs \
unzip \
xz-utils \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
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} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
RUN --mount=type=cache,target=/root/.cache \
--mount=type=bind,source=poetry.lock,target=/opt/jumpserver/poetry.lock \
--mount=type=bind,source=pyproject.toml,target=/opt/jumpserver/pyproject.toml \
set -ex \
&& python3 -m venv /opt/py3 \
&& pip install poetry -i ${PIP_MIRROR} \
&& poetry config virtualenvs.create false \
&& . /opt/py3/bin/activate \
&& poetry install
FROM python:3.11-slim-bullseye
ARG TARGETARCH
ENV LANG=zh_CN.UTF-8 \
PATH=/opt/py3/bin:$PATH
ARG DEPENDENCIES=" \
libjpeg-dev \
libpq-dev \
libx11-dev \
freerdp2-dev \
libxmlsec1-openssl"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
iputils-ping \
locales \
netcat-openbsd \
nmap \
openssh-client \
patch \
sshpass \
telnet \
vim \
bubblewrap \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
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 ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
&& echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc
ARG RECEPTOR_VERSION=v1.4.5
RUN set -ex \
&& wget -O /opt/receptor.tar.gz https://github.com/ansible/receptor/releases/download/${RECEPTOR_VERSION}/receptor_${RECEPTOR_VERSION/v/}_linux_${TARGETARCH}.tar.gz \
&& tar -xf /opt/receptor.tar.gz -C /usr/local/bin/ \
&& chown root:root /usr/local/bin/receptor \
&& chmod 755 /usr/local/bin/receptor \
&& rm -f /opt/receptor.tar.gz
COPY --from=stage-2 /opt/py3 /opt/py3
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
COPY --from=stage-1 /opt/jumpserver/release/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
WORKDIR /opt/jumpserver
ARG VERSION
ENV VERSION=$VERSION
VOLUME /opt/jumpserver/data
EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]

View File

@@ -1,4 +1,4 @@
FROM python:3.11-slim-bullseye AS stage-1
FROM python:3.11-slim-bullseye as stage-1
ARG TARGETARCH
ARG VERSION
@@ -87,14 +87,12 @@ ARG TOOLS=" \
default-mysql-client \
iputils-ping \
locales \
netcat-openbsd \
nmap \
openssh-client \
patch \
sshpass \
telnet \
vim \
bubblewrap \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
@@ -113,17 +111,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc
ARG RECEPTOR_VERSION=v1.4.5
RUN set -ex \
&& wget -O /opt/receptor.tar.gz https://github.com/ansible/receptor/releases/download/${RECEPTOR_VERSION}/receptor_${RECEPTOR_VERSION/v/}_linux_${TARGETARCH}.tar.gz \
&& tar -xf /opt/receptor.tar.gz -C /usr/local/bin/ \
&& chown root:root /usr/local/bin/receptor \
&& chmod 755 /usr/local/bin/receptor \
&& rm -f /opt/receptor.tar.gz
COPY --from=stage-2 /opt/py3 /opt/py3
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
COPY --from=stage-1 /opt/jumpserver/release/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
WORKDIR /opt/jumpserver

View File

@@ -1,5 +1,5 @@
ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} AS build-xpack
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack

2
GITSHA
View File

@@ -1 +1 @@
a6b5437f6a42fd8265ee188732d7abc8d829db06
c49c394b5bb011373202b31aef3f29b53a25e10d

View File

@@ -85,7 +85,7 @@ If you find a security problem, please contact us directly
- 400-052-0755
### License & Copyright
Copyright (c) 2014-2024 FIT2CLOUD Tech, Inc., All rights reserved.
Copyright (c) 2014-2022 FIT2CLOUD Tech, Inc., All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

View File

@@ -20,6 +20,7 @@ class AccountBackupPlanViewSet(OrgBulkModelViewSet):
model = AccountBackupAutomation
filterset_fields = ('name',)
search_fields = filterset_fields
ordering = ('name',)
serializer_class = serializers.AccountBackupSerializer

View File

@@ -6,12 +6,9 @@ from rest_framework.response import Response
from accounts import serializers
from accounts.const import AutomationTypes
from accounts.filters import ChangeSecretRecordFilterSet
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord
from accounts.tasks import execute_automation_record_task
from authentication.permissions import UserConfirmation, ConfirmType
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from rbac.permissions import RBACPermission
from .base import (
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
@@ -33,48 +30,29 @@ class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
filterset_class = ChangeSecretRecordFilterSet
serializer_class = serializers.ChangeSecretRecordSerializer
filterset_fields = ('asset_id', 'execution_id')
search_fields = ('asset__address',)
tp = AutomationTypes.change_secret
serializer_classes = {
'default': serializers.ChangeSecretRecordSerializer,
'secret': serializers.ChangeSecretRecordViewSecretSerializer,
}
rbac_perms = {
'execute': 'accounts.add_changesecretexecution',
'secret': 'accounts.view_changesecretrecord',
}
def get_permissions(self):
if self.action == 'secret':
self.permission_classes = [
RBACPermission,
UserConfirmation.require(ConfirmType.MFA)
]
return super().get_permissions()
def get_queryset(self):
return ChangeSecretRecord.objects.all()
@action(methods=['post'], detail=False, url_path='execute')
def execute(self, request, *args, **kwargs):
record_ids = request.data.get('record_ids')
records = self.get_queryset().filter(id__in=record_ids)
execution_count = records.values_list('execution_id', flat=True).distinct().count()
if execution_count != 1:
record_id = request.data.get('record_id')
record = self.get_queryset().filter(pk=record_id)
if not record:
return Response(
{'detail': 'Only one execution is allowed to execute'},
status=status.HTTP_400_BAD_REQUEST
{'detail': 'record not found'},
status=status.HTTP_404_NOT_FOUND
)
task = execute_automation_record_task.delay(record_ids, self.tp)
task = execute_automation_record_task.delay(record_id, self.tp)
return Response({'task': task.id}, status=status.HTTP_200_OK)
@action(methods=['get'], detail=True, url_path='secret')
def secret(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
rbac_perms = (

View File

@@ -6,7 +6,7 @@ from django.conf import settings
from rest_framework import serializers
from xlsxwriter import Workbook
from accounts.const import AccountBackupType
from accounts.const.automation import AccountBackupType
from accounts.models.automations.backup_account import AccountBackupAutomation
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
from accounts.serializers import AccountSecretSerializer

View File

@@ -13,13 +13,11 @@
login_password: "{{ jms_account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ jms_custom_become | default(False) }}"
become_method: "{{ jms_custom_become_method | default('su') }}"
become_user: "{{ jms_custom_become_user | default('') }}"
become_password: "{{ jms_custom_become_password | default('') }}"
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
register: ping_info
delegate_to: localhost
@@ -31,11 +29,11 @@
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ jms_custom_become | default(False) }}"
become_method: "{{ jms_custom_become_method | default('su') }}"
become_user: "{{ jms_custom_become_user | default('') }}"
become_password: "{{ jms_custom_become_password | default('') }}"
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
commands: "{{ params.commands }}"
@@ -56,6 +54,4 @@
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
delegate_to: localhost

View File

@@ -53,5 +53,3 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: result
failed_when: not result.is_available

View File

@@ -36,8 +36,7 @@
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
append_privs: "{{ db_name != '' | bool }}"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
ignore_errors: true
when: db_info is succeeded

View File

@@ -39,5 +39,3 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
register: result
failed_when: not result.is_available

View File

@@ -35,24 +35,12 @@
- user_info.failed
- params.groups
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed or params.modify_sudo
- params.sudo
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}"
update_password: always
ignore_errors: true
register: change_secret_result
when: account.secret_type == "password"
- name: remove jumpserver ssh key
@@ -69,9 +57,19 @@
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
register: change_secret_result
when: account.secret_type == "ssh_key"
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
@@ -87,10 +85,7 @@
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when:
- account.secret_type == "password"
- check_conn_after_change or change_secret_result.failed | default(false)
when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
@@ -100,8 +95,5 @@
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when:
- account.secret_type == "ssh_key"
- check_conn_after_change or change_secret_result.failed | default(false)
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -5,12 +5,6 @@ type:
- AIX
method: change_secret
params:
- name: modify_sudo
type: bool
label: "{{ 'Modify sudo label' | trans }}"
default: False
help_text: "{{ 'Modify params sudo help text' | trans }}"
- name: sudo
type: str
label: 'Sudo'
@@ -40,11 +34,6 @@ i18n:
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
en: 'Using Ansible module user to change account secret (DES)'
Modify params sudo help text:
zh: '如果用户存在可以修改sudo权限'
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
en: 'If the user exists, sudo permissions can be modified'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
@@ -60,11 +49,6 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
en: 'Modify sudo'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'

View File

@@ -35,24 +35,12 @@
- user_info.failed
- params.groups
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed or params.modify_sudo
- params.sudo
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
register: change_secret_result
when: account.secret_type == "password"
- name: remove jumpserver ssh key
@@ -69,9 +57,19 @@
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
register: change_secret_result
when: account.secret_type == "ssh_key"
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
@@ -87,10 +85,7 @@
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when:
- account.secret_type == "password"
- check_conn_after_change or change_secret_result.failed | default(false)
when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
@@ -100,8 +95,5 @@
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when:
- account.secret_type == "ssh_key"
- check_conn_after_change or change_secret_result.failed | default(false)
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -6,12 +6,6 @@ type:
- linux
method: change_secret
params:
- name: modify_sudo
type: bool
label: "{{ 'Modify sudo label' | trans }}"
default: False
help_text: "{{ 'Modify params sudo help text' | trans }}"
- name: sudo
type: str
label: 'Sudo'
@@ -42,11 +36,6 @@ i18n:
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
en: 'Using Ansible module user to change account secret (SHA512)'
Modify params sudo help text:
zh: '如果用户存在可以修改sudo权限'
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
en: 'If the user exists, sudo permissions can be modified'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
@@ -62,11 +51,6 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
en: 'Modify sudo'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'

View File

@@ -28,6 +28,4 @@
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
when:
- account.secret_type == "password"
- check_conn_after_change
when: account.secret_type == "password"

View File

@@ -31,7 +31,5 @@
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"
when:
- account.secret_type == "password"
- check_conn_after_change
when: account.secret_type == "password"
delegate_to: localhost

View File

@@ -7,9 +7,9 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from xlsxwriter import Workbook
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy, ChangeSecretRecordStatusChoice
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretFailedMsg
from accounts.notifications import ChangeSecretExecutionTaskMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer
from assets.const import HostTypes
from common.utils import get_logger
@@ -27,7 +27,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.record_map = self.execution.snapshot.get('record_map', {})
self.record_id = self.execution.snapshot.get('record_id')
self.secret_type = self.execution.snapshot.get('secret_type')
self.secret_strategy = self.execution.snapshot.get(
'secret_strategy', SecretStrategy.custom
@@ -123,21 +123,14 @@ class ChangeSecretManager(AccountBasePlaybookManager):
print(f'new_secret is None, account: {account}')
continue
asset_account_id = f'{asset.id}-{account.id}'
if asset_account_id not in self.record_map:
if self.record_id is None:
recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret,
)
records.append(recorder)
else:
record_id = self.record_map[asset_account_id]
try:
recorder = ChangeSecretRecord.objects.get(id=record_id)
new_secret = recorder.new_secret
except ChangeSecretRecord.DoesNotExist:
print(f"Record {record_id} not found")
continue
recorder = ChangeSecretRecord.objects.get(id=self.record_id)
self.name_recorder_mapper[h['name']] = recorder
@@ -165,43 +158,25 @@ class ChangeSecretManager(AccountBasePlaybookManager):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.success.value
recorder.status = 'success'
recorder.date_finished = timezone.now()
recorder.save()
account = recorder.account
if not account:
print("Account not found, deleted ?")
return
account.secret = recorder.new_secret
account.date_updated = timezone.now()
max_retries = 3
retry_count = 0
while retry_count < max_retries:
try:
recorder.save()
account.save(update_fields=['secret', 'version', 'date_updated'])
break
except Exception as e:
retry_count += 1
if retry_count == max_retries:
self.on_host_error(host, str(e), result)
else:
print(f'retry {retry_count} times for {host} recorder save error: {e}')
time.sleep(1)
account.save(update_fields=['secret', 'date_updated'])
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.failed.value
recorder.status = 'failed'
recorder.date_finished = timezone.now()
recorder.error = error
try:
recorder.save()
except Exception as e:
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
recorder.save()
def on_runner_failed(self, runner, e):
logger.error("Account error: ", e)
@@ -217,7 +192,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
def get_summary(recorders):
total, succeed, failed = 0, 0, 0
for recorder in recorders:
if recorder.status == ChangeSecretRecordStatusChoice.success.value:
if recorder.status == 'success':
succeed += 1
else:
failed += 1
@@ -234,35 +209,18 @@ class ChangeSecretManager(AccountBasePlaybookManager):
summary = self.get_summary(recorders)
print(summary, end='')
if self.record_map:
if self.record_id:
return
failed_recorders = [
r for r in recorders
if r.status == ChangeSecretRecordStatusChoice.failed.value
]
self.send_recorder_mail(recorders, summary)
def send_recorder_mail(self, recorders, summary):
recipients = self.execution.recipients
if not recorders or not recipients:
return
recipients = User.objects.filter(id__in=list(recipients.keys()))
if not recipients:
return
if failed_recorders:
name = self.execution.snapshot.get('name')
execution_id = str(self.execution.id)
_ids = [r.id for r in failed_recorders]
asset_account_errors = ChangeSecretRecord.objects.filter(
id__in=_ids).values_list('asset__name', 'account__username', 'error')
for user in recipients:
ChangeSecretFailedMsg(name, execution_id, user, asset_account_errors).publish()
if not recorders:
return
self.send_recorder_mail(recipients, recorders, summary)
def send_recorder_mail(self, recipients, recorders, summary):
name = self.execution.snapshot['name']
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
filename = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.xlsx')

View File

@@ -51,22 +51,14 @@ class GatherAccountsManager(AccountBasePlaybookManager):
data = self.generate_data(asset, result)
self.asset_account_info[asset] = data
@staticmethod
def get_nested_info(data, *keys):
for key in keys:
data = data.get(key, {})
if not data:
break
return data
def on_host_success(self, host, result):
info = self.get_nested_info(result, 'debug', 'res', 'info')
info = result.get('debug', {}).get('res', {}).get('info', {})
asset = self.host_asset_mapper.get(host)
if asset and info:
result = self.filter_success_result(asset.type, info)
self.collect_asset_account_info(asset, result)
else:
print(f'\033[31m Not found {host} info \033[0m\n')
logger.error(f'Not found {host} info')
def update_or_create_accounts(self):
for asset, data in self.asset_account_info.items():

View File

@@ -53,5 +53,3 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: result
failed_when: not result.is_available

View File

@@ -36,8 +36,7 @@
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
append_privs: "{{ db_name != '' | bool }}"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
ignore_errors: true
when: db_info is succeeded

View File

@@ -31,6 +31,7 @@
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
register: change_info
- name: Verify password
community.postgresql.postgresql_ping:
@@ -39,5 +40,8 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
when:
- result is succeeded
- change_info is succeeded
register: result
failed_when: not result.is_available

View File

@@ -35,24 +35,12 @@
- user_info.failed
- params.groups
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed or params.modify_sudo
- params.sudo
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}"
update_password: always
ignore_errors: true
register: change_secret_result
when: account.secret_type == "password"
- name: remove jumpserver ssh key
@@ -69,9 +57,19 @@
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
register: change_secret_result
when: account.secret_type == "ssh_key"
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
@@ -87,10 +85,7 @@
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when:
- account.secret_type == "password"
- check_conn_after_change or change_secret_result.failed | default(false)
when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
@@ -100,9 +95,6 @@
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when:
- account.secret_type == "ssh_key"
- check_conn_after_change or change_secret_result.failed | default(false)
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -5,12 +5,6 @@ type:
- AIX
method: push_account
params:
- name: modify_sudo
type: bool
label: "{{ 'Modify sudo label' | trans }}"
default: False
help_text: "{{ 'Modify params sudo help text' | trans }}"
- name: sudo
type: str
label: 'Sudo'
@@ -40,11 +34,6 @@ i18n:
ja: 'Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)'
en: 'Using Ansible module user to push account (DES)'
Modify params sudo help text:
zh: '如果用户存在可以修改sudo权限'
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
en: 'If the user exists, sudo permissions can be modified'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
@@ -60,11 +49,6 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
en: 'Modify sudo'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'

View File

@@ -35,24 +35,12 @@
- user_info.failed
- params.groups
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed or params.modify_sudo
- params.sudo
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
ignore_errors: true
register: change_secret_result
when: account.secret_type == "password"
- name: remove jumpserver ssh key
@@ -69,9 +57,19 @@
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
register: change_secret_result
when: account.secret_type == "ssh_key"
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
@@ -87,10 +85,7 @@
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when:
- account.secret_type == "password"
- check_conn_after_change or change_secret_result.failed | default(false)
when: account.secret_type == "password"
delegate_to: localhost
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
@@ -100,9 +95,6 @@
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when:
- account.secret_type == "ssh_key"
- check_conn_after_change or change_secret_result.failed | default(false)
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -6,12 +6,6 @@ type:
- linux
method: push_account
params:
- name: modify_sudo
type: bool
label: "{{ 'Modify sudo label' | trans }}"
default: False
help_text: "{{ 'Modify params sudo help text' | trans }}"
- name: sudo
type: str
label: 'Sudo'
@@ -42,11 +36,6 @@ i18n:
ja: 'Ansible user モジュールを使用してアカウントをプッシュする (sha512)'
en: 'Using Ansible module user to push account (sha512)'
Modify params sudo help text:
zh: '如果用户存在可以修改sudo权限'
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
en: 'If the user exists, sudo permissions can be modified'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
@@ -62,11 +51,6 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
en: 'Modify sudo'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'

View File

@@ -28,6 +28,4 @@
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
when:
- account.secret_type == "password"
- check_conn_after_change
when: account.secret_type == "password"

View File

@@ -31,7 +31,5 @@
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"
when:
- account.secret_type == "password"
- check_conn_after_change
when: account.secret_type == "password"
delegate_to: localhost

View File

@@ -12,13 +12,11 @@
path: "{{ user_home_dir.stdout }}"
register: home_dir
when: user_home_dir.stdout != ""
ignore_errors: yes
- name: "Rename user home directory if it exists"
ansible.builtin.command:
cmd: "mv {{ user_home_dir.stdout }} {{ user_home_dir.stdout }}.bak"
when: home_dir.stat | default(false) and user_home_dir.stdout != ""
ignore_errors: yes
- name: "Remove account"
ansible.builtin.user:

View File

@@ -4,4 +4,6 @@
- name: "Remove account"
ansible.windows.win_user:
name: "{{ account.username }}"
state: absent
state: absent
purge: yes
force: yes

View File

@@ -60,11 +60,8 @@ class RemoveAccountManager(AccountBasePlaybookManager):
if not tuple_asset_gather_account:
return
asset, gather_account = tuple_asset_gather_account
try:
Account.objects.filter(
asset_id=asset.id,
username=gather_account.username
).delete()
gather_account.delete()
except Exception as e:
print(f'\033[31m Delete account {gather_account.username} failed: {e} \033[0m\n')
Account.objects.filter(
asset_id=asset.id,
username=gather_account.username
).delete()
gather_account.delete()

View File

@@ -3,7 +3,6 @@
vars:
ansible_shell_type: sh
ansible_connection: local
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Verify account (pyfreerdp)

View File

@@ -19,5 +19,3 @@
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"

View File

@@ -16,5 +16,3 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
register: result
failed_when: not result.is_available

View File

@@ -8,7 +8,6 @@
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_timeout: 30
when: not account.become.ansible_become
- name: Verify account connectivity(Switch)
@@ -21,5 +20,4 @@
ansible_become_method: "{{ account.become.ansible_become_method }}"
ansible_become_user: "{{ account.become.ansible_become_user }}"
ansible_become_password: "{{ account.become.ansible_become_password }}"
ansible_timeout: 30
when: account.become.ansible_become

View File

@@ -9,4 +9,3 @@
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_timeout: 30

View File

@@ -76,14 +76,8 @@ class VerifyAccountManager(AccountBasePlaybookManager):
def on_host_success(self, host, result):
account = self.host_account_mapper.get(host)
try:
account.set_connectivity(Connectivity.OK)
except Exception as e:
print(f'\033[31m Update account {account.name} connectivity failed: {e} \033[0m\n')
account.set_connectivity(Connectivity.OK)
def on_host_error(self, host, error, result):
account = self.host_account_mapper.get(host)
try:
account.set_connectivity(Connectivity.ERR)
except Exception as e:
print(f'\033[31m Update account {account.name} connectivity failed: {e} \033[0m\n')
account.set_connectivity(Connectivity.ERR)

View File

@@ -15,7 +15,6 @@ class AliasAccount(TextChoices):
INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user')
ANON = '@ANON', _('Anonymous account')
SPEC = '@SPEC', _('Specified account')
@classmethod
def virtual_choices(cls):

View File

@@ -16,7 +16,7 @@ DEFAULT_PASSWORD_RULES = {
__all__ = [
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
'PushAccountActionChoice', 'AccountBackupType', 'ChangeSecretRecordStatusChoice',
'PushAccountActionChoice', 'AccountBackupType'
]
@@ -103,9 +103,3 @@ class AccountBackupType(models.TextChoices):
email = 'email', _('Email')
# 目前只支持sftp方式
object_storage = 'object_storage', _('SFTP')
class ChangeSecretRecordStatusChoice(models.TextChoices):
failed = 'failed', _('Failed')
success = 'success', _('Success')
pending = 'pending', _('Pending')

View File

@@ -5,7 +5,7 @@ from django_filters import rest_framework as drf_filters
from assets.models import Node
from common.drf.filters import BaseFilterSet
from .models import Account, GatheredAccount, ChangeSecretRecord
from .models import Account, GatheredAccount
class AccountFilterSet(BaseFilterSet):
@@ -61,13 +61,3 @@ class GatheredAccountFilterSet(BaseFilterSet):
class Meta:
model = GatheredAccount
fields = ['id', 'username']
class ChangeSecretRecordFilterSet(BaseFilterSet):
asset_name = drf_filters.CharFilter(field_name='asset__name', lookup_expr='icontains')
account_username = drf_filters.CharFilter(field_name='account__username', lookup_expr='icontains')
execution_id = drf_filters.CharFilter(field_name='execution_id', lookup_expr='exact')
class Meta:
model = ChangeSecretRecord
fields = ['id', 'status', 'asset_id', 'execution']

View File

@@ -1,8 +1,7 @@
# Generated by Django 4.1.10 on 2023-08-01 09:12
import uuid
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
@@ -21,7 +20,7 @@ class Migration(migrations.Migration):
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('alias', models.CharField(choices=[('@INPUT', 'Manual input'), ('@USER', 'Dynamic user'), ('@ANON', 'Anonymous account'), ('@SPEC', 'Specified account')], max_length=128, verbose_name='Alias')),
('alias', models.CharField(choices=[('@INPUT', 'Manual input'), ('@USER', 'Dynamic user'), ('@ANON', 'Anonymous account')], max_length=128, verbose_name='Alias')),
('secret_from_login', models.BooleanField(default=None, null=True, verbose_name='Secret from login')),
],
options={

View File

@@ -53,8 +53,7 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
on_delete=models.SET_NULL, verbose_name=_("Su from")
)
version = models.IntegerField(default=0, verbose_name=_('Version'))
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'],
verbose_name=_("historical Account"))
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'])
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
@@ -120,8 +119,7 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
return auth
auth.update(self.make_account_ansible_vars(su_from))
become_method = platform.ansible_become_method
become_method = platform.su_method if platform.su_method else 'sudo'
password = su_from.secret if become_method == 'sudo' else self.secret
auth['ansible_become'] = True
auth['ansible_become_method'] = become_method

View File

@@ -8,7 +8,7 @@ from django.db import models
from django.db.models import F
from django.utils.translation import gettext_lazy as _
from accounts.const import AccountBackupType
from accounts.const.automation import AccountBackupType
from common.const.choices import Trigger
from common.db import fields
from common.db.encoder import ModelJSONFieldEncoder

View File

@@ -2,7 +2,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from accounts.const import (
AutomationTypes, ChangeSecretRecordStatusChoice
AutomationTypes
)
from common.db import fields
from common.db.models import JMSBaseModel
@@ -40,10 +40,7 @@ class ChangeSecretRecord(JMSBaseModel):
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
status = models.CharField(
max_length=16, verbose_name=_('Status'),
default=ChangeSecretRecordStatusChoice.pending.value
)
status = models.CharField(max_length=16, default='pending', verbose_name=_('Status'))
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
class Meta:

View File

@@ -137,13 +137,16 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
else:
return None
def get_private_key_path(self, path):
@property
def private_key_path(self):
if self.secret_type != SecretType.SSH_KEY \
or not self.secret \
or not self.private_key:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(path, key_name)
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
# https://github.com/ansible/ansible-runner/issues/544
# ssh requires OpenSSH format keys to have a full ending newline.
@@ -155,12 +158,6 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
os.chmod(key_path, 0o400)
return key_path
@property
def private_key_path(self):
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
return self.get_private_key_path(tmp_dir)
def get_private_key(self):
if not self.private_key:
return None

View File

@@ -1,7 +1,6 @@
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
from accounts.models import ChangeSecretRecord
from common.tasks import send_mail_attachment_async, upload_backup_to_obj_storage
from notifications.notifications import UserMessage
from terminal.models.component.storage import ReplayStorage
@@ -99,35 +98,3 @@ class GatherAccountChangeMsg(UserMessage):
def gen_test_msg(cls):
user = User.objects.first()
return cls(user, {})
class ChangeSecretFailedMsg(UserMessage):
subject = _('Change secret or push account failed information')
def __init__(self, name, execution_id, user, asset_account_errors: list):
self.name = name
self.execution_id = execution_id
self.asset_account_errors = asset_account_errors
super().__init__(user)
def get_html_msg(self) -> dict:
context = {
'name': self.name,
'recipient': self.user,
'execution_id': self.execution_id,
'asset_account_errors': self.asset_account_errors
}
message = render_to_string('accounts/change_secret_failed_info.html', context)
return {
'subject': str(self.subject),
'message': message
}
@classmethod
def gen_test_msg(cls):
name = 'test'
user = User.objects.first()
record = ChangeSecretRecord.objects.first()
execution_id = str(record.execution_id)
return cls(name, execution_id, user, [])

View File

@@ -79,28 +79,18 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
@staticmethod
def get_template_attr_for_account(template):
# Set initial data from template
field_names = [
'name', 'username',
'secret_type', 'secret',
'privileged', 'is_active'
'name', 'username', 'secret',
'secret_type', 'privileged', 'is_active'
]
field_map = {
'push_params': 'params',
'auto_push': 'push_now'
}
field_names.extend(field_map.keys())
attrs = {}
for name in field_names:
value = getattr(template, name, None)
if value is None:
continue
attr_name = field_map.get(name, name)
attrs[attr_name] = value
attrs[name] = value
attrs['secret'] = template.get_secret()
return attrs
@@ -183,8 +173,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
params = validated_data.pop('params', None)
self.clean_auth_fields(validated_data)
instance, stat = self.do_create(validated_data)
if instance.source == Source.LOCAL:
self.push_account_if_need(instance, push_now, params, stat)
self.push_account_if_need(instance, push_now, params, stat)
return instance
def update(self, instance, validated_data):
@@ -286,8 +275,8 @@ class AssetAccountBulkSerializer(
fields = [
'name', 'username', 'secret', 'secret_type', 'passphrase',
'privileged', 'is_active', 'comment', 'template',
'on_invalid', 'push_now', 'params', 'assets',
'su_from_username', 'source', 'source_id',
'on_invalid', 'push_now', 'assets', 'su_from_username',
'source', 'source_id',
]
extra_kwargs = {
'name': {'required': False},
@@ -425,23 +414,16 @@ class AssetAccountBulkSerializer(
return results
@staticmethod
def push_accounts_if_need(results, push_now, params):
def push_accounts_if_need(results, push_now):
if not push_now:
return
account_ids = [v['instance'] for v in results if v.get('instance')]
accounts = Account.objects.filter(id__in=account_ids, source=Source.LOCAL)
if not accounts.exists():
return
account_ids = [str(_id) for _id in accounts.values_list('id', flat=True)]
push_accounts_to_assets_task.delay(account_ids, params)
accounts = [str(v['instance']) for v in results if v.get('instance')]
push_accounts_to_assets_task.delay(accounts)
def create(self, validated_data):
params = validated_data.pop('params', None)
push_now = validated_data.pop('push_now', False)
results = self.perform_bulk_create(validated_data)
self.push_accounts_if_need(results, push_now, params)
self.push_accounts_if_need(results, push_now)
for res in results:
res['asset'] = str(res['asset'])
return results
@@ -449,11 +431,8 @@ class AssetAccountBulkSerializer(
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
class Meta(AccountSerializer.Meta):
fields = AccountSerializer.Meta.fields + ['spec_info']
extra_kwargs = {
**AccountSerializer.Meta.extra_kwargs,
'secret': {'write_only': False},
'spec_info': {'label': _('Spec info')},
}

View File

@@ -67,14 +67,15 @@ class BaseAccountSerializer(AuthValidateMixin, ResourceLabelsMixin, BulkOrgResou
fields_mini = ['id', 'name', 'username']
fields_small = fields_mini + [
'secret_type', 'secret', 'passphrase',
'privileged', 'is_active',
'privileged', 'is_active', 'spec_info',
]
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
fields = fields_small + fields_other + ['labels']
read_only_fields = [
'date_verified', 'created_by', 'date_created',
'spec_info', 'date_verified', 'created_by', 'date_created',
]
extra_kwargs = {
'spec_info': {'label': _('Spec info')},
'username': {'help_text': _(
"Tip: If no username is required for authentication, fill in `null`, "
"If AD account, like `username@domain`"

View File

@@ -35,7 +35,6 @@ class AccountTemplateSerializer(BaseAccountSerializer):
'su_from'
]
extra_kwargs = {
**BaseAccountSerializer.Meta.extra_kwargs,
'secret_strategy': {'help_text': _('Secret generation strategy for account creation')},
'auto_push': {'help_text': _('Whether to automatically push the account to the asset')},
'platforms': {
@@ -65,9 +64,6 @@ class AccountTemplateSerializer(BaseAccountSerializer):
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
class Meta(AccountTemplateSerializer.Meta):
fields = AccountTemplateSerializer.Meta.fields + ['spec_info']
extra_kwargs = {
**AccountTemplateSerializer.Meta.extra_kwargs,
'secret': {'write_only': False},
'spec_info': {'label': _('Spec info')},
}

View File

@@ -21,7 +21,6 @@ __all__ = [
class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
assets = ObjectRelatedField(many=True, required=False, queryset=Asset.objects, label=_('Assets'))
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
is_periodic = serializers.BooleanField(default=False, required=False, label=_("Periodic perform"))
class Meta:
read_only_fields = [

View File

@@ -4,8 +4,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import (
AutomationTypes, SecretType, SecretStrategy,
SSHKeyStrategy, ChangeSecretRecordStatusChoice
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
)
from accounts.models import (
Account, ChangeSecretAutomation,
@@ -22,7 +21,6 @@ logger = get_logger(__file__)
__all__ = [
'ChangeSecretAutomationSerializer',
'ChangeSecretRecordSerializer',
'ChangeSecretRecordViewSecretSerializer',
'ChangeSecretRecordBackUpSerializer',
'ChangeSecretUpdateAssetSerializer',
'ChangeSecretUpdateNodeSerializer',
@@ -106,10 +104,7 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
class ChangeSecretRecordSerializer(serializers.ModelSerializer):
is_success = serializers.SerializerMethodField(label=_('Is success'))
asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset'))
account = ObjectRelatedField(
queryset=Account.objects, label=_('Account'),
attrs=("id", "name", "username")
)
account = ObjectRelatedField(queryset=Account.objects, label=_('Account'))
execution = ObjectRelatedField(
queryset=AutomationExecution.objects, label=_('Automation task execution')
)
@@ -124,16 +119,7 @@ class ChangeSecretRecordSerializer(serializers.ModelSerializer):
@staticmethod
def get_is_success(obj):
return obj.status == ChangeSecretRecordStatusChoice.success.value
class ChangeSecretRecordViewSecretSerializer(serializers.ModelSerializer):
class Meta:
model = ChangeSecretRecord
fields = [
'id', 'old_secret', 'new_secret',
]
read_only_fields = fields
return obj.status == 'success'
class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
@@ -159,7 +145,7 @@ class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
@staticmethod
def get_is_success(obj):
if obj.status == ChangeSecretRecordStatusChoice.success.value:
if obj.status == 'success':
return _("Success")
return _("Failed")

View File

@@ -6,7 +6,6 @@ from django.dispatch import receiver
from django.utils.translation import gettext_noop
from accounts.backends import vault_client
from accounts.const import Source
from audits.const import ActivityChoices
from audits.signal_handlers import create_activities
from common.decorators import merge_delay_run
@@ -33,7 +32,7 @@ def push_accounts_if_need(accounts=()):
template_accounts = defaultdict(list)
for ac in accounts:
# 再强调一次吧
if ac.source != Source.TEMPLATE:
if ac.source != 'template':
continue
template_accounts[ac.source_id].append(ac)
@@ -62,7 +61,7 @@ def create_accounts_activities(account, action='create'):
@receiver(post_save, sender=Account)
def on_account_create_by_template(sender, instance, created=False, **kwargs):
if not created:
if not created or instance.source != 'template':
return
push_accounts_if_need.delay(accounts=(instance,))
create_accounts_activities(instance, action='create')

View File

@@ -36,14 +36,14 @@ def execute_account_automation_task(pid, trigger, tp):
instance.execute(trigger)
def record_task_activity_callback(self, record_ids, *args, **kwargs):
def record_task_activity_callback(self, record_id, *args, **kwargs):
from accounts.models import ChangeSecretRecord
with tmp_to_root_org():
records = ChangeSecretRecord.objects.filter(id__in=record_ids)
if not records:
record = get_object_or_none(ChangeSecretRecord, id=record_id)
if not record:
return
resource_ids = [str(i.id) for i in records]
org_id = records[0].execution.org_id
resource_ids = [record.id]
org_id = record.execution.org_id
return resource_ids, org_id
@@ -51,26 +51,22 @@ def record_task_activity_callback(self, record_ids, *args, **kwargs):
queue='ansible', verbose_name=_('Execute automation record'),
activity_callback=record_task_activity_callback
)
def execute_automation_record_task(record_ids, tp):
def execute_automation_record_task(record_id, tp):
from accounts.models import ChangeSecretRecord
task_name = gettext_noop('Execute automation record')
with tmp_to_root_org():
records = ChangeSecretRecord.objects.filter(id__in=record_ids)
if not records:
logger.error('No automation record found: {}'.format(record_ids))
instance = get_object_or_none(ChangeSecretRecord, pk=record_id)
if not instance:
logger.error("No automation record found: {}".format(record_id))
return
record = records[0]
record_map = {f'{record.asset_id}-{record.account_id}': str(record.id) for record in records}
task_name = gettext_noop('Execute automation record')
task_snapshot = {
'secret': instance.new_secret,
'secret_type': instance.execution.snapshot.get('secret_type'),
'accounts': [str(instance.account_id)],
'assets': [str(instance.asset_id)],
'params': {},
'record_map': record_map,
'secret': record.new_secret,
'secret_type': record.execution.snapshot.get('secret_type'),
'assets': [str(instance.asset_id) for instance in records],
'accounts': [str(instance.account_id) for instance in records],
'record_id': record_id,
}
with tmp_to_org(record.execution.org_id):
with tmp_to_org(instance.execution.org_id):
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)

View File

@@ -55,7 +55,7 @@ def clean_historical_accounts():
history_model = Account.history.model
history_id_mapper = defaultdict(list)
ids = history_model.objects.values('id').annotate(count=Count('id')) \
ids = history_model.objects.values('id').annotate(count=Count('id', distinct=True)) \
.filter(count__gte=limit).values_list('id', flat=True)
if not ids:

View File

@@ -29,8 +29,7 @@ def template_sync_related_accounts(template_id, user_id=None):
name = template.name
username = template.username
secret_type = template.secret_type
print(
f'\033[32m>>> 开始同步模板名称、用户名、密钥类型到相关联的账号 ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})')
print(f'\033[32m>>> 开始同步模版名称、用户名、密钥类型到相关联的账号 ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})')
with tmp_to_org(org_id):
for account in accounts:
account.name = name

View File

@@ -1,10 +1,10 @@
{% load i18n %}
<h3>{% trans 'Gather account change information' %}</h3>
<table style="width: 100%; border-collapse: collapse; max-width: 100%; text-align: left; margin-top: 20px;">
<caption></caption>
<tr style="background-color: #f2f2f2;">
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Asset' %}</th>
<th style="border: 1px solid #ddd; padding: 10px; font-weight: bold;">{% trans 'Asset' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Added account' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Deleted account' %}</th>
</tr>

View File

@@ -1,36 +0,0 @@
{% load i18n %}
<h3>{% trans 'Task name' %}: {{ name }}</h3>
<h3>{% trans 'Task execution id' %}: {{ execution_id }}</h3>
<p>{% trans 'Respectful' %} {{ recipient }}</p>
<p>{% trans 'Hello! The following is the failure of changing the password of your assets or pushing the account. Please check and handle it in time.' %}</p>
<table style="width: 100%; border-collapse: collapse; max-width: 100%; text-align: left; margin-top: 20px;">
<caption></caption>
<thead>
<tr style="background-color: #f2f2f2;">
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Asset' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Account' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Error' %}</th>
</tr>
</thead>
<tbody>
{% for asset_name, account_username, error in asset_account_errors %}
<tr>
<td style="border: 1px solid #ddd; padding: 10px;">{{ asset_name }}</td>
<td style="border: 1px solid #ddd; padding: 10px;">{{ account_username }}</td>
<td style="border: 1px solid #ddd; padding: 10px;">
<div style="
max-width: 90%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;"
title="{{ error }}"
>
{{ error }}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -2,11 +2,10 @@ import copy
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import SecretType, DEFAULT_PASSWORD_RULES
from common.utils import (
validate_ssh_private_key, parse_ssh_private_key_str, ssh_key_gen,
random_string
)
from common.utils import ssh_key_gen, random_string
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
class SecretGenerator:

View File

@@ -1,7 +1,5 @@
# coding: utf-8
#
from urllib.parse import urlparse
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
@@ -10,7 +8,7 @@ from common.utils.ip import is_ip_address, is_ip_network, is_ip_segment
logger = get_logger(__file__)
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text', 'address_validator']
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text']
def ip_group_child_validator(ip_group_child):
@@ -23,19 +21,6 @@ def ip_group_child_validator(ip_group_child):
raise serializers.ValidationError(error)
def address_validator(value):
parsed = urlparse(value)
is_basic_url = parsed.scheme in ('http', 'https') and parsed.netloc
is_valid = value == '*' \
or is_ip_address(value) \
or is_ip_network(value) \
or is_ip_segment(value) \
or is_basic_url
if not is_valid:
error = _('address invalid: `{}`').format(value)
raise serializers.ValidationError(error)
ip_group_help_text = _(
'With * indicating a match all. '
'Such as: '

View File

@@ -32,7 +32,6 @@ __all__ = [
class AssetFilterSet(BaseFilterSet):
platform = django_filters.CharFilter(method='filter_platform')
exclude_platform = django_filters.CharFilter(field_name="platform__name", lookup_expr='exact', exclude=True)
domain = django_filters.CharFilter(method='filter_domain')
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
@@ -93,6 +92,7 @@ class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet):
model = Asset
filterset_class = AssetFilterSet
search_fields = ("name", "address", "comment")
ordering = ('name',)
ordering_fields = ('name', 'address', 'connectivity', 'platform', 'date_updated', 'date_created')
serializer_classes = (
("default", serializers.AssetSerializer),
@@ -292,7 +292,6 @@ class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
def check_permissions(self, request):
action_perm_require = {
"refresh": "assets.refresh_assethardwareinfo",
"test": "assets.test_assetconnectivity",
}
_action = request.data.get("action")
perm_required = action_perm_require.get(_action)

View File

@@ -19,6 +19,7 @@ class DomainViewSet(OrgBulkModelViewSet):
model = Domain
filterset_fields = ("name",)
search_fields = filterset_fields
ordering = ('name',)
serializer_classes = {
'default': serializers.DomainSerializer,
'list': serializers.DomainListSerializer,
@@ -29,10 +30,6 @@ class DomainViewSet(OrgBulkModelViewSet):
return serializers.DomainWithGatewaySerializer
return super().get_serializer_class()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
class GatewayViewSet(HostViewSet):
perm_model = Gateway

View File

@@ -22,7 +22,6 @@ from orgs.utils import current_org
from rbac.permissions import RBACPermission
from .. import serializers
from ..models import Node
from ..signal_handlers import update_nodes_assets_amount
from ..tasks import (
update_node_assets_hardware_info_manual,
test_node_assets_connectivity_manual,
@@ -95,7 +94,6 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
children = Node.objects.filter(id__in=node_ids)
for node in children:
node.parent = instance
update_nodes_assets_amount.delay(ttl=5, node_ids=(instance.id,))
return Response("OK")

View File

@@ -21,7 +21,6 @@ class AssetPlatformViewSet(JMSModelViewSet):
}
filterset_fields = ['name', 'category', 'type']
search_fields = ['name']
ordering = ['-internal', 'name']
rbac_perms = {
'categories': 'assets.view_platform',
'type_constraints': 'assets.view_platform',

View File

@@ -39,16 +39,16 @@ class NodeChildrenApi(generics.ListCreateAPIView):
self.instance = self.get_object()
def perform_create(self, serializer):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if value:
children = self.instance.get_children()
if children.filter(value=value).exists():
raise JMSException(_('The same level node name cannot be the same'))
else:
value = self.instance.get_next_child_preset_name()
with NodeAddChildrenLock(self.instance):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if value:
children = self.instance.get_children()
if children.filter(value=value).exists():
raise JMSException(_('The same level node name cannot be the same'))
else:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value
node._full_value = node.value
@@ -126,7 +126,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not self.instance or not include_assets:
return Asset.objects.none()
if not self.request.GET.get('search') and self.instance.is_org_root():
if self.instance.is_org_root():
return Asset.objects.none()
if query_all:
assets = self.instance.get_all_assets()

View File

@@ -11,10 +11,8 @@ from django.utils.translation import gettext as _
from sshtunnel import SSHTunnelForwarder
from assets.automations.methods import platform_automation_methods
from common.db.utils import safe_db_connection
from common.utils import get_logger, lazyproperty, is_openssh_format_key, ssh_pubkey_gen
from ops.ansible import JMSInventory, DefaultCallback, SuperPlaybookRunner
from ops.ansible.interface import interface
from ops.ansible import JMSInventory, SuperPlaybookRunner, DefaultCallback
logger = get_logger(__name__)
@@ -38,7 +36,7 @@ class SSHTunnelManager:
info = self.file_to_json(runner.inventory)
servers, not_valid = [], []
for k, host in info['all']['hosts'].items():
jms_asset, jms_gateway = host.get('jms_asset'), host.get('jms_gateway')
jms_asset, jms_gateway = host.get('jms_asset'), host.get('gateway')
if not jms_gateway:
continue
try:
@@ -56,9 +54,7 @@ class SSHTunnelManager:
not_valid.append(k)
else:
local_bind_port = server.local_bind_port
host['ansible_host'] = jms_asset['address'] = host[
'login_host'] = interface.get_gateway_proxy_host()
host['ansible_host'] = jms_asset['address'] = host['login_host'] = '127.0.0.1'
host['ansible_port'] = jms_asset['port'] = host['login_port'] = local_bind_port
servers.append(server)
@@ -114,7 +110,11 @@ class BasePlaybookManager:
if not data:
data = automation_params.get(method_id, {})
params = serializer(data).data
return params
return {
field_name: automation_params.get(field_name, '')
if not params[field_name] else params[field_name]
for field_name in params
}
@property
def platform_automation_methods(self):
@@ -188,7 +188,6 @@ class BasePlaybookManager:
host['error'] = _('{} disabled'.format(self.__class__.method_type()))
return host
host['check_conn_after_change'] = settings.CHECK_CONN_AFTER_CHANGE
host = self.convert_cert_to_file(host, kwargs.get('path_dir'))
host['params'] = self.get_params(automation, method_type)
return host
@@ -298,16 +297,12 @@ class BasePlaybookManager:
for host in hosts:
result = cb.host_results.get(host)
if state == 'ok':
self.on_host_success(host, result.get('ok', ''))
self.on_host_success(host, result)
elif state == 'skipped':
pass
else:
error = hosts.get(host)
self.on_host_error(
host, error,
result.get('failures', '')
or result.get('dark', '')
)
self.on_host_error(host, error, result)
def on_runner_failed(self, runner, e):
print("Runner failed: {} {}".format(e, self))
@@ -319,7 +314,7 @@ class BasePlaybookManager:
def delete_runtime_dir(self):
if settings.DEBUG_DEV:
return
shutil.rmtree(self.runtime_dir, ignore_errors=True)
shutil.rmtree(self.runtime_dir)
def run(self, *args, **kwargs):
print(">>> 任务准备阶段\n")
@@ -338,10 +333,8 @@ class BasePlaybookManager:
ssh_tunnel = SSHTunnelManager()
ssh_tunnel.local_gateway_prepare(runner)
try:
kwargs.update({"clean_workspace": False})
cb = runner.run(**kwargs)
with safe_db_connection():
self.on_runner_success(runner, cb)
self.on_runner_success(runner, cb)
except Exception as e:
self.on_runner_failed(runner, e)
finally:

View File

@@ -1,5 +1,3 @@
from collections import Counter
__all__ = ['FormatAssetInfo']
@@ -9,28 +7,13 @@ class FormatAssetInfo:
self.tp = tp
@staticmethod
def get_cpu_model_count(cpus):
try:
if len(cpus) % 3 == 0:
step = 3
models = [cpus[i + 2] for i in range(0, len(cpus), step)]
elif len(cpus) % 2 == 0:
step = 2
models = [cpus[i + 1] for i in range(0, len(cpus), step)]
else:
raise ValueError("CPU list format not recognized")
model_counts = Counter(models)
result = ', '.join([f"{model} x{count}" for model, count in model_counts.items()])
except Exception as e:
print(f"Error processing CPU model list: {e}")
result = ''
return result
def posix_format(self, info):
cpus = self.get_cpu_model_count(info.get('cpu_model', []))
info['cpu_model'] = cpus
def posix_format(info):
for cpu_model in info.get('cpu_model', []):
if cpu_model.endswith('GHz') or cpu_model.startswith("Intel"):
break
else:
cpu_model = ''
info['cpu_model'] = cpu_model[:48]
info['cpu_count'] = info.get('cpu_count', 0)
return info

View File

@@ -3,7 +3,6 @@
vars:
ansible_shell_type: sh
ansible_connection: local
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test asset connection (pyfreerdp)

View File

@@ -14,11 +14,8 @@
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ jms_custom_become | default(False) }}"
become_method: "{{ jms_custom_become_method | default('su') }}"
become_user: "{{ jms_custom_become_user | default('') }}"
become_password: "{{ jms_custom_become_password | default('') }}"
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"

View File

@@ -1,11 +0,0 @@
- hosts: custom
gather_facts: no
vars:
ansible_connection: local
ansible_shell_type: sh
tasks:
- name: Test asset connection (telnet)
telnet_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"

View File

@@ -1,16 +0,0 @@
id: ping_by_telnet
name: "{{ 'Ping by telnet' | trans }}"
category:
- device
- host
type:
- all
method: ping
protocol: telnet
priority: 50
i18n:
Ping by telnet:
zh: '使用 Python 模块 telnet 测试主机可连接性'
en: 'Ping by telnet module'
ja: 'Pythonモジュールtelnetを使用したホスト接続性のテスト'

View File

@@ -16,5 +16,3 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: result
failed_when: not result.is_available

View File

@@ -1,7 +1,5 @@
- hosts: demo
gather_facts: no
vars:
ansible_timeout: 30
tasks:
- name: Posix ping
ansible.builtin.ping:

View File

@@ -1,7 +1,5 @@
- hosts: windows
gather_facts: no
vars:
ansible_timeout: 30
tasks:
- name: Refresh connection
ansible.builtin.meta: reset_connection

View File

@@ -25,22 +25,14 @@ class PingManager(BasePlaybookManager):
def on_host_success(self, host, result):
asset, account = self.host_asset_and_account_mapper.get(host)
try:
asset.set_connectivity(Connectivity.OK)
if not account:
return
account.set_connectivity(Connectivity.OK)
except Exception as e:
print(f'\033[31m Update account {account.name} or '
f'update asset {asset.name} connectivity failed: {e} \033[0m\n')
asset.set_connectivity(Connectivity.OK)
if not account:
return
account.set_connectivity(Connectivity.OK)
def on_host_error(self, host, error, result):
asset, account = self.host_asset_and_account_mapper.get(host)
try:
asset.set_connectivity(Connectivity.ERR)
if not account:
return
account.set_connectivity(Connectivity.ERR)
except Exception as e:
print(f'\033[31m Update account {account.name} or '
f'update asset {asset.name} connectivity failed: {e} \033[0m\n')
asset.set_connectivity(Connectivity.ERR)
if not account:
return
account.set_connectivity(Connectivity.ERR)

View File

@@ -92,26 +92,18 @@ class PingGatewayManager:
@staticmethod
def on_host_success(gateway, account):
print('\033[32m {} -> {}\033[0m\n'.format(gateway, account))
try:
gateway.set_connectivity(Connectivity.OK)
if not account:
return
account.set_connectivity(Connectivity.OK)
except Exception as e:
print(f'\033[31m Update account {account.name} or '
f'update asset {gateway.name} connectivity failed: {e} \033[0m\n')
gateway.set_connectivity(Connectivity.OK)
if not account:
return
account.set_connectivity(Connectivity.OK)
@staticmethod
def on_host_error(gateway, account, error):
print('\033[31m {} -> {} 原因: {} \033[0m\n'.format(gateway, account, error))
try:
gateway.set_connectivity(Connectivity.ERR)
if not account:
return
account.set_connectivity(Connectivity.ERR)
except Exception as e:
print(f'\033[31m Update account {account.name} or '
f'update asset {gateway.name} connectivity failed: {e} \033[0m\n')
gateway.set_connectivity(Connectivity.ERR)
if not account:
return
account.set_connectivity(Connectivity.ERR)
@staticmethod
def before_runner_start():

View File

@@ -2,6 +2,5 @@ from .automation import *
from .base import *
from .category import *
from .host import *
from .platform import *
from .protocol import *
from .types import *

View File

@@ -8,7 +8,6 @@ class DatabaseTypes(BaseType):
ORACLE = 'oracle', 'Oracle'
SQLSERVER = 'sqlserver', 'SQLServer'
DB2 = 'db2', 'DB2'
DAMENG = 'dameng', 'Dameng'
CLICKHOUSE = 'clickhouse', 'ClickHouse'
MONGODB = 'mongodb', 'MongoDB'
REDIS = 'redis', 'Redis'
@@ -37,7 +36,6 @@ class DatabaseTypes(BaseType):
'verify_account_enabled': True,
'change_secret_enabled': True,
'push_account_enabled': True,
'remove_account_enabled': True,
},
cls.REDIS: {
'ansible_enabled': False,
@@ -57,15 +55,6 @@ class DatabaseTypes(BaseType):
'change_secret_enabled': False,
'push_account_enabled': False,
},
cls.DAMENG: {
'ansible_enabled': False,
'ping_enabled': False,
'gather_facts_enabled': False,
'gather_accounts_enabled': False,
'verify_account_enabled': False,
'change_secret_enabled': False,
'push_account_enabled': False,
},
cls.CLICKHOUSE: {
'ansible_enabled': False,
'ping_enabled': False,
@@ -95,7 +84,6 @@ class DatabaseTypes(BaseType):
cls.ORACLE: [{'name': 'Oracle'}],
cls.SQLSERVER: [{'name': 'SQLServer'}],
cls.DB2: [{'name': 'DB2'}],
cls.DAMENG: [{'name': 'Dameng'}],
cls.CLICKHOUSE: [{'name': 'ClickHouse'}],
cls.MONGODB: [{'name': 'MongoDB'}],
cls.REDIS: [

View File

@@ -19,7 +19,7 @@ class HostTypes(BaseType):
'charset': 'utf-8', # default
'domain_enabled': True,
'su_enabled': True,
'su_methods': ['sudo', 'su', 'only_sudo', 'only_su'],
'su_methods': ['sudo', 'su'],
},
cls.WINDOWS: {
'su_enabled': False,
@@ -53,8 +53,7 @@ class HostTypes(BaseType):
'gather_accounts_enabled': True,
'verify_account_enabled': True,
'change_secret_enabled': True,
'push_account_enabled': True,
'remove_account_enabled': True,
'push_account_enabled': True
},
cls.WINDOWS: {
'ansible_config': {

View File

@@ -1,11 +0,0 @@
from django.db.models import TextChoices
class SuMethodChoices(TextChoices):
sudo = "sudo", "sudo su -"
su = "su", "su - "
only_sudo = "only_sudo", "sudo su"
only_su = "only_su", "su"
enable = "enable", "enable"
super = "super", "super 15"
super_level = "super_level", "super level 15"

View File

@@ -23,7 +23,6 @@ class Protocol(ChoicesMixin, models.TextChoices):
postgresql = 'postgresql', 'PostgreSQL'
sqlserver = 'sqlserver', 'SQLServer'
db2 = 'db2', 'DB2'
dameng = 'dameng', 'Dameng'
clickhouse = 'clickhouse', 'ClickHouse'
redis = 'redis', 'Redis'
mongodb = 'mongodb', 'MongoDB'
@@ -39,14 +38,6 @@ class Protocol(ChoicesMixin, models.TextChoices):
cls.ssh: {
'port': 22,
'secret_types': ['password', 'ssh_key'],
'setting': {
'old_ssh_version': {
'type': 'bool',
'default': False,
'label': _('Old SSH version'),
'help_text': _('Old SSH version like openssh 5.x or 6.x')
}
}
},
cls.sftp: {
'port': 22,
@@ -186,12 +177,6 @@ class Protocol(ChoicesMixin, models.TextChoices):
'secret_types': ['password'],
'xpack': True,
},
cls.dameng: {
'port': 5236,
'required': True,
'secret_types': ['password'],
'xpack': True,
},
cls.clickhouse: {
'port': 9000,
'required': True,
@@ -202,20 +187,6 @@ class Protocol(ChoicesMixin, models.TextChoices):
'port': 27017,
'required': True,
'secret_types': ['password'],
'setting': {
'auth_source': {
'type': 'str',
'default': 'admin',
'label': _('Auth source'),
'help_text': _('The database to authenticate against')
},
'connection_options': {
'type': 'str',
'default': '',
'label': _('Connection options'),
'help_text': _('The connection specific options eg. retryWrites=false&retryReads=false')
}
}
},
cls.redis: {
'port': 6379,
@@ -295,17 +266,22 @@ class Protocol(ChoicesMixin, models.TextChoices):
'setting': {
'api_mode': {
'type': 'choice',
'default': 'gpt-4o-mini',
'default': 'gpt-3.5-turbo',
'label': _('API mode'),
'choices': [
('gpt-4o-mini', 'GPT-4o-mini'),
('gpt-4o', 'GPT-4o'),
('gpt-4-turbo', 'GPT-4 Turbo'),
('gpt-3.5-turbo', 'GPT-3.5 Turbo'),
('gpt-3.5-turbo-16k', 'GPT-3.5 Turbo 16K'),
]
}
}
}
}
if settings.XPACK_LICENSE_IS_VALID:
choices = protocols[cls.chatgpt]['setting']['api_mode']['choices']
choices.extend([
('gpt-4', 'GPT-4'),
('gpt-4-32k', 'GPT-4 32K'),
])
return protocols
@classmethod

View File

@@ -1,7 +1,6 @@
# Generated by Django 3.2.12 on 2022-07-11 06:13
import time
import math
from django.utils import timezone
from itertools import groupby
from django.db import migrations
@@ -41,13 +40,9 @@ def migrate_asset_accounts(apps, schema_editor):
if system_user:
# 更新一次系统用户的认证属性
account_values.update({attr: getattr(system_user, attr, '') for attr in all_attrs})
account_values['created_by'] = str(system_user.id)
account_values['privileged'] = system_user.type == 'admin' \
or system_user.username in ['root', 'Administrator']
if system_user.su_enabled and system_user.su_from:
created_by = f'{str(system_user.id)}::{str(system_user.su_from.username)}'
else:
created_by = str(system_user.id)
account_values['created_by'] = created_by
auth_book_auth = {attr: getattr(auth_book, attr, '') for attr in all_attrs if getattr(auth_book, attr, '')}
# 最终优先使用 auth_book 的认证属性
@@ -122,70 +117,6 @@ def migrate_asset_accounts(apps, schema_editor):
print("\t - histories: {}".format(len(accounts_to_history)))
def update_asset_accounts_su_from(apps, schema_editor):
# Update accounts su_from
print("\n\tStart update asset accounts su_from field")
account_model = apps.get_model('accounts', 'Account')
platform_model = apps.get_model('assets', 'Platform')
asset_model = apps.get_model('assets', 'Asset')
platform_ids = list(platform_model.objects.filter(su_enabled=True).values_list('id', flat=True))
count = 0
step_size = 1000
count_account = 0
while True:
start = time.time()
asset_ids = asset_model.objects \
.filter(platform_id__in=platform_ids) \
.values_list('id', flat=True)[count:count + step_size]
asset_ids = list(asset_ids)
if not asset_ids:
break
count += len(asset_ids)
accounts = list(account_model.objects.filter(asset_id__in=asset_ids))
# {asset_id_account_username: account.id}}
asset_accounts_mapper = {}
for a in accounts:
try:
k = f'{a.asset_id}_{a.username}'
asset_accounts_mapper[k] = str(a.id)
except Exception as e:
pass
update_accounts = []
for a in accounts:
try:
if not a.created_by:
continue
created_by_list = a.created_by.split('::')
if len(created_by_list) != 2:
continue
su_from_username = created_by_list[1]
if not su_from_username:
continue
k = f'{a.asset_id}_{su_from_username}'
su_from_id = asset_accounts_mapper.get(k)
if not su_from_id:
continue
a.su_from_id = su_from_id
update_accounts.append(a)
except Exception as e:
pass
count_account += len(update_accounts)
log_msg = "\t - [{}]: Update accounts su_from: {}-{} {:.2f}s"
try:
account_model.objects.bulk_update(update_accounts, ['su_from_id'])
except Exception as e:
status = 'Failed'
else:
status = 'Success'
print(log_msg.format(status, count_account - len(update_accounts), count_account, time.time() - start))
def migrate_db_accounts(apps, schema_editor):
app_perm_model = apps.get_model('perms', 'ApplicationPermission')
account_model = apps.get_model('accounts', 'Account')
@@ -265,6 +196,5 @@ class Migration(migrations.Migration):
operations = [
migrations.RunPython(migrate_asset_accounts),
migrations.RunPython(update_asset_accounts_su_from),
migrations.RunPython(migrate_db_accounts),
]

View File

@@ -1,11 +1,9 @@
# Generated by Django 3.2.16 on 2022-12-30 08:08
import uuid
import django.db.models.deletion
from django.db import migrations, models
import common.db.fields
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
@@ -55,7 +53,7 @@ class Migration(migrations.Migration):
],
options={
'verbose_name': 'Automation task execution',
'ordering': ('org_id', '-date_start',),
'ordering': ('-date_start',),
},
),
migrations.CreateModel(

View File

@@ -1,31 +0,0 @@
# Generated by Django 4.1.10 on 2023-10-07 06:37
from django.db import migrations
def add_dameng_platform(apps, schema_editor):
platform_cls = apps.get_model('assets', 'Platform')
automation_cls = apps.get_model('assets', 'PlatformAutomation')
platform, _ = platform_cls.objects.update_or_create(
name='Dameng', defaults={
'name': 'Dameng', 'category': 'database',
'internal': True, 'type': 'dameng',
'domain_enabled': True, 'su_enabled': False,
'su_method': None, 'comment': 'Dameng', 'created_by': 'System',
'updated_by': 'System', 'custom_fields': []
}
)
platform.protocols.update_or_create(name='dameng', defaults={
'name': 'dameng', 'port': 5236, 'primary': True, 'setting': {}
})
automation_cls.objects.update_or_create(platform=platform, defaults={'ansible_enabled': False})
class Migration(migrations.Migration):
dependencies = [
('assets', '0127_automation_remove_account'),
]
operations = [
migrations.RunPython(add_dameng_platform)
]

Some files were not shown because too many files have changed in this diff Show More